From e420c9665d2f52edf92b38c884b5b8f332f6128b Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Tue, 22 Aug 2017 17:14:13 -0700 Subject: Switch SubtitleView to use new linespacing API This allows expanding linespacing based on actual fallback fonts used. Bug: 28963299 Test: none Change-Id: If3fe56e45ca77c28890bb51da86941483f620384 --- src/com/android/exoplayer/text/SubtitleView.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/com/android/exoplayer/text/SubtitleView.java b/src/com/android/exoplayer/text/SubtitleView.java index b1161f22..7cde69a1 100644 --- a/src/com/android/exoplayer/text/SubtitleView.java +++ b/src/com/android/exoplayer/text/SubtitleView.java @@ -252,8 +252,11 @@ public class SubtitleView extends View { mHasMeasurements = true; mLastMeasuredWidth = maxWidth; - mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, - mSpacingMult, mSpacingAdd, true); + mLayout = StaticLayout.Builder.obtain(mText, 0, mText.length(), mTextPaint, maxWidth) + .setAlignment(mAlignment) + .setLineSpacing(mSpacingAdd, mSpacingMult) + .setUseLineSpacingFromFallbacks(true) + .build(); return true; } -- cgit v1.2.3 From 633eb826b8c97731dfc5ef12c7bf78a63734275d Mon Sep 17 00:00:00 2001 From: Nick Chalko Date: Tue, 3 Oct 2017 10:16:37 -0700 Subject: Sync to match Live Channels 1.15(ncis) aka ub-tv-dev at a73a150bb7d0d1ce867ef980c6ac8411899d40ad Bug: 64021596 Change-Id: I7c544fd15e2c58784f8babc31877ad0dfeebb4c0 --- Android.mk | 25 +- AndroidManifest.xml | 65 +- assets/licenses.html | 298 -- assets/whitelist.policy | 39 + common/Android.mk | 5 +- common/OWNERS | 2 + common/res/drawable/setup_selector_background.xml | 3 +- common/res/layout/fragment_setup_multi_pane.xml | 25 +- common/res/values-af/strings.xml | 5 +- common/res/values-am/strings.xml | 5 +- common/res/values-ar/strings.xml | 5 +- common/res/values-az-rAZ/strings.xml | 28 + common/res/values-az/strings.xml | 27 - common/res/values-bg/strings.xml | 5 +- common/res/values-bn-rBD/strings.xml | 28 + common/res/values-bn/strings.xml | 27 - common/res/values-ca/strings.xml | 5 +- common/res/values-cs/strings.xml | 5 +- common/res/values-da/strings.xml | 5 +- common/res/values-de/strings.xml | 5 +- common/res/values-el/strings.xml | 5 +- common/res/values-en-rAU/strings.xml | 5 +- common/res/values-en-rGB/strings.xml | 5 +- common/res/values-en-rIN/strings.xml | 5 +- common/res/values-es-rUS/strings.xml | 5 +- common/res/values-es/strings.xml | 5 +- common/res/values-et-rEE/strings.xml | 28 + common/res/values-et/strings.xml | 27 - common/res/values-eu-rES/strings.xml | 28 + common/res/values-eu/strings.xml | 27 - common/res/values-fa/strings.xml | 5 +- common/res/values-fi/strings.xml | 5 +- common/res/values-fr-rCA/strings.xml | 5 +- common/res/values-fr/strings.xml | 5 +- common/res/values-gl-rES/strings.xml | 28 + common/res/values-gl/strings.xml | 27 - common/res/values-gu-rIN/strings.xml | 28 + common/res/values-hi/strings.xml | 5 +- common/res/values-hr/strings.xml | 5 +- common/res/values-hu/strings.xml | 5 +- common/res/values-hy-rAM/strings.xml | 28 + common/res/values-hy/strings.xml | 27 - common/res/values-in/strings.xml | 5 +- common/res/values-is-rIS/strings.xml | 28 + common/res/values-is/strings.xml | 27 - common/res/values-it/strings.xml | 5 +- common/res/values-iw/strings.xml | 5 +- common/res/values-ja/strings.xml | 5 +- common/res/values-ka-rGE/strings.xml | 28 + common/res/values-ka/strings.xml | 27 - common/res/values-kk-rKZ/strings.xml | 28 + common/res/values-kk/strings.xml | 27 - common/res/values-km-rKH/strings.xml | 28 + common/res/values-km/strings.xml | 27 - common/res/values-kn-rIN/strings.xml | 28 + common/res/values-kn/strings.xml | 27 - common/res/values-ko/strings.xml | 5 +- common/res/values-ky-rKG/strings.xml | 28 + common/res/values-ky/strings.xml | 27 - common/res/values-lo-rLA/strings.xml | 28 + common/res/values-lo/strings.xml | 27 - common/res/values-lt/strings.xml | 5 +- common/res/values-lv/strings.xml | 5 +- common/res/values-mk-rMK/strings.xml | 28 + common/res/values-mk/strings.xml | 27 - common/res/values-ml-rIN/strings.xml | 28 + common/res/values-ml/strings.xml | 27 - common/res/values-mn-rMN/strings.xml | 28 + common/res/values-mn/strings.xml | 27 - common/res/values-mr-rIN/strings.xml | 28 + common/res/values-mr/strings.xml | 27 - common/res/values-ms-rMY/strings.xml | 28 + common/res/values-ms/strings.xml | 27 - common/res/values-my-rMM/strings.xml | 28 + common/res/values-my/strings.xml | 27 - common/res/values-nb/strings.xml | 5 +- common/res/values-ne-rNP/strings.xml | 28 + common/res/values-ne/strings.xml | 27 - common/res/values-nl/strings.xml | 5 +- common/res/values-pa-rIN/strings.xml | 28 + common/res/values-pl/strings.xml | 5 +- common/res/values-pt-rPT/strings.xml | 5 +- common/res/values-pt/strings.xml | 5 +- common/res/values-ro/strings.xml | 5 +- common/res/values-ru/strings.xml | 5 +- common/res/values-si-rLK/strings.xml | 28 + common/res/values-si/strings.xml | 27 - common/res/values-sk/strings.xml | 5 +- common/res/values-sl/strings.xml | 5 +- common/res/values-sq-rAL/strings.xml | 28 + common/res/values-sr/strings.xml | 5 +- common/res/values-sv/strings.xml | 5 +- common/res/values-sw/strings.xml | 5 +- common/res/values-ta-rIN/strings.xml | 28 + common/res/values-ta/strings.xml | 27 - common/res/values-te-rIN/strings.xml | 28 + common/res/values-te/strings.xml | 27 - common/res/values-th/strings.xml | 5 +- common/res/values-tl/strings.xml | 5 +- common/res/values-tr/strings.xml | 5 +- common/res/values-uk/strings.xml | 5 +- common/res/values-ur-rPK/strings.xml | 28 + common/res/values-ur/strings.xml | 27 - common/res/values-uz-rUZ/strings.xml | 28 + common/res/values-uz/strings.xml | 27 - common/res/values-vi/strings.xml | 5 +- common/res/values-zh-rCN/strings.xml | 5 +- common/res/values-zh-rHK/strings.xml | 5 +- common/res/values-zh-rTW/strings.xml | 5 +- common/res/values-zu/strings.xml | 5 +- common/res/values/dimens.xml | 7 - common/res/values/strings.xml | 32 +- common/res/values/styles.xml | 6 - common/res/values/themes.xml | 1 - .../android/tv/common/SharedPreferencesUtils.java | 6 + .../src/com/android/tv/common/TvCommonUtils.java | 17 +- .../android/tv/common/TvContentRatingCache.java | 5 +- .../android/tv/common/feature/CommonFeatures.java | 3 +- common/src/com/android/tv/common/feature/Sdk.java | 44 +- .../common/feature/SharedPreferencesFeature.java | 1 - .../android/tv/common/feature/TestableFeature.java | 15 + .../common/ui/setup/SetupGuidedStepFragment.java | 5 - .../tv/common/ui/setup/SetupMultiPaneFragment.java | 14 +- .../classes/core/src/com/ibm/icu/text/SCSU.java | 2 + .../src/com/ibm/icu/text/UnicodeDecompressor.java | 2 + jni/Android.mk | 21 +- jni/DvbManager.cpp | 233 +- jni/DvbManager.h | 22 + jni/minijail/Android.mk | 28 + jni/minijail/minijail.cpp | 65 + jni/minijail/minijail.h | 44 + jni/tunertvinput_jni.cpp | 18 + jni/tunertvinput_jni.h | 22 + libs/exoplayer_v2.jar | Bin 0 -> 1077775 bytes libs/exoplayer_v2_ext_ffmpeg.jar | Bin 0 -> 7271 bytes proguard.flags | 4 + proto/channel.proto | 5 + proto/track.proto | 2 + res/drawable-xhdpi/cloud01.png | Bin 2357 -> 2260 bytes res/drawable-xhdpi/cloud02.png | Bin 1118 -> 936 bytes res/drawable-xhdpi/dvr_default_poster.png | Bin 766 -> 747 bytes res/drawable-xhdpi/dvr_default_program_art.png | Bin 11562 -> 0 bytes res/drawable-xhdpi/ic_channel_guide.png | Bin 906 -> 737 bytes res/drawable-xhdpi/ic_delete_32dp.png | Bin 802 -> 417 bytes .../ic_developer_mode_tv_white_48dp.png | Bin 851 -> 552 bytes res/drawable-xhdpi/ic_dvr.png | Bin 934 -> 713 bytes res/drawable-xhdpi/ic_dvr_cancel.png | Bin 658 -> 413 bytes res/drawable-xhdpi/ic_dvr_cancel_32dp.png | Bin 1514 -> 849 bytes res/drawable-xhdpi/ic_dvr_cancel_large.png | Bin 1158 -> 755 bytes res/drawable-xhdpi/ic_dvr_delete.png | Bin 657 -> 383 bytes res/drawable-xhdpi/ic_error_recording.png | Bin 729 -> 0 bytes res/drawable-xhdpi/ic_error_white_48dp.png | Bin 1117 -> 712 bytes res/drawable-xhdpi/ic_fresh.png | Bin 13892 -> 0 bytes res/drawable-xhdpi/ic_guide_lock.png | Bin 737 -> 673 bytes res/drawable-xhdpi/ic_launcher_s.png | Bin 401 -> 363 bytes .../ic_message_lock_no_permission.png | Bin 2147 -> 2052 bytes res/drawable-xhdpi/ic_message_lock_preview.png | Bin 1847 -> 1639 bytes res/drawable-xhdpi/ic_pip_option_input.png | Bin 270 -> 0 bytes res/drawable-xhdpi/ic_pip_option_layout1.png | Bin 175 -> 0 bytes res/drawable-xhdpi/ic_pip_option_layout2.png | Bin 176 -> 0 bytes res/drawable-xhdpi/ic_pip_option_layout3.png | Bin 176 -> 0 bytes res/drawable-xhdpi/ic_pip_option_layout4.png | Bin 175 -> 0 bytes res/drawable-xhdpi/ic_pip_option_layout5.png | Bin 178 -> 0 bytes res/drawable-xhdpi/ic_pip_option_size.png | Bin 155 -> 0 bytes res/drawable-xhdpi/ic_pip_option_swap.png | Bin 241 -> 0 bytes res/drawable-xhdpi/ic_pip_option_swap_audio.png | Bin 479 -> 0 bytes res/drawable-xhdpi/ic_record_start.png | Bin 556 -> 494 bytes res/drawable-xhdpi/ic_recorded_program.png | Bin 612 -> 0 bytes res/drawable-xhdpi/ic_related_actor.png | Bin 1275 -> 0 bytes res/drawable-xhdpi/ic_related_search.png | Bin 2707 -> 0 bytes res/drawable-xhdpi/ic_schedule_32dp.png | Bin 1490 -> 821 bytes res/drawable-xhdpi/ic_scheduled_recording.png | Bin 902 -> 556 bytes res/drawable-xhdpi/ic_scheduled_white.png | Bin 491 -> 275 bytes res/drawable-xhdpi/ic_setup_antenna.png | Bin 1264 -> 0 bytes res/drawable-xhdpi/ic_setup_channels.png | Bin 1465 -> 1267 bytes res/drawable-xhdpi/ic_tvoption_multi_track.png | Bin 412 -> 388 bytes res/drawable-xhdpi/ic_tvoption_pip.png | Bin 192 -> 176 bytes res/drawable-xhdpi/ic_tvoption_pip_off.png | Bin 261 -> 0 bytes .../ic_tvsidepanel_partial_locked.png | Bin 754 -> 684 bytes res/drawable-xhdpi/ic_warning_gray600_36dp.png | Bin 335 -> 329 bytes res/drawable-xhdpi/ic_warning_white_12dp.png | Bin 242 -> 228 bytes res/drawable-xhdpi/ic_warning_white_18dp.png | Bin 356 -> 329 bytes res/drawable-xhdpi/ic_warning_white_32dp.png | Bin 553 -> 507 bytes res/drawable-xhdpi/ic_warning_white_96dp.png | Bin 992 -> 976 bytes res/drawable-xhdpi/ic_welcome_ripple_000.png | Bin 1120 -> 987 bytes res/drawable-xhdpi/tv_3a_00.png | Bin 106 -> 0 bytes res/drawable-xhdpi/tv_error.png | Bin 2846 -> 0 bytes res/drawable-xhdpi/tv_usb_antenna.png | Bin 21390 -> 0 bytes res/drawable/play_controls_time_indicator.xml | 2 - res/drawable/playback_progress_bar.xml | 33 + .../priority_settings_action_item_selected.xml | 12 +- res/drawable/setup_item_background.xml | 28 - res/layout/activity_dvr_playback.xml | 6 +- res/layout/activity_dvr_schedules.xml | 1 + res/layout/activity_tv.xml | 22 - res/layout/block_screen.xml | 11 +- res/layout/dvr_details_description.xml | 3 +- res/layout/dvr_main.xml | 2 +- res/layout/dvr_play.xml | 28 - res/layout/dvr_recording_card_view.xml | 37 +- res/layout/dvr_schedules_item.xml | 12 +- res/layout/guided_action_editable.xml | 41 + res/layout/input_banner.xml | 2 + res/layout/list_item_dvr_history.xml | 59 + res/layout/menu_card_action.xml | 57 +- res/layout/menu_card_app_link.xml | 7 - res/layout/menu_card_channel.xml | 7 - res/layout/menu_card_dvr.xml | 16 +- res/layout/menu_card_guide.xml | 16 +- res/layout/menu_card_setup.xml | 16 +- res/layout/menu_card_text.xml | 1 - res/layout/option_container.xml | 2 + res/layout/option_fragment.xml | 1 - res/layout/option_item_action.xml | 15 +- res/layout/option_item_channel_check.xml | 1 - res/layout/option_item_channel_lock.xml | 1 - res/layout/option_item_check_box.xml | 1 - res/layout/option_item_common.xml | 2 +- res/layout/option_item_radio_button.xml | 1 - res/layout/option_item_switch.xml | 1 - res/layout/play_controls_contents.xml | 218 +- res/layout/select_input_item.xml | 2 + res/layout/tunable_tv_view.xml | 14 +- res/raw/third_party_license_metadata | 21 + res/raw/third_party_licenses | 3751 ++++++++++++++++++++ ...dvr_details_shared_element_enter_transition.xml | 46 + ...vr_details_shared_element_return_transition.xml | 45 + res/values-af/strings.xml | 103 +- res/values-am/strings.xml | 103 +- res/values-ar/strings.xml | 133 +- res/values-az-rAZ/arrays.xml | 57 + res/values-az-rAZ/rating_system_strings.xml | 101 + res/values-az-rAZ/strings.xml | 359 ++ res/values-az/arrays.xml | 57 - res/values-az/rating_system_strings.xml | 101 - res/values-az/strings.xml | 352 -- res/values-bg/strings.xml | 105 +- res/values-bn-rBD-v23/strings.xml | 21 + res/values-bn-rBD/arrays.xml | 57 + res/values-bn-rBD/rating_system_strings.xml | 101 + res/values-bn-rBD/strings.xml | 357 ++ res/values-bn-v23/strings.xml | 21 - res/values-bn/arrays.xml | 57 - res/values-bn/rating_system_strings.xml | 101 - res/values-bn/strings.xml | 350 -- res/values-ca/strings.xml | 111 +- res/values-cs/strings.xml | 119 +- res/values-da/strings.xml | 117 +- res/values-de/strings.xml | 149 +- res/values-el/strings.xml | 107 +- res/values-en-rAU/strings.xml | 103 +- res/values-en-rGB/strings.xml | 103 +- res/values-en-rIN/strings.xml | 103 +- res/values-es-rUS/strings.xml | 107 +- res/values-es/strings.xml | 115 +- res/values-et-rEE-v23/strings.xml | 21 + res/values-et-rEE/arrays.xml | 57 + res/values-et-rEE/rating_system_strings.xml | 101 + res/values-et-rEE/strings.xml | 357 ++ res/values-et-v23/strings.xml | 21 - res/values-et/arrays.xml | 57 - res/values-et/rating_system_strings.xml | 101 - res/values-et/strings.xml | 350 -- res/values-eu-rES-v23/strings.xml | 21 + res/values-eu-rES/arrays.xml | 57 + res/values-eu-rES/rating_system_strings.xml | 101 + res/values-eu-rES/strings.xml | 357 ++ res/values-eu-v23/strings.xml | 21 - res/values-eu/arrays.xml | 57 - res/values-eu/rating_system_strings.xml | 101 - res/values-eu/strings.xml | 350 -- res/values-fa/strings.xml | 103 +- res/values-fi/strings.xml | 103 +- res/values-fr-rCA/strings.xml | 107 +- res/values-fr/strings.xml | 107 +- res/values-gl-rES-v23/strings.xml | 21 + res/values-gl-rES/arrays.xml | 57 + res/values-gl-rES/rating_system_strings.xml | 101 + res/values-gl-rES/strings.xml | 357 ++ res/values-gl-v23/strings.xml | 21 - res/values-gl/arrays.xml | 57 - res/values-gl/rating_system_strings.xml | 101 - res/values-gl/strings.xml | 350 -- res/values-gu-rIN/strings.xml | 356 ++ res/values-hi/strings.xml | 103 +- res/values-hr/strings.xml | 108 +- res/values-hu/strings.xml | 113 +- res/values-hy-rAM-v23/strings.xml | 21 + res/values-hy-rAM/arrays.xml | 57 + res/values-hy-rAM/rating_system_strings.xml | 101 + res/values-hy-rAM/strings.xml | 357 ++ res/values-hy-v23/strings.xml | 21 - res/values-hy/arrays.xml | 57 - res/values-hy/rating_system_strings.xml | 101 - res/values-hy/strings.xml | 350 -- res/values-in/strings.xml | 105 +- res/values-is-rIS-v23/strings.xml | 21 + res/values-is-rIS/arrays.xml | 57 + res/values-is-rIS/rating_system_strings.xml | 101 + res/values-is-rIS/strings.xml | 357 ++ res/values-is-v23/strings.xml | 21 - res/values-is/arrays.xml | 57 - res/values-is/rating_system_strings.xml | 101 - res/values-is/strings.xml | 350 -- res/values-it/strings.xml | 117 +- res/values-iw/strings.xml | 113 +- res/values-ja/strings.xml | 117 +- res/values-ka-rGE-v23/strings.xml | 21 + res/values-ka-rGE/arrays.xml | 57 + res/values-ka-rGE/rating_system_strings.xml | 101 + res/values-ka-rGE/strings.xml | 357 ++ res/values-ka-v23/strings.xml | 21 - res/values-ka/arrays.xml | 57 - res/values-ka/rating_system_strings.xml | 101 - res/values-ka/strings.xml | 350 -- res/values-kk-rKZ-v23/strings.xml | 21 + res/values-kk-rKZ/arrays.xml | 57 + res/values-kk-rKZ/rating_system_strings.xml | 101 + res/values-kk-rKZ/strings.xml | 357 ++ res/values-kk-v23/strings.xml | 21 - res/values-kk/arrays.xml | 57 - res/values-kk/rating_system_strings.xml | 101 - res/values-kk/strings.xml | 350 -- res/values-km-rKH-v23/strings.xml | 21 + res/values-km-rKH/arrays.xml | 57 + res/values-km-rKH/rating_system_strings.xml | 101 + res/values-km-rKH/strings.xml | 357 ++ res/values-km-v23/strings.xml | 21 - res/values-km/arrays.xml | 57 - res/values-km/rating_system_strings.xml | 101 - res/values-km/strings.xml | 350 -- res/values-kn-rIN-v23/strings.xml | 21 + res/values-kn-rIN/arrays.xml | 57 + res/values-kn-rIN/rating_system_strings.xml | 101 + res/values-kn-rIN/strings.xml | 357 ++ res/values-kn-v23/strings.xml | 21 - res/values-kn/arrays.xml | 57 - res/values-kn/rating_system_strings.xml | 101 - res/values-kn/strings.xml | 350 -- res/values-ko/strings.xml | 103 +- res/values-ky-rKG-v23/strings.xml | 21 + res/values-ky-rKG/arrays.xml | 57 + res/values-ky-rKG/rating_system_strings.xml | 101 + res/values-ky-rKG/strings.xml | 357 ++ res/values-ky-v23/strings.xml | 21 - res/values-ky/arrays.xml | 57 - res/values-ky/rating_system_strings.xml | 101 - res/values-ky/strings.xml | 350 -- res/values-ldrtl/dimens.xml | 1 - res/values-lo-rLA-v23/strings.xml | 21 + res/values-lo-rLA/arrays.xml | 57 + res/values-lo-rLA/rating_system_strings.xml | 101 + res/values-lo-rLA/strings.xml | 357 ++ res/values-lo-v23/strings.xml | 21 - res/values-lo/arrays.xml | 57 - res/values-lo/rating_system_strings.xml | 101 - res/values-lo/strings.xml | 350 -- res/values-lt/strings.xml | 113 +- res/values-lv/strings.xml | 108 +- res/values-mk-rMK-v23/strings.xml | 21 + res/values-mk-rMK/arrays.xml | 57 + res/values-mk-rMK/rating_system_strings.xml | 101 + res/values-mk-rMK/strings.xml | 357 ++ res/values-mk-v23/strings.xml | 21 - res/values-mk/arrays.xml | 57 - res/values-mk/rating_system_strings.xml | 101 - res/values-mk/strings.xml | 350 -- res/values-ml-rIN-v23/strings.xml | 21 + res/values-ml-rIN/arrays.xml | 57 + res/values-ml-rIN/rating_system_strings.xml | 101 + res/values-ml-rIN/strings.xml | 355 ++ res/values-ml-v23/strings.xml | 21 - res/values-ml/arrays.xml | 57 - res/values-ml/rating_system_strings.xml | 101 - res/values-ml/strings.xml | 348 -- res/values-mn-rMN-v23/strings.xml | 21 + res/values-mn-rMN/arrays.xml | 57 + res/values-mn-rMN/rating_system_strings.xml | 101 + res/values-mn-rMN/strings.xml | 355 ++ res/values-mn-v23/strings.xml | 21 - res/values-mn/arrays.xml | 57 - res/values-mn/rating_system_strings.xml | 101 - res/values-mn/strings.xml | 348 -- res/values-mr-rIN-v23/strings.xml | 21 + res/values-mr-rIN/arrays.xml | 57 + res/values-mr-rIN/rating_system_strings.xml | 101 + res/values-mr-rIN/strings.xml | 357 ++ res/values-mr-v23/strings.xml | 21 - res/values-mr/arrays.xml | 57 - res/values-mr/rating_system_strings.xml | 101 - res/values-mr/strings.xml | 350 -- res/values-ms-rMY-v23/strings.xml | 21 + res/values-ms-rMY/arrays.xml | 57 + res/values-ms-rMY/rating_system_strings.xml | 101 + res/values-ms-rMY/strings.xml | 357 ++ res/values-ms-v23/strings.xml | 21 - res/values-ms/arrays.xml | 57 - res/values-ms/rating_system_strings.xml | 101 - res/values-ms/strings.xml | 350 -- res/values-my-rMM-v23/strings.xml | 21 + res/values-my-rMM/arrays.xml | 57 + res/values-my-rMM/rating_system_strings.xml | 101 + res/values-my-rMM/strings.xml | 357 ++ res/values-my-v23/strings.xml | 21 - res/values-my/arrays.xml | 57 - res/values-my/rating_system_strings.xml | 101 - res/values-my/strings.xml | 350 -- res/values-nb/strings.xml | 103 +- res/values-ne-rNP-v23/strings.xml | 21 + res/values-ne-rNP/arrays.xml | 57 + res/values-ne-rNP/rating_system_strings.xml | 101 + res/values-ne-rNP/strings.xml | 357 ++ res/values-ne-v23/strings.xml | 21 - res/values-ne/arrays.xml | 57 - res/values-ne/rating_system_strings.xml | 101 - res/values-ne/strings.xml | 350 -- res/values-nl/strings.xml | 109 +- res/values-pa-rIN/strings.xml | 356 ++ res/values-pl/strings.xml | 115 +- res/values-pt-rPT/strings.xml | 131 +- res/values-pt/strings.xml | 131 +- res/values-ro/strings.xml | 108 +- res/values-ru/strings.xml | 113 +- res/values-si-rLK-v23/strings.xml | 21 + res/values-si-rLK/arrays.xml | 57 + res/values-si-rLK/rating_system_strings.xml | 101 + res/values-si-rLK/strings.xml | 357 ++ res/values-si-v23/strings.xml | 21 - res/values-si/arrays.xml | 57 - res/values-si/rating_system_strings.xml | 101 - res/values-si/strings.xml | 350 -- res/values-sk/strings.xml | 133 +- res/values-sl/strings.xml | 119 +- res/values-sq-rAL/strings.xml | 356 ++ res/values-sr/strings.xml | 110 +- res/values-sv/strings.xml | 107 +- res/values-sw/strings.xml | 107 +- res/values-ta-rIN-v23/strings.xml | 21 + res/values-ta-rIN/arrays.xml | 57 + res/values-ta-rIN/rating_system_strings.xml | 101 + res/values-ta-rIN/strings.xml | 357 ++ res/values-ta-v23/strings.xml | 21 - res/values-ta/arrays.xml | 57 - res/values-ta/rating_system_strings.xml | 101 - res/values-ta/strings.xml | 350 -- res/values-te-rIN-v23/strings.xml | 21 + res/values-te-rIN/arrays.xml | 57 + res/values-te-rIN/rating_system_strings.xml | 101 + res/values-te-rIN/strings.xml | 357 ++ res/values-te-v23/strings.xml | 21 - res/values-te/arrays.xml | 57 - res/values-te/rating_system_strings.xml | 101 - res/values-te/strings.xml | 350 -- res/values-th/strings.xml | 105 +- res/values-tl/strings.xml | 107 +- res/values-tr/strings.xml | 105 +- res/values-uk/strings.xml | 117 +- res/values-ur-rPK-v23/strings.xml | 21 + res/values-ur-rPK/arrays.xml | 57 + res/values-ur-rPK/rating_system_strings.xml | 101 + res/values-ur-rPK/strings.xml | 357 ++ res/values-ur-v23/strings.xml | 21 - res/values-ur/arrays.xml | 57 - res/values-ur/rating_system_strings.xml | 101 - res/values-ur/strings.xml | 350 -- res/values-uz-rUZ-v23/strings.xml | 21 + res/values-uz-rUZ/arrays.xml | 57 + res/values-uz-rUZ/rating_system_strings.xml | 101 + res/values-uz-rUZ/strings.xml | 357 ++ res/values-uz-v23/strings.xml | 21 - res/values-uz/arrays.xml | 57 - res/values-uz/rating_system_strings.xml | 101 - res/values-uz/strings.xml | 350 -- res/values-v23/strings.xml | 21 - res/values-vi/strings.xml | 103 +- res/values-zh-rCN/strings.xml | 129 +- res/values-zh-rHK/strings.xml | 103 +- res/values-zh-rTW/strings.xml | 105 +- res/values-zu/strings.xml | 103 +- res/values/attr.xml | 25 - res/values/attrs.xml | 48 + res/values/colors.xml | 4 +- res/values/dimens.xml | 69 +- res/values/google-services.xml | 8 - res/values/integers.xml | 9 +- res/values/strings.xml | 299 +- res/values/styles.xml | 7 +- res/values/themes.xml | 7 +- res/xml/remote_config_defaults.xml | 15 + src/com/android/exoplayer/text/SubtitleView.java | 11 +- .../exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java | 127 + .../exoplayer2/ext/ffmpeg/FfmpegLibrary.java | 84 + src/com/android/tv/ApplicationSingletons.java | 27 +- src/com/android/tv/AudioManagerHelper.java | 108 + src/com/android/tv/Features.java | 151 +- src/com/android/tv/InputSessionManager.java | 30 +- src/com/android/tv/MainActivity.java | 1498 +++----- src/com/android/tv/MainActivityWrapper.java | 2 +- src/com/android/tv/MediaSessionWrapper.java | 216 ++ src/com/android/tv/SetupPassthroughActivity.java | 176 +- src/com/android/tv/TimeShiftManager.java | 26 +- src/com/android/tv/TvApplication.java | 162 +- src/com/android/tv/TvOptionsManager.java | 133 +- src/com/android/tv/analytics/DurationTimer.java | 62 - .../android/tv/config/DefaultConfigManager.java | 6 + src/com/android/tv/config/RemoteConfig.java | 3 + src/com/android/tv/config/RemoteConfigUtils.java | 42 + .../tv/customization/TvCustomizationManager.java | 118 +- src/com/android/tv/data/BaseProgram.java | 39 +- src/com/android/tv/data/Channel.java | 114 +- src/com/android/tv/data/ChannelDataManager.java | 200 +- src/com/android/tv/data/ChannelLogoFetcher.java | 307 +- src/com/android/tv/data/ChannelNumber.java | 71 +- src/com/android/tv/data/InternalDataUtils.java | 2 +- src/com/android/tv/data/PreviewDataManager.java | 636 ++++ src/com/android/tv/data/PreviewProgramContent.java | 259 ++ src/com/android/tv/data/Program.java | 140 +- src/com/android/tv/data/ProgramDataManager.java | 76 +- src/com/android/tv/data/StreamInfo.java | 4 + src/com/android/tv/data/WatchedHistoryManager.java | 161 +- src/com/android/tv/data/epg/EpgFetchHelper.java | 233 ++ src/com/android/tv/data/epg/EpgFetcher.java | 988 +++--- src/com/android/tv/data/epg/EpgReader.java | 45 +- src/com/android/tv/data/epg/StubEpgReader.java | 37 +- .../tv/dialog/DvrHistoryDialogFragment.java | 130 + .../tv/dialog/FullscreenDialogFragment.java | 2 +- .../android/tv/dialog/HalfSizedDialogFragment.java | 123 + src/com/android/tv/dialog/PinDialogFragment.java | 103 +- .../tv/dialog/SafeDismissDialogFragment.java | 26 - src/com/android/tv/dialog/WebDialogFragment.java | 17 +- src/com/android/tv/dvr/BaseDvrDataManager.java | 41 +- src/com/android/tv/dvr/ConflictChecker.java | 277 -- src/com/android/tv/dvr/DvrDataManager.java | 12 +- src/com/android/tv/dvr/DvrDataManagerImpl.java | 151 +- src/com/android/tv/dvr/DvrDbSync.java | 363 -- src/com/android/tv/dvr/DvrManager.java | 113 +- src/com/android/tv/dvr/DvrPlaybackActivity.java | 67 - .../tv/dvr/DvrPlaybackMediaSessionHelper.java | 327 -- src/com/android/tv/dvr/DvrPlayer.java | 425 --- src/com/android/tv/dvr/DvrRecordingService.java | 122 - src/com/android/tv/dvr/DvrScheduleManager.java | 139 +- .../android/tv/dvr/DvrStartRecordingReceiver.java | 34 - .../android/tv/dvr/DvrStorageStatusManager.java | 35 +- src/com/android/tv/dvr/DvrUiHelper.java | 450 --- .../android/tv/dvr/DvrWatchedPositionManager.java | 4 +- .../android/tv/dvr/EpisodicProgramLoadTask.java | 382 -- src/com/android/tv/dvr/IdGenerator.java | 50 - src/com/android/tv/dvr/InputTaskScheduler.java | 431 --- src/com/android/tv/dvr/RecordedProgram.java | 868 ----- src/com/android/tv/dvr/RecordingTask.java | 519 --- src/com/android/tv/dvr/ScheduledProgramReaper.java | 67 - src/com/android/tv/dvr/ScheduledRecording.java | 887 ----- src/com/android/tv/dvr/Scheduler.java | 283 -- src/com/android/tv/dvr/SeriesInfo.java | 76 - src/com/android/tv/dvr/SeriesRecording.java | 755 ---- .../android/tv/dvr/SeriesRecordingScheduler.java | 579 --- src/com/android/tv/dvr/WritableDvrDataManager.java | 6 +- src/com/android/tv/dvr/data/IdGenerator.java | 50 + src/com/android/tv/dvr/data/RecordedProgram.java | 845 +++++ .../android/tv/dvr/data/ScheduledRecording.java | 882 +++++ .../android/tv/dvr/data/SeasonEpisodeNumber.java | 72 + src/com/android/tv/dvr/data/SeriesInfo.java | 76 + src/com/android/tv/dvr/data/SeriesRecording.java | 755 ++++ .../android/tv/dvr/provider/AsyncDvrDbTask.java | 4 +- .../android/tv/dvr/provider/DvrDatabaseHelper.java | 4 +- src/com/android/tv/dvr/provider/DvrDbSync.java | 373 ++ .../tv/dvr/provider/EpisodicProgramLoadTask.java | 329 ++ .../android/tv/dvr/recorder/ConflictChecker.java | 280 ++ .../tv/dvr/recorder/DvrRecordingService.java | 207 ++ .../tv/dvr/recorder/DvrStartRecordingReceiver.java | 40 + .../tv/dvr/recorder/InputTaskScheduler.java | 429 +++ .../tv/dvr/recorder/RecordingScheduler.java | 329 ++ src/com/android/tv/dvr/recorder/RecordingTask.java | 529 +++ .../tv/dvr/recorder/ScheduledProgramReaper.java | 70 + .../tv/dvr/recorder/SeriesRecordingScheduler.java | 563 +++ .../android/tv/dvr/ui/ActionPresenterSelector.java | 138 - src/com/android/tv/dvr/ui/BigArguments.java | 54 + .../ui/ChangeImageTransformWithScaledParent.java | 79 + .../tv/dvr/ui/CurrentRecordingDetailsFragment.java | 59 - src/com/android/tv/dvr/ui/DetailsContent.java | 207 -- .../android/tv/dvr/ui/DetailsContentPresenter.java | 300 -- .../tv/dvr/ui/DetailsViewBackgroundHelper.java | 92 - src/com/android/tv/dvr/ui/DvrActivity.java | 35 - .../tv/dvr/ui/DvrAlreadyRecordedFragment.java | 26 +- .../tv/dvr/ui/DvrAlreadyScheduledFragment.java | 26 +- src/com/android/tv/dvr/ui/DvrBrowseFragment.java | 601 ---- .../ui/DvrChannelRecordDurationOptionFragment.java | 25 +- src/com/android/tv/dvr/ui/DvrConflictFragment.java | 48 +- src/com/android/tv/dvr/ui/DvrDetailsActivity.java | 98 - src/com/android/tv/dvr/ui/DvrDetailsFragment.java | 344 -- .../tv/dvr/ui/DvrForgetStorageErrorFragment.java | 87 - .../android/tv/dvr/ui/DvrGuidedStepFragment.java | 116 +- .../tv/dvr/ui/DvrHalfSizedDialogFragment.java | 39 +- .../dvr/ui/DvrInsufficientSpaceErrorFragment.java | 101 +- src/com/android/tv/dvr/ui/DvrItemPresenter.java | 80 - .../tv/dvr/ui/DvrMissingStorageErrorFragment.java | 67 +- .../tv/dvr/ui/DvrPlaybackCardPresenter.java | 82 - .../tv/dvr/ui/DvrPlaybackControlHelper.java | 313 -- .../tv/dvr/ui/DvrPlaybackOverlayFragment.java | 304 -- .../tv/dvr/ui/DvrPrioritySettingsFragment.java | 262 ++ src/com/android/tv/dvr/ui/DvrScheduleFragment.java | 36 +- .../android/tv/dvr/ui/DvrSchedulesActivity.java | 104 - .../tv/dvr/ui/DvrSeriesDeletionActivity.java | 5 +- .../tv/dvr/ui/DvrSeriesDeletionFragment.java | 253 ++ .../tv/dvr/ui/DvrSeriesScheduledFragment.java | 62 +- .../tv/dvr/ui/DvrSeriesSettingsActivity.java | 20 +- .../tv/dvr/ui/DvrSeriesSettingsFragment.java | 366 ++ .../tv/dvr/ui/DvrStopRecordingFragment.java | 29 +- .../tv/dvr/ui/DvrStopSeriesRecordingFragment.java | 20 +- src/com/android/tv/dvr/ui/DvrUiHelper.java | 649 ++++ src/com/android/tv/dvr/ui/FadeBackground.java | 70 + .../android/tv/dvr/ui/FullScheduleCardHolder.java | 29 - .../tv/dvr/ui/FullSchedulesCardPresenter.java | 84 - .../android/tv/dvr/ui/HalfSizedDialogFragment.java | 117 - .../tv/dvr/ui/PrioritySettingsFragment.java | 251 -- .../tv/dvr/ui/RecordedProgramDetailsFragment.java | 170 - .../tv/dvr/ui/RecordedProgramPresenter.java | 182 - src/com/android/tv/dvr/ui/RecordingCardView.java | 185 - .../tv/dvr/ui/RecordingDetailsFragment.java | 87 - .../dvr/ui/ScheduledRecordingDetailsFragment.java | 97 - .../tv/dvr/ui/ScheduledRecordingPresenter.java | 177 - .../android/tv/dvr/ui/SeriesDeletionFragment.java | 252 -- .../tv/dvr/ui/SeriesRecordingDetailsFragment.java | 375 -- .../tv/dvr/ui/SeriesRecordingPresenter.java | 234 -- .../android/tv/dvr/ui/SeriesSettingsFragment.java | 397 --- src/com/android/tv/dvr/ui/SortedArrayAdapter.java | 90 +- .../tv/dvr/ui/TrackedGuidedStepFragment.java | 82 + .../tv/dvr/ui/browse/ActionPresenterSelector.java | 134 + .../ui/browse/CurrentRecordingDetailsFragment.java | 120 + .../android/tv/dvr/ui/browse/DetailsContent.java | 317 ++ .../tv/dvr/ui/browse/DetailsContentPresenter.java | 319 ++ .../dvr/ui/browse/DetailsViewBackgroundHelper.java | 92 + .../tv/dvr/ui/browse/DvrBrowseActivity.java | 52 + .../tv/dvr/ui/browse/DvrBrowseFragment.java | 665 ++++ .../tv/dvr/ui/browse/DvrDetailsActivity.java | 154 + .../tv/dvr/ui/browse/DvrDetailsFragment.java | 307 ++ .../android/tv/dvr/ui/browse/DvrItemPresenter.java | 140 + .../tv/dvr/ui/browse/DvrListRowPresenter.java | 34 + .../tv/dvr/ui/browse/FullScheduleCardHolder.java | 29 + .../dvr/ui/browse/FullSchedulesCardPresenter.java | 84 + .../ui/browse/RecordedProgramDetailsFragment.java | 149 + .../tv/dvr/ui/browse/RecordedProgramPresenter.java | 142 + .../tv/dvr/ui/browse/RecordingCardView.java | 290 ++ .../tv/dvr/ui/browse/RecordingDetailsFragment.java | 51 + .../browse/ScheduledRecordingDetailsFragment.java | 97 + .../dvr/ui/browse/ScheduledRecordingPresenter.java | 138 + .../ui/browse/SeriesRecordingDetailsFragment.java | 354 ++ .../tv/dvr/ui/browse/SeriesRecordingPresenter.java | 216 ++ .../tv/dvr/ui/list/BaseDvrSchedulesFragment.java | 5 +- .../tv/dvr/ui/list/DvrSchedulesActivity.java | 116 + .../tv/dvr/ui/list/DvrSchedulesFragment.java | 5 +- .../tv/dvr/ui/list/DvrSeriesSchedulesFragment.java | 72 +- .../android/tv/dvr/ui/list/EpisodicProgramRow.java | 9 +- src/com/android/tv/dvr/ui/list/ScheduleRow.java | 8 +- .../android/tv/dvr/ui/list/ScheduleRowAdapter.java | 4 +- .../tv/dvr/ui/list/ScheduleRowPresenter.java | 50 +- .../android/tv/dvr/ui/list/SchedulesHeaderRow.java | 20 +- .../dvr/ui/list/SchedulesHeaderRowPresenter.java | 26 +- .../tv/dvr/ui/list/SeriesScheduleRowAdapter.java | 24 +- .../tv/dvr/ui/list/SeriesScheduleRowPresenter.java | 17 +- .../tv/dvr/ui/playback/DvrPlaybackActivity.java | 94 + .../dvr/ui/playback/DvrPlaybackCardPresenter.java | 45 + .../dvr/ui/playback/DvrPlaybackControlHelper.java | 373 ++ .../ui/playback/DvrPlaybackMediaSessionHelper.java | 335 ++ .../ui/playback/DvrPlaybackOverlayFragment.java | 494 +++ .../dvr/ui/playback/DvrPlaybackSideFragment.java | 154 + src/com/android/tv/dvr/ui/playback/DvrPlayer.java | 583 +++ src/com/android/tv/experiments/ExperimentFlag.java | 32 +- src/com/android/tv/experiments/Experiments.java | 9 +- src/com/android/tv/guide/GenreListAdapter.java | 23 +- src/com/android/tv/guide/GuideUtils.java | 110 +- src/com/android/tv/guide/ProgramGrid.java | 270 +- src/com/android/tv/guide/ProgramGuide.java | 265 +- src/com/android/tv/guide/ProgramItemView.java | 33 +- src/com/android/tv/guide/ProgramListAdapter.java | 36 +- src/com/android/tv/guide/ProgramManager.java | 658 ++-- src/com/android/tv/guide/ProgramRow.java | 33 +- src/com/android/tv/guide/ProgramTableAdapter.java | 172 +- src/com/android/tv/guide/TimeListAdapter.java | 35 +- src/com/android/tv/license/License.java | 112 + .../android/tv/license/LicenseDialogFragment.java | 97 + .../android/tv/license/LicenseSideFragment.java | 80 + src/com/android/tv/license/LicenseUtils.java | 12 - src/com/android/tv/license/Licenses.java | 122 + src/com/android/tv/menu/ActionCardView.java | 6 +- src/com/android/tv/menu/AppLinkCardView.java | 286 +- src/com/android/tv/menu/BaseCardView.java | 86 +- src/com/android/tv/menu/ChannelCardView.java | 140 +- .../android/tv/menu/ChannelsPosterPrefetcher.java | 9 +- src/com/android/tv/menu/ChannelsRow.java | 10 +- src/com/android/tv/menu/ChannelsRowAdapter.java | 198 +- src/com/android/tv/menu/ChannelsRowItem.java | 101 + src/com/android/tv/menu/ItemListRowView.java | 20 +- src/com/android/tv/menu/Menu.java | 43 +- src/com/android/tv/menu/MenuAction.java | 69 +- src/com/android/tv/menu/MenuLayoutManager.java | 54 +- src/com/android/tv/menu/MenuRow.java | 5 - src/com/android/tv/menu/MenuRowFactory.java | 33 +- src/com/android/tv/menu/MenuUpdater.java | 48 +- src/com/android/tv/menu/OptionsRowAdapter.java | 50 +- .../android/tv/menu/PartnerOptionsRowAdapter.java | 3 +- src/com/android/tv/menu/PipOptionsRowAdapter.java | 137 - src/com/android/tv/menu/PlayControlsButton.java | 24 +- src/com/android/tv/menu/PlayControlsRowView.java | 194 +- src/com/android/tv/menu/PlaybackProgressBar.java | 168 + src/com/android/tv/menu/SimpleCardView.java | 4 +- src/com/android/tv/menu/TvOptionsRowAdapter.java | 138 +- .../tv/onboarding/SetupSourcesFragment.java | 2 + .../android/tv/parental/ContentRatingSystem.java | 9 + .../android/tv/parental/ContentRatingsManager.java | 19 + .../tv/parental/ParentalControlSettings.java | 33 +- src/com/android/tv/perf/EventNames.java | 56 + src/com/android/tv/perf/PerformanceMonitor.java | 99 + .../android/tv/perf/StubPerformanceMonitor.java | 65 + src/com/android/tv/perf/TimerEvent.java | 20 + .../android/tv/receiver/BootCompletedReceiver.java | 27 +- src/com/android/tv/receiver/GlobalKeyReceiver.java | 52 +- .../tv/receiver/PackageIntentsReceiver.java | 12 + .../tv/recommendation/ChannelPreviewUpdater.java | 323 ++ .../tv/recommendation/NotificationService.java | 11 +- .../recommendation/RecommendationDataManager.java | 6 +- .../RecordedProgramPreviewUpdater.java | 176 + src/com/android/tv/search/DataManagerSearch.java | 4 +- src/com/android/tv/search/LocalSearchProvider.java | 105 +- src/com/android/tv/search/SearchInterface.java | 4 +- src/com/android/tv/search/TvProviderSearch.java | 10 +- .../android/tv/tuner/ChannelScanFileParser.java | 4 - src/com/android/tv/tuner/DvbTunerHal.java | 179 + src/com/android/tv/tuner/TunerHal.java | 129 +- src/com/android/tv/tuner/TunerInputController.java | 338 +- src/com/android/tv/tuner/TunerPreferences.java | 196 +- src/com/android/tv/tuner/UsbTunerHal.java | 174 - .../android/tv/tuner/cc/CaptionTrackRenderer.java | 4 + src/com/android/tv/tuner/cc/Cea708Parser.java | 14 +- src/com/android/tv/tuner/data/PsipData.java | 133 +- src/com/android/tv/tuner/data/TunerChannel.java | 163 +- .../tuner/exoplayer/Cea708TextTrackRenderer.java | 29 +- .../exoplayer/ExoPlayerExtractorsFactory.java | 41 + .../tuner/exoplayer/ExoPlayerSampleExtractor.java | 378 +- .../tv/tuner/exoplayer/FileSampleExtractor.java | 14 +- .../android/tv/tuner/exoplayer/MpegTsPlayer.java | 89 +- .../tv/tuner/exoplayer/MpegTsRendererBuilder.java | 20 +- .../exoplayer/ac3/Ac3PassthroughTrackRenderer.java | 540 --- .../tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java | 94 - .../android/tv/tuner/exoplayer/ac3/AudioClock.java | 107 - .../tv/tuner/exoplayer/ac3/AudioTrackMonitor.java | 121 - .../tv/tuner/exoplayer/ac3/AudioTrackWrapper.java | 164 - .../tv/tuner/exoplayer/audio/AudioClock.java | 107 + .../tv/tuner/exoplayer/audio/AudioDecoder.java | 70 + .../tuner/exoplayer/audio/AudioTrackMonitor.java | 129 + .../tuner/exoplayer/audio/AudioTrackWrapper.java | 176 + .../exoplayer/audio/MediaCodecAudioDecoder.java | 235 ++ .../audio/MpegTsDefaultAudioTrackRenderer.java | 735 ++++ .../audio/MpegTsMediaCodecAudioTrackRenderer.java | 94 + .../tv/tuner/exoplayer/buffer/BufferManager.java | 280 +- .../tuner/exoplayer/buffer/DvrStorageManager.java | 209 +- .../exoplayer/buffer/RecordingSampleBuffer.java | 23 +- .../tv/tuner/exoplayer/buffer/SampleChunk.java | 24 +- .../exoplayer/buffer/SampleChunkIoHelper.java | 115 +- .../tv/tuner/exoplayer/buffer/SampleQueue.java | 1 + .../tuner/exoplayer/buffer/SimpleSampleBuffer.java | 8 +- .../exoplayer/buffer/TrickplayStorageManager.java | 85 +- .../exoplayer/ffmpeg/FfmpegDecoderClient.java | 249 ++ .../exoplayer/ffmpeg/FfmpegDecoderService.java | 205 ++ .../tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl | 29 + .../tv/tuner/setup/ConnectionTypeFragment.java | 19 + .../android/tv/tuner/setup/PostalCodeFragment.java | 178 + src/com/android/tv/tuner/setup/ScanFragment.java | 72 +- .../android/tv/tuner/setup/ScanResultFragment.java | 17 +- .../android/tv/tuner/setup/TunerSetupActivity.java | 375 +- .../android/tv/tuner/setup/WelcomeFragment.java | 54 +- .../android/tv/tuner/source/FileTsStreamer.java | 24 +- .../tv/tuner/source/TsDataSourceManager.java | 16 +- .../android/tv/tuner/source/TunerTsStreamer.java | 79 +- .../tv/tuner/source/TunerTsStreamerManager.java | 25 +- src/com/android/tv/tuner/ts/SectionParser.java | 569 ++- src/com/android/tv/tuner/ts/TsParser.java | 74 +- .../tv/tuner/tvinput/ChannelDataManager.java | 54 +- .../android/tv/tuner/tvinput/EventDetector.java | 101 +- .../tv/tuner/tvinput/FileSourceEventDetector.java | 45 +- src/com/android/tv/tuner/tvinput/TunerDebug.java | 4 +- .../tuner/tvinput/TunerRecordingSessionWorker.java | 116 +- src/com/android/tv/tuner/tvinput/TunerSession.java | 38 +- .../tv/tuner/tvinput/TunerSessionWorker.java | 457 ++- .../tuner/tvinput/TunerStorageCleanUpService.java | 8 + .../tv/tuner/tvinput/TunerTvInputService.java | 35 +- src/com/android/tv/tuner/util/PostalCodeUtils.java | 138 + src/com/android/tv/tuner/util/StringUtils.java | 38 - .../tv/tuner/util/SystemPropertiesProxy.java | 16 + .../android/tv/tuner/util/TunerInputInfoUtils.java | 87 +- src/com/android/tv/ui/AppLayerTvView.java | 14 +- src/com/android/tv/ui/BlockScreenView.java | 137 +- src/com/android/tv/ui/ChannelBannerView.java | 189 +- src/com/android/tv/ui/KeypadChannelSwitchView.java | 2 +- src/com/android/tv/ui/SelectInputView.java | 74 +- src/com/android/tv/ui/TunableTvView.java | 644 ++-- src/com/android/tv/ui/TvOverlayManager.java | 231 +- src/com/android/tv/ui/TvTransitionManager.java | 2 +- src/com/android/tv/ui/TvViewUiManager.java | 408 +-- src/com/android/tv/ui/sidepanel/ActionItem.java | 20 +- .../tv/ui/sidepanel/ClosedCaptionFragment.java | 108 +- .../tv/ui/sidepanel/CompoundButtonItem.java | 21 + .../ui/sidepanel/CustomizeChannelListFragment.java | 116 +- .../tv/ui/sidepanel/DeveloperOptionFragment.java | 45 +- src/com/android/tv/ui/sidepanel/Item.java | 12 + .../tv/ui/sidepanel/PipInputSelectorFragment.java | 170 - .../android/tv/ui/sidepanel/SettingsFragment.java | 137 +- src/com/android/tv/ui/sidepanel/SideFragment.java | 158 +- .../tv/ui/sidepanel/SideFragmentManager.java | 38 +- .../android/tv/ui/sidepanel/SimpleActionItem.java | 34 + src/com/android/tv/ui/sidepanel/SimpleItem.java | 34 - src/com/android/tv/ui/sidepanel/SubMenuItem.java | 13 +- src/com/android/tv/ui/sidepanel/SwitchItem.java | 5 + .../parentalcontrols/ParentalControlsFragment.java | 13 +- .../parentalcontrols/RatingsFragment.java | 42 +- .../parentalcontrols/SubRatingsFragment.java | 32 +- src/com/android/tv/util/AsyncDbTask.java | 4 +- src/com/android/tv/util/BitmapUtils.java | 10 + src/com/android/tv/util/Debug.java | 60 + src/com/android/tv/util/DurationTimer.java | 91 + src/com/android/tv/util/ImageLoader.java | 3 +- src/com/android/tv/util/LocationUtils.java | 25 +- src/com/android/tv/util/NetworkTrafficTags.java | 64 + src/com/android/tv/util/OnboardingUtils.java | 41 +- src/com/android/tv/util/Partner.java | 181 + src/com/android/tv/util/PermissionUtils.java | 5 + src/com/android/tv/util/PipInputManager.java | 432 --- src/com/android/tv/util/RecurringRunner.java | 9 +- src/com/android/tv/util/SearchManagerHelper.java | 61 - src/com/android/tv/util/SetupUtils.java | 25 +- src/com/android/tv/util/StringUtils.java | 38 + src/com/android/tv/util/TimeShiftUtils.java | 4 +- src/com/android/tv/util/TvInputManagerHelper.java | 358 +- src/com/android/tv/util/TvProviderUriMatcher.java | 72 - src/com/android/tv/util/TvSettings.java | 160 +- src/com/android/tv/util/TvTrackInfoUtils.java | 37 +- src/com/android/tv/util/TvUriMatcher.java | 80 + src/com/android/tv/util/Utils.java | 100 +- src/com/android/tv/util/ViewCache.java | 100 + tests/OWNERS | 2 + tests/common/Android.mk | 2 +- tests/common/OWNERS | 2 + tests/common/res/drawable-xhdpi/ch_1000_logo.png | Bin 0 -> 1041 bytes tests/common/res/drawable-xhdpi/ch_100_logo.png | Bin 0 -> 1128 bytes tests/common/res/drawable-xhdpi/ch_101_logo.png | Bin 0 -> 921 bytes tests/common/res/drawable-xhdpi/ch_102_logo.png | Bin 0 -> 1224 bytes tests/common/res/drawable-xhdpi/ch_103_logo.png | Bin 0 -> 971 bytes tests/common/res/drawable-xhdpi/ch_104_logo.png | Bin 0 -> 952 bytes tests/common/res/drawable-xhdpi/ch_105_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_106_logo.png | Bin 0 -> 884 bytes tests/common/res/drawable-xhdpi/ch_107_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_108_logo.png | Bin 0 -> 848 bytes tests/common/res/drawable-xhdpi/ch_109_logo.png | Bin 0 -> 842 bytes tests/common/res/drawable-xhdpi/ch_10_logo.png | Bin 0 -> 936 bytes tests/common/res/drawable-xhdpi/ch_110_logo.png | Bin 0 -> 1155 bytes tests/common/res/drawable-xhdpi/ch_111_logo.png | Bin 0 -> 915 bytes tests/common/res/drawable-xhdpi/ch_112_logo.png | Bin 0 -> 843 bytes tests/common/res/drawable-xhdpi/ch_113_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_114_logo.png | Bin 0 -> 861 bytes tests/common/res/drawable-xhdpi/ch_115_logo.png | Bin 0 -> 995 bytes tests/common/res/drawable-xhdpi/ch_116_logo.png | Bin 0 -> 1086 bytes tests/common/res/drawable-xhdpi/ch_117_logo.png | Bin 0 -> 917 bytes tests/common/res/drawable-xhdpi/ch_118_logo.png | Bin 0 -> 1131 bytes tests/common/res/drawable-xhdpi/ch_119_logo.png | Bin 0 -> 875 bytes tests/common/res/drawable-xhdpi/ch_11_logo.png | Bin 0 -> 932 bytes tests/common/res/drawable-xhdpi/ch_120_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_121_logo.png | Bin 0 -> 663 bytes tests/common/res/drawable-xhdpi/ch_122_logo.png | Bin 0 -> 845 bytes tests/common/res/drawable-xhdpi/ch_123_logo.png | Bin 0 -> 1090 bytes tests/common/res/drawable-xhdpi/ch_124_logo.png | Bin 0 -> 1033 bytes tests/common/res/drawable-xhdpi/ch_125_logo.png | Bin 0 -> 880 bytes tests/common/res/drawable-xhdpi/ch_126_logo.png | Bin 0 -> 1051 bytes tests/common/res/drawable-xhdpi/ch_127_logo.png | Bin 0 -> 1044 bytes tests/common/res/drawable-xhdpi/ch_128_logo.png | Bin 0 -> 1051 bytes tests/common/res/drawable-xhdpi/ch_129_logo.png | Bin 0 -> 983 bytes tests/common/res/drawable-xhdpi/ch_12_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_130_logo.png | Bin 0 -> 891 bytes tests/common/res/drawable-xhdpi/ch_131_logo.png | Bin 0 -> 975 bytes tests/common/res/drawable-xhdpi/ch_132_logo.png | Bin 0 -> 1076 bytes tests/common/res/drawable-xhdpi/ch_133_logo.png | Bin 0 -> 971 bytes tests/common/res/drawable-xhdpi/ch_134_logo.png | Bin 0 -> 1191 bytes tests/common/res/drawable-xhdpi/ch_135_logo.png | Bin 0 -> 1143 bytes tests/common/res/drawable-xhdpi/ch_136_logo.png | Bin 0 -> 1057 bytes tests/common/res/drawable-xhdpi/ch_137_logo.png | Bin 0 -> 1007 bytes tests/common/res/drawable-xhdpi/ch_138_logo.png | Bin 0 -> 995 bytes tests/common/res/drawable-xhdpi/ch_139_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_13_logo.png | Bin 0 -> 896 bytes tests/common/res/drawable-xhdpi/ch_140_logo.png | Bin 0 -> 1010 bytes tests/common/res/drawable-xhdpi/ch_141_logo.png | Bin 0 -> 970 bytes tests/common/res/drawable-xhdpi/ch_142_logo.png | Bin 0 -> 1103 bytes tests/common/res/drawable-xhdpi/ch_143_logo.png | Bin 0 -> 1206 bytes tests/common/res/drawable-xhdpi/ch_144_logo.png | Bin 0 -> 1023 bytes tests/common/res/drawable-xhdpi/ch_145_logo.png | Bin 0 -> 1037 bytes tests/common/res/drawable-xhdpi/ch_146_logo.png | Bin 0 -> 994 bytes tests/common/res/drawable-xhdpi/ch_147_logo.png | Bin 0 -> 828 bytes tests/common/res/drawable-xhdpi/ch_148_logo.png | Bin 0 -> 1009 bytes tests/common/res/drawable-xhdpi/ch_149_logo.png | Bin 0 -> 882 bytes tests/common/res/drawable-xhdpi/ch_14_logo.png | Bin 0 -> 1193 bytes tests/common/res/drawable-xhdpi/ch_150_logo.png | Bin 0 -> 1314 bytes tests/common/res/drawable-xhdpi/ch_151_logo.png | Bin 0 -> 931 bytes tests/common/res/drawable-xhdpi/ch_152_logo.png | Bin 0 -> 1030 bytes tests/common/res/drawable-xhdpi/ch_153_logo.png | Bin 0 -> 1245 bytes tests/common/res/drawable-xhdpi/ch_154_logo.png | Bin 0 -> 1017 bytes tests/common/res/drawable-xhdpi/ch_155_logo.png | Bin 0 -> 1317 bytes tests/common/res/drawable-xhdpi/ch_156_logo.png | Bin 0 -> 1080 bytes tests/common/res/drawable-xhdpi/ch_157_logo.png | Bin 0 -> 1126 bytes tests/common/res/drawable-xhdpi/ch_158_logo.png | Bin 0 -> 1223 bytes tests/common/res/drawable-xhdpi/ch_159_logo.png | Bin 0 -> 1362 bytes tests/common/res/drawable-xhdpi/ch_15_logo.png | Bin 0 -> 1123 bytes tests/common/res/drawable-xhdpi/ch_160_logo.png | Bin 0 -> 1059 bytes tests/common/res/drawable-xhdpi/ch_161_logo.png | Bin 0 -> 1074 bytes tests/common/res/drawable-xhdpi/ch_162_logo.png | Bin 0 -> 1015 bytes tests/common/res/drawable-xhdpi/ch_163_logo.png | Bin 0 -> 1093 bytes tests/common/res/drawable-xhdpi/ch_164_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_165_logo.png | Bin 0 -> 1001 bytes tests/common/res/drawable-xhdpi/ch_166_logo.png | Bin 0 -> 1276 bytes tests/common/res/drawable-xhdpi/ch_167_logo.png | Bin 0 -> 1184 bytes tests/common/res/drawable-xhdpi/ch_168_logo.png | Bin 0 -> 963 bytes tests/common/res/drawable-xhdpi/ch_169_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_16_logo.png | Bin 0 -> 877 bytes tests/common/res/drawable-xhdpi/ch_170_logo.png | Bin 0 -> 957 bytes tests/common/res/drawable-xhdpi/ch_171_logo.png | Bin 0 -> 978 bytes tests/common/res/drawable-xhdpi/ch_172_logo.png | Bin 0 -> 939 bytes tests/common/res/drawable-xhdpi/ch_173_logo.png | Bin 0 -> 906 bytes tests/common/res/drawable-xhdpi/ch_174_logo.png | Bin 0 -> 875 bytes tests/common/res/drawable-xhdpi/ch_175_logo.png | Bin 0 -> 974 bytes tests/common/res/drawable-xhdpi/ch_176_logo.png | Bin 0 -> 1146 bytes tests/common/res/drawable-xhdpi/ch_177_logo.png | Bin 0 -> 951 bytes tests/common/res/drawable-xhdpi/ch_178_logo.png | Bin 0 -> 1224 bytes tests/common/res/drawable-xhdpi/ch_179_logo.png | Bin 0 -> 948 bytes tests/common/res/drawable-xhdpi/ch_17_logo.png | Bin 0 -> 956 bytes tests/common/res/drawable-xhdpi/ch_180_logo.png | Bin 0 -> 980 bytes tests/common/res/drawable-xhdpi/ch_181_logo.png | Bin 0 -> 903 bytes tests/common/res/drawable-xhdpi/ch_182_logo.png | Bin 0 -> 854 bytes tests/common/res/drawable-xhdpi/ch_183_logo.png | Bin 0 -> 908 bytes tests/common/res/drawable-xhdpi/ch_184_logo.png | Bin 0 -> 804 bytes tests/common/res/drawable-xhdpi/ch_185_logo.png | Bin 0 -> 840 bytes tests/common/res/drawable-xhdpi/ch_186_logo.png | Bin 0 -> 1238 bytes tests/common/res/drawable-xhdpi/ch_187_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_188_logo.png | Bin 0 -> 883 bytes tests/common/res/drawable-xhdpi/ch_189_logo.png | Bin 0 -> 1127 bytes tests/common/res/drawable-xhdpi/ch_18_logo.png | Bin 0 -> 903 bytes tests/common/res/drawable-xhdpi/ch_190_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_191_logo.png | Bin 0 -> 984 bytes tests/common/res/drawable-xhdpi/ch_192_logo.png | Bin 0 -> 1131 bytes tests/common/res/drawable-xhdpi/ch_193_logo.png | Bin 0 -> 1050 bytes tests/common/res/drawable-xhdpi/ch_194_logo.png | Bin 0 -> 1164 bytes tests/common/res/drawable-xhdpi/ch_195_logo.png | Bin 0 -> 918 bytes tests/common/res/drawable-xhdpi/ch_196_logo.png | Bin 0 -> 1124 bytes tests/common/res/drawable-xhdpi/ch_197_logo.png | Bin 0 -> 723 bytes tests/common/res/drawable-xhdpi/ch_198_logo.png | Bin 0 -> 929 bytes tests/common/res/drawable-xhdpi/ch_199_logo.png | Bin 0 -> 1072 bytes tests/common/res/drawable-xhdpi/ch_19_logo.png | Bin 0 -> 1011 bytes tests/common/res/drawable-xhdpi/ch_1_logo.png | Bin 0 -> 1009 bytes tests/common/res/drawable-xhdpi/ch_200_logo.png | Bin 0 -> 1019 bytes tests/common/res/drawable-xhdpi/ch_201_logo.png | Bin 0 -> 923 bytes tests/common/res/drawable-xhdpi/ch_202_logo.png | Bin 0 -> 1029 bytes tests/common/res/drawable-xhdpi/ch_203_logo.png | Bin 0 -> 1107 bytes tests/common/res/drawable-xhdpi/ch_204_logo.png | Bin 0 -> 1148 bytes tests/common/res/drawable-xhdpi/ch_205_logo.png | Bin 0 -> 954 bytes tests/common/res/drawable-xhdpi/ch_206_logo.png | Bin 0 -> 940 bytes tests/common/res/drawable-xhdpi/ch_207_logo.png | Bin 0 -> 1062 bytes tests/common/res/drawable-xhdpi/ch_208_logo.png | Bin 0 -> 1108 bytes tests/common/res/drawable-xhdpi/ch_209_logo.png | Bin 0 -> 1062 bytes tests/common/res/drawable-xhdpi/ch_20_logo.png | Bin 0 -> 973 bytes tests/common/res/drawable-xhdpi/ch_210_logo.png | Bin 0 -> 1230 bytes tests/common/res/drawable-xhdpi/ch_211_logo.png | Bin 0 -> 1048 bytes tests/common/res/drawable-xhdpi/ch_212_logo.png | Bin 0 -> 962 bytes tests/common/res/drawable-xhdpi/ch_213_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_214_logo.png | Bin 0 -> 888 bytes tests/common/res/drawable-xhdpi/ch_215_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_216_logo.png | Bin 0 -> 1038 bytes tests/common/res/drawable-xhdpi/ch_217_logo.png | Bin 0 -> 1058 bytes tests/common/res/drawable-xhdpi/ch_218_logo.png | Bin 0 -> 1124 bytes tests/common/res/drawable-xhdpi/ch_219_logo.png | Bin 0 -> 1277 bytes tests/common/res/drawable-xhdpi/ch_21_logo.png | Bin 0 -> 833 bytes tests/common/res/drawable-xhdpi/ch_220_logo.png | Bin 0 -> 1100 bytes tests/common/res/drawable-xhdpi/ch_221_logo.png | Bin 0 -> 972 bytes tests/common/res/drawable-xhdpi/ch_222_logo.png | Bin 0 -> 943 bytes tests/common/res/drawable-xhdpi/ch_223_logo.png | Bin 0 -> 850 bytes tests/common/res/drawable-xhdpi/ch_224_logo.png | Bin 0 -> 967 bytes tests/common/res/drawable-xhdpi/ch_225_logo.png | Bin 0 -> 898 bytes tests/common/res/drawable-xhdpi/ch_226_logo.png | Bin 0 -> 1287 bytes tests/common/res/drawable-xhdpi/ch_227_logo.png | Bin 0 -> 1013 bytes tests/common/res/drawable-xhdpi/ch_228_logo.png | Bin 0 -> 999 bytes tests/common/res/drawable-xhdpi/ch_229_logo.png | Bin 0 -> 1209 bytes tests/common/res/drawable-xhdpi/ch_22_logo.png | Bin 0 -> 814 bytes tests/common/res/drawable-xhdpi/ch_230_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_231_logo.png | Bin 0 -> 1371 bytes tests/common/res/drawable-xhdpi/ch_232_logo.png | Bin 0 -> 1038 bytes tests/common/res/drawable-xhdpi/ch_233_logo.png | Bin 0 -> 1234 bytes tests/common/res/drawable-xhdpi/ch_234_logo.png | Bin 0 -> 1122 bytes tests/common/res/drawable-xhdpi/ch_235_logo.png | Bin 0 -> 1351 bytes tests/common/res/drawable-xhdpi/ch_236_logo.png | Bin 0 -> 1089 bytes tests/common/res/drawable-xhdpi/ch_237_logo.png | Bin 0 -> 1143 bytes tests/common/res/drawable-xhdpi/ch_238_logo.png | Bin 0 -> 1028 bytes tests/common/res/drawable-xhdpi/ch_239_logo.png | Bin 0 -> 1116 bytes tests/common/res/drawable-xhdpi/ch_23_logo.png | Bin 0 -> 905 bytes tests/common/res/drawable-xhdpi/ch_240_logo.png | Bin 0 -> 1101 bytes tests/common/res/drawable-xhdpi/ch_241_logo.png | Bin 0 -> 959 bytes tests/common/res/drawable-xhdpi/ch_242_logo.png | Bin 0 -> 1238 bytes tests/common/res/drawable-xhdpi/ch_243_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_244_logo.png | Bin 0 -> 849 bytes tests/common/res/drawable-xhdpi/ch_245_logo.png | Bin 0 -> 1058 bytes tests/common/res/drawable-xhdpi/ch_246_logo.png | Bin 0 -> 991 bytes tests/common/res/drawable-xhdpi/ch_247_logo.png | Bin 0 -> 991 bytes tests/common/res/drawable-xhdpi/ch_248_logo.png | Bin 0 -> 1034 bytes tests/common/res/drawable-xhdpi/ch_249_logo.png | Bin 0 -> 942 bytes tests/common/res/drawable-xhdpi/ch_24_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_250_logo.png | Bin 0 -> 955 bytes tests/common/res/drawable-xhdpi/ch_251_logo.png | Bin 0 -> 966 bytes tests/common/res/drawable-xhdpi/ch_252_logo.png | Bin 0 -> 1048 bytes tests/common/res/drawable-xhdpi/ch_253_logo.png | Bin 0 -> 1084 bytes tests/common/res/drawable-xhdpi/ch_254_logo.png | Bin 0 -> 1231 bytes tests/common/res/drawable-xhdpi/ch_255_logo.png | Bin 0 -> 917 bytes tests/common/res/drawable-xhdpi/ch_256_logo.png | Bin 0 -> 999 bytes tests/common/res/drawable-xhdpi/ch_257_logo.png | Bin 0 -> 963 bytes tests/common/res/drawable-xhdpi/ch_258_logo.png | Bin 0 -> 968 bytes tests/common/res/drawable-xhdpi/ch_259_logo.png | Bin 0 -> 970 bytes tests/common/res/drawable-xhdpi/ch_25_logo.png | Bin 0 -> 994 bytes tests/common/res/drawable-xhdpi/ch_260_logo.png | Bin 0 -> 858 bytes tests/common/res/drawable-xhdpi/ch_261_logo.png | Bin 0 -> 809 bytes tests/common/res/drawable-xhdpi/ch_262_logo.png | Bin 0 -> 1179 bytes tests/common/res/drawable-xhdpi/ch_263_logo.png | Bin 0 -> 1170 bytes tests/common/res/drawable-xhdpi/ch_264_logo.png | Bin 0 -> 914 bytes tests/common/res/drawable-xhdpi/ch_265_logo.png | Bin 0 -> 1154 bytes tests/common/res/drawable-xhdpi/ch_266_logo.png | Bin 0 -> 947 bytes tests/common/res/drawable-xhdpi/ch_267_logo.png | Bin 0 -> 986 bytes tests/common/res/drawable-xhdpi/ch_268_logo.png | Bin 0 -> 1103 bytes tests/common/res/drawable-xhdpi/ch_269_logo.png | Bin 0 -> 1075 bytes tests/common/res/drawable-xhdpi/ch_26_logo.png | Bin 0 -> 1193 bytes tests/common/res/drawable-xhdpi/ch_270_logo.png | Bin 0 -> 1143 bytes tests/common/res/drawable-xhdpi/ch_271_logo.png | Bin 0 -> 872 bytes tests/common/res/drawable-xhdpi/ch_272_logo.png | Bin 0 -> 1057 bytes tests/common/res/drawable-xhdpi/ch_273_logo.png | Bin 0 -> 748 bytes tests/common/res/drawable-xhdpi/ch_274_logo.png | Bin 0 -> 896 bytes tests/common/res/drawable-xhdpi/ch_275_logo.png | Bin 0 -> 1095 bytes tests/common/res/drawable-xhdpi/ch_276_logo.png | Bin 0 -> 1061 bytes tests/common/res/drawable-xhdpi/ch_277_logo.png | Bin 0 -> 847 bytes tests/common/res/drawable-xhdpi/ch_278_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_279_logo.png | Bin 0 -> 1008 bytes tests/common/res/drawable-xhdpi/ch_27_logo.png | Bin 0 -> 887 bytes tests/common/res/drawable-xhdpi/ch_280_logo.png | Bin 0 -> 1142 bytes tests/common/res/drawable-xhdpi/ch_281_logo.png | Bin 0 -> 980 bytes tests/common/res/drawable-xhdpi/ch_282_logo.png | Bin 0 -> 833 bytes tests/common/res/drawable-xhdpi/ch_283_logo.png | Bin 0 -> 1080 bytes tests/common/res/drawable-xhdpi/ch_284_logo.png | Bin 0 -> 1082 bytes tests/common/res/drawable-xhdpi/ch_285_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_286_logo.png | Bin 0 -> 1254 bytes tests/common/res/drawable-xhdpi/ch_287_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_288_logo.png | Bin 0 -> 1013 bytes tests/common/res/drawable-xhdpi/ch_289_logo.png | Bin 0 -> 1058 bytes tests/common/res/drawable-xhdpi/ch_28_logo.png | Bin 0 -> 928 bytes tests/common/res/drawable-xhdpi/ch_290_logo.png | Bin 0 -> 1002 bytes tests/common/res/drawable-xhdpi/ch_291_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_292_logo.png | Bin 0 -> 946 bytes tests/common/res/drawable-xhdpi/ch_293_logo.png | Bin 0 -> 1160 bytes tests/common/res/drawable-xhdpi/ch_294_logo.png | Bin 0 -> 1133 bytes tests/common/res/drawable-xhdpi/ch_295_logo.png | Bin 0 -> 1303 bytes tests/common/res/drawable-xhdpi/ch_296_logo.png | Bin 0 -> 1085 bytes tests/common/res/drawable-xhdpi/ch_297_logo.png | Bin 0 -> 1060 bytes tests/common/res/drawable-xhdpi/ch_298_logo.png | Bin 0 -> 1028 bytes tests/common/res/drawable-xhdpi/ch_299_logo.png | Bin 0 -> 842 bytes tests/common/res/drawable-xhdpi/ch_29_logo.png | Bin 0 -> 886 bytes tests/common/res/drawable-xhdpi/ch_2_logo.png | Bin 0 -> 907 bytes tests/common/res/drawable-xhdpi/ch_300_logo.png | Bin 0 -> 934 bytes tests/common/res/drawable-xhdpi/ch_301_logo.png | Bin 0 -> 887 bytes tests/common/res/drawable-xhdpi/ch_302_logo.png | Bin 0 -> 1338 bytes tests/common/res/drawable-xhdpi/ch_303_logo.png | Bin 0 -> 1055 bytes tests/common/res/drawable-xhdpi/ch_304_logo.png | Bin 0 -> 1045 bytes tests/common/res/drawable-xhdpi/ch_305_logo.png | Bin 0 -> 1282 bytes tests/common/res/drawable-xhdpi/ch_306_logo.png | Bin 0 -> 1027 bytes tests/common/res/drawable-xhdpi/ch_307_logo.png | Bin 0 -> 1272 bytes tests/common/res/drawable-xhdpi/ch_308_logo.png | Bin 0 -> 1115 bytes tests/common/res/drawable-xhdpi/ch_309_logo.png | Bin 0 -> 1324 bytes tests/common/res/drawable-xhdpi/ch_30_logo.png | Bin 0 -> 894 bytes tests/common/res/drawable-xhdpi/ch_310_logo.png | Bin 0 -> 1222 bytes tests/common/res/drawable-xhdpi/ch_311_logo.png | Bin 0 -> 1303 bytes tests/common/res/drawable-xhdpi/ch_312_logo.png | Bin 0 -> 1057 bytes tests/common/res/drawable-xhdpi/ch_313_logo.png | Bin 0 -> 1085 bytes tests/common/res/drawable-xhdpi/ch_314_logo.png | Bin 0 -> 990 bytes tests/common/res/drawable-xhdpi/ch_315_logo.png | Bin 0 -> 1109 bytes tests/common/res/drawable-xhdpi/ch_316_logo.png | Bin 0 -> 1109 bytes tests/common/res/drawable-xhdpi/ch_317_logo.png | Bin 0 -> 968 bytes tests/common/res/drawable-xhdpi/ch_318_logo.png | Bin 0 -> 1206 bytes tests/common/res/drawable-xhdpi/ch_319_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_31_logo.png | Bin 0 -> 858 bytes tests/common/res/drawable-xhdpi/ch_320_logo.png | Bin 0 -> 979 bytes tests/common/res/drawable-xhdpi/ch_321_logo.png | Bin 0 -> 1092 bytes tests/common/res/drawable-xhdpi/ch_322_logo.png | Bin 0 -> 939 bytes tests/common/res/drawable-xhdpi/ch_323_logo.png | Bin 0 -> 1017 bytes tests/common/res/drawable-xhdpi/ch_324_logo.png | Bin 0 -> 1008 bytes tests/common/res/drawable-xhdpi/ch_325_logo.png | Bin 0 -> 953 bytes tests/common/res/drawable-xhdpi/ch_326_logo.png | Bin 0 -> 966 bytes tests/common/res/drawable-xhdpi/ch_327_logo.png | Bin 0 -> 1000 bytes tests/common/res/drawable-xhdpi/ch_328_logo.png | Bin 0 -> 1205 bytes tests/common/res/drawable-xhdpi/ch_329_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_32_logo.png | Bin 0 -> 781 bytes tests/common/res/drawable-xhdpi/ch_330_logo.png | Bin 0 -> 1201 bytes tests/common/res/drawable-xhdpi/ch_331_logo.png | Bin 0 -> 899 bytes tests/common/res/drawable-xhdpi/ch_332_logo.png | Bin 0 -> 946 bytes tests/common/res/drawable-xhdpi/ch_333_logo.png | Bin 0 -> 863 bytes tests/common/res/drawable-xhdpi/ch_334_logo.png | Bin 0 -> 857 bytes tests/common/res/drawable-xhdpi/ch_335_logo.png | Bin 0 -> 913 bytes tests/common/res/drawable-xhdpi/ch_336_logo.png | Bin 0 -> 805 bytes tests/common/res/drawable-xhdpi/ch_337_logo.png | Bin 0 -> 773 bytes tests/common/res/drawable-xhdpi/ch_338_logo.png | Bin 0 -> 1167 bytes tests/common/res/drawable-xhdpi/ch_339_logo.png | Bin 0 -> 1110 bytes tests/common/res/drawable-xhdpi/ch_33_logo.png | Bin 0 -> 708 bytes tests/common/res/drawable-xhdpi/ch_340_logo.png | Bin 0 -> 937 bytes tests/common/res/drawable-xhdpi/ch_341_logo.png | Bin 0 -> 1070 bytes tests/common/res/drawable-xhdpi/ch_342_logo.png | Bin 0 -> 984 bytes tests/common/res/drawable-xhdpi/ch_343_logo.png | Bin 0 -> 1010 bytes tests/common/res/drawable-xhdpi/ch_344_logo.png | Bin 0 -> 1091 bytes tests/common/res/drawable-xhdpi/ch_345_logo.png | Bin 0 -> 996 bytes tests/common/res/drawable-xhdpi/ch_346_logo.png | Bin 0 -> 1243 bytes tests/common/res/drawable-xhdpi/ch_347_logo.png | Bin 0 -> 920 bytes tests/common/res/drawable-xhdpi/ch_348_logo.png | Bin 0 -> 1103 bytes tests/common/res/drawable-xhdpi/ch_349_logo.png | Bin 0 -> 770 bytes tests/common/res/drawable-xhdpi/ch_34_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_350_logo.png | Bin 0 -> 961 bytes tests/common/res/drawable-xhdpi/ch_351_logo.png | Bin 0 -> 1147 bytes tests/common/res/drawable-xhdpi/ch_352_logo.png | Bin 0 -> 1036 bytes tests/common/res/drawable-xhdpi/ch_353_logo.png | Bin 0 -> 920 bytes tests/common/res/drawable-xhdpi/ch_354_logo.png | Bin 0 -> 1075 bytes tests/common/res/drawable-xhdpi/ch_355_logo.png | Bin 0 -> 1010 bytes tests/common/res/drawable-xhdpi/ch_356_logo.png | Bin 0 -> 1176 bytes tests/common/res/drawable-xhdpi/ch_357_logo.png | Bin 0 -> 997 bytes tests/common/res/drawable-xhdpi/ch_358_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_359_logo.png | Bin 0 -> 1102 bytes tests/common/res/drawable-xhdpi/ch_35_logo.png | Bin 0 -> 1038 bytes tests/common/res/drawable-xhdpi/ch_360_logo.png | Bin 0 -> 1119 bytes tests/common/res/drawable-xhdpi/ch_361_logo.png | Bin 0 -> 1025 bytes tests/common/res/drawable-xhdpi/ch_362_logo.png | Bin 0 -> 1264 bytes tests/common/res/drawable-xhdpi/ch_363_logo.png | Bin 0 -> 1099 bytes tests/common/res/drawable-xhdpi/ch_364_logo.png | Bin 0 -> 1032 bytes tests/common/res/drawable-xhdpi/ch_365_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_366_logo.png | Bin 0 -> 967 bytes tests/common/res/drawable-xhdpi/ch_367_logo.png | Bin 0 -> 1061 bytes tests/common/res/drawable-xhdpi/ch_368_logo.png | Bin 0 -> 1023 bytes tests/common/res/drawable-xhdpi/ch_369_logo.png | Bin 0 -> 1178 bytes tests/common/res/drawable-xhdpi/ch_36_logo.png | Bin 0 -> 847 bytes tests/common/res/drawable-xhdpi/ch_370_logo.png | Bin 0 -> 1148 bytes tests/common/res/drawable-xhdpi/ch_371_logo.png | Bin 0 -> 1249 bytes tests/common/res/drawable-xhdpi/ch_372_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_373_logo.png | Bin 0 -> 999 bytes tests/common/res/drawable-xhdpi/ch_374_logo.png | Bin 0 -> 989 bytes tests/common/res/drawable-xhdpi/ch_375_logo.png | Bin 0 -> 900 bytes tests/common/res/drawable-xhdpi/ch_376_logo.png | Bin 0 -> 1031 bytes tests/common/res/drawable-xhdpi/ch_377_logo.png | Bin 0 -> 886 bytes tests/common/res/drawable-xhdpi/ch_378_logo.png | Bin 0 -> 1334 bytes tests/common/res/drawable-xhdpi/ch_379_logo.png | Bin 0 -> 1099 bytes tests/common/res/drawable-xhdpi/ch_37_logo.png | Bin 0 -> 1060 bytes tests/common/res/drawable-xhdpi/ch_380_logo.png | Bin 0 -> 1009 bytes tests/common/res/drawable-xhdpi/ch_381_logo.png | Bin 0 -> 1223 bytes tests/common/res/drawable-xhdpi/ch_382_logo.png | Bin 0 -> 1072 bytes tests/common/res/drawable-xhdpi/ch_383_logo.png | Bin 0 -> 1249 bytes tests/common/res/drawable-xhdpi/ch_384_logo.png | Bin 0 -> 1083 bytes tests/common/res/drawable-xhdpi/ch_385_logo.png | Bin 0 -> 1344 bytes tests/common/res/drawable-xhdpi/ch_386_logo.png | Bin 0 -> 1244 bytes tests/common/res/drawable-xhdpi/ch_387_logo.png | Bin 0 -> 1380 bytes tests/common/res/drawable-xhdpi/ch_388_logo.png | Bin 0 -> 981 bytes tests/common/res/drawable-xhdpi/ch_389_logo.png | Bin 0 -> 1197 bytes tests/common/res/drawable-xhdpi/ch_38_logo.png | Bin 0 -> 938 bytes tests/common/res/drawable-xhdpi/ch_390_logo.png | Bin 0 -> 986 bytes tests/common/res/drawable-xhdpi/ch_391_logo.png | Bin 0 -> 1084 bytes tests/common/res/drawable-xhdpi/ch_392_logo.png | Bin 0 -> 1118 bytes tests/common/res/drawable-xhdpi/ch_393_logo.png | Bin 0 -> 981 bytes tests/common/res/drawable-xhdpi/ch_394_logo.png | Bin 0 -> 1231 bytes tests/common/res/drawable-xhdpi/ch_395_logo.png | Bin 0 -> 1243 bytes tests/common/res/drawable-xhdpi/ch_396_logo.png | Bin 0 -> 995 bytes tests/common/res/drawable-xhdpi/ch_397_logo.png | Bin 0 -> 1118 bytes tests/common/res/drawable-xhdpi/ch_398_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_399_logo.png | Bin 0 -> 1049 bytes tests/common/res/drawable-xhdpi/ch_39_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_3_logo.png | Bin 0 -> 1160 bytes tests/common/res/drawable-xhdpi/ch_400_logo.png | Bin 0 -> 974 bytes tests/common/res/drawable-xhdpi/ch_401_logo.png | Bin 0 -> 862 bytes tests/common/res/drawable-xhdpi/ch_402_logo.png | Bin 0 -> 945 bytes tests/common/res/drawable-xhdpi/ch_403_logo.png | Bin 0 -> 1004 bytes tests/common/res/drawable-xhdpi/ch_404_logo.png | Bin 0 -> 1106 bytes tests/common/res/drawable-xhdpi/ch_405_logo.png | Bin 0 -> 1051 bytes tests/common/res/drawable-xhdpi/ch_406_logo.png | Bin 0 -> 1184 bytes tests/common/res/drawable-xhdpi/ch_407_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_408_logo.png | Bin 0 -> 992 bytes tests/common/res/drawable-xhdpi/ch_409_logo.png | Bin 0 -> 988 bytes tests/common/res/drawable-xhdpi/ch_40_logo.png | Bin 0 -> 1078 bytes tests/common/res/drawable-xhdpi/ch_410_logo.png | Bin 0 -> 915 bytes tests/common/res/drawable-xhdpi/ch_411_logo.png | Bin 0 -> 842 bytes tests/common/res/drawable-xhdpi/ch_412_logo.png | Bin 0 -> 814 bytes tests/common/res/drawable-xhdpi/ch_413_logo.png | Bin 0 -> 817 bytes tests/common/res/drawable-xhdpi/ch_414_logo.png | Bin 0 -> 1120 bytes tests/common/res/drawable-xhdpi/ch_415_logo.png | Bin 0 -> 1116 bytes tests/common/res/drawable-xhdpi/ch_416_logo.png | Bin 0 -> 923 bytes tests/common/res/drawable-xhdpi/ch_417_logo.png | Bin 0 -> 1044 bytes tests/common/res/drawable-xhdpi/ch_418_logo.png | Bin 0 -> 946 bytes tests/common/res/drawable-xhdpi/ch_419_logo.png | Bin 0 -> 1045 bytes tests/common/res/drawable-xhdpi/ch_41_logo.png | Bin 0 -> 857 bytes tests/common/res/drawable-xhdpi/ch_420_logo.png | Bin 0 -> 1056 bytes tests/common/res/drawable-xhdpi/ch_421_logo.png | Bin 0 -> 1003 bytes tests/common/res/drawable-xhdpi/ch_422_logo.png | Bin 0 -> 1154 bytes tests/common/res/drawable-xhdpi/ch_423_logo.png | Bin 0 -> 921 bytes tests/common/res/drawable-xhdpi/ch_424_logo.png | Bin 0 -> 1069 bytes tests/common/res/drawable-xhdpi/ch_425_logo.png | Bin 0 -> 744 bytes tests/common/res/drawable-xhdpi/ch_426_logo.png | Bin 0 -> 923 bytes tests/common/res/drawable-xhdpi/ch_427_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_428_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_429_logo.png | Bin 0 -> 940 bytes tests/common/res/drawable-xhdpi/ch_42_logo.png | Bin 0 -> 1135 bytes tests/common/res/drawable-xhdpi/ch_430_logo.png | Bin 0 -> 1072 bytes tests/common/res/drawable-xhdpi/ch_431_logo.png | Bin 0 -> 1059 bytes tests/common/res/drawable-xhdpi/ch_432_logo.png | Bin 0 -> 1149 bytes tests/common/res/drawable-xhdpi/ch_433_logo.png | Bin 0 -> 933 bytes tests/common/res/drawable-xhdpi/ch_434_logo.png | Bin 0 -> 867 bytes tests/common/res/drawable-xhdpi/ch_435_logo.png | Bin 0 -> 1055 bytes tests/common/res/drawable-xhdpi/ch_436_logo.png | Bin 0 -> 1039 bytes tests/common/res/drawable-xhdpi/ch_437_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_438_logo.png | Bin 0 -> 1241 bytes tests/common/res/drawable-xhdpi/ch_439_logo.png | Bin 0 -> 1134 bytes tests/common/res/drawable-xhdpi/ch_43_logo.png | Bin 0 -> 810 bytes tests/common/res/drawable-xhdpi/ch_440_logo.png | Bin 0 -> 987 bytes tests/common/res/drawable-xhdpi/ch_441_logo.png | Bin 0 -> 905 bytes tests/common/res/drawable-xhdpi/ch_442_logo.png | Bin 0 -> 906 bytes tests/common/res/drawable-xhdpi/ch_443_logo.png | Bin 0 -> 1071 bytes tests/common/res/drawable-xhdpi/ch_444_logo.png | Bin 0 -> 871 bytes tests/common/res/drawable-xhdpi/ch_445_logo.png | Bin 0 -> 1073 bytes tests/common/res/drawable-xhdpi/ch_446_logo.png | Bin 0 -> 1083 bytes tests/common/res/drawable-xhdpi/ch_447_logo.png | Bin 0 -> 1196 bytes tests/common/res/drawable-xhdpi/ch_448_logo.png | Bin 0 -> 1077 bytes tests/common/res/drawable-xhdpi/ch_449_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_44_logo.png | Bin 0 -> 966 bytes tests/common/res/drawable-xhdpi/ch_450_logo.png | Bin 0 -> 1066 bytes tests/common/res/drawable-xhdpi/ch_451_logo.png | Bin 0 -> 845 bytes tests/common/res/drawable-xhdpi/ch_452_logo.png | Bin 0 -> 1028 bytes tests/common/res/drawable-xhdpi/ch_453_logo.png | Bin 0 -> 946 bytes tests/common/res/drawable-xhdpi/ch_454_logo.png | Bin 0 -> 1228 bytes tests/common/res/drawable-xhdpi/ch_455_logo.png | Bin 0 -> 1035 bytes tests/common/res/drawable-xhdpi/ch_456_logo.png | Bin 0 -> 1061 bytes tests/common/res/drawable-xhdpi/ch_457_logo.png | Bin 0 -> 1162 bytes tests/common/res/drawable-xhdpi/ch_458_logo.png | Bin 0 -> 1067 bytes tests/common/res/drawable-xhdpi/ch_459_logo.png | Bin 0 -> 1384 bytes tests/common/res/drawable-xhdpi/ch_45_logo.png | Bin 0 -> 674 bytes tests/common/res/drawable-xhdpi/ch_460_logo.png | Bin 0 -> 1087 bytes tests/common/res/drawable-xhdpi/ch_461_logo.png | Bin 0 -> 1234 bytes tests/common/res/drawable-xhdpi/ch_462_logo.png | Bin 0 -> 1212 bytes tests/common/res/drawable-xhdpi/ch_463_logo.png | Bin 0 -> 1360 bytes tests/common/res/drawable-xhdpi/ch_464_logo.png | Bin 0 -> 1024 bytes tests/common/res/drawable-xhdpi/ch_465_logo.png | Bin 0 -> 1159 bytes tests/common/res/drawable-xhdpi/ch_466_logo.png | Bin 0 -> 973 bytes tests/common/res/drawable-xhdpi/ch_467_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_468_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_469_logo.png | Bin 0 -> 1011 bytes tests/common/res/drawable-xhdpi/ch_46_logo.png | Bin 0 -> 815 bytes tests/common/res/drawable-xhdpi/ch_470_logo.png | Bin 0 -> 1271 bytes tests/common/res/drawable-xhdpi/ch_471_logo.png | Bin 0 -> 1096 bytes tests/common/res/drawable-xhdpi/ch_472_logo.png | Bin 0 -> 925 bytes tests/common/res/drawable-xhdpi/ch_473_logo.png | Bin 0 -> 1104 bytes tests/common/res/drawable-xhdpi/ch_474_logo.png | Bin 0 -> 892 bytes tests/common/res/drawable-xhdpi/ch_475_logo.png | Bin 0 -> 1065 bytes tests/common/res/drawable-xhdpi/ch_476_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_477_logo.png | Bin 0 -> 833 bytes tests/common/res/drawable-xhdpi/ch_478_logo.png | Bin 0 -> 936 bytes tests/common/res/drawable-xhdpi/ch_479_logo.png | Bin 0 -> 991 bytes tests/common/res/drawable-xhdpi/ch_47_logo.png | Bin 0 -> 1054 bytes tests/common/res/drawable-xhdpi/ch_480_logo.png | Bin 0 -> 1193 bytes tests/common/res/drawable-xhdpi/ch_481_logo.png | Bin 0 -> 1001 bytes tests/common/res/drawable-xhdpi/ch_482_logo.png | Bin 0 -> 1242 bytes tests/common/res/drawable-xhdpi/ch_483_logo.png | Bin 0 -> 981 bytes tests/common/res/drawable-xhdpi/ch_484_logo.png | Bin 0 -> 870 bytes tests/common/res/drawable-xhdpi/ch_485_logo.png | Bin 0 -> 992 bytes tests/common/res/drawable-xhdpi/ch_486_logo.png | Bin 0 -> 899 bytes tests/common/res/drawable-xhdpi/ch_487_logo.png | Bin 0 -> 930 bytes tests/common/res/drawable-xhdpi/ch_488_logo.png | Bin 0 -> 790 bytes tests/common/res/drawable-xhdpi/ch_489_logo.png | Bin 0 -> 846 bytes tests/common/res/drawable-xhdpi/ch_48_logo.png | Bin 0 -> 1008 bytes tests/common/res/drawable-xhdpi/ch_490_logo.png | Bin 0 -> 1228 bytes tests/common/res/drawable-xhdpi/ch_491_logo.png | Bin 0 -> 1125 bytes tests/common/res/drawable-xhdpi/ch_492_logo.png | Bin 0 -> 940 bytes tests/common/res/drawable-xhdpi/ch_493_logo.png | Bin 0 -> 1151 bytes tests/common/res/drawable-xhdpi/ch_494_logo.png | Bin 0 -> 866 bytes tests/common/res/drawable-xhdpi/ch_495_logo.png | Bin 0 -> 1072 bytes tests/common/res/drawable-xhdpi/ch_496_logo.png | Bin 0 -> 1155 bytes tests/common/res/drawable-xhdpi/ch_497_logo.png | Bin 0 -> 945 bytes tests/common/res/drawable-xhdpi/ch_498_logo.png | Bin 0 -> 1241 bytes tests/common/res/drawable-xhdpi/ch_499_logo.png | Bin 0 -> 873 bytes tests/common/res/drawable-xhdpi/ch_49_logo.png | Bin 0 -> 872 bytes tests/common/res/drawable-xhdpi/ch_4_logo.png | Bin 0 -> 852 bytes tests/common/res/drawable-xhdpi/ch_500_logo.png | Bin 0 -> 1022 bytes tests/common/res/drawable-xhdpi/ch_501_logo.png | Bin 0 -> 745 bytes tests/common/res/drawable-xhdpi/ch_502_logo.png | Bin 0 -> 944 bytes tests/common/res/drawable-xhdpi/ch_503_logo.png | Bin 0 -> 1198 bytes tests/common/res/drawable-xhdpi/ch_504_logo.png | Bin 0 -> 1087 bytes tests/common/res/drawable-xhdpi/ch_505_logo.png | Bin 0 -> 928 bytes tests/common/res/drawable-xhdpi/ch_506_logo.png | Bin 0 -> 1104 bytes tests/common/res/drawable-xhdpi/ch_507_logo.png | Bin 0 -> 1088 bytes tests/common/res/drawable-xhdpi/ch_508_logo.png | Bin 0 -> 1109 bytes tests/common/res/drawable-xhdpi/ch_509_logo.png | Bin 0 -> 973 bytes tests/common/res/drawable-xhdpi/ch_50_logo.png | Bin 0 -> 992 bytes tests/common/res/drawable-xhdpi/ch_510_logo.png | Bin 0 -> 935 bytes tests/common/res/drawable-xhdpi/ch_511_logo.png | Bin 0 -> 981 bytes tests/common/res/drawable-xhdpi/ch_512_logo.png | Bin 0 -> 1078 bytes tests/common/res/drawable-xhdpi/ch_513_logo.png | Bin 0 -> 1065 bytes tests/common/res/drawable-xhdpi/ch_514_logo.png | Bin 0 -> 1132 bytes tests/common/res/drawable-xhdpi/ch_515_logo.png | Bin 0 -> 1066 bytes tests/common/res/drawable-xhdpi/ch_516_logo.png | Bin 0 -> 1061 bytes tests/common/res/drawable-xhdpi/ch_517_logo.png | Bin 0 -> 1005 bytes tests/common/res/drawable-xhdpi/ch_518_logo.png | Bin 0 -> 975 bytes tests/common/res/drawable-xhdpi/ch_519_logo.png | Bin 0 -> 1143 bytes tests/common/res/drawable-xhdpi/ch_51_logo.png | Bin 0 -> 1003 bytes tests/common/res/drawable-xhdpi/ch_520_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_521_logo.png | Bin 0 -> 1123 bytes tests/common/res/drawable-xhdpi/ch_522_logo.png | Bin 0 -> 1086 bytes tests/common/res/drawable-xhdpi/ch_523_logo.png | Bin 0 -> 1308 bytes tests/common/res/drawable-xhdpi/ch_524_logo.png | Bin 0 -> 1129 bytes tests/common/res/drawable-xhdpi/ch_525_logo.png | Bin 0 -> 1021 bytes tests/common/res/drawable-xhdpi/ch_526_logo.png | Bin 0 -> 1020 bytes tests/common/res/drawable-xhdpi/ch_527_logo.png | Bin 0 -> 879 bytes tests/common/res/drawable-xhdpi/ch_528_logo.png | Bin 0 -> 1043 bytes tests/common/res/drawable-xhdpi/ch_529_logo.png | Bin 0 -> 961 bytes tests/common/res/drawable-xhdpi/ch_52_logo.png | Bin 0 -> 1083 bytes tests/common/res/drawable-xhdpi/ch_530_logo.png | Bin 0 -> 1332 bytes tests/common/res/drawable-xhdpi/ch_531_logo.png | Bin 0 -> 1071 bytes tests/common/res/drawable-xhdpi/ch_532_logo.png | Bin 0 -> 1071 bytes tests/common/res/drawable-xhdpi/ch_533_logo.png | Bin 0 -> 1211 bytes tests/common/res/drawable-xhdpi/ch_534_logo.png | Bin 0 -> 1058 bytes tests/common/res/drawable-xhdpi/ch_535_logo.png | Bin 0 -> 1380 bytes tests/common/res/drawable-xhdpi/ch_536_logo.png | Bin 0 -> 1124 bytes tests/common/res/drawable-xhdpi/ch_537_logo.png | Bin 0 -> 1298 bytes tests/common/res/drawable-xhdpi/ch_538_logo.png | Bin 0 -> 1273 bytes tests/common/res/drawable-xhdpi/ch_539_logo.png | Bin 0 -> 1391 bytes tests/common/res/drawable-xhdpi/ch_53_logo.png | Bin 0 -> 938 bytes tests/common/res/drawable-xhdpi/ch_540_logo.png | Bin 0 -> 1086 bytes tests/common/res/drawable-xhdpi/ch_541_logo.png | Bin 0 -> 1127 bytes tests/common/res/drawable-xhdpi/ch_542_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_543_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_544_logo.png | Bin 0 -> 1059 bytes tests/common/res/drawable-xhdpi/ch_545_logo.png | Bin 0 -> 949 bytes tests/common/res/drawable-xhdpi/ch_546_logo.png | Bin 0 -> 1343 bytes tests/common/res/drawable-xhdpi/ch_547_logo.png | Bin 0 -> 1138 bytes tests/common/res/drawable-xhdpi/ch_548_logo.png | Bin 0 -> 975 bytes tests/common/res/drawable-xhdpi/ch_549_logo.png | Bin 0 -> 1144 bytes tests/common/res/drawable-xhdpi/ch_54_logo.png | Bin 0 -> 808 bytes tests/common/res/drawable-xhdpi/ch_550_logo.png | Bin 0 -> 923 bytes tests/common/res/drawable-xhdpi/ch_551_logo.png | Bin 0 -> 953 bytes tests/common/res/drawable-xhdpi/ch_552_logo.png | Bin 0 -> 1005 bytes tests/common/res/drawable-xhdpi/ch_553_logo.png | Bin 0 -> 918 bytes tests/common/res/drawable-xhdpi/ch_554_logo.png | Bin 0 -> 892 bytes tests/common/res/drawable-xhdpi/ch_555_logo.png | Bin 0 -> 903 bytes tests/common/res/drawable-xhdpi/ch_556_logo.png | Bin 0 -> 1172 bytes tests/common/res/drawable-xhdpi/ch_557_logo.png | Bin 0 -> 1005 bytes tests/common/res/drawable-xhdpi/ch_558_logo.png | Bin 0 -> 1241 bytes tests/common/res/drawable-xhdpi/ch_559_logo.png | Bin 0 -> 963 bytes tests/common/res/drawable-xhdpi/ch_55_logo.png | Bin 0 -> 951 bytes tests/common/res/drawable-xhdpi/ch_560_logo.png | Bin 0 -> 1011 bytes tests/common/res/drawable-xhdpi/ch_561_logo.png | Bin 0 -> 968 bytes tests/common/res/drawable-xhdpi/ch_562_logo.png | Bin 0 -> 962 bytes tests/common/res/drawable-xhdpi/ch_563_logo.png | Bin 0 -> 982 bytes tests/common/res/drawable-xhdpi/ch_564_logo.png | Bin 0 -> 854 bytes tests/common/res/drawable-xhdpi/ch_565_logo.png | Bin 0 -> 827 bytes tests/common/res/drawable-xhdpi/ch_566_logo.png | Bin 0 -> 1186 bytes tests/common/res/drawable-xhdpi/ch_567_logo.png | Bin 0 -> 1145 bytes tests/common/res/drawable-xhdpi/ch_568_logo.png | Bin 0 -> 984 bytes tests/common/res/drawable-xhdpi/ch_569_logo.png | Bin 0 -> 1134 bytes tests/common/res/drawable-xhdpi/ch_56_logo.png | Bin 0 -> 967 bytes tests/common/res/drawable-xhdpi/ch_570_logo.png | Bin 0 -> 992 bytes tests/common/res/drawable-xhdpi/ch_571_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_572_logo.png | Bin 0 -> 1138 bytes tests/common/res/drawable-xhdpi/ch_573_logo.png | Bin 0 -> 995 bytes tests/common/res/drawable-xhdpi/ch_574_logo.png | Bin 0 -> 1169 bytes tests/common/res/drawable-xhdpi/ch_575_logo.png | Bin 0 -> 853 bytes tests/common/res/drawable-xhdpi/ch_576_logo.png | Bin 0 -> 1148 bytes tests/common/res/drawable-xhdpi/ch_577_logo.png | Bin 0 -> 684 bytes tests/common/res/drawable-xhdpi/ch_578_logo.png | Bin 0 -> 932 bytes tests/common/res/drawable-xhdpi/ch_579_logo.png | Bin 0 -> 1159 bytes tests/common/res/drawable-xhdpi/ch_57_logo.png | Bin 0 -> 981 bytes tests/common/res/drawable-xhdpi/ch_580_logo.png | Bin 0 -> 1107 bytes tests/common/res/drawable-xhdpi/ch_581_logo.png | Bin 0 -> 939 bytes tests/common/res/drawable-xhdpi/ch_582_logo.png | Bin 0 -> 1093 bytes tests/common/res/drawable-xhdpi/ch_583_logo.png | Bin 0 -> 1066 bytes tests/common/res/drawable-xhdpi/ch_584_logo.png | Bin 0 -> 1134 bytes tests/common/res/drawable-xhdpi/ch_585_logo.png | Bin 0 -> 960 bytes tests/common/res/drawable-xhdpi/ch_586_logo.png | Bin 0 -> 925 bytes tests/common/res/drawable-xhdpi/ch_587_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_588_logo.png | Bin 0 -> 1053 bytes tests/common/res/drawable-xhdpi/ch_589_logo.png | Bin 0 -> 1081 bytes tests/common/res/drawable-xhdpi/ch_58_logo.png | Bin 0 -> 1193 bytes tests/common/res/drawable-xhdpi/ch_590_logo.png | Bin 0 -> 1258 bytes tests/common/res/drawable-xhdpi/ch_591_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_592_logo.png | Bin 0 -> 1072 bytes tests/common/res/drawable-xhdpi/ch_593_logo.png | Bin 0 -> 1071 bytes tests/common/res/drawable-xhdpi/ch_594_logo.png | Bin 0 -> 988 bytes tests/common/res/drawable-xhdpi/ch_595_logo.png | Bin 0 -> 1106 bytes tests/common/res/drawable-xhdpi/ch_596_logo.png | Bin 0 -> 1089 bytes tests/common/res/drawable-xhdpi/ch_597_logo.png | Bin 0 -> 1132 bytes tests/common/res/drawable-xhdpi/ch_598_logo.png | Bin 0 -> 1164 bytes tests/common/res/drawable-xhdpi/ch_599_logo.png | Bin 0 -> 1257 bytes tests/common/res/drawable-xhdpi/ch_59_logo.png | Bin 0 -> 1097 bytes tests/common/res/drawable-xhdpi/ch_5_logo.png | Bin 0 -> 1137 bytes tests/common/res/drawable-xhdpi/ch_600_logo.png | Bin 0 -> 1107 bytes tests/common/res/drawable-xhdpi/ch_601_logo.png | Bin 0 -> 1059 bytes tests/common/res/drawable-xhdpi/ch_602_logo.png | Bin 0 -> 1078 bytes tests/common/res/drawable-xhdpi/ch_603_logo.png | Bin 0 -> 925 bytes tests/common/res/drawable-xhdpi/ch_604_logo.png | Bin 0 -> 961 bytes tests/common/res/drawable-xhdpi/ch_605_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_606_logo.png | Bin 0 -> 1217 bytes tests/common/res/drawable-xhdpi/ch_607_logo.png | Bin 0 -> 1094 bytes tests/common/res/drawable-xhdpi/ch_608_logo.png | Bin 0 -> 1050 bytes tests/common/res/drawable-xhdpi/ch_609_logo.png | Bin 0 -> 1273 bytes tests/common/res/drawable-xhdpi/ch_60_logo.png | Bin 0 -> 958 bytes tests/common/res/drawable-xhdpi/ch_610_logo.png | Bin 0 -> 1009 bytes tests/common/res/drawable-xhdpi/ch_611_logo.png | Bin 0 -> 1320 bytes tests/common/res/drawable-xhdpi/ch_612_logo.png | Bin 0 -> 1076 bytes tests/common/res/drawable-xhdpi/ch_613_logo.png | Bin 0 -> 1300 bytes tests/common/res/drawable-xhdpi/ch_614_logo.png | Bin 0 -> 1185 bytes tests/common/res/drawable-xhdpi/ch_615_logo.png | Bin 0 -> 1360 bytes tests/common/res/drawable-xhdpi/ch_616_logo.png | Bin 0 -> 985 bytes tests/common/res/drawable-xhdpi/ch_617_logo.png | Bin 0 -> 1113 bytes tests/common/res/drawable-xhdpi/ch_618_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_619_logo.png | Bin 0 -> 1031 bytes tests/common/res/drawable-xhdpi/ch_61_logo.png | Bin 0 -> 898 bytes tests/common/res/drawable-xhdpi/ch_620_logo.png | Bin 0 -> 1127 bytes tests/common/res/drawable-xhdpi/ch_621_logo.png | Bin 0 -> 975 bytes tests/common/res/drawable-xhdpi/ch_622_logo.png | Bin 0 -> 1268 bytes tests/common/res/drawable-xhdpi/ch_623_logo.png | Bin 0 -> 1233 bytes tests/common/res/drawable-xhdpi/ch_624_logo.png | Bin 0 -> 954 bytes tests/common/res/drawable-xhdpi/ch_625_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_626_logo.png | Bin 0 -> 931 bytes tests/common/res/drawable-xhdpi/ch_627_logo.png | Bin 0 -> 1068 bytes tests/common/res/drawable-xhdpi/ch_628_logo.png | Bin 0 -> 994 bytes tests/common/res/drawable-xhdpi/ch_629_logo.png | Bin 0 -> 911 bytes tests/common/res/drawable-xhdpi/ch_62_logo.png | Bin 0 -> 926 bytes tests/common/res/drawable-xhdpi/ch_630_logo.png | Bin 0 -> 974 bytes tests/common/res/drawable-xhdpi/ch_631_logo.png | Bin 0 -> 1006 bytes tests/common/res/drawable-xhdpi/ch_632_logo.png | Bin 0 -> 1207 bytes tests/common/res/drawable-xhdpi/ch_633_logo.png | Bin 0 -> 1019 bytes tests/common/res/drawable-xhdpi/ch_634_logo.png | Bin 0 -> 1185 bytes tests/common/res/drawable-xhdpi/ch_635_logo.png | Bin 0 -> 1005 bytes tests/common/res/drawable-xhdpi/ch_636_logo.png | Bin 0 -> 950 bytes tests/common/res/drawable-xhdpi/ch_637_logo.png | Bin 0 -> 942 bytes tests/common/res/drawable-xhdpi/ch_638_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_639_logo.png | Bin 0 -> 982 bytes tests/common/res/drawable-xhdpi/ch_63_logo.png | Bin 0 -> 1070 bytes tests/common/res/drawable-xhdpi/ch_640_logo.png | Bin 0 -> 861 bytes tests/common/res/drawable-xhdpi/ch_641_logo.png | Bin 0 -> 811 bytes tests/common/res/drawable-xhdpi/ch_642_logo.png | Bin 0 -> 1230 bytes tests/common/res/drawable-xhdpi/ch_643_logo.png | Bin 0 -> 1172 bytes tests/common/res/drawable-xhdpi/ch_644_logo.png | Bin 0 -> 883 bytes tests/common/res/drawable-xhdpi/ch_645_logo.png | Bin 0 -> 1078 bytes tests/common/res/drawable-xhdpi/ch_646_logo.png | Bin 0 -> 878 bytes tests/common/res/drawable-xhdpi/ch_647_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_648_logo.png | Bin 0 -> 1161 bytes tests/common/res/drawable-xhdpi/ch_649_logo.png | Bin 0 -> 1068 bytes tests/common/res/drawable-xhdpi/ch_64_logo.png | Bin 0 -> 976 bytes tests/common/res/drawable-xhdpi/ch_650_logo.png | Bin 0 -> 1263 bytes tests/common/res/drawable-xhdpi/ch_651_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_652_logo.png | Bin 0 -> 1138 bytes tests/common/res/drawable-xhdpi/ch_653_logo.png | Bin 0 -> 777 bytes tests/common/res/drawable-xhdpi/ch_654_logo.png | Bin 0 -> 894 bytes tests/common/res/drawable-xhdpi/ch_655_logo.png | Bin 0 -> 1052 bytes tests/common/res/drawable-xhdpi/ch_656_logo.png | Bin 0 -> 1049 bytes tests/common/res/drawable-xhdpi/ch_657_logo.png | Bin 0 -> 937 bytes tests/common/res/drawable-xhdpi/ch_658_logo.png | Bin 0 -> 1120 bytes tests/common/res/drawable-xhdpi/ch_659_logo.png | Bin 0 -> 1134 bytes tests/common/res/drawable-xhdpi/ch_65_logo.png | Bin 0 -> 1048 bytes tests/common/res/drawable-xhdpi/ch_660_logo.png | Bin 0 -> 1107 bytes tests/common/res/drawable-xhdpi/ch_661_logo.png | Bin 0 -> 929 bytes tests/common/res/drawable-xhdpi/ch_662_logo.png | Bin 0 -> 887 bytes tests/common/res/drawable-xhdpi/ch_663_logo.png | Bin 0 -> 1039 bytes tests/common/res/drawable-xhdpi/ch_664_logo.png | Bin 0 -> 1033 bytes tests/common/res/drawable-xhdpi/ch_665_logo.png | Bin 0 -> 1036 bytes tests/common/res/drawable-xhdpi/ch_666_logo.png | Bin 0 -> 1134 bytes tests/common/res/drawable-xhdpi/ch_667_logo.png | Bin 0 -> 1087 bytes tests/common/res/drawable-xhdpi/ch_668_logo.png | Bin 0 -> 1032 bytes tests/common/res/drawable-xhdpi/ch_669_logo.png | Bin 0 -> 1029 bytes tests/common/res/drawable-xhdpi/ch_66_logo.png | Bin 0 -> 1023 bytes tests/common/res/drawable-xhdpi/ch_670_logo.png | Bin 0 -> 974 bytes tests/common/res/drawable-xhdpi/ch_671_logo.png | Bin 0 -> 1103 bytes tests/common/res/drawable-xhdpi/ch_672_logo.png | Bin 0 -> 1016 bytes tests/common/res/drawable-xhdpi/ch_673_logo.png | Bin 0 -> 1157 bytes tests/common/res/drawable-xhdpi/ch_674_logo.png | Bin 0 -> 1118 bytes tests/common/res/drawable-xhdpi/ch_675_logo.png | Bin 0 -> 1288 bytes tests/common/res/drawable-xhdpi/ch_676_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_677_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_678_logo.png | Bin 0 -> 1081 bytes tests/common/res/drawable-xhdpi/ch_679_logo.png | Bin 0 -> 898 bytes tests/common/res/drawable-xhdpi/ch_67_logo.png | Bin 0 -> 1208 bytes tests/common/res/drawable-xhdpi/ch_680_logo.png | Bin 0 -> 1061 bytes tests/common/res/drawable-xhdpi/ch_681_logo.png | Bin 0 -> 901 bytes tests/common/res/drawable-xhdpi/ch_682_logo.png | Bin 0 -> 1340 bytes tests/common/res/drawable-xhdpi/ch_683_logo.png | Bin 0 -> 1123 bytes tests/common/res/drawable-xhdpi/ch_684_logo.png | Bin 0 -> 1045 bytes tests/common/res/drawable-xhdpi/ch_685_logo.png | Bin 0 -> 1220 bytes tests/common/res/drawable-xhdpi/ch_686_logo.png | Bin 0 -> 1042 bytes tests/common/res/drawable-xhdpi/ch_687_logo.png | Bin 0 -> 1417 bytes tests/common/res/drawable-xhdpi/ch_688_logo.png | Bin 0 -> 1052 bytes tests/common/res/drawable-xhdpi/ch_689_logo.png | Bin 0 -> 1331 bytes tests/common/res/drawable-xhdpi/ch_68_logo.png | Bin 0 -> 1106 bytes tests/common/res/drawable-xhdpi/ch_690_logo.png | Bin 0 -> 1253 bytes tests/common/res/drawable-xhdpi/ch_691_logo.png | Bin 0 -> 1359 bytes tests/common/res/drawable-xhdpi/ch_692_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_693_logo.png | Bin 0 -> 1169 bytes tests/common/res/drawable-xhdpi/ch_694_logo.png | Bin 0 -> 1025 bytes tests/common/res/drawable-xhdpi/ch_695_logo.png | Bin 0 -> 1136 bytes tests/common/res/drawable-xhdpi/ch_696_logo.png | Bin 0 -> 1078 bytes tests/common/res/drawable-xhdpi/ch_697_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_698_logo.png | Bin 0 -> 1366 bytes tests/common/res/drawable-xhdpi/ch_699_logo.png | Bin 0 -> 1191 bytes tests/common/res/drawable-xhdpi/ch_69_logo.png | Bin 0 -> 1035 bytes tests/common/res/drawable-xhdpi/ch_6_logo.png | Bin 0 -> 1002 bytes tests/common/res/drawable-xhdpi/ch_700_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_701_logo.png | Bin 0 -> 1088 bytes tests/common/res/drawable-xhdpi/ch_702_logo.png | Bin 0 -> 967 bytes tests/common/res/drawable-xhdpi/ch_703_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_704_logo.png | Bin 0 -> 986 bytes tests/common/res/drawable-xhdpi/ch_705_logo.png | Bin 0 -> 926 bytes tests/common/res/drawable-xhdpi/ch_706_logo.png | Bin 0 -> 952 bytes tests/common/res/drawable-xhdpi/ch_707_logo.png | Bin 0 -> 928 bytes tests/common/res/drawable-xhdpi/ch_708_logo.png | Bin 0 -> 1089 bytes tests/common/res/drawable-xhdpi/ch_709_logo.png | Bin 0 -> 1062 bytes tests/common/res/drawable-xhdpi/ch_70_logo.png | Bin 0 -> 996 bytes tests/common/res/drawable-xhdpi/ch_710_logo.png | Bin 0 -> 1152 bytes tests/common/res/drawable-xhdpi/ch_711_logo.png | Bin 0 -> 855 bytes tests/common/res/drawable-xhdpi/ch_712_logo.png | Bin 0 -> 944 bytes tests/common/res/drawable-xhdpi/ch_713_logo.png | Bin 0 -> 952 bytes tests/common/res/drawable-xhdpi/ch_714_logo.png | Bin 0 -> 861 bytes tests/common/res/drawable-xhdpi/ch_715_logo.png | Bin 0 -> 918 bytes tests/common/res/drawable-xhdpi/ch_716_logo.png | Bin 0 -> 827 bytes tests/common/res/drawable-xhdpi/ch_717_logo.png | Bin 0 -> 736 bytes tests/common/res/drawable-xhdpi/ch_718_logo.png | Bin 0 -> 1183 bytes tests/common/res/drawable-xhdpi/ch_719_logo.png | Bin 0 -> 1115 bytes tests/common/res/drawable-xhdpi/ch_71_logo.png | Bin 0 -> 770 bytes tests/common/res/drawable-xhdpi/ch_720_logo.png | Bin 0 -> 927 bytes tests/common/res/drawable-xhdpi/ch_721_logo.png | Bin 0 -> 1080 bytes tests/common/res/drawable-xhdpi/ch_722_logo.png | Bin 0 -> 909 bytes tests/common/res/drawable-xhdpi/ch_723_logo.png | Bin 0 -> 1002 bytes tests/common/res/drawable-xhdpi/ch_724_logo.png | Bin 0 -> 1110 bytes tests/common/res/drawable-xhdpi/ch_725_logo.png | Bin 0 -> 1037 bytes tests/common/res/drawable-xhdpi/ch_726_logo.png | Bin 0 -> 1195 bytes tests/common/res/drawable-xhdpi/ch_727_logo.png | Bin 0 -> 814 bytes tests/common/res/drawable-xhdpi/ch_728_logo.png | Bin 0 -> 1129 bytes tests/common/res/drawable-xhdpi/ch_729_logo.png | Bin 0 -> 743 bytes tests/common/res/drawable-xhdpi/ch_72_logo.png | Bin 0 -> 919 bytes tests/common/res/drawable-xhdpi/ch_730_logo.png | Bin 0 -> 921 bytes tests/common/res/drawable-xhdpi/ch_731_logo.png | Bin 0 -> 1122 bytes tests/common/res/drawable-xhdpi/ch_732_logo.png | Bin 0 -> 1054 bytes tests/common/res/drawable-xhdpi/ch_733_logo.png | Bin 0 -> 865 bytes tests/common/res/drawable-xhdpi/ch_734_logo.png | Bin 0 -> 1052 bytes tests/common/res/drawable-xhdpi/ch_735_logo.png | Bin 0 -> 1086 bytes tests/common/res/drawable-xhdpi/ch_736_logo.png | Bin 0 -> 1135 bytes tests/common/res/drawable-xhdpi/ch_737_logo.png | Bin 0 -> 911 bytes tests/common/res/drawable-xhdpi/ch_738_logo.png | Bin 0 -> 936 bytes tests/common/res/drawable-xhdpi/ch_739_logo.png | Bin 0 -> 1081 bytes tests/common/res/drawable-xhdpi/ch_73_logo.png | Bin 0 -> 861 bytes tests/common/res/drawable-xhdpi/ch_740_logo.png | Bin 0 -> 1011 bytes tests/common/res/drawable-xhdpi/ch_741_logo.png | Bin 0 -> 982 bytes tests/common/res/drawable-xhdpi/ch_742_logo.png | Bin 0 -> 1149 bytes tests/common/res/drawable-xhdpi/ch_743_logo.png | Bin 0 -> 1119 bytes tests/common/res/drawable-xhdpi/ch_744_logo.png | Bin 0 -> 944 bytes tests/common/res/drawable-xhdpi/ch_745_logo.png | Bin 0 -> 967 bytes tests/common/res/drawable-xhdpi/ch_746_logo.png | Bin 0 -> 958 bytes tests/common/res/drawable-xhdpi/ch_747_logo.png | Bin 0 -> 1035 bytes tests/common/res/drawable-xhdpi/ch_748_logo.png | Bin 0 -> 1023 bytes tests/common/res/drawable-xhdpi/ch_749_logo.png | Bin 0 -> 1105 bytes tests/common/res/drawable-xhdpi/ch_74_logo.png | Bin 0 -> 1126 bytes tests/common/res/drawable-xhdpi/ch_750_logo.png | Bin 0 -> 1147 bytes tests/common/res/drawable-xhdpi/ch_751_logo.png | Bin 0 -> 1249 bytes tests/common/res/drawable-xhdpi/ch_752_logo.png | Bin 0 -> 1117 bytes tests/common/res/drawable-xhdpi/ch_753_logo.png | Bin 0 -> 1070 bytes tests/common/res/drawable-xhdpi/ch_754_logo.png | Bin 0 -> 1040 bytes tests/common/res/drawable-xhdpi/ch_755_logo.png | Bin 0 -> 837 bytes tests/common/res/drawable-xhdpi/ch_756_logo.png | Bin 0 -> 1032 bytes tests/common/res/drawable-xhdpi/ch_757_logo.png | Bin 0 -> 879 bytes tests/common/res/drawable-xhdpi/ch_758_logo.png | Bin 0 -> 1220 bytes tests/common/res/drawable-xhdpi/ch_759_logo.png | Bin 0 -> 1088 bytes tests/common/res/drawable-xhdpi/ch_75_logo.png | Bin 0 -> 993 bytes tests/common/res/drawable-xhdpi/ch_760_logo.png | Bin 0 -> 1047 bytes tests/common/res/drawable-xhdpi/ch_761_logo.png | Bin 0 -> 1201 bytes tests/common/res/drawable-xhdpi/ch_762_logo.png | Bin 0 -> 1033 bytes tests/common/res/drawable-xhdpi/ch_763_logo.png | Bin 0 -> 1294 bytes tests/common/res/drawable-xhdpi/ch_764_logo.png | Bin 0 -> 1059 bytes tests/common/res/drawable-xhdpi/ch_765_logo.png | Bin 0 -> 1271 bytes tests/common/res/drawable-xhdpi/ch_766_logo.png | Bin 0 -> 1153 bytes tests/common/res/drawable-xhdpi/ch_767_logo.png | Bin 0 -> 1309 bytes tests/common/res/drawable-xhdpi/ch_768_logo.png | Bin 0 -> 1081 bytes tests/common/res/drawable-xhdpi/ch_769_logo.png | Bin 0 -> 1162 bytes tests/common/res/drawable-xhdpi/ch_76_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_770_logo.png | Bin 0 -> 897 bytes tests/common/res/drawable-xhdpi/ch_771_logo.png | Bin 0 -> 990 bytes tests/common/res/drawable-xhdpi/ch_772_logo.png | Bin 0 -> 1034 bytes tests/common/res/drawable-xhdpi/ch_773_logo.png | Bin 0 -> 933 bytes tests/common/res/drawable-xhdpi/ch_774_logo.png | Bin 0 -> 1208 bytes tests/common/res/drawable-xhdpi/ch_775_logo.png | Bin 0 -> 1064 bytes tests/common/res/drawable-xhdpi/ch_776_logo.png | Bin 0 -> 912 bytes tests/common/res/drawable-xhdpi/ch_777_logo.png | Bin 0 -> 943 bytes tests/common/res/drawable-xhdpi/ch_778_logo.png | Bin 0 -> 924 bytes tests/common/res/drawable-xhdpi/ch_779_logo.png | Bin 0 -> 1025 bytes tests/common/res/drawable-xhdpi/ch_77_logo.png | Bin 0 -> 1091 bytes tests/common/res/drawable-xhdpi/ch_780_logo.png | Bin 0 -> 1033 bytes tests/common/res/drawable-xhdpi/ch_781_logo.png | Bin 0 -> 904 bytes tests/common/res/drawable-xhdpi/ch_782_logo.png | Bin 0 -> 899 bytes tests/common/res/drawable-xhdpi/ch_783_logo.png | Bin 0 -> 1013 bytes tests/common/res/drawable-xhdpi/ch_784_logo.png | Bin 0 -> 1153 bytes tests/common/res/drawable-xhdpi/ch_785_logo.png | Bin 0 -> 1052 bytes tests/common/res/drawable-xhdpi/ch_786_logo.png | Bin 0 -> 1264 bytes tests/common/res/drawable-xhdpi/ch_787_logo.png | Bin 0 -> 910 bytes tests/common/res/drawable-xhdpi/ch_788_logo.png | Bin 0 -> 931 bytes tests/common/res/drawable-xhdpi/ch_789_logo.png | Bin 0 -> 983 bytes tests/common/res/drawable-xhdpi/ch_78_logo.png | Bin 0 -> 969 bytes tests/common/res/drawable-xhdpi/ch_790_logo.png | Bin 0 -> 943 bytes tests/common/res/drawable-xhdpi/ch_791_logo.png | Bin 0 -> 864 bytes tests/common/res/drawable-xhdpi/ch_792_logo.png | Bin 0 -> 838 bytes tests/common/res/drawable-xhdpi/ch_793_logo.png | Bin 0 -> 843 bytes tests/common/res/drawable-xhdpi/ch_794_logo.png | Bin 0 -> 1175 bytes tests/common/res/drawable-xhdpi/ch_795_logo.png | Bin 0 -> 1087 bytes tests/common/res/drawable-xhdpi/ch_796_logo.png | Bin 0 -> 934 bytes tests/common/res/drawable-xhdpi/ch_797_logo.png | Bin 0 -> 1075 bytes tests/common/res/drawable-xhdpi/ch_798_logo.png | Bin 0 -> 986 bytes tests/common/res/drawable-xhdpi/ch_799_logo.png | Bin 0 -> 1010 bytes tests/common/res/drawable-xhdpi/ch_79_logo.png | Bin 0 -> 1300 bytes tests/common/res/drawable-xhdpi/ch_7_logo.png | Bin 0 -> 1196 bytes tests/common/res/drawable-xhdpi/ch_800_logo.png | Bin 0 -> 1135 bytes tests/common/res/drawable-xhdpi/ch_801_logo.png | Bin 0 -> 1043 bytes tests/common/res/drawable-xhdpi/ch_802_logo.png | Bin 0 -> 1254 bytes tests/common/res/drawable-xhdpi/ch_803_logo.png | Bin 0 -> 931 bytes tests/common/res/drawable-xhdpi/ch_804_logo.png | Bin 0 -> 1145 bytes tests/common/res/drawable-xhdpi/ch_805_logo.png | Bin 0 -> 778 bytes tests/common/res/drawable-xhdpi/ch_806_logo.png | Bin 0 -> 964 bytes tests/common/res/drawable-xhdpi/ch_807_logo.png | Bin 0 -> 1157 bytes tests/common/res/drawable-xhdpi/ch_808_logo.png | Bin 0 -> 1047 bytes tests/common/res/drawable-xhdpi/ch_809_logo.png | Bin 0 -> 982 bytes tests/common/res/drawable-xhdpi/ch_80_logo.png | Bin 0 -> 1035 bytes tests/common/res/drawable-xhdpi/ch_810_logo.png | Bin 0 -> 1079 bytes tests/common/res/drawable-xhdpi/ch_811_logo.png | Bin 0 -> 960 bytes tests/common/res/drawable-xhdpi/ch_812_logo.png | Bin 0 -> 1154 bytes tests/common/res/drawable-xhdpi/ch_813_logo.png | Bin 0 -> 1006 bytes tests/common/res/drawable-xhdpi/ch_814_logo.png | Bin 0 -> 849 bytes tests/common/res/drawable-xhdpi/ch_815_logo.png | Bin 0 -> 1054 bytes tests/common/res/drawable-xhdpi/ch_816_logo.png | Bin 0 -> 1093 bytes tests/common/res/drawable-xhdpi/ch_817_logo.png | Bin 0 -> 1031 bytes tests/common/res/drawable-xhdpi/ch_818_logo.png | Bin 0 -> 1164 bytes tests/common/res/drawable-xhdpi/ch_819_logo.png | Bin 0 -> 1132 bytes tests/common/res/drawable-xhdpi/ch_81_logo.png | Bin 0 -> 1191 bytes tests/common/res/drawable-xhdpi/ch_820_logo.png | Bin 0 -> 1066 bytes tests/common/res/drawable-xhdpi/ch_821_logo.png | Bin 0 -> 1012 bytes tests/common/res/drawable-xhdpi/ch_822_logo.png | Bin 0 -> 947 bytes tests/common/res/drawable-xhdpi/ch_823_logo.png | Bin 0 -> 1151 bytes tests/common/res/drawable-xhdpi/ch_824_logo.png | Bin 0 -> 984 bytes tests/common/res/drawable-xhdpi/ch_825_logo.png | Bin 0 -> 1086 bytes tests/common/res/drawable-xhdpi/ch_826_logo.png | Bin 0 -> 1161 bytes tests/common/res/drawable-xhdpi/ch_827_logo.png | Bin 0 -> 1283 bytes tests/common/res/drawable-xhdpi/ch_828_logo.png | Bin 0 -> 1100 bytes tests/common/res/drawable-xhdpi/ch_829_logo.png | Bin 0 -> 1100 bytes tests/common/res/drawable-xhdpi/ch_82_logo.png | Bin 0 -> 1151 bytes tests/common/res/drawable-xhdpi/ch_830_logo.png | Bin 0 -> 1034 bytes tests/common/res/drawable-xhdpi/ch_831_logo.png | Bin 0 -> 885 bytes tests/common/res/drawable-xhdpi/ch_832_logo.png | Bin 0 -> 1056 bytes tests/common/res/drawable-xhdpi/ch_833_logo.png | Bin 0 -> 924 bytes tests/common/res/drawable-xhdpi/ch_834_logo.png | Bin 0 -> 1339 bytes tests/common/res/drawable-xhdpi/ch_835_logo.png | Bin 0 -> 1111 bytes tests/common/res/drawable-xhdpi/ch_836_logo.png | Bin 0 -> 1075 bytes tests/common/res/drawable-xhdpi/ch_837_logo.png | Bin 0 -> 1251 bytes tests/common/res/drawable-xhdpi/ch_838_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_839_logo.png | Bin 0 -> 1448 bytes tests/common/res/drawable-xhdpi/ch_83_logo.png | Bin 0 -> 1315 bytes tests/common/res/drawable-xhdpi/ch_840_logo.png | Bin 0 -> 1102 bytes tests/common/res/drawable-xhdpi/ch_841_logo.png | Bin 0 -> 1254 bytes tests/common/res/drawable-xhdpi/ch_842_logo.png | Bin 0 -> 1209 bytes tests/common/res/drawable-xhdpi/ch_843_logo.png | Bin 0 -> 1346 bytes tests/common/res/drawable-xhdpi/ch_844_logo.png | Bin 0 -> 985 bytes tests/common/res/drawable-xhdpi/ch_845_logo.png | Bin 0 -> 1095 bytes tests/common/res/drawable-xhdpi/ch_846_logo.png | Bin 0 -> 1056 bytes tests/common/res/drawable-xhdpi/ch_847_logo.png | Bin 0 -> 1008 bytes tests/common/res/drawable-xhdpi/ch_848_logo.png | Bin 0 -> 1057 bytes tests/common/res/drawable-xhdpi/ch_849_logo.png | Bin 0 -> 1018 bytes tests/common/res/drawable-xhdpi/ch_84_logo.png | Bin 0 -> 991 bytes tests/common/res/drawable-xhdpi/ch_850_logo.png | Bin 0 -> 1347 bytes tests/common/res/drawable-xhdpi/ch_851_logo.png | Bin 0 -> 1196 bytes tests/common/res/drawable-xhdpi/ch_852_logo.png | Bin 0 -> 941 bytes tests/common/res/drawable-xhdpi/ch_853_logo.png | Bin 0 -> 1156 bytes tests/common/res/drawable-xhdpi/ch_854_logo.png | Bin 0 -> 986 bytes tests/common/res/drawable-xhdpi/ch_855_logo.png | Bin 0 -> 1071 bytes tests/common/res/drawable-xhdpi/ch_856_logo.png | Bin 0 -> 1074 bytes tests/common/res/drawable-xhdpi/ch_857_logo.png | Bin 0 -> 942 bytes tests/common/res/drawable-xhdpi/ch_858_logo.png | Bin 0 -> 920 bytes tests/common/res/drawable-xhdpi/ch_859_logo.png | Bin 0 -> 1046 bytes tests/common/res/drawable-xhdpi/ch_85_logo.png | Bin 0 -> 1104 bytes tests/common/res/drawable-xhdpi/ch_860_logo.png | Bin 0 -> 1210 bytes tests/common/res/drawable-xhdpi/ch_861_logo.png | Bin 0 -> 1032 bytes tests/common/res/drawable-xhdpi/ch_862_logo.png | Bin 0 -> 1263 bytes tests/common/res/drawable-xhdpi/ch_863_logo.png | Bin 0 -> 1015 bytes tests/common/res/drawable-xhdpi/ch_864_logo.png | Bin 0 -> 989 bytes tests/common/res/drawable-xhdpi/ch_865_logo.png | Bin 0 -> 1014 bytes tests/common/res/drawable-xhdpi/ch_866_logo.png | Bin 0 -> 923 bytes tests/common/res/drawable-xhdpi/ch_867_logo.png | Bin 0 -> 961 bytes tests/common/res/drawable-xhdpi/ch_868_logo.png | Bin 0 -> 824 bytes tests/common/res/drawable-xhdpi/ch_869_logo.png | Bin 0 -> 875 bytes tests/common/res/drawable-xhdpi/ch_86_logo.png | Bin 0 -> 975 bytes tests/common/res/drawable-xhdpi/ch_870_logo.png | Bin 0 -> 1125 bytes tests/common/res/drawable-xhdpi/ch_871_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_872_logo.png | Bin 0 -> 937 bytes tests/common/res/drawable-xhdpi/ch_873_logo.png | Bin 0 -> 1131 bytes tests/common/res/drawable-xhdpi/ch_874_logo.png | Bin 0 -> 944 bytes tests/common/res/drawable-xhdpi/ch_875_logo.png | Bin 0 -> 1026 bytes tests/common/res/drawable-xhdpi/ch_876_logo.png | Bin 0 -> 1149 bytes tests/common/res/drawable-xhdpi/ch_877_logo.png | Bin 0 -> 977 bytes tests/common/res/drawable-xhdpi/ch_878_logo.png | Bin 0 -> 1075 bytes tests/common/res/drawable-xhdpi/ch_879_logo.png | Bin 0 -> 941 bytes tests/common/res/drawable-xhdpi/ch_87_logo.png | Bin 0 -> 959 bytes tests/common/res/drawable-xhdpi/ch_880_logo.png | Bin 0 -> 1081 bytes tests/common/res/drawable-xhdpi/ch_881_logo.png | Bin 0 -> 679 bytes tests/common/res/drawable-xhdpi/ch_882_logo.png | Bin 0 -> 883 bytes tests/common/res/drawable-xhdpi/ch_883_logo.png | Bin 0 -> 1127 bytes tests/common/res/drawable-xhdpi/ch_884_logo.png | Bin 0 -> 1028 bytes tests/common/res/drawable-xhdpi/ch_885_logo.png | Bin 0 -> 904 bytes tests/common/res/drawable-xhdpi/ch_886_logo.png | Bin 0 -> 1046 bytes tests/common/res/drawable-xhdpi/ch_887_logo.png | Bin 0 -> 1040 bytes tests/common/res/drawable-xhdpi/ch_888_logo.png | Bin 0 -> 1030 bytes tests/common/res/drawable-xhdpi/ch_889_logo.png | Bin 0 -> 961 bytes tests/common/res/drawable-xhdpi/ch_88_logo.png | Bin 0 -> 982 bytes tests/common/res/drawable-xhdpi/ch_890_logo.png | Bin 0 -> 954 bytes tests/common/res/drawable-xhdpi/ch_891_logo.png | Bin 0 -> 1070 bytes tests/common/res/drawable-xhdpi/ch_892_logo.png | Bin 0 -> 1055 bytes tests/common/res/drawable-xhdpi/ch_893_logo.png | Bin 0 -> 1092 bytes tests/common/res/drawable-xhdpi/ch_894_logo.png | Bin 0 -> 1237 bytes tests/common/res/drawable-xhdpi/ch_895_logo.png | Bin 0 -> 1100 bytes tests/common/res/drawable-xhdpi/ch_896_logo.png | Bin 0 -> 1095 bytes tests/common/res/drawable-xhdpi/ch_897_logo.png | Bin 0 -> 1040 bytes tests/common/res/drawable-xhdpi/ch_898_logo.png | Bin 0 -> 959 bytes tests/common/res/drawable-xhdpi/ch_899_logo.png | Bin 0 -> 1099 bytes tests/common/res/drawable-xhdpi/ch_89_logo.png | Bin 0 -> 939 bytes tests/common/res/drawable-xhdpi/ch_8_logo.png | Bin 0 -> 914 bytes tests/common/res/drawable-xhdpi/ch_900_logo.png | Bin 0 -> 1021 bytes tests/common/res/drawable-xhdpi/ch_901_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_902_logo.png | Bin 0 -> 1157 bytes tests/common/res/drawable-xhdpi/ch_903_logo.png | Bin 0 -> 1307 bytes tests/common/res/drawable-xhdpi/ch_904_logo.png | Bin 0 -> 1151 bytes tests/common/res/drawable-xhdpi/ch_905_logo.png | Bin 0 -> 1106 bytes tests/common/res/drawable-xhdpi/ch_906_logo.png | Bin 0 -> 1102 bytes tests/common/res/drawable-xhdpi/ch_907_logo.png | Bin 0 -> 900 bytes tests/common/res/drawable-xhdpi/ch_908_logo.png | Bin 0 -> 1054 bytes tests/common/res/drawable-xhdpi/ch_909_logo.png | Bin 0 -> 928 bytes tests/common/res/drawable-xhdpi/ch_90_logo.png | Bin 0 -> 1252 bytes tests/common/res/drawable-xhdpi/ch_910_logo.png | Bin 0 -> 1225 bytes tests/common/res/drawable-xhdpi/ch_911_logo.png | Bin 0 -> 1016 bytes tests/common/res/drawable-xhdpi/ch_912_logo.png | Bin 0 -> 1002 bytes tests/common/res/drawable-xhdpi/ch_913_logo.png | Bin 0 -> 1245 bytes tests/common/res/drawable-xhdpi/ch_914_logo.png | Bin 0 -> 1030 bytes tests/common/res/drawable-xhdpi/ch_915_logo.png | Bin 0 -> 1384 bytes tests/common/res/drawable-xhdpi/ch_916_logo.png | Bin 0 -> 1095 bytes tests/common/res/drawable-xhdpi/ch_917_logo.png | Bin 0 -> 1259 bytes tests/common/res/drawable-xhdpi/ch_918_logo.png | Bin 0 -> 1229 bytes tests/common/res/drawable-xhdpi/ch_919_logo.png | Bin 0 -> 1311 bytes tests/common/res/drawable-xhdpi/ch_91_logo.png | Bin 0 -> 1121 bytes tests/common/res/drawable-xhdpi/ch_920_logo.png | Bin 0 -> 1011 bytes tests/common/res/drawable-xhdpi/ch_921_logo.png | Bin 0 -> 1133 bytes tests/common/res/drawable-xhdpi/ch_922_logo.png | Bin 0 -> 913 bytes tests/common/res/drawable-xhdpi/ch_923_logo.png | Bin 0 -> 1119 bytes tests/common/res/drawable-xhdpi/ch_924_logo.png | Bin 0 -> 1104 bytes tests/common/res/drawable-xhdpi/ch_925_logo.png | Bin 0 -> 1027 bytes tests/common/res/drawable-xhdpi/ch_926_logo.png | Bin 0 -> 1346 bytes tests/common/res/drawable-xhdpi/ch_927_logo.png | Bin 0 -> 1126 bytes tests/common/res/drawable-xhdpi/ch_928_logo.png | Bin 0 -> 989 bytes tests/common/res/drawable-xhdpi/ch_929_logo.png | Bin 0 -> 1088 bytes tests/common/res/drawable-xhdpi/ch_92_logo.png | Bin 0 -> 907 bytes tests/common/res/drawable-xhdpi/ch_930_logo.png | Bin 0 -> 1017 bytes tests/common/res/drawable-xhdpi/ch_931_logo.png | Bin 0 -> 1008 bytes tests/common/res/drawable-xhdpi/ch_932_logo.png | Bin 0 -> 1047 bytes tests/common/res/drawable-xhdpi/ch_933_logo.png | Bin 0 -> 908 bytes tests/common/res/drawable-xhdpi/ch_934_logo.png | Bin 0 -> 912 bytes tests/common/res/drawable-xhdpi/ch_935_logo.png | Bin 0 -> 1041 bytes tests/common/res/drawable-xhdpi/ch_936_logo.png | Bin 0 -> 1219 bytes tests/common/res/drawable-xhdpi/ch_937_logo.png | Bin 0 -> 1065 bytes tests/common/res/drawable-xhdpi/ch_938_logo.png | Bin 0 -> 1194 bytes tests/common/res/drawable-xhdpi/ch_939_logo.png | Bin 0 -> 908 bytes tests/common/res/drawable-xhdpi/ch_93_logo.png | Bin 0 -> 1077 bytes tests/common/res/drawable-xhdpi/ch_940_logo.png | Bin 0 -> 1009 bytes tests/common/res/drawable-xhdpi/ch_941_logo.png | Bin 0 -> 913 bytes tests/common/res/drawable-xhdpi/ch_942_logo.png | Bin 0 -> 947 bytes tests/common/res/drawable-xhdpi/ch_943_logo.png | Bin 0 -> 971 bytes tests/common/res/drawable-xhdpi/ch_944_logo.png | Bin 0 -> 795 bytes tests/common/res/drawable-xhdpi/ch_945_logo.png | Bin 0 -> 850 bytes tests/common/res/drawable-xhdpi/ch_946_logo.png | Bin 0 -> 1255 bytes tests/common/res/drawable-xhdpi/ch_947_logo.png | Bin 0 -> 1135 bytes tests/common/res/drawable-xhdpi/ch_948_logo.png | Bin 0 -> 942 bytes tests/common/res/drawable-xhdpi/ch_949_logo.png | Bin 0 -> 1091 bytes tests/common/res/drawable-xhdpi/ch_94_logo.png | Bin 0 -> 913 bytes tests/common/res/drawable-xhdpi/ch_950_logo.png | Bin 0 -> 997 bytes tests/common/res/drawable-xhdpi/ch_951_logo.png | Bin 0 -> 1060 bytes tests/common/res/drawable-xhdpi/ch_952_logo.png | Bin 0 -> 1153 bytes tests/common/res/drawable-xhdpi/ch_953_logo.png | Bin 0 -> 1057 bytes tests/common/res/drawable-xhdpi/ch_954_logo.png | Bin 0 -> 1233 bytes tests/common/res/drawable-xhdpi/ch_955_logo.png | Bin 0 -> 901 bytes tests/common/res/drawable-xhdpi/ch_956_logo.png | Bin 0 -> 1177 bytes tests/common/res/drawable-xhdpi/ch_957_logo.png | Bin 0 -> 751 bytes tests/common/res/drawable-xhdpi/ch_958_logo.png | Bin 0 -> 965 bytes tests/common/res/drawable-xhdpi/ch_959_logo.png | Bin 0 -> 1125 bytes tests/common/res/drawable-xhdpi/ch_95_logo.png | Bin 0 -> 1048 bytes tests/common/res/drawable-xhdpi/ch_960_logo.png | Bin 0 -> 1102 bytes tests/common/res/drawable-xhdpi/ch_961_logo.png | Bin 0 -> 943 bytes tests/common/res/drawable-xhdpi/ch_962_logo.png | Bin 0 -> 1107 bytes tests/common/res/drawable-xhdpi/ch_963_logo.png | Bin 0 -> 1138 bytes tests/common/res/drawable-xhdpi/ch_964_logo.png | Bin 0 -> 1173 bytes tests/common/res/drawable-xhdpi/ch_965_logo.png | Bin 0 -> 1024 bytes tests/common/res/drawable-xhdpi/ch_966_logo.png | Bin 0 -> 904 bytes tests/common/res/drawable-xhdpi/ch_967_logo.png | Bin 0 -> 1020 bytes tests/common/res/drawable-xhdpi/ch_968_logo.png | Bin 0 -> 1074 bytes tests/common/res/drawable-xhdpi/ch_969_logo.png | Bin 0 -> 1022 bytes tests/common/res/drawable-xhdpi/ch_96_logo.png | Bin 0 -> 989 bytes tests/common/res/drawable-xhdpi/ch_970_logo.png | Bin 0 -> 1164 bytes tests/common/res/drawable-xhdpi/ch_971_logo.png | Bin 0 -> 1103 bytes tests/common/res/drawable-xhdpi/ch_972_logo.png | Bin 0 -> 1054 bytes tests/common/res/drawable-xhdpi/ch_973_logo.png | Bin 0 -> 1044 bytes tests/common/res/drawable-xhdpi/ch_974_logo.png | Bin 0 -> 966 bytes tests/common/res/drawable-xhdpi/ch_975_logo.png | Bin 0 -> 1098 bytes tests/common/res/drawable-xhdpi/ch_976_logo.png | Bin 0 -> 1068 bytes tests/common/res/drawable-xhdpi/ch_977_logo.png | Bin 0 -> 1062 bytes tests/common/res/drawable-xhdpi/ch_978_logo.png | Bin 0 -> 1157 bytes tests/common/res/drawable-xhdpi/ch_979_logo.png | Bin 0 -> 1234 bytes tests/common/res/drawable-xhdpi/ch_97_logo.png | Bin 0 -> 861 bytes tests/common/res/drawable-xhdpi/ch_980_logo.png | Bin 0 -> 1188 bytes tests/common/res/drawable-xhdpi/ch_981_logo.png | Bin 0 -> 1069 bytes tests/common/res/drawable-xhdpi/ch_982_logo.png | Bin 0 -> 1087 bytes tests/common/res/drawable-xhdpi/ch_983_logo.png | Bin 0 -> 920 bytes tests/common/res/drawable-xhdpi/ch_984_logo.png | Bin 0 -> 1041 bytes tests/common/res/drawable-xhdpi/ch_985_logo.png | Bin 0 -> 984 bytes tests/common/res/drawable-xhdpi/ch_986_logo.png | Bin 0 -> 1346 bytes tests/common/res/drawable-xhdpi/ch_987_logo.png | Bin 0 -> 1091 bytes tests/common/res/drawable-xhdpi/ch_988_logo.png | Bin 0 -> 1001 bytes tests/common/res/drawable-xhdpi/ch_989_logo.png | Bin 0 -> 1201 bytes tests/common/res/drawable-xhdpi/ch_98_logo.png | Bin 0 -> 902 bytes tests/common/res/drawable-xhdpi/ch_990_logo.png | Bin 0 -> 1027 bytes tests/common/res/drawable-xhdpi/ch_991_logo.png | Bin 0 -> 1334 bytes tests/common/res/drawable-xhdpi/ch_992_logo.png | Bin 0 -> 1055 bytes tests/common/res/drawable-xhdpi/ch_993_logo.png | Bin 0 -> 1252 bytes tests/common/res/drawable-xhdpi/ch_994_logo.png | Bin 0 -> 1168 bytes tests/common/res/drawable-xhdpi/ch_995_logo.png | Bin 0 -> 1305 bytes tests/common/res/drawable-xhdpi/ch_996_logo.png | Bin 0 -> 1047 bytes tests/common/res/drawable-xhdpi/ch_997_logo.png | Bin 0 -> 1104 bytes tests/common/res/drawable-xhdpi/ch_998_logo.png | Bin 0 -> 996 bytes tests/common/res/drawable-xhdpi/ch_999_logo.png | Bin 0 -> 999 bytes tests/common/res/drawable-xhdpi/ch_99_logo.png | Bin 0 -> 900 bytes tests/common/res/drawable-xhdpi/ch_9_logo.png | Bin 0 -> 1031 bytes .../src/com/android/tv/testing/ChannelInfo.java | 22 +- tests/common/src/com/android/tv/testing/Utils.java | 38 +- .../android/tv/testing/dvr/RecordingTestUtils.java | 2 +- .../com/android/tv/testing/uihelper/Constants.java | 3 + .../uihelper/LiveChannelsUiDeviceHelper.java | 11 + .../android/tv/testing/uihelper/MenuHelper.java | 11 +- .../tv/testing/uihelper/SidePanelHelper.java | 16 +- .../tv/testing/uihelper/UiDeviceAsserts.java | 7 + .../android/tv/testing/uihelper/UiDeviceUtils.java | 63 +- tests/func/Android.mk | 2 - tests/func/OWNERS | 2 + .../android/tv/tests/ui/ChannelSourcesTest.java | 4 +- .../android/tv/tests/ui/LiveChannelsTestCase.java | 2 + .../tv/tests/ui/PlayControlsRowViewTest.java | 54 +- .../tv/tests/ui/ProgramGuidePerformanceTest.java | 59 - .../android/tv/tests/ui/dvr/DvrLibraryTest.java | 219 ++ .../CustomizeChannelListFragmentTest.java | 117 + tests/input/OWNERS | 2 + tests/input/res/drawable-xhdpi/ch_1000_logo.png | Bin 1041 -> 0 bytes tests/input/res/drawable-xhdpi/ch_100_logo.png | Bin 1128 -> 0 bytes tests/input/res/drawable-xhdpi/ch_101_logo.png | Bin 921 -> 0 bytes tests/input/res/drawable-xhdpi/ch_102_logo.png | Bin 1224 -> 0 bytes tests/input/res/drawable-xhdpi/ch_103_logo.png | Bin 971 -> 0 bytes tests/input/res/drawable-xhdpi/ch_104_logo.png | Bin 952 -> 0 bytes tests/input/res/drawable-xhdpi/ch_105_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_106_logo.png | Bin 884 -> 0 bytes tests/input/res/drawable-xhdpi/ch_107_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_108_logo.png | Bin 848 -> 0 bytes tests/input/res/drawable-xhdpi/ch_109_logo.png | Bin 842 -> 0 bytes tests/input/res/drawable-xhdpi/ch_10_logo.png | Bin 936 -> 0 bytes tests/input/res/drawable-xhdpi/ch_110_logo.png | Bin 1155 -> 0 bytes tests/input/res/drawable-xhdpi/ch_111_logo.png | Bin 915 -> 0 bytes tests/input/res/drawable-xhdpi/ch_112_logo.png | Bin 843 -> 0 bytes tests/input/res/drawable-xhdpi/ch_113_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_114_logo.png | Bin 861 -> 0 bytes tests/input/res/drawable-xhdpi/ch_115_logo.png | Bin 995 -> 0 bytes tests/input/res/drawable-xhdpi/ch_116_logo.png | Bin 1086 -> 0 bytes tests/input/res/drawable-xhdpi/ch_117_logo.png | Bin 917 -> 0 bytes tests/input/res/drawable-xhdpi/ch_118_logo.png | Bin 1131 -> 0 bytes tests/input/res/drawable-xhdpi/ch_119_logo.png | Bin 875 -> 0 bytes tests/input/res/drawable-xhdpi/ch_11_logo.png | Bin 932 -> 0 bytes tests/input/res/drawable-xhdpi/ch_120_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_121_logo.png | Bin 663 -> 0 bytes tests/input/res/drawable-xhdpi/ch_122_logo.png | Bin 845 -> 0 bytes tests/input/res/drawable-xhdpi/ch_123_logo.png | Bin 1090 -> 0 bytes tests/input/res/drawable-xhdpi/ch_124_logo.png | Bin 1033 -> 0 bytes tests/input/res/drawable-xhdpi/ch_125_logo.png | Bin 880 -> 0 bytes tests/input/res/drawable-xhdpi/ch_126_logo.png | Bin 1051 -> 0 bytes tests/input/res/drawable-xhdpi/ch_127_logo.png | Bin 1044 -> 0 bytes tests/input/res/drawable-xhdpi/ch_128_logo.png | Bin 1051 -> 0 bytes tests/input/res/drawable-xhdpi/ch_129_logo.png | Bin 983 -> 0 bytes tests/input/res/drawable-xhdpi/ch_12_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_130_logo.png | Bin 891 -> 0 bytes tests/input/res/drawable-xhdpi/ch_131_logo.png | Bin 975 -> 0 bytes tests/input/res/drawable-xhdpi/ch_132_logo.png | Bin 1076 -> 0 bytes tests/input/res/drawable-xhdpi/ch_133_logo.png | Bin 971 -> 0 bytes tests/input/res/drawable-xhdpi/ch_134_logo.png | Bin 1191 -> 0 bytes tests/input/res/drawable-xhdpi/ch_135_logo.png | Bin 1143 -> 0 bytes tests/input/res/drawable-xhdpi/ch_136_logo.png | Bin 1057 -> 0 bytes tests/input/res/drawable-xhdpi/ch_137_logo.png | Bin 1007 -> 0 bytes tests/input/res/drawable-xhdpi/ch_138_logo.png | Bin 995 -> 0 bytes tests/input/res/drawable-xhdpi/ch_139_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_13_logo.png | Bin 896 -> 0 bytes tests/input/res/drawable-xhdpi/ch_140_logo.png | Bin 1010 -> 0 bytes tests/input/res/drawable-xhdpi/ch_141_logo.png | Bin 970 -> 0 bytes tests/input/res/drawable-xhdpi/ch_142_logo.png | Bin 1103 -> 0 bytes tests/input/res/drawable-xhdpi/ch_143_logo.png | Bin 1206 -> 0 bytes tests/input/res/drawable-xhdpi/ch_144_logo.png | Bin 1023 -> 0 bytes tests/input/res/drawable-xhdpi/ch_145_logo.png | Bin 1037 -> 0 bytes tests/input/res/drawable-xhdpi/ch_146_logo.png | Bin 994 -> 0 bytes tests/input/res/drawable-xhdpi/ch_147_logo.png | Bin 828 -> 0 bytes tests/input/res/drawable-xhdpi/ch_148_logo.png | Bin 1009 -> 0 bytes tests/input/res/drawable-xhdpi/ch_149_logo.png | Bin 882 -> 0 bytes tests/input/res/drawable-xhdpi/ch_14_logo.png | Bin 1193 -> 0 bytes tests/input/res/drawable-xhdpi/ch_150_logo.png | Bin 1314 -> 0 bytes tests/input/res/drawable-xhdpi/ch_151_logo.png | Bin 931 -> 0 bytes tests/input/res/drawable-xhdpi/ch_152_logo.png | Bin 1030 -> 0 bytes tests/input/res/drawable-xhdpi/ch_153_logo.png | Bin 1245 -> 0 bytes tests/input/res/drawable-xhdpi/ch_154_logo.png | Bin 1017 -> 0 bytes tests/input/res/drawable-xhdpi/ch_155_logo.png | Bin 1317 -> 0 bytes tests/input/res/drawable-xhdpi/ch_156_logo.png | Bin 1080 -> 0 bytes tests/input/res/drawable-xhdpi/ch_157_logo.png | Bin 1126 -> 0 bytes tests/input/res/drawable-xhdpi/ch_158_logo.png | Bin 1223 -> 0 bytes tests/input/res/drawable-xhdpi/ch_159_logo.png | Bin 1362 -> 0 bytes tests/input/res/drawable-xhdpi/ch_15_logo.png | Bin 1123 -> 0 bytes tests/input/res/drawable-xhdpi/ch_160_logo.png | Bin 1059 -> 0 bytes tests/input/res/drawable-xhdpi/ch_161_logo.png | Bin 1074 -> 0 bytes tests/input/res/drawable-xhdpi/ch_162_logo.png | Bin 1015 -> 0 bytes tests/input/res/drawable-xhdpi/ch_163_logo.png | Bin 1093 -> 0 bytes tests/input/res/drawable-xhdpi/ch_164_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_165_logo.png | Bin 1001 -> 0 bytes tests/input/res/drawable-xhdpi/ch_166_logo.png | Bin 1276 -> 0 bytes tests/input/res/drawable-xhdpi/ch_167_logo.png | Bin 1184 -> 0 bytes tests/input/res/drawable-xhdpi/ch_168_logo.png | Bin 963 -> 0 bytes tests/input/res/drawable-xhdpi/ch_169_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_16_logo.png | Bin 877 -> 0 bytes tests/input/res/drawable-xhdpi/ch_170_logo.png | Bin 957 -> 0 bytes tests/input/res/drawable-xhdpi/ch_171_logo.png | Bin 978 -> 0 bytes tests/input/res/drawable-xhdpi/ch_172_logo.png | Bin 939 -> 0 bytes tests/input/res/drawable-xhdpi/ch_173_logo.png | Bin 906 -> 0 bytes tests/input/res/drawable-xhdpi/ch_174_logo.png | Bin 875 -> 0 bytes tests/input/res/drawable-xhdpi/ch_175_logo.png | Bin 974 -> 0 bytes tests/input/res/drawable-xhdpi/ch_176_logo.png | Bin 1146 -> 0 bytes tests/input/res/drawable-xhdpi/ch_177_logo.png | Bin 951 -> 0 bytes tests/input/res/drawable-xhdpi/ch_178_logo.png | Bin 1224 -> 0 bytes tests/input/res/drawable-xhdpi/ch_179_logo.png | Bin 948 -> 0 bytes tests/input/res/drawable-xhdpi/ch_17_logo.png | Bin 956 -> 0 bytes tests/input/res/drawable-xhdpi/ch_180_logo.png | Bin 980 -> 0 bytes tests/input/res/drawable-xhdpi/ch_181_logo.png | Bin 903 -> 0 bytes tests/input/res/drawable-xhdpi/ch_182_logo.png | Bin 854 -> 0 bytes tests/input/res/drawable-xhdpi/ch_183_logo.png | Bin 908 -> 0 bytes tests/input/res/drawable-xhdpi/ch_184_logo.png | Bin 804 -> 0 bytes tests/input/res/drawable-xhdpi/ch_185_logo.png | Bin 840 -> 0 bytes tests/input/res/drawable-xhdpi/ch_186_logo.png | Bin 1238 -> 0 bytes tests/input/res/drawable-xhdpi/ch_187_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_188_logo.png | Bin 883 -> 0 bytes tests/input/res/drawable-xhdpi/ch_189_logo.png | Bin 1127 -> 0 bytes tests/input/res/drawable-xhdpi/ch_18_logo.png | Bin 903 -> 0 bytes tests/input/res/drawable-xhdpi/ch_190_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_191_logo.png | Bin 984 -> 0 bytes tests/input/res/drawable-xhdpi/ch_192_logo.png | Bin 1131 -> 0 bytes tests/input/res/drawable-xhdpi/ch_193_logo.png | Bin 1050 -> 0 bytes tests/input/res/drawable-xhdpi/ch_194_logo.png | Bin 1164 -> 0 bytes tests/input/res/drawable-xhdpi/ch_195_logo.png | Bin 918 -> 0 bytes tests/input/res/drawable-xhdpi/ch_196_logo.png | Bin 1124 -> 0 bytes tests/input/res/drawable-xhdpi/ch_197_logo.png | Bin 723 -> 0 bytes tests/input/res/drawable-xhdpi/ch_198_logo.png | Bin 929 -> 0 bytes tests/input/res/drawable-xhdpi/ch_199_logo.png | Bin 1072 -> 0 bytes tests/input/res/drawable-xhdpi/ch_19_logo.png | Bin 1011 -> 0 bytes tests/input/res/drawable-xhdpi/ch_1_logo.png | Bin 1009 -> 0 bytes tests/input/res/drawable-xhdpi/ch_200_logo.png | Bin 1019 -> 0 bytes tests/input/res/drawable-xhdpi/ch_201_logo.png | Bin 923 -> 0 bytes tests/input/res/drawable-xhdpi/ch_202_logo.png | Bin 1029 -> 0 bytes tests/input/res/drawable-xhdpi/ch_203_logo.png | Bin 1107 -> 0 bytes tests/input/res/drawable-xhdpi/ch_204_logo.png | Bin 1148 -> 0 bytes tests/input/res/drawable-xhdpi/ch_205_logo.png | Bin 954 -> 0 bytes tests/input/res/drawable-xhdpi/ch_206_logo.png | Bin 940 -> 0 bytes tests/input/res/drawable-xhdpi/ch_207_logo.png | Bin 1062 -> 0 bytes tests/input/res/drawable-xhdpi/ch_208_logo.png | Bin 1108 -> 0 bytes tests/input/res/drawable-xhdpi/ch_209_logo.png | Bin 1062 -> 0 bytes tests/input/res/drawable-xhdpi/ch_20_logo.png | Bin 973 -> 0 bytes tests/input/res/drawable-xhdpi/ch_210_logo.png | Bin 1230 -> 0 bytes tests/input/res/drawable-xhdpi/ch_211_logo.png | Bin 1048 -> 0 bytes tests/input/res/drawable-xhdpi/ch_212_logo.png | Bin 962 -> 0 bytes tests/input/res/drawable-xhdpi/ch_213_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_214_logo.png | Bin 888 -> 0 bytes tests/input/res/drawable-xhdpi/ch_215_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_216_logo.png | Bin 1038 -> 0 bytes tests/input/res/drawable-xhdpi/ch_217_logo.png | Bin 1058 -> 0 bytes tests/input/res/drawable-xhdpi/ch_218_logo.png | Bin 1124 -> 0 bytes tests/input/res/drawable-xhdpi/ch_219_logo.png | Bin 1277 -> 0 bytes tests/input/res/drawable-xhdpi/ch_21_logo.png | Bin 833 -> 0 bytes tests/input/res/drawable-xhdpi/ch_220_logo.png | Bin 1100 -> 0 bytes tests/input/res/drawable-xhdpi/ch_221_logo.png | Bin 972 -> 0 bytes tests/input/res/drawable-xhdpi/ch_222_logo.png | Bin 943 -> 0 bytes tests/input/res/drawable-xhdpi/ch_223_logo.png | Bin 850 -> 0 bytes tests/input/res/drawable-xhdpi/ch_224_logo.png | Bin 967 -> 0 bytes tests/input/res/drawable-xhdpi/ch_225_logo.png | Bin 898 -> 0 bytes tests/input/res/drawable-xhdpi/ch_226_logo.png | Bin 1287 -> 0 bytes tests/input/res/drawable-xhdpi/ch_227_logo.png | Bin 1013 -> 0 bytes tests/input/res/drawable-xhdpi/ch_228_logo.png | Bin 999 -> 0 bytes tests/input/res/drawable-xhdpi/ch_229_logo.png | Bin 1209 -> 0 bytes tests/input/res/drawable-xhdpi/ch_22_logo.png | Bin 814 -> 0 bytes tests/input/res/drawable-xhdpi/ch_230_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_231_logo.png | Bin 1371 -> 0 bytes tests/input/res/drawable-xhdpi/ch_232_logo.png | Bin 1038 -> 0 bytes tests/input/res/drawable-xhdpi/ch_233_logo.png | Bin 1234 -> 0 bytes tests/input/res/drawable-xhdpi/ch_234_logo.png | Bin 1122 -> 0 bytes tests/input/res/drawable-xhdpi/ch_235_logo.png | Bin 1351 -> 0 bytes tests/input/res/drawable-xhdpi/ch_236_logo.png | Bin 1089 -> 0 bytes tests/input/res/drawable-xhdpi/ch_237_logo.png | Bin 1143 -> 0 bytes tests/input/res/drawable-xhdpi/ch_238_logo.png | Bin 1028 -> 0 bytes tests/input/res/drawable-xhdpi/ch_239_logo.png | Bin 1116 -> 0 bytes tests/input/res/drawable-xhdpi/ch_23_logo.png | Bin 905 -> 0 bytes tests/input/res/drawable-xhdpi/ch_240_logo.png | Bin 1101 -> 0 bytes tests/input/res/drawable-xhdpi/ch_241_logo.png | Bin 959 -> 0 bytes tests/input/res/drawable-xhdpi/ch_242_logo.png | Bin 1238 -> 0 bytes tests/input/res/drawable-xhdpi/ch_243_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_244_logo.png | Bin 849 -> 0 bytes tests/input/res/drawable-xhdpi/ch_245_logo.png | Bin 1058 -> 0 bytes tests/input/res/drawable-xhdpi/ch_246_logo.png | Bin 991 -> 0 bytes tests/input/res/drawable-xhdpi/ch_247_logo.png | Bin 991 -> 0 bytes tests/input/res/drawable-xhdpi/ch_248_logo.png | Bin 1034 -> 0 bytes tests/input/res/drawable-xhdpi/ch_249_logo.png | Bin 942 -> 0 bytes tests/input/res/drawable-xhdpi/ch_24_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_250_logo.png | Bin 955 -> 0 bytes tests/input/res/drawable-xhdpi/ch_251_logo.png | Bin 966 -> 0 bytes tests/input/res/drawable-xhdpi/ch_252_logo.png | Bin 1048 -> 0 bytes tests/input/res/drawable-xhdpi/ch_253_logo.png | Bin 1084 -> 0 bytes tests/input/res/drawable-xhdpi/ch_254_logo.png | Bin 1231 -> 0 bytes tests/input/res/drawable-xhdpi/ch_255_logo.png | Bin 917 -> 0 bytes tests/input/res/drawable-xhdpi/ch_256_logo.png | Bin 999 -> 0 bytes tests/input/res/drawable-xhdpi/ch_257_logo.png | Bin 963 -> 0 bytes tests/input/res/drawable-xhdpi/ch_258_logo.png | Bin 968 -> 0 bytes tests/input/res/drawable-xhdpi/ch_259_logo.png | Bin 970 -> 0 bytes tests/input/res/drawable-xhdpi/ch_25_logo.png | Bin 994 -> 0 bytes tests/input/res/drawable-xhdpi/ch_260_logo.png | Bin 858 -> 0 bytes tests/input/res/drawable-xhdpi/ch_261_logo.png | Bin 809 -> 0 bytes tests/input/res/drawable-xhdpi/ch_262_logo.png | Bin 1179 -> 0 bytes tests/input/res/drawable-xhdpi/ch_263_logo.png | Bin 1170 -> 0 bytes tests/input/res/drawable-xhdpi/ch_264_logo.png | Bin 914 -> 0 bytes tests/input/res/drawable-xhdpi/ch_265_logo.png | Bin 1154 -> 0 bytes tests/input/res/drawable-xhdpi/ch_266_logo.png | Bin 947 -> 0 bytes tests/input/res/drawable-xhdpi/ch_267_logo.png | Bin 986 -> 0 bytes tests/input/res/drawable-xhdpi/ch_268_logo.png | Bin 1103 -> 0 bytes tests/input/res/drawable-xhdpi/ch_269_logo.png | Bin 1075 -> 0 bytes tests/input/res/drawable-xhdpi/ch_26_logo.png | Bin 1193 -> 0 bytes tests/input/res/drawable-xhdpi/ch_270_logo.png | Bin 1143 -> 0 bytes tests/input/res/drawable-xhdpi/ch_271_logo.png | Bin 872 -> 0 bytes tests/input/res/drawable-xhdpi/ch_272_logo.png | Bin 1057 -> 0 bytes tests/input/res/drawable-xhdpi/ch_273_logo.png | Bin 748 -> 0 bytes tests/input/res/drawable-xhdpi/ch_274_logo.png | Bin 896 -> 0 bytes tests/input/res/drawable-xhdpi/ch_275_logo.png | Bin 1095 -> 0 bytes tests/input/res/drawable-xhdpi/ch_276_logo.png | Bin 1061 -> 0 bytes tests/input/res/drawable-xhdpi/ch_277_logo.png | Bin 847 -> 0 bytes tests/input/res/drawable-xhdpi/ch_278_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_279_logo.png | Bin 1008 -> 0 bytes tests/input/res/drawable-xhdpi/ch_27_logo.png | Bin 887 -> 0 bytes tests/input/res/drawable-xhdpi/ch_280_logo.png | Bin 1142 -> 0 bytes tests/input/res/drawable-xhdpi/ch_281_logo.png | Bin 980 -> 0 bytes tests/input/res/drawable-xhdpi/ch_282_logo.png | Bin 833 -> 0 bytes tests/input/res/drawable-xhdpi/ch_283_logo.png | Bin 1080 -> 0 bytes tests/input/res/drawable-xhdpi/ch_284_logo.png | Bin 1082 -> 0 bytes tests/input/res/drawable-xhdpi/ch_285_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_286_logo.png | Bin 1254 -> 0 bytes tests/input/res/drawable-xhdpi/ch_287_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_288_logo.png | Bin 1013 -> 0 bytes tests/input/res/drawable-xhdpi/ch_289_logo.png | Bin 1058 -> 0 bytes tests/input/res/drawable-xhdpi/ch_28_logo.png | Bin 928 -> 0 bytes tests/input/res/drawable-xhdpi/ch_290_logo.png | Bin 1002 -> 0 bytes tests/input/res/drawable-xhdpi/ch_291_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_292_logo.png | Bin 946 -> 0 bytes tests/input/res/drawable-xhdpi/ch_293_logo.png | Bin 1160 -> 0 bytes tests/input/res/drawable-xhdpi/ch_294_logo.png | Bin 1133 -> 0 bytes tests/input/res/drawable-xhdpi/ch_295_logo.png | Bin 1303 -> 0 bytes tests/input/res/drawable-xhdpi/ch_296_logo.png | Bin 1085 -> 0 bytes tests/input/res/drawable-xhdpi/ch_297_logo.png | Bin 1060 -> 0 bytes tests/input/res/drawable-xhdpi/ch_298_logo.png | Bin 1028 -> 0 bytes tests/input/res/drawable-xhdpi/ch_299_logo.png | Bin 842 -> 0 bytes tests/input/res/drawable-xhdpi/ch_29_logo.png | Bin 886 -> 0 bytes tests/input/res/drawable-xhdpi/ch_2_logo.png | Bin 907 -> 0 bytes tests/input/res/drawable-xhdpi/ch_300_logo.png | Bin 934 -> 0 bytes tests/input/res/drawable-xhdpi/ch_301_logo.png | Bin 887 -> 0 bytes tests/input/res/drawable-xhdpi/ch_302_logo.png | Bin 1338 -> 0 bytes tests/input/res/drawable-xhdpi/ch_303_logo.png | Bin 1055 -> 0 bytes tests/input/res/drawable-xhdpi/ch_304_logo.png | Bin 1045 -> 0 bytes tests/input/res/drawable-xhdpi/ch_305_logo.png | Bin 1282 -> 0 bytes tests/input/res/drawable-xhdpi/ch_306_logo.png | Bin 1027 -> 0 bytes tests/input/res/drawable-xhdpi/ch_307_logo.png | Bin 1272 -> 0 bytes tests/input/res/drawable-xhdpi/ch_308_logo.png | Bin 1115 -> 0 bytes tests/input/res/drawable-xhdpi/ch_309_logo.png | Bin 1324 -> 0 bytes tests/input/res/drawable-xhdpi/ch_30_logo.png | Bin 894 -> 0 bytes tests/input/res/drawable-xhdpi/ch_310_logo.png | Bin 1222 -> 0 bytes tests/input/res/drawable-xhdpi/ch_311_logo.png | Bin 1303 -> 0 bytes tests/input/res/drawable-xhdpi/ch_312_logo.png | Bin 1057 -> 0 bytes tests/input/res/drawable-xhdpi/ch_313_logo.png | Bin 1085 -> 0 bytes tests/input/res/drawable-xhdpi/ch_314_logo.png | Bin 990 -> 0 bytes tests/input/res/drawable-xhdpi/ch_315_logo.png | Bin 1109 -> 0 bytes tests/input/res/drawable-xhdpi/ch_316_logo.png | Bin 1109 -> 0 bytes tests/input/res/drawable-xhdpi/ch_317_logo.png | Bin 968 -> 0 bytes tests/input/res/drawable-xhdpi/ch_318_logo.png | Bin 1206 -> 0 bytes tests/input/res/drawable-xhdpi/ch_319_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_31_logo.png | Bin 858 -> 0 bytes tests/input/res/drawable-xhdpi/ch_320_logo.png | Bin 979 -> 0 bytes tests/input/res/drawable-xhdpi/ch_321_logo.png | Bin 1092 -> 0 bytes tests/input/res/drawable-xhdpi/ch_322_logo.png | Bin 939 -> 0 bytes tests/input/res/drawable-xhdpi/ch_323_logo.png | Bin 1017 -> 0 bytes tests/input/res/drawable-xhdpi/ch_324_logo.png | Bin 1008 -> 0 bytes tests/input/res/drawable-xhdpi/ch_325_logo.png | Bin 953 -> 0 bytes tests/input/res/drawable-xhdpi/ch_326_logo.png | Bin 966 -> 0 bytes tests/input/res/drawable-xhdpi/ch_327_logo.png | Bin 1000 -> 0 bytes tests/input/res/drawable-xhdpi/ch_328_logo.png | Bin 1205 -> 0 bytes tests/input/res/drawable-xhdpi/ch_329_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_32_logo.png | Bin 781 -> 0 bytes tests/input/res/drawable-xhdpi/ch_330_logo.png | Bin 1201 -> 0 bytes tests/input/res/drawable-xhdpi/ch_331_logo.png | Bin 899 -> 0 bytes tests/input/res/drawable-xhdpi/ch_332_logo.png | Bin 946 -> 0 bytes tests/input/res/drawable-xhdpi/ch_333_logo.png | Bin 863 -> 0 bytes tests/input/res/drawable-xhdpi/ch_334_logo.png | Bin 857 -> 0 bytes tests/input/res/drawable-xhdpi/ch_335_logo.png | Bin 913 -> 0 bytes tests/input/res/drawable-xhdpi/ch_336_logo.png | Bin 805 -> 0 bytes tests/input/res/drawable-xhdpi/ch_337_logo.png | Bin 773 -> 0 bytes tests/input/res/drawable-xhdpi/ch_338_logo.png | Bin 1167 -> 0 bytes tests/input/res/drawable-xhdpi/ch_339_logo.png | Bin 1110 -> 0 bytes tests/input/res/drawable-xhdpi/ch_33_logo.png | Bin 708 -> 0 bytes tests/input/res/drawable-xhdpi/ch_340_logo.png | Bin 937 -> 0 bytes tests/input/res/drawable-xhdpi/ch_341_logo.png | Bin 1070 -> 0 bytes tests/input/res/drawable-xhdpi/ch_342_logo.png | Bin 984 -> 0 bytes tests/input/res/drawable-xhdpi/ch_343_logo.png | Bin 1010 -> 0 bytes tests/input/res/drawable-xhdpi/ch_344_logo.png | Bin 1091 -> 0 bytes tests/input/res/drawable-xhdpi/ch_345_logo.png | Bin 996 -> 0 bytes tests/input/res/drawable-xhdpi/ch_346_logo.png | Bin 1243 -> 0 bytes tests/input/res/drawable-xhdpi/ch_347_logo.png | Bin 920 -> 0 bytes tests/input/res/drawable-xhdpi/ch_348_logo.png | Bin 1103 -> 0 bytes tests/input/res/drawable-xhdpi/ch_349_logo.png | Bin 770 -> 0 bytes tests/input/res/drawable-xhdpi/ch_34_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_350_logo.png | Bin 961 -> 0 bytes tests/input/res/drawable-xhdpi/ch_351_logo.png | Bin 1147 -> 0 bytes tests/input/res/drawable-xhdpi/ch_352_logo.png | Bin 1036 -> 0 bytes tests/input/res/drawable-xhdpi/ch_353_logo.png | Bin 920 -> 0 bytes tests/input/res/drawable-xhdpi/ch_354_logo.png | Bin 1075 -> 0 bytes tests/input/res/drawable-xhdpi/ch_355_logo.png | Bin 1010 -> 0 bytes tests/input/res/drawable-xhdpi/ch_356_logo.png | Bin 1176 -> 0 bytes tests/input/res/drawable-xhdpi/ch_357_logo.png | Bin 997 -> 0 bytes tests/input/res/drawable-xhdpi/ch_358_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_359_logo.png | Bin 1102 -> 0 bytes tests/input/res/drawable-xhdpi/ch_35_logo.png | Bin 1038 -> 0 bytes tests/input/res/drawable-xhdpi/ch_360_logo.png | Bin 1119 -> 0 bytes tests/input/res/drawable-xhdpi/ch_361_logo.png | Bin 1025 -> 0 bytes tests/input/res/drawable-xhdpi/ch_362_logo.png | Bin 1264 -> 0 bytes tests/input/res/drawable-xhdpi/ch_363_logo.png | Bin 1099 -> 0 bytes tests/input/res/drawable-xhdpi/ch_364_logo.png | Bin 1032 -> 0 bytes tests/input/res/drawable-xhdpi/ch_365_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_366_logo.png | Bin 967 -> 0 bytes tests/input/res/drawable-xhdpi/ch_367_logo.png | Bin 1061 -> 0 bytes tests/input/res/drawable-xhdpi/ch_368_logo.png | Bin 1023 -> 0 bytes tests/input/res/drawable-xhdpi/ch_369_logo.png | Bin 1178 -> 0 bytes tests/input/res/drawable-xhdpi/ch_36_logo.png | Bin 847 -> 0 bytes tests/input/res/drawable-xhdpi/ch_370_logo.png | Bin 1148 -> 0 bytes tests/input/res/drawable-xhdpi/ch_371_logo.png | Bin 1249 -> 0 bytes tests/input/res/drawable-xhdpi/ch_372_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_373_logo.png | Bin 999 -> 0 bytes tests/input/res/drawable-xhdpi/ch_374_logo.png | Bin 989 -> 0 bytes tests/input/res/drawable-xhdpi/ch_375_logo.png | Bin 900 -> 0 bytes tests/input/res/drawable-xhdpi/ch_376_logo.png | Bin 1031 -> 0 bytes tests/input/res/drawable-xhdpi/ch_377_logo.png | Bin 886 -> 0 bytes tests/input/res/drawable-xhdpi/ch_378_logo.png | Bin 1334 -> 0 bytes tests/input/res/drawable-xhdpi/ch_379_logo.png | Bin 1099 -> 0 bytes tests/input/res/drawable-xhdpi/ch_37_logo.png | Bin 1060 -> 0 bytes tests/input/res/drawable-xhdpi/ch_380_logo.png | Bin 1009 -> 0 bytes tests/input/res/drawable-xhdpi/ch_381_logo.png | Bin 1223 -> 0 bytes tests/input/res/drawable-xhdpi/ch_382_logo.png | Bin 1072 -> 0 bytes tests/input/res/drawable-xhdpi/ch_383_logo.png | Bin 1249 -> 0 bytes tests/input/res/drawable-xhdpi/ch_384_logo.png | Bin 1083 -> 0 bytes tests/input/res/drawable-xhdpi/ch_385_logo.png | Bin 1344 -> 0 bytes tests/input/res/drawable-xhdpi/ch_386_logo.png | Bin 1244 -> 0 bytes tests/input/res/drawable-xhdpi/ch_387_logo.png | Bin 1380 -> 0 bytes tests/input/res/drawable-xhdpi/ch_388_logo.png | Bin 981 -> 0 bytes tests/input/res/drawable-xhdpi/ch_389_logo.png | Bin 1197 -> 0 bytes tests/input/res/drawable-xhdpi/ch_38_logo.png | Bin 938 -> 0 bytes tests/input/res/drawable-xhdpi/ch_390_logo.png | Bin 986 -> 0 bytes tests/input/res/drawable-xhdpi/ch_391_logo.png | Bin 1084 -> 0 bytes tests/input/res/drawable-xhdpi/ch_392_logo.png | Bin 1118 -> 0 bytes tests/input/res/drawable-xhdpi/ch_393_logo.png | Bin 981 -> 0 bytes tests/input/res/drawable-xhdpi/ch_394_logo.png | Bin 1231 -> 0 bytes tests/input/res/drawable-xhdpi/ch_395_logo.png | Bin 1243 -> 0 bytes tests/input/res/drawable-xhdpi/ch_396_logo.png | Bin 995 -> 0 bytes tests/input/res/drawable-xhdpi/ch_397_logo.png | Bin 1118 -> 0 bytes tests/input/res/drawable-xhdpi/ch_398_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_399_logo.png | Bin 1049 -> 0 bytes tests/input/res/drawable-xhdpi/ch_39_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_3_logo.png | Bin 1160 -> 0 bytes tests/input/res/drawable-xhdpi/ch_400_logo.png | Bin 974 -> 0 bytes tests/input/res/drawable-xhdpi/ch_401_logo.png | Bin 862 -> 0 bytes tests/input/res/drawable-xhdpi/ch_402_logo.png | Bin 945 -> 0 bytes tests/input/res/drawable-xhdpi/ch_403_logo.png | Bin 1004 -> 0 bytes tests/input/res/drawable-xhdpi/ch_404_logo.png | Bin 1106 -> 0 bytes tests/input/res/drawable-xhdpi/ch_405_logo.png | Bin 1051 -> 0 bytes tests/input/res/drawable-xhdpi/ch_406_logo.png | Bin 1184 -> 0 bytes tests/input/res/drawable-xhdpi/ch_407_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_408_logo.png | Bin 992 -> 0 bytes tests/input/res/drawable-xhdpi/ch_409_logo.png | Bin 988 -> 0 bytes tests/input/res/drawable-xhdpi/ch_40_logo.png | Bin 1078 -> 0 bytes tests/input/res/drawable-xhdpi/ch_410_logo.png | Bin 915 -> 0 bytes tests/input/res/drawable-xhdpi/ch_411_logo.png | Bin 842 -> 0 bytes tests/input/res/drawable-xhdpi/ch_412_logo.png | Bin 814 -> 0 bytes tests/input/res/drawable-xhdpi/ch_413_logo.png | Bin 817 -> 0 bytes tests/input/res/drawable-xhdpi/ch_414_logo.png | Bin 1120 -> 0 bytes tests/input/res/drawable-xhdpi/ch_415_logo.png | Bin 1116 -> 0 bytes tests/input/res/drawable-xhdpi/ch_416_logo.png | Bin 923 -> 0 bytes tests/input/res/drawable-xhdpi/ch_417_logo.png | Bin 1044 -> 0 bytes tests/input/res/drawable-xhdpi/ch_418_logo.png | Bin 946 -> 0 bytes tests/input/res/drawable-xhdpi/ch_419_logo.png | Bin 1045 -> 0 bytes tests/input/res/drawable-xhdpi/ch_41_logo.png | Bin 857 -> 0 bytes tests/input/res/drawable-xhdpi/ch_420_logo.png | Bin 1056 -> 0 bytes tests/input/res/drawable-xhdpi/ch_421_logo.png | Bin 1003 -> 0 bytes tests/input/res/drawable-xhdpi/ch_422_logo.png | Bin 1154 -> 0 bytes tests/input/res/drawable-xhdpi/ch_423_logo.png | Bin 921 -> 0 bytes tests/input/res/drawable-xhdpi/ch_424_logo.png | Bin 1069 -> 0 bytes tests/input/res/drawable-xhdpi/ch_425_logo.png | Bin 744 -> 0 bytes tests/input/res/drawable-xhdpi/ch_426_logo.png | Bin 923 -> 0 bytes tests/input/res/drawable-xhdpi/ch_427_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_428_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_429_logo.png | Bin 940 -> 0 bytes tests/input/res/drawable-xhdpi/ch_42_logo.png | Bin 1135 -> 0 bytes tests/input/res/drawable-xhdpi/ch_430_logo.png | Bin 1072 -> 0 bytes tests/input/res/drawable-xhdpi/ch_431_logo.png | Bin 1059 -> 0 bytes tests/input/res/drawable-xhdpi/ch_432_logo.png | Bin 1149 -> 0 bytes tests/input/res/drawable-xhdpi/ch_433_logo.png | Bin 933 -> 0 bytes tests/input/res/drawable-xhdpi/ch_434_logo.png | Bin 867 -> 0 bytes tests/input/res/drawable-xhdpi/ch_435_logo.png | Bin 1055 -> 0 bytes tests/input/res/drawable-xhdpi/ch_436_logo.png | Bin 1039 -> 0 bytes tests/input/res/drawable-xhdpi/ch_437_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_438_logo.png | Bin 1241 -> 0 bytes tests/input/res/drawable-xhdpi/ch_439_logo.png | Bin 1134 -> 0 bytes tests/input/res/drawable-xhdpi/ch_43_logo.png | Bin 810 -> 0 bytes tests/input/res/drawable-xhdpi/ch_440_logo.png | Bin 987 -> 0 bytes tests/input/res/drawable-xhdpi/ch_441_logo.png | Bin 905 -> 0 bytes tests/input/res/drawable-xhdpi/ch_442_logo.png | Bin 906 -> 0 bytes tests/input/res/drawable-xhdpi/ch_443_logo.png | Bin 1071 -> 0 bytes tests/input/res/drawable-xhdpi/ch_444_logo.png | Bin 871 -> 0 bytes tests/input/res/drawable-xhdpi/ch_445_logo.png | Bin 1073 -> 0 bytes tests/input/res/drawable-xhdpi/ch_446_logo.png | Bin 1083 -> 0 bytes tests/input/res/drawable-xhdpi/ch_447_logo.png | Bin 1196 -> 0 bytes tests/input/res/drawable-xhdpi/ch_448_logo.png | Bin 1077 -> 0 bytes tests/input/res/drawable-xhdpi/ch_449_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_44_logo.png | Bin 966 -> 0 bytes tests/input/res/drawable-xhdpi/ch_450_logo.png | Bin 1066 -> 0 bytes tests/input/res/drawable-xhdpi/ch_451_logo.png | Bin 845 -> 0 bytes tests/input/res/drawable-xhdpi/ch_452_logo.png | Bin 1028 -> 0 bytes tests/input/res/drawable-xhdpi/ch_453_logo.png | Bin 946 -> 0 bytes tests/input/res/drawable-xhdpi/ch_454_logo.png | Bin 1228 -> 0 bytes tests/input/res/drawable-xhdpi/ch_455_logo.png | Bin 1035 -> 0 bytes tests/input/res/drawable-xhdpi/ch_456_logo.png | Bin 1061 -> 0 bytes tests/input/res/drawable-xhdpi/ch_457_logo.png | Bin 1162 -> 0 bytes tests/input/res/drawable-xhdpi/ch_458_logo.png | Bin 1067 -> 0 bytes tests/input/res/drawable-xhdpi/ch_459_logo.png | Bin 1384 -> 0 bytes tests/input/res/drawable-xhdpi/ch_45_logo.png | Bin 674 -> 0 bytes tests/input/res/drawable-xhdpi/ch_460_logo.png | Bin 1087 -> 0 bytes tests/input/res/drawable-xhdpi/ch_461_logo.png | Bin 1234 -> 0 bytes tests/input/res/drawable-xhdpi/ch_462_logo.png | Bin 1212 -> 0 bytes tests/input/res/drawable-xhdpi/ch_463_logo.png | Bin 1360 -> 0 bytes tests/input/res/drawable-xhdpi/ch_464_logo.png | Bin 1024 -> 0 bytes tests/input/res/drawable-xhdpi/ch_465_logo.png | Bin 1159 -> 0 bytes tests/input/res/drawable-xhdpi/ch_466_logo.png | Bin 973 -> 0 bytes tests/input/res/drawable-xhdpi/ch_467_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_468_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_469_logo.png | Bin 1011 -> 0 bytes tests/input/res/drawable-xhdpi/ch_46_logo.png | Bin 815 -> 0 bytes tests/input/res/drawable-xhdpi/ch_470_logo.png | Bin 1271 -> 0 bytes tests/input/res/drawable-xhdpi/ch_471_logo.png | Bin 1096 -> 0 bytes tests/input/res/drawable-xhdpi/ch_472_logo.png | Bin 925 -> 0 bytes tests/input/res/drawable-xhdpi/ch_473_logo.png | Bin 1104 -> 0 bytes tests/input/res/drawable-xhdpi/ch_474_logo.png | Bin 892 -> 0 bytes tests/input/res/drawable-xhdpi/ch_475_logo.png | Bin 1065 -> 0 bytes tests/input/res/drawable-xhdpi/ch_476_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_477_logo.png | Bin 833 -> 0 bytes tests/input/res/drawable-xhdpi/ch_478_logo.png | Bin 936 -> 0 bytes tests/input/res/drawable-xhdpi/ch_479_logo.png | Bin 991 -> 0 bytes tests/input/res/drawable-xhdpi/ch_47_logo.png | Bin 1054 -> 0 bytes tests/input/res/drawable-xhdpi/ch_480_logo.png | Bin 1193 -> 0 bytes tests/input/res/drawable-xhdpi/ch_481_logo.png | Bin 1001 -> 0 bytes tests/input/res/drawable-xhdpi/ch_482_logo.png | Bin 1242 -> 0 bytes tests/input/res/drawable-xhdpi/ch_483_logo.png | Bin 981 -> 0 bytes tests/input/res/drawable-xhdpi/ch_484_logo.png | Bin 870 -> 0 bytes tests/input/res/drawable-xhdpi/ch_485_logo.png | Bin 992 -> 0 bytes tests/input/res/drawable-xhdpi/ch_486_logo.png | Bin 899 -> 0 bytes tests/input/res/drawable-xhdpi/ch_487_logo.png | Bin 930 -> 0 bytes tests/input/res/drawable-xhdpi/ch_488_logo.png | Bin 790 -> 0 bytes tests/input/res/drawable-xhdpi/ch_489_logo.png | Bin 846 -> 0 bytes tests/input/res/drawable-xhdpi/ch_48_logo.png | Bin 1008 -> 0 bytes tests/input/res/drawable-xhdpi/ch_490_logo.png | Bin 1228 -> 0 bytes tests/input/res/drawable-xhdpi/ch_491_logo.png | Bin 1125 -> 0 bytes tests/input/res/drawable-xhdpi/ch_492_logo.png | Bin 940 -> 0 bytes tests/input/res/drawable-xhdpi/ch_493_logo.png | Bin 1151 -> 0 bytes tests/input/res/drawable-xhdpi/ch_494_logo.png | Bin 866 -> 0 bytes tests/input/res/drawable-xhdpi/ch_495_logo.png | Bin 1072 -> 0 bytes tests/input/res/drawable-xhdpi/ch_496_logo.png | Bin 1155 -> 0 bytes tests/input/res/drawable-xhdpi/ch_497_logo.png | Bin 945 -> 0 bytes tests/input/res/drawable-xhdpi/ch_498_logo.png | Bin 1241 -> 0 bytes tests/input/res/drawable-xhdpi/ch_499_logo.png | Bin 873 -> 0 bytes tests/input/res/drawable-xhdpi/ch_49_logo.png | Bin 872 -> 0 bytes tests/input/res/drawable-xhdpi/ch_4_logo.png | Bin 852 -> 0 bytes tests/input/res/drawable-xhdpi/ch_500_logo.png | Bin 1022 -> 0 bytes tests/input/res/drawable-xhdpi/ch_501_logo.png | Bin 745 -> 0 bytes tests/input/res/drawable-xhdpi/ch_502_logo.png | Bin 944 -> 0 bytes tests/input/res/drawable-xhdpi/ch_503_logo.png | Bin 1198 -> 0 bytes tests/input/res/drawable-xhdpi/ch_504_logo.png | Bin 1087 -> 0 bytes tests/input/res/drawable-xhdpi/ch_505_logo.png | Bin 928 -> 0 bytes tests/input/res/drawable-xhdpi/ch_506_logo.png | Bin 1104 -> 0 bytes tests/input/res/drawable-xhdpi/ch_507_logo.png | Bin 1088 -> 0 bytes tests/input/res/drawable-xhdpi/ch_508_logo.png | Bin 1109 -> 0 bytes tests/input/res/drawable-xhdpi/ch_509_logo.png | Bin 973 -> 0 bytes tests/input/res/drawable-xhdpi/ch_50_logo.png | Bin 992 -> 0 bytes tests/input/res/drawable-xhdpi/ch_510_logo.png | Bin 935 -> 0 bytes tests/input/res/drawable-xhdpi/ch_511_logo.png | Bin 981 -> 0 bytes tests/input/res/drawable-xhdpi/ch_512_logo.png | Bin 1078 -> 0 bytes tests/input/res/drawable-xhdpi/ch_513_logo.png | Bin 1065 -> 0 bytes tests/input/res/drawable-xhdpi/ch_514_logo.png | Bin 1132 -> 0 bytes tests/input/res/drawable-xhdpi/ch_515_logo.png | Bin 1066 -> 0 bytes tests/input/res/drawable-xhdpi/ch_516_logo.png | Bin 1061 -> 0 bytes tests/input/res/drawable-xhdpi/ch_517_logo.png | Bin 1005 -> 0 bytes tests/input/res/drawable-xhdpi/ch_518_logo.png | Bin 975 -> 0 bytes tests/input/res/drawable-xhdpi/ch_519_logo.png | Bin 1143 -> 0 bytes tests/input/res/drawable-xhdpi/ch_51_logo.png | Bin 1003 -> 0 bytes tests/input/res/drawable-xhdpi/ch_520_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_521_logo.png | Bin 1123 -> 0 bytes tests/input/res/drawable-xhdpi/ch_522_logo.png | Bin 1086 -> 0 bytes tests/input/res/drawable-xhdpi/ch_523_logo.png | Bin 1308 -> 0 bytes tests/input/res/drawable-xhdpi/ch_524_logo.png | Bin 1129 -> 0 bytes tests/input/res/drawable-xhdpi/ch_525_logo.png | Bin 1021 -> 0 bytes tests/input/res/drawable-xhdpi/ch_526_logo.png | Bin 1020 -> 0 bytes tests/input/res/drawable-xhdpi/ch_527_logo.png | Bin 879 -> 0 bytes tests/input/res/drawable-xhdpi/ch_528_logo.png | Bin 1043 -> 0 bytes tests/input/res/drawable-xhdpi/ch_529_logo.png | Bin 961 -> 0 bytes tests/input/res/drawable-xhdpi/ch_52_logo.png | Bin 1083 -> 0 bytes tests/input/res/drawable-xhdpi/ch_530_logo.png | Bin 1332 -> 0 bytes tests/input/res/drawable-xhdpi/ch_531_logo.png | Bin 1071 -> 0 bytes tests/input/res/drawable-xhdpi/ch_532_logo.png | Bin 1071 -> 0 bytes tests/input/res/drawable-xhdpi/ch_533_logo.png | Bin 1211 -> 0 bytes tests/input/res/drawable-xhdpi/ch_534_logo.png | Bin 1058 -> 0 bytes tests/input/res/drawable-xhdpi/ch_535_logo.png | Bin 1380 -> 0 bytes tests/input/res/drawable-xhdpi/ch_536_logo.png | Bin 1124 -> 0 bytes tests/input/res/drawable-xhdpi/ch_537_logo.png | Bin 1298 -> 0 bytes tests/input/res/drawable-xhdpi/ch_538_logo.png | Bin 1273 -> 0 bytes tests/input/res/drawable-xhdpi/ch_539_logo.png | Bin 1391 -> 0 bytes tests/input/res/drawable-xhdpi/ch_53_logo.png | Bin 938 -> 0 bytes tests/input/res/drawable-xhdpi/ch_540_logo.png | Bin 1086 -> 0 bytes tests/input/res/drawable-xhdpi/ch_541_logo.png | Bin 1127 -> 0 bytes tests/input/res/drawable-xhdpi/ch_542_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_543_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_544_logo.png | Bin 1059 -> 0 bytes tests/input/res/drawable-xhdpi/ch_545_logo.png | Bin 949 -> 0 bytes tests/input/res/drawable-xhdpi/ch_546_logo.png | Bin 1343 -> 0 bytes tests/input/res/drawable-xhdpi/ch_547_logo.png | Bin 1138 -> 0 bytes tests/input/res/drawable-xhdpi/ch_548_logo.png | Bin 975 -> 0 bytes tests/input/res/drawable-xhdpi/ch_549_logo.png | Bin 1144 -> 0 bytes tests/input/res/drawable-xhdpi/ch_54_logo.png | Bin 808 -> 0 bytes tests/input/res/drawable-xhdpi/ch_550_logo.png | Bin 923 -> 0 bytes tests/input/res/drawable-xhdpi/ch_551_logo.png | Bin 953 -> 0 bytes tests/input/res/drawable-xhdpi/ch_552_logo.png | Bin 1005 -> 0 bytes tests/input/res/drawable-xhdpi/ch_553_logo.png | Bin 918 -> 0 bytes tests/input/res/drawable-xhdpi/ch_554_logo.png | Bin 892 -> 0 bytes tests/input/res/drawable-xhdpi/ch_555_logo.png | Bin 903 -> 0 bytes tests/input/res/drawable-xhdpi/ch_556_logo.png | Bin 1172 -> 0 bytes tests/input/res/drawable-xhdpi/ch_557_logo.png | Bin 1005 -> 0 bytes tests/input/res/drawable-xhdpi/ch_558_logo.png | Bin 1241 -> 0 bytes tests/input/res/drawable-xhdpi/ch_559_logo.png | Bin 963 -> 0 bytes tests/input/res/drawable-xhdpi/ch_55_logo.png | Bin 951 -> 0 bytes tests/input/res/drawable-xhdpi/ch_560_logo.png | Bin 1011 -> 0 bytes tests/input/res/drawable-xhdpi/ch_561_logo.png | Bin 968 -> 0 bytes tests/input/res/drawable-xhdpi/ch_562_logo.png | Bin 962 -> 0 bytes tests/input/res/drawable-xhdpi/ch_563_logo.png | Bin 982 -> 0 bytes tests/input/res/drawable-xhdpi/ch_564_logo.png | Bin 854 -> 0 bytes tests/input/res/drawable-xhdpi/ch_565_logo.png | Bin 827 -> 0 bytes tests/input/res/drawable-xhdpi/ch_566_logo.png | Bin 1186 -> 0 bytes tests/input/res/drawable-xhdpi/ch_567_logo.png | Bin 1145 -> 0 bytes tests/input/res/drawable-xhdpi/ch_568_logo.png | Bin 984 -> 0 bytes tests/input/res/drawable-xhdpi/ch_569_logo.png | Bin 1134 -> 0 bytes tests/input/res/drawable-xhdpi/ch_56_logo.png | Bin 967 -> 0 bytes tests/input/res/drawable-xhdpi/ch_570_logo.png | Bin 992 -> 0 bytes tests/input/res/drawable-xhdpi/ch_571_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_572_logo.png | Bin 1138 -> 0 bytes tests/input/res/drawable-xhdpi/ch_573_logo.png | Bin 995 -> 0 bytes tests/input/res/drawable-xhdpi/ch_574_logo.png | Bin 1169 -> 0 bytes tests/input/res/drawable-xhdpi/ch_575_logo.png | Bin 853 -> 0 bytes tests/input/res/drawable-xhdpi/ch_576_logo.png | Bin 1148 -> 0 bytes tests/input/res/drawable-xhdpi/ch_577_logo.png | Bin 684 -> 0 bytes tests/input/res/drawable-xhdpi/ch_578_logo.png | Bin 932 -> 0 bytes tests/input/res/drawable-xhdpi/ch_579_logo.png | Bin 1159 -> 0 bytes tests/input/res/drawable-xhdpi/ch_57_logo.png | Bin 981 -> 0 bytes tests/input/res/drawable-xhdpi/ch_580_logo.png | Bin 1107 -> 0 bytes tests/input/res/drawable-xhdpi/ch_581_logo.png | Bin 939 -> 0 bytes tests/input/res/drawable-xhdpi/ch_582_logo.png | Bin 1093 -> 0 bytes tests/input/res/drawable-xhdpi/ch_583_logo.png | Bin 1066 -> 0 bytes tests/input/res/drawable-xhdpi/ch_584_logo.png | Bin 1134 -> 0 bytes tests/input/res/drawable-xhdpi/ch_585_logo.png | Bin 960 -> 0 bytes tests/input/res/drawable-xhdpi/ch_586_logo.png | Bin 925 -> 0 bytes tests/input/res/drawable-xhdpi/ch_587_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_588_logo.png | Bin 1053 -> 0 bytes tests/input/res/drawable-xhdpi/ch_589_logo.png | Bin 1081 -> 0 bytes tests/input/res/drawable-xhdpi/ch_58_logo.png | Bin 1193 -> 0 bytes tests/input/res/drawable-xhdpi/ch_590_logo.png | Bin 1258 -> 0 bytes tests/input/res/drawable-xhdpi/ch_591_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_592_logo.png | Bin 1072 -> 0 bytes tests/input/res/drawable-xhdpi/ch_593_logo.png | Bin 1071 -> 0 bytes tests/input/res/drawable-xhdpi/ch_594_logo.png | Bin 988 -> 0 bytes tests/input/res/drawable-xhdpi/ch_595_logo.png | Bin 1106 -> 0 bytes tests/input/res/drawable-xhdpi/ch_596_logo.png | Bin 1089 -> 0 bytes tests/input/res/drawable-xhdpi/ch_597_logo.png | Bin 1132 -> 0 bytes tests/input/res/drawable-xhdpi/ch_598_logo.png | Bin 1164 -> 0 bytes tests/input/res/drawable-xhdpi/ch_599_logo.png | Bin 1257 -> 0 bytes tests/input/res/drawable-xhdpi/ch_59_logo.png | Bin 1097 -> 0 bytes tests/input/res/drawable-xhdpi/ch_5_logo.png | Bin 1137 -> 0 bytes tests/input/res/drawable-xhdpi/ch_600_logo.png | Bin 1107 -> 0 bytes tests/input/res/drawable-xhdpi/ch_601_logo.png | Bin 1059 -> 0 bytes tests/input/res/drawable-xhdpi/ch_602_logo.png | Bin 1078 -> 0 bytes tests/input/res/drawable-xhdpi/ch_603_logo.png | Bin 925 -> 0 bytes tests/input/res/drawable-xhdpi/ch_604_logo.png | Bin 961 -> 0 bytes tests/input/res/drawable-xhdpi/ch_605_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_606_logo.png | Bin 1217 -> 0 bytes tests/input/res/drawable-xhdpi/ch_607_logo.png | Bin 1094 -> 0 bytes tests/input/res/drawable-xhdpi/ch_608_logo.png | Bin 1050 -> 0 bytes tests/input/res/drawable-xhdpi/ch_609_logo.png | Bin 1273 -> 0 bytes tests/input/res/drawable-xhdpi/ch_60_logo.png | Bin 958 -> 0 bytes tests/input/res/drawable-xhdpi/ch_610_logo.png | Bin 1009 -> 0 bytes tests/input/res/drawable-xhdpi/ch_611_logo.png | Bin 1320 -> 0 bytes tests/input/res/drawable-xhdpi/ch_612_logo.png | Bin 1076 -> 0 bytes tests/input/res/drawable-xhdpi/ch_613_logo.png | Bin 1300 -> 0 bytes tests/input/res/drawable-xhdpi/ch_614_logo.png | Bin 1185 -> 0 bytes tests/input/res/drawable-xhdpi/ch_615_logo.png | Bin 1360 -> 0 bytes tests/input/res/drawable-xhdpi/ch_616_logo.png | Bin 985 -> 0 bytes tests/input/res/drawable-xhdpi/ch_617_logo.png | Bin 1113 -> 0 bytes tests/input/res/drawable-xhdpi/ch_618_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_619_logo.png | Bin 1031 -> 0 bytes tests/input/res/drawable-xhdpi/ch_61_logo.png | Bin 898 -> 0 bytes tests/input/res/drawable-xhdpi/ch_620_logo.png | Bin 1127 -> 0 bytes tests/input/res/drawable-xhdpi/ch_621_logo.png | Bin 975 -> 0 bytes tests/input/res/drawable-xhdpi/ch_622_logo.png | Bin 1268 -> 0 bytes tests/input/res/drawable-xhdpi/ch_623_logo.png | Bin 1233 -> 0 bytes tests/input/res/drawable-xhdpi/ch_624_logo.png | Bin 954 -> 0 bytes tests/input/res/drawable-xhdpi/ch_625_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_626_logo.png | Bin 931 -> 0 bytes tests/input/res/drawable-xhdpi/ch_627_logo.png | Bin 1068 -> 0 bytes tests/input/res/drawable-xhdpi/ch_628_logo.png | Bin 994 -> 0 bytes tests/input/res/drawable-xhdpi/ch_629_logo.png | Bin 911 -> 0 bytes tests/input/res/drawable-xhdpi/ch_62_logo.png | Bin 926 -> 0 bytes tests/input/res/drawable-xhdpi/ch_630_logo.png | Bin 974 -> 0 bytes tests/input/res/drawable-xhdpi/ch_631_logo.png | Bin 1006 -> 0 bytes tests/input/res/drawable-xhdpi/ch_632_logo.png | Bin 1207 -> 0 bytes tests/input/res/drawable-xhdpi/ch_633_logo.png | Bin 1019 -> 0 bytes tests/input/res/drawable-xhdpi/ch_634_logo.png | Bin 1185 -> 0 bytes tests/input/res/drawable-xhdpi/ch_635_logo.png | Bin 1005 -> 0 bytes tests/input/res/drawable-xhdpi/ch_636_logo.png | Bin 950 -> 0 bytes tests/input/res/drawable-xhdpi/ch_637_logo.png | Bin 942 -> 0 bytes tests/input/res/drawable-xhdpi/ch_638_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_639_logo.png | Bin 982 -> 0 bytes tests/input/res/drawable-xhdpi/ch_63_logo.png | Bin 1070 -> 0 bytes tests/input/res/drawable-xhdpi/ch_640_logo.png | Bin 861 -> 0 bytes tests/input/res/drawable-xhdpi/ch_641_logo.png | Bin 811 -> 0 bytes tests/input/res/drawable-xhdpi/ch_642_logo.png | Bin 1230 -> 0 bytes tests/input/res/drawable-xhdpi/ch_643_logo.png | Bin 1172 -> 0 bytes tests/input/res/drawable-xhdpi/ch_644_logo.png | Bin 883 -> 0 bytes tests/input/res/drawable-xhdpi/ch_645_logo.png | Bin 1078 -> 0 bytes tests/input/res/drawable-xhdpi/ch_646_logo.png | Bin 878 -> 0 bytes tests/input/res/drawable-xhdpi/ch_647_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_648_logo.png | Bin 1161 -> 0 bytes tests/input/res/drawable-xhdpi/ch_649_logo.png | Bin 1068 -> 0 bytes tests/input/res/drawable-xhdpi/ch_64_logo.png | Bin 976 -> 0 bytes tests/input/res/drawable-xhdpi/ch_650_logo.png | Bin 1263 -> 0 bytes tests/input/res/drawable-xhdpi/ch_651_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_652_logo.png | Bin 1138 -> 0 bytes tests/input/res/drawable-xhdpi/ch_653_logo.png | Bin 777 -> 0 bytes tests/input/res/drawable-xhdpi/ch_654_logo.png | Bin 894 -> 0 bytes tests/input/res/drawable-xhdpi/ch_655_logo.png | Bin 1052 -> 0 bytes tests/input/res/drawable-xhdpi/ch_656_logo.png | Bin 1049 -> 0 bytes tests/input/res/drawable-xhdpi/ch_657_logo.png | Bin 937 -> 0 bytes tests/input/res/drawable-xhdpi/ch_658_logo.png | Bin 1120 -> 0 bytes tests/input/res/drawable-xhdpi/ch_659_logo.png | Bin 1134 -> 0 bytes tests/input/res/drawable-xhdpi/ch_65_logo.png | Bin 1048 -> 0 bytes tests/input/res/drawable-xhdpi/ch_660_logo.png | Bin 1107 -> 0 bytes tests/input/res/drawable-xhdpi/ch_661_logo.png | Bin 929 -> 0 bytes tests/input/res/drawable-xhdpi/ch_662_logo.png | Bin 887 -> 0 bytes tests/input/res/drawable-xhdpi/ch_663_logo.png | Bin 1039 -> 0 bytes tests/input/res/drawable-xhdpi/ch_664_logo.png | Bin 1033 -> 0 bytes tests/input/res/drawable-xhdpi/ch_665_logo.png | Bin 1036 -> 0 bytes tests/input/res/drawable-xhdpi/ch_666_logo.png | Bin 1134 -> 0 bytes tests/input/res/drawable-xhdpi/ch_667_logo.png | Bin 1087 -> 0 bytes tests/input/res/drawable-xhdpi/ch_668_logo.png | Bin 1032 -> 0 bytes tests/input/res/drawable-xhdpi/ch_669_logo.png | Bin 1029 -> 0 bytes tests/input/res/drawable-xhdpi/ch_66_logo.png | Bin 1023 -> 0 bytes tests/input/res/drawable-xhdpi/ch_670_logo.png | Bin 974 -> 0 bytes tests/input/res/drawable-xhdpi/ch_671_logo.png | Bin 1103 -> 0 bytes tests/input/res/drawable-xhdpi/ch_672_logo.png | Bin 1016 -> 0 bytes tests/input/res/drawable-xhdpi/ch_673_logo.png | Bin 1157 -> 0 bytes tests/input/res/drawable-xhdpi/ch_674_logo.png | Bin 1118 -> 0 bytes tests/input/res/drawable-xhdpi/ch_675_logo.png | Bin 1288 -> 0 bytes tests/input/res/drawable-xhdpi/ch_676_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_677_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_678_logo.png | Bin 1081 -> 0 bytes tests/input/res/drawable-xhdpi/ch_679_logo.png | Bin 898 -> 0 bytes tests/input/res/drawable-xhdpi/ch_67_logo.png | Bin 1208 -> 0 bytes tests/input/res/drawable-xhdpi/ch_680_logo.png | Bin 1061 -> 0 bytes tests/input/res/drawable-xhdpi/ch_681_logo.png | Bin 901 -> 0 bytes tests/input/res/drawable-xhdpi/ch_682_logo.png | Bin 1340 -> 0 bytes tests/input/res/drawable-xhdpi/ch_683_logo.png | Bin 1123 -> 0 bytes tests/input/res/drawable-xhdpi/ch_684_logo.png | Bin 1045 -> 0 bytes tests/input/res/drawable-xhdpi/ch_685_logo.png | Bin 1220 -> 0 bytes tests/input/res/drawable-xhdpi/ch_686_logo.png | Bin 1042 -> 0 bytes tests/input/res/drawable-xhdpi/ch_687_logo.png | Bin 1417 -> 0 bytes tests/input/res/drawable-xhdpi/ch_688_logo.png | Bin 1052 -> 0 bytes tests/input/res/drawable-xhdpi/ch_689_logo.png | Bin 1331 -> 0 bytes tests/input/res/drawable-xhdpi/ch_68_logo.png | Bin 1106 -> 0 bytes tests/input/res/drawable-xhdpi/ch_690_logo.png | Bin 1253 -> 0 bytes tests/input/res/drawable-xhdpi/ch_691_logo.png | Bin 1359 -> 0 bytes tests/input/res/drawable-xhdpi/ch_692_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_693_logo.png | Bin 1169 -> 0 bytes tests/input/res/drawable-xhdpi/ch_694_logo.png | Bin 1025 -> 0 bytes tests/input/res/drawable-xhdpi/ch_695_logo.png | Bin 1136 -> 0 bytes tests/input/res/drawable-xhdpi/ch_696_logo.png | Bin 1078 -> 0 bytes tests/input/res/drawable-xhdpi/ch_697_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_698_logo.png | Bin 1366 -> 0 bytes tests/input/res/drawable-xhdpi/ch_699_logo.png | Bin 1191 -> 0 bytes tests/input/res/drawable-xhdpi/ch_69_logo.png | Bin 1035 -> 0 bytes tests/input/res/drawable-xhdpi/ch_6_logo.png | Bin 1002 -> 0 bytes tests/input/res/drawable-xhdpi/ch_700_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_701_logo.png | Bin 1088 -> 0 bytes tests/input/res/drawable-xhdpi/ch_702_logo.png | Bin 967 -> 0 bytes tests/input/res/drawable-xhdpi/ch_703_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_704_logo.png | Bin 986 -> 0 bytes tests/input/res/drawable-xhdpi/ch_705_logo.png | Bin 926 -> 0 bytes tests/input/res/drawable-xhdpi/ch_706_logo.png | Bin 952 -> 0 bytes tests/input/res/drawable-xhdpi/ch_707_logo.png | Bin 928 -> 0 bytes tests/input/res/drawable-xhdpi/ch_708_logo.png | Bin 1089 -> 0 bytes tests/input/res/drawable-xhdpi/ch_709_logo.png | Bin 1062 -> 0 bytes tests/input/res/drawable-xhdpi/ch_70_logo.png | Bin 996 -> 0 bytes tests/input/res/drawable-xhdpi/ch_710_logo.png | Bin 1152 -> 0 bytes tests/input/res/drawable-xhdpi/ch_711_logo.png | Bin 855 -> 0 bytes tests/input/res/drawable-xhdpi/ch_712_logo.png | Bin 944 -> 0 bytes tests/input/res/drawable-xhdpi/ch_713_logo.png | Bin 952 -> 0 bytes tests/input/res/drawable-xhdpi/ch_714_logo.png | Bin 861 -> 0 bytes tests/input/res/drawable-xhdpi/ch_715_logo.png | Bin 918 -> 0 bytes tests/input/res/drawable-xhdpi/ch_716_logo.png | Bin 827 -> 0 bytes tests/input/res/drawable-xhdpi/ch_717_logo.png | Bin 736 -> 0 bytes tests/input/res/drawable-xhdpi/ch_718_logo.png | Bin 1183 -> 0 bytes tests/input/res/drawable-xhdpi/ch_719_logo.png | Bin 1115 -> 0 bytes tests/input/res/drawable-xhdpi/ch_71_logo.png | Bin 770 -> 0 bytes tests/input/res/drawable-xhdpi/ch_720_logo.png | Bin 927 -> 0 bytes tests/input/res/drawable-xhdpi/ch_721_logo.png | Bin 1080 -> 0 bytes tests/input/res/drawable-xhdpi/ch_722_logo.png | Bin 909 -> 0 bytes tests/input/res/drawable-xhdpi/ch_723_logo.png | Bin 1002 -> 0 bytes tests/input/res/drawable-xhdpi/ch_724_logo.png | Bin 1110 -> 0 bytes tests/input/res/drawable-xhdpi/ch_725_logo.png | Bin 1037 -> 0 bytes tests/input/res/drawable-xhdpi/ch_726_logo.png | Bin 1195 -> 0 bytes tests/input/res/drawable-xhdpi/ch_727_logo.png | Bin 814 -> 0 bytes tests/input/res/drawable-xhdpi/ch_728_logo.png | Bin 1129 -> 0 bytes tests/input/res/drawable-xhdpi/ch_729_logo.png | Bin 743 -> 0 bytes tests/input/res/drawable-xhdpi/ch_72_logo.png | Bin 919 -> 0 bytes tests/input/res/drawable-xhdpi/ch_730_logo.png | Bin 921 -> 0 bytes tests/input/res/drawable-xhdpi/ch_731_logo.png | Bin 1122 -> 0 bytes tests/input/res/drawable-xhdpi/ch_732_logo.png | Bin 1054 -> 0 bytes tests/input/res/drawable-xhdpi/ch_733_logo.png | Bin 865 -> 0 bytes tests/input/res/drawable-xhdpi/ch_734_logo.png | Bin 1052 -> 0 bytes tests/input/res/drawable-xhdpi/ch_735_logo.png | Bin 1086 -> 0 bytes tests/input/res/drawable-xhdpi/ch_736_logo.png | Bin 1135 -> 0 bytes tests/input/res/drawable-xhdpi/ch_737_logo.png | Bin 911 -> 0 bytes tests/input/res/drawable-xhdpi/ch_738_logo.png | Bin 936 -> 0 bytes tests/input/res/drawable-xhdpi/ch_739_logo.png | Bin 1081 -> 0 bytes tests/input/res/drawable-xhdpi/ch_73_logo.png | Bin 861 -> 0 bytes tests/input/res/drawable-xhdpi/ch_740_logo.png | Bin 1011 -> 0 bytes tests/input/res/drawable-xhdpi/ch_741_logo.png | Bin 982 -> 0 bytes tests/input/res/drawable-xhdpi/ch_742_logo.png | Bin 1149 -> 0 bytes tests/input/res/drawable-xhdpi/ch_743_logo.png | Bin 1119 -> 0 bytes tests/input/res/drawable-xhdpi/ch_744_logo.png | Bin 944 -> 0 bytes tests/input/res/drawable-xhdpi/ch_745_logo.png | Bin 967 -> 0 bytes tests/input/res/drawable-xhdpi/ch_746_logo.png | Bin 958 -> 0 bytes tests/input/res/drawable-xhdpi/ch_747_logo.png | Bin 1035 -> 0 bytes tests/input/res/drawable-xhdpi/ch_748_logo.png | Bin 1023 -> 0 bytes tests/input/res/drawable-xhdpi/ch_749_logo.png | Bin 1105 -> 0 bytes tests/input/res/drawable-xhdpi/ch_74_logo.png | Bin 1126 -> 0 bytes tests/input/res/drawable-xhdpi/ch_750_logo.png | Bin 1147 -> 0 bytes tests/input/res/drawable-xhdpi/ch_751_logo.png | Bin 1249 -> 0 bytes tests/input/res/drawable-xhdpi/ch_752_logo.png | Bin 1117 -> 0 bytes tests/input/res/drawable-xhdpi/ch_753_logo.png | Bin 1070 -> 0 bytes tests/input/res/drawable-xhdpi/ch_754_logo.png | Bin 1040 -> 0 bytes tests/input/res/drawable-xhdpi/ch_755_logo.png | Bin 837 -> 0 bytes tests/input/res/drawable-xhdpi/ch_756_logo.png | Bin 1032 -> 0 bytes tests/input/res/drawable-xhdpi/ch_757_logo.png | Bin 879 -> 0 bytes tests/input/res/drawable-xhdpi/ch_758_logo.png | Bin 1220 -> 0 bytes tests/input/res/drawable-xhdpi/ch_759_logo.png | Bin 1088 -> 0 bytes tests/input/res/drawable-xhdpi/ch_75_logo.png | Bin 993 -> 0 bytes tests/input/res/drawable-xhdpi/ch_760_logo.png | Bin 1047 -> 0 bytes tests/input/res/drawable-xhdpi/ch_761_logo.png | Bin 1201 -> 0 bytes tests/input/res/drawable-xhdpi/ch_762_logo.png | Bin 1033 -> 0 bytes tests/input/res/drawable-xhdpi/ch_763_logo.png | Bin 1294 -> 0 bytes tests/input/res/drawable-xhdpi/ch_764_logo.png | Bin 1059 -> 0 bytes tests/input/res/drawable-xhdpi/ch_765_logo.png | Bin 1271 -> 0 bytes tests/input/res/drawable-xhdpi/ch_766_logo.png | Bin 1153 -> 0 bytes tests/input/res/drawable-xhdpi/ch_767_logo.png | Bin 1309 -> 0 bytes tests/input/res/drawable-xhdpi/ch_768_logo.png | Bin 1081 -> 0 bytes tests/input/res/drawable-xhdpi/ch_769_logo.png | Bin 1162 -> 0 bytes tests/input/res/drawable-xhdpi/ch_76_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_770_logo.png | Bin 897 -> 0 bytes tests/input/res/drawable-xhdpi/ch_771_logo.png | Bin 990 -> 0 bytes tests/input/res/drawable-xhdpi/ch_772_logo.png | Bin 1034 -> 0 bytes tests/input/res/drawable-xhdpi/ch_773_logo.png | Bin 933 -> 0 bytes tests/input/res/drawable-xhdpi/ch_774_logo.png | Bin 1208 -> 0 bytes tests/input/res/drawable-xhdpi/ch_775_logo.png | Bin 1064 -> 0 bytes tests/input/res/drawable-xhdpi/ch_776_logo.png | Bin 912 -> 0 bytes tests/input/res/drawable-xhdpi/ch_777_logo.png | Bin 943 -> 0 bytes tests/input/res/drawable-xhdpi/ch_778_logo.png | Bin 924 -> 0 bytes tests/input/res/drawable-xhdpi/ch_779_logo.png | Bin 1025 -> 0 bytes tests/input/res/drawable-xhdpi/ch_77_logo.png | Bin 1091 -> 0 bytes tests/input/res/drawable-xhdpi/ch_780_logo.png | Bin 1033 -> 0 bytes tests/input/res/drawable-xhdpi/ch_781_logo.png | Bin 904 -> 0 bytes tests/input/res/drawable-xhdpi/ch_782_logo.png | Bin 899 -> 0 bytes tests/input/res/drawable-xhdpi/ch_783_logo.png | Bin 1013 -> 0 bytes tests/input/res/drawable-xhdpi/ch_784_logo.png | Bin 1153 -> 0 bytes tests/input/res/drawable-xhdpi/ch_785_logo.png | Bin 1052 -> 0 bytes tests/input/res/drawable-xhdpi/ch_786_logo.png | Bin 1264 -> 0 bytes tests/input/res/drawable-xhdpi/ch_787_logo.png | Bin 910 -> 0 bytes tests/input/res/drawable-xhdpi/ch_788_logo.png | Bin 931 -> 0 bytes tests/input/res/drawable-xhdpi/ch_789_logo.png | Bin 983 -> 0 bytes tests/input/res/drawable-xhdpi/ch_78_logo.png | Bin 969 -> 0 bytes tests/input/res/drawable-xhdpi/ch_790_logo.png | Bin 943 -> 0 bytes tests/input/res/drawable-xhdpi/ch_791_logo.png | Bin 864 -> 0 bytes tests/input/res/drawable-xhdpi/ch_792_logo.png | Bin 838 -> 0 bytes tests/input/res/drawable-xhdpi/ch_793_logo.png | Bin 843 -> 0 bytes tests/input/res/drawable-xhdpi/ch_794_logo.png | Bin 1175 -> 0 bytes tests/input/res/drawable-xhdpi/ch_795_logo.png | Bin 1087 -> 0 bytes tests/input/res/drawable-xhdpi/ch_796_logo.png | Bin 934 -> 0 bytes tests/input/res/drawable-xhdpi/ch_797_logo.png | Bin 1075 -> 0 bytes tests/input/res/drawable-xhdpi/ch_798_logo.png | Bin 986 -> 0 bytes tests/input/res/drawable-xhdpi/ch_799_logo.png | Bin 1010 -> 0 bytes tests/input/res/drawable-xhdpi/ch_79_logo.png | Bin 1300 -> 0 bytes tests/input/res/drawable-xhdpi/ch_7_logo.png | Bin 1196 -> 0 bytes tests/input/res/drawable-xhdpi/ch_800_logo.png | Bin 1135 -> 0 bytes tests/input/res/drawable-xhdpi/ch_801_logo.png | Bin 1043 -> 0 bytes tests/input/res/drawable-xhdpi/ch_802_logo.png | Bin 1254 -> 0 bytes tests/input/res/drawable-xhdpi/ch_803_logo.png | Bin 931 -> 0 bytes tests/input/res/drawable-xhdpi/ch_804_logo.png | Bin 1145 -> 0 bytes tests/input/res/drawable-xhdpi/ch_805_logo.png | Bin 778 -> 0 bytes tests/input/res/drawable-xhdpi/ch_806_logo.png | Bin 964 -> 0 bytes tests/input/res/drawable-xhdpi/ch_807_logo.png | Bin 1157 -> 0 bytes tests/input/res/drawable-xhdpi/ch_808_logo.png | Bin 1047 -> 0 bytes tests/input/res/drawable-xhdpi/ch_809_logo.png | Bin 982 -> 0 bytes tests/input/res/drawable-xhdpi/ch_80_logo.png | Bin 1035 -> 0 bytes tests/input/res/drawable-xhdpi/ch_810_logo.png | Bin 1079 -> 0 bytes tests/input/res/drawable-xhdpi/ch_811_logo.png | Bin 960 -> 0 bytes tests/input/res/drawable-xhdpi/ch_812_logo.png | Bin 1154 -> 0 bytes tests/input/res/drawable-xhdpi/ch_813_logo.png | Bin 1006 -> 0 bytes tests/input/res/drawable-xhdpi/ch_814_logo.png | Bin 849 -> 0 bytes tests/input/res/drawable-xhdpi/ch_815_logo.png | Bin 1054 -> 0 bytes tests/input/res/drawable-xhdpi/ch_816_logo.png | Bin 1093 -> 0 bytes tests/input/res/drawable-xhdpi/ch_817_logo.png | Bin 1031 -> 0 bytes tests/input/res/drawable-xhdpi/ch_818_logo.png | Bin 1164 -> 0 bytes tests/input/res/drawable-xhdpi/ch_819_logo.png | Bin 1132 -> 0 bytes tests/input/res/drawable-xhdpi/ch_81_logo.png | Bin 1191 -> 0 bytes tests/input/res/drawable-xhdpi/ch_820_logo.png | Bin 1066 -> 0 bytes tests/input/res/drawable-xhdpi/ch_821_logo.png | Bin 1012 -> 0 bytes tests/input/res/drawable-xhdpi/ch_822_logo.png | Bin 947 -> 0 bytes tests/input/res/drawable-xhdpi/ch_823_logo.png | Bin 1151 -> 0 bytes tests/input/res/drawable-xhdpi/ch_824_logo.png | Bin 984 -> 0 bytes tests/input/res/drawable-xhdpi/ch_825_logo.png | Bin 1086 -> 0 bytes tests/input/res/drawable-xhdpi/ch_826_logo.png | Bin 1161 -> 0 bytes tests/input/res/drawable-xhdpi/ch_827_logo.png | Bin 1283 -> 0 bytes tests/input/res/drawable-xhdpi/ch_828_logo.png | Bin 1100 -> 0 bytes tests/input/res/drawable-xhdpi/ch_829_logo.png | Bin 1100 -> 0 bytes tests/input/res/drawable-xhdpi/ch_82_logo.png | Bin 1151 -> 0 bytes tests/input/res/drawable-xhdpi/ch_830_logo.png | Bin 1034 -> 0 bytes tests/input/res/drawable-xhdpi/ch_831_logo.png | Bin 885 -> 0 bytes tests/input/res/drawable-xhdpi/ch_832_logo.png | Bin 1056 -> 0 bytes tests/input/res/drawable-xhdpi/ch_833_logo.png | Bin 924 -> 0 bytes tests/input/res/drawable-xhdpi/ch_834_logo.png | Bin 1339 -> 0 bytes tests/input/res/drawable-xhdpi/ch_835_logo.png | Bin 1111 -> 0 bytes tests/input/res/drawable-xhdpi/ch_836_logo.png | Bin 1075 -> 0 bytes tests/input/res/drawable-xhdpi/ch_837_logo.png | Bin 1251 -> 0 bytes tests/input/res/drawable-xhdpi/ch_838_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_839_logo.png | Bin 1448 -> 0 bytes tests/input/res/drawable-xhdpi/ch_83_logo.png | Bin 1315 -> 0 bytes tests/input/res/drawable-xhdpi/ch_840_logo.png | Bin 1102 -> 0 bytes tests/input/res/drawable-xhdpi/ch_841_logo.png | Bin 1254 -> 0 bytes tests/input/res/drawable-xhdpi/ch_842_logo.png | Bin 1209 -> 0 bytes tests/input/res/drawable-xhdpi/ch_843_logo.png | Bin 1346 -> 0 bytes tests/input/res/drawable-xhdpi/ch_844_logo.png | Bin 985 -> 0 bytes tests/input/res/drawable-xhdpi/ch_845_logo.png | Bin 1095 -> 0 bytes tests/input/res/drawable-xhdpi/ch_846_logo.png | Bin 1056 -> 0 bytes tests/input/res/drawable-xhdpi/ch_847_logo.png | Bin 1008 -> 0 bytes tests/input/res/drawable-xhdpi/ch_848_logo.png | Bin 1057 -> 0 bytes tests/input/res/drawable-xhdpi/ch_849_logo.png | Bin 1018 -> 0 bytes tests/input/res/drawable-xhdpi/ch_84_logo.png | Bin 991 -> 0 bytes tests/input/res/drawable-xhdpi/ch_850_logo.png | Bin 1347 -> 0 bytes tests/input/res/drawable-xhdpi/ch_851_logo.png | Bin 1196 -> 0 bytes tests/input/res/drawable-xhdpi/ch_852_logo.png | Bin 941 -> 0 bytes tests/input/res/drawable-xhdpi/ch_853_logo.png | Bin 1156 -> 0 bytes tests/input/res/drawable-xhdpi/ch_854_logo.png | Bin 986 -> 0 bytes tests/input/res/drawable-xhdpi/ch_855_logo.png | Bin 1071 -> 0 bytes tests/input/res/drawable-xhdpi/ch_856_logo.png | Bin 1074 -> 0 bytes tests/input/res/drawable-xhdpi/ch_857_logo.png | Bin 942 -> 0 bytes tests/input/res/drawable-xhdpi/ch_858_logo.png | Bin 920 -> 0 bytes tests/input/res/drawable-xhdpi/ch_859_logo.png | Bin 1046 -> 0 bytes tests/input/res/drawable-xhdpi/ch_85_logo.png | Bin 1104 -> 0 bytes tests/input/res/drawable-xhdpi/ch_860_logo.png | Bin 1210 -> 0 bytes tests/input/res/drawable-xhdpi/ch_861_logo.png | Bin 1032 -> 0 bytes tests/input/res/drawable-xhdpi/ch_862_logo.png | Bin 1263 -> 0 bytes tests/input/res/drawable-xhdpi/ch_863_logo.png | Bin 1015 -> 0 bytes tests/input/res/drawable-xhdpi/ch_864_logo.png | Bin 989 -> 0 bytes tests/input/res/drawable-xhdpi/ch_865_logo.png | Bin 1014 -> 0 bytes tests/input/res/drawable-xhdpi/ch_866_logo.png | Bin 923 -> 0 bytes tests/input/res/drawable-xhdpi/ch_867_logo.png | Bin 961 -> 0 bytes tests/input/res/drawable-xhdpi/ch_868_logo.png | Bin 824 -> 0 bytes tests/input/res/drawable-xhdpi/ch_869_logo.png | Bin 875 -> 0 bytes tests/input/res/drawable-xhdpi/ch_86_logo.png | Bin 975 -> 0 bytes tests/input/res/drawable-xhdpi/ch_870_logo.png | Bin 1125 -> 0 bytes tests/input/res/drawable-xhdpi/ch_871_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_872_logo.png | Bin 937 -> 0 bytes tests/input/res/drawable-xhdpi/ch_873_logo.png | Bin 1131 -> 0 bytes tests/input/res/drawable-xhdpi/ch_874_logo.png | Bin 944 -> 0 bytes tests/input/res/drawable-xhdpi/ch_875_logo.png | Bin 1026 -> 0 bytes tests/input/res/drawable-xhdpi/ch_876_logo.png | Bin 1149 -> 0 bytes tests/input/res/drawable-xhdpi/ch_877_logo.png | Bin 977 -> 0 bytes tests/input/res/drawable-xhdpi/ch_878_logo.png | Bin 1075 -> 0 bytes tests/input/res/drawable-xhdpi/ch_879_logo.png | Bin 941 -> 0 bytes tests/input/res/drawable-xhdpi/ch_87_logo.png | Bin 959 -> 0 bytes tests/input/res/drawable-xhdpi/ch_880_logo.png | Bin 1081 -> 0 bytes tests/input/res/drawable-xhdpi/ch_881_logo.png | Bin 679 -> 0 bytes tests/input/res/drawable-xhdpi/ch_882_logo.png | Bin 883 -> 0 bytes tests/input/res/drawable-xhdpi/ch_883_logo.png | Bin 1127 -> 0 bytes tests/input/res/drawable-xhdpi/ch_884_logo.png | Bin 1028 -> 0 bytes tests/input/res/drawable-xhdpi/ch_885_logo.png | Bin 904 -> 0 bytes tests/input/res/drawable-xhdpi/ch_886_logo.png | Bin 1046 -> 0 bytes tests/input/res/drawable-xhdpi/ch_887_logo.png | Bin 1040 -> 0 bytes tests/input/res/drawable-xhdpi/ch_888_logo.png | Bin 1030 -> 0 bytes tests/input/res/drawable-xhdpi/ch_889_logo.png | Bin 961 -> 0 bytes tests/input/res/drawable-xhdpi/ch_88_logo.png | Bin 982 -> 0 bytes tests/input/res/drawable-xhdpi/ch_890_logo.png | Bin 954 -> 0 bytes tests/input/res/drawable-xhdpi/ch_891_logo.png | Bin 1070 -> 0 bytes tests/input/res/drawable-xhdpi/ch_892_logo.png | Bin 1055 -> 0 bytes tests/input/res/drawable-xhdpi/ch_893_logo.png | Bin 1092 -> 0 bytes tests/input/res/drawable-xhdpi/ch_894_logo.png | Bin 1237 -> 0 bytes tests/input/res/drawable-xhdpi/ch_895_logo.png | Bin 1100 -> 0 bytes tests/input/res/drawable-xhdpi/ch_896_logo.png | Bin 1095 -> 0 bytes tests/input/res/drawable-xhdpi/ch_897_logo.png | Bin 1040 -> 0 bytes tests/input/res/drawable-xhdpi/ch_898_logo.png | Bin 959 -> 0 bytes tests/input/res/drawable-xhdpi/ch_899_logo.png | Bin 1099 -> 0 bytes tests/input/res/drawable-xhdpi/ch_89_logo.png | Bin 939 -> 0 bytes tests/input/res/drawable-xhdpi/ch_8_logo.png | Bin 914 -> 0 bytes tests/input/res/drawable-xhdpi/ch_900_logo.png | Bin 1021 -> 0 bytes tests/input/res/drawable-xhdpi/ch_901_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_902_logo.png | Bin 1157 -> 0 bytes tests/input/res/drawable-xhdpi/ch_903_logo.png | Bin 1307 -> 0 bytes tests/input/res/drawable-xhdpi/ch_904_logo.png | Bin 1151 -> 0 bytes tests/input/res/drawable-xhdpi/ch_905_logo.png | Bin 1106 -> 0 bytes tests/input/res/drawable-xhdpi/ch_906_logo.png | Bin 1102 -> 0 bytes tests/input/res/drawable-xhdpi/ch_907_logo.png | Bin 900 -> 0 bytes tests/input/res/drawable-xhdpi/ch_908_logo.png | Bin 1054 -> 0 bytes tests/input/res/drawable-xhdpi/ch_909_logo.png | Bin 928 -> 0 bytes tests/input/res/drawable-xhdpi/ch_90_logo.png | Bin 1252 -> 0 bytes tests/input/res/drawable-xhdpi/ch_910_logo.png | Bin 1225 -> 0 bytes tests/input/res/drawable-xhdpi/ch_911_logo.png | Bin 1016 -> 0 bytes tests/input/res/drawable-xhdpi/ch_912_logo.png | Bin 1002 -> 0 bytes tests/input/res/drawable-xhdpi/ch_913_logo.png | Bin 1245 -> 0 bytes tests/input/res/drawable-xhdpi/ch_914_logo.png | Bin 1030 -> 0 bytes tests/input/res/drawable-xhdpi/ch_915_logo.png | Bin 1384 -> 0 bytes tests/input/res/drawable-xhdpi/ch_916_logo.png | Bin 1095 -> 0 bytes tests/input/res/drawable-xhdpi/ch_917_logo.png | Bin 1259 -> 0 bytes tests/input/res/drawable-xhdpi/ch_918_logo.png | Bin 1229 -> 0 bytes tests/input/res/drawable-xhdpi/ch_919_logo.png | Bin 1311 -> 0 bytes tests/input/res/drawable-xhdpi/ch_91_logo.png | Bin 1121 -> 0 bytes tests/input/res/drawable-xhdpi/ch_920_logo.png | Bin 1011 -> 0 bytes tests/input/res/drawable-xhdpi/ch_921_logo.png | Bin 1133 -> 0 bytes tests/input/res/drawable-xhdpi/ch_922_logo.png | Bin 913 -> 0 bytes tests/input/res/drawable-xhdpi/ch_923_logo.png | Bin 1119 -> 0 bytes tests/input/res/drawable-xhdpi/ch_924_logo.png | Bin 1104 -> 0 bytes tests/input/res/drawable-xhdpi/ch_925_logo.png | Bin 1027 -> 0 bytes tests/input/res/drawable-xhdpi/ch_926_logo.png | Bin 1346 -> 0 bytes tests/input/res/drawable-xhdpi/ch_927_logo.png | Bin 1126 -> 0 bytes tests/input/res/drawable-xhdpi/ch_928_logo.png | Bin 989 -> 0 bytes tests/input/res/drawable-xhdpi/ch_929_logo.png | Bin 1088 -> 0 bytes tests/input/res/drawable-xhdpi/ch_92_logo.png | Bin 907 -> 0 bytes tests/input/res/drawable-xhdpi/ch_930_logo.png | Bin 1017 -> 0 bytes tests/input/res/drawable-xhdpi/ch_931_logo.png | Bin 1008 -> 0 bytes tests/input/res/drawable-xhdpi/ch_932_logo.png | Bin 1047 -> 0 bytes tests/input/res/drawable-xhdpi/ch_933_logo.png | Bin 908 -> 0 bytes tests/input/res/drawable-xhdpi/ch_934_logo.png | Bin 912 -> 0 bytes tests/input/res/drawable-xhdpi/ch_935_logo.png | Bin 1041 -> 0 bytes tests/input/res/drawable-xhdpi/ch_936_logo.png | Bin 1219 -> 0 bytes tests/input/res/drawable-xhdpi/ch_937_logo.png | Bin 1065 -> 0 bytes tests/input/res/drawable-xhdpi/ch_938_logo.png | Bin 1194 -> 0 bytes tests/input/res/drawable-xhdpi/ch_939_logo.png | Bin 908 -> 0 bytes tests/input/res/drawable-xhdpi/ch_93_logo.png | Bin 1077 -> 0 bytes tests/input/res/drawable-xhdpi/ch_940_logo.png | Bin 1009 -> 0 bytes tests/input/res/drawable-xhdpi/ch_941_logo.png | Bin 913 -> 0 bytes tests/input/res/drawable-xhdpi/ch_942_logo.png | Bin 947 -> 0 bytes tests/input/res/drawable-xhdpi/ch_943_logo.png | Bin 971 -> 0 bytes tests/input/res/drawable-xhdpi/ch_944_logo.png | Bin 795 -> 0 bytes tests/input/res/drawable-xhdpi/ch_945_logo.png | Bin 850 -> 0 bytes tests/input/res/drawable-xhdpi/ch_946_logo.png | Bin 1255 -> 0 bytes tests/input/res/drawable-xhdpi/ch_947_logo.png | Bin 1135 -> 0 bytes tests/input/res/drawable-xhdpi/ch_948_logo.png | Bin 942 -> 0 bytes tests/input/res/drawable-xhdpi/ch_949_logo.png | Bin 1091 -> 0 bytes tests/input/res/drawable-xhdpi/ch_94_logo.png | Bin 913 -> 0 bytes tests/input/res/drawable-xhdpi/ch_950_logo.png | Bin 997 -> 0 bytes tests/input/res/drawable-xhdpi/ch_951_logo.png | Bin 1060 -> 0 bytes tests/input/res/drawable-xhdpi/ch_952_logo.png | Bin 1153 -> 0 bytes tests/input/res/drawable-xhdpi/ch_953_logo.png | Bin 1057 -> 0 bytes tests/input/res/drawable-xhdpi/ch_954_logo.png | Bin 1233 -> 0 bytes tests/input/res/drawable-xhdpi/ch_955_logo.png | Bin 901 -> 0 bytes tests/input/res/drawable-xhdpi/ch_956_logo.png | Bin 1177 -> 0 bytes tests/input/res/drawable-xhdpi/ch_957_logo.png | Bin 751 -> 0 bytes tests/input/res/drawable-xhdpi/ch_958_logo.png | Bin 965 -> 0 bytes tests/input/res/drawable-xhdpi/ch_959_logo.png | Bin 1125 -> 0 bytes tests/input/res/drawable-xhdpi/ch_95_logo.png | Bin 1048 -> 0 bytes tests/input/res/drawable-xhdpi/ch_960_logo.png | Bin 1102 -> 0 bytes tests/input/res/drawable-xhdpi/ch_961_logo.png | Bin 943 -> 0 bytes tests/input/res/drawable-xhdpi/ch_962_logo.png | Bin 1107 -> 0 bytes tests/input/res/drawable-xhdpi/ch_963_logo.png | Bin 1138 -> 0 bytes tests/input/res/drawable-xhdpi/ch_964_logo.png | Bin 1173 -> 0 bytes tests/input/res/drawable-xhdpi/ch_965_logo.png | Bin 1024 -> 0 bytes tests/input/res/drawable-xhdpi/ch_966_logo.png | Bin 904 -> 0 bytes tests/input/res/drawable-xhdpi/ch_967_logo.png | Bin 1020 -> 0 bytes tests/input/res/drawable-xhdpi/ch_968_logo.png | Bin 1074 -> 0 bytes tests/input/res/drawable-xhdpi/ch_969_logo.png | Bin 1022 -> 0 bytes tests/input/res/drawable-xhdpi/ch_96_logo.png | Bin 989 -> 0 bytes tests/input/res/drawable-xhdpi/ch_970_logo.png | Bin 1164 -> 0 bytes tests/input/res/drawable-xhdpi/ch_971_logo.png | Bin 1103 -> 0 bytes tests/input/res/drawable-xhdpi/ch_972_logo.png | Bin 1054 -> 0 bytes tests/input/res/drawable-xhdpi/ch_973_logo.png | Bin 1044 -> 0 bytes tests/input/res/drawable-xhdpi/ch_974_logo.png | Bin 966 -> 0 bytes tests/input/res/drawable-xhdpi/ch_975_logo.png | Bin 1098 -> 0 bytes tests/input/res/drawable-xhdpi/ch_976_logo.png | Bin 1068 -> 0 bytes tests/input/res/drawable-xhdpi/ch_977_logo.png | Bin 1062 -> 0 bytes tests/input/res/drawable-xhdpi/ch_978_logo.png | Bin 1157 -> 0 bytes tests/input/res/drawable-xhdpi/ch_979_logo.png | Bin 1234 -> 0 bytes tests/input/res/drawable-xhdpi/ch_97_logo.png | Bin 861 -> 0 bytes tests/input/res/drawable-xhdpi/ch_980_logo.png | Bin 1188 -> 0 bytes tests/input/res/drawable-xhdpi/ch_981_logo.png | Bin 1069 -> 0 bytes tests/input/res/drawable-xhdpi/ch_982_logo.png | Bin 1087 -> 0 bytes tests/input/res/drawable-xhdpi/ch_983_logo.png | Bin 920 -> 0 bytes tests/input/res/drawable-xhdpi/ch_984_logo.png | Bin 1041 -> 0 bytes tests/input/res/drawable-xhdpi/ch_985_logo.png | Bin 984 -> 0 bytes tests/input/res/drawable-xhdpi/ch_986_logo.png | Bin 1346 -> 0 bytes tests/input/res/drawable-xhdpi/ch_987_logo.png | Bin 1091 -> 0 bytes tests/input/res/drawable-xhdpi/ch_988_logo.png | Bin 1001 -> 0 bytes tests/input/res/drawable-xhdpi/ch_989_logo.png | Bin 1201 -> 0 bytes tests/input/res/drawable-xhdpi/ch_98_logo.png | Bin 902 -> 0 bytes tests/input/res/drawable-xhdpi/ch_990_logo.png | Bin 1027 -> 0 bytes tests/input/res/drawable-xhdpi/ch_991_logo.png | Bin 1334 -> 0 bytes tests/input/res/drawable-xhdpi/ch_992_logo.png | Bin 1055 -> 0 bytes tests/input/res/drawable-xhdpi/ch_993_logo.png | Bin 1252 -> 0 bytes tests/input/res/drawable-xhdpi/ch_994_logo.png | Bin 1168 -> 0 bytes tests/input/res/drawable-xhdpi/ch_995_logo.png | Bin 1305 -> 0 bytes tests/input/res/drawable-xhdpi/ch_996_logo.png | Bin 1047 -> 0 bytes tests/input/res/drawable-xhdpi/ch_997_logo.png | Bin 1104 -> 0 bytes tests/input/res/drawable-xhdpi/ch_998_logo.png | Bin 996 -> 0 bytes tests/input/res/drawable-xhdpi/ch_999_logo.png | Bin 999 -> 0 bytes tests/input/res/drawable-xhdpi/ch_99_logo.png | Bin 900 -> 0 bytes tests/input/res/drawable-xhdpi/ch_9_logo.png | Bin 1031 -> 0 bytes tests/input/res/values/strings.xml | 1 - .../android/tv/testinput/TestTvInputService.java | 15 +- .../tv/testinput/TestTvInputSetupActivity.java | 19 +- .../instrument/TestSetupInstrumentation.java | 2 +- tests/jank/Android.mk | 2 - tests/jank/OWNERS | 2 + .../tv/tests/jank/ChannelZappingJankTest.java | 14 +- .../tv/tests/jank/LiveChannelsTestCase.java | 48 + .../com/android/tv/tests/jank/MenuJankTest.java | 16 +- .../tv/tests/jank/ProgramGuideJankTest.java | 30 +- tests/unit/Android.mk | 4 +- tests/unit/AndroidManifest.xml | 2 +- tests/unit/OWNERS | 2 + .../com/android/tv/BaseMainActivityTestCase.java | 63 +- .../android/tv/CurrentPositionMediatorTest.java | 26 +- tests/unit/src/com/android/tv/FeaturesTest.java | 10 +- .../unit/src/com/android/tv/MainActivityTest.java | 27 +- .../src/com/android/tv/TimeShiftManagerTest.java | 13 +- .../tv/common/TvContentRatingCacheTest.java | 172 - .../android/tv/data/ChannelDataManagerTest.java | 101 +- .../src/com/android/tv/data/ChannelNumberTest.java | 23 +- .../unit/src/com/android/tv/data/ChannelTest.java | 38 +- .../src/com/android/tv/data/GenreItemTest.java | 17 +- .../android/tv/data/ProgramDataManagerTest.java | 55 +- .../unit/src/com/android/tv/data/ProgramTest.java | 23 +- .../android/tv/data/TvInputNewComparatorTest.java | 27 +- .../android/tv/data/WatchedHistoryManagerTest.java | 73 +- .../com/android/tv/dvr/BaseDvrDataManagerTest.java | 33 +- .../com/android/tv/dvr/DvrDataManagerImplTest.java | 17 +- .../android/tv/dvr/DvrDataManagerInMemoryImpl.java | 14 +- .../unit/src/com/android/tv/dvr/DvrDbSyncTest.java | 121 - .../android/tv/dvr/DvrRecordingServiceTest.java | 68 - .../com/android/tv/dvr/DvrScheduleManagerTest.java | 159 +- .../tv/dvr/EpisodicProgramLoadTaskTest.java | 76 - .../com/android/tv/dvr/InputTaskSchedulerTest.java | 221 -- .../src/com/android/tv/dvr/RecordingTaskTest.java | 166 - .../android/tv/dvr/ScheduledProgramReaperTest.java | 114 - .../com/android/tv/dvr/ScheduledRecordingTest.java | 23 +- .../unit/src/com/android/tv/dvr/SchedulerTest.java | 107 - .../tv/dvr/SeriesRecordingSchedulerTest.java | 111 - .../com/android/tv/dvr/SeriesRecordingTest.java | 125 - .../android/tv/dvr/data/SeriesRecordingTest.java | 133 + .../com/android/tv/dvr/provider/DvrDbSyncTest.java | 143 + .../dvr/provider/EpisodicProgramLoadTaskTest.java | 83 + .../tv/dvr/recorder/DvrRecordingServiceTest.java | 183 + .../tv/dvr/recorder/InputTaskSchedulerTest.java | 231 ++ .../android/tv/dvr/recorder/RecordingTaskTest.java | 149 + .../dvr/recorder/ScheduledProgramReaperTest.java | 137 + .../com/android/tv/dvr/recorder/SchedulerTest.java | 125 + .../dvr/recorder/SeriesRecordingSchedulerTest.java | 129 + .../android/tv/dvr/ui/SortedArrayAdapterTest.java | 67 +- .../android/tv/experiments/ExperimentsTest.java | 53 + tests/unit/src/com/android/tv/menu/MenuTest.java | 20 +- .../android/tv/menu/TvOptionsRowAdapterTest.java | 83 +- .../tv/recommendation/ChannelRecordTest.java | 22 +- .../tv/recommendation/EvaluatorTestCase.java | 30 +- .../FavoriteChannelEvaluatorTest.java | 16 +- .../recommendation/RecentChannelEvaluatorTest.java | 15 +- .../tv/recommendation/RecommendationUtils.java | 1 - .../android/tv/recommendation/RecommenderTest.java | 27 +- .../recommendation/RoutineWatchEvaluatorTest.java | 35 +- .../android/tv/search/LocalSearchProviderTest.java | 132 + .../src/com/android/tv/tests/TvActivityTest.java | 23 +- .../src/com/android/tv/util/ImageCacheTest.java | 14 +- .../android/tv/util/MockApplicationSingletons.java | 154 + .../android/tv/util/MultiLongSparseArrayTest.java | 14 +- .../com/android/tv/util/ScaledBitmapInfoTest.java | 10 +- tests/unit/src/com/android/tv/util/TestUtils.java | 27 +- .../android/tv/util/TvInputManagerHelperTest.java | 141 +- .../com/android/tv/util/TvTrackInfoUtilsTest.java | 15 +- .../tv/util/UtilsTest_GetDurationString.java | 36 +- .../tv/util/UtilsTest_GetMultiAudioString.java | 21 +- .../android/tv/util/UtilsTest_IsInGivenDay.java | 11 +- usbtuner-res/animator/setup_before_entry.xml | 32 - usbtuner-res/animator/setup_before_exit.xml | 34 - usbtuner-res/animator/setup_entry.xml | 35 - usbtuner-res/animator/setup_exit.xml | 35 - usbtuner-res/drawable-xhdpi/ic_setup_antenna.png | Bin 1264 -> 0 bytes usbtuner-res/drawable/ut_selector_background.xml | 28 - usbtuner-res/layout/ut_activity_playback.xml | 31 - usbtuner-res/layout/ut_guidance.xml | 48 - usbtuner-res/layout/ut_guidedactions.xml | 41 - usbtuner-res/raw/ut_euro_dvbt_all | 287 ++ usbtuner-res/raw/ut_kr_all | 1 - .../raw/ut_kr_atsc_center_frequencies_8vsb | 1 - usbtuner-res/raw/ut_us_all | 2 - .../raw/ut_us_atsc_center_frequencies_8vsb | 1 - .../ut_us_cable_standard_center_frequencies_qam256 | 1 - usbtuner-res/values-af/strings.xml | 23 +- usbtuner-res/values-am/strings.xml | 23 +- usbtuner-res/values-ar/strings.xml | 23 +- usbtuner-res/values-az-rAZ/strings.xml | 90 + usbtuner-res/values-az/strings.xml | 87 - usbtuner-res/values-bg/strings.xml | 23 +- usbtuner-res/values-bn-rBD/strings.xml | 90 + usbtuner-res/values-bn/strings.xml | 87 - usbtuner-res/values-ca/strings.xml | 23 +- usbtuner-res/values-cs/strings.xml | 23 +- usbtuner-res/values-da/strings.xml | 35 +- usbtuner-res/values-de/strings.xml | 23 +- usbtuner-res/values-el/strings.xml | 23 +- usbtuner-res/values-en-rAU/strings.xml | 23 +- usbtuner-res/values-en-rGB/strings.xml | 23 +- usbtuner-res/values-en-rIN/strings.xml | 23 +- usbtuner-res/values-es-rUS/strings.xml | 23 +- usbtuner-res/values-es/strings.xml | 27 +- usbtuner-res/values-et-rEE/strings.xml | 90 + usbtuner-res/values-et/strings.xml | 87 - usbtuner-res/values-eu-rES/strings.xml | 90 + usbtuner-res/values-eu/strings.xml | 87 - usbtuner-res/values-fa/strings.xml | 23 +- usbtuner-res/values-fi/strings.xml | 23 +- usbtuner-res/values-fr-rCA/strings.xml | 23 +- usbtuner-res/values-fr/strings.xml | 23 +- usbtuner-res/values-gl-rES/strings.xml | 90 + usbtuner-res/values-gl/strings.xml | 87 - usbtuner-res/values-hi/strings.xml | 25 +- usbtuner-res/values-hr/strings.xml | 23 +- usbtuner-res/values-hu/strings.xml | 23 +- usbtuner-res/values-hy-rAM/strings.xml | 90 + usbtuner-res/values-hy/strings.xml | 87 - usbtuner-res/values-in/strings.xml | 25 +- usbtuner-res/values-is-rIS/strings.xml | 90 + usbtuner-res/values-is/strings.xml | 87 - usbtuner-res/values-it/strings.xml | 23 +- usbtuner-res/values-iw/strings.xml | 23 +- usbtuner-res/values-ja/strings.xml | 23 +- usbtuner-res/values-ka-rGE/strings.xml | 90 + usbtuner-res/values-ka/strings.xml | 87 - usbtuner-res/values-kk-rKZ/strings.xml | 90 + usbtuner-res/values-kk/strings.xml | 87 - usbtuner-res/values-km-rKH/strings.xml | 90 + usbtuner-res/values-km/strings.xml | 87 - usbtuner-res/values-kn-rIN/strings.xml | 90 + usbtuner-res/values-kn/strings.xml | 87 - usbtuner-res/values-ko/strings.xml | 25 +- usbtuner-res/values-ky-rKG/strings.xml | 90 + usbtuner-res/values-ky/strings.xml | 87 - usbtuner-res/values-lo-rLA/strings.xml | 90 + usbtuner-res/values-lo/strings.xml | 87 - usbtuner-res/values-lt/strings.xml | 23 +- usbtuner-res/values-lv/strings.xml | 23 +- usbtuner-res/values-mk-rMK/strings.xml | 90 + usbtuner-res/values-mk/strings.xml | 87 - usbtuner-res/values-ml-rIN/strings.xml | 90 + usbtuner-res/values-ml/strings.xml | 87 - usbtuner-res/values-mn-rMN/strings.xml | 90 + usbtuner-res/values-mn/strings.xml | 87 - usbtuner-res/values-mr-rIN/strings.xml | 90 + usbtuner-res/values-mr/strings.xml | 87 - usbtuner-res/values-ms-rMY/strings.xml | 90 + usbtuner-res/values-ms/strings.xml | 87 - usbtuner-res/values-my-rMM/strings.xml | 90 + usbtuner-res/values-my/strings.xml | 87 - usbtuner-res/values-nb/strings.xml | 23 +- usbtuner-res/values-ne-rNP/strings.xml | 90 + usbtuner-res/values-ne/strings.xml | 87 - usbtuner-res/values-nl/strings.xml | 23 +- usbtuner-res/values-pl/strings.xml | 23 +- usbtuner-res/values-pt-rPT/strings.xml | 29 +- usbtuner-res/values-pt/strings.xml | 23 +- usbtuner-res/values-ro/strings.xml | 23 +- usbtuner-res/values-ru/strings.xml | 23 +- usbtuner-res/values-si-rLK/strings.xml | 90 + usbtuner-res/values-si/strings.xml | 87 - usbtuner-res/values-sk/strings.xml | 23 +- usbtuner-res/values-sl/strings.xml | 23 +- usbtuner-res/values-sr/strings.xml | 23 +- usbtuner-res/values-sv/strings.xml | 23 +- usbtuner-res/values-sw/strings.xml | 23 +- usbtuner-res/values-ta-rIN/strings.xml | 90 + usbtuner-res/values-ta/strings.xml | 87 - usbtuner-res/values-te-rIN/strings.xml | 90 + usbtuner-res/values-te/strings.xml | 87 - usbtuner-res/values-th/strings.xml | 23 +- usbtuner-res/values-tl/strings.xml | 23 +- usbtuner-res/values-tr/strings.xml | 23 +- usbtuner-res/values-uk/strings.xml | 23 +- usbtuner-res/values-ur-rPK/strings.xml | 90 + usbtuner-res/values-ur/strings.xml | 87 - usbtuner-res/values-uz-rUZ/strings.xml | 90 + usbtuner-res/values-uz/strings.xml | 87 - usbtuner-res/values-vi/strings.xml | 23 +- usbtuner-res/values-zh-rCN/strings.xml | 23 +- usbtuner-res/values-zh-rHK/strings.xml | 23 +- usbtuner-res/values-zh-rTW/strings.xml | 23 +- usbtuner-res/values-zu/strings.xml | 23 +- usbtuner-res/values/colors.xml | 6 - usbtuner-res/values/dimens.xml | 34 - usbtuner-res/values/integers.xml | 5 - usbtuner-res/values/strings.xml | 72 +- usbtuner-res/values/styles.xml | 120 - version.mk | 6 +- 3053 files changed, 60862 insertions(+), 43870 deletions(-) delete mode 100644 assets/licenses.html create mode 100644 assets/whitelist.policy create mode 100644 common/OWNERS create mode 100644 common/res/values-az-rAZ/strings.xml delete mode 100644 common/res/values-az/strings.xml create mode 100644 common/res/values-bn-rBD/strings.xml delete mode 100644 common/res/values-bn/strings.xml create mode 100644 common/res/values-et-rEE/strings.xml delete mode 100644 common/res/values-et/strings.xml create mode 100644 common/res/values-eu-rES/strings.xml delete mode 100644 common/res/values-eu/strings.xml create mode 100644 common/res/values-gl-rES/strings.xml delete mode 100644 common/res/values-gl/strings.xml create mode 100644 common/res/values-gu-rIN/strings.xml create mode 100644 common/res/values-hy-rAM/strings.xml delete mode 100644 common/res/values-hy/strings.xml create mode 100644 common/res/values-is-rIS/strings.xml delete mode 100644 common/res/values-is/strings.xml create mode 100644 common/res/values-ka-rGE/strings.xml delete mode 100644 common/res/values-ka/strings.xml create mode 100644 common/res/values-kk-rKZ/strings.xml delete mode 100644 common/res/values-kk/strings.xml create mode 100644 common/res/values-km-rKH/strings.xml delete mode 100644 common/res/values-km/strings.xml create mode 100644 common/res/values-kn-rIN/strings.xml delete mode 100644 common/res/values-kn/strings.xml create mode 100644 common/res/values-ky-rKG/strings.xml delete mode 100644 common/res/values-ky/strings.xml create mode 100644 common/res/values-lo-rLA/strings.xml delete mode 100644 common/res/values-lo/strings.xml create mode 100644 common/res/values-mk-rMK/strings.xml delete mode 100644 common/res/values-mk/strings.xml create mode 100644 common/res/values-ml-rIN/strings.xml delete mode 100644 common/res/values-ml/strings.xml create mode 100644 common/res/values-mn-rMN/strings.xml delete mode 100644 common/res/values-mn/strings.xml create mode 100644 common/res/values-mr-rIN/strings.xml delete mode 100644 common/res/values-mr/strings.xml create mode 100644 common/res/values-ms-rMY/strings.xml delete mode 100644 common/res/values-ms/strings.xml create mode 100644 common/res/values-my-rMM/strings.xml delete mode 100644 common/res/values-my/strings.xml create mode 100644 common/res/values-ne-rNP/strings.xml delete mode 100644 common/res/values-ne/strings.xml create mode 100644 common/res/values-pa-rIN/strings.xml create mode 100644 common/res/values-si-rLK/strings.xml delete mode 100644 common/res/values-si/strings.xml create mode 100644 common/res/values-sq-rAL/strings.xml create mode 100644 common/res/values-ta-rIN/strings.xml delete mode 100644 common/res/values-ta/strings.xml create mode 100644 common/res/values-te-rIN/strings.xml delete mode 100644 common/res/values-te/strings.xml create mode 100644 common/res/values-ur-rPK/strings.xml delete mode 100644 common/res/values-ur/strings.xml create mode 100644 common/res/values-uz-rUZ/strings.xml delete mode 100644 common/res/values-uz/strings.xml create mode 100644 jni/minijail/Android.mk create mode 100644 jni/minijail/minijail.cpp create mode 100644 jni/minijail/minijail.h create mode 100644 libs/exoplayer_v2.jar create mode 100644 libs/exoplayer_v2_ext_ffmpeg.jar delete mode 100644 res/drawable-xhdpi/dvr_default_program_art.png mode change 100755 => 100644 res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png delete mode 100644 res/drawable-xhdpi/ic_error_recording.png delete mode 100644 res/drawable-xhdpi/ic_fresh.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_input.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_layout1.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_layout2.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_layout3.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_layout4.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_layout5.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_size.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_swap.png delete mode 100644 res/drawable-xhdpi/ic_pip_option_swap_audio.png delete mode 100644 res/drawable-xhdpi/ic_recorded_program.png delete mode 100644 res/drawable-xhdpi/ic_related_actor.png delete mode 100644 res/drawable-xhdpi/ic_related_search.png delete mode 100644 res/drawable-xhdpi/ic_setup_antenna.png delete mode 100644 res/drawable-xhdpi/ic_tvoption_pip_off.png delete mode 100644 res/drawable-xhdpi/tv_3a_00.png delete mode 100644 res/drawable-xhdpi/tv_error.png delete mode 100644 res/drawable-xhdpi/tv_usb_antenna.png create mode 100644 res/drawable/playback_progress_bar.xml delete mode 100644 res/drawable/setup_item_background.xml delete mode 100644 res/layout/dvr_play.xml create mode 100644 res/layout/guided_action_editable.xml create mode 100644 res/layout/list_item_dvr_history.xml create mode 100755 res/raw/third_party_license_metadata create mode 100755 res/raw/third_party_licenses create mode 100644 res/transition/dvr_details_shared_element_enter_transition.xml create mode 100644 res/transition/dvr_details_shared_element_return_transition.xml create mode 100644 res/values-az-rAZ/arrays.xml create mode 100644 res/values-az-rAZ/rating_system_strings.xml create mode 100644 res/values-az-rAZ/strings.xml delete mode 100644 res/values-az/arrays.xml delete mode 100644 res/values-az/rating_system_strings.xml delete mode 100644 res/values-az/strings.xml create mode 100644 res/values-bn-rBD-v23/strings.xml create mode 100644 res/values-bn-rBD/arrays.xml create mode 100644 res/values-bn-rBD/rating_system_strings.xml create mode 100644 res/values-bn-rBD/strings.xml delete mode 100644 res/values-bn-v23/strings.xml delete mode 100644 res/values-bn/arrays.xml delete mode 100644 res/values-bn/rating_system_strings.xml delete mode 100644 res/values-bn/strings.xml create mode 100644 res/values-et-rEE-v23/strings.xml create mode 100644 res/values-et-rEE/arrays.xml create mode 100644 res/values-et-rEE/rating_system_strings.xml create mode 100644 res/values-et-rEE/strings.xml delete mode 100644 res/values-et-v23/strings.xml delete mode 100644 res/values-et/arrays.xml delete mode 100644 res/values-et/rating_system_strings.xml delete mode 100644 res/values-et/strings.xml create mode 100644 res/values-eu-rES-v23/strings.xml create mode 100644 res/values-eu-rES/arrays.xml create mode 100644 res/values-eu-rES/rating_system_strings.xml create mode 100644 res/values-eu-rES/strings.xml delete mode 100644 res/values-eu-v23/strings.xml delete mode 100644 res/values-eu/arrays.xml delete mode 100644 res/values-eu/rating_system_strings.xml delete mode 100644 res/values-eu/strings.xml create mode 100644 res/values-gl-rES-v23/strings.xml create mode 100644 res/values-gl-rES/arrays.xml create mode 100644 res/values-gl-rES/rating_system_strings.xml create mode 100644 res/values-gl-rES/strings.xml delete mode 100644 res/values-gl-v23/strings.xml delete mode 100644 res/values-gl/arrays.xml delete mode 100644 res/values-gl/rating_system_strings.xml delete mode 100644 res/values-gl/strings.xml create mode 100644 res/values-gu-rIN/strings.xml create mode 100644 res/values-hy-rAM-v23/strings.xml create mode 100644 res/values-hy-rAM/arrays.xml create mode 100644 res/values-hy-rAM/rating_system_strings.xml create mode 100644 res/values-hy-rAM/strings.xml delete mode 100644 res/values-hy-v23/strings.xml delete mode 100644 res/values-hy/arrays.xml delete mode 100644 res/values-hy/rating_system_strings.xml delete mode 100644 res/values-hy/strings.xml create mode 100644 res/values-is-rIS-v23/strings.xml create mode 100644 res/values-is-rIS/arrays.xml create mode 100644 res/values-is-rIS/rating_system_strings.xml create mode 100644 res/values-is-rIS/strings.xml delete mode 100644 res/values-is-v23/strings.xml delete mode 100644 res/values-is/arrays.xml delete mode 100644 res/values-is/rating_system_strings.xml delete mode 100644 res/values-is/strings.xml create mode 100644 res/values-ka-rGE-v23/strings.xml create mode 100644 res/values-ka-rGE/arrays.xml create mode 100644 res/values-ka-rGE/rating_system_strings.xml create mode 100644 res/values-ka-rGE/strings.xml delete mode 100644 res/values-ka-v23/strings.xml delete mode 100644 res/values-ka/arrays.xml delete mode 100644 res/values-ka/rating_system_strings.xml delete mode 100644 res/values-ka/strings.xml create mode 100644 res/values-kk-rKZ-v23/strings.xml create mode 100644 res/values-kk-rKZ/arrays.xml create mode 100644 res/values-kk-rKZ/rating_system_strings.xml create mode 100644 res/values-kk-rKZ/strings.xml delete mode 100644 res/values-kk-v23/strings.xml delete mode 100644 res/values-kk/arrays.xml delete mode 100644 res/values-kk/rating_system_strings.xml delete mode 100644 res/values-kk/strings.xml create mode 100644 res/values-km-rKH-v23/strings.xml create mode 100644 res/values-km-rKH/arrays.xml create mode 100644 res/values-km-rKH/rating_system_strings.xml create mode 100644 res/values-km-rKH/strings.xml delete mode 100644 res/values-km-v23/strings.xml delete mode 100644 res/values-km/arrays.xml delete mode 100644 res/values-km/rating_system_strings.xml delete mode 100644 res/values-km/strings.xml create mode 100644 res/values-kn-rIN-v23/strings.xml create mode 100644 res/values-kn-rIN/arrays.xml create mode 100644 res/values-kn-rIN/rating_system_strings.xml create mode 100644 res/values-kn-rIN/strings.xml delete mode 100644 res/values-kn-v23/strings.xml delete mode 100644 res/values-kn/arrays.xml delete mode 100644 res/values-kn/rating_system_strings.xml delete mode 100644 res/values-kn/strings.xml create mode 100644 res/values-ky-rKG-v23/strings.xml create mode 100644 res/values-ky-rKG/arrays.xml create mode 100644 res/values-ky-rKG/rating_system_strings.xml create mode 100644 res/values-ky-rKG/strings.xml delete mode 100644 res/values-ky-v23/strings.xml delete mode 100644 res/values-ky/arrays.xml delete mode 100644 res/values-ky/rating_system_strings.xml delete mode 100644 res/values-ky/strings.xml create mode 100644 res/values-lo-rLA-v23/strings.xml create mode 100644 res/values-lo-rLA/arrays.xml create mode 100644 res/values-lo-rLA/rating_system_strings.xml create mode 100644 res/values-lo-rLA/strings.xml delete mode 100644 res/values-lo-v23/strings.xml delete mode 100644 res/values-lo/arrays.xml delete mode 100644 res/values-lo/rating_system_strings.xml delete mode 100644 res/values-lo/strings.xml create mode 100644 res/values-mk-rMK-v23/strings.xml create mode 100644 res/values-mk-rMK/arrays.xml create mode 100644 res/values-mk-rMK/rating_system_strings.xml create mode 100644 res/values-mk-rMK/strings.xml delete mode 100644 res/values-mk-v23/strings.xml delete mode 100644 res/values-mk/arrays.xml delete mode 100644 res/values-mk/rating_system_strings.xml delete mode 100644 res/values-mk/strings.xml create mode 100644 res/values-ml-rIN-v23/strings.xml create mode 100644 res/values-ml-rIN/arrays.xml create mode 100644 res/values-ml-rIN/rating_system_strings.xml create mode 100644 res/values-ml-rIN/strings.xml delete mode 100644 res/values-ml-v23/strings.xml delete mode 100644 res/values-ml/arrays.xml delete mode 100644 res/values-ml/rating_system_strings.xml delete mode 100644 res/values-ml/strings.xml create mode 100644 res/values-mn-rMN-v23/strings.xml create mode 100644 res/values-mn-rMN/arrays.xml create mode 100644 res/values-mn-rMN/rating_system_strings.xml create mode 100644 res/values-mn-rMN/strings.xml delete mode 100644 res/values-mn-v23/strings.xml delete mode 100644 res/values-mn/arrays.xml delete mode 100644 res/values-mn/rating_system_strings.xml delete mode 100644 res/values-mn/strings.xml create mode 100644 res/values-mr-rIN-v23/strings.xml create mode 100644 res/values-mr-rIN/arrays.xml create mode 100644 res/values-mr-rIN/rating_system_strings.xml create mode 100644 res/values-mr-rIN/strings.xml delete mode 100644 res/values-mr-v23/strings.xml delete mode 100644 res/values-mr/arrays.xml delete mode 100644 res/values-mr/rating_system_strings.xml delete mode 100644 res/values-mr/strings.xml create mode 100644 res/values-ms-rMY-v23/strings.xml create mode 100644 res/values-ms-rMY/arrays.xml create mode 100644 res/values-ms-rMY/rating_system_strings.xml create mode 100644 res/values-ms-rMY/strings.xml delete mode 100644 res/values-ms-v23/strings.xml delete mode 100644 res/values-ms/arrays.xml delete mode 100644 res/values-ms/rating_system_strings.xml delete mode 100644 res/values-ms/strings.xml create mode 100644 res/values-my-rMM-v23/strings.xml create mode 100644 res/values-my-rMM/arrays.xml create mode 100644 res/values-my-rMM/rating_system_strings.xml create mode 100644 res/values-my-rMM/strings.xml delete mode 100644 res/values-my-v23/strings.xml delete mode 100644 res/values-my/arrays.xml delete mode 100644 res/values-my/rating_system_strings.xml delete mode 100644 res/values-my/strings.xml create mode 100644 res/values-ne-rNP-v23/strings.xml create mode 100644 res/values-ne-rNP/arrays.xml create mode 100644 res/values-ne-rNP/rating_system_strings.xml create mode 100644 res/values-ne-rNP/strings.xml delete mode 100644 res/values-ne-v23/strings.xml delete mode 100644 res/values-ne/arrays.xml delete mode 100644 res/values-ne/rating_system_strings.xml delete mode 100644 res/values-ne/strings.xml create mode 100644 res/values-pa-rIN/strings.xml create mode 100644 res/values-si-rLK-v23/strings.xml create mode 100644 res/values-si-rLK/arrays.xml create mode 100644 res/values-si-rLK/rating_system_strings.xml create mode 100644 res/values-si-rLK/strings.xml delete mode 100644 res/values-si-v23/strings.xml delete mode 100644 res/values-si/arrays.xml delete mode 100644 res/values-si/rating_system_strings.xml delete mode 100644 res/values-si/strings.xml create mode 100644 res/values-sq-rAL/strings.xml create mode 100644 res/values-ta-rIN-v23/strings.xml create mode 100644 res/values-ta-rIN/arrays.xml create mode 100644 res/values-ta-rIN/rating_system_strings.xml create mode 100644 res/values-ta-rIN/strings.xml delete mode 100644 res/values-ta-v23/strings.xml delete mode 100644 res/values-ta/arrays.xml delete mode 100644 res/values-ta/rating_system_strings.xml delete mode 100644 res/values-ta/strings.xml create mode 100644 res/values-te-rIN-v23/strings.xml create mode 100644 res/values-te-rIN/arrays.xml create mode 100644 res/values-te-rIN/rating_system_strings.xml create mode 100644 res/values-te-rIN/strings.xml delete mode 100644 res/values-te-v23/strings.xml delete mode 100644 res/values-te/arrays.xml delete mode 100644 res/values-te/rating_system_strings.xml delete mode 100644 res/values-te/strings.xml create mode 100644 res/values-ur-rPK-v23/strings.xml create mode 100644 res/values-ur-rPK/arrays.xml create mode 100644 res/values-ur-rPK/rating_system_strings.xml create mode 100644 res/values-ur-rPK/strings.xml delete mode 100644 res/values-ur-v23/strings.xml delete mode 100644 res/values-ur/arrays.xml delete mode 100644 res/values-ur/rating_system_strings.xml delete mode 100644 res/values-ur/strings.xml create mode 100644 res/values-uz-rUZ-v23/strings.xml create mode 100644 res/values-uz-rUZ/arrays.xml create mode 100644 res/values-uz-rUZ/rating_system_strings.xml create mode 100644 res/values-uz-rUZ/strings.xml delete mode 100644 res/values-uz-v23/strings.xml delete mode 100644 res/values-uz/arrays.xml delete mode 100644 res/values-uz/rating_system_strings.xml delete mode 100644 res/values-uz/strings.xml delete mode 100644 res/values-v23/strings.xml delete mode 100644 res/values/attr.xml create mode 100644 res/values/attrs.xml delete mode 100755 res/values/google-services.xml create mode 100644 src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java create mode 100644 src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java create mode 100644 src/com/android/tv/AudioManagerHelper.java create mode 100644 src/com/android/tv/MediaSessionWrapper.java delete mode 100644 src/com/android/tv/analytics/DurationTimer.java create mode 100644 src/com/android/tv/config/RemoteConfigUtils.java create mode 100644 src/com/android/tv/data/PreviewDataManager.java create mode 100644 src/com/android/tv/data/PreviewProgramContent.java create mode 100644 src/com/android/tv/data/epg/EpgFetchHelper.java create mode 100644 src/com/android/tv/dialog/DvrHistoryDialogFragment.java create mode 100644 src/com/android/tv/dialog/HalfSizedDialogFragment.java delete mode 100644 src/com/android/tv/dvr/ConflictChecker.java delete mode 100644 src/com/android/tv/dvr/DvrDbSync.java delete mode 100644 src/com/android/tv/dvr/DvrPlaybackActivity.java delete mode 100644 src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java delete mode 100644 src/com/android/tv/dvr/DvrPlayer.java delete mode 100644 src/com/android/tv/dvr/DvrRecordingService.java delete mode 100644 src/com/android/tv/dvr/DvrStartRecordingReceiver.java delete mode 100644 src/com/android/tv/dvr/DvrUiHelper.java delete mode 100644 src/com/android/tv/dvr/EpisodicProgramLoadTask.java delete mode 100644 src/com/android/tv/dvr/IdGenerator.java delete mode 100644 src/com/android/tv/dvr/InputTaskScheduler.java delete mode 100644 src/com/android/tv/dvr/RecordedProgram.java delete mode 100644 src/com/android/tv/dvr/RecordingTask.java delete mode 100644 src/com/android/tv/dvr/ScheduledProgramReaper.java delete mode 100644 src/com/android/tv/dvr/ScheduledRecording.java delete mode 100644 src/com/android/tv/dvr/Scheduler.java delete mode 100644 src/com/android/tv/dvr/SeriesInfo.java delete mode 100644 src/com/android/tv/dvr/SeriesRecording.java delete mode 100644 src/com/android/tv/dvr/SeriesRecordingScheduler.java create mode 100644 src/com/android/tv/dvr/data/IdGenerator.java create mode 100644 src/com/android/tv/dvr/data/RecordedProgram.java create mode 100644 src/com/android/tv/dvr/data/ScheduledRecording.java create mode 100644 src/com/android/tv/dvr/data/SeasonEpisodeNumber.java create mode 100644 src/com/android/tv/dvr/data/SeriesInfo.java create mode 100644 src/com/android/tv/dvr/data/SeriesRecording.java create mode 100644 src/com/android/tv/dvr/provider/DvrDbSync.java create mode 100644 src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java create mode 100644 src/com/android/tv/dvr/recorder/ConflictChecker.java create mode 100644 src/com/android/tv/dvr/recorder/DvrRecordingService.java create mode 100644 src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java create mode 100644 src/com/android/tv/dvr/recorder/InputTaskScheduler.java create mode 100644 src/com/android/tv/dvr/recorder/RecordingScheduler.java create mode 100644 src/com/android/tv/dvr/recorder/RecordingTask.java create mode 100644 src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java create mode 100644 src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java delete mode 100644 src/com/android/tv/dvr/ui/ActionPresenterSelector.java create mode 100644 src/com/android/tv/dvr/ui/BigArguments.java create mode 100644 src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java delete mode 100644 src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/DetailsContent.java delete mode 100644 src/com/android/tv/dvr/ui/DetailsContentPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java delete mode 100644 src/com/android/tv/dvr/ui/DvrActivity.java delete mode 100644 src/com/android/tv/dvr/ui/DvrBrowseFragment.java delete mode 100644 src/com/android/tv/dvr/ui/DvrDetailsActivity.java delete mode 100644 src/com/android/tv/dvr/ui/DvrDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java delete mode 100644 src/com/android/tv/dvr/ui/DvrItemPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java delete mode 100644 src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java create mode 100644 src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/DvrSchedulesActivity.java create mode 100644 src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java create mode 100644 src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java create mode 100644 src/com/android/tv/dvr/ui/DvrUiHelper.java create mode 100644 src/com/android/tv/dvr/ui/FadeBackground.java delete mode 100644 src/com/android/tv/dvr/ui/FullScheduleCardHolder.java delete mode 100644 src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java delete mode 100644 src/com/android/tv/dvr/ui/PrioritySettingsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/RecordedProgramPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/RecordingCardView.java delete mode 100644 src/com/android/tv/dvr/ui/RecordingDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/SeriesDeletionFragment.java delete mode 100644 src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java delete mode 100644 src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java delete mode 100644 src/com/android/tv/dvr/ui/SeriesSettingsFragment.java create mode 100644 src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java create mode 100644 src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/DetailsContent.java create mode 100644 src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java create mode 100644 src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/RecordingCardView.java create mode 100644 src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java create mode 100644 src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java create mode 100644 src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java create mode 100644 src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java create mode 100644 src/com/android/tv/dvr/ui/playback/DvrPlayer.java create mode 100644 src/com/android/tv/license/License.java create mode 100644 src/com/android/tv/license/LicenseDialogFragment.java create mode 100644 src/com/android/tv/license/LicenseSideFragment.java create mode 100644 src/com/android/tv/license/Licenses.java create mode 100644 src/com/android/tv/menu/ChannelsRowItem.java delete mode 100644 src/com/android/tv/menu/PipOptionsRowAdapter.java create mode 100644 src/com/android/tv/menu/PlaybackProgressBar.java create mode 100644 src/com/android/tv/perf/EventNames.java create mode 100644 src/com/android/tv/perf/PerformanceMonitor.java create mode 100644 src/com/android/tv/perf/StubPerformanceMonitor.java create mode 100644 src/com/android/tv/perf/TimerEvent.java create mode 100644 src/com/android/tv/recommendation/ChannelPreviewUpdater.java create mode 100644 src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java create mode 100644 src/com/android/tv/tuner/DvbTunerHal.java delete mode 100644 src/com/android/tv/tuner/UsbTunerHal.java create mode 100644 src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ac3/AudioClock.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioClock.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java create mode 100644 src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java create mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java create mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java create mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl create mode 100644 src/com/android/tv/tuner/setup/PostalCodeFragment.java create mode 100644 src/com/android/tv/tuner/util/PostalCodeUtils.java delete mode 100644 src/com/android/tv/tuner/util/StringUtils.java delete mode 100644 src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java create mode 100644 src/com/android/tv/ui/sidepanel/SimpleActionItem.java delete mode 100644 src/com/android/tv/ui/sidepanel/SimpleItem.java create mode 100644 src/com/android/tv/util/Debug.java create mode 100644 src/com/android/tv/util/DurationTimer.java create mode 100644 src/com/android/tv/util/NetworkTrafficTags.java create mode 100644 src/com/android/tv/util/Partner.java delete mode 100644 src/com/android/tv/util/PipInputManager.java delete mode 100644 src/com/android/tv/util/SearchManagerHelper.java create mode 100644 src/com/android/tv/util/StringUtils.java delete mode 100644 src/com/android/tv/util/TvProviderUriMatcher.java create mode 100644 src/com/android/tv/util/TvUriMatcher.java create mode 100644 src/com/android/tv/util/ViewCache.java create mode 100644 tests/OWNERS create mode 100644 tests/common/OWNERS create mode 100644 tests/common/res/drawable-xhdpi/ch_1000_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_100_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_101_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_102_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_103_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_104_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_105_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_106_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_107_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_108_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_109_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_10_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_110_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_111_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_112_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_113_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_114_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_115_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_116_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_117_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_118_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_119_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_11_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_120_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_121_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_122_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_123_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_124_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_125_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_126_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_127_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_128_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_129_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_12_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_130_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_131_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_132_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_133_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_134_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_135_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_136_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_137_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_138_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_139_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_13_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_140_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_141_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_142_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_143_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_144_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_145_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_146_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_147_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_148_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_149_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_14_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_150_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_151_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_152_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_153_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_154_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_155_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_156_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_157_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_158_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_159_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_15_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_160_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_161_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_162_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_163_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_164_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_165_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_166_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_167_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_168_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_169_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_16_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_170_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_171_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_172_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_173_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_174_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_175_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_176_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_177_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_178_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_179_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_17_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_180_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_181_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_182_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_183_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_184_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_185_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_186_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_187_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_188_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_189_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_18_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_190_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_191_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_192_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_193_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_194_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_195_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_196_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_197_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_198_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_199_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_19_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_1_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_200_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_201_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_202_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_203_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_204_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_205_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_206_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_207_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_208_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_209_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_20_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_210_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_211_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_212_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_213_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_214_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_215_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_216_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_217_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_218_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_219_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_21_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_220_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_221_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_222_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_223_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_224_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_225_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_226_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_227_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_228_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_229_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_22_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_230_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_231_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_232_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_233_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_234_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_235_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_236_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_237_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_238_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_239_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_23_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_240_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_241_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_242_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_243_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_244_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_245_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_246_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_247_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_248_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_249_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_24_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_250_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_251_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_252_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_253_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_254_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_255_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_256_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_257_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_258_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_259_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_25_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_260_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_261_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_262_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_263_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_264_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_265_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_266_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_267_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_268_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_269_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_26_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_270_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_271_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_272_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_273_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_274_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_275_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_276_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_277_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_278_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_279_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_27_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_280_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_281_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_282_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_283_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_284_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_285_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_286_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_287_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_288_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_289_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_28_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_290_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_291_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_292_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_293_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_294_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_295_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_296_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_297_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_298_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_299_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_29_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_2_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_300_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_301_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_302_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_303_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_304_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_305_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_306_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_307_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_308_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_309_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_30_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_310_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_311_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_312_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_313_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_314_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_315_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_316_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_317_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_318_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_319_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_31_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_320_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_321_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_322_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_323_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_324_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_325_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_326_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_327_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_328_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_329_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_32_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_330_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_331_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_332_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_333_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_334_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_335_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_336_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_337_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_338_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_339_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_33_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_340_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_341_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_342_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_343_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_344_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_345_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_346_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_347_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_348_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_349_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_34_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_350_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_351_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_352_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_353_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_354_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_355_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_356_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_357_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_358_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_359_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_35_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_360_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_361_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_362_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_363_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_364_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_365_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_366_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_367_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_368_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_369_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_36_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_370_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_371_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_372_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_373_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_374_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_375_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_376_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_377_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_378_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_379_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_37_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_380_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_381_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_382_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_383_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_384_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_385_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_386_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_387_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_388_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_389_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_38_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_390_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_391_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_392_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_393_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_394_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_395_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_396_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_397_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_398_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_399_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_39_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_3_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_400_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_401_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_402_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_403_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_404_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_405_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_406_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_407_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_408_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_409_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_40_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_410_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_411_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_412_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_413_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_414_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_415_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_416_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_417_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_418_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_419_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_41_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_420_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_421_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_422_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_423_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_424_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_425_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_426_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_427_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_428_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_429_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_42_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_430_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_431_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_432_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_433_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_434_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_435_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_436_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_437_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_438_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_439_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_43_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_440_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_441_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_442_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_443_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_444_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_445_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_446_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_447_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_448_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_449_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_44_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_450_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_451_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_452_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_453_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_454_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_455_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_456_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_457_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_458_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_459_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_45_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_460_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_461_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_462_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_463_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_464_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_465_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_466_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_467_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_468_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_469_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_46_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_470_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_471_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_472_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_473_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_474_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_475_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_476_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_477_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_478_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_479_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_47_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_480_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_481_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_482_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_483_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_484_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_485_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_486_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_487_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_488_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_489_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_48_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_490_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_491_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_492_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_493_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_494_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_495_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_496_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_497_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_498_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_499_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_49_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_4_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_500_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_501_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_502_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_503_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_504_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_505_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_506_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_507_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_508_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_509_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_50_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_510_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_511_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_512_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_513_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_514_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_515_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_516_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_517_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_518_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_519_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_51_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_520_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_521_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_522_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_523_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_524_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_525_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_526_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_527_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_528_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_529_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_52_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_530_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_531_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_532_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_533_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_534_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_535_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_536_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_537_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_538_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_539_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_53_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_540_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_541_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_542_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_543_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_544_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_545_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_546_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_547_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_548_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_549_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_54_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_550_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_551_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_552_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_553_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_554_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_555_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_556_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_557_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_558_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_559_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_55_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_560_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_561_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_562_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_563_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_564_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_565_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_566_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_567_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_568_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_569_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_56_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_570_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_571_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_572_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_573_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_574_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_575_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_576_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_577_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_578_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_579_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_57_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_580_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_581_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_582_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_583_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_584_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_585_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_586_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_587_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_588_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_589_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_58_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_590_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_591_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_592_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_593_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_594_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_595_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_596_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_597_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_598_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_599_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_59_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_5_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_600_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_601_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_602_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_603_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_604_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_605_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_606_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_607_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_608_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_609_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_60_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_610_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_611_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_612_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_613_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_614_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_615_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_616_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_617_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_618_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_619_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_61_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_620_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_621_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_622_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_623_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_624_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_625_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_626_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_627_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_628_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_629_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_62_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_630_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_631_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_632_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_633_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_634_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_635_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_636_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_637_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_638_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_639_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_63_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_640_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_641_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_642_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_643_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_644_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_645_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_646_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_647_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_648_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_649_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_64_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_650_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_651_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_652_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_653_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_654_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_655_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_656_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_657_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_658_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_659_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_65_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_660_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_661_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_662_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_663_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_664_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_665_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_666_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_667_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_668_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_669_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_66_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_670_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_671_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_672_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_673_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_674_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_675_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_676_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_677_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_678_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_679_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_67_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_680_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_681_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_682_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_683_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_684_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_685_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_686_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_687_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_688_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_689_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_68_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_690_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_691_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_692_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_693_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_694_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_695_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_696_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_697_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_698_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_699_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_69_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_6_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_700_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_701_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_702_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_703_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_704_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_705_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_706_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_707_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_708_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_709_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_70_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_710_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_711_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_712_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_713_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_714_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_715_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_716_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_717_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_718_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_719_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_71_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_720_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_721_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_722_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_723_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_724_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_725_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_726_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_727_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_728_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_729_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_72_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_730_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_731_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_732_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_733_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_734_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_735_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_736_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_737_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_738_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_739_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_73_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_740_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_741_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_742_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_743_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_744_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_745_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_746_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_747_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_748_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_749_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_74_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_750_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_751_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_752_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_753_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_754_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_755_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_756_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_757_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_758_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_759_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_75_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_760_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_761_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_762_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_763_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_764_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_765_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_766_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_767_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_768_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_769_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_76_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_770_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_771_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_772_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_773_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_774_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_775_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_776_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_777_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_778_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_779_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_77_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_780_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_781_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_782_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_783_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_784_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_785_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_786_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_787_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_788_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_789_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_78_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_790_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_791_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_792_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_793_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_794_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_795_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_796_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_797_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_798_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_799_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_79_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_7_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_800_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_801_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_802_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_803_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_804_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_805_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_806_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_807_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_808_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_809_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_80_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_810_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_811_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_812_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_813_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_814_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_815_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_816_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_817_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_818_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_819_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_81_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_820_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_821_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_822_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_823_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_824_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_825_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_826_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_827_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_828_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_829_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_82_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_830_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_831_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_832_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_833_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_834_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_835_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_836_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_837_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_838_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_839_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_83_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_840_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_841_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_842_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_843_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_844_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_845_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_846_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_847_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_848_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_849_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_84_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_850_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_851_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_852_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_853_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_854_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_855_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_856_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_857_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_858_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_859_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_85_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_860_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_861_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_862_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_863_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_864_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_865_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_866_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_867_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_868_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_869_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_86_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_870_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_871_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_872_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_873_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_874_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_875_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_876_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_877_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_878_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_879_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_87_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_880_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_881_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_882_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_883_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_884_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_885_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_886_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_887_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_888_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_889_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_88_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_890_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_891_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_892_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_893_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_894_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_895_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_896_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_897_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_898_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_899_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_89_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_8_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_900_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_901_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_902_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_903_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_904_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_905_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_906_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_907_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_908_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_909_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_90_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_910_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_911_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_912_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_913_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_914_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_915_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_916_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_917_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_918_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_919_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_91_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_920_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_921_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_922_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_923_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_924_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_925_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_926_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_927_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_928_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_929_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_92_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_930_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_931_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_932_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_933_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_934_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_935_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_936_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_937_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_938_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_939_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_93_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_940_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_941_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_942_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_943_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_944_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_945_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_946_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_947_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_948_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_949_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_94_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_950_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_951_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_952_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_953_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_954_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_955_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_956_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_957_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_958_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_959_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_95_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_960_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_961_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_962_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_963_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_964_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_965_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_966_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_967_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_968_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_969_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_96_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_970_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_971_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_972_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_973_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_974_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_975_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_976_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_977_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_978_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_979_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_97_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_980_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_981_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_982_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_983_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_984_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_985_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_986_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_987_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_988_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_989_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_98_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_990_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_991_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_992_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_993_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_994_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_995_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_996_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_997_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_998_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_999_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_99_logo.png create mode 100644 tests/common/res/drawable-xhdpi/ch_9_logo.png create mode 100644 tests/func/OWNERS delete mode 100644 tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java create mode 100644 tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java create mode 100644 tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java create mode 100644 tests/input/OWNERS delete mode 100644 tests/input/res/drawable-xhdpi/ch_1000_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_100_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_101_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_102_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_103_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_104_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_105_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_106_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_107_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_108_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_109_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_10_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_110_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_111_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_112_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_113_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_114_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_115_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_116_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_117_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_118_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_119_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_11_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_120_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_121_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_122_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_123_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_124_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_125_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_126_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_127_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_128_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_129_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_12_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_130_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_131_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_132_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_133_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_134_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_135_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_136_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_137_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_138_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_139_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_13_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_140_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_141_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_142_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_143_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_144_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_145_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_146_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_147_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_148_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_149_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_14_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_150_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_151_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_152_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_153_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_154_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_155_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_156_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_157_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_158_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_159_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_15_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_160_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_161_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_162_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_163_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_164_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_165_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_166_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_167_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_168_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_169_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_16_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_170_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_171_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_172_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_173_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_174_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_175_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_176_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_177_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_178_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_179_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_17_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_180_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_181_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_182_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_183_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_184_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_185_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_186_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_187_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_188_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_189_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_18_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_190_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_191_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_192_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_193_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_194_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_195_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_196_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_197_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_198_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_199_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_19_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_1_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_200_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_201_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_202_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_203_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_204_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_205_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_206_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_207_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_208_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_209_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_20_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_210_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_211_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_212_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_213_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_214_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_215_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_216_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_217_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_218_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_219_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_21_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_220_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_221_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_222_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_223_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_224_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_225_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_226_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_227_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_228_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_229_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_22_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_230_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_231_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_232_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_233_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_234_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_235_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_236_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_237_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_238_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_239_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_23_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_240_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_241_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_242_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_243_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_244_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_245_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_246_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_247_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_248_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_249_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_24_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_250_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_251_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_252_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_253_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_254_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_255_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_256_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_257_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_258_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_259_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_25_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_260_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_261_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_262_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_263_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_264_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_265_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_266_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_267_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_268_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_269_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_26_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_270_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_271_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_272_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_273_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_274_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_275_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_276_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_277_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_278_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_279_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_27_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_280_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_281_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_282_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_283_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_284_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_285_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_286_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_287_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_288_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_289_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_28_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_290_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_291_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_292_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_293_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_294_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_295_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_296_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_297_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_298_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_299_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_29_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_2_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_300_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_301_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_302_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_303_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_304_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_305_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_306_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_307_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_308_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_309_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_30_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_310_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_311_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_312_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_313_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_314_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_315_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_316_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_317_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_318_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_319_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_31_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_320_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_321_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_322_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_323_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_324_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_325_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_326_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_327_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_328_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_329_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_32_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_330_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_331_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_332_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_333_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_334_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_335_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_336_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_337_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_338_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_339_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_33_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_340_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_341_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_342_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_343_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_344_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_345_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_346_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_347_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_348_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_349_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_34_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_350_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_351_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_352_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_353_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_354_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_355_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_356_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_357_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_358_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_359_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_35_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_360_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_361_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_362_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_363_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_364_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_365_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_366_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_367_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_368_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_369_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_36_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_370_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_371_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_372_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_373_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_374_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_375_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_376_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_377_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_378_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_379_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_37_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_380_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_381_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_382_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_383_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_384_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_385_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_386_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_387_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_388_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_389_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_38_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_390_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_391_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_392_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_393_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_394_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_395_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_396_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_397_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_398_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_399_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_39_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_3_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_400_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_401_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_402_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_403_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_404_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_405_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_406_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_407_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_408_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_409_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_40_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_410_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_411_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_412_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_413_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_414_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_415_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_416_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_417_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_418_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_419_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_41_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_420_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_421_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_422_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_423_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_424_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_425_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_426_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_427_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_428_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_429_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_42_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_430_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_431_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_432_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_433_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_434_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_435_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_436_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_437_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_438_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_439_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_43_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_440_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_441_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_442_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_443_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_444_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_445_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_446_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_447_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_448_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_449_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_44_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_450_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_451_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_452_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_453_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_454_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_455_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_456_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_457_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_458_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_459_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_45_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_460_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_461_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_462_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_463_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_464_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_465_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_466_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_467_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_468_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_469_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_46_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_470_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_471_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_472_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_473_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_474_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_475_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_476_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_477_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_478_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_479_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_47_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_480_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_481_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_482_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_483_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_484_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_485_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_486_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_487_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_488_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_489_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_48_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_490_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_491_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_492_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_493_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_494_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_495_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_496_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_497_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_498_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_499_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_49_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_4_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_500_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_501_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_502_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_503_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_504_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_505_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_506_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_507_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_508_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_509_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_50_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_510_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_511_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_512_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_513_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_514_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_515_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_516_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_517_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_518_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_519_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_51_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_520_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_521_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_522_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_523_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_524_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_525_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_526_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_527_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_528_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_529_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_52_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_530_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_531_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_532_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_533_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_534_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_535_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_536_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_537_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_538_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_539_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_53_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_540_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_541_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_542_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_543_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_544_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_545_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_546_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_547_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_548_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_549_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_54_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_550_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_551_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_552_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_553_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_554_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_555_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_556_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_557_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_558_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_559_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_55_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_560_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_561_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_562_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_563_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_564_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_565_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_566_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_567_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_568_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_569_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_56_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_570_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_571_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_572_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_573_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_574_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_575_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_576_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_577_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_578_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_579_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_57_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_580_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_581_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_582_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_583_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_584_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_585_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_586_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_587_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_588_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_589_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_58_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_590_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_591_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_592_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_593_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_594_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_595_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_596_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_597_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_598_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_599_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_59_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_5_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_600_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_601_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_602_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_603_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_604_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_605_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_606_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_607_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_608_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_609_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_60_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_610_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_611_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_612_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_613_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_614_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_615_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_616_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_617_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_618_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_619_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_61_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_620_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_621_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_622_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_623_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_624_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_625_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_626_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_627_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_628_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_629_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_62_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_630_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_631_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_632_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_633_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_634_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_635_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_636_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_637_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_638_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_639_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_63_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_640_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_641_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_642_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_643_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_644_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_645_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_646_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_647_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_648_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_649_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_64_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_650_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_651_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_652_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_653_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_654_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_655_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_656_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_657_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_658_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_659_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_65_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_660_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_661_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_662_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_663_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_664_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_665_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_666_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_667_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_668_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_669_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_66_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_670_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_671_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_672_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_673_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_674_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_675_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_676_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_677_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_678_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_679_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_67_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_680_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_681_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_682_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_683_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_684_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_685_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_686_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_687_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_688_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_689_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_68_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_690_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_691_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_692_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_693_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_694_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_695_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_696_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_697_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_698_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_699_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_69_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_6_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_700_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_701_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_702_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_703_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_704_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_705_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_706_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_707_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_708_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_709_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_70_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_710_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_711_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_712_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_713_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_714_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_715_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_716_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_717_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_718_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_719_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_71_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_720_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_721_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_722_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_723_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_724_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_725_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_726_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_727_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_728_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_729_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_72_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_730_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_731_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_732_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_733_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_734_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_735_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_736_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_737_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_738_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_739_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_73_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_740_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_741_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_742_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_743_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_744_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_745_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_746_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_747_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_748_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_749_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_74_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_750_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_751_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_752_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_753_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_754_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_755_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_756_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_757_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_758_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_759_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_75_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_760_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_761_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_762_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_763_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_764_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_765_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_766_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_767_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_768_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_769_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_76_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_770_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_771_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_772_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_773_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_774_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_775_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_776_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_777_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_778_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_779_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_77_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_780_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_781_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_782_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_783_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_784_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_785_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_786_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_787_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_788_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_789_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_78_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_790_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_791_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_792_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_793_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_794_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_795_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_796_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_797_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_798_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_799_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_79_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_7_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_800_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_801_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_802_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_803_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_804_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_805_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_806_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_807_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_808_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_809_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_80_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_810_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_811_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_812_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_813_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_814_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_815_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_816_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_817_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_818_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_819_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_81_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_820_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_821_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_822_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_823_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_824_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_825_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_826_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_827_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_828_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_829_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_82_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_830_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_831_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_832_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_833_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_834_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_835_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_836_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_837_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_838_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_839_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_83_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_840_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_841_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_842_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_843_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_844_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_845_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_846_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_847_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_848_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_849_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_84_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_850_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_851_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_852_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_853_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_854_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_855_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_856_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_857_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_858_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_859_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_85_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_860_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_861_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_862_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_863_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_864_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_865_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_866_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_867_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_868_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_869_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_86_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_870_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_871_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_872_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_873_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_874_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_875_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_876_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_877_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_878_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_879_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_87_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_880_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_881_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_882_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_883_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_884_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_885_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_886_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_887_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_888_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_889_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_88_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_890_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_891_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_892_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_893_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_894_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_895_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_896_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_897_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_898_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_899_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_89_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_8_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_900_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_901_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_902_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_903_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_904_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_905_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_906_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_907_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_908_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_909_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_90_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_910_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_911_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_912_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_913_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_914_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_915_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_916_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_917_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_918_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_919_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_91_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_920_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_921_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_922_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_923_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_924_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_925_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_926_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_927_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_928_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_929_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_92_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_930_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_931_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_932_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_933_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_934_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_935_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_936_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_937_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_938_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_939_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_93_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_940_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_941_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_942_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_943_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_944_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_945_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_946_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_947_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_948_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_949_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_94_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_950_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_951_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_952_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_953_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_954_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_955_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_956_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_957_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_958_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_959_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_95_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_960_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_961_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_962_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_963_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_964_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_965_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_966_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_967_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_968_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_969_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_96_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_970_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_971_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_972_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_973_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_974_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_975_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_976_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_977_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_978_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_979_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_97_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_980_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_981_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_982_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_983_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_984_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_985_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_986_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_987_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_988_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_989_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_98_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_990_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_991_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_992_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_993_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_994_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_995_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_996_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_997_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_998_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_999_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_99_logo.png delete mode 100644 tests/input/res/drawable-xhdpi/ch_9_logo.png create mode 100644 tests/jank/OWNERS create mode 100644 tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java create mode 100644 tests/unit/OWNERS delete mode 100644 tests/unit/src/com/android/tv/common/TvContentRatingCacheTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/DvrDbSyncTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/DvrRecordingServiceTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/EpisodicProgramLoadTaskTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/InputTaskSchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/RecordingTaskTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/ScheduledProgramReaperTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/SchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/SeriesRecordingSchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/SeriesRecordingTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java create mode 100644 tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java create mode 100644 tests/unit/src/com/android/tv/experiments/ExperimentsTest.java create mode 100644 tests/unit/src/com/android/tv/search/LocalSearchProviderTest.java create mode 100644 tests/unit/src/com/android/tv/util/MockApplicationSingletons.java delete mode 100644 usbtuner-res/animator/setup_before_entry.xml delete mode 100644 usbtuner-res/animator/setup_before_exit.xml delete mode 100644 usbtuner-res/animator/setup_entry.xml delete mode 100644 usbtuner-res/animator/setup_exit.xml delete mode 100644 usbtuner-res/drawable-xhdpi/ic_setup_antenna.png delete mode 100644 usbtuner-res/drawable/ut_selector_background.xml delete mode 100644 usbtuner-res/layout/ut_activity_playback.xml delete mode 100644 usbtuner-res/layout/ut_guidance.xml delete mode 100644 usbtuner-res/layout/ut_guidedactions.xml create mode 100644 usbtuner-res/raw/ut_euro_dvbt_all create mode 100644 usbtuner-res/values-az-rAZ/strings.xml delete mode 100644 usbtuner-res/values-az/strings.xml create mode 100644 usbtuner-res/values-bn-rBD/strings.xml delete mode 100644 usbtuner-res/values-bn/strings.xml create mode 100644 usbtuner-res/values-et-rEE/strings.xml delete mode 100644 usbtuner-res/values-et/strings.xml create mode 100644 usbtuner-res/values-eu-rES/strings.xml delete mode 100644 usbtuner-res/values-eu/strings.xml create mode 100644 usbtuner-res/values-gl-rES/strings.xml delete mode 100644 usbtuner-res/values-gl/strings.xml create mode 100644 usbtuner-res/values-hy-rAM/strings.xml delete mode 100644 usbtuner-res/values-hy/strings.xml create mode 100644 usbtuner-res/values-is-rIS/strings.xml delete mode 100644 usbtuner-res/values-is/strings.xml create mode 100644 usbtuner-res/values-ka-rGE/strings.xml delete mode 100644 usbtuner-res/values-ka/strings.xml create mode 100644 usbtuner-res/values-kk-rKZ/strings.xml delete mode 100644 usbtuner-res/values-kk/strings.xml create mode 100644 usbtuner-res/values-km-rKH/strings.xml delete mode 100644 usbtuner-res/values-km/strings.xml create mode 100644 usbtuner-res/values-kn-rIN/strings.xml delete mode 100644 usbtuner-res/values-kn/strings.xml create mode 100644 usbtuner-res/values-ky-rKG/strings.xml delete mode 100644 usbtuner-res/values-ky/strings.xml create mode 100644 usbtuner-res/values-lo-rLA/strings.xml delete mode 100644 usbtuner-res/values-lo/strings.xml create mode 100644 usbtuner-res/values-mk-rMK/strings.xml delete mode 100644 usbtuner-res/values-mk/strings.xml create mode 100644 usbtuner-res/values-ml-rIN/strings.xml delete mode 100644 usbtuner-res/values-ml/strings.xml create mode 100644 usbtuner-res/values-mn-rMN/strings.xml delete mode 100644 usbtuner-res/values-mn/strings.xml create mode 100644 usbtuner-res/values-mr-rIN/strings.xml delete mode 100644 usbtuner-res/values-mr/strings.xml create mode 100644 usbtuner-res/values-ms-rMY/strings.xml delete mode 100644 usbtuner-res/values-ms/strings.xml create mode 100644 usbtuner-res/values-my-rMM/strings.xml delete mode 100644 usbtuner-res/values-my/strings.xml create mode 100644 usbtuner-res/values-ne-rNP/strings.xml delete mode 100644 usbtuner-res/values-ne/strings.xml create mode 100644 usbtuner-res/values-si-rLK/strings.xml delete mode 100644 usbtuner-res/values-si/strings.xml create mode 100644 usbtuner-res/values-ta-rIN/strings.xml delete mode 100644 usbtuner-res/values-ta/strings.xml create mode 100644 usbtuner-res/values-te-rIN/strings.xml delete mode 100644 usbtuner-res/values-te/strings.xml create mode 100644 usbtuner-res/values-ur-rPK/strings.xml delete mode 100644 usbtuner-res/values-ur/strings.xml create mode 100644 usbtuner-res/values-uz-rUZ/strings.xml delete mode 100644 usbtuner-res/values-uz/strings.xml delete mode 100644 usbtuner-res/values/styles.xml diff --git a/Android.mk b/Android.mk index 18468adc..7970f6f1 100644 --- a/Android.mk +++ b/Android.mk @@ -18,6 +18,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) + LOCAL_MODULE_TAGS := optional include $(LOCAL_PATH)/version.mk @@ -28,6 +29,8 @@ LOCAL_SRC_FILES := \ LOCAL_PACKAGE_NAME := LiveTv + + # It is required for com.android.providers.tv.permission.ALL_EPG_DATA LOCAL_PRIVILEGED_MODULE := true @@ -40,29 +43,40 @@ LOCAL_RESOURCE_DIR := \ ifdef TARGET_BUILD_APPS LOCAL_RESOURCE_DIR += \ + $(TOP)/prebuilts/sdk/current/support/compat/res \ $(TOP)/prebuilts/sdk/current/support/v17/leanback/res \ $(TOP)/prebuilts/sdk/current/support/v7/recyclerview/res else # !TARGET_BUILD_APPS LOCAL_RESOURCE_DIR += \ + $(TOP)/frameworks/support/compat/res \ $(TOP)/frameworks/support/v17/leanback/res \ $(TOP)/frameworks/support/v7/recyclerview/res endif +LOCAL_SRC_FILES += \ + src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-annotations \ - android-support-v4 \ + android-support-compat \ + android-support-core-ui \ android-support-v7-palette \ android-support-v7-recyclerview \ android-support-v17-leanback \ icu4j-usbtuner \ lib-exoplayer \ + lib-exoplayer-v2 \ + lib-exoplayer-v2-ext-ffmpeg \ + prebuilt-support-tv-provider \ tv-common \ -LOCAL_JAVA_LIBRARIES := junit + + + LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked -LOCAL_AAPT_FLAGS := --auto-add-overlay \ +LOCAL_AAPT_FLAGS += --auto-add-overlay \ + --extra-packages android.support.compat \ --extra-packages android.support.v7.recyclerview \ --extra-packages android.support.v17.leanback \ --extra-packages com.android.tv.common \ @@ -95,16 +109,19 @@ LOCAL_SDK_VERSION := system_current include $(BUILD_STATIC_JAVA_LIBRARY) + ############################################################# # Pre-built dependency jars ############################################################# - include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ lib-exoplayer:libs/exoplayer.jar \ + lib-exoplayer-v2:libs/exoplayer_v2.jar \ + lib-exoplayer-v2-ext-ffmpeg:libs/exoplayer_v2_ext_ffmpeg.jar \ + prebuilt-support-tv-provider:../../../prebuilts/sdk/current/support/tv-provider/android-support-tv-provider.jar \ include $(BUILD_MULTI_PREBUILT) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1faa2ae3..8770301e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -20,12 +20,14 @@ + + + - @@ -45,8 +47,16 @@ - + android:description="@string/permdesc_receiveInputEvent" + tools:ignore="SignatureOrSystemPermissions"/> + + + + - + android:theme="@style/Theme.Leanback.Browse"> + + + + + + + + + + - + android:theme="@style/Theme.Leanback"> + + + + + + - @@ -138,7 +164,7 @@ - @@ -151,6 +177,8 @@ + @@ -206,6 +234,13 @@ + + + + + - + + - - + + + + + diff --git a/assets/licenses.html b/assets/licenses.html deleted file mode 100644 index 000aacf6..00000000 --- a/assets/licenses.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - -

Notices for files:

-
    -
  • protobuf
  • -
-
Copyright 2008, Google Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-    * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-Code generated by the Protocol Buffer compiler is owned by the owner
-of the input file used when generating it.  This code is not
-standalone and requires a support library to be linked with it.  This
-support library is itself covered by the above license.
-
- - - -

Notices for files:

-
    -
  • icu4j
  • -
-
Copyright (c) 1995-2013 International Business Machines Corporation and others
-
-All rights reserved.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, and/or sell copies of the
-Software, and to permit persons to whom the Software is furnished to do so,
-provided that the above copyright notice(s) and this permission notice appear
-in all copies of the Software and that both the above copyright notice(s) and
-this permission notice appear in supporting documentation.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
-NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
-DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder shall not be
-used in advertising or otherwise to promote the sale, use or other dealings in
-this Software without prior written authorization of the copyright holder.
-
-

Notices for files:

-
    -
  • ExoPlayer
  • -
-
                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
-
- - - \ No newline at end of file diff --git a/assets/whitelist.policy b/assets/whitelist.policy new file mode 100644 index 00000000..33af781d --- /dev/null +++ b/assets/whitelist.policy @@ -0,0 +1,39 @@ +clone: 1 +close: 1 +epoll_pwait: 1 +exit: 1 +faccessat: 1 +fstat64: 1 +fstatfs64: 1 +futex: 1 +getegid32: 1 +geteuid32: 1 +getgid32: 1 +getgroups32: 1 +getpriority: 1 +getsockopt: 1 +gettimeofday: 1 +getuid32: 1 +ioctl: 1 +madvise: 1 +mmap: 1 +mmap2: 1 +mprotect: 1 +munmap: 1 +open: 1 +openat: 1 +pread64: 1 +prctl: 1 +pselect6: 1 +read: 1 +readlinkat: 1 +rt_sigprocmask: 1 +rt_sigtimedwait: 1 +sched_yield: 1 +setpriority: 1 +setsockopt: 1 +set_thread_area: 1 +set_tid_address: 1 +sigaction: 1 +sigaltstack: 1 +write: 1 \ No newline at end of file diff --git a/common/Android.mk b/common/Android.mk index bd4d99bc..d632597f 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -10,17 +10,20 @@ LOCAL_MODULE_TAGS := optional LOCAL_SDK_VERSION := system_current LOCAL_RESOURCE_DIR := \ + $(TOP)/prebuilts/sdk/current/support/compat/res \ $(TOP)/prebuilts/sdk/current/support/v7/recyclerview/res \ $(TOP)/prebuilts/sdk/current/support/v17/leanback/res \ $(LOCAL_PATH)/res \ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-annotations \ - android-support-v4 \ + android-support-compat \ + android-support-core-ui \ android-support-v7-recyclerview \ android-support-v17-leanback \ LOCAL_AAPT_FLAGS := --auto-add-overlay \ + --extra-packages android.support.compat \ --extra-packages android.support.v7.recyclerview \ --extra-packages android.support.v17.leanback \ diff --git a/common/OWNERS b/common/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/common/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/common/res/drawable/setup_selector_background.xml b/common/res/drawable/setup_selector_background.xml index 7351270b..b73c5813 100644 --- a/common/res/drawable/setup_selector_background.xml +++ b/common/res/drawable/setup_selector_background.xml @@ -17,8 +17,7 @@ - - + diff --git a/common/res/layout/fragment_setup_multi_pane.xml b/common/res/layout/fragment_setup_multi_pane.xml index 45aff132..e3ff8d83 100644 --- a/common/res/layout/fragment_setup_multi_pane.xml +++ b/common/res/layout/fragment_setup_multi_pane.xml @@ -40,11 +40,13 @@ android:clipChildren="false" android:clipToPadding="false" /> - @@ -54,7 +56,6 @@ android:layout_height="45dp" android:layout_marginStart="24dp" android:layout_marginEnd="40dp" - android:layout_marginTop="190dp" android:elevation="0dp" android:focusable="true" android:fontFamily="sans-serif-condensed" @@ -65,5 +66,23 @@ android:text="@string/action_text_done" android:textColor="#EEEEEE" android:textSize="14sp" /> - + + diff --git a/common/res/values-af/strings.xml b/common/res/values-af/strings.xml index 7a114849..ae7dbb60 100644 --- a/common/res/values-af/strings.xml +++ b/common/res/values-af/strings.xml @@ -18,10 +18,11 @@ "Klaar" + "Slaan oor" "S.%1$s Ep.%2$s" "Ep.%1$s" "S.%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S.%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S.%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-am/strings.xml b/common/res/values-am/strings.xml index 45a1b7cb..5c2f0964 100644 --- a/common/res/values-am/strings.xml +++ b/common/res/values-am/strings.xml @@ -18,10 +18,11 @@ "ተከናውኗል" + "ዝለል" "ም%1$s፦ ክ.%2$s" "ክ.%1$s" "ም%1$s፦ ክፍል %2$s %3$s" "ክፍል %1$s %2$s" - "%1$s%2$s፦ ክፍል %3$s" - "%1$s ክፍል %2$s" + "%1$s <i>ም%2$s: ክፍል፦ %3$s</i>" + "%1$s <i>ክፍል %2$s</i>" diff --git a/common/res/values-ar/strings.xml b/common/res/values-ar/strings.xml index bedd137b..1cb0daa7 100644 --- a/common/res/values-ar/strings.xml +++ b/common/res/values-ar/strings.xml @@ -18,10 +18,11 @@ "تم" + "تخطي" "الموسم %1$s: الحلقة %2$s" "الحلقة %1$s" "الموسم %1$s: الحلقة %2$s %3$s" "الحلقة %1$s %2$s" - "%1$s الموسم رقم %2$s: الحلقة رقم %3$s" - "%1$s الحلقة رقم %2$s" + "‏%1$s <i>الموسم رقم %2$s: الحلقة %3$s</i>" + "‏%1$s <i>الحلقة %2$s</i>" diff --git a/common/res/values-az-rAZ/strings.xml b/common/res/values-az-rAZ/strings.xml new file mode 100644 index 00000000..ca96c57b --- /dev/null +++ b/common/res/values-az-rAZ/strings.xml @@ -0,0 +1,28 @@ + + + + + "Hazırdır" + "Keçin" + "S%1$s: Ep.%2$s" + "Epizod%1$s" + "S%1$s: Ep. %2$s %3$s" + "Ep. %1$s %2$s" + "%1$s <i>Fəsil%2$s: Epizod. %3$s</i>" + "%1$s <i>Epizod. %2$s <i>" + diff --git a/common/res/values-az/strings.xml b/common/res/values-az/strings.xml deleted file mode 100644 index c844cd60..00000000 --- a/common/res/values-az/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Hazırdır" - "S%1$s: Ep.%2$s" - "Epizod%1$s" - "S%1$s: Ep. %2$s %3$s" - "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" - diff --git a/common/res/values-bg/strings.xml b/common/res/values-bg/strings.xml index 264a1118..607ad9d7 100644 --- a/common/res/values-bg/strings.xml +++ b/common/res/values-bg/strings.xml @@ -18,10 +18,11 @@ "Готово" + "Пропускане" "Сезон %1$s: епизод %2$s" "Епизод %1$s" "Сезон %1$s: Еп. %2$s – „%3$s“" "Еп. %1$s – „%2$s“" - "„%1$s“, сезон %2$s, епизод %3$s" - "„%1$s“, епизод %2$s" + "„%1$s“, <i>сез. %2$s, еп. %3$s</i>" + "„%1$s“, <i>еп. %2$s</i>" diff --git a/common/res/values-bn-rBD/strings.xml b/common/res/values-bn-rBD/strings.xml new file mode 100644 index 00000000..73706be7 --- /dev/null +++ b/common/res/values-bn-rBD/strings.xml @@ -0,0 +1,28 @@ + + + + + "সম্পন্ন" + "এড়িয়ে যান" + "সিজন %1$s , পর্ব %2$s" + "পর্ব%1$s" + "সিঃ%1$s: এপিঃ %2$s %3$s" + "এপিঃ %1$s %2$s" + "%1$s <i>সিজিন%2$s: পর্ব %3$s</i>" + "%1$s <i>পর্ব %2$s</i>" + diff --git a/common/res/values-bn/strings.xml b/common/res/values-bn/strings.xml deleted file mode 100644 index 836a62a5..00000000 --- a/common/res/values-bn/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "সম্পন্ন" - "সিজন %1$s , পর্ব %2$s" - "পর্ব%1$s" - "সিঃ%1$s: এপিঃ %2$s %3$s" - "এপিঃ %1$s %2$s" - "%1$s সিজিন%2$s: পর্ব %3$s" - "%1$s পর্ব. %2$s" - diff --git a/common/res/values-ca/strings.xml b/common/res/values-ca/strings.xml index 6382648e..4ef6c99b 100644 --- a/common/res/values-ca/strings.xml +++ b/common/res/values-ca/strings.xml @@ -18,10 +18,11 @@ "Fet" + "Omet" "Temporada %1$s, episodi %2$s" "Episodi %1$s" "Temporada %1$s, episodi %2$s: %3$s" "Episodi %1$s: %2$s" - "%1$s: temporada %2$s, episodi %3$s" - "%1$s: episodi %2$s" + "%1$s: <i>temp. %2$s, ep. %3$s</i>" + "%1$s: <i>ep. %2$s</i>" diff --git a/common/res/values-cs/strings.xml b/common/res/values-cs/strings.xml index 0be8026c..a6aa1173 100644 --- a/common/res/values-cs/strings.xml +++ b/common/res/values-cs/strings.xml @@ -18,10 +18,11 @@ "Hotovo" + "Přeskočit" "S%1$sE%2$s" "E%1$s" "S%1$s: Ep. %2$s%3$s" "Ep. %1$s%2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-da/strings.xml b/common/res/values-da/strings.xml index 4685b818..3eeb51da 100644 --- a/common/res/values-da/strings.xml +++ b/common/res/values-da/strings.xml @@ -18,10 +18,11 @@ "Udført" + "Spring over" "S. %1$s, afsn. %2$s" "Afsn. %1$s" "S. %1$s: Afsnit %2$s, %3$s" "Afsnit %1$s %2$s" - "%1$s S. %2$s: Afsn. %3$s" - "%1$s afsn. %2$s" + "%1$s <i>S. %2$s: Afsn. %3$s</i>" + "%1$s <i>Afsn. %2$s</i>" diff --git a/common/res/values-de/strings.xml b/common/res/values-de/strings.xml index a487ff8a..11e8b0da 100644 --- a/common/res/values-de/strings.xml +++ b/common/res/values-de/strings.xml @@ -18,10 +18,11 @@ "Fertig" + "Überspringen" "S%1$s: F%2$s" "Folge %1$s" "Staffel %1$s: Folge %2$s, %3$s" "Folge %1$s, %2$s" - "%1$s, Staffel %2$s: Folge %3$s" - "%1$s, Folge %2$s" + "%1$s, <i>Staffel %2$s: Folge %3$s</i>" + "%1$s, <i>Folge %2$s</i>" diff --git a/common/res/values-el/strings.xml b/common/res/values-el/strings.xml index dba894cb..60a73442 100644 --- a/common/res/values-el/strings.xml +++ b/common/res/values-el/strings.xml @@ -18,10 +18,11 @@ "Τέλος" + "Παράβλεψη" "Σεζ. %1$s: Επ.%2$s" "Επ.%1$s" "Σεζ.%1$s: Επ. %2$s %3$s" "Επ. %1$s %2$s" - "%1$s Σεζόν%2$s: Επ. %3$s" - "%1$s Επ. %2$s" + "%1$s <i>S%2$s: Επ. %3$s</i>" + "%1$s <i>Επ. %2$s</i>" diff --git a/common/res/values-en-rAU/strings.xml b/common/res/values-en-rAU/strings.xml index 0ea27edc..94d1c120 100644 --- a/common/res/values-en-rAU/strings.xml +++ b/common/res/values-en-rAU/strings.xml @@ -18,10 +18,11 @@ "Finished" + "Skip" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-en-rGB/strings.xml b/common/res/values-en-rGB/strings.xml index 0ea27edc..94d1c120 100644 --- a/common/res/values-en-rGB/strings.xml +++ b/common/res/values-en-rGB/strings.xml @@ -18,10 +18,11 @@ "Finished" + "Skip" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-en-rIN/strings.xml b/common/res/values-en-rIN/strings.xml index 0ea27edc..94d1c120 100644 --- a/common/res/values-en-rIN/strings.xml +++ b/common/res/values-en-rIN/strings.xml @@ -18,10 +18,11 @@ "Finished" + "Skip" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-es-rUS/strings.xml b/common/res/values-es-rUS/strings.xml index 87c881b8..3a15ff3a 100644 --- a/common/res/values-es-rUS/strings.xml +++ b/common/res/values-es-rUS/strings.xml @@ -18,10 +18,11 @@ "Listo" + "Omitir" "Temporada %1$s: episodio %2$s" "Episodio %1$s" "Temporada %1$s, episodio %2$s: %3$s" "Episodio %1$s: %2$s" - "%1$s: T%2$s, episodio %3$s" - "%1$s: episodio %2$s" + "%1$s <i>Temporada %2$s: Episodio %3$s</i>" + "%1$s <i>Episodio %2$s</i>" diff --git a/common/res/values-es/strings.xml b/common/res/values-es/strings.xml index 32536d93..0e7e5ef4 100644 --- a/common/res/values-es/strings.xml +++ b/common/res/values-es/strings.xml @@ -18,10 +18,11 @@ "Listo" + "Saltar" "Temporada %1$s: episodio %2$s" "Episodio %1$s" "Temporada %1$s: episodio %2$s %3$s" "Episodio %1$s %2$s" - "Temporada %2$s de %1$s, episodio %3$s" - "Episodio %2$s de %1$s" + "Temporada %2$s de %1$s: <i>episodio %3$s</i>" + "<i>Episodio %2$s</i> de %1$s" diff --git a/common/res/values-et-rEE/strings.xml b/common/res/values-et-rEE/strings.xml new file mode 100644 index 00000000..baa39d7b --- /dev/null +++ b/common/res/values-et-rEE/strings.xml @@ -0,0 +1,28 @@ + + + + + "Valmis" + "Jäta vahele" + "%1$s. hooaeg: %2$s. jagu" + "%1$s. jagu" + "%1$s. hooaeg: %2$s. jagu – %3$s" + "%1$s. jagu – %2$s" + "%1$s <i>%2$s. h.: %3$s. jagu </i>" + "%1$s <i> %2$s. jagu </i>" + diff --git a/common/res/values-et/strings.xml b/common/res/values-et/strings.xml deleted file mode 100644 index 9052178a..00000000 --- a/common/res/values-et/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Valmis" - "%1$s. hooaeg: %2$s. jagu" - "%1$s. jagu" - "%1$s. hooaeg: %2$s. jagu – %3$s" - "%1$s. jagu – %2$s" - "%1$s%2$s. hooaeg, %3$s. jagu" - "%1$s%2$s. jagu" - diff --git a/common/res/values-eu-rES/strings.xml b/common/res/values-eu-rES/strings.xml new file mode 100644 index 00000000..90e82da1 --- /dev/null +++ b/common/res/values-eu-rES/strings.xml @@ -0,0 +1,28 @@ + + + + + "Eginda" + "Saltatu" + "%1$s. denboraldiko %2$s. atala" + "%1$s. atala" + "%1$s. denboraldiko %2$s. atala: %3$s" + "%1$s. atala: %2$s" + "%1$s <i>%2$s. denboraldia: %3$s. atala</i>" + "%1$s <i>%2$s. atala</i>" + diff --git a/common/res/values-eu/strings.xml b/common/res/values-eu/strings.xml deleted file mode 100644 index d7ab52a8..00000000 --- a/common/res/values-eu/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Eginda" - "%1$s. denboraldiko %2$s. atala" - "%1$s. atala" - "%1$s. denboraldiko %2$s. atala: %3$s" - "%1$s. atala: %2$s" - "%1$s, %2$s. denboraldia: %3$s. atala" - "%1$s, %2$s. atala" - diff --git a/common/res/values-fa/strings.xml b/common/res/values-fa/strings.xml index 67e9c4e6..55e7c488 100644 --- a/common/res/values-fa/strings.xml +++ b/common/res/values-fa/strings.xml @@ -18,10 +18,11 @@ "تمام" + "رد شدن" "فصل%1$s: قسمت%2$s" "اپیزود %1$s" "ف.%1$s: ق. %2$s %3$s" "ق. %1$s %2$s" - "%1$s فصل %2$s: قسمت %3$s" - "%1$s قسمت %2$s" + "‏%1$s <i>فصل%2$s: قسمت %3$s</i>" + "‏%1$s <i>قسمت %2$s</i>" diff --git a/common/res/values-fi/strings.xml b/common/res/values-fi/strings.xml index 8c6b99bf..274aeb5f 100644 --- a/common/res/values-fi/strings.xml +++ b/common/res/values-fi/strings.xml @@ -18,10 +18,11 @@ "Valmis" + "Ohita" "Kausi %1$s, jakso %2$s" "Jakso %1$s" "Kausi %1$s, jakso %2$s: %3$s" "Jakso %1$s: %2$s" - "%1$s kausi %2$s, jakso %3$s" - "%1$s jakso %2$s" + "%1$s <i>K%2$s, J%3$s</i>" + "%1$s <i>J%2$s</i>" diff --git a/common/res/values-fr-rCA/strings.xml b/common/res/values-fr-rCA/strings.xml index ed5678e2..4f88b206 100644 --- a/common/res/values-fr-rCA/strings.xml +++ b/common/res/values-fr-rCA/strings.xml @@ -18,10 +18,11 @@ "Terminé" + "Ignorer" "Saison %1$s, ép. %2$s" "Ép. %1$s" "Saison %1$s, épisode %2$s, « %3$s »" "Épisode %1$s, « %2$s »" - "%1$s, saison %2$s : épisode %3$s" - "%1$s, épisode %2$s" + "%1$s <i>S %2$s : ép. %3$s</i>" + "%1$s <i>Ép. %2$s</i>" diff --git a/common/res/values-fr/strings.xml b/common/res/values-fr/strings.xml index b5b28104..95408bde 100644 --- a/common/res/values-fr/strings.xml +++ b/common/res/values-fr/strings.xml @@ -18,10 +18,11 @@ "OK" + "Passer" "Saison %1$s, épisode %2$s" "Épisode %1$s" "S%1$s, ép. %2$s : %3$s" "Ép. %1$s : %2$s" - "%1$s – S %2$s, ép. %3$s" - "%1$s – Ép. %2$s" + "%1$s – <i> S %2$s, ép. %3$s</i>" + "%1$s – <i>, ép. %2$s</i>" diff --git a/common/res/values-gl-rES/strings.xml b/common/res/values-gl-rES/strings.xml new file mode 100644 index 00000000..dfcbdfc2 --- /dev/null +++ b/common/res/values-gl-rES/strings.xml @@ -0,0 +1,28 @@ + + + + + "Feito" + "Omitir" + "T %1$s: Ep. %2$s" + "Ep. %1$s" + "T%1$s: Ep. %2$s %3$s" + "Ep. %1$s %2$s" + "%1$s <i>T%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" + diff --git a/common/res/values-gl/strings.xml b/common/res/values-gl/strings.xml deleted file mode 100644 index 356ac562..00000000 --- a/common/res/values-gl/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Feito" - "T %1$s: Ep. %2$s" - "Ep. %1$s" - "T%1$s: Ep. %2$s %3$s" - "Ep. %1$s %2$s" - "%1$s T%2$s: ep. %3$s" - "%1$s: ep. %2$s" - diff --git a/common/res/values-gu-rIN/strings.xml b/common/res/values-gu-rIN/strings.xml new file mode 100644 index 00000000..701977fa --- /dev/null +++ b/common/res/values-gu-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "થઈ ગયું" + "છોડો" + "સિઝન%1$s: એપિસોડ%2$s" + "એપિસોડ%1$s" + "સિઝન%1$s: એપિસોડ %2$s %3$s" + "એપિસોડ%1$s%2$s" + "%1$s <i>સિઝન%2$s: ઍપિસોડ %3$s</i>" + "%1$s <i>ઍપિસોડ %2$s</i>" + diff --git a/common/res/values-hi/strings.xml b/common/res/values-hi/strings.xml index 27cae84d..7bed8f0c 100644 --- a/common/res/values-hi/strings.xml +++ b/common/res/values-hi/strings.xml @@ -18,10 +18,11 @@ "हो गया" + "अभी नहीं" "सीज़न%1$s: एपिसोड%2$s" "एपिसोड %1$s" "सीज़न%1$s: एपिसोड %2$s %3$s" "एपिसोड %1$s %2$s" - "%1$s सीज़न%2$s: एपिसोड %3$s" - "%1$s एपिसोड %2$s" + "%1$s <i>सीज़न%2$s: एपिसोड %3$s</i>" + "%1$s <i>एपिसोड %2$s</i>" diff --git a/common/res/values-hr/strings.xml b/common/res/values-hr/strings.xml index 66127784..7fad7d05 100644 --- a/common/res/values-hr/strings.xml +++ b/common/res/values-hr/strings.xml @@ -18,10 +18,11 @@ "Gotovo" + "Preskoči" "S. %1$s: Ep. %2$s" "Ep. %1$s" "S. %1$s: ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s %2$s. s.: %3$s. ep." - "%1$s %2$s. ep." + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-hu/strings.xml b/common/res/values-hu/strings.xml index 138ad857..7b5867e3 100644 --- a/common/res/values-hu/strings.xml +++ b/common/res/values-hu/strings.xml @@ -18,10 +18,11 @@ "Kész" + "Kihagyás" "%1$s. évad, %2$s. rész" "%1$s. rész" "%1$s. évad, %2$s. rész: %3$s" "%1$s. rész: %2$s" - "%1$s%2$s. évad, %3$s. epizód" - "%1$s%2$s. epizód" + "%1$s – <i>%2$s. évad %3$s. rész</i>" + "%1$s – <i>%2$s. rész</i>" diff --git a/common/res/values-hy-rAM/strings.xml b/common/res/values-hy-rAM/strings.xml new file mode 100644 index 00000000..ed13be2d --- /dev/null +++ b/common/res/values-hy-rAM/strings.xml @@ -0,0 +1,28 @@ + + + + + "Պատրաստ է" + "Բաց թողնել" + %1$s՝ դրվ. %2$s" + "Դրվ. %1$s" + %1$s՝ դրվ. %2$s %3$s" + "Դրվ. %1$s %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" + diff --git a/common/res/values-hy/strings.xml b/common/res/values-hy/strings.xml deleted file mode 100644 index aaa7470f..00000000 --- a/common/res/values-hy/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Պատրաստ է" - %1$s՝ դրվ. %2$s" - "Դրվ. %1$s" - %1$s՝ դրվ. %2$s %3$s" - "Դրվ. %1$s %2$s" - "%1$s Ե%2$s՝ Դրվ. %3$s" - "%1$s Դրվ. %2$s" - diff --git a/common/res/values-in/strings.xml b/common/res/values-in/strings.xml index da4b8203..89f4d532 100644 --- a/common/res/values-in/strings.xml +++ b/common/res/values-in/strings.xml @@ -18,10 +18,11 @@ "Selesai" + "Lewati" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-is-rIS/strings.xml b/common/res/values-is-rIS/strings.xml new file mode 100644 index 00000000..d68d8250 --- /dev/null +++ b/common/res/values-is-rIS/strings.xml @@ -0,0 +1,28 @@ + + + + + "Lokið" + "Sleppa" + "S.%1$s: Þ.%2$s" + "%1$s. þáttur" + "S.%1$s: Þ. %2$s %3$s" + "Þ. %1$s %2$s" + "%1$s <i>S%2$s: Þ. %3$s</i>" + "%1$s <i>Þ. %2$s</i>" + diff --git a/common/res/values-is/strings.xml b/common/res/values-is/strings.xml deleted file mode 100644 index 47529403..00000000 --- a/common/res/values-is/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Lokið" - "S.%1$s: Þ.%2$s" - "%1$s. þáttur" - "S.%1$s: Þ. %2$s %3$s" - "Þ. %1$s %2$s" - "%1$s S.%2$s: Þ. %3$s" - "%1$s Þ. %2$s" - diff --git a/common/res/values-it/strings.xml b/common/res/values-it/strings.xml index f61e02f7..137d5458 100644 --- a/common/res/values-it/strings.xml +++ b/common/res/values-it/strings.xml @@ -18,10 +18,11 @@ "Fine" + "Ignora" "Stagione %1$s, puntata %2$s" "Ep. %1$s" "Stag. %1$s: punt. %2$s %3$s" "Punt. %1$s %2$s" - "%1$s Stag. %2$s: punt. %3$s" - "%1$s Punt. %2$s" + "%1$s <i>Stag. %2$s: punt. %3$s</i>" + "%1$s <i>Punt. %2$s</i>" diff --git a/common/res/values-iw/strings.xml b/common/res/values-iw/strings.xml index 7ad220ad..fdc75ce1 100644 --- a/common/res/values-iw/strings.xml +++ b/common/res/values-iw/strings.xml @@ -18,10 +18,11 @@ "סיום" + "דלג" "עונה %1$s: פרק %2$s" "פרק %1$s" "עונה%1$s: פרק %2$s %3$s" "פרק %1$s %2$s" - "%1$s עונה %2$s: פרק %3$s" - "%1$s פרק %2$s" + "‏%1$s <i>עונה %2$s פרק %3$s</i>" + "‏%1$s <i>פרק %2$s</i>" diff --git a/common/res/values-ja/strings.xml b/common/res/values-ja/strings.xml index 682fd0d8..e0aa93bb 100644 --- a/common/res/values-ja/strings.xml +++ b/common/res/values-ja/strings.xml @@ -18,10 +18,11 @@ "完了" + "スキップ" "シーズン %1$s: エピソード %2$s" "エピソード %1$s" "シーズン %1$s: エピソード %2$s%3$s」" "エピソード %1$s%2$s」" - "%1$s、シーズン %2$s: エピソード %3$s" - "%1$s、エピソード %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-ka-rGE/strings.xml b/common/res/values-ka-rGE/strings.xml new file mode 100644 index 00000000..f930c021 --- /dev/null +++ b/common/res/values-ka-rGE/strings.xml @@ -0,0 +1,28 @@ + + + + + "მზადაა" + "გამოტოვება" + "სეზ. %1$s: ეპ. %2$s" + "ეპ. %1$s" + "სეზ. %1$s, ეპ. %2$s%3$s" + "ეპ. %1$s%2$s" + "%1$s <i>სეზ.%2$s: ეპიზ. %3$s</i>" + "%1$s <i>ეპიზ. %2$s</i>" + diff --git a/common/res/values-ka/strings.xml b/common/res/values-ka/strings.xml deleted file mode 100644 index 3c7062ff..00000000 --- a/common/res/values-ka/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "მზადაა" - "სეზ. %1$s: ეპ. %2$s" - "ეპ. %1$s" - "სეზ. %1$s, ეპ. %2$s%3$s" - "ეპ. %1$s%2$s" - "%1$s: სეზ. %2$s, ეპ. %3$s" - "%1$s: სეზ. %2$s" - diff --git a/common/res/values-kk-rKZ/strings.xml b/common/res/values-kk-rKZ/strings.xml new file mode 100644 index 00000000..2aca092b --- /dev/null +++ b/common/res/values-kk-rKZ/strings.xml @@ -0,0 +1,28 @@ + + + + + "Орындалды" + "Өткізіп жіберу" + "%1$s-маусым, %2$s-серия" + "%1$s-серия" + "%1$s-маусым: %2$s-серия, \"%3$s\"" + "%1$s-серия, \"%2$s\"" + "%1$s, <i>%2$s-маусым: %3$s-серия</i>" + "%1$s, <i>%2$s-серия</i>" + diff --git a/common/res/values-kk/strings.xml b/common/res/values-kk/strings.xml deleted file mode 100644 index 12de7d34..00000000 --- a/common/res/values-kk/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Орындалды" - "%1$s-маусым, %2$s-серия" - "%1$s-серия" - "%1$s-маусым: %2$s-серия, \"%3$s\"" - "%1$s-серия, \"%2$s\"" - "%1$s, %2$s-маусым, %3$s-серия" - "%1$s, %2$s-серия" - diff --git a/common/res/values-km-rKH/strings.xml b/common/res/values-km-rKH/strings.xml new file mode 100644 index 00000000..252bd37a --- /dev/null +++ b/common/res/values-km-rKH/strings.xml @@ -0,0 +1,28 @@ + + + + + "រួចរាល់" + "រំលង" + "រដូវកាល%1$s៖ ភាគ%2$s" + "ភាគ៖ %1$s" + "រដូវកាលទី %1$s៖ វគ្គ %2$s %3$s" + "វគ្គ %1$s %2$s" + "%1$s <i>រដូវកាល​ទី %2$s៖ ភាគ​ទី %3$s</i>" + "%1$s <i>ភាគ​ទី %2$s</i>" + diff --git a/common/res/values-km/strings.xml b/common/res/values-km/strings.xml deleted file mode 100644 index b8353897..00000000 --- a/common/res/values-km/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "រួចរាល់" - "រដូវកាល%1$s៖ ភាគ%2$s" - "ភាគ៖ %1$s" - "រដូវកាលទី %1$s៖ វគ្គ %2$s %3$s" - "វគ្គ %1$s %2$s" - "%1$s រដូវ%2$s៖ ភាគ %3$s" - "%1$s ភាគ %2$s" - diff --git a/common/res/values-kn-rIN/strings.xml b/common/res/values-kn-rIN/strings.xml new file mode 100644 index 00000000..6cfb5cab --- /dev/null +++ b/common/res/values-kn-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "ಮುಗಿದಿದೆ" + "ಸ್ಕಿಪ್‌ ಮಾಡಿ" + "ಕಾಲ%1$sಭಾಗ%2$s" + "ಭಾಗ%1$s" + "ಸೀ%1$s: ಸಂ. %2$s %3$s" + "ಸಂ. %1$s %2$s" + "%1$s <i>ಸೀ%2$s: ಸಂ. %3$s</i>" + "%1$s <i>ಸಂ. %2$s</i>" + diff --git a/common/res/values-kn/strings.xml b/common/res/values-kn/strings.xml deleted file mode 100644 index 7cb096b7..00000000 --- a/common/res/values-kn/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "ಮುಗಿದಿದೆ" - "ಕಾಲ%1$sಭಾಗ%2$s" - "ಭಾಗ%1$s" - "ಸೀ%1$s: ಸಂ. %2$s %3$s" - "ಸಂ. %1$s %2$s" - "%1$s ಸೀ%2$s: ಸಂ. %3$s" - "%1$s ಸಂ. %2$s" - diff --git a/common/res/values-ko/strings.xml b/common/res/values-ko/strings.xml index 923831f3..fe8a5c28 100644 --- a/common/res/values-ko/strings.xml +++ b/common/res/values-ko/strings.xml @@ -18,10 +18,11 @@ "완료" + "건너뛰기" "시즌 %1$s: 에피소드 %2$s" "에피소드 %1$s" "시즌 %1$s: 에피소드 %2$s \'%3$s\'" "에피소드 %1$s \'%2$s\'" - "%1$s 시즌 %2$s: 에피소드 %3$s" - "%1$s 에피소드 %2$s" + "%1$s <i>시즌 %2$s, 에피소드 %3$s</i>" + "%1$s <i>에피소드 %2$s</i>" diff --git a/common/res/values-ky-rKG/strings.xml b/common/res/values-ky-rKG/strings.xml new file mode 100644 index 00000000..b5b804d3 --- /dev/null +++ b/common/res/values-ky-rKG/strings.xml @@ -0,0 +1,28 @@ + + + + + "Бүттү" + "Өткөрүп жиберүү" + "%1$s-мезгил: %2$s-серия" + "%1$s-серия" + %1$s: %3$s %2$s-серия" + "%2$s %1$s-серия" + "%1$s <i>М%2$s: С. %3$s</i>" + "%1$s <i>С. %2$s</i>" + diff --git a/common/res/values-ky/strings.xml b/common/res/values-ky/strings.xml deleted file mode 100644 index 8bdfadde..00000000 --- a/common/res/values-ky/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Бүттү" - "%1$s-мезгил: %2$s-серия" - "%1$s-серия" - %1$s: %3$s %2$s-серия" - "%2$s %1$s-серия" - "%1$s S%2$s: %3$s-эпизод" - "%1$s %2$s-эпизод" - diff --git a/common/res/values-lo-rLA/strings.xml b/common/res/values-lo-rLA/strings.xml new file mode 100644 index 00000000..c7ac4e21 --- /dev/null +++ b/common/res/values-lo-rLA/strings.xml @@ -0,0 +1,28 @@ + + + + + "ສຳເລັດແລ້ວ" + "ຂ້າມ" + "S%1$s: Ep.%2$s" + "Ep.%1$s" + "S%1$s: Ep. %2$s %3$s" + "Ep. %1$s %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" + diff --git a/common/res/values-lo/strings.xml b/common/res/values-lo/strings.xml deleted file mode 100644 index a6dad515..00000000 --- a/common/res/values-lo/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "ສຳເລັດແລ້ວ" - "S%1$s: Ep.%2$s" - "Ep.%1$s" - "S%1$s: Ep. %2$s %3$s" - "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" - diff --git a/common/res/values-lt/strings.xml b/common/res/values-lt/strings.xml index 6d6c0ee8..2a38d45a 100644 --- a/common/res/values-lt/strings.xml +++ b/common/res/values-lt/strings.xml @@ -18,10 +18,11 @@ "Atlikta" + "Praleisti" "%1$s sezonas: %2$s serija" "%1$s serija" "%1$s sezonas: %2$s serija „%3$s“" "%1$s serija „%2$s“" - "Programos „%1$s%2$s sezonas: %3$s serija" - "Programos „%1$s%2$s serija" + "Programos „%1$s“ <i>%2$s sezonas: %3$s serija</i>" + "Programos „%1$s“ <i>%2$s serija</i>" diff --git a/common/res/values-lv/strings.xml b/common/res/values-lv/strings.xml index f0366a48..473b69fc 100644 --- a/common/res/values-lv/strings.xml +++ b/common/res/values-lv/strings.xml @@ -18,10 +18,11 @@ "Gatavs" + "Izlaist" "%1$s. sez., %2$s. sēr." "%1$s. sēr." "%1$s. sezona: %2$s. sērija “%3$s”" "%1$s. sērija “%2$s”" - "%1$s: %2$s. sezona, %3$s. sērija" - "%1$s: %2$s. sērija" + "%1$s: <i>%2$s. sezona, %3$s. sērija</i>" + "%1$s: <i>%2$s. sērija</i>" diff --git a/common/res/values-mk-rMK/strings.xml b/common/res/values-mk-rMK/strings.xml new file mode 100644 index 00000000..80603a1a --- /dev/null +++ b/common/res/values-mk-rMK/strings.xml @@ -0,0 +1,28 @@ + + + + + "Готово" + "Прескокни" + "Сез.%1$s: Еп.%2$s" + "Еп.%1$s" + "Сез. %1$s: Еп. %2$s %3$s" + "Еп. %1$s %2$s" + "%1$s <i>Сез.%2$s: Еп. %3$s</i>" + "%1$s <i>Еп. %2$s</i>" + diff --git a/common/res/values-mk/strings.xml b/common/res/values-mk/strings.xml deleted file mode 100644 index 73045d3d..00000000 --- a/common/res/values-mk/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Готово" - "Сез.%1$s: Еп.%2$s" - "Еп.%1$s" - "Сез. %1$s: Еп. %2$s %3$s" - "Еп. %1$s %2$s" - "%1$s Сез.%2$s: Еп.. %3$s" - "%1$s Еп. %2$s" - diff --git a/common/res/values-ml-rIN/strings.xml b/common/res/values-ml-rIN/strings.xml new file mode 100644 index 00000000..abe8deda --- /dev/null +++ b/common/res/values-ml-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "പൂർത്തിയായി" + "ഒഴിവാക്കുക" + "സീസൺ%1$s: എപ്പി.%2$s" + "എപ്പി.%1$s" + "സീസൺ%1$s: എപ്പിസോഡ് %2$s %3$s" + "എപ്പിസോഡ് %1$s %2$s" + "%1$s <i>S%2$s: എപ്പി. %3$s</i>" + "%1$s <i>എപ്പി. %2$s</i>" + diff --git a/common/res/values-ml/strings.xml b/common/res/values-ml/strings.xml deleted file mode 100644 index 07727684..00000000 --- a/common/res/values-ml/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "പൂർത്തിയായി" - "സീസൺ%1$s: എപ്പി.%2$s" - "എപ്പി.%1$s" - "സീസൺ%1$s: എപ്പിസോഡ് %2$s %3$s" - "എപ്പിസോഡ് %1$s %2$s" - "%1$s സീരിയൽ%2$s: എപ്പിസോഡ് %3$s" - "%1$s എപ്പിസോഡ് %2$s" - diff --git a/common/res/values-mn-rMN/strings.xml b/common/res/values-mn-rMN/strings.xml new file mode 100644 index 00000000..aec6c268 --- /dev/null +++ b/common/res/values-mn-rMN/strings.xml @@ -0,0 +1,28 @@ + + + + + "Дууссан" + "Алгасах" + "Бүлэг%1$s: Цуврал.%2$s" + "Анги.%1$s" + "Бүлэг%1$s: Анги. %2$s %3$s" + "Анги. %1$s %2$s" + "%1$s <i>S%2$s: Анги %3$s</i>" + "%1$s <i>Анги %2$s</i>" + diff --git a/common/res/values-mn/strings.xml b/common/res/values-mn/strings.xml deleted file mode 100644 index ff1552e0..00000000 --- a/common/res/values-mn/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Дууссан" - "Бүлэг%1$s: Цуврал.%2$s" - "Анги.%1$s" - "Бүлэг%1$s: Анги. %2$s %3$s" - "Анги. %1$s %2$s" - "%1$s Бүлэг%2$s: Анги. %3$s" - "%1$s Анги. %2$s" - diff --git a/common/res/values-mr-rIN/strings.xml b/common/res/values-mr-rIN/strings.xml new file mode 100644 index 00000000..1084c7f1 --- /dev/null +++ b/common/res/values-mr-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "पूर्ण झाले" + "वगळा" + "हंगाम%1$s: भाग%2$s" + "भाग%1$s" + "हंगाम%1$s: भाग. %2$s %3$s" + "भाग. %1$s %2$s" + "%1$s <i>S%2$s: भाग %3$s</i>" + "%1$s <i>भाग %2$s</i>" + diff --git a/common/res/values-mr/strings.xml b/common/res/values-mr/strings.xml deleted file mode 100644 index adc669ae..00000000 --- a/common/res/values-mr/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "पूर्ण झाले" - "हंगाम%1$s: भाग%2$s" - "भाग%1$s" - "हंगाम%1$s: भाग. %2$s %3$s" - "भाग. %1$s %2$s" - "%1$s सत्र%2$s: भाग %3$s" - "%1$s भाग %2$s" - diff --git a/common/res/values-ms-rMY/strings.xml b/common/res/values-ms-rMY/strings.xml new file mode 100644 index 00000000..14343683 --- /dev/null +++ b/common/res/values-ms-rMY/strings.xml @@ -0,0 +1,28 @@ + + + + + "Selesai" + "Langkau" + "M%1$s: Ep.%2$s" + "Ep.%1$s" + "M%1$s: Ep. %2$s %3$s" + "Ep. %1$s %2$s" + "%1$s <i>M%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" + diff --git a/common/res/values-ms/strings.xml b/common/res/values-ms/strings.xml deleted file mode 100644 index 06eb6173..00000000 --- a/common/res/values-ms/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Selesai" - "M%1$s: Ep.%2$s" - "Ep.%1$s" - "M%1$s: Ep. %2$s %3$s" - "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" - diff --git a/common/res/values-my-rMM/strings.xml b/common/res/values-my-rMM/strings.xml new file mode 100644 index 00000000..0e9b43cd --- /dev/null +++ b/common/res/values-my-rMM/strings.xml @@ -0,0 +1,28 @@ + + + + + "ပြီးပါပြီ" + "ကျော်ရန်" + "အတွဲ%1$s− အပိုင်း%2$s" + "အပိုင်း %1$s" + "အတွဲ%1$s− အပိုင်း %2$s %3$s" + "အပိုင်း %1$s %2$s" + "%1$s <i>အတွဲ%2$s: အပိုင်း %3$s</i>" + "%1$s <i>အပိုင်း %2$s</i>" + diff --git a/common/res/values-my/strings.xml b/common/res/values-my/strings.xml deleted file mode 100644 index 977c4315..00000000 --- a/common/res/values-my/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "ပြီးပါပြီ" - "အတွဲ%1$s− အပိုင်း%2$s" - "အပိုင်း %1$s" - "အတွဲ%1$s− အပိုင်း %2$s %3$s" - "အပိုင်း %1$s %2$s" - "%1$s အတွဲ%2$s− အပိုင်း %3$s" - "%1$s အပိုင်း %2$s" - diff --git a/common/res/values-nb/strings.xml b/common/res/values-nb/strings.xml index 8344a76b..61d36076 100644 --- a/common/res/values-nb/strings.xml +++ b/common/res/values-nb/strings.xml @@ -18,10 +18,11 @@ "Ferdig" + "Hopp over" "Sesong %1$s episode %2$s" "Episode %1$s" "Sesong %1$s: episode %2$s%3$s" "Episode %1$s%2$s" - "%1$s Sesong %2$s: Episode %3$s" - "%1$s Episode %2$s" + "%1$s <i>S%2$s: Episode %3$s</i>" + "%1$s <i>Episode %2$s</i>" diff --git a/common/res/values-ne-rNP/strings.xml b/common/res/values-ne-rNP/strings.xml new file mode 100644 index 00000000..064fc63b --- /dev/null +++ b/common/res/values-ne-rNP/strings.xml @@ -0,0 +1,28 @@ + + + + + "सम्पन्न भयो" + "छाड्नुहोस्" + "सिजन %1$s: एपिसोड %2$s" + "एपिसोड %1$s" + "सिजन %1$s: एपिसोड %2$s %3$s" + "एपिसोड %1$s %2$s" + "%1$s <i>S%2$s: एपिसोड %3$s</i>" + "%1$s <i>एपिसोड %2$s</i>" + diff --git a/common/res/values-ne/strings.xml b/common/res/values-ne/strings.xml deleted file mode 100644 index 4f3d24c1..00000000 --- a/common/res/values-ne/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "सम्पन्न भयो" - "सिजन %1$s: एपिसोड %2$s" - "एपिसोड %1$s" - "सिजन %1$s: एपिसोड %2$s %3$s" - "एपिसोड %1$s %2$s" - "%1$s सिजन %2$s: एपिसोड %3$s" - "%1$s एपिसोड %2$s" - diff --git a/common/res/values-nl/strings.xml b/common/res/values-nl/strings.xml index 4e5a1011..3b601618 100644 --- a/common/res/values-nl/strings.xml +++ b/common/res/values-nl/strings.xml @@ -18,10 +18,11 @@ "Gereed" + "Overslaan" "S %1$s afl. %2$s" "Afl. %1$s" "S. %1$s: afl. %2$s %3$s" "Afl. %1$s %2$s" - "%1$s S%2$s: Afl. %3$s" - "%1$s Afl. %2$s" + "%1$s <i>S%2$s, A%3$s</i>" + "%1$s <i>A %2$s</i>" diff --git a/common/res/values-pa-rIN/strings.xml b/common/res/values-pa-rIN/strings.xml new file mode 100644 index 00000000..c32ca9b7 --- /dev/null +++ b/common/res/values-pa-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "ਹੋ ਗਿਆ" + "ਛੱਡੋ" + "ਸੀਜ਼ਨ%1$s: ਐਪੀਸੋਡ%2$s" + "ਐਪੀਸੋਡ%1$s" + "ਸੀਜ਼ਨ%1$s: ਐਪੀਸੋਡ %2$s %3$s" + "ਐਪੀਸੋਡ %1$s %2$s" + "%1$s <i>S%2$s: ਐਪੀਸੋਡ %3$s</i>" + "%1$s <i>ਐਪੀਸੋਡ %2$s</i>" + diff --git a/common/res/values-pl/strings.xml b/common/res/values-pl/strings.xml index 5161e3ba..baa6a78c 100644 --- a/common/res/values-pl/strings.xml +++ b/common/res/values-pl/strings.xml @@ -18,10 +18,11 @@ "Gotowe" + "Pomiń" "Sez. %1$s: odc. %2$s" "Odc. %1$s" "Sez. %1$s, odc. %2$s: %3$s" "Odc. %1$s: %2$s" - "%1$s – sez. %2$s, odc. %3$s" - "%1$s, odc. %2$s" + "%1$s <i>sez. %2$s: odc. %3$s</i>" + "%1$s <i>odc. %2$s</i>" diff --git a/common/res/values-pt-rPT/strings.xml b/common/res/values-pt-rPT/strings.xml index badaae27..9d22b24f 100644 --- a/common/res/values-pt-rPT/strings.xml +++ b/common/res/values-pt-rPT/strings.xml @@ -18,10 +18,11 @@ "Concluído" + "Ignorar" "T. %1$s: ep. %2$s" "Ep. %1$s" "T%1$s: ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s T%2$s: ep. %3$s" - "%1$s ep. %2$s" + "%1$s <i>T%2$s: ep. %3$s</i>" + "%1$s <i>ep. %2$s</i>" diff --git a/common/res/values-pt/strings.xml b/common/res/values-pt/strings.xml index 0fc3e097..e5aed96d 100644 --- a/common/res/values-pt/strings.xml +++ b/common/res/values-pt/strings.xml @@ -18,10 +18,11 @@ "Concluído" + "Pular" "T%1$s: Ep.%2$s" "Ep.%1$s" "T%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s T%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>T %2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-ro/strings.xml b/common/res/values-ro/strings.xml index ae6fc497..370c71d8 100644 --- a/common/res/values-ro/strings.xml +++ b/common/res/values-ro/strings.xml @@ -18,10 +18,11 @@ "Terminat" + "Omiteți" "Sezonul %1$s, episodul %2$s" "Episodul %1$s" "Sezonul %1$s, episodul %2$s: %3$s" "Episodul %1$s: %2$s" - "%1$s, sezonul %2$s, episodul %3$s" - "%1$s, episodul %2$s" + "%1$s, <i>sezonul %2$s, episodul %3$s</i>" + "%1$s, <i>episodul %2$s</i>" diff --git a/common/res/values-ru/strings.xml b/common/res/values-ru/strings.xml index 53c341aa..afdbcd5a 100644 --- a/common/res/values-ru/strings.xml +++ b/common/res/values-ru/strings.xml @@ -18,10 +18,11 @@ "Готово" + "Пропустить" "Сезон %1$s, серия %2$s" "Серия %1$s" "Сезон %1$s: серия %2$s, \"%3$s\"" "Серия %1$s, \"%2$s\"" - "%1$s, сезон %2$s, серия %3$s" - "%1$s, серия %2$s" + "%1$s <i>сезон %2$s, серия %3$s</i>" + "%1$s <i>серия %2$s</i>" diff --git a/common/res/values-si-rLK/strings.xml b/common/res/values-si-rLK/strings.xml new file mode 100644 index 00000000..59d85a69 --- /dev/null +++ b/common/res/values-si-rLK/strings.xml @@ -0,0 +1,28 @@ + + + + + "නිමයි" + "මඟ හරින්න" + "වාරය%1$sකථාංගය%2$s" + "කථාංගය %1$s" + "වාරය%1$s: කථාංගය %2$s %3$s" + "කථාංගය %1$s %2$s" + "%1$s <i>වා%2$s: කථාංග. %3$s</i>" + "%1$s <i>කථාංග. %2$s</i>" + diff --git a/common/res/values-si/strings.xml b/common/res/values-si/strings.xml deleted file mode 100644 index 85fc1f94..00000000 --- a/common/res/values-si/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "නිමයි" - "වාරය%1$sකථාංගය%2$s" - "කථාංගය %1$s" - "වාරය%1$s: කථාංගය %2$s %3$s" - "කථාංගය %1$s %2$s" - "%1$s වාරය%2$s: කථාංගය. %3$s" - "%1$s කථාංගය. %2$s" - diff --git a/common/res/values-sk/strings.xml b/common/res/values-sk/strings.xml index 1d1a673f..4d6df3cb 100644 --- a/common/res/values-sk/strings.xml +++ b/common/res/values-sk/strings.xml @@ -18,10 +18,11 @@ "Hotovo" + "Preskočiť" "S%1$s: ep.%2$s" "E%1$s" "S %1$s: ep. %2$s%3$s" "Ep. %1$s%2$s" - "%1$s %2$s. sezóna: %3$s. epizóda" - "%1$s %2$s. epizóda" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-sl/strings.xml b/common/res/values-sl/strings.xml index 26ec614d..603d12de 100644 --- a/common/res/values-sl/strings.xml +++ b/common/res/values-sl/strings.xml @@ -18,10 +18,11 @@ "Končano" + "Preskoči" "%1$s. sezona: %2$s. epizoda" "%1$s. epizoda" "%1$s. sezona – %2$s. epizoda: %3$s" "%1$s. epizoda: %2$s" - "%1$s%2$s. sezona: %3$s. epizoda" - "%1$s%2$s. epizoda" + "%1$s <i>%2$s. sezona: %3$s.epizoda</i>" + "%1$s <i>%2$s.epizoda</i>" diff --git a/common/res/values-sq-rAL/strings.xml b/common/res/values-sq-rAL/strings.xml new file mode 100644 index 00000000..ac188984 --- /dev/null +++ b/common/res/values-sq-rAL/strings.xml @@ -0,0 +1,28 @@ + + + + + "U krye" + "Kapërce" + "S%1$s: Ep.%2$s" + "Ep.%1$s" + "S%1$s: Ep. %2$s %3$s" + "Ep. %1$s %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" + diff --git a/common/res/values-sr/strings.xml b/common/res/values-sr/strings.xml index ef7f9ee5..b64eb7f7 100644 --- a/common/res/values-sr/strings.xml +++ b/common/res/values-sr/strings.xml @@ -18,10 +18,11 @@ "Готово" + "Прескочи" "Серијал: %1$s: Епизода: %2$s" "%1$s. епизода" "%1$s. серијал: %2$s. епизода, %3$s" "%1$s. епизода, %2$s" - "%1$s %2$s. серијал: %3$s. епизода" - "%1$s %2$s. епизода" + "%1$s <i>%2$s. серијал: %3$s. епизода</i>" + "%1$s <i> %2$s. епозода</i>" diff --git a/common/res/values-sv/strings.xml b/common/res/values-sv/strings.xml index 9911a0de..14741001 100644 --- a/common/res/values-sv/strings.xml +++ b/common/res/values-sv/strings.xml @@ -18,10 +18,11 @@ "Klar" + "Hoppa över" "Säsong %1$s, avsnitt %2$s" "Avsnitt %1$s" "Säsong %1$s: avsnitt %2$s%3$s" "Avsnitt %1$s%2$s" - "%1$s Säsong%2$s: avsnitt %3$s" - "%1$s avsnitt. %2$s" + "%1$s <i>säsong %2$s, avsnitt %3$s</i>" + "%1$s <i>avsnitt %2$s</i>" diff --git a/common/res/values-sw/strings.xml b/common/res/values-sw/strings.xml index 7d44cedf..0b6883f6 100644 --- a/common/res/values-sw/strings.xml +++ b/common/res/values-sw/strings.xml @@ -18,10 +18,11 @@ "Nimemaliza" + "Ruka" "Msimu wa %1$s: Kipindi cha %2$s" "Kipindi cha %1$s" "Msimu wa %1$s: Kipindi cha %2$s %3$s" "Kipindi cha %1$s %2$s" - "%1$sMsimu wa %2$s: Kipindi cha %3$s" - "%1$s Kipindi cha %2$s" + "%1$s <i>Msimu wa %2$s: Kipindi cha %3$s</i>" + "%1$s <i>Kipindi cha %2$s</i>" diff --git a/common/res/values-ta-rIN/strings.xml b/common/res/values-ta-rIN/strings.xml new file mode 100644 index 00000000..7e3faa13 --- /dev/null +++ b/common/res/values-ta-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "முடிந்தது" + "தவிர்" + "சீ%1$s: எபி.%2$s" + "எபி.%1$s" + "சீ%1$s: எபி. %2$s %3$s" + "எபி. %1$s %2$s" + "%1$s <i>சீ%2$s: எபி. %3$s</i>" + "%1$s <i>எபி. %2$s</i>" + diff --git a/common/res/values-ta/strings.xml b/common/res/values-ta/strings.xml deleted file mode 100644 index bc2eed62..00000000 --- a/common/res/values-ta/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "முடிந்தது" - "சீ%1$s: எபி.%2$s" - "எபி.%1$s" - "சீ%1$s: எபி. %2$s %3$s" - "எபி. %1$s %2$s" - "%1$s சீ%2$s: எபி. %3$s" - "%1$s எபி. %2$s" - diff --git a/common/res/values-te-rIN/strings.xml b/common/res/values-te-rIN/strings.xml new file mode 100644 index 00000000..066abcc5 --- /dev/null +++ b/common/res/values-te-rIN/strings.xml @@ -0,0 +1,28 @@ + + + + + "పూర్తయింది" + "దాటవేయి" + "సీ%1$s: ఎపి.%2$s" + "ఎపి.%1$s" + "సీ%1$s: ఎపి. %2$s %3$s" + "ఎపి. %1$s %2$s" + "%1$s <i>సీ.%2$s: ఎపి. %3$s</i>" + "%1$s <i>ఎపి. %2$s</i>" + diff --git a/common/res/values-te/strings.xml b/common/res/values-te/strings.xml deleted file mode 100644 index 80df229d..00000000 --- a/common/res/values-te/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "పూర్తయింది" - "సీ%1$s: ఎపి.%2$s" - "ఎపి.%1$s" - "సీ%1$s: ఎపి. %2$s %3$s" - "ఎపి. %1$s %2$s" - "%1$s సీ%2$s: ఎపి. %3$s" - "%1$s ఎపి. %2$s" - diff --git a/common/res/values-th/strings.xml b/common/res/values-th/strings.xml index 5ff9f1ff..a9f94b70 100644 --- a/common/res/values-th/strings.xml +++ b/common/res/values-th/strings.xml @@ -18,10 +18,11 @@ "เสร็จสิ้น" + "ข้าม" "ซีซัน %1$s: ตอนที่ %2$s" "ตอนที่ %1$s" "ซีซัน %1$s: ตอนที่ %2$s %3$s" "ตอนที่ %1$s %2$s" - "%1$s ซีซัน %2$s: ตอน %3$s" - "%1$s ตอน %2$s" + "%1$s <i>ซีซัน %2$s: ตอน %3$s</i>S" + "%1$s <i>ตอน %2$s</i>" diff --git a/common/res/values-tl/strings.xml b/common/res/values-tl/strings.xml index e6b8bbbc..24e13951 100644 --- a/common/res/values-tl/strings.xml +++ b/common/res/values-tl/strings.xml @@ -18,10 +18,11 @@ "Tapos Na" + "Laktawan" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values-tr/strings.xml b/common/res/values-tr/strings.xml index c25c834f..56e375fc 100644 --- a/common/res/values-tr/strings.xml +++ b/common/res/values-tr/strings.xml @@ -18,10 +18,11 @@ "Bitti" + "Atla" "S%1$s: Böl.%2$s" "Böl.%1$s" "S%1$s: %2$s. Bölüm %3$s" "%1$s. Bölüm %2$s" - "%1$s %2$s.S: %3$s. Böl." - "%1$s %2$s. Böl." + "%1$s <i>S%2$s: Böl. %3$s</i>" + "%1$s <i>Böl. %2$s</i>" diff --git a/common/res/values-uk/strings.xml b/common/res/values-uk/strings.xml index 9b67cf4a..10bfb267 100644 --- a/common/res/values-uk/strings.xml +++ b/common/res/values-uk/strings.xml @@ -18,10 +18,11 @@ "Готово" + "Пропустити" "Сезон %1$s: серія %2$s" "Серія %1$s" "Сезон %1$s: серія %2$s \"%3$s\"" "Серія %1$s \"%2$s\"" - "\"%1$s\": сезон %2$s, серія %3$s" - "\"%1$s\": серія %2$s" + "\"%1$s\": <i>сезон %2$s, серія %3$s</i>" + "\"%1$s\": <i>серія %2$s</i>" diff --git a/common/res/values-ur-rPK/strings.xml b/common/res/values-ur-rPK/strings.xml new file mode 100644 index 00000000..9edebcf0 --- /dev/null +++ b/common/res/values-ur-rPK/strings.xml @@ -0,0 +1,28 @@ + + + + + "ہو گیا" + "نظر انداز کریں" + "سیزن%1$s: ایپی سوڈ۔%2$s" + "ایپی سوڈ %1$s" + "سیزن %1$s: ایپی سوڈ %2$s %3$s" + "ایپی سوڈ %1$s %2$s" + "‏%1$s <i>سیزن%2$s: ایپی سوڈ %3$s</i>" + "‏%1$s <i>ایپی سوڈ %2$s</i>" + diff --git a/common/res/values-ur/strings.xml b/common/res/values-ur/strings.xml deleted file mode 100644 index b04fb3c3..00000000 --- a/common/res/values-ur/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "ہو گیا" - "سیزن%1$s: ایپی سوڈ۔%2$s" - "ایپی سوڈ %1$s" - "سیزن %1$s: ایپی سوڈ %2$s %3$s" - "ایپی سوڈ %1$s %2$s" - "%1$s سیزن %2$s: ایپی سوڈ %3$s" - "%1$s ایپی سوڈ %2$s" - diff --git a/common/res/values-uz-rUZ/strings.xml b/common/res/values-uz-rUZ/strings.xml new file mode 100644 index 00000000..1aef80d5 --- /dev/null +++ b/common/res/values-uz-rUZ/strings.xml @@ -0,0 +1,28 @@ + + + + + "Tayyor" + "Tashlab ketish" + "%1$s-fasl: %2$s-qism" + "%1$s-qism" + "%1$s-fasl: %2$s-qism – “%3$s”" + "%1$s-qism – “%2$s”" + "%1$s <i>%2$s-fasl %3$s-qism</i>" + "%1$s <i>%2$s-qism</i>" + diff --git a/common/res/values-uz/strings.xml b/common/res/values-uz/strings.xml deleted file mode 100644 index ffa1cf9c..00000000 --- a/common/res/values-uz/strings.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - "Tayyor" - "%1$s-fasl: %2$s-qism" - "%1$s-qism" - "%1$s-fasl: %2$s-qism – “%3$s”" - "%1$s-qism – “%2$s”" - "%1$s, %2$s-fasl %3$s-qism" - "%1$s, %2$s-qism" - diff --git a/common/res/values-vi/strings.xml b/common/res/values-vi/strings.xml index 2852bb10..b7f209b3 100644 --- a/common/res/values-vi/strings.xml +++ b/common/res/values-vi/strings.xml @@ -18,10 +18,11 @@ "Xong" + "Bỏ qua" "Phần%1$s: Tập%2$s" "Tập%1$s" "Phần %1$s: Tập %2$s %3$s" "Tập %1$s %2$s" - "%1$s P%2$s: Tập %3$s" - "%1$s Tập %2$s" + "%1$s <i>S%2$s: Tập %3$s</i>" + "%1$s <i>Tập %2$s</i>" diff --git a/common/res/values-zh-rCN/strings.xml b/common/res/values-zh-rCN/strings.xml index 25e13379..1eb99dbe 100644 --- a/common/res/values-zh-rCN/strings.xml +++ b/common/res/values-zh-rCN/strings.xml @@ -18,10 +18,11 @@ "完成" + "跳过" "第 %1$s 季:第 %2$s 集" "第 %1$s 集" "第 %1$s 季第 %2$s 集:%3$s" "第 %1$s 集:%2$s" - "《%1$s》第 %2$s 季:第 %3$s 集" - "《%1$s》第 %2$s 集" + "《%1$s》第 %2$s 季:第 %3$s 集<i></i>" + "《%1$s》第 %2$s 集<i></i>" diff --git a/common/res/values-zh-rHK/strings.xml b/common/res/values-zh-rHK/strings.xml index ed323c6a..346db42b 100644 --- a/common/res/values-zh-rHK/strings.xml +++ b/common/res/values-zh-rHK/strings.xml @@ -18,10 +18,11 @@ "完成" + "略過" "第 %1$s 季:第 %2$s 集" "第 %1$s 集" "第 %1$s 季:第 %2$s%3$s" "第 %1$s%2$s" - "《%1$s》第 %2$s 季:第 %3$s 集" - "《%1$s》第 %2$s 集" + "《%1$s》<i>第 %2$s 季:第 %3$s 集</i>" + "《%1$s》<i>第 %2$s 集</i>" diff --git a/common/res/values-zh-rTW/strings.xml b/common/res/values-zh-rTW/strings.xml index 25e13379..7cc3e3d3 100644 --- a/common/res/values-zh-rTW/strings.xml +++ b/common/res/values-zh-rTW/strings.xml @@ -18,10 +18,11 @@ "完成" + "略過" "第 %1$s 季:第 %2$s 集" "第 %1$s 集" "第 %1$s 季第 %2$s 集:%3$s" "第 %1$s 集:%2$s" - "《%1$s》第 %2$s 季:第 %3$s 集" - "《%1$s》第 %2$s 集" + "《%1$s》<i>第 %2$s 季:第 %3$s 集</i>" + "《%1$s》<i>第 %2$s 集</i>" diff --git a/common/res/values-zu/strings.xml b/common/res/values-zu/strings.xml index 4574ac01..227a1919 100644 --- a/common/res/values-zu/strings.xml +++ b/common/res/values-zu/strings.xml @@ -18,10 +18,11 @@ "Kwenziwe" + "Yeqa" "S%1$s: Ep.%2$s" "Ep.%1$s" "S%1$s: Ep. %2$s %3$s" "Ep. %1$s %2$s" - "%1$s S%2$s: Ep. %3$s" - "%1$s Ep. %2$s" + "%1$s <i>S%2$s: Ep. %3$s</i>" + "%1$s <i>Ep. %2$s</i>" diff --git a/common/res/values/dimens.xml b/common/res/values/dimens.xml index 9d6e0e67..be534898 100644 --- a/common/res/values/dimens.xml +++ b/common/res/values/dimens.xml @@ -16,10 +16,6 @@ --> - - 300dp - -300dp - 600dp 476dp @@ -27,10 +23,7 @@ 24dp 24dp 220dp - 40dp - 40dp 12dp - 5dp 1 10dp diff --git a/common/res/values/strings.xml b/common/res/values/strings.xml index e5b9b625..ec029136 100644 --- a/common/res/values/strings.xml +++ b/common/res/values/strings.xml @@ -17,16 +17,32 @@ Done - + Skip + S%1$s: Ep.%2$s - + Ep.%1$s - + S%1$s: Ep. %2$s %3$s - + Ep. %1$s %2$s - - %1$s S%2$s: Ep. %3$s - - %1$s Ep. %2$s + + %1$s ]]>S%2$s: Ep. %3$s]]> + + %1$s ]]>Ep. %2$s]]> diff --git a/common/res/values/styles.xml b/common/res/values/styles.xml index 3c3c71c9..50ef08f1 100644 --- a/common/res/values/styles.xml +++ b/common/res/values/styles.xml @@ -57,15 +57,9 @@ 16sp - - diff --git a/common/res/values/themes.xml b/common/res/values/themes.xml index 598ae9a5..6016daf9 100644 --- a/common/res/values/themes.xml +++ b/common/res/values/themes.xml @@ -31,7 +31,6 @@ @style/Widget.Setup.GuidedActionItemDescriptionStyle @color/common_setup_action_background 0dp - @style/Widget.Setup.GuidedActionsListStyle @drawable/setup_selector_background @android:color/transparent @android:color/transparent diff --git a/common/src/com/android/tv/common/SharedPreferencesUtils.java b/common/src/com/android/tv/common/SharedPreferencesUtils.java index fb3d9b56..140c4e6f 100644 --- a/common/src/com/android/tv/common/SharedPreferencesUtils.java +++ b/common/src/com/android/tv/common/SharedPreferencesUtils.java @@ -35,6 +35,11 @@ public final class SharedPreferencesUtils { public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner"; public static final String SHARED_PREF_EPG = "epg_preferences"; public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings"; + /** No need to pre-initialize. It's used only on the worker thread. */ + public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris"; + /** Stores the UI related settings */ + public static final String SHARED_PREF_UI_SETTINGS = "ui_settings"; + public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms"; private static boolean sInitializeCalled; @@ -63,6 +68,7 @@ public final class SharedPreferencesUtils { context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE); context.getSharedPreferences(SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); return null; } diff --git a/common/src/com/android/tv/common/TvCommonUtils.java b/common/src/com/android/tv/common/TvCommonUtils.java index a88dd3a8..c391ad24 100644 --- a/common/src/com/android/tv/common/TvCommonUtils.java +++ b/common/src/com/android/tv/common/TvCommonUtils.java @@ -23,6 +23,8 @@ import android.media.tv.TvInputInfo; * Util class for common use in TV app and inputs. */ public final class TvCommonUtils { + private static Boolean sRunningInTest; + private TvCommonUtils() { } /** @@ -58,12 +60,15 @@ public final class TvCommonUtils { * the usual devices even the application is running in tests. We need to figure it out by * checking whether the class in tv-tests-common module can be loaded or not. */ - public static boolean isRunningInTest() { - try { - Class.forName("com.android.tv.testing.Utils"); - return true; - } catch (ClassNotFoundException e) { - return false; + public static synchronized boolean isRunningInTest() { + if (sRunningInTest == null) { + try { + Class.forName("com.android.tv.testing.Utils"); + sRunningInTest = true; + } catch (ClassNotFoundException e) { + sRunningInTest = false; + } } + return sRunningInTest; } } diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java index 5694cda7..8b3c06f1 100644 --- a/common/src/com/android/tv/common/TvContentRatingCache.java +++ b/common/src/com/android/tv/common/TvContentRatingCache.java @@ -43,6 +43,7 @@ public final class TvContentRatingCache implements MemoryManageable { return INSTANCE; } + // @GuardedBy("TvContentRatingCache.this") private final Map mRatingsMultiMap = new ArrayMap<>(); /** @@ -51,7 +52,7 @@ public final class TvContentRatingCache implements MemoryManageable { * Returns {@code null} if the string is empty or contains no valid ratings. */ @Nullable - public TvContentRating[] getRatings(String commaSeparatedRatings) { + public synchronized TvContentRating[] getRatings(String commaSeparatedRatings) { if (TextUtils.isEmpty(commaSeparatedRatings)) { return null; } @@ -136,7 +137,7 @@ public final class TvContentRatingCache implements MemoryManageable { } @Override - public void performTrimMemory(int level) { + public synchronized void performTrimMemory(int level) { mRatingsMultiMap.clear(); } diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java index d47aa603..62c88ead 100644 --- a/common/src/com/android/tv/common/feature/CommonFeatures.java +++ b/common/src/com/android/tv/common/feature/CommonFeatures.java @@ -17,7 +17,6 @@ package com.android.tv.common.feature; import static com.android.tv.common.feature.FeatureUtils.AND; -import static com.android.tv.common.feature.FeatureUtils.OR; import static com.android.tv.common.feature.TestableFeature.createTestableFeature; /** @@ -34,7 +33,7 @@ public class CommonFeatures { * DVR API is introduced in N, it only works when app runs as a system app. */ public static final TestableFeature DVR = createTestableFeature( - AND(OR(Sdk.N_PRE_2_OR_HIGHER, Sdk.AT_LEAST_N), SystemAppFeature.SYSTEM_APP_FEATURE)); + AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE)); /** * ENABLE_RECORDING_REGARDLESS_OF_STORAGE_STATUS diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java index 46a681f8..9f99a64f 100644 --- a/common/src/com/android/tv/common/feature/Sdk.java +++ b/common/src/com/android/tv/common/feature/Sdk.java @@ -18,50 +18,18 @@ package com.android.tv.common.feature; import android.content.Context; import android.os.Build; -import android.support.v4.os.BuildCompat; /** * Holder for SDK version features */ public class Sdk { - - public static final Feature N_PRE_2_OR_HIGHER = - new SdkPreviewVersionFeature(Build.VERSION_CODES.M, 2, true); - - private static class SdkPreviewVersionFeature implements Feature { - private final int mVersionCode; - private final int mPreviewCode; - private final boolean mAllowHigherPreview; - - private SdkPreviewVersionFeature(int versionCode, int previewCode, - boolean allowHigerPreview) { - mVersionCode = versionCode; - mPreviewCode = previewCode; - mAllowHigherPreview = allowHigerPreview; - } - - @Override - public boolean isEnabled(Context context) { - try { - if (mAllowHigherPreview) { - return Build.VERSION.SDK_INT == mVersionCode - && Build.VERSION.PREVIEW_SDK_INT >= mPreviewCode; - } else { - return Build.VERSION.SDK_INT == mVersionCode - && Build.VERSION.PREVIEW_SDK_INT == mPreviewCode; + public static final Feature AT_LEAST_N = + new Feature() { + @Override + public boolean isEnabled(Context context) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; } - } catch (NoSuchFieldError e) { - return false; - } - } - } - - public static final Feature AT_LEAST_N = new Feature() { - @Override - public boolean isEnabled(Context context) { - return BuildCompat.isAtLeastN(); - } - }; + }; private Sdk() {} } diff --git a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java index a4a79b38..881f53d6 100644 --- a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java +++ b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java @@ -19,7 +19,6 @@ package com.android.tv.common.feature; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; - import com.android.tv.common.SharedPreferencesUtils; /** diff --git a/common/src/com/android/tv/common/feature/TestableFeature.java b/common/src/com/android/tv/common/feature/TestableFeature.java index a02877ec..d7e707a1 100644 --- a/common/src/com/android/tv/common/feature/TestableFeature.java +++ b/common/src/com/android/tv/common/feature/TestableFeature.java @@ -36,14 +36,29 @@ public class TestableFeature implements Feature { private final Feature mDelegate; private Boolean mTestValue = null; + /** + * Creates testable feature. + */ public static TestableFeature createTestableFeature(Feature delegate) { return new TestableFeature(delegate); } + /** + * Creates testable feature with initial value. + */ + public static TestableFeature createTestableFeature(Feature delegate, Boolean initialValue) { + return new TestableFeature(delegate, initialValue); + } + private TestableFeature(Feature delegate) { mDelegate = delegate; } + private TestableFeature(Feature delegate, Boolean initialValue) { + mDelegate = delegate; + mTestValue = initialValue; + } + @VisibleForTesting public void enableForTest() { if (!TvCommonUtils.isRunningInTest()) { diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java index bcaefec9..88159da9 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java @@ -72,9 +72,6 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { } // gridView Alignment VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView(); - // Workaround of b/28274171 - // TODO: Remove the following line once b/28274171 is resolved. - gridView.setFocusable(true); int offset = getResources().getDimensionPixelOffset( R.dimen.setup_guidedactions_selector_margin_top); gridView.setWindowAlignmentOffset(offset); @@ -86,8 +83,6 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { ViewGroup group = (ViewGroup) view.findViewById(R.id.content_frame); group.setClipChildren(false); group.setClipToPadding(false); - // Workaround b/26205201 - view.findViewById(R.id.guidedactions_list2).setFocusable(false); return view; } diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java index 63247481..b9ad4657 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java @@ -34,6 +34,7 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { private static final boolean DEBUG = false; public static final int ACTION_DONE = Integer.MAX_VALUE; + public static final int ACTION_SKIP = ACTION_DONE - 1; private static final String CONTENT_FRAGMENT_TAG = "content_fragment"; @@ -53,7 +54,12 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { } if (needsDoneButton()) { setOnClickAction(view.findViewById(R.id.button_done), getActionCategory(), ACTION_DONE); - } else { + } + if (needsSkipButton()) { + view.findViewById(R.id.button_skip).setVisibility(View.VISIBLE); + setOnClickAction(view.findViewById(R.id.button_skip), getActionCategory(), ACTION_SKIP); + } + if (!needsDoneButton() && !needsSkipButton()) { View doneButtonContainer = view.findViewById(R.id.done_button_container); // Use content view to check layout direction while view is being created. if (getResources().getConfiguration().getLayoutDirection() @@ -90,6 +96,10 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { return true; } + protected boolean needsSkipButton() { + return false; + } + @Override protected int[] getParentIdsForDelay() { return new int[] {R.id.content_fragment, R.id.guidedactions_list}; @@ -99,4 +109,4 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { public int[] getSharedElementIds() { return new int[] {R.id.action_fragment_background, R.id.done_button_container}; } -} +} \ No newline at end of file diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java index b12b9260..39196fe3 100644 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java +++ b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java @@ -1,3 +1,5 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 1996-2007, International Business Machines Corporation and * diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java index e799ea14..6789469f 100644 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java +++ b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java @@ -1,3 +1,5 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 1996-2016, International Business Machines Corporation and * diff --git a/jni/Android.mk b/jni/Android.mk index 684830c9..39923a15 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -1,12 +1,29 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + LOCAL_PATH := $(call my-dir) # -------------------------------------------------------------- include $(CLEAR_VARS) -LOCAL_MODULE := libtunertvinput_jni +LOCAL_MODULE := libtunertvinput_jni LOCAL_SRC_FILES += tunertvinput_jni.cpp DvbManager.cpp -LOCAL_SDK_VERSION := 21 +LOCAL_SDK_VERSION := 23 LOCAL_NDK_STL_VARIANT := stlport_static LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/jni/DvbManager.cpp b/jni/DvbManager.cpp index aa4ed530..941a1fa7 100644 --- a/jni/DvbManager.cpp +++ b/jni/DvbManager.cpp @@ -42,6 +42,8 @@ DvbManager::DvbManager(JNIEnv *env, jobject) mDemuxFd(-1), mDvrFd(-1), mPatFilterFd(-1), + mDvbApiVersion(DVB_API_VERSION_UNDEFINED), + mDeliverySystemType(-1), mFeHasLock(false), mHasPendingTune(false) { jclass clazz = env->FindClass( @@ -59,16 +61,26 @@ DvbManager::~DvbManager() { } bool DvbManager::isFeLocked() { - struct pollfd pollFd; - pollFd.fd = mFeFd; - pollFd.events = POLLIN; - pollFd.revents = 0; - int poll_result = poll(&pollFd, NUM_POLLFDS, FE_POLL_TIMEOUT_MS); - if (poll_result > 0 && (pollFd.revents & POLLIN)) { - struct dvb_frontend_event kevent; - memset(&kevent, 0, sizeof(kevent)); - if (ioctl(mFeFd, FE_GET_EVENT, &kevent) == 0) { - return (kevent.status & FE_HAS_LOCK); + if (mDvbApiVersion == DVB_API_VERSION5) { + fe_status_t status; + if (ioctl(mFeFd, FE_READ_STATUS, &status) < 0) { + return false; + } + if (status & FE_HAS_LOCK) { + return true; + } + } else { + struct pollfd pollFd; + pollFd.fd = mFeFd; + pollFd.events = POLLIN; + pollFd.revents = 0; + int poll_result = poll(&pollFd, NUM_POLLFDS, FE_POLL_TIMEOUT_MS); + if (poll_result > 0 && (pollFd.revents & POLLIN)) { + struct dvb_frontend_event kevent; + memset(&kevent, 0, sizeof(kevent)); + if (ioctl(mFeFd, FE_GET_EVENT, &kevent) == 0) { + return (kevent.status & FE_HAS_LOCK); + } } } return false; @@ -78,38 +90,111 @@ int DvbManager::tune(JNIEnv *env, jobject thiz, const int frequency, const char *modulationStr, int timeout_ms) { resetExceptFe(); - struct dvb_frontend_parameters feParams; - memset(&feParams, 0, sizeof(struct dvb_frontend_parameters)); - feParams.frequency = frequency; - if (strcmp(modulationStr, "8VSB") == 0) { - feParams.u.vsb.modulation = VSB_8; - } else if (strcmp(modulationStr, "QAM256") == 0) { - feParams.u.vsb.modulation = QAM_256; - } else { - ALOGE("Unrecognized modulation mode : %s", modulationStr); - return -1; - } - - if (mHasPendingTune) { - return -1; - } if (openDvbFe(env, thiz) != 0) { return -1; } - - feParams.inversion = INVERSION_AUTO; - /* Check frontend capability */ - struct dvb_frontend_info feInfo; - if (ioctl(mFeFd, FE_GET_INFO, &feInfo) != -1) { - if (!(feInfo.caps & FE_CAN_INVERSION_AUTO)) { - // FE can't do INVERSION_AUTO, trying INVERSION_OFF instead - feParams.inversion = INVERSION_OFF; + if (mDvbApiVersion == DVB_API_VERSION_UNDEFINED) { + struct dtv_property testProps[1] = { + { .cmd = DTV_DELIVERY_SYSTEM } + }; + struct dtv_properties feProp = { + .num = 1, .props = testProps + }; + // On fugu, DVB_API_VERSION is 5 but it doesn't support FE_SET_PROPERTY. Checking the device + // support FE_GET_PROPERTY or not to determine the DVB API version is greater than 5 or not. + if (ioctl(mFeFd, FE_GET_PROPERTY, &feProp) == -1) { + ALOGD("FE_GET_PROPERTY failed, %s", strerror(errno)); + mDvbApiVersion = DVB_API_VERSION3; + } else { + mDvbApiVersion = DVB_API_VERSION5; } } - if (ioctl(mFeFd, FE_SET_FRONTEND, &feParams) != 0) { - ALOGD("Can't set Frontend : %s", strerror(errno)); - return -1; + if (mDvbApiVersion == DVB_API_VERSION5) { + struct dtv_property deliverySystemProperty = { + .cmd = DTV_DELIVERY_SYSTEM, .u.data = SYS_ATSC + }; + struct dtv_property frequencyProperty = { + .cmd = DTV_FREQUENCY, .u.data = frequency + }; + struct dtv_property modulationProperty = { .cmd = DTV_MODULATION }; + if (strncmp(modulationStr, "QAM", 3) == 0) { + modulationProperty.u.data = QAM_AUTO; + } else if (strcmp(modulationStr, "8VSB") == 0) { + modulationProperty.u.data = VSB_8; + } else { + ALOGE("Unrecognized modulation mode : %s", modulationStr); + return -1; + } + struct dtv_property tuneProperty = { .cmd = DTV_TUNE }; + + struct dtv_property props[] = { + deliverySystemProperty, frequencyProperty, modulationProperty, tuneProperty + }; + struct dtv_properties dtvProperty = { + .num = 4, .props = props + }; + + if (mHasPendingTune) { + return -1; + } + if (ioctl(mFeFd, FE_SET_PROPERTY, &dtvProperty) != 0) { + ALOGD("Can't set Frontend : %s", strerror(errno)); + return -1; + } + } else { + struct dvb_frontend_parameters feParams; + memset(&feParams, 0, sizeof(struct dvb_frontend_parameters)); + feParams.frequency = frequency; + feParams.inversion = INVERSION_AUTO; + /* Check frontend capability */ + struct dvb_frontend_info feInfo; + if (ioctl(mFeFd, FE_GET_INFO, &feInfo) != -1) { + if (!(feInfo.caps & FE_CAN_INVERSION_AUTO)) { + // FE can't do INVERSION_AUTO, trying INVERSION_OFF instead + feParams.inversion = INVERSION_OFF; + } + } + switch (feInfo.type) { + case FE_ATSC: + if (strcmp(modulationStr, "8VSB") == 0) { + feParams.u.vsb.modulation = VSB_8; + } else if (strncmp(modulationStr, "QAM", 3) == 0) { + feParams.u.vsb.modulation = QAM_AUTO; + } else { + ALOGE("Unrecognized modulation mode : %s", modulationStr); + return -1; + } + break; + case FE_OFDM: + if (strcmp(modulationStr, "8VSB") == 0) { + feParams.u.ofdm.constellation = VSB_8; + } else if (strcmp(modulationStr, "QAM16") == 0) { + feParams.u.ofdm.constellation = QAM_16; + } else if (strcmp(modulationStr, "QAM64") == 0) { + feParams.u.ofdm.constellation = QAM_64; + } else if (strcmp(modulationStr, "QAM256") == 0) { + feParams.u.ofdm.constellation = QAM_256; + } else if (strcmp(modulationStr, "QPSK") == 0) { + feParams.u.ofdm.constellation = QPSK; + } else { + ALOGE("Unrecognized modulation mode : %s", modulationStr); + return -1; + } + break; + default: + ALOGE("Unsupported delivery system."); + return -1; + } + + if (mHasPendingTune) { + return -1; + } + + if (ioctl(mFeFd, FE_SET_FRONTEND, &feParams) != 0) { + ALOGD("Can't set Frontend : %s", strerror(errno)); + return -1; + } } int lockSuccessCount = 0; @@ -238,6 +323,10 @@ int DvbManager::startTsPidFilter(JNIEnv *env, jobject thiz, int pid, int filterT return -1; } + if (mDvbApiVersion == DVB_API_VERSION5) { + ioctl(demuxFd, DMX_START, 0); + } + if (pid != PAT_PID) { mPidFilters.insert(std::pair(pid, demuxFd)); } else { @@ -337,3 +426,75 @@ int DvbManager::readTsStream(JNIEnv *env, jobject thiz, void DvbManager::setHasPendingTune(bool hasPendingTune) { mHasPendingTune = hasPendingTune; } + +int DvbManager::getDeliverySystemType(JNIEnv *env, jobject thiz) { + if (mDeliverySystemType != -1) { + return mDeliverySystemType; + } + if (mFeFd == -1) { + if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) { + ALOGD("Can't open FE file : %s", strerror(errno)); + return DELIVERY_SYSTEM_UNDEFINED; + } + } + struct dtv_property testProps[1] = { + { .cmd = DTV_DELIVERY_SYSTEM } + }; + struct dtv_properties feProp = { + .num = 1, .props = testProps + }; + mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; + if (ioctl(mFeFd, FE_GET_PROPERTY, &feProp) == -1) { + mDvbApiVersion = DVB_API_VERSION3; + if (openDvbFe(env, thiz) == 0) { + struct dvb_frontend_info info; + if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) { + switch (info.type) { + case FE_QPSK: + mDeliverySystemType = DELIVERY_SYSTEM_DVBS; + break; + case FE_QAM: + mDeliverySystemType = DELIVERY_SYSTEM_DVBC; + break; + case FE_OFDM: + mDeliverySystemType = DELIVERY_SYSTEM_DVBT; + break; + case FE_ATSC: + mDeliverySystemType = DELIVERY_SYSTEM_ATSC; + break; + default: + mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; + break; + } + } + } + } else { + mDvbApiVersion = DVB_API_VERSION5; + switch (feProp.props[0].u.data) { + case SYS_DVBT: + mDeliverySystemType = DELIVERY_SYSTEM_DVBT; + break; + case SYS_DVBT2: + mDeliverySystemType = DELIVERY_SYSTEM_DVBT2; + break; + case SYS_DVBS: + mDeliverySystemType = DELIVERY_SYSTEM_DVBS; + break; + case SYS_DVBS2: + mDeliverySystemType = DELIVERY_SYSTEM_DVBS2; + break; + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_B: + case SYS_DVBC_ANNEX_C: + mDeliverySystemType = DELIVERY_SYSTEM_DVBC; + break; + case SYS_ATSC: + mDeliverySystemType = DELIVERY_SYSTEM_ATSC; + break; + default: + mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; + break; + } + } + return mDeliverySystemType; +} \ No newline at end of file diff --git a/jni/DvbManager.h b/jni/DvbManager.h index 7475bd41..2252332c 100644 --- a/jni/DvbManager.h +++ b/jni/DvbManager.h @@ -31,6 +31,9 @@ class DvbManager { static const int DVB_TUNE_STOP_DELAY_MS = 100 * 1000; static const int FE_POLL_TIMEOUT_MS = 100; static const int PAT_PID = 0; + static const int DVB_API_VERSION_UNDEFINED = -1; + static const int DVB_API_VERSION3 = 3; + static const int DVB_API_VERSION5 = 5; static const int FILTER_TYPE_OTHER = com_android_tv_tuner_TunerHal_FILTER_TYPE_OTHER; @@ -41,10 +44,28 @@ class DvbManager { static const int FILTER_TYPE_PCR = com_android_tv_tuner_TunerHal_FILTER_TYPE_PCR; + static const int DELIVERY_SYSTEM_UNDEFINED = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED; + static const int DELIVERY_SYSTEM_ATSC = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC; + static const int DELIVERY_SYSTEM_DVBC = + com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC; + static const int DELIVERY_SYSTEM_DVBS = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS; + static const int DELIVERY_SYSTEM_DVBS2 = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2; + static const int DELIVERY_SYSTEM_DVBT = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT; + static const int DELIVERY_SYSTEM_DVBT2 = + com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2; + + int mFeFd; int mDemuxFd; int mDvrFd; int mPatFilterFd; + int mDvbApiVersion; + int mDeliverySystemType; bool mFeHasLock; // Flag for pending tune request. Used for canceling the current tune operation. bool volatile mHasPendingTune; @@ -65,6 +86,7 @@ public: int startTsPidFilter(JNIEnv *env, jobject thiz, int pid, int filterType); void closeAllDvbPidFilter(); void setHasPendingTune(bool hasPendingTune); + int getDeliverySystemType(JNIEnv *env, jobject thiz); private: int openDvbFe(JNIEnv *env, jobject thiz); diff --git a/jni/minijail/Android.mk b/jni/minijail/Android.mk new file mode 100644 index 00000000..940237db --- /dev/null +++ b/jni/minijail/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +# -------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := libminijail_jni +LOCAL_SRC_FILES := minijail.cpp +LOCAL_CXX_STL := none +LOCAL_STATIC_LIBRARIES := libc++_static libminijail +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/jni/minijail/minijail.cpp b/jni/minijail/minijail.cpp new file mode 100644 index 00000000..9eebc49b --- /dev/null +++ b/jni/minijail/minijail.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "minijail.h" +#include +#include +#include + +#include +#include +#include + +#ifndef LOG_TAG +#define LOG_TAG "minijail" +#endif + +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG, __VA_ARGS__) + + +/* + * Class: com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService + * Method: nativeSetupMinijail + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_nativeSetupMinijail +(JNIEnv *, jobject, jint policyFd) { + ScopedMinijail jail{minijail_new()}; + if (!jail) { + ALOGE("Failed to create minijail"); + } + + minijail_no_new_privs(jail.get()); + minijail_log_seccomp_filter_failures(jail.get()); + minijail_use_seccomp_filter(jail.get()); + minijail_set_seccomp_filter_tsync(jail.get()); + // Transfer ownership of |policy_fd|. + minijail_parse_seccomp_filters_from_fd(jail.get(), policyFd); + minijail_enter(jail.get()); + close(policyFd); +} + +/* + * Class: com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService + * Method: nativeTestMinijail + * Signature: ()V + */ +JNIEXPORT void JNICALL +Java_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_nativeTestMinijail +(JNIEnv *, jobject) { + kill(getpid(), SIGUSR1); +} \ No newline at end of file diff --git a/jni/minijail/minijail.h b/jni/minijail/minijail.h new file mode 100644 index 00000000..cdf272c7 --- /dev/null +++ b/jni/minijail/minijail.h @@ -0,0 +1,44 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService */ + +#ifndef _Included_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService +#define _Included_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService +#ifdef __cplusplus +extern "C" { +#endif +#undef com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_DEBUG +#define com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_DEBUG 0L +#undef com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_MINIJAIL_SETUP_WAIT_TIMEOUT_MS +#define com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_MINIJAIL_SETUP_WAIT_TIMEOUT_MS 5000LL +/* + * Class: com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService + * Method: nativeSetupMinijail + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_nativeSetupMinijail + (JNIEnv *, jobject, jint); + +/* + * Class: com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService + * Method: nativeTestMinijail + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_nativeTestMinijail + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_FfmpegDecoder */ + +#ifndef _Included_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_FfmpegDecoder +#define _Included_com_android_tv_tuner_exoplayer_ffmpeg_FfmpegDecoderService_FfmpegDecoder +#ifdef __cplusplus +extern "C" { +#endif +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/jni/tunertvinput_jni.cpp b/jni/tunertvinput_jni.cpp index bcbc4c29..5b1a1615 100644 --- a/jni/tunertvinput_jni.cpp +++ b/jni/tunertvinput_jni.cpp @@ -155,3 +155,21 @@ Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune it->second->setHasPendingTune(hasPendingTune); } } + +/* + * Class: com_android_tv_tuner_TunerHal + * Method: nativeGetDeliverySystemType + * Signature: (J)I + */ +JNIEXPORT int JNICALL +Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType +(JNIEnv *env, jobject thiz, jlong deviceId) { + std::map::iterator it = sDvbManagers.find(deviceId); + if (it != sDvbManagers.end()) { + return it->second->getDeliverySystemType(env, thiz); + } else { + DvbManager *dvbManager = new DvbManager(env, thiz); + sDvbManagers.insert(std::pair(deviceId, dvbManager)); + return dvbManager->getDeliverySystemType(env, thiz); + } +} \ No newline at end of file diff --git a/jni/tunertvinput_jni.h b/jni/tunertvinput_jni.h index 4ade29e4..fcd64d50 100644 --- a/jni/tunertvinput_jni.h +++ b/jni/tunertvinput_jni.h @@ -25,6 +25,20 @@ extern "C" { #define com_android_tv_tuner_TunerHal_DEFAULT_VSB_TUNE_TIMEOUT_MS 2000L #undef com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS #define com_android_tv_tuner_TunerHal_DEFAULT_QAM_TUNE_TIMEOUT_MS 4000L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED 0L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC 1L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBC +#define com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC 2L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS 3L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2 +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2 4L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT 5L +#undef com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2 +#define com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2 6L /* * Class: com_android_tv_tuner_TunerHal * Method: nativeFinalize @@ -81,6 +95,14 @@ JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune (JNIEnv *, jobject, jlong, jboolean); +/* + * Class: com_android_tv_tuner_TunerHal + * Method: nativeGetDeliverySystemType + * Signature: (J)I + */ +JNIEXPORT int JNICALL Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType + (JNIEnv *, jobject, jlong); + #ifdef __cplusplus } #endif diff --git a/libs/exoplayer_v2.jar b/libs/exoplayer_v2.jar new file mode 100644 index 00000000..6cfcde9c Binary files /dev/null and b/libs/exoplayer_v2.jar differ diff --git a/libs/exoplayer_v2_ext_ffmpeg.jar b/libs/exoplayer_v2_ext_ffmpeg.jar new file mode 100644 index 00000000..65117267 Binary files /dev/null and b/libs/exoplayer_v2_ext_ffmpeg.jar differ diff --git a/proguard.flags b/proguard.flags index 6ffed3e2..0edd14f3 100644 --- a/proguard.flags +++ b/proguard.flags @@ -38,6 +38,9 @@ long getSize(); void close(); } +-keepclasseswithmembers class com.google.android.exoplayer2.ext.ffmpeg { + native ; +} # Keep method which is used for reflection. -keep @com.android.tv.common.annotation.UsedByReflection class * {*;} @@ -53,3 +56,4 @@ # Grpc used by epg via reflection -keep class io.grpc.internal.DnsNameResolverProvider + diff --git a/proto/channel.proto b/proto/channel.proto index 982a1aa1..b4e67e07 100644 --- a/proto/channel.proto +++ b/proto/channel.proto @@ -14,6 +14,8 @@ * limitations under the License. */ +syntax = 'proto2'; + package com.android.tv.tuner.data; option java_package = "com.android.tv.tuner.data"; @@ -45,12 +47,15 @@ message TunerChannelProto { repeated AtscCaptionTrack caption_tracks = 20; optional bool has_caption_track = 21; optional AtscServiceType service_type = 22 [default = SERVICE_TYPE_ATSC_DIGITAL_TELEVISION]; + optional bool recording_prohibited = 23; + optional string video_format = 24; } // Enum describing the types of tuner. enum TunerType { TYPE_TUNER = 0; TYPE_FILE = 1; + TYPE_NETWORK = 2; } // Enum describing the types of video stream. diff --git a/proto/track.proto b/proto/track.proto index 62b8f271..fe60fed5 100644 --- a/proto/track.proto +++ b/proto/track.proto @@ -14,6 +14,8 @@ * limitations under the License. */ +syntax = "proto2"; + package com.android.tv.tuner.data; option java_package = "com.android.tv.tuner.data"; diff --git a/res/drawable-xhdpi/cloud01.png b/res/drawable-xhdpi/cloud01.png index 2bb6a2aa..75f81ce0 100644 Binary files a/res/drawable-xhdpi/cloud01.png and b/res/drawable-xhdpi/cloud01.png differ diff --git a/res/drawable-xhdpi/cloud02.png b/res/drawable-xhdpi/cloud02.png index 35c97466..25dc3bc5 100644 Binary files a/res/drawable-xhdpi/cloud02.png and b/res/drawable-xhdpi/cloud02.png differ diff --git a/res/drawable-xhdpi/dvr_default_poster.png b/res/drawable-xhdpi/dvr_default_poster.png index 683a693d..fd752afe 100644 Binary files a/res/drawable-xhdpi/dvr_default_poster.png and b/res/drawable-xhdpi/dvr_default_poster.png differ diff --git a/res/drawable-xhdpi/dvr_default_program_art.png b/res/drawable-xhdpi/dvr_default_program_art.png deleted file mode 100644 index 6a8d68ee..00000000 Binary files a/res/drawable-xhdpi/dvr_default_program_art.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_channel_guide.png b/res/drawable-xhdpi/ic_channel_guide.png index 526b2f6e..8051202f 100644 Binary files a/res/drawable-xhdpi/ic_channel_guide.png and b/res/drawable-xhdpi/ic_channel_guide.png differ diff --git a/res/drawable-xhdpi/ic_delete_32dp.png b/res/drawable-xhdpi/ic_delete_32dp.png index b3e9754c..7393a653 100644 Binary files a/res/drawable-xhdpi/ic_delete_32dp.png and b/res/drawable-xhdpi/ic_delete_32dp.png differ diff --git a/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png b/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png old mode 100755 new mode 100644 index ab2949b7..594af851 Binary files a/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png and b/res/drawable-xhdpi/ic_developer_mode_tv_white_48dp.png differ diff --git a/res/drawable-xhdpi/ic_dvr.png b/res/drawable-xhdpi/ic_dvr.png index 347db044..512ae00d 100644 Binary files a/res/drawable-xhdpi/ic_dvr.png and b/res/drawable-xhdpi/ic_dvr.png differ diff --git a/res/drawable-xhdpi/ic_dvr_cancel.png b/res/drawable-xhdpi/ic_dvr_cancel.png index bc761c70..6d6165e6 100644 Binary files a/res/drawable-xhdpi/ic_dvr_cancel.png and b/res/drawable-xhdpi/ic_dvr_cancel.png differ diff --git a/res/drawable-xhdpi/ic_dvr_cancel_32dp.png b/res/drawable-xhdpi/ic_dvr_cancel_32dp.png index 339247ef..8c834685 100644 Binary files a/res/drawable-xhdpi/ic_dvr_cancel_32dp.png and b/res/drawable-xhdpi/ic_dvr_cancel_32dp.png differ diff --git a/res/drawable-xhdpi/ic_dvr_cancel_large.png b/res/drawable-xhdpi/ic_dvr_cancel_large.png index f3686d05..4a1aed35 100644 Binary files a/res/drawable-xhdpi/ic_dvr_cancel_large.png and b/res/drawable-xhdpi/ic_dvr_cancel_large.png differ diff --git a/res/drawable-xhdpi/ic_dvr_delete.png b/res/drawable-xhdpi/ic_dvr_delete.png index bcdca673..5054af97 100644 Binary files a/res/drawable-xhdpi/ic_dvr_delete.png and b/res/drawable-xhdpi/ic_dvr_delete.png differ diff --git a/res/drawable-xhdpi/ic_error_recording.png b/res/drawable-xhdpi/ic_error_recording.png deleted file mode 100644 index 5878c3b3..00000000 Binary files a/res/drawable-xhdpi/ic_error_recording.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_error_white_48dp.png b/res/drawable-xhdpi/ic_error_white_48dp.png index e8e8ab5a..8c2cf1e5 100644 Binary files a/res/drawable-xhdpi/ic_error_white_48dp.png and b/res/drawable-xhdpi/ic_error_white_48dp.png differ diff --git a/res/drawable-xhdpi/ic_fresh.png b/res/drawable-xhdpi/ic_fresh.png deleted file mode 100644 index c1bc9583..00000000 Binary files a/res/drawable-xhdpi/ic_fresh.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_guide_lock.png b/res/drawable-xhdpi/ic_guide_lock.png index e36bd12d..b0efff1c 100644 Binary files a/res/drawable-xhdpi/ic_guide_lock.png and b/res/drawable-xhdpi/ic_guide_lock.png differ diff --git a/res/drawable-xhdpi/ic_launcher_s.png b/res/drawable-xhdpi/ic_launcher_s.png index 8fe7fd59..552992d6 100644 Binary files a/res/drawable-xhdpi/ic_launcher_s.png and b/res/drawable-xhdpi/ic_launcher_s.png differ diff --git a/res/drawable-xhdpi/ic_message_lock_no_permission.png b/res/drawable-xhdpi/ic_message_lock_no_permission.png index bb996971..8fbd256c 100644 Binary files a/res/drawable-xhdpi/ic_message_lock_no_permission.png and b/res/drawable-xhdpi/ic_message_lock_no_permission.png differ diff --git a/res/drawable-xhdpi/ic_message_lock_preview.png b/res/drawable-xhdpi/ic_message_lock_preview.png index 69a5eedf..8a99c36d 100644 Binary files a/res/drawable-xhdpi/ic_message_lock_preview.png and b/res/drawable-xhdpi/ic_message_lock_preview.png differ diff --git a/res/drawable-xhdpi/ic_pip_option_input.png b/res/drawable-xhdpi/ic_pip_option_input.png deleted file mode 100644 index 47c5006f..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_input.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_layout1.png b/res/drawable-xhdpi/ic_pip_option_layout1.png deleted file mode 100644 index 14b2602a..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_layout1.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_layout2.png b/res/drawable-xhdpi/ic_pip_option_layout2.png deleted file mode 100644 index e5d77278..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_layout2.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_layout3.png b/res/drawable-xhdpi/ic_pip_option_layout3.png deleted file mode 100644 index dfe110d0..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_layout3.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_layout4.png b/res/drawable-xhdpi/ic_pip_option_layout4.png deleted file mode 100644 index 8ab5fa45..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_layout4.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_layout5.png b/res/drawable-xhdpi/ic_pip_option_layout5.png deleted file mode 100644 index d6b53641..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_layout5.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_size.png b/res/drawable-xhdpi/ic_pip_option_size.png deleted file mode 100644 index 96fb0b0c..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_size.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_swap.png b/res/drawable-xhdpi/ic_pip_option_swap.png deleted file mode 100644 index fa2088ed..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_swap.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_pip_option_swap_audio.png b/res/drawable-xhdpi/ic_pip_option_swap_audio.png deleted file mode 100644 index a5f5431b..00000000 Binary files a/res/drawable-xhdpi/ic_pip_option_swap_audio.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_record_start.png b/res/drawable-xhdpi/ic_record_start.png index 53e3f03f..bae336c3 100644 Binary files a/res/drawable-xhdpi/ic_record_start.png and b/res/drawable-xhdpi/ic_record_start.png differ diff --git a/res/drawable-xhdpi/ic_recorded_program.png b/res/drawable-xhdpi/ic_recorded_program.png deleted file mode 100644 index fe22714e..00000000 Binary files a/res/drawable-xhdpi/ic_recorded_program.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_related_actor.png b/res/drawable-xhdpi/ic_related_actor.png deleted file mode 100644 index 9b726b93..00000000 Binary files a/res/drawable-xhdpi/ic_related_actor.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_related_search.png b/res/drawable-xhdpi/ic_related_search.png deleted file mode 100644 index aa5cd0d2..00000000 Binary files a/res/drawable-xhdpi/ic_related_search.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_schedule_32dp.png b/res/drawable-xhdpi/ic_schedule_32dp.png index b3e4bb5a..a665c6fa 100644 Binary files a/res/drawable-xhdpi/ic_schedule_32dp.png and b/res/drawable-xhdpi/ic_schedule_32dp.png differ diff --git a/res/drawable-xhdpi/ic_scheduled_recording.png b/res/drawable-xhdpi/ic_scheduled_recording.png index 520823a2..fbe2cd8a 100644 Binary files a/res/drawable-xhdpi/ic_scheduled_recording.png and b/res/drawable-xhdpi/ic_scheduled_recording.png differ diff --git a/res/drawable-xhdpi/ic_scheduled_white.png b/res/drawable-xhdpi/ic_scheduled_white.png index 49778def..40814e4b 100644 Binary files a/res/drawable-xhdpi/ic_scheduled_white.png and b/res/drawable-xhdpi/ic_scheduled_white.png differ diff --git a/res/drawable-xhdpi/ic_setup_antenna.png b/res/drawable-xhdpi/ic_setup_antenna.png deleted file mode 100644 index bb6d416e..00000000 Binary files a/res/drawable-xhdpi/ic_setup_antenna.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_setup_channels.png b/res/drawable-xhdpi/ic_setup_channels.png index 03ff8501..fcfd596a 100644 Binary files a/res/drawable-xhdpi/ic_setup_channels.png and b/res/drawable-xhdpi/ic_setup_channels.png differ diff --git a/res/drawable-xhdpi/ic_tvoption_multi_track.png b/res/drawable-xhdpi/ic_tvoption_multi_track.png index d1aff333..e0a42e74 100644 Binary files a/res/drawable-xhdpi/ic_tvoption_multi_track.png and b/res/drawable-xhdpi/ic_tvoption_multi_track.png differ diff --git a/res/drawable-xhdpi/ic_tvoption_pip.png b/res/drawable-xhdpi/ic_tvoption_pip.png index 0f78d834..e5d77278 100644 Binary files a/res/drawable-xhdpi/ic_tvoption_pip.png and b/res/drawable-xhdpi/ic_tvoption_pip.png differ diff --git a/res/drawable-xhdpi/ic_tvoption_pip_off.png b/res/drawable-xhdpi/ic_tvoption_pip_off.png deleted file mode 100644 index 6001677b..00000000 Binary files a/res/drawable-xhdpi/ic_tvoption_pip_off.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png b/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png index 0997a2a4..18a12f4e 100644 Binary files a/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png and b/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png differ diff --git a/res/drawable-xhdpi/ic_warning_gray600_36dp.png b/res/drawable-xhdpi/ic_warning_gray600_36dp.png index 7c08b8f8..8a84d3b9 100644 Binary files a/res/drawable-xhdpi/ic_warning_gray600_36dp.png and b/res/drawable-xhdpi/ic_warning_gray600_36dp.png differ diff --git a/res/drawable-xhdpi/ic_warning_white_12dp.png b/res/drawable-xhdpi/ic_warning_white_12dp.png index 7123470a..91856a98 100644 Binary files a/res/drawable-xhdpi/ic_warning_white_12dp.png and b/res/drawable-xhdpi/ic_warning_white_12dp.png differ diff --git a/res/drawable-xhdpi/ic_warning_white_18dp.png b/res/drawable-xhdpi/ic_warning_white_18dp.png index 1e69e397..13d573e1 100644 Binary files a/res/drawable-xhdpi/ic_warning_white_18dp.png and b/res/drawable-xhdpi/ic_warning_white_18dp.png differ diff --git a/res/drawable-xhdpi/ic_warning_white_32dp.png b/res/drawable-xhdpi/ic_warning_white_32dp.png index f5267b5a..86f98771 100644 Binary files a/res/drawable-xhdpi/ic_warning_white_32dp.png and b/res/drawable-xhdpi/ic_warning_white_32dp.png differ diff --git a/res/drawable-xhdpi/ic_warning_white_96dp.png b/res/drawable-xhdpi/ic_warning_white_96dp.png index de684e4e..50d1f295 100644 Binary files a/res/drawable-xhdpi/ic_warning_white_96dp.png and b/res/drawable-xhdpi/ic_warning_white_96dp.png differ diff --git a/res/drawable-xhdpi/ic_welcome_ripple_000.png b/res/drawable-xhdpi/ic_welcome_ripple_000.png index c4aac06c..2ffb55dd 100644 Binary files a/res/drawable-xhdpi/ic_welcome_ripple_000.png and b/res/drawable-xhdpi/ic_welcome_ripple_000.png differ diff --git a/res/drawable-xhdpi/tv_3a_00.png b/res/drawable-xhdpi/tv_3a_00.png deleted file mode 100644 index ad8f256d..00000000 Binary files a/res/drawable-xhdpi/tv_3a_00.png and /dev/null differ diff --git a/res/drawable-xhdpi/tv_error.png b/res/drawable-xhdpi/tv_error.png deleted file mode 100644 index 718f203b..00000000 Binary files a/res/drawable-xhdpi/tv_error.png and /dev/null differ diff --git a/res/drawable-xhdpi/tv_usb_antenna.png b/res/drawable-xhdpi/tv_usb_antenna.png deleted file mode 100644 index ff6c5cc1..00000000 Binary files a/res/drawable-xhdpi/tv_usb_antenna.png and /dev/null differ diff --git a/res/drawable/play_controls_time_indicator.xml b/res/drawable/play_controls_time_indicator.xml index 16acb107..85fee1fb 100644 --- a/res/drawable/play_controls_time_indicator.xml +++ b/res/drawable/play_controls_time_indicator.xml @@ -17,7 +17,5 @@ - diff --git a/res/drawable/playback_progress_bar.xml b/res/drawable/playback_progress_bar.xml new file mode 100644 index 00000000..2a70ec82 --- /dev/null +++ b/res/drawable/playback_progress_bar.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/priority_settings_action_item_selected.xml b/res/drawable/priority_settings_action_item_selected.xml index a1ab18ae..3e017313 100644 --- a/res/drawable/priority_settings_action_item_selected.xml +++ b/res/drawable/priority_settings_action_item_selected.xml @@ -15,12 +15,8 @@ ~ limitations under the License. --> - - - - - - - - + + + + diff --git a/res/drawable/setup_item_background.xml b/res/drawable/setup_item_background.xml deleted file mode 100644 index fb3899aa..00000000 --- a/res/drawable/setup_item_background.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - diff --git a/res/layout/activity_dvr_playback.xml b/res/layout/activity_dvr_playback.xml index 204001c7..02641d84 100644 --- a/res/layout/activity_dvr_playback.xml +++ b/res/layout/activity_dvr_playback.xml @@ -34,7 +34,11 @@ + + diff --git a/res/layout/activity_dvr_schedules.xml b/res/layout/activity_dvr_schedules.xml index 61e1f2a6..43b22866 100644 --- a/res/layout/activity_dvr_schedules.xml +++ b/res/layout/activity_dvr_schedules.xml @@ -16,6 +16,7 @@ --> diff --git a/res/layout/activity_tv.xml b/res/layout/activity_tv.xml index f766ae00..b6a0a3a3 100644 --- a/res/layout/activity_tv.xml +++ b/res/layout/activity_tv.xml @@ -23,33 +23,11 @@ android:foreground="@android:color/transparent" android:keepScreenOn="true"> - - - - - - + + + + android:background="?android:attr/selectableItemBackground"> \ No newline at end of file diff --git a/res/layout/dvr_play.xml b/res/layout/dvr_play.xml deleted file mode 100644 index 4df13686..00000000 --- a/res/layout/dvr_play.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/dvr_recording_card_view.xml b/res/layout/dvr_recording_card_view.xml index d3808a31..53a7cf3d 100644 --- a/res/layout/dvr_recording_card_view.xml +++ b/res/layout/dvr_recording_card_view.xml @@ -16,12 +16,12 @@ - + android:layout_height="@dimen/dvr_library_card_folded_title_height"> + + + + + + + + android:layout_height="match_parent" + android:focusable="true" + android:descendantFocusability="afterDescendants"> + android:clickable="true"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/input_banner.xml b/res/layout/input_banner.xml index cd8770c8..f4035fcb 100644 --- a/res/layout/input_banner.xml +++ b/res/layout/input_banner.xml @@ -34,6 +34,7 @@ + + + + + + + + + + + + + + + diff --git a/res/layout/menu_card_action.xml b/res/layout/menu_card_action.xml index f17c5b6a..e8114b52 100644 --- a/res/layout/menu_card_action.xml +++ b/res/layout/menu_card_action.xml @@ -19,50 +19,43 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="@dimen/action_card_width" android:layout_height="@dimen/action_card_height" - android:orientation="vertical" android:focusable="true" android:background="@drawable/action_card_background"> - + android:layout_alignParentTop="true" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:layout_marginTop="6dp" + android:fontFamily="@string/condensed_font" + android:maxLines="1" + android:textColor="@color/action_card_label_color" + android:textSize="@dimen/action_card_label_font_size" /> - - - - - + + android:scaleType="fitCenter" /> diff --git a/res/layout/menu_card_app_link.xml b/res/layout/menu_card_app_link.xml index 918cb788..a7e3bd4a 100644 --- a/res/layout/menu_card_app_link.xml +++ b/res/layout/menu_card_app_link.xml @@ -33,13 +33,6 @@ android:layout_gravity="top" android:scaleType="centerCrop"/> - - - - diff --git a/res/layout/option_container.xml b/res/layout/option_container.xml index f053e660..dd8c96c7 100644 --- a/res/layout/option_container.xml +++ b/res/layout/option_container.xml @@ -22,6 +22,8 @@ android:layout_gravity="end" android:elevation="@dimen/side_panel_elevation" android:background="@color/side_panel_background" + android:translationX="@dimen/side_panel_enter_offset_x" + android:alpha="0.0" android:visibility="gone"> diff --git a/res/layout/option_fragment.xml b/res/layout/option_fragment.xml index 39f67497..4a4cbbdf 100644 --- a/res/layout/option_fragment.xml +++ b/res/layout/option_fragment.xml @@ -41,7 +41,6 @@ android:clipChildren="false" android:clipToPadding="false" android:focusable="true" - android:paddingTop="@dimen/side_panel_list_padding_top" android:paddingBottom="@dimen/side_panel_list_padding_bottom" lb:verticalMargin="@dimen/side_panel_list_vertical_margin" /> diff --git a/res/layout/option_item_action.xml b/res/layout/option_item_action.xml index a7a02b2d..6dc5b35b 100644 --- a/res/layout/option_item_action.xml +++ b/res/layout/option_item_action.xml @@ -24,19 +24,8 @@ android:focusable="true" android:background="?android:attr/selectableItemBackground" > - - + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/res/layout/option_item_channel_check.xml b/res/layout/option_item_channel_check.xml index 2817d2b9..f68c2f9a 100644 --- a/res/layout/option_item_channel_check.xml +++ b/res/layout/option_item_channel_check.xml @@ -28,7 +28,6 @@ android:id="@+id/check_box" android:layout_width="@dimen/option_item_compound_button_width" android:layout_height="match_parent" - android:layout_weight="0" android:background="@null" android:clickable="false" /> diff --git a/res/layout/option_item_channel_lock.xml b/res/layout/option_item_channel_lock.xml index f4f65f99..f7b5c9e9 100644 --- a/res/layout/option_item_channel_lock.xml +++ b/res/layout/option_item_channel_lock.xml @@ -28,7 +28,6 @@ android:id="@+id/check_box" android:layout_width="@dimen/option_item_compound_button_width" android:layout_height="match_parent" - android:layout_weight="0" android:button="@drawable/btn_lock_material_anim" android:background="@null" android:clickable="false" /> diff --git a/res/layout/option_item_check_box.xml b/res/layout/option_item_check_box.xml index 1ab2ba45..711e362b 100644 --- a/res/layout/option_item_check_box.xml +++ b/res/layout/option_item_check_box.xml @@ -28,7 +28,6 @@ android:id="@+id/check_box" android:layout_width="@dimen/option_item_compound_button_width" android:layout_height="wrap_content" - android:layout_weight="0" android:layout_gravity="center" android:background="@null" android:clickable="false" /> diff --git a/res/layout/option_item_common.xml b/res/layout/option_item_common.xml index ca0013c6..65294211 100644 --- a/res/layout/option_item_common.xml +++ b/res/layout/option_item_common.xml @@ -42,7 +42,7 @@ android:fontFamily="@string/option_item_secondary_text_font" android:textSize="@dimen/option_item_secondary_text_size" android:textColor="@color/option_item_secondary_text_color" - android:maxLines="3" + android:maxLines="@integer/option_item_description_max_lines" android:ellipsize="end" android:focusable="false" /> diff --git a/res/layout/option_item_radio_button.xml b/res/layout/option_item_radio_button.xml index 2e4fa11a..35be8c78 100644 --- a/res/layout/option_item_radio_button.xml +++ b/res/layout/option_item_radio_button.xml @@ -28,7 +28,6 @@ android:id="@+id/radio_button" android:layout_width="@dimen/option_item_compound_button_width" android:layout_height="match_parent" - android:layout_weight="0" android:background="@null" android:clickable="false" /> diff --git a/res/layout/option_item_switch.xml b/res/layout/option_item_switch.xml index 4750ebc4..7b5c6ece 100644 --- a/res/layout/option_item_switch.xml +++ b/res/layout/option_item_switch.xml @@ -33,7 +33,6 @@ android:id="@+id/switch_button" android:layout_width="@dimen/option_item_compound_button_width" android:layout_height="match_parent" - android:layout_weight="0" android:background="@null" android:clickable="false" /> diff --git a/res/layout/play_controls_contents.xml b/res/layout/play_controls_contents.xml index ac61a2d4..9afc5f3d 100644 --- a/res/layout/play_controls_contents.xml +++ b/res/layout/play_controls_contents.xml @@ -16,6 +16,7 @@ --> @@ -28,153 +29,118 @@ android:layout_alignStart="@+id/body" android:layout_marginBottom="@dimen/play_controls_time_bottom_margin" android:gravity="center" - android:singleLine="true" + android:maxLines="1" android:textColor="@color/play_controls_time_text_color" android:textSize="@dimen/play_controls_time_text_size" android:fontFamily="@string/font" /> - - - - - - - - - - - + + - - - - - - - - - - - - - - + android:layout_alignParentStart="true" + android:layout_below="@id/progress" + android:layout_marginStart="@dimen/play_controls_program_time_margin_start" + android:layout_marginTop="@dimen/play_controls_program_time_margin_top" + android:maxLines="1" + android:textColor="@color/play_controls_rec_time_text_color" + android:textSize="@dimen/play_controls_rec_time_text_size" + android:fontFamily="@string/font" /> + - - - + + + + + + + - diff --git a/res/layout/select_input_item.xml b/res/layout/select_input_item.xml index 1ff6df29..12fedca6 100644 --- a/res/layout/select_input_item.xml +++ b/res/layout/select_input_item.xml @@ -28,6 +28,7 @@ - + android:layout_gravity="start|center_vertical" /> + android:id="@+id/block_screen" /> - - + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + + +icu4j: + +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2016 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +--------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +1. ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyrighy (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +3. Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (c) 2013 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: http://code.google.com/p/lao-dictionary/ + # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt + # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary, with slight + # modifications. + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, + # are permitted provided that the following conditions are met: + # + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in + # binary form must reproduce the above copyright notice, this list of + # conditions and the following disclaimer in the documentation and/or + # other materials provided with the distribution. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +4. Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +5. Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database. diff --git a/res/transition/dvr_details_shared_element_enter_transition.xml b/res/transition/dvr_details_shared_element_enter_transition.xml new file mode 100644 index 00000000..d3fc0651 --- /dev/null +++ b/res/transition/dvr_details_shared_element_enter_transition.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/transition/dvr_details_shared_element_return_transition.xml b/res/transition/dvr_details_shared_element_return_transition.xml new file mode 100644 index 00000000..ceabca46 --- /dev/null +++ b/res/transition/dvr_details_shared_element_return_transition.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 7778e751..24d484d9 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Speelkontroles" - "Onlangse kanale" + "Kanale" "TV-opsies" - "PIP-opsies" "Speelkontroles onbeskikbaar vir hierdie kanaal" "Speel of laat wag" "Vinnig vorentoe" @@ -35,33 +34,15 @@ "Onderskrifte" "Vertoonmodus" "PIP" - "Aan" - "Af" "Multi-oudio" "Kry meer kanale" "Instellings" - "Bron" - "Ruil" - "Aan" - "Af" - "Klank" - "Hoof" - "PIP-venster" - "Uitleg" - "Regs onder" - "Regs bo" - "Links bo" - "Links onder" - "Langs mekaar" - "Grootte" - "Groot" - "Klein" - "Invoerbron" "TV (antenna/kabel)" "Geen programinligting nie" "Geen inligting nie" "Geblokkeerde kanaal" - "Onbekende taal" + "Onbekende taal" + "Onderskrifte %1$d" "Onderskrifte" "Af" "Pasmaak formatering" @@ -83,6 +64,7 @@ "SD" "Groepeer volgens" "Hierdie program word geblokkeer" + "Hierdie program is ongegradeer" "Hierdie program is %1$s gegradeer" "Die invoer steun nie outoskandering nie" "Kan nie outoskandeer vir \'%s\' begin nie" @@ -92,7 +74,6 @@ %1$d kanaal is bygevoeg "Geen kanale bygevoeg nie" - "Ontvanger" "Ouerkontroles" "Aan" "Af" @@ -108,6 +89,8 @@ "Ander lande" "Geen" "Geen" + "Ongegradeer" + "Keer ongegradeerde programme" "Geen" "Hoë beperkings" "Mediumbeperkings" @@ -124,6 +107,7 @@ "Voer jou PIN in om hierdie kanaal te kyk" "Voer jou PIN in om hierdie program te kyk" "Hierdie program is %1$s gegradeer. Voer jou PIN in om hierdie program te kyk" + "Hierdie program is ongegradeer. Voer jou PIN in om hierdie program te kyk" "Voer jou PIN in" "Skep \'n PIN om ouerkontroles op te stel" "Voer nuwe PIN in" @@ -135,22 +119,31 @@ "Die PIN is verkeerd. Probeer weer." "Probeer weer. PIN stem nie ooreen nie" + "Voer jou poskode in." + "Regstreekse kanale-program sal die poskode gebruik om \'n volledige programgids vir die TV-kanale te voorsien." + "Voer jou poskode in" + "Ongeldige poskode" "Instellings" "Pasmaak kanaallys" "Kies kanale vir jou programgids" "Kanaalbronne" "Nuwe kanale beskikbaar" "Ouerkontroles" + "Tydskuif" + "Neem op terwyl jy kyk sodat jy regstreekse programme kan onderbreek of terugspeel.\nWaarskuwing: Dit kan die lewe van die interne berging deur die intensiewe gebruik van die berging verkort." "Oopbronlisensies" - "Oopbronlisensies" + "Stuur terugvoer" "Weergawe" "Druk Regs en voer jou PIN in om hierdie kanaal te kyk" "Druk Regs en voer jou PIN in om hierdie program te kyk" + "Hierdie program is ongegradeer.\nDruk Regs en voer jou PIN in om hierdie program te kyk" "Hierdie program is %1$s gegradeer.\nDruk Regs en voer jou PIN in om hierdie program te kyk." "Gebruik die verstekprogram vir Regstreekse TV om hierdie kanaal te kyk." "Gebruik die verstekprogram vir Regstreekse TV om hierdie program te kyk." + "Hierdie program is ongegradeer.\nGebruik die verstekprogram vir Regstreekse TV om hierdie program te kyk." "Hierdie program is %1$s gegradeer.\nGebruik die verstekprogram vir Regstreekse TV om hierdie kanaal te kyk." "Program is geblokkeer" + "Hierdie program is ongegradeer" "Hierdie program is %1$s gegradeer" "Net oudio" "Swak sein" @@ -181,8 +174,6 @@ "Druk KIES"" om na die TV-kieslys te gaan." "Geen TV-invoer gevind nie" "Kan nie die TV-invoer vind nie" - "PIP word nie gesteun nie" - "Daar is geen beskikbare invoer om met PIP te wys nie" "Instemmertipe is nie geskik nie. Begin asseblief die Live TV-program vir instemmertipe-TV-invoer." "Kon nie instel nie" "Geen program is gevind om hierdie handeling te behartig nie." @@ -226,6 +217,8 @@ %1$d opnames geskeduleer %1$d opname geskeduleer + "Kanselleer opname" + "Stop opname" "Kyk" "Speel van begin af" "Speel verder" @@ -258,9 +251,6 @@ "As daar te veel programme is wat op dieselfde tyd opgeneem moet word, sal net die programme met die hoër prioriteit opgeneem word." "Stoor" "Eenmalige opnames het die hoogste prioriteit" - "Kanselleer" - "Kanselleer" - "Vergeet" "Stop" "Bekyk opnameskedule" "Net hierdie een program" @@ -270,25 +260,28 @@ "Neem eerder hierdie een op" "Kanselleer hierdie opname" "Kyk nou" + "Vee opnames uit …" "Kan opgeneem word" "Opname geskeduleer" "Opneemkonflik" "Neem tans op" "Kon nie opneem nie" - "Lees tans programme om opneemskedules te skep" - "Leesprogramme" - - + "Leesprogramme" + "Bekyk onlangse opnames" + "Die opname van %1$s is nie volledig nie." + "Die opnames van %1$s en %2$s is onvolledig." + "Die opnames van %1$s, %2$s en %3$s is onvolledig." + "Die opname van %1$s het nie klaargemaak nie weens onvoldoende bergingspasie." + "Die opnames van %1$s en %2$s het nie klaargemaak nie weens onvoldoende bergingspasie." + "Die opnames van %1$s, %2$s en %3$s het nie klaargemaak nie weens onvoldoende bergingspasie." "DVR het meer berging nodig" - "Jy sal programme met DVR kan opneem. DVR werk egter nie op die oomblik nie omdat daar nie genoeg berging op jou toestel beskikbaar is nie. Koppel asseblief \'n eksterne hardeskryf wat %1$s GB of groter is en volg die stappe om dit as toestelberging te formateer." + "Jy sal programme met DVR kan opneem. DVR werk egter nie op die oomblik nie omdat daar nie genoeg berging op jou toestel beskikbaar is nie. Koppel asseblief \'n eksterne hardeskryf wat %1$d GB of groter is en volg die stappe om dit as toestelberging te formateer." + "Te min bergingspasie" + "Hierdie program sal nie opgeneem word nie omdat daar te min bergingspasie is. Probeer \'n paar bestaande opnames uitvee." "Berging ontbreek" - "Van die berging wat DVR gebruik, ontbreek. Koppel asseblief die eksterne skyf wat jy vroeër gebruik het om DVR te heraktiveer. Andersins kan jy kies om die berging te vergeet as dit nie meer beskikbaar is nie." - "Vergeet berging?" - "Al jou opgeneemde inhoud en skedules sal verloor word." "Stop opname?" "Die opgeneemde inhoud sal gestoor word." - - + "Die opname van %1$s sal gestaak word omdat dit met hierdie program bots. Die inhoud wat opgeneem is, sal gestoor word." "Opname is geskeduleer, maar daar is botsings" "Opname het begin, maar daar is konflikte" "%1$s sal opgeneem word." @@ -306,17 +299,29 @@ "Dieselfde program is reeds geskeduleer om om %1$s opgeneem te word." "Reeds opgeneem" "Hierdie program is reeds opgeneem. Dit is in die DVR-biblioteek beskikbaar." - - - - - - - - + "Reeksopname geskeduleer" + + %1$d opnames is geskeduleer vir %2$s. + %1$d opname is geskeduleer vir %2$s. + + + %1$d opnames is geskeduleer vir %2$s. %3$d van hulle sal weens oorvleueling nie opgeneem word nie. + %1$d opname is geskeduleer vir %2$s. Dit sal weens oorvleueling nie opgeneem word nie. + + + %1$d opnames is geskeduleer vir %2$s. %3$d episodes van hierdie reeks en ander reekse sal weens oorvleueling nie opgeneem word nie. + %1$d opname is geskeduleer vir %2$s. %3$d episodes van hierdie reeks en ander reekse sal weens oorvleueling nie opgeneem word nie. + + + %1$d opnames is geskeduleer vir %2$s. 1 episode van \'n ander reeks sal weens oorvleueling nie opgeneem word nie. + %1$d opname is geskeduleer vir %2$s. 1 episode van \'n ander reeks sal weens oorvleueling nie opgeneem word nie. + + + %1$d opnames is geskeduleer vir %2$s. %3$d episodes van \'n ander reeks sal weens oorvleueling nie opgeneem word nie. + %1$d opname is geskeduleer vir %2$s. %3$d episodes van \'n ander reeks sal weens oorvleueling nie opgeneem word nie. + "Kon nie opgeneemde program vind nie." "Verwante opnames" - "(Geen programbeskrywing nie)" %1$d opnames %1$d opname @@ -336,6 +341,7 @@ "Stop om reeks op te neem?" "Opgeneemde episodes sal in die DVR-biblioteek beskikbaar bly." "Stop" + "Geen episodes is tans op die lug nie." "Geen episodes is beskikbaar nie.\nHulle sal opgeneem word sodra hulle beskikbaar is." (%1$d minute) @@ -347,4 +353,5 @@ "Vandag %1$s" "Môre %1$s" "Telling" + "Opgeneemde programme" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index bd63879b..5b181215 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -20,9 +20,8 @@ "ሞኖ" "ስቲሪዮ" "የማጫወቻ መቆጣጠሪያዎች" - "የቅርብ ጊዜ ሰርጦች" + "ሰርጦች" "የቴሌቪዥን አማራጮች" - "የፒአይፒ አማራጮች" "ለዚህ ሰርጥ የማጫወቻ መቆጣጠሪያዎች አይገኝም" "አጫውት ወይም ለአፍታ አቁም" "በፍጥነት አሳልፍ" @@ -35,33 +34,15 @@ "የተዘጉ የስዕል መግለጫዎች" "የማሳያ ሁኔታ" "ፒአይፒ" - "በርቷል" - "ጠፍቷል" "ባለብዙ ተሰሚ" "ተጨማሪ ሰርጦችን ያግኙ" "ቅንብሮች" - "ምንጭ" - "ማገላበጥ" - "በርቷል" - "ጠፍቷል" - "ድምፅ" - "ዋና" - "የፒአይፒ መስኮት" - "አቀማመጥ" - "ከታች በስተቀኝ" - "ከላይ በስተቀኝ" - "ከላይ በስተግራ" - "ከታች በስተግራ" - "ጎን ለጎን" - "መጠን" - "ትልቅ" - "ትንሽ" - "የግብዓት ምንጭ" "ቴሌቪዥን (አንቴና/ገመድ)" "ምንም የፕሮግራም መረጃ የለም" "ምንም መረጃ የለም" "የታገደ ሰርጥ" - "ያልታወቀ ቋንቋ" + "ያልታወቀ ቋንቋ" + "ዝግ መግለጫ ጽሑፎች %1$d" "የተዘጉ መግለጫ ጽሑፎች" "ጠፍቷል" "ቅርጸትን አብጅ" @@ -83,6 +64,7 @@ "ኤስዲ" "ሰብስብ በ" "ይህ ፕሮግራም ታግዷል" + "ይህ ፕሮግራም ደረጃ ያልተሰጠው ነው" "ይህ ፕሮግራም %1$s ደረጃ ነው የተሰጠው" "ግብዓቱ ራስ-ቃኝን አይደግፍም።" "ለ«%s» ራስ-ቃኝን መጀመር አልተቻለም" @@ -92,7 +74,6 @@ %1$d ሰርጦች ታክለዋል "ምንም ሰርጦች አልታከሉም" - "ማስተካከያ" "የወላጅ ቁጥጥሮች" "በርቷል" "ጠፍቷል" @@ -108,6 +89,8 @@ "ሌሎች አገሮች" "ምንም" "ምንም" + "ደረጃ አልተሰጠውም" + "ደረጃ ያልተሰጣቸው ፕሮግራሞችን አግድ" "ምንም" "ከፍተኛ ገደቦች" "መካከለኛ ገደቦች" @@ -124,6 +107,7 @@ "ይህን ሰርጥ ለመመልከት የእርስዎን ፒን ያስገቡ" "ይህን ፕሮግራም ለመመልከት የእርስዎን ፒን ያስገቡ" "ይህ ፕሮግራም የ%1$s ደረጃ ተሰጥቶታል። ይህን ፕሮግራም ለመመልከት የእርስዎን ፒን ያስገቡ።" + "ይህ ፕሮግራም ደረጃ ያልተሰጠው ነው። ይህን ፕሮግራም ለመከታተል የእርስዎን ፒን ያስገቡት።" "የእርስዎን ፒን ያስገቡ" "የወላጅ መቆጣጠሪያዎችን ለማዋቀር ፒን ይፍጠሩ።" "አዲስ ፒን ያስገቡ" @@ -135,22 +119,31 @@ "ይህ ፒን የተሳሳተ ነበር። እንደገና ይሞክሩ።" "እንደገና ይሞክሩ፣ ፒኑ አይዛመድም" + "ዚፕ ኮድዎን ያስገቡ።" + "የLive TV መተግበሪያ ለቴሌቪዥን ሰርጦቹ ሙሉ የፕሮግራም ዝርዝር ለማቅረብ ዚፕ ኮዱን ይጠቀማል።" + "ዚፕ ኮድዎን ያስገቡ" + "ልክ ያልኾነ ዚፕ ኮድ" "ቅንብሮች" "የሰርጥ ዝርዝር አብጅ" "ለፕሮግራሙ መመሪያዎ ሰርጦችን ይምረጡ" "የሰርጥ ምንጮች" "አዲስ ሰርጦች ይገኛሉ" "የወላጅ መቆጣጠሪያዎች" + "የጊዜ ሽግሽግ" + "የቀጥታ ስርጭቶችን ባሉበት ለማቆም ወይም ለማጠንጠን እየተመለከቱ ሳለ ይቅረጹ።\nማስጠንቀቂያ፦ ይሄ ባለው ከፍተኛ የማከማቻ ፍጆታ ምክንያት የውስጣዊ ማከማቻ ዕድሜውን ሊቀንስ ይችላል።" "የክፍት ምንጭ ፍቃዶች" - "የክፍት ምንጭ ፍቃዶች" + "ግብረ ምላሽ ይላኩ" "ስሪት" "ይህን ሰርጥ ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ" "ይህን ፕሮግራም ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ" + "ይህ ፕሮግራም ደረጃ ያልተሰጠው ነው።\nይህን ፕሮግራም ለመመልከት ቀኝን ይጫኑና የእርስዎን ፒን ያስገቡት" "ይህ ፕሮግራም %1$s ደረጃ ነው የተሰጠው።\nይህን ፕሮግራም ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ።" "ይህን ሰርጥ ለመመልከት ነባሪውን የቀጥተኛ ቴሌቪዥን መተግበሪያ ይጠቀሙ።" "ይህን ፕሮግራም ለመመልከት ነባሪውን የቀጥተኛ ቴሌቪዥን መተግበሪያ ይጠቀሙ።" + "ይህ ፕሮግራም ደረጃ ያልተሰጠው ነው።\nይህን ፕሮግራም ለመመልከት ነባሪውን የቀጥታ ቴሌቪዥን መተግበሪያ ይጠቀሙ።" "ይህ ፕሮግራም የ%1$s ደረጃ ነው የተሰጠው።\nይህን ፕሮግራም ለመመልከት ነባሪውን የቀጥተኛ ቴሌቪዥን መተግበሪያ ይጠቀሙ።" "ፕሮግራም ታግዷል" + "ይህ ፕሮግራም ደረጃ ያልተሰጠው ነው" "ይህ ፕሮግራም %1$s ደረጃ ነው የተሰጠው" "ኦዲዮ ብቻ" "ደካማ ምልክት" @@ -181,8 +174,6 @@ "የቴሌቪዥን ምናሌውን ለመድረስ ""ምረጥን ይጫኑ""።" "ምንም የቴሌቪዥን ግብዓት አልተገኘም" "የቴሌቪዥን ግብዓቱን ማግኘት አልተቻለም" - "ፒአይፒ አይደገፍም" - "ከፒአይፒው ጋር አብሮ ሊታይ የሚችል ግቤት አይገኝም" "የቃኚ አይነት ተገቢ አይደለም። እባክዎ የቃኚ አይነት ቴሌቪዥን ግብዓት ለማግኘት የቀጥተኛ ሰርጦች መተግበሪያውን ያስጀምሩት።" "መቃኘት አልተሳካም" "ይህን እርምጃ የሚያከናውን ምንም መተግበሪያ አልተገኘም።" @@ -226,6 +217,8 @@ %1$d ቀረጻዎች በመርሐግብር ተይዘዋል %1$d ቀረጻዎች በመርሐግብር ተይዘዋል + "መቅረጹን አስቀር" + "መቅረጽ አቁም" "ይመልከቱ" "ከመጀመሪያው አጫውት" "ከቆመበት ቀጥል" @@ -258,9 +251,6 @@ "በተመሳሳዩ ጊዜ ላይ የሚቀረጹ በጣም ብዙ ፕሮግራሞች ሲኖሩ ከፍ ያለ ቅድሚያ ያላቸው ብቻ ናቸው የሚቀረጹት።" "አስቀምጥ" "የአንድ-ጊዜ ቀረጻዎች ከፍተኛ ቅድሚያ ተሰጭነት አላቸው" - "ይቅር" - "ተወው" - "እርሳ" "አቁም" "የምዝገባ መርሐግብርን ይመልከቱ" "ይህ ነጠላ ፕሮግራም" @@ -270,25 +260,28 @@ "በምትኩ ይሄኛውን ቅረጽ" "ይህን ቀረጻ ተወው" "አሁን ይመልከቱ" + "ቀረጻዎችን ሰርዝ…" "ሊቀረጽ የሚችል" "ቀረጻ መርሐግብር ተይዞለታል" "የቀረጻ ግጭት" "መቅዳት" "መቅረጽ አልተሳካም" - "የቀረጻ መርሐግብሮችን ለመፍጠር ፕሮግራሞችን በማንበብ ላይ" - "ፕሮግራሞችን በማንበብ ላይ" - - + "ፕሮግራሞችን በማንበብ ላይ" + "የቅርብ ጊዜ ቅጂዎችን አሳይ" + "የ%1$s ቅጂ አልተጠናቀቀም።" + "የ%1$s እና %2$s ቅጂዎች አልተጠናቀቁም።" + "የ%1$s%2$s እና %3$s ቅጂዎች አልተጠናቀቁም።" + "የ%1$s ቅጂ ባለው በቂ ያልሆነ ማከማቻ ምክንያት አልተጠናቀቀም።" + "የ%1$s እና %2$s ቅጂዎች ባለው በቂ ያልሆነ ማከማቻ ምክንያት አልተጠናቀቁም።" + "የ%1$s%2$s እና %3$s ቅጂዎች ባለው በቂ ያልሆነ ማከማቻ ምክንያት አልተጠናቀቁም።" "ዲቪአር ተጨማሪ ማከማቻ ያስፈልገዋል" - "ፕሮግራሞችን በዲቪአር መቅረጽ ይችላሉ። ይሁንና አሁን ዲቪአር እንዲሰራ በመሣሪያዎ ላይ በቂ የማከማቻ ቦታ የለም። እባክዎ %1$s ጊባ ወይም ከዚያ በላይ የሆነ ውጫዊ አንጻፊ ይሰኩና እንደ የመሣሪያ ማከማቻ ቅርጸት ለመስራት ያሉትን ደረጃዎች ይከተሉ።" + "ፕሮግራሞችን በዲቪአር መቅረጽ ይችላሉ። ይሁንና አሁን ዲቪአር እንዲሰራ በመሣሪያዎ ላይ በቂ የማከማቻ ቦታ የለም። እባክዎ %1$d ጊባ ወይም ከዚያ በላይ የሆነ ውጫዊ አንጻፊ ይሰኩና እንደ የመሣሪያ ማከማቻ ቅርጸት ለመስራት ያሉትን ደረጃዎች ይከተሉ።" + "በቂ ማከማቻ የለም" + "በቂ ማከማቻ ስለሌለ ይህ ፕሮግራም አይቀረጽም። አሁን ያሉ አንዳንድ ቀረጻዎችን ለመሰረዝ ይሞክሩ።" "የሚጎድል ማከማቻ" - "በDVR ጥቅም ላይ የዋለ አንዳንድ ማከማቻ ይጎድላል። DVRን ዳግም ለማንቃት ከዚህ በፊት የሚጠቀሙበትን ውጫዊ አንጻፊ እባክዎ ያገናኙ። በአማራጭነት፣ ከእንግዲህ የማይገኝ ከሆነ ማከማቻውን ለመርሳት መምረጥ ይችላሉ።" - "ማከማቻ ይረሳ?" - "ሁሉም የእርስዎ የተቀዳ ይዘት እና መርሐግብሮች ይጠፋሉ።" "መቅረጽ ይቁም?" "የተቀረጸው ይዘት ይቀመጣል።" - - + "የ%1$s ቀረጻ ከዚህ ፕሮግራም ጋር ስለሚጋጭ ይቆማል። የተቀረጸው ይዘት ይቀመጣል።" "ምዝገባ መርሐግብር ተይዞለታል፣ ነገር ግን ግጭቶች አሉ" "ቀረጻ ተጀምሯል፣ ነገር ግን ግጭቶች አሉት" "%1$s ይቀረጻል።" @@ -306,17 +299,29 @@ "ተመሳሳዩ ፕሮግራም አስቀድሞ በ%1$s ላይ እንዲቀረጽ መርሐግብር ተይዞለታል።" "አስቀድሞ ተቀርጿል" "ይህ ፕሮግራም አስቀድሞ ተቀርጿል። በዲቪአር ቤተ-መጽሐፍት ውስጥ ይገኛል።" - - - - - - - - + "የተከታታዮች ቀረጻ መርሐግብር ተይዞለታል" + + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። + + + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። ከእነሱ ውስጥ %3$d በግጭቶች ምክንያት አይቀረጹም። + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። ከእነሱ ውስጥ %3$d በግጭቶች ምክንያት አይቀረጹም። + + + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። %3$d የዚህ ተከታታይ እና የሌሎች ተከታታዮች ትርዒት ክፍሎች በግጭቶች ምክንያት አይቀረጹም። + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። %3$d የዚህ ተከታታይ እና የሌሎች ተከታታዮች ትርዒት ክፍሎች በግጭቶች ምክንያት አይቀረጹም። + + + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። 1 የሌሎች ተከታታዮች የትርዒት ክፍል በግጭት ምክንያት አይቀረጽም። + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። 1 የሌሎች ተከታታዮች የትርዒት ክፍል በግጭት ምክንያት አይቀረጽም። + + + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። %3$d የሌላ ተከታታይ የትዕይንት ክፍሎች በግጭቶች ምክንያት አይቀረጹም። + %1$d ቀረጻዎች ለ%2$s የጊዜ መርሐግብር ተይዞላቸዋል። %3$d የሌላ ተከታታይ የትዕይንት ክፍሎች በግጭቶች ምክንያት አይቀረጹም። + "የተቀረጸ ፕሮግራም አልተገኘም።" "ተዛማጅ ቀረጻዎች" - "(ምንም የፕሮግራም መግለጫ የለም)" %1$d ቀረጻዎች %1$d ቀረጻዎች @@ -336,6 +341,7 @@ "የተከታታይ ቀረጻ ይቆም?" "የተቀረጹ ክፍሎች በዲቪአር ቤተ-መጽሐፍቱ ላይ የሚገኙ እንደሆኑ ይቆያሉ።" "አቁም" + "ምንም ተከታታይ የትዕይንት ክፍሎች የሉም።" "ምንም ክፍሎች አይገኙም።\nልክ የሚገኙ ሲሆኑ ይቀረጻሉ።" (%1$d ደቂቃዎች) @@ -347,4 +353,5 @@ "%1$s ዛሬ" "%1$s ነገ" "ነጥብ" + "የተቀረጹ ፕሮግራሞች" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index f91139b9..a1b47209 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -20,9 +20,8 @@ "أحادية" "ستيريو" "عناصر التحكم في التشغيل" - "أحدث القنوات" + "القنوات" "خيارات التلفزيون" - "‏خيارات PIP" "عناصر تحكم التشغيل غير متاحة لهذه القناة" "تشغيل الفيديو أو إيقافه مؤقتًا" "تقديم سريع" @@ -32,40 +31,22 @@ "دليل البرامج" "تتوفر قنوات جديدة" "فتح %1$s" - "التسميات التوضيحية المغلقة" + "الترجمة والشرح" "وضع العرض" "PIP" - "تشغيل" - "إيقاف" "إعدادات صوتية متعددة" "جلب قنوات أخرى" "الإعدادات" - "المصدر" - "تبديل" - "تشغيل" - "إيقاف" - "الصوت" - "الرئيسي" - "‏نافذة PIP" - "التنسيق" - "أسفل اليمين" - "أعلى اليمين" - "أعلى اليسار" - "أسفل اليسار" - "جنبًا إلى جنب" - "الحجم" - "كبيرة" - "صغيرة" - "مصدر الإدخال" "تلفزيون (هوائي/كابل)" "لا تتوفّر معلومات عن البرنامج" "لا توجد معلومات" "القناة المحظورة" - "لغة غير معروفة" - "التسميات التوضيحية المغلقة" + "لغة غير معروفة" + "‏مقاطع ترجمة وشرح %1$d" + "الترجمة والشرح" "إيقاف" "تخصيص التنسيق" - "تعيين التفضيلات عبر النظام للتسميات التوضيحية المغلقة" + "تعيين التفضيلات عبر النظام للترجمة والشرح" "وضع العرض" "إعدادات صوتية متعددة" "أحادية" @@ -83,6 +64,7 @@ "دقة قياسية" "تجميع بحسب" "تم حظر هذا البرنامج" + "لم يتم تقييم هذا البرنامج" "تم تصنيف هذا البرنامج على أنه %1$s." "الإدخال ليس متوافقًا مع الفحص التلقائي" "تعذر بدء البحث التلقائي في \"%s\"" @@ -96,7 +78,6 @@ ‏تمت إضافة %1$d قناة "لم تتم إضافة قنوات" - "موالف" "عناصر التحكم الأبوي" "تشغيل" "إيقاف" @@ -112,6 +93,8 @@ "بلدان أخرى" "لا شيء" "لا شيء" + "غير مقيَّم" + "حظر البرامج غير المقيَّمة" "لا شيء" "قيود مرتفعة" "قيود متوسّطة" @@ -128,6 +111,7 @@ "أدخل رقم التعريف الشخصي لمشاهدة هذه القناة" "أدخل رقم التعريف الشخصي لمشاهدة هذا البرنامج" "هذا البرنامج يحمل التقييم %1$s. يمكنك إدخال رقم التعريف الشخصي لمشاهدة هذا البرنامج" + "لم يتم تقييم هذا البرنامج. أدخل رقم التعريف الشخصي لمشاهدة هذا البرنامج" "أدخل رقم التعريف الشخصي" "لتعيين عناصر التحكم الأبوي، عليك إنشاء رقم تعريف شخصي" "إدخال رقم تعريف شخصي جديد" @@ -143,22 +127,31 @@ "رقم التعريف الشخصي هذا خاطئ. أعد المحاولة." "أعد المحاولة، رقم التعريف الشخصي غير مطابق" + "أدخل الرمز البريدي." + "‏سيستخدم تطبيق Live TV الرمز البريدي لتوفير دليل برامج كامل لقنوات التلفزيون." + "أدخل الرمز البريدي" + "الرمز البريدي غير صالح" "الإعدادات" "تخصيص قائمة قنوات" "اختر القنوات الخاصة بدليل البرامج." "مصادر القنوات" "تتوفر قنوات جديدة." "أدوات الرقابة الأبوية" + "الانتقال الزمني" + "يمكنك التسجيل أثناء المشاهدة حتى يتسنى لك إيقاف بث البرامج المباشرة مؤقتًا أو إرجاع البث إلى أوقات سابقة.\nتحذير: قد يؤدي هذا إلى استهلاك مكثّف لمساحة وحدة التخزين الداخلية؛ ما يؤدي إلى نفادها في فترة قصيرة." "تراخيص البرامج المفتوحة المصدر" - "تراخيص البرامج المفتوحة المصدر" + "إرسال تعليقات" "الإصدار" "لمشاهدة هذه القناة، اضغط على اليمين وأدخل رقم التعريف الشخصي" "لمشاهدة هذا البرنامج، اضغط على اليمين وأدخل رقم التعريف الشخصي" + "لم يتم تقييم هذا البرنامج.\nلمشاهدة هذا البرنامج، اضغط على اليمين وأدخل رقم التعريف الشخصي" "تم تصنيف هذا البرنامج على أنه %1$s.\nلمشاهدة هذا البرنامج، اضغط على اليمين وأدخل رقم التعريف الشخصي." "‏لمشاهدة هذه القناة، يمكنك استخدام تطبيق Live TV الافتراضي." "‏لمشاهدة هذا البرنامج، يمكنك استخدام تطبيق Live TV الافتراضي." + "‏لم يتم تقييم هذا البرنامج.\nلمشاهدة هذا البرنامج، استخدم تطبيق Live TV الافتراضي." "‏هذا البرنامج يحمل التقييم %1$s.\nلمشاهدة هذا البرنامج، يمكنك استخدام تطبيق Live TV الافتراضي." "البرنامج محظور" + "لم يتم تقييم هذا البرنامج" "تم تصنيف هذا البرنامج على أنه %1$s." "الصوت فقط" "الإشارة ضعيفة" @@ -197,10 +190,8 @@ "‏""اضغط على \"تحديد\""" للوصول إلى قائمة TV." "لم يتم العثور على إدخال تلفزيون" "لا يمكن العثور على إدخال تلفزيون" - "‏خدمة PIP ليست متوافقة" - "‏ليس هناك إدخال متاح يمكن عرضه باستخدام PIP" "نوع الموالف غير مناسب، لذلك يُرجى تشغيل تطبيق القنوات المباشرة لنوع الموالف إدخال التلفزيون." - "أخفق التوليف" + "تعذّر التوليف" "لم يتم العثور على تطبيق يمكنه مباشرة هذا الإجراء." "كل قنوات المصدر مخفية.\nحدد على الأقل قناة واحدة لمشاهدتها." "الفيديو غير متوفر بشكل مفاجئ" @@ -262,6 +253,8 @@ ‏%1$d تسجيل حسب جدول زمني ‏%1$d تسجيل حسب جدول زمني + "إلغاء التسجيل" + "إيقاف التسجيل" "مشاهدة" "تشغيل من البداية" "استئناف التشغيل" @@ -298,9 +291,6 @@ "عندما تكون هناك برامج كثيرة جدًا مطلوب تسجيلها في آنٍ واحد، فلن يتم تسجيل أي برامج منها إلا تلك ذات القدر الأعلى من الأولوية فقط." "حفظ" "تسجيلات المرة الواحدة لها الأولوية القصوى" - "إلغاء" - "إلغاء" - "حذف" "إيقاف" "عرض جدول عمليات التسجيل الزمني" "هذا البرنامج وحده" @@ -310,25 +300,28 @@ "تسجيل هذا بدلاً من ذلك" "إلغاء هذا التسجيل" "المشاهدة الآن" + "حذف التسجيلات…" "قابل للتسجيل" "تمتّ جدولة التسجيل" "تعارض في التسجيل" "جارٍ التسجيل" - "أخفق التسجيل" - "جارٍ قراءة البرامج لإنشاء جداول زمنية للتسجيل" - "جارٍ قراءة البرامج" - - + "تعذّر التسجيل" + "جارٍ قراءة البرامج" + "عرض التسجيلات الأخيرة" + "لم يكتمل تسجيل %1$s." + "لم يكتمل تسجيل كل من %1$s و%2$s." + "لم يكتمل تسجيل كل من %1$s و%2$s و%3$s." + "لم يكتمل تسجيل %1$s نظرًا لأن سعة التخزين غير كافية." + "لم يكتمل تسجيل كل من %1$s و%2$s نظرًا لأن سعة التخزين غير كافية." + "لم يكتمل تسجيل كل من %1$s و%2$s و%3$s نظرًا لأن سعة التخزين غير كافية." "يحتاج مسجِّل الفيديو الرقمي إلى المزيد من السعة التخزينية" - "ستتمكن من تسجيل البرامج باستخدام مسجّل الفيديو الرقمي؛ ولكن ليست هناك سعة تخزينية كافية على جهازك الآن ليعمل مسجِّل الفيديو الرقمي. يُرجى توصيل محرك أقراص خارجي بسعة تخزين %1$sغيغابايت أو أكبر واتباع الخطوات لتهيئته كوحدة تخزين للجهاز." + "ستتمكن من تسجيل البرامج باستخدام مسجّل الفيديو الرقمي؛ ولكن ليست هناك سعة تخزينية كافية على جهازك الآن ليعمل مسجِّل الفيديو الرقمي. يُرجى توصيل محرك أقراص خارجي بسعة تخزين %1$dغيغابايت أو أكبر واتباع الخطوات لتهيئته كوحدة تخزين للجهاز." + "السعة التخزينية غير كافية" + "لن يتم تسجيل هذا البرنامج لأنه ليست هناك سعة تخزين كافية. جرِّب حذف بعض التسجيلات الحالية." "سعة التخزين المفقودة" - "‏بعض سعة التخزين المستخدمة في جهاز DVR مفقود. يُرجى توصيل محرك الأقراص الخارجي الذي سبق لك استخدامه لإعادة تمكين جهاز DVR. بدلاً من ذلك، يمكنك اختيار حذف سعة التخزين إذا لم تعد متاحة." - "هل تريد حذف سعة التخزين؟" - "سيتم فقد جميع المحتويات والجداول الزمنية المسجَّلة." "هل تريد إيقاف التسجيل؟" "سيتم حفظ المحتوى الذي تم تسجيله." - - + "سيتم إيقاف تسجيل %1$s لأنه يتعارض مع هذا البرنامج. وسيتم حفظ المحتوى الذي تم تسجيله." "تمت إضافة جدول زمني لإجراء التسجيل ولكنه يتعارض مع جداول زمنية أخرى" "تم بدء التسجيل ولكنه يتعارض مع جداول زمنية أخرى" "لن يتم تسجيل %1$s." @@ -350,17 +343,49 @@ "تم إعداد جدول زمني من قبل لتسجيل البرنامج نفسه في %1$s." "تم التسجيل من قبل" "‏تم تسجيل هذا البرنامج من قبل. وسيكون متاحًا في مكتبة DVR." - - - - - - - - + "تمت جدولة تسجيل المسلسل" + + تمت جدولة %1$d تسجيل لمسلسل %2$s. + تمت جدولة تسجيلين (%1$d) لمسلسل %2$s. + تمت جدولة %1$d تسجيلات لمسلسل %2$s. + تمت جدولة %1$d تسجيلاً لمسلسل %2$s. + تمت جدولة %1$d تسجيل لمسلسل %2$s. + تمت جدولة تسجيل واحد (%1$d) لمسلسل %2$s. + + + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من الحلقات بسبب تعارض المواعيد. + تمت جدولة تسجيلين (%1$d) لمسلسل %2$s. ولن يتم تسجيل %3$d من الحلقات بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلات لمسلسل %2$s. ولن يتم تسجيل %3$d من الحلقات بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلاً لمسلسل %2$s. ولن يتم تسجيل %3$d من الحلقات بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من الحلقات بسبب تعارض المواعيد. + تمت جدولة تسجيل واحد (%1$d) لمسلسل %2$s. ولن يتم إجراء هذا التسجيل بسبب تعارض المواعيد. + + + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيلين (%1$d) لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلات لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلاً لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيل واحد (%1$d) لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات هذا المسلسل والمسلسل الآخر بسبب تعارض المواعيد. + + + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيلين (%1$d) لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلات لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلاً لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيل واحد (%1$d) لمسلسل %2$s. ولن يتم تسجيل حلقة واحدة من المسلسل الآخر بسبب تعارض المواعيد. + + + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيلين (%1$d) لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلات لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيلاً لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة %1$d تسجيل لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + تمت جدولة تسجيل واحد (%1$d) لمسلسل %2$s. ولن يتم تسجيل %3$d من حلقات المسلسل الآخر بسبب تعارض المواعيد. + "لم يتم العثور على البرنامج المُسّجل" "تسجيلات ذات صلة" - "(لا يتوفر وصف للبرنامج)" ‏%1$d تسجيل ‏تسجيلان (%1$d) @@ -388,6 +413,7 @@ "هل تريد إيقاف تسجيل السلسلة؟" "‏ستظل الحلقات المسجّلة متاحة في مكتبة DVR." "إيقاف" + "ليست هناك حلقات يتم بثها على الهواء حاليًا." "لا تتوفر أي حلقات.\nسيتم تسجيلها بعد توفرها." ‏(%1$d دقيقة) @@ -403,4 +429,5 @@ "%1$s اليوم" "%1$s غدًا" "النتيجة" + "البرامج المسجّلة" diff --git a/res/values-az-rAZ/arrays.xml b/res/values-az-rAZ/arrays.xml new file mode 100644 index 00000000..7f891617 --- /dev/null +++ b/res/values-az-rAZ/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Normal" + "Tam" + "Zoom" + + + "Bütün kanallar" + "Ailə/Uşaqlar" + "İdman" + "Şoppinq" + "Filmlər" + "Komediya" + "Səyahət" + "Dram" + "Təhsil" + "Heyvan/Vəhşi təbiət" + "Xəbərlər" + "Oyun" + "İncəsənət" + "Əyləncə" + "Həyat tərzi" + "Musiqi" + "Premyera" + "Texnologiya/Elm" + + + "Canlı Kanallar" + "Məzmun kəşfi üçün sadə yol" + "Tətbiqləri endirin, daha çox kanal əldə edin" + "Kanallarınızın sıralamasını fərdiləşdirin" + + + "TV kanallarını izlədiyiniz kimi tətbiqlərinizdən məzmuna baxın." + "Oxşar təlimat və səmimi interfeysi olan tətbiqlərinizdən məzmun axtarışı edin, \nelə TV kanallarındakı kimi." + "Canlı kanallar təklif edən tətbiqləri yükləyərək daha çox kanal əlavə edin. \n Və ya TV menyusu olan linki istifadə edərək Google Play Store\'da müvafiq tətbiqi tapın." + "Kanal siyahısını fərdiləşdirmək üçün yeni quraşdırılmış kanal mənbələrini ayarlayın. \n Başlamaq üçün Ayarlar menyusundan kanal mənbələrini seçin." + + diff --git a/res/values-az-rAZ/rating_system_strings.xml b/res/values-az-rAZ/rating_system_strings.xml new file mode 100644 index 00000000..bd28a4e8 --- /dev/null +++ b/res/values-az-rAZ/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Proqramlarda 15 yaşından kiçik auditoriya üçün uyğun olmayan material ola bilər və onlar üçün valideyn istədiyi istifadə olunmalıdır." + "Proqramlarda 19 yaşından kiçik auditoriya üçün uyğun olmayan material ola bilər və bunlar 19 yaşından kiçiklər üçün nəzərdə tutulmayıb." + "Nalayiq dialoq" + "Kobud dil" + "Seksual məzmun" + "Qəddarlıq" + "Fantaziya Zorakılığı" + "Bu proqram bütün uşaqlara uyğun şəkildə hazırlanmışdır." + "Bu proqram 7 yaşdan böyük uşaqlar üçün hazırlanmışdır." + "Bir çox valideyn bu proqramı bütün yaş kateqoriyalarına uyğun hesab edir." + "Bu proqramdakı materiallar bir çox valideyn tərəfindən kiçik uşaqlara uyğun hesab edilmir. Bir çox valideynlər buna kiçik uşaqları ilə baxmaq istəyə bilər." + "Bu proqramdakı materiallar bir çox valideyn tərəfindən 14 yaşdan aşağı uşaqlara uyğun hesab edilmir." + "Bu proqram xüsusən böyüklər üçün nəzərdə tutulub və 17 yaşdan aşağı şəxslər tərəfindən baxılması məsləhətli deyil." + "Film reytinqləri" + "Ümumi auditoriya. Uşaqların izləməsi valideynlərin narahatlığına səbəb olmayacaq." + "Valideyn təlimatı təklif olunur. Bəzi valideynlərin bəyənməyəcəyi materialdan ibarət ola bilər." + "Valideynlərə ciddi xəbərdarlıq verilir.Bəzi materiallar erkən yeniyetmələr üçün uyğun olmaya bilər." + "Qadağandır, Böyüklər üçün materialdan ibarətdir. Uşaqlar izləməmişdən əvvəl valideynlər film haqqında ətraflı məlumat almağa məcburdurlar." + "17 və daha daha az yaşı olanlar qəbul edilmir. Yalnız böyüklər üçün. Uşaqlar qəbul edilmir." + diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml new file mode 100644 index 00000000..7fe8ae8e --- /dev/null +++ b/res/values-az-rAZ/strings.xml @@ -0,0 +1,359 @@ + + + + + "mono" + "stereo" + "Oyun kontrolu" + "Kanallar" + "TV seçənəkləri" + "Oxutma idarə elementləri bu kanal üçün əlçatan deyil" + "Oxudun və ya durdurun" + "Sürətlə irəli" + "Geri qayıdın" + "Növbəti" + "Əvvəlki" + "Proqram Təlimatı" + "Yeni əlçatan kanallar" + "%1$s tətbiqini açın" + "Qapalı başlıqlar" + "Ekran rejimi" + "PIP" + "Multi-audio" + "Daha çox kanal əldə edin" + "Ayarlar" + "TV (antena/kabel)" + "No proqram informasiya" + "Məlumat yoxdur" + "bloklanmış kanallar" + "Naməlum dil" + "Subtitr %1$d" + "Qapalı başlıqlar" + "Deaktiv" + "Formatı fərdiləşdirin" + "Qapalı başlıqlar üçün daxili sistem tərcihlərini ayarlayın" + "Ekran rejimi" + "Multi-audio" + "Mono" + "Stereo" + "5.1 Əhatəli" + "7.1 Əhatəli" + + + + "Kanal siyahısını fərdiləşdirin" + "Qrup seçin" + "Qrupu silin" + "Qruplaşdırın" + "Kanal mənbəyi" + "HD/SD" + "HD" + "SD" + "Qruplaşdırın" + "Bu proqram bloklanıb." + "Bu proqram reytinqsizdir" + "Bu proqram %1$s ilə qiymətləndirilib." + "Daxiletmə avto-skanı dəstəkləmir" + "\'%s\' üçün avto-skanı başlatmaq olmur" + "Qapalı mövzular üçün sistem tərcihlərini başlatmaq olmur." + + %1$d kanal əlavə edildi + %1$d kanal əlavə edildi + + "Kanal əlavə edilmədi" + "Valideyn nəzarəti" + "Aktiv" + "Deaktiv" + "Kanallar bloklanıb" + "Hamısını bloklayın" + "Hamısını blokdan çıxarın" + "Gizli kanallar" + "Proqram məhdudiyyətləri" + "PIN kodu dəyişin" + "Reytinq sistemləri" + "Ratings" + "Bütün reytinq sistemlərinə baxın" + "Digər ölkələr" + "Heç bir" + "Heç bir" + "Reytinqsiz" + "Reytinqsiz proqramları blok edin" + "Heç bir" + "Yüksək məhdudiyyətlər" + "Orta məhdudiyyətlər" + "Aşağı məhdudiyyətlər" + "Fərdi" + "Uşaqlar üçün uyğun məzmun" + "Böyük uşaqlar üçün uyğun məzmun" + "Yeniyetmələr üçün uyğun məzmun" + "Manual məhdudiyyətlər" + + + "%1$s və sub-reytinqlər" + "Alt-reytinqlər" + "Bu kanalı izləmək üçün PİN kodunuzu daxil edin" + "Bu proqramı izləmək üçün PİN kodunuzu daxil edin" + "Bu proqram %1$s ilə qiymətləndirilib. Bu proqramı izləmək üçün PİN kodunuzu daxil edin" + "Bu proqram reytinqsizdir. Bu proqramı izləmək üçün PIN\'i daxil edin" + "PİN kodunuzu daxil edin" + "Valideyn nəzarəi yaratmaq üçün PİN yaradın" + "Yeni PIN kodu daxil edin" + "PİN kodunuzu təsdiq edin" + "Cari PİN kodu daxil edin" + + Yanlış PİN kodu 5 dəfə daxil etdiniz.\n%1$d saniyə sonra yenidən cəhd edin. + Yanlış PİN kodu 5 dəfə daxil etdiniz.\n%1$d saniyə sonra yenidən cəhd edin. + + "Həmin PİN kod səhv idi. Yenidən cəhd edin." + "Yenidən cəhd edin, PİN uyğun deyil" + "Poçt İndeksi daxil edin." + "Canlı Kanal tətbiqi TV kanallarını tam proqram təlimatı ilə təmin etmək üçün Poçt İndeksi istifadə edəcək." + "Poçt İndeksi daxil edin" + "Yanlış Poçt İndeksi" + "Ayarlar" + "Kanal siyahısını fərdiləşdirin" + "Proqram təlimatınız üçün kanal seçin" + "Kanal mənbələri" + "Yeni əlçatan kanallar" + "Valideyn nəzarəti" + "Zaman keçidi" + "İzləyərkən qeyd edin, beləliklə canlı proqramlara fasilə verə və ya geri sara bilərsiniz.\nXəbərdarlıq: İnstensiv yaddaş istifadəsi daxili yaddaşı azalda bilər." + "Açıq mənbə lisenziyaları" + "Cavab rəyi göndərin" + "Versiya" + "Bu kanalı izləmək üçün Sağa basın və PİN kodunuzu daxil edin" + "Bu proqramı izləmək üçün Sağa basın və PİN kodunuzu daxil edin" + "Bu proqram reytinqsizdir.\nBu proqramı izləmək üçün Right düyməsini basın və PIN kodu daxil edin" + "Bu proqram %1$s ilə qiymətləndirilib.\nBu proqramı izləmək üçün Sağa basın və PİN kodunuzu daxil edin." + "Bu kanalı izləmək üçün defolt Live TV tətbiqini istifadə edin." + "Bu proqramı izləmək üçün defolt Live TV tətbiqini istifadə edin." + "Bu proqram reytinqsizdir.\nBu proqramı izləmək üçün defolt Live TV tətbiqindən istifadə edin." + "Bu proqram %1$s ilə qiymətləndirilib. \n Bu proqramı izləmək üçün defolt Live TV tətbiqini istifadə edin." + "Proqram blok edilib" + "Bu proqram reytinqsizdir" + "Bu proqram %1$s ilə qiymətləndirilib." + "Yalnız Audio" + "Zəif siqnal" + "İnternet bağlantısı yoxdur" + + Bu kanal %1$s radələrinə kimi fəaliyyət göstərə bilməz, cünki, digər kanallar qeydə alınır \n\nQeydə alma cədvəlini nizamlamaq üçün Sağı basın. + Digər kanal qeydə alındığı üçün bu kanal %1$s radələrinə kimi fəaliyyət göstərə bilməz. \n\nQeydə alma cədvəlini nizamlamaq üçün Sağı basın. + + "Başlıq yoxdur" + "Kanal blok edilib" + "Yeni" + "Mənbələr" + + %1$d kanal + %1$d kanal + + "Əlçatan kanal yoxdur" + "Yeni" + "Quraşdırılmayıb" + "Əlavə mənbə əldə edin" + "Canlı kanallar təklif edən tətbiqlər axtarın" + "Yeni kanal mənbələri əlçatandır" + "Yeni kanal mənbələrinin kanal təklifləri var.\nOnları indi ayarlayın, və ya daha sonra kanal mənbələri ayarından edin." + "İndi ayarlayın" + "Ok, oldu." + + + "TV menyusuna giriş üçün ""SEÇİN basın""." + "TV daxiletmə tapılmadı" + "TV daxiletmə tapılmır" + "Sazlama növü uyğun deyil. TV daxiletmə sazlama növü üçün Canlı Kanallar tətbiqini işə salın." + "Sazlama alınmadı" + "Bu əməliyyatı idarə etmək üçün heç bir tətbiq tapılmadı." + "Bütün mənbə kanalları gizlədilib.\nİzləmək üçün ən azı bir kanal seçin." + "Bu video gözlənilmədən əlçatmazdır" + "Geri düyməsi qoşulu cihazlar üçündür. Çıxmaq üçün ƏSAS SƏHİFƏ düyməsini basın." + "Canlı Kanallar TV siyahıları oxumaq üçün icazə istəyir." + "Mənbələrinizi quraşdırın" + "Canlı kanallar tətbiq tərəfindən yayımlanan kanallar ilə ənənəvi TV kanallarının təcrübəsini özündə birləşdirir. \n\n Artıq yüklənmiş kanal mənbələrini quraşdıraraq başlayın. Və ya canlı kanallar təklif edən bir neçə tətbiq üçün Google Play Store\'da axtarış edin." + "Qeydə almalar və cədvəllər" + "10 dəqiqə" + "30 dəqiqə" + "1 saat" + "3 saat" + "Son" + "Planlaşdırılıb" + "Seriyalar" + "Digərləri" + "Kanal qeydə alına bilməz." + "Proqram qeydə alına bilməz." + "%1$s tətbiqinin qeydə alınması üçün vaxt təyin edilib" + "%1$s bu dəqiqədən etibarən %2$s radələrinə kimi deydə alınır" + "Tam cədvəl" + + Növbəti %1$d gün + Növbəti %1$d gün + + + %1$d dəqiqə + %1$d dəqiqə + + + %1$d yeni qeydə alma + %1$d yeni qeydə alma + + + %1$d qeydə alma + %1$d qeydə alma + + + %1$d qeydə alma təyin edildi + %1$d qeydə alma təyin edildi + + "Qeyd etməni ləğv edin" + "Qeyd etməyi dayandırın" + "İzləyin" + "Əvvəldən oxudun" + "Oxutmağa davam edin" + "Silin" + "Qeydə almaları silin" + "Davam edin" + "%1$s mövsüm" + "Cədvələ baxın" + "Ətraflı məlumat" + "Qeydə almaları silin" + "Silmək istədiyiniz epizodları seçin. Silinənlər bərpa oluna bilməz." + "Silinmək üçün heç bir qeydə alma yoxdur." + "Baxılmış epizodları seçin" + "Bütün epizodları seçin" + "Bütün epizodların seçimlərini qaldırın" + "%2$d müddətdən %1$d dəqiqəsinə baxıldı" + "%2$d müddətdən %1$d saniyəsinə baxıldı" + "Heç vaxt baxılmayıb" + + %2$d epizoddan %1$d epizod silindi + %2$d epizoddan %1$d epizod silindi + + "Prioritet" + "Ən yüksək" + "Ən alçaq" + "Yox. %1$d" + "Kanallar" + "Hər hansı" + "Prioriteti seçin" + "Eyni anda qeydə alınacaq bir neçə proqram olduqda, yalnız vacib olanlar qeydə alınacaq." + "Yadda saxlayın" + "Bir dəfəlik qeydiyyatlar yüksək prioritetlidir" + "Dayandırın" + "Qeydiyyat cədvəlinə baxın" + "Tək bu proqram" + "indi - %1$s" + "Bütün seriyalar…" + "İstənilən halda vaxt təyin edin" + "Əvəzinə bunu qeydə alın" + "Bu qeydə almanı ləğv edin" + "İndi baxın" + "Qeydə almaları silin..." + "Qeydə alınabilən" + "Qeydiyyat planlaşdırılıb" + "Qeydiyyat münaqişəsi" + "Qeydə alınır" + "Qeydə alma uğursuz oldu" + "Oxuma proqramları" + "Son yazılara baxın" + "%1$s qeydə alınması tamamlanmadı." + "%1$s%2$s qeydə alınması tamamlanmadı." + "%1$s, %2$s%3$s qeydə alınması tamamlanmadı." + "Yetərsiz yaddaş səbəbi ilə %1$s qeydə alınması tamamlanmadı." + "Yetərsiz yaddaş səbəbi ilə %1$s%2$s qeydə alınması tamamlanmadı." + "Yetərsiz yaddaş səbəbi ilə %1$s, %2$s%3$s qeydə alınması tamamlanmadı." + "DVR üçün əlavə yaddaş tələb olunur" + "DVR ilə proqram qeydə ala biləcəksiniz. Hazırda DVR-ın işləməsi üçün cihazda kifayət qədər yaddaş yoxdur. %1$dGB və ya daha böyük həcmli xarici yaddaşı qoşun və cihaz yaddaşı olaraq format etmək üçün mərhələlərə riayət edin." + "Kifayət qədər yer yoxdur" + "Kifayət qədər boş yer olmadığı üçün bu proqram qeydə alınmayacaq. Bəzi mövcud qeydə almaları silməyə çalışın." + "Yaddaş catışmır" + "Qeydetmə dayandırılsın?" + "Qeydə alınan məzmun yadda saxlanacaq." + "Bu proqram ilə ziddiyyəti olduğu üçün %1$s proqramının qeydə alınması dayandırılacaq. Qeydə alınmış məzmun yadda saxlanacaq." + "Qeydiyyat vaxtı təyin edilib lakin ziddiyətlər var" + "Qeydə alma başladı, lakin ziddiyətlər var." + "%1$s qeydə alınacaq." + "%1$s qeydə alındı." + "%1$s tətbiqinin bəzi hissələri qeydə alınmayacaq." + "%1$s%2$s tətbiqlərinin bəzi hissələri qeydə alınmayacaq." + "%1$s, %2$s tətbiqlərinin bəzi hissələri və daha bir cədvəl qeydə alınmayacaq." + + %1$s, %2$s tətbiqlərinin bəzi hissələri və daha %3$d cədvəl qeydə alınmayacaq. + %1$s, %2$s tətbiqlərinin bəzi hissələri və daha %3$d cədvəl qeydə alınmayacaq. + + "Qeydə almaq istəyirsiniz?" + "Nə qədər müddətə qeydə almaq istəyirsiniz?" + "Artıq vaxt təyin edilib" + "Eyni proqramın qeydə alınması üçün artıq %1$s radələrində vaxt təyin edilib." + "Artıq qeydə alınıb" + "Bu proqram artıq qeydə alınıb. O, DVR kitabxanasında əlçatandır." + "Seriyaların qeydiyyatı planlaşdırıldı" + + %1$d qeydə alma %2$s üçün təyin edildi. + %1$d qeydə alma %2$s üçün təyin edildi. + + + %1$d qeydə alma %2$s üçün təyin edildi. Onlardan %3$d ədədi ziddiyyət səbəbilə qəydə alınmayacaq. + %1$d qeydə alma %2$s üçün təyin edildi. Bu, ziddiyyət səbəbilə qəydə alınmayacaq. + + + %1$d qeydə alma %2$s üçün təyin edildi. Bu seriyaların %3$d epizodu və digər seriyalar ziddiyyət səbəbilə qeydə alınmayacaq. + %1$d qeydə alma %2$s üçün təyin edildi. Bu seriyaların %3$d epizodu və digər seriyalar ziddiyyət səbəbilə qeydə alınmayacaq. + + + %1$d qeydə alma %2$s üçün təyin edildi. Digər seriyaların 1 epizodu ziddiyyət səbəbilə qeydə alınmayacaq. + %1$d qeydə alma %2$s üçün təyin edildi. Digər seriyaların 1 epizodu ziddiyyət səbəbilə qeydə alınmayacaq. + + + %1$d qeydə alma %2$s üçün təyin edildi. Digər seriyaların %3$d epizodu ziddiyyət səbəbilə qeydə alınmayacaq. + %1$d qeydə alma %2$s üçün təyin edildi. Digər seriyaların %3$d epizodu ziddiyyət səbəbilə qeydə alınmayacaq. + + "Qeyd edilmiş proqram tapılmadı." + "Əlaqədar qeydetmələr" + + %1$d qeyd etmə + %1$d qeyd etmə + + " / " + "%1$s qeyd cədvəlindən çıxarıldı" + "Kökləyici ziddiyətləri səbəbi ilə qismən qeydə alınacaq." + "Kökləyici ziddiyətləri səbəbi ilə qeydə alınmacaq." + "Hələ ki cədvəldə heç bir qeydə alma yoxdur.\nProqram bələdçisindən qeydə alma üçün vaxt təyin edə bilərsiniz." + + %1$d qeydə alma münaqişəsi + %1$d qeydə alma münaqişəsi + + "Davamlılıq ayarları" + "Ardıcıl qeydə almanı başladın" + "Ardıcıl qeydə alma dayandırılsın" + "Ardıcıl qeydə alma dayandırılsın?" + "Qeydə alınmış epizodlar DVR kitabxanasında əlçatan olacaq." + "Dayandırın" + "Heç bir epizod indi canlı yayımda deyil." + "Heç bir epizod əlçatan deyil.\nOnlar, əlçatan olduqda qeydə alınacaq." + + (%1$d dəqiqə) + (%1$d dəqiqə) + + "Bugün" + "Sabah" + "Dünən" + "%1$s bugün" + "%1$s sabah" + "Hesab" + "Qeyd edilmiş Proqramlar" + diff --git a/res/values-az/arrays.xml b/res/values-az/arrays.xml deleted file mode 100644 index 7f891617..00000000 --- a/res/values-az/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Normal" - "Tam" - "Zoom" - - - "Bütün kanallar" - "Ailə/Uşaqlar" - "İdman" - "Şoppinq" - "Filmlər" - "Komediya" - "Səyahət" - "Dram" - "Təhsil" - "Heyvan/Vəhşi təbiət" - "Xəbərlər" - "Oyun" - "İncəsənət" - "Əyləncə" - "Həyat tərzi" - "Musiqi" - "Premyera" - "Texnologiya/Elm" - - - "Canlı Kanallar" - "Məzmun kəşfi üçün sadə yol" - "Tətbiqləri endirin, daha çox kanal əldə edin" - "Kanallarınızın sıralamasını fərdiləşdirin" - - - "TV kanallarını izlədiyiniz kimi tətbiqlərinizdən məzmuna baxın." - "Oxşar təlimat və səmimi interfeysi olan tətbiqlərinizdən məzmun axtarışı edin, \nelə TV kanallarındakı kimi." - "Canlı kanallar təklif edən tətbiqləri yükləyərək daha çox kanal əlavə edin. \n Və ya TV menyusu olan linki istifadə edərək Google Play Store\'da müvafiq tətbiqi tapın." - "Kanal siyahısını fərdiləşdirmək üçün yeni quraşdırılmış kanal mənbələrini ayarlayın. \n Başlamaq üçün Ayarlar menyusundan kanal mənbələrini seçin." - - diff --git a/res/values-az/rating_system_strings.xml b/res/values-az/rating_system_strings.xml deleted file mode 100644 index bd28a4e8..00000000 --- a/res/values-az/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Proqramlarda 15 yaşından kiçik auditoriya üçün uyğun olmayan material ola bilər və onlar üçün valideyn istədiyi istifadə olunmalıdır." - "Proqramlarda 19 yaşından kiçik auditoriya üçün uyğun olmayan material ola bilər və bunlar 19 yaşından kiçiklər üçün nəzərdə tutulmayıb." - "Nalayiq dialoq" - "Kobud dil" - "Seksual məzmun" - "Qəddarlıq" - "Fantaziya Zorakılığı" - "Bu proqram bütün uşaqlara uyğun şəkildə hazırlanmışdır." - "Bu proqram 7 yaşdan böyük uşaqlar üçün hazırlanmışdır." - "Bir çox valideyn bu proqramı bütün yaş kateqoriyalarına uyğun hesab edir." - "Bu proqramdakı materiallar bir çox valideyn tərəfindən kiçik uşaqlara uyğun hesab edilmir. Bir çox valideynlər buna kiçik uşaqları ilə baxmaq istəyə bilər." - "Bu proqramdakı materiallar bir çox valideyn tərəfindən 14 yaşdan aşağı uşaqlara uyğun hesab edilmir." - "Bu proqram xüsusən böyüklər üçün nəzərdə tutulub və 17 yaşdan aşağı şəxslər tərəfindən baxılması məsləhətli deyil." - "Film reytinqləri" - "Ümumi auditoriya. Uşaqların izləməsi valideynlərin narahatlığına səbəb olmayacaq." - "Valideyn təlimatı təklif olunur. Bəzi valideynlərin bəyənməyəcəyi materialdan ibarət ola bilər." - "Valideynlərə ciddi xəbərdarlıq verilir.Bəzi materiallar erkən yeniyetmələr üçün uyğun olmaya bilər." - "Qadağandır, Böyüklər üçün materialdan ibarətdir. Uşaqlar izləməmişdən əvvəl valideynlər film haqqında ətraflı məlumat almağa məcburdurlar." - "17 və daha daha az yaşı olanlar qəbul edilmir. Yalnız böyüklər üçün. Uşaqlar qəbul edilmir." - diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml deleted file mode 100644 index 6ccb6d24..00000000 --- a/res/values-az/strings.xml +++ /dev/null @@ -1,352 +0,0 @@ - - - - - "mono" - "stereo" - "Oyun kontrolu" - "Son kanallar" - "TV seçənəkləri" - "PIP seçənəklər" - "Oxutma idarə elementləri bu kanal üçün əlçatan deyil" - "Oxudun və ya durdurun" - "Sürətlə irəli" - "Geri qayıdın" - "Növbəti" - "Əvvəlki" - "Proqram Təlimatı" - "Yeni əlçatan kanallar" - "%1$s tətbiqini açın" - "Qapalı başlıqlar" - "Ekran rejimi" - "PIP" - "Aktiv" - "Qeyri-aktiv" - "Multi-audio" - "Daha çox kanal əldə edin" - "Ayarlar" - "Mənbə" - "Dəyişdirin" - "Aktiv" - "Qeyri-aktiv" - "Səs" - "Əsas" - "PIP pəncərəsi" - "Etiket" - "Aşağı sağ" - "Yuxarı sağ" - "Yuxarı sol" - "Aşağı sol" - "Yan-yana" - "Ölçü" - "Böyük" - "Kiçik" - "Mənbəni daxil edin" - "TV (antena/kabel)" - "No proqram informasiya" - "Məlumat yoxdur" - "bloklanmış kanallar" - "Naməlum dil" - "Qapalı başlıqlar" - "Deaktiv" - "Formatı fərdiləşdirin" - "Qapalı başlıqlar üçün daxili sistem tərcihlərini ayarlayın" - "Ekran rejimi" - "Multi-audio" - "Mono" - "Stereo" - "5.1 Əhatəli" - "7.1 Əhatəli" - - - - "Kanal siyahısını fərdiləşdirin" - "Qrup seçin" - "Qrupu silin" - "Qruplaşdırın" - "Kanal mənbəyi" - "HD/SD" - "HD" - "SD" - "Qruplaşdırın" - "Bu proqram bloklanıb." - "Bu proqram %1$s ilə qiymətləndirilib." - "Daxiletmə avto-skanı dəstəkləmir" - "\'%s\' üçün avto-skanı başlatmaq olmur" - "Qapalı mövzular üçün sistem tərcihlərini başlatmaq olmur." - - %1$d kanal əlavə edildi - %1$d kanal əlavə edildi - - "Kanal əlavə edilmədi" - "Tuner" - "Valideyn nəzarəti" - "Aktiv" - "Deaktiv" - "Kanallar bloklanıb" - "Hamısını bloklayın" - "Hamısını blokdan çıxarın" - "Gizli kanallar" - "Proqram məhdudiyyətləri" - "PIN kodu dəyişin" - "Reytinq sistemləri" - "Ratings" - "Bütün reytinq sistemlərinə baxın" - "Digər ölkələr" - "Heç bir" - "Heç bir" - "Heç bir" - "Yüksək məhdudiyyətlər" - "Orta məhdudiyyətlər" - "Aşağı məhdudiyyətlər" - "Fərdi" - "Uşaqlar üçün uyğun məzmun" - "Böyük uşaqlar üçün uyğun məzmun" - "Yeniyetmələr üçün uyğun məzmun" - "Manual məhdudiyyətlər" - - - "%1$s və sub-reytinqlər" - "Alt-reytinqlər" - "Bu kanalı izləmək üçün PİN kodunuzu daxil edin" - "Bu proqramı izləmək üçün PİN kodunuzu daxil edin" - "Bu proqram %1$s ilə qiymətləndirilib. Bu proqramı izləmək üçün PİN kodunuzu daxil edin" - "PİN kodunuzu daxil edin" - "Valideyn nəzarəi yaratmaq üçün PİN yaradın" - "Yeni PIN kodu daxil edin" - "PİN kodunuzu təsdiq edin" - "Cari PİN kodu daxil edin" - - Yanlış PİN kodu 5 dəfə daxil etdiniz.\n%1$d saniyə sonra yenidən cəhd edin. - Yanlış PİN kodu 5 dəfə daxil etdiniz.\n%1$d saniyə sonra yenidən cəhd edin. - - "Həmin PİN kod səhv idi. Yenidən cəhd edin." - "Yenidən cəhd edin, PİN uyğun deyil" - "Ayarlar" - "Kanal siyahısını fərdiləşdirin" - "Proqram təlimatınız üçün kanal seçin" - "Kanal mənbələri" - "Yeni əlçatan kanallar" - "Valideyn nəzarəti" - "Açıq mənbə lisenziyaları" - "Açıq mənbə lisenziyaları" - "Versiya" - "Bu kanalı izləmək üçün Sağa basın və PİN kodunuzu daxil edin" - "Bu proqramı izləmək üçün Sağa basın və PİN kodunuzu daxil edin" - "Bu proqram %1$s ilə qiymətləndirilib.\nBu proqramı izləmək üçün Sağa basın və PİN kodunuzu daxil edin." - "Bu kanalı izləmək üçün defolt Live TV tətbiqini istifadə edin." - "Bu proqramı izləmək üçün defolt Live TV tətbiqini istifadə edin." - "Bu proqram %1$s ilə qiymətləndirilib. \n Bu proqramı izləmək üçün defolt Live TV tətbiqini istifadə edin." - "Proqram blok edilib" - "Bu proqram %1$s ilə qiymətləndirilib." - "Yalnız Audio" - "Zəif siqnal" - "İnternet bağlantısı yoxdur" - - Bu kanal %1$s radələrinə kimi fəaliyyət göstərə bilməz, cünki, digər kanallar qeydə alınır \n\nQeydə alma cədvəlini nizamlamaq üçün Sağı basın. - Digər kanal qeydə alındığı üçün bu kanal %1$s radələrinə kimi fəaliyyət göstərə bilməz. \n\nQeydə alma cədvəlini nizamlamaq üçün Sağı basın. - - "Başlıq yoxdur" - "Kanal blok edilib" - "Yeni" - "Mənbələr" - - %1$d kanal - %1$d kanal - - "Əlçatan kanal yoxdur" - "Yeni" - "Quraşdırılmayıb" - "Əlavə mənbə əldə edin" - "Canlı kanallar təklif edən tətbiqlər axtarın" - "Yeni kanal mənbələri əlçatandır" - "Yeni kanal mənbələrinin kanal təklifləri var.\nOnları indi ayarlayın, və ya daha sonra kanal mənbələri ayarından edin." - "İndi ayarlayın" - "Ok, oldu." - - - "TV menyusuna giriş üçün ""SEÇİN basın""." - "TV daxiletmə tapılmadı" - "TV daxiletmə tapılmır" - "PIP dəstəklənmir" - "PIP ilə göstərilə biləcək əlçatan daxiletmə yoxdur" - "Sazlama növü uyğun deyil. TV daxiletmə sazlama növü üçün Canlı Kanallar tətbiqini işə salın." - "Sazlama alınmadı" - "Bu əməliyyatı idarə etmək üçün heç bir tətbiq tapılmadı." - "Bütün mənbə kanalları gizlədilib.\nİzləmək üçün ən azı bir kanal seçin." - "Bu video gözlənilmədən əlçatmazdır" - "Geri düyməsi qoşulu cihazlar üçündür. Çıxmaq üçün ƏSAS SƏHİFƏ düyməsini basın." - "Canlı Kanallar TV siyahıları oxumaq üçün icazə istəyir." - "Mənbələrinizi quraşdırın" - "Canlı kanallar tətbiq tərəfindən yayımlanan kanallar ilə ənənəvi TV kanallarının təcrübəsini özündə birləşdirir. \n\n Artıq yüklənmiş kanal mənbələrini quraşdıraraq başlayın. Və ya canlı kanallar təklif edən bir neçə tətbiq üçün Google Play Store\'da axtarış edin." - "Qeydə almalar və cədvəllər" - "10 dəqiqə" - "30 dəqiqə" - "1 saat" - "3 saat" - "Son" - "Planlaşdırılıb" - "Seriyalar" - "Digərləri" - "Kanal qeydə alına bilməz." - "Proqram qeydə alına bilməz." - "%1$s tətbiqinin qeydə alınması üçün vaxt təyin edilib" - "%1$s bu dəqiqədən etibarən %2$s radələrinə kimi deydə alınır" - "Tam cədvəl" - - Növbəti %1$d gün - Növbəti %1$d gün - - - %1$d dəqiqə - %1$d dəqiqə - - - %1$d yeni qeydə alma - %1$d yeni qeydə alma - - - %1$d qeydə alma - %1$d qeydə alma - - - %1$d qeydə alma təyin edildi - %1$d qeydə alma təyin edildi - - "İzləyin" - "Əvvəldən oxudun" - "Oxutmağa davam edin" - "Silin" - "Qeydə almaları silin" - "Davam edin" - "%1$s mövsüm" - "Cədvələ baxın" - "Ətraflı məlumat" - "Qeydə almaları silin" - "Silmək istədiyiniz epizodları seçin. Silinənlər bərpa oluna bilməz." - "Silinmək üçün heç bir qeydə alma yoxdur." - "Baxılmış epizodları seçin" - "Bütün epizodları seçin" - "Bütün epizodların seçimlərini qaldırın" - "%2$d müddətdən %1$d dəqiqəsinə baxıldı" - "%2$d müddətdən %1$d saniyəsinə baxıldı" - "Heç vaxt baxılmayıb" - - %2$d epizoddan %1$d epizod silindi - %2$d epizoddan %1$d epizod silindi - - "Prioritet" - "Ən yüksək" - "Ən alçaq" - "Yox. %1$d" - "Kanallar" - "Hər hansı" - "Prioriteti seçin" - "Eyni anda qeydə alınacaq bir neçə proqram olduqda, yalnız vacib olanlar qeydə alınacaq." - "Yadda saxlayın" - "Bir dəfəlik qeydiyyatlar yüksək prioritetlidir" - "Ləğv edin" - "Ləğv edin" - "Unudun" - "Dayandırın" - "Qeydiyyat cədvəlinə baxın" - "Tək bu proqram" - "indi - %1$s" - "Bütün seriyalar…" - "İstənilən halda vaxt təyin edin" - "Əvəzinə bunu qeydə alın" - "Bu qeydə almanı ləğv edin" - "İndi baxın" - "Qeydə alınabilən" - "Qeydiyyat planlaşdırılıb" - "Qeydiyyat münaqişəsi" - "Qeydə alınır" - "Qeydə alma uğursuz oldu" - "Qeyd etmə cədvəli yaratmaq üçün proqramlar oxunur" - "Oxuma proqramları" - - - "DVR üçün əlavə yaddaş tələb olunur" - "DVR ilə proqram qeydə ala biləcəksiniz. Hazırda DVR-ın işləməsi üçün cihazda kifayət qədər yaddaş yoxdur. %1$sGB və ya daha böyük həcmli xarici yaddaşı qoşun və cihaz yaddaşı olaraq format etmək üçün mərhələlərə riayət edin." - "Yaddaş catışmır" - "DVR tərəfindən istifadə olunan yaddaşın bir hissəsi əlçatan deyil. DVR\'ı yenidən aktiv etməmişdən əvvəl istifadə etdiyiniz xarici diskə qoşulun. Bundan başqa, artıq əlçatan deyilsə, yaddaşı unutmağı seçə bilərsiniz." - "Yaddaş ehtiyyatını unutmusunuz?" - "Qeydə alınmış bütün məzmun və cədvəlləriniz itəcək." - "Qeydetmə dayandırılsın?" - "Qeydə alınan məzmun yadda saxlanacaq." - - - "Qeydiyyat vaxtı təyin edilib lakin ziddiyətlər var" - "Qeydə alma başladı, lakin ziddiyətlər var." - "%1$s qeydə alınacaq." - "%1$s qeydə alındı." - "%1$s tətbiqinin bəzi hissələri qeydə alınmayacaq." - "%1$s%2$s tətbiqlərinin bəzi hissələri qeydə alınmayacaq." - "%1$s, %2$s tətbiqlərinin bəzi hissələri və daha bir cədvəl qeydə alınmayacaq." - - %1$s, %2$s tətbiqlərinin bəzi hissələri və daha %3$d cədvəl qeydə alınmayacaq. - %1$s, %2$s tətbiqlərinin bəzi hissələri və daha %3$d cədvəl qeydə alınmayacaq. - - "Qeydə almaq istəyirsiniz?" - "Nə qədər müddətə qeydə almaq istəyirsiniz?" - "Artıq vaxt təyin edilib" - "Eyni proqramın qeydə alınması üçün artıq %1$s radələrində vaxt təyin edilib." - "Artıq qeydə alınıb" - "Bu proqram artıq qeydə alınıb. O, DVR kitabxanasında əlçatandır." - - - - - - - - - "Qeyd edilmiş proqram tapılmadı." - "Əlaqədar qeydetmələr" - "(Proqramın təsviri yoxdur)" - - %1$d qeyd etmə - %1$d qeyd etmə - - " / " - "%1$s qeyd cədvəlindən çıxarıldı" - "Kökləyici ziddiyətləri səbəbi ilə qismən qeydə alınacaq." - "Kökləyici ziddiyətləri səbəbi ilə qeydə alınmacaq." - "Hələ ki cədvəldə heç bir qeydə alma yoxdur.\nProqram bələdçisindən qeydə alma üçün vaxt təyin edə bilərsiniz." - - %1$d qeydə alma münaqişəsi - %1$d qeydə alma münaqişəsi - - "Davamlılıq ayarları" - "Ardıcıl qeydə almanı başladın" - "Ardıcıl qeydə alma dayandırılsın" - "Ardıcıl qeydə alma dayandırılsın?" - "Qeydə alınmış epizodlar DVR kitabxanasında əlçatan olacaq." - "Dayandırın" - "Heç bir epizod əlçatan deyil.\nOnlar, əlçatan olduqda qeydə alınacaq." - - (%1$d dəqiqə) - (%1$d dəqiqə) - - "Bugün" - "Sabah" - "Dünən" - "%1$s bugün" - "%1$s sabah" - "Hesab" - diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index ab202fe2..cd4ae375 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -20,9 +20,8 @@ "моно" "стерео" "Контроли за пускане" - "Скорошни канали" + "Канали" "Опции за TV" - "Опции за PIP" "За този канал няма налични контроли за възпроизвеждане" "Пускане или поставяне на пауза" "Превъртане напред" @@ -35,33 +34,15 @@ "Надписи" "Показв.: Режим" "PIP" - "Вкл." - "Изкл." "Много записи" "Още канали" "Настройки" - "Източник" - "Размяна" - "Вкл." - "Изкл." - "Звук" - "Основен" - "Прозорец на PIP" - "Оформление" - "Долу вдясно" - "Горе вдясно" - "Горе вляво" - "Долу вляво" - "Редом" - "Размер" - "Голям" - "Малък" - "Вход" "Телевизор (антена/кабел)" "Няма информация за програмите" "Няма информация" "Блокиран канал" - "Неизвестен език" + "Неизвестен език" + "Надписи: %1$d" "Надписи" "Изключване" "Форматиране: Персон." @@ -79,10 +60,11 @@ "Групиране по" "Източник на канала" "Висока/стандартна детайлност" - "Висока детайлност" + "HD" "Стандартна детайлност" "Групиране по" "Тази програма е блокирана" + "Тази програма е без класификация" "Класификацията на тази програма е „%1$s“" "Входът не поддържа автоматично сканиране" "Автоматичното сканиране за „%s“ не може да се стартира" @@ -92,7 +74,6 @@ Добавен е %1$d канал "Няма добавени канали" - "Тунер" "Родителски контрол" "Вкл." "Изкл." @@ -108,6 +89,8 @@ "Други държави" "Няма" "Няма" + "Без класификация" + "Програми без класиф.: Блокир." "Няма" "Високи ограничения" "Средни ограничения" @@ -124,6 +107,7 @@ "Въведете ПИН кода си, за да гледате този канал" "Въведете ПИН кода си, за да гледате тази програма" "Тази програма е класифицирана като „%1$s“. Въведете ПИН кода си, за да я гледате" + "Тази програма е без класификация. Въведете ПИН кода си, за да я гледате" "Въведете ПИН кода си" "За да зададете родителски контроли, създайте ПИН код" "Въведете новия ПИН код" @@ -135,22 +119,31 @@ "Този ПИН код бе грешен. Опитайте отново." "Опитайте отново, ПИН кодът не е идентичен" + "Въведете пощенския си код." + "Приложението Телевизия онлайн ще използва пощенския код, за да предоставя пълния програмен справочник за телевизионните канали." + "Въведете пощенския си код" + "Пощенският код е невалиден" "Настройки" "Персон. на списъка с канали" "Изберете канали за програмния си справочник" "Източници на канали" "Налице са нови канали" "Родителски контроли" + "Отложено възпроизвеждане" + "Записвайте, докато гледате, за да можете да поставяте на пауза или превъртате назад програми на живо.\nПредупреждение: Тази функция може да намали живота на вътрешното хранилище чрез интензивното му използване." "Лицензи за отворен код" - "Лицензи за отворен код" + "Изпращане на отзиви" "Версия" "За да гледате този канал, натиснете стрелката за надясно и въведете ПИН кода си" "За да гледате тази програма, натиснете стрелката за надясно и въведете ПИН кода си" + "Тази програма е без класификация.\nЗа да я гледате, натиснете бутона за надясно и въведете ПИН кода си." "Класификацията на тази програма е „%1$s“.\nЗа да я гледате, натиснете стрелката за надясно и въведете ПИН кода си." "За да гледате този канал, използвайте стандартното приложение за телевизия на живо." "За да гледате тази програма, използвайте стандартното приложение за телевизия на живо." + "Тази програма е без класификация.\nЗа да я гледате, използвайте стандартното приложение за телевизия на живо." "Тази програма е класифицирана като „%1$s“.\nЗа да я гледате, използвайте стандартното приложение за телевизия на живо." "Програмата е блокирана" + "Тази програма е без класификация" "Класификацията на тази програма е „%1$s“" "Само аудио" "Слаб сигнал" @@ -181,8 +174,6 @@ "Натиснете „ИЗБИРАНЕ“"" за достъп до менюто на телевизора." "Няма намерен вход на телевизора" "Входът на телевизора не може да бъде намерен" - "Функцията „Картина в картината“ не се поддържа" - "Няма наличен вход, който да може да се показва с PIP" "Типът тунер не е подходящ. Моля, стартирайте приложението Live TV за телевизионен вход от типа „тунер“." "Настройването не бе успешно" "Не бе намерено приложение за извършване на това действие." @@ -226,6 +217,8 @@ %1$d насрочени записа %1$d насрочен запис + "Анулиране на записа" + "Спиране на записа" "Гледане" "Пускане от началото" "Продължаване" @@ -258,9 +251,6 @@ "Когато има прекалено много програми за записване по едно и също време, ще бъдат записани само тези с най-висок приоритет." "Запазване" "Най-висок приоритет имат еднократните записи" - "Отказ" - "Отказ" - "Забравяне" "Спиране" "Вижте графика за записване" "Само тази програма" @@ -270,25 +260,28 @@ "Записване на тази програма" "Анулиране на този запис" "Гледайте сега" + "Изтриване на записи…" "С възможност за запис" "Записът е насрочен" "Конфликт със записа" "Записва се" "Записването не бе успешно" - "Програмите се четат с цел създаване на графици за записване" - "Програмите се четат" - - + "Програмите се четат" + "Преглед на скорошните записи" + "Записът на „%1$s“ е непълен." + "Записите на „%1$s“ и „%2$s“ са непълни." + "Записите на „%1$s“, „%2$s“ и „%3$s“ са непълни." + "Записването на „%1$s“ не завърши поради недостатъчно място в хранилището." + "Записването на „%1$s“ и „%2$s“ не завърши поради недостатъчно място в хранилището." + "Записването на „%1$s“, „%2$s“ и „%3$s“ не завърши поради недостатъчно място в хранилището." "Дигиталният видеорекордер се нуждае от още място за съхранение" - "Ще сте в състояние да записвате програми посредством дигиталния видеорекордер. В момента обаче той не може да работи, тъй като няма достатъчно място в хранилището на устройството ви. Моля, свържете външен диск с размер от поне %1$s ГБ и изпълнете стъпките, за да го форматирате като хранилище на устройството." + "Ще сте в състояние да записвате програми посредством дигиталния видеорекордер. В момента обаче той не може да работи, тъй като няма достатъчно място в хранилището на устройството ви. Моля, свържете външен диск с размер от поне %1$d ГБ и изпълнете стъпките, за да го форматирате като хранилище на устройството." + "Няма достатъчно място в хранилището" + "Тази програма няма да бъде записана, защото няма достатъчно място в хранилището. Пробвайте да изтриете някои съществуващи записи." "Хранилището липсва" - "Част от използваното от цифровия видеорекордер хранилище липсва. Моля, свържете по-рано ползвания от вас външен диск, за да активирате отново видеорекордера. Друга възможност е да изберете хранилището да се забрави, ако вече не е налично." - "Да се забрави ли хранилището?" - "Цялото ви записано съдържание и графици ще бъдат изгубени." "Да се спре ли записването?" "Записаното съдържание ще бъде запазено." - - + "Записването на „%1$s“ ще бъде спряно, защото е в конфликт с тази програма. Записаното съдържание ще бъде запазено." "Записът е насрочен, но има конфликти" "Записването започна, но има конфликти" "„%1$s“ ще се запише." @@ -306,17 +299,29 @@ "Същата програма вече е насрочена за записване в %1$s." "Вече записахте" "Тази програма вече е записана. Тя е налична в библиотеката на устройството за дигитален видеозапис." - - - - - - - - + "Записването на поредицата е насрочено" + + За „%2$s“ са насрочени %1$d записа. + За „%2$s“ е насрочен %1$d запис. + + + За „%2$s“ са насрочени %1$d записа. %3$d епизода няма да бъдат записани поради конфликти. + За „%2$s“ е насрочен %1$d запис. Епизодът няма да бъде записан поради конфликти. + + + За „%2$s“ са насрочени %1$d записа. %3$d епизода от тази и от друга поредица няма да бъдат записани поради конфликти. + За „%2$s“ е насрочен %1$d запис. %3$d епизода от тази и от друга поредица няма да бъдат записани поради конфликти. + + + За „%2$s“ са насрочени %1$d записа. 1 епизод от друга поредица няма да бъде записан поради конфликти. + За „%2$s“ е насрочен %1$d запис. 1 епизод от друга поредица няма да бъде записан поради конфликти. + + + За „%2$s“ са насрочени %1$d записа. %3$d епизода от друга поредица няма да бъдат записани поради конфликти. + За „%2$s“ е насрочен %1$d запис. %3$d епизода от друга поредица няма да бъдат записани поради конфликти. + "Записаната програма не е намерена." "Сродни записи" - "(Няма описание на програмата)" %1$d записа %1$d запис @@ -336,6 +341,7 @@ "Да се спре ли записването на поредицата?" "Записаните епизоди ще останат налице в библиотеката на устройството за дигитален видеозапис." "Спиране" + "В момента не се излъчва нито един епизод." "Няма налични епизоди.\nТе ще бъдат записани, когато са налице." (%1$d минути) @@ -347,4 +353,5 @@ "Днес: %1$s" "Утре: %1$s" "Рейтинг" + "Записани програми" diff --git a/res/values-bn-rBD-v23/strings.xml b/res/values-bn-rBD-v23/strings.xml new file mode 100644 index 00000000..b3134242 --- /dev/null +++ b/res/values-bn-rBD-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "চ্যানেলগুলি" + diff --git a/res/values-bn-rBD/arrays.xml b/res/values-bn-rBD/arrays.xml new file mode 100644 index 00000000..76a0792a --- /dev/null +++ b/res/values-bn-rBD/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "স্বাভাবিক" + "সম্পূর্ণ" + "জুম" + + + "সমস্ত চ্যানেল" + "পরিবার/শিশু" + "খেলাধুলা" + "কেনাকাটা" + "চলচ্চিত্র" + "কমেডি" + "ভ্রমণ" + "নাটক" + "শিক্ষা" + "প্রাণী/বন্যজীবন" + "সংবাদ" + "গেমিং" + "কলা" + "বিনোদন" + "জীবনশৈলী" + "সঙ্গীত" + "প্রিমিয়ার" + "প্রযুক্তি/বিজ্ঞান" + + + "লাইভ চ্যানেলগুলি" + "সামগ্রী আবিষ্কার করার একটি সহজ উপায়" + "অ্যাপ্লিকেশানগুলি ডাউনলোড করুন, আর আরো বেশি চ্যানেল পান" + "আপনার চ্যানেল লাইন-আপ কাস্টমাইজ করুন" + + + "টিভিতে চ্যানেল দেখার মত আপনার অ্যাপ্লিকেশানগুলি থেকে সামগ্রী দেখুন৷" + "টিভিতে চ্যানেলগুলি মত কোনো \nপরিচিত গাইড এবং সহজ ইন্টারফেসের মাধ্যমে আপনার অ্যাপ্লিকেশানগুলি থেকে সামগ্রী ব্রাউজ করুন৷" + "লাইভ চ্যনেলগুলি অফার করে এমন অ্যাপ্লিকেশানগুলিকে ইনস্টল করার মাধ্যেমে আরো চ্যানেল যোগ করুন৷ \n টিভি মেনুর মধ্যে থাকা লিঙ্কটি ব্যবহার করে Google Play স্টোরে আরো উপযুক্ত অ্যাপ্লিকেশানগুলি খুঁজুন৷" + "আপনার চ্যানেল তালিকা কাস্টমাইজ করতে আপনার নতুন ইনস্টল করা চ্যানেল উৎসগুলি সেট আপ করুন৷ \nশুরু করতে সেটিংস মেনুতে থাকা চ্যানেল উৎসগুলি চয়ন করুন৷" + + diff --git a/res/values-bn-rBD/rating_system_strings.xml b/res/values-bn-rBD/rating_system_strings.xml new file mode 100644 index 00000000..81c9593b --- /dev/null +++ b/res/values-bn-rBD/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "প্রোগ্র্রামগুলির মধ্যে ১৫ বছরের চাইতে কমবয়সী শিশুদের জন্য অনুপযুক্ত উপাদান থাকতে পারে এবং অভিভাবকদের বিবেচনার ভিত্তিতে সেগুলি ব্যবহার করা উচিৎ৷" + "প্রোগ্র্রামগুলির মধ্যে থাকা উপাদান ১৯ বছরের চেয়ে কমবয়সীদের জন্য অনুপযুক্ত হতে পারে এবং এগুলি ১৯ বছরের চেয়ে কমবয়সী বালকদের জন্য উপযুক্ত নয়৷" + "প্রস্তাবিত সংলাপ" + "অমার্জিত ভাষা" + "যৌন সামগ্রী" + "হিংস্রতা" + "কল্পনাপ্রসূত হিংস্রতা" + "এই প্রোগ্রামটিকে সব বয়সী বাচ্চাদের জন্য উপযুক্ত হিসাবে তৈরি করা হয়েছে।" + "এই প্রোগ্রামটিকে ৭ বছর ও তার বেশি বয়সী বাচ্চাদের জন্য তৈরি করা হয়েছে।" + "বেশিরভাগ অভিভাবক এই প্রোগ্রামটিকে সব বয়সীদের জন্য উপযুক্ত বলে মনে করবেন।" + "এই প্রোগ্রামটিতে অল্পবয়সী বাচ্চাদের জন্য অনুপযুক্ত কিছু উপাদান রয়েছে বলে অভিভাবকরা মনে করতে পারেন৷ অনেক অভিভাবকরা তাদের অল্পবয়সী বাচ্চাদের সাথে এটি দেখতে চাইতে পারেন৷" + "এই প্রোগ্রামে এমন কিছু উপাদান আছে যা অনেক অভিভাবক ১৪ বছরের নীচের বাচ্চাদের জন্য উপযুক্ত নয় বলে মনে করতে পারেন।" + "এই প্রোগ্রামটিকে বিশেষভাবে প্রাপ্তবয়স্কদের দেখার জন্য তৈরি করা হয়েছে এবং তাই ১৭ বছরের নীচের বাচ্চাদের জন্য এটি উপযুক্ত নাও হতে পারে।" + "ফিল্ম রেটিং" + "সাধারণ দর্শকদের জন্য৷ বাচ্চারা এটি দেখলে অভিভাবকদের ক্ষুব্ধ হওয়ার মতো এতে কিছু নেই৷" + "অভিভাবকীয় নির্দেশিকার জন্য পরামর্শ দেওয়া হয়েছে৷ এমন কিছু উপাদান থাকতে পারে যা অভিভাবকদের পছন্দ অনুযায়ী তাদের অল্পবয়সী বাচ্চাদের জন্য উপযুক্ত নাও হতে পারে৷" + "অভিভাবকদের দৃঢ়ভাবে সতর্ক করা হচ্ছে৷ কিছু উপাদান অনুর্দ্ধ তেরো থেকে ঊনিশ বছর বয়সীদের জন্য উপযুক্ত নাও হতে পারে৷" + "সীমাবদ্ধ করা হয়েছে, এতে কিছু প্রাপ্তবয়স্কদের উপাদান আছে৷এই চলচ্চিত্রটি বাচ্চাদের সাথে নিয়ে যাওয়ার আগে অভিভাবকদের চলচ্চিত্রটি সম্পর্কে আরো জানার জন্য অনুরোধ করা হচ্ছে৷" + "১৭ এবং তার চেয়ে কম বয়সের কোনো ব্যক্তিকে অনুমতি দেওয়া হয়নি৷ সম্পূর্ণভাবে প্রাপ্তবয়স্কদের জন্য৷ বাচ্চাদের অনুমতি দেওয়া হয়নি৷" + diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml new file mode 100644 index 00000000..83e28250 --- /dev/null +++ b/res/values-bn-rBD/strings.xml @@ -0,0 +1,357 @@ + + + + + "মোনো" + "স্টিরিও" + "খেলার নিয়ন্ত্রণগুলি" + "চ্যানেলগুলি" + "টিভি বিকল্পগুলি" + "এই চ্যানেলটির জন্য প্লে নিয়ন্ত্রণগুলি অনুপলব্ধ" + "প্লে করুন বা বিরাম দিন" + "দ্রুত ফরওয়ার্ড" + "পেছনের দিকে যান" + "পরবর্তী" + "পূর্ববর্তী" + "প্রোগ্রাম গাইড" + "নতুন চ্যানেলগুলি উপলব্ধ" + "%1$s খুলুন" + "সাবটাইটেলগুলি" + "প্রদর্শন মোড" + "PIP" + "একাধিক-অডিও" + "আরো চ্যানেল পান" + "সেটিংস" + "TV (অ্যান্টেনা/কেবল)" + "কোনো প্রোগ্রাম তথ্য নেই" + "কোনো তথ্য নেই" + "অবরুদ্ধ চ্যানেল" + "অজানা ভাষা" + "সাবটাইটেল %1$d" + "সাবটাইটেলগুলি" + "বন্ধ করুন" + "ফর্ম্যাটিং কাস্টমাইজ করুন" + "সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি সেট করুন" + "প্রদর্শন মোড" + "একাধিক-অডিও" + "মনো" + "স্টিরিও" + "৫.১ সারাউন্ড" + "৭.১ সারাউন্ড" + "%d চ্যানেলগুলি" + "চ্যানেল তালিকা কাস্টমাইজ করুন" + "গোষ্ঠী নির্বাচন করুন" + "গোষ্ঠী নির্বাচন মুক্ত করুন" + "এর ভিত্তিতে গোষ্ঠীভুক্ত করুন" + "চ্যানেল উৎস" + "HD/SD" + "HD" + "SD" + "এর ভিত্তিতে গোষ্ঠীভুক্ত করুন" + "এই প্রোগ্রামটি অবরুদ্ধ করা হয়েছে" + "এই প্রোগ্রামটিতে কোনও রেটিং দেওয়া হয়নি" + "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷" + "ইনপুটটি অটো-স্ক্যান সমর্থন করে না" + "\'%s\' এর জন্য স্বয়ংক্রিয়-স্ক্যান শুরু করা যায়নি" + "সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি শুরু করা যায়নি৷" + + %1$dটি চ্যানেল যোগ করা হয়েছে + %1$dটি চ্যানেল যোগ করা হয়েছে + + "কোনো চ্যানেল যোগ করা হয়নি" + "অভিভাবকীয় নিয়ন্ত্রণগুলি" + "চালু করুন" + "বন্ধ করুন" + "অবরুদ্ধ চ্যানেলগুলি" + "সবগুলি অবরুদ্ধ করুন" + "সবগুলিকে অবরোধ মুক্ত করুন" + "লুকানো চ্যানেলগুলি" + "প্রোগ্রামের বিধিনিষেধগুলি" + "পিন পরিবর্তন করুন" + "রেটিং পদ্ধতি" + "রেটিংগুলি" + "রেটিংয়ের সমস্ত পদ্ধতি দেখুন" + "অন্যান্য দেশ" + "একটিও নেই" + "কোনো কিছুই নেই" + "রেটিং দেওয়া হয়নি" + "রেটিং ছাড়া প্রোগ্রাম ব্লক করুন" + "কোনো কিছুই নেই" + "উচ্চ মাত্রার বিধিনিষেধগুলি" + "মাঝারি মাত্রার বিধিনিষেধগুলি" + "কম মাত্রার বিধিনিষেধগুলি" + "কাস্টম" + "শিশুদের জন্য উপযুক্ত সামগ্রী" + "বয়স্ক শিশুদের জন্য উপযুক্ত সামগ্রী" + "তেরো থেকে উনিশ বছর বয়সীদের জন্য উপযুক্ত সামগ্রী" + "নিজে করা সম্পর্কিত বিধিনিষেধগুলি" + + + "%1$s এবং উপ-রেটিংগুলি" + "উপ-রেটিংগুলি" + "এই চ্যানেলটি দেখতে আপনার পিন লিখুন" + "এই প্রোগ্রামটি দেখতে আপনার পিন লিখুন" + "এই প্রোগ্রামটি %1$s রেটপ্রাপ্ত। এই প্রোগ্রামটি দেখার জন্য আপনার PIN লিখুন।" + "এই প্রোগ্রামটিতে কোনও রেটিং দেওয়া হয়নি এটি দেখতে আপনার পিন লিখুন" + "আপনার পিন লিখুন" + "অভিভাবকীয় নিয়ন্ত্রণগুলি সেট করতে, একটি পিন তৈরি করুন" + "নতুন পিন লিখুন" + "আপনার পিন নিশ্চিত করুন" + "আপনার বর্তমান পিন লিখুন" + + আপনি ৫ বার ভুল পিন প্রবেশ করিয়েছেন।\n%1$d সেকেন্ডের মধ্যে আবার চেষ্টা করুন। + আপনি ৫ বার ভুল পিন প্রবেশ করিয়েছেন।\n %1$d সেকেন্ডের মধ্যে আবার চেষ্টা করুন। + + "এই PINটি ভুল ছিল৷ আবার চেষ্টা করুন৷" + "আবার চেষ্টা করুন, পিন মেলেনি" + "আপনার ডাক পিন কোড লিখুন৷" + "Live TV অ্যাপটি টিভি চ্যানেলগুলির প্রোগ্রামের সম্পূর্ণ নির্দেশিকা প্রদান করার জন্য ডাক পিন কোড ব্যবহার করবে৷" + "আপনার ডাক পিন কোড লিখুন" + "অবৈধ ডাক পিন কোড" + "সেটিংস" + "চ্যানেল তালিকা কাস্টমাইজ করুন" + "আপনার প্রোগ্রাম গাইডের জন্য চ্যানেলগুলি নির্বাচন করুন" + "চ্যানেলের উৎসগুলি" + "নতুন চ্যানেলগুলি উপলব্ধ" + "অভিভাবকীয় নিয়ন্ত্রণগুলি" + "টাইমশিফ্ট" + "দেখার সময় রেকর্ড করুন সুতারং আপনি লাইভ প্রোগ্রামগুলিকে বিরাম দিতে বা রিওয়াইন্ড করতে পারেন৷\nসতর্কতা: সঞ্চয়স্থানের বেশি পরিমাণে ব্যবহার করার ফলে অভ্যন্তরীণ সঞ্চয়স্থানের কর্মক্ষমতা হ্রাস করতে পারে৷" + "মুক্ত উৎস লাইসেন্সগুলি" + "প্রতিক্রিয়া পাঠান" + "সংস্করণ" + "এই চ্যানেলটিকে দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" + "এই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" + "এই প্রোগ্রামটিতে কোনও রেটিং দেওয়া হয়নি।\nএটি দেখতে ডান দিকের বোতামটি টিপুন এবং আপনার পিন লিখুন" + "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷\nএই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" + "এই চ্যানেলটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" + "এই প্রোগ্রামটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" + "এই প্রোগ্রামটিতে কোনও রেটিং দেওয়া হয়নি\nডিফল্ট লাইভ TV অ্যাপে এটি দেখুন।" + "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷\nএই প্রোগ্রামটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" + "প্রোগ্রামটি অবরুদ্ধ" + "এই প্রোগ্রামটিতে কোনও রেটিং দেওয়া হয়নি" + "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷" + "কেবলমাত্র অডিও" + "সিগন্যাল দুর্বল" + "কোনো ইন্টারনেট সংযোগ নেই" + + %1$sপর্যন্ত এই চ্যালেনটি চালানো যাবে না কারণ অন্যান্য চ্যানেলগুলি রেকর্ড করা হচ্ছে৷ \n\nরেকর্ড করার সময়সূচী সামঞ্জস্য করতে ডান দিকে ক্লিক করুন৷ + %1$sপর্যন্ত এই চ্যালেনটি চালানো যাবে না কারণ অন্যান্য চ্যানেলগুলি রেকর্ড করা হচ্ছে৷ \n\nরেকর্ড করার সময়সূচী সামঞ্জস্য করতে ডান দিকে ক্লিক করুন৷ + + "কোনো শিরোনাম নেই" + "চ্যানেল অবরুদ্ধ করা হয়েছে" + "নতুন" + "উৎসগুলি" + + %1$dটি চ্যানেল + %1$dটি চ্যানেল + + "কোনো চ্যানেল উপলব্ধ নেই" + "নতুন" + "সেট আপ করা নেই" + "আরো উৎস পান" + "লাইভ চ্যানেল অফার করে এমন অ্যাপ্স ব্রাউজ করুন" + "নতুন চ্যানেলের সূত্রগুলি উপলব্ধ রয়েছে" + "নতুন চ্যানেলের সূত্রগুলিতে প্রদান করার জন্য চ্যানেল রয়েছে।\nসেগুলিকে এখনই সেট আপ করুন, বা চ্যানের সূত্রের সেটিংয়ের মধ্যে পরে এটি করুন।" + "এখনই সেট আপ করুন" + "ঠিক আছে, বুঝেছি" + + + "টিভি মেনু অ্যাক্সেস করতে ""নির্বাচন করুন টিপুন""৷" + "কোনো TV ইনপুট খুঁজে পাওয়া যায়নি" + "TV ইনপুট খুঁজে পাওয়া যায়নি" + "টিউনারের প্রকারটি উপযুক্ত নয়; টিউনার প্রকারের টিভি ইনপুটের জন্য দয়া করে লইভ চ্যানেলগুলি অ্যাপ্লিকেশানটি লঞ্চ করুন৷" + "টিউন করা ব্যর্থ হয়েছে" + "এই ক্রিয়াটিকে চালনা করার জন্য কোনো অ্যাপ্লিকেশান পাওয়া যায়নি৷" + "সমস্ত উৎস চ্যানেল লুকানো আছে৷\nদেখার জন্য কমপক্ষে একটি চ্যানেল নির্বাচন করুন৷" + "ভিডিওটি অপ্রত্যাশিতভাবে অনুপলব্ধ" + "\'ব্যাক\' কীটি সংযুক্ত ডিভাইসের ক্ষেত্রে ব্যবহারের জন্য৷ প্রস্থান করতে হোম বোতামটি টিপুন৷" + "টিভির তালিকাগুলি পড়ার জন্য লাইভ চ্যানেলগুলিকে অনুমতি নিতে হবে।" + "আপনার উৎসগুলি সেট আপ করুন" + "লাইভ চ্যানেলগুলি অ্যাপ্লিকেশানগুলির দ্বারা সরবরাহ করা স্ট্রিমিং চ্যানেলের সঙ্গে ঐতিহ্যগত টিভি চ্যানেলের সম্মিলিত অভিজ্ঞতা প্রদান করে৷ \n\nইতিমধ্যেই ইনস্টল থাকা চ্যানেল উৎসগুলি সেট আপ করার মাধ্যেমে শুরু করুন৷ অথবা লাইভ চ্যানেলগুলি অফার করে এমন অ্যাপ্লিকেশানগুলি পেতে Google Play স্টোর ব্রাউজ করুন৷" + "রেকডিং & সময়সূচী" + "১০ মিনিট" + "৩০ মিনিট" + "১ ঘণ্টা" + "৩ ঘণ্টা" + "সাম্প্রতিক" + "নির্ধারিত" + "সিরিজ" + "অন্যরা" + "চ্যানেলটি রেকর্ড করা যাবে না৷" + "প্রোগ্রামটি রেকর্ড করা যাবে না৷" + "%1$s রেকর্ড করার সময় নির্ধারিত হয়েছে" + "%1$sকে এখান থেকে %2$s পর্যন্ত রেকর্ড করা হচ্ছে" + "সম্পূর্ণ সময়সূচী" + + পরবর্তী %1$d দিন + পরবর্তী %1$d দিন + + + %1$d মিনিট + %1$d মিনিট + + + %1$dটি নতুন রেকডিং + %1$dটি নতুন রেকডিং + + + %1$dটি রেকর্ডিং + %1$dটি রেকর্ডিং + + + %1$dটি রেকর্ডিংয়ের সময় নির্ধারিত হয়েছে + %1$dটি রেকর্ডিংয়ের সময় নির্ধারিত হয়েছে + + "রেকর্ডিং বাতিল করুন" + "রেকডিং বন্ধ করুন" + "দেখুন" + "শুরু থেকে প্লে করুন" + "আবার প্লে করুন" + "মুছুন" + "রেকডিংগুলি মুছুন" + "আবার শুরু করুন" + "সিজন %1$s" + "সময়সূচী দেখুন" + "আরো পড়ুন" + "রেকডিংগুলি মুছুন" + "আপনি যে পর্বগুলিকে মুছতে চান সেগুলিকে নির্বাচন করুন। একবার মোছা হলে সেগুলিকে পুনরুদ্ধার করা যাবে না।" + "মোছার জন্য সেখানে কোনো রেকডিং নেই।" + "দেখা পর্বগুলিকে নির্বাচন করুন" + "সমস্ত পর্ব নির্বাচন করুন" + "সমস্ত পর্ব নির্বাচন মুক্ত করুন" + "%2$d এর মধ্যে %1$d মিনিট দেখা হয়েছে" + "%2$d এর মধ্যে %1$d সেকেন্ড দেখা হয়েছে" + "কখনই দেখা হয়নি" + + %2$dটির মধ্যে %1$dটি পর্ব মোছা হয়েছে + %2$dটির মধ্যে %1$dটি পর্ব মোছা হয়েছে + + "অগ্রাধিকার" + "সর্বোচ্চ" + "সর্বনিম্ন" + "না৷ %1$d" + "চ্যানেল" + "যে কোনো" + "অগ্রাধিকার চয়ন করুন" + "যখন একই সময়ে অনেকগুলি প্রোগ্রাম রেকর্ড করা হয় তখন শুধুমাত্র উচ্চ অগ্রাধিকারযুক্ত প্রোগ্রামগুলিকে রেকর্ড করা হবে৷" + "সংরক্ষণ করুন" + "একবার করা রেকর্ডিংগুলিতে সর্বোচ্চ অগ্রাধিকার রয়েছে" + "থামান" + "রেকডিং এর সময়সূচী দেখুন" + "শুধুমাত্র এই প্রোগ্রামটি" + "এখন - %1$s" + "সম্পূর্ণ সিরিজ…" + "যাই হোক, সময়সূচি নির্ধারণ করুন" + "বরং এটি রেকর্ড করুন" + "এই রেকর্ডিং বাতিল করুন" + "এখনই দেখুন" + "রেকডিংগুলি মুছুন..." + "রেকর্ড করা যাবে" + "রেকর্ডিংএর সময় নির্ধারিত হয়েছে" + "রেকর্ডিং দ্বন্দ্ব" + "রেকর্ড করা হচ্ছে" + "রেকডিং করা গেল না" + "প্রোগ্রামগুলি পড়া হচ্ছে" + "সাম্প্রতিক রেকর্ডিংগুলি দেখুন" + "%1$s এর রেকর্ডিং অসম্পূর্ণ।" + "%1$s এবং %2$s এর রেকর্ডিং সম্পূর্ণ।" + "%1$s, %2$s এবং %3$s এর রেকর্ডিং অসম্পূর্ণ।" + "অপর্যাপ্ত সঞ্চয়স্থান থাকার কারণে %1$s এর রেকর্ডিং সম্পূর্ণ হয়নি।" + "অপর্যাপ্ত সঞ্চয়স্থান থাকার কারণে %1$s এবং %2$s এর রেকর্ডিং সম্পূর্ণ হয়নি।" + "অপর্যাপ্ত সঞ্চয়স্থান থাকার কারণে %1$s, %2$s এবং %3$s এর রেকর্ডিং সম্পূর্ণ হয়নি।" + "DVR এর আরো সঞ্চয়স্থান দরকার" + "আপনি DVR এর মাধ্যমে প্রোগ্রাম রেকর্ড করতে পারবেন৷ তবে DVR কাজ করার জন্য আপনার ডিভাইসে এখন যথেষ্ঠ সঞ্চয়স্থান নেই৷ অনুগ্রহ করে %1$dGB বা তার থেকে বড় আকারের কোনো বাহ্যিক ডিভাইসের সাথে সংযোগ করুন এবং ডিভাইসের সঞ্চয়স্থান হিসাবে ফর্ম্যাট করতে পদক্ষেপগুলি অনুসরণ করুন৷" + "পর্যাপ্ত সঞ্চয়স্থান নেই" + "এখানে পর্যাপ্ত সঞ্চয়স্থান না থাকার কারণে এই প্রোগ্রামটিকে রেকর্ড করা যাবে না৷ বিদ্যমান কিছু রেকর্ডিং মোছার চেষ্টা করুন৷" + "সঞ্চয়স্থান অনুপস্থিত" + "রেকর্ড করা থামাবেন?" + "রেকর্ড করা সামগ্রী সংরক্ষণ করা হবে৷" + "এই প্রোগ্রামের সাথে রেকডিং দ্বন্দ্ব থাকায় %1$s এর রেকর্ডিং বন্ধ হবে। রেকর্ড করা সামগ্রী সংরক্ষিত হবে।" + "রেকর্ডিংয়ের যে সময় নির্ধারিত করা হয়েছে তাতে অন্যদের সমসয়ের সাথে বিরোধ ঘটাতে পারে।" + "রেকর্ডিং শুরু হয়েছে কিন্তু দ্বন্দ্বগুলি রয়েছে" + "%1$s রেকর্ড করা হবে৷" + "%1$s রেকর্ড করা হচ্ছে৷" + "%1$s এর কিছু অংশ রেকর্ড করা হবে না৷" + "%1$s এবং %2$s এর কিছু অংশ রেকর্ড করা হবে না৷" + "%1$s %2$sএর কিছু অংশ এবং আরো একটি সময়সূচী রেকর্ড করা হবে না৷" + + %1$s, %2$s এর কিছু অংশ এবং আরো %3$dটি সময়সূচী রেকর্ড করা হবে না৷ + %1$s, %2$s এর কিছু অংশ এবং আরো %3$dটি সময়সূচী রেকর্ড করা হবে না৷ + + "আপনি কি রেকর্ড করতে চান?" + "আপনি কতক্ষন রেকর্ড করতে চান?" + "ইতিমধ্যে সময়সূচী নির্ধারণ করা হয়েছে" + "একই প্রোগ্রাম ইতিমধ্যেই %1$s এ রেকর্ড করার জন্য নির্ধারণ করা হয়েছে।" + "ইতিমধ্যে রেকর্ড করা হয়েছে" + "এই প্রোগ্রামটি ইতিমধ্যে রেকর্ড করা হয়েছে। এটি DVR লাইব্রেরিতে উপলব্ধ।" + "সিরিজ রেকর্ডিংয়ের সময় নির্ধারিত হয়েছে" + + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। + + + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে এগুলির মধ্যে %3$dটি পর্ব রেকর্ড করা যাবে না৷ + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে এগুলির মধ্যে %3$dটি পর্ব রেকর্ড করা যাবে না৷ + + + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে এটি এবং অন্য সিরিজের %3$dটি পর্ব রেকর্ড করা যাবে না৷ + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে এটি এবং অন্য সিরিজের %3$dটি পর্ব রেকর্ড করা যাবে না৷ + + + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে৷ বিরোধগুলির কারণে অন্য সিরিজের ১টি পর্ব রেকর্ড করা যাবে না৷ + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে৷ বিরোধগুলির কারণে অন্য সিরিজের ১টি পর্ব রেকর্ড করা যাবে না৷ + + + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে অন্য সিরিজের %3$dটি পর্ব রেকর্ড করা যাবে না৷ + %2$s এর জন্য %1$dটি রেকর্ডিংয়ের সময়সূচি নির্ধারণ করা হয়েছে। বিরোধগুলির কারণে অন্য সিরিজের %3$dটি পর্ব রেকর্ড করা যাবে না৷ + + "রেকর্ড করা প্রোগ্রাম খুঁজে পাওয়া যায়নি৷" + "সম্পর্কিত রেকর্ডিং" + + %1$dটি রেকর্ডিং + %1$dটি রেকর্ডিং + + " / " + "রেকর্ড করার সময়সূচী থেকে %1$s সরানো হয়েছে" + "টিউনার না থাকার কারণে আংশিকভাবে রেকর্ড করা হবে৷" + "টিউনার না থাকার কারণে রেকর্ড করা হবে না৷" + "এখনো পর্যন্ত কোনো রেকর্ডিংয়ের জন্য সময়সূচী নির্ধারণ করা হয়নি।\nআপনি প্রোগ্রাম গাইড থেকে রেকর্ডিংয়ের সময়সূচী নির্ধারণ করতে পারেন।" + + %1$dটি রেকডিং দ্বন্দ্ব + %1$dটি রেকডিং দ্বন্দ্ব + + "সিরিজ সেটিংস" + "সিরিজ রেকডিং শুরু করুন" + "সিরিজ রেকডিং বন্ধ করুন" + "সিরিজি রেকর্ড করা বন্ধ করতে চান?" + "রেকর্ড করা পর্বগুলি DVR লাইব্রেরিতে উপলব্ধ থাকবে৷" + "বন্ধ করুন" + "এখন কোনো পর্বের সম্প্রচার করা হচ্ছে না।" + "কোনো পর্ব উপলব্ধ নেই।\nএকবার উপলব্ধ হলে সেগুলিকে রেকর্ড করা হবে।" + + (%1$d মিনিট) + (%1$d মিনিট) + + "আজ" + "আগামীকাল" + "গতকাল" + "আজ %1$s টায়" + "আগামীকাল %1$s" + "স্কোর" + "রেকর্ড করা প্রোগ্রামগুলি" + diff --git a/res/values-bn-v23/strings.xml b/res/values-bn-v23/strings.xml deleted file mode 100644 index b3134242..00000000 --- a/res/values-bn-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "চ্যানেলগুলি" - diff --git a/res/values-bn/arrays.xml b/res/values-bn/arrays.xml deleted file mode 100644 index 76a0792a..00000000 --- a/res/values-bn/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "স্বাভাবিক" - "সম্পূর্ণ" - "জুম" - - - "সমস্ত চ্যানেল" - "পরিবার/শিশু" - "খেলাধুলা" - "কেনাকাটা" - "চলচ্চিত্র" - "কমেডি" - "ভ্রমণ" - "নাটক" - "শিক্ষা" - "প্রাণী/বন্যজীবন" - "সংবাদ" - "গেমিং" - "কলা" - "বিনোদন" - "জীবনশৈলী" - "সঙ্গীত" - "প্রিমিয়ার" - "প্রযুক্তি/বিজ্ঞান" - - - "লাইভ চ্যানেলগুলি" - "সামগ্রী আবিষ্কার করার একটি সহজ উপায়" - "অ্যাপ্লিকেশানগুলি ডাউনলোড করুন, আর আরো বেশি চ্যানেল পান" - "আপনার চ্যানেল লাইন-আপ কাস্টমাইজ করুন" - - - "টিভিতে চ্যানেল দেখার মত আপনার অ্যাপ্লিকেশানগুলি থেকে সামগ্রী দেখুন৷" - "টিভিতে চ্যানেলগুলি মত কোনো \nপরিচিত গাইড এবং সহজ ইন্টারফেসের মাধ্যমে আপনার অ্যাপ্লিকেশানগুলি থেকে সামগ্রী ব্রাউজ করুন৷" - "লাইভ চ্যনেলগুলি অফার করে এমন অ্যাপ্লিকেশানগুলিকে ইনস্টল করার মাধ্যেমে আরো চ্যানেল যোগ করুন৷ \n টিভি মেনুর মধ্যে থাকা লিঙ্কটি ব্যবহার করে Google Play স্টোরে আরো উপযুক্ত অ্যাপ্লিকেশানগুলি খুঁজুন৷" - "আপনার চ্যানেল তালিকা কাস্টমাইজ করতে আপনার নতুন ইনস্টল করা চ্যানেল উৎসগুলি সেট আপ করুন৷ \nশুরু করতে সেটিংস মেনুতে থাকা চ্যানেল উৎসগুলি চয়ন করুন৷" - - diff --git a/res/values-bn/rating_system_strings.xml b/res/values-bn/rating_system_strings.xml deleted file mode 100644 index 81c9593b..00000000 --- a/res/values-bn/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "প্রোগ্র্রামগুলির মধ্যে ১৫ বছরের চাইতে কমবয়সী শিশুদের জন্য অনুপযুক্ত উপাদান থাকতে পারে এবং অভিভাবকদের বিবেচনার ভিত্তিতে সেগুলি ব্যবহার করা উচিৎ৷" - "প্রোগ্র্রামগুলির মধ্যে থাকা উপাদান ১৯ বছরের চেয়ে কমবয়সীদের জন্য অনুপযুক্ত হতে পারে এবং এগুলি ১৯ বছরের চেয়ে কমবয়সী বালকদের জন্য উপযুক্ত নয়৷" - "প্রস্তাবিত সংলাপ" - "অমার্জিত ভাষা" - "যৌন সামগ্রী" - "হিংস্রতা" - "কল্পনাপ্রসূত হিংস্রতা" - "এই প্রোগ্রামটিকে সব বয়সী বাচ্চাদের জন্য উপযুক্ত হিসাবে তৈরি করা হয়েছে।" - "এই প্রোগ্রামটিকে ৭ বছর ও তার বেশি বয়সী বাচ্চাদের জন্য তৈরি করা হয়েছে।" - "বেশিরভাগ অভিভাবক এই প্রোগ্রামটিকে সব বয়সীদের জন্য উপযুক্ত বলে মনে করবেন।" - "এই প্রোগ্রামটিতে অল্পবয়সী বাচ্চাদের জন্য অনুপযুক্ত কিছু উপাদান রয়েছে বলে অভিভাবকরা মনে করতে পারেন৷ অনেক অভিভাবকরা তাদের অল্পবয়সী বাচ্চাদের সাথে এটি দেখতে চাইতে পারেন৷" - "এই প্রোগ্রামে এমন কিছু উপাদান আছে যা অনেক অভিভাবক ১৪ বছরের নীচের বাচ্চাদের জন্য উপযুক্ত নয় বলে মনে করতে পারেন।" - "এই প্রোগ্রামটিকে বিশেষভাবে প্রাপ্তবয়স্কদের দেখার জন্য তৈরি করা হয়েছে এবং তাই ১৭ বছরের নীচের বাচ্চাদের জন্য এটি উপযুক্ত নাও হতে পারে।" - "ফিল্ম রেটিং" - "সাধারণ দর্শকদের জন্য৷ বাচ্চারা এটি দেখলে অভিভাবকদের ক্ষুব্ধ হওয়ার মতো এতে কিছু নেই৷" - "অভিভাবকীয় নির্দেশিকার জন্য পরামর্শ দেওয়া হয়েছে৷ এমন কিছু উপাদান থাকতে পারে যা অভিভাবকদের পছন্দ অনুযায়ী তাদের অল্পবয়সী বাচ্চাদের জন্য উপযুক্ত নাও হতে পারে৷" - "অভিভাবকদের দৃঢ়ভাবে সতর্ক করা হচ্ছে৷ কিছু উপাদান অনুর্দ্ধ তেরো থেকে ঊনিশ বছর বয়সীদের জন্য উপযুক্ত নাও হতে পারে৷" - "সীমাবদ্ধ করা হয়েছে, এতে কিছু প্রাপ্তবয়স্কদের উপাদান আছে৷এই চলচ্চিত্রটি বাচ্চাদের সাথে নিয়ে যাওয়ার আগে অভিভাবকদের চলচ্চিত্রটি সম্পর্কে আরো জানার জন্য অনুরোধ করা হচ্ছে৷" - "১৭ এবং তার চেয়ে কম বয়সের কোনো ব্যক্তিকে অনুমতি দেওয়া হয়নি৷ সম্পূর্ণভাবে প্রাপ্তবয়স্কদের জন্য৷ বাচ্চাদের অনুমতি দেওয়া হয়নি৷" - diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml deleted file mode 100644 index 52938ab2..00000000 --- a/res/values-bn/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "মোনো" - "স্টিরিও" - "খেলার নিয়ন্ত্রণগুলি" - "সাম্প্রতিক চ্যানেলগুলি" - "টিভি বিকল্পগুলি" - "PIP বিকল্পগুলি" - "এই চ্যানেলটির জন্য প্লে নিয়ন্ত্রণগুলি অনুপলব্ধ" - "প্লে করুন বা বিরাম দিন" - "দ্রুত ফরওয়ার্ড" - "পেছনের দিকে যান" - "পরবর্তী" - "পূর্ববর্তী" - "প্রোগ্রাম গাইড" - "নতুন চ্যানেলগুলি উপলব্ধ" - "%1$s খুলুন" - "সাবটাইটেলগুলি" - "প্রদর্শন মোড" - "PIP" - "চালু" - "বন্ধ" - "একাধিক-অডিও" - "আরো চ্যানেল পান" - "সেটিংস" - "উৎস" - "সোয়াইপ করুন" - "চালু" - "বন্ধ" - "আওয়াজ" - "প্রধান" - "PIP উইন্ডো" - "লেআউট" - "ডানদিকে নীচে" - "ডানদিকে শীর্ষে" - "বামদিকে শীর্ষে" - "বামদিকে নীচে" - "পাশাপাশি" - "আকার" - "বড়" - "ক্ষুদ্র" - "ইনপুট উৎস" - "TV (অ্যান্টেনা/কেবল)" - "কোনো প্রোগ্রাম তথ্য নেই" - "কোনো তথ্য নেই" - "অবরুদ্ধ চ্যানেল" - "অজানা ভাষা" - "সাবটাইটেলগুলি" - "বন্ধ করুন" - "ফর্ম্যাটিং কাস্টমাইজ করুন" - "সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি সেট করুন" - "প্রদর্শন মোড" - "একাধিক-অডিও" - "মনো" - "স্টিরিও" - "৫.১ সারাউন্ড" - "৭.১ সারাউন্ড" - "%d চ্যানেলগুলি" - "চ্যানেল তালিকা কাস্টমাইজ করুন" - "গোষ্ঠী নির্বাচন করুন" - "গোষ্ঠী নির্বাচন মুক্ত করুন" - "এর ভিত্তিতে গোষ্ঠীভুক্ত করুন" - "চ্যানেল উৎস" - "HD/SD" - "HD" - "SD" - "এর ভিত্তিতে গোষ্ঠীভুক্ত করুন" - "এই প্রোগ্রামটি অবরুদ্ধ করা হয়েছে" - "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷" - "ইনপুটটি অটো-স্ক্যান সমর্থন করে না" - "\'%s\' এর জন্য স্বয়ংক্রিয়-স্ক্যান শুরু করা যায়নি" - "সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি শুরু করা যায়নি৷" - - %1$dটি চ্যানেল যোগ করা হয়েছে - %1$dটি চ্যানেল যোগ করা হয়েছে - - "কোনো চ্যানেল যোগ করা হয়নি" - "ট্যিউনার" - "অভিভাবকীয় নিয়ন্ত্রণগুলি" - "চালু করুন" - "বন্ধ করুন" - "অবরুদ্ধ চ্যানেলগুলি" - "সবগুলি অবরুদ্ধ করুন" - "সবগুলিকে অবরোধ মুক্ত করুন" - "লুকানো চ্যানেলগুলি" - "প্রোগ্রামের বিধিনিষেধগুলি" - "পিন পরিবর্তন করুন" - "রেটিং পদ্ধতি" - "রেটিংগুলি" - "রেটিংয়ের সমস্ত পদ্ধতি দেখুন" - "অন্যান্য দেশ" - "একটিও নেই" - "কোনো কিছুই নেই" - "কোনো কিছুই নেই" - "উচ্চ মাত্রার বিধিনিষেধগুলি" - "মাঝারি মাত্রার বিধিনিষেধগুলি" - "কম মাত্রার বিধিনিষেধগুলি" - "কাস্টম" - "শিশুদের জন্য উপযুক্ত সামগ্রী" - "বয়স্ক শিশুদের জন্য উপযুক্ত সামগ্রী" - "তেরো থেকে উনিশ বছর বয়সীদের জন্য উপযুক্ত সামগ্রী" - "নিজে করা সম্পর্কিত বিধিনিষেধগুলি" - - - "%1$s এবং উপ-রেটিংগুলি" - "উপ-রেটিংগুলি" - "এই চ্যানেলটি দেখতে আপনার পিন লিখুন" - "এই প্রোগ্রামটি দেখতে আপনার পিন লিখুন" - "এই প্রোগ্রামটি %1$s রেটপ্রাপ্ত। এই প্রোগ্রামটি দেখার জন্য আপনার PIN লিখুন।" - "আপনার পিন লিখুন" - "অভিভাবকীয় নিয়ন্ত্রণগুলি সেট করতে, একটি পিন তৈরি করুন" - "নতুন পিন লিখুন" - "আপনার পিন নিশ্চিত করুন" - "আপনার বর্তমান পিন লিখুন" - - আপনি ৫ বার ভুল পিন প্রবেশ করিয়েছেন।\n%1$d সেকেন্ডের মধ্যে আবার চেষ্টা করুন। - আপনি ৫ বার ভুল পিন প্রবেশ করিয়েছেন।\n %1$d সেকেন্ডের মধ্যে আবার চেষ্টা করুন। - - "এই PINটি ভুল ছিল৷ আবার চেষ্টা করুন৷" - "আবার চেষ্টা করুন, পিন মেলেনি" - "সেটিংস" - "চ্যানেল তালিকা কাস্টমাইজ করুন" - "আপনার প্রোগ্রাম গাইডের জন্য চ্যানেলগুলি নির্বাচন করুন" - "চ্যানেলের উৎসগুলি" - "নতুন চ্যানেলগুলি উপলব্ধ" - "অভিভাবকীয় নিয়ন্ত্রণগুলি" - "মুক্ত উৎস লাইসেন্সগুলি" - "মুক্ত উৎস লাইসেন্সগুলি" - "সংস্করণ" - "এই চ্যানেলটিকে দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" - "এই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" - "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷\nএই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন" - "এই চ্যানেলটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" - "এই প্রোগ্রামটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" - "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷\nএই প্রোগ্রামটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷" - "প্রোগ্রামটি অবরুদ্ধ" - "এই প্রোগ্রামটি %1$s রেট প্রাপ্ত৷" - "কেবলমাত্র অডিও" - "সিগন্যাল দুর্বল" - "কোনো ইন্টারনেট সংযোগ নেই" - - %1$sপর্যন্ত এই চ্যালেনটি চালানো যাবে না কারণ অন্যান্য চ্যানেলগুলি রেকর্ড করা হচ্ছে৷ \n\nরেকর্ড করার সময়সূচী সামঞ্জস্য করতে ডান দিকে ক্লিক করুন৷ - %1$sপর্যন্ত এই চ্যালেনটি চালানো যাবে না কারণ অন্যান্য চ্যানেলগুলি রেকর্ড করা হচ্ছে৷ \n\nরেকর্ড করার সময়সূচী সামঞ্জস্য করতে ডান দিকে ক্লিক করুন৷ - - "কোনো শিরোনাম নেই" - "চ্যানেল অবরুদ্ধ করা হয়েছে" - "নতুন" - "উৎসগুলি" - - %1$dটি চ্যানেল - %1$dটি চ্যানেল - - "কোনো চ্যানেল উপলব্ধ নেই" - "নতুন" - "সেট আপ করা নেই" - "আরো উৎস পান" - "লাইভ চ্যানেল অফার করে এমন অ্যাপ্স ব্রাউজ করুন" - "নতুন চ্যানেলের সূত্রগুলি উপলব্ধ রয়েছে" - "নতুন চ্যানেলের সূত্রগুলিতে প্রদান করার জন্য চ্যানেল রয়েছে।\nসেগুলিকে এখনই সেট আপ করুন, বা চ্যানের সূত্রের সেটিংয়ের মধ্যে পরে এটি করুন।" - "এখনই সেট আপ করুন" - "ঠিক আছে, বুঝেছি" - - - "টিভি মেনু অ্যাক্সেস করতে ""নির্বাচন করুন টিপুন""৷" - "কোনো TV ইনপুট খুঁজে পাওয়া যায়নি" - "TV ইনপুট খুঁজে পাওয়া যায়নি" - "PIP সমর্থিত নয়" - "PIP এর সাথে দেখানো যেতে পারে এমন কোনো উপলব্ধ ইনপুট নেই" - "টিউনারের প্রকারটি উপযুক্ত নয়; টিউনার প্রকারের টিভি ইনপুটের জন্য দয়া করে লইভ চ্যানেলগুলি অ্যাপ্লিকেশানটি লঞ্চ করুন৷" - "টিউন করা ব্যর্থ হয়েছে" - "এই ক্রিয়াটিকে চালনা করার জন্য কোনো অ্যাপ্লিকেশান পাওয়া যায়নি৷" - "সমস্ত উৎস চ্যানেল লুকানো আছে৷\nদেখার জন্য কমপক্ষে একটি চ্যানেল নির্বাচন করুন৷" - "ভিডিওটি অপ্রত্যাশিতভাবে অনুপলব্ধ" - "\'ব্যাক\' কীটি সংযুক্ত ডিভাইসের ক্ষেত্রে ব্যবহারের জন্য৷ প্রস্থান করতে হোম বোতামটি টিপুন৷" - "টিভির তালিকাগুলি পড়ার জন্য লাইভ চ্যানেলগুলিকে অনুমতি নিতে হবে।" - "আপনার উৎসগুলি সেট আপ করুন" - "লাইভ চ্যানেলগুলি অ্যাপ্লিকেশানগুলির দ্বারা সরবরাহ করা স্ট্রিমিং চ্যানেলের সঙ্গে ঐতিহ্যগত টিভি চ্যানেলের সম্মিলিত অভিজ্ঞতা প্রদান করে৷ \n\nইতিমধ্যেই ইনস্টল থাকা চ্যানেল উৎসগুলি সেট আপ করার মাধ্যেমে শুরু করুন৷ অথবা লাইভ চ্যানেলগুলি অফার করে এমন অ্যাপ্লিকেশানগুলি পেতে Google Play স্টোর ব্রাউজ করুন৷" - "রেকডিং & সময়সূচী" - "১০ মিনিট" - "৩০ মিনিট" - "১ ঘণ্টা" - "৩ ঘণ্টা" - "সাম্প্রতিক" - "নির্ধারিত" - "সিরিজ" - "অন্যরা" - "চ্যানেলটি রেকর্ড করা যাবে না৷" - "প্রোগ্রামটি রেকর্ড করা যাবে না৷" - "%1$s রেকর্ড করার সময় নির্ধারিত হয়েছে" - "%1$sকে এখান থেকে %2$s পর্যন্ত রেকর্ড করা হচ্ছে" - "সম্পূর্ণ সময়সূচী" - - পরবর্তী %1$d দিন - পরবর্তী %1$d দিন - - - %1$d মিনিট - %1$d মিনিট - - - %1$dটি নতুন রেকডিং - %1$dটি নতুন রেকডিং - - - %1$dটি রেকর্ডিং - %1$dটি রেকর্ডিং - - - %1$dটি রেকর্ডিংয়ের সময় নির্ধারিত হয়েছে - %1$dটি রেকর্ডিংয়ের সময় নির্ধারিত হয়েছে - - "দেখুন" - "শুরু থেকে প্লে করুন" - "পুনরায় প্লে করুন" - "মুছুন" - "রেকডিংগুলি মুছুন" - "পুনরায় শুরু করুন" - "সিজন %1$s" - "সময়সূচী দেখুন" - "আরো পড়ুন" - "রেকডিংগুলি মুছুন" - "আপনি যে পর্বগুলিকে মুছতে চান সেগুলিকে নির্বাচন করুন। একবার মোছা হলে সেগুলিকে পুনরুদ্ধার করা যাবে না।" - "মোছার জন্য সেখানে কোনো রেকডিং নেই।" - "দেখা পর্বগুলিকে নির্বাচন করুন" - "সমস্ত পর্ব নির্বাচন করুন" - "সমস্ত পর্ব নির্বাচন মুক্ত করুন" - "%2$d এর মধ্যে %1$d মিনিট দেখা হয়েছে" - "%2$d এর মধ্যে %1$d সেকেন্ড দেখা হয়েছে" - "কখনই দেখা হয়নি" - - %2$dটির মধ্যে %1$dটি পর্ব মোছা হয়েছে - %2$dটির মধ্যে %1$dটি পর্ব মোছা হয়েছে - - "অগ্রাধিকার" - "সর্বোচ্চ" - "সর্বনিম্ন" - "না৷ %1$d" - "চ্যানেল" - "যে কোনো" - "অগ্রাধিকার চয়ন করুন" - "যখন একই সময়ে অনেকগুলি প্রোগ্রাম রেকর্ড করা হয় তখন শুধুমাত্র উচ্চ অগ্রাধিকারযুক্ত প্রোগ্রামগুলিকে রেকর্ড করা হবে৷" - "সংরক্ষণ করুন" - "একবার করা রেকর্ডিংগুলিতে সর্বোচ্চ অগ্রাধিকার রয়েছে" - "বাতিল করুন" - "বাতিল করুন" - "মুছে দিন" - "থামান" - "রেকডিং এর সময়সূচী দেখুন" - "শুধুমাত্র এই প্রোগ্রামটি" - "এখন - %1$s" - "সম্পূর্ণ সিরিজ…" - "যাই হোক, সময়সূচি নির্ধারণ করুন" - "বরং এটি রেকর্ড করুন" - "এই রেকর্ডিং বাতিল করুন" - "এখনই দেখুন" - "রেকর্ড করা যাবে" - "রেকর্ডিংএর সময় নির্ধারিত হয়েছে" - "রেকর্ডিং দ্বন্দ্ব" - "রেকর্ড করা হচ্ছে" - "রেকডিং করা গেল না" - "রেকর্ডিংয়ের সময়সূচীগুলি তৈরি করতে প্রোগ্রামগুলি পড়া হচ্ছে" - "প্রোগ্রামগুলি পড়া হচ্ছে" - - - "DVR এর আরো সঞ্চয়স্থান দরকার" - "আপনি DVR এর মাধ্যমে প্রোগ্রাম রেকর্ড করতে পারবেন৷ তবে DVR কাজ করার জন্য আপনার ডিভাইসে এখন যথেষ্ঠ সঞ্চয়স্থান নেই৷ অনুগ্রহ করে %1$sGB বা তার থেকে বড় আকারের কোনো বাহ্যিক ডিভাইসের সাথে সংযোগ করুন এবং ডিভাইসের সঞ্চয়স্থান হিসাবে ফর্ম্যাট করতে পদক্ষেপগুলি অনুসরণ করুন৷" - "সঞ্চয়স্থান অনুপস্থিত" - "DVR দ্বারা ব্যবহৃত কিছু সঞ্চয়স্থান অনুপস্থিত৷ DVR পুনরায় সক্ষম করার আগে অনুগ্রহ করে আপনার আগে ব্যবহার করা বাহ্যিক ড্রাইভ সংযোগ করুন৷ অথবা, যদি সঞ্চয়স্থানটি আর উপলব্ধ না থাকে তবে আপনি সেটিকে মুছে দিতে পারবেন৷" - "সঞ্চয়স্থান মুছতে চান?" - "আপনার রেকর্ড করা সমস্ত সামগ্রী এবং সময়সূচী মুছে যাবে৷" - "রেকর্ড করা থামাবেন?" - "রেকর্ড করা সামগ্রী সংরক্ষণ করা হবে৷" - - - "রেকর্ডিংয়ের যে সময় নির্ধারিত করা হয়েছে তাতে অন্যদের সমসয়ের সাথে বিরোধ ঘটাতে পারে।" - "রেকর্ডিং শুরু হয়েছে কিন্তু দ্বন্দ্বগুলি রয়েছে" - "%1$s রেকর্ড করা হবে৷" - "%1$s রেকর্ড করা হচ্ছে৷" - "%1$s এর কিছু অংশ রেকর্ড করা হবে না৷" - "%1$s এবং %2$s এর কিছু অংশ রেকর্ড করা হবে না৷" - "%1$s %2$sএর কিছু অংশ এবং আরো একটি সময়সূচী রেকর্ড করা হবে না৷" - - %1$s, %2$s এর কিছু অংশ এবং আরো %3$dটি সময়সূচী রেকর্ড করা হবে না৷ - %1$s, %2$s এর কিছু অংশ এবং আরো %3$dটি সময়সূচী রেকর্ড করা হবে না৷ - - "আপনি কি রেকর্ড করতে চান?" - "আপনি কতক্ষন রেকর্ড করতে চান?" - "ইতিমধ্যে সময়সূচী নির্ধারণ করা হয়েছে" - "একই প্রোগ্রাম ইতিমধ্যেই %1$s এ রেকর্ড করার জন্য নির্ধারণ করা হয়েছে।" - "ইতিমধ্যে রেকর্ড করা হয়েছে" - "এই প্রোগ্রামটি ইতিমধ্যে রেকর্ড করা হয়েছে। এটি DVR লাইব্রেরিতে উপলব্ধ।" - - - - - - - - - "রেকর্ড করা প্রোগ্রাম খুঁজে পাওয়া যায়নি৷" - "সম্পর্কিত রেকর্ডিং" - "(প্রোগ্রামের কোনো বিবরণ নেই)" - - %1$dটি রেকর্ডিং - %1$dটি রেকর্ডিং - - " / " - "রেকর্ড করার সময়সূচী থেকে %1$s সরানো হয়েছে" - "টিউনার না থাকার কারণে আংশিকভাবে রেকর্ড করা হবে৷" - "টিউনার না থাকার কারণে রেকর্ড করা হবে না৷" - "এখনো পর্যন্ত কোনো রেকর্ডিংয়ের জন্য সময়সূচী নির্ধারণ করা হয়নি।\nআপনি প্রোগ্রাম গাইড থেকে রেকর্ডিংয়ের সময়সূচী নির্ধারণ করতে পারেন।" - - %1$dটি রেকডিং দ্বন্দ্ব - %1$dটি রেকডিং দ্বন্দ্ব - - "সিরিজ সেটিংস" - "সিরিজ রেকডিং শুরু করুন" - "সিরিজ রেকডিং বন্ধ করুন" - "সিরিজি রেকর্ড করা বন্ধ করতে চান?" - "রেকর্ড করা পর্বগুলি DVR লাইব্রেরিতে উপলব্ধ থাকবে৷" - "বন্ধ করুন" - "কোনো পর্ব উপলব্ধ নেই।\nএকবার উপলব্ধ হলে সেগুলিকে রেকর্ড করা হবে।" - - (%1$d মিনিট) - (%1$d মিনিট) - - "আজ" - "আগামীকাল" - "গতকাল" - "আজ %1$s টায়" - "আগামীকাল %1$s" - "স্কোর" - diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 3ab61ffb..e26cc99d 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -20,9 +20,8 @@ "mono" "estèreo" "Controls de reproducció" - "Canals recents" + "Canals" "Opcions de TV" - "Opcions de PIP" "Els controls de reproducció no estan disponibles en aquest canal" "Reprodueix o atura" "Avança ràpidament" @@ -35,33 +34,15 @@ "Subtítols" "Mode visualitz." "PIP" - "Activada" - "Desactivada" "Multiàudio" - "Obtén més canals" + "Troba més canals" "Configuració" - "Font" - "Canvia" - "Activada" - "Desactivada" - "So" - "Principal" - "Finestra PIP" - "Disseny" - "A baix a dreta" - "A dalt a dreta" - "A dalt a esq." - "A baix a esq." - "En paral·lel" - "Mida" - "Gran" - "Petita" - "Font d\'entrada" "Televisió (antena/cable)" "No hi ha informació del programa" "No hi ha informació" "Canal bloquejat" - "Idioma desconegut" + "Idioma desconegut" + "Subtítols en %1$d" "Subtítols ocults" "Desactivat" "Personalitza format" @@ -83,6 +64,7 @@ "SD" "Agrupa per" "Aquest programa està bloquejat" + "Aquest programa no s\'ha classificat" "Aquest programa està classificat com a %1$s" "L\'entrada no és compatible amb la cerca automàtica." "No es pot iniciar la cerca automàtica de: %s." @@ -92,7 +74,6 @@ S\'ha afegit %1$d canal "No s\'ha afegit cap canal." - "Sintonitzador" "Controls dels pares" "Activats" "Desactivats" @@ -108,6 +89,8 @@ "Altres països" "Cap" "Cap" + "Sense classificar" + "Bloqueja prog. sense classificar" "Cap" "Restriccions altes" "Restricc. mitjanes" @@ -124,6 +107,7 @@ "Introducció del PIN per mirar aquest canal" "Introducció del PIN per mirar aquest programa" "Aquest programa està classificat com a %1$s. Introdueix el PIN per mirar-lo" + "Aquest programa no s\'ha classificat. Introdueix el PIN per veure\'l." "Introducció del PIN" "Per definir els controls dels pares, crea un PIN" "Introdueix el PIN nou" @@ -135,22 +119,31 @@ "El PIN era incorrecte. Torna-ho a provar." "Torna-ho a provar. El PIN no coincideix." + "Introdueix el codi postal." + "L\'aplicació TV en directe farà servir el codi postal per oferir una programació completa dels canals de televisió." + "Introdueix el codi postal" + "El codi postal no és vàlid" "Configuració" "Personalitza la llista de canals" "Tria canals per a la programació" "Fonts de canals" "Nous canals disponibles" "Controls parentals" + "Desplaçament temporal" + "Enregistra els programes en directe mentre els mires per poder-los posar en pausa o rebobinar.\nAdvertiment: aquesta opció suposa un ús intens de l\'emmagatzematge intern, i això en pot disminuir la vida útil." "Llicències de programari lliure" - "Llicències de programari lliure" + "Envia suggeriments" "Versió" "Per veure aquest canal, prem el botó dret i introdueix el PIN." "Per veure aquest programa, prem el botó dret i introdueix el PIN." + "Aquest programa no s\'ha classificat.\nPer veure\'l, prem el botó dret i introdueix el PIN." "Aquest programa està classificat com a %1$s.\nPer veure\'l, prem el botó dret i introdueix el PIN." "Per mirar aquest canal, fes servir l\'aplicació Televisió en directe predeterminada." "Per mirar aquest programa, fes servir l\'aplicació Televisió en directe predeterminada." + "Aquest programa no s\'ha classificat.\nPer veure\'l, utilitza l\'aplicació Televisió en directe predeterminada." "Aquest programa té la classificació següent: %1$s.\nPer mirar-lo, fes servir l\'aplicació Televisió en directe predeterminada." "El programa està bloquejat." + "Aquest programa no s\'ha classificat" "Aquest programa està classificat com a %1$s" "Només àudio" "El senyal és feble" @@ -169,8 +162,8 @@ "No hi ha cap canal disponible" "Nou" - "No s\'ha configurat" - "Obtén més fonts" + "No configurat" + "Troba més fonts" "Navega per les aplicacions que ofereixen TV en directe" "Hi ha noves fonts de canals disponibles" "Les noves fonts de canals tenen canals per oferir.\nConfigura-les ara o fes-ho més tard des de la seva secció de configuració." @@ -181,8 +174,6 @@ "Prem SELECCIONA"" per accedir al menú de TV." "No s\'ha trobat cap entrada de televisió" "No es troba l\'entrada de televisió" - "No s\'admet la funció PIP" - "No hi ha cap entrada disponible que es pugui mostrar amb PIP" "El sintonitzador no és apte; inicia l\'aplicació Canals en directe per accedir a l\'entrada del sintonitzador." "No s\'ha pogut sintonitzar" "No s\'ha trobat cap aplicació per processar aquesta acció." @@ -191,7 +182,7 @@ "La tecla ENRERE és per als dispositius connectats. Prem el botó INICI per sortir." "Canals en directe necessita permís per consultar les programacions de TV." "Configura les teves fonts" - "L\'aplicació TV en directe combina l\'experiència dels canals de televisió tradicionals amb els canals de reproducció en temps real proporcionats per les aplicacions. \n\nPer començar, configura les fonts de canals que ja hi ha instal·lades. També pots navegar per Google Play Store per descobrir més aplicacions que ofereixin TV en directe." + "TV en directe combina l\'experiència dels canals de televisió tradicionals amb els canals de reproducció en temps real proporcionats per les aplicacions. \n\nPer començar, configura les fonts de canals que ja hi ha instal·lades. També pots navegar per Google Play Store per descobrir més aplicacions que ofereixin TV en directe." "Enregistraments i horaris" "10 minuts" "30 minuts" @@ -224,6 +215,8 @@ %1$d enregist. programats %1$d enregistr. programat + "Cancel·la l\'enregistrament" + "Atura l\'enregistrament" "Reprodueix" "Reprodueix des del principi" "Reprèn la reproducció" @@ -256,9 +249,6 @@ "Quan hi hagi massa programes per enregistrar a la mateixa hora, només s\'enregistraran els que tinguin una prioritat més elevada." "Desa" "Els enregistraments únics tenen la prioritat més alta" - "Cancel·la" - "Cancel·la" - "No recordis" "Atura" "Mostra programa d\'enregistrament" "Només aquest programa" @@ -268,25 +258,28 @@ "Enregistra aquest programa" "Cancel·la aquest enregistrament" "Mira ara" + "Suprimeix els enregistraments…" "Enregistrable" "Enregistrament programat" "Conflicte d\'enregistrament" "S\'està enregistrant" "Error d\'enregistrament" - "S\'estan llegint els programes per crear programacions d\'enregistrament" - "S\'estan llegint els programes" - - + "S\'estan llegint els programes" + "Mostra els enregistraments recents" + "L\'enregistrament del programa %1$s no s\'ha completat." + "Els enregistraments dels programes %1$s i %2$s no s\'han completat." + "Els enregistraments dels programes %1$s, %2$s i %3$s no s\'han completat." + "L\'enregistrament del programa %1$s no s\'ha completat perquè no hi ha prou espai d\'emmagatzematge." + "Els enregistraments dels programes %1$s i %2$s no s\'han completat perquè no hi ha prou espai d\'emmagatzematge." + "Els enregistraments dels programes %1$s, %2$s i %3$s no s\'han completat perquè no hi ha prou espai d\'emmagatzematge." "El DVR necessita més emmagatzematge" - "Podràs· enregistrar· programes· amb· el· DVR.· No· obstant· això,· en· aquests· moments· no· tens· prou· emmagatzematge· al· dispositiu· perquè· el· DVR· pugui· funcionar.· Connecta· una· unitat· externa· que· tingui· com· a· mínim· %1$s GB· d\'espai· disponible· i· segueix· els· passos· per· formatar-la· com· a· unitat· d\'emmagatzematge· del· dispositiu." + "Podràs· enregistrar· programes· amb· el· DVR.· No· obstant· això,· en· aquests· moments· no· tens· prou· emmagatzematge· al· dispositiu· perquè· el· DVR· pugui· funcionar.· Connecta· una· unitat· externa· que· tingui· com· a· mínim· %1$d GB· d\'espai· disponible· i· segueix· els· passos· per· formatar-la· com· a· unitat· d\'emmagatzematge· del· dispositiu." + "No hi ha prou espai d\'emmagatzematge" + "Aquest programa no s\'enregistrarà perquè no hi ha prou espai d\'emmagatzematge. Prova de suprimir algun dels enregistraments actuals." "Falta el dispositiu d\'emmagatzematge" - "Falta contingut emmagatzemat pel DVR. Connecta la unitat externa que utilitzaves abans per tornar a activar el DVR. Si el dispositiu d\'emmagatzematge ja no està disponible, pots optar perquè s\'oblidi." - "Vols que s\'oblidi el dispositiu d\'emmagatzematge?" - "Tot el contingut i totes les agendes que tinguis desades es perdran." "Vols aturar l\'enregistrament?" "El contingut enregistrat es desarà." - - + "L\'enregistrament de %1$s s\'aturarà perquè entra en conflicte amb aquest programa. El contingut enregistrat es desarà." "Hi ha un enregistrament programat, però té conflictes" "L\'enregistrament ha començat, però té conflictes" "%1$s s\'enregistrarà." @@ -304,17 +297,29 @@ "Ja s\'ha programat l\'enregistrament d\'aquest programa per a les %1$s." "Ja s\'ha enregistrat" "Aquest programa ja s\'ha enregistrat. El trobaràs a la biblioteca de DVR." - - - - - - - - + "S\'ha programat l\'enregistrament de la sèrie" + + S\'han programat %1$d enregistraments de la sèrie %2$s. + S\'ha programat %1$d enregistrament de la sèrie %2$s. + + + S\'han programat %1$d enregistraments de la sèrie %2$s. %3$d no s\'enregistraran a causa d\'un conflicte. + S\'ha programat %1$d enregistrament de la sèrie %2$s. No s\'enregistrarà a causa d\'un conflicte. + + + S\'han programat %1$d enregistraments de la sèrie %2$s. %3$d episodis d\'aquesta i d\'altres sèries no s\'enregistraran a causa d\'un conflicte. + S\'ha programat %1$d enregistrament de la sèrie %2$s. %3$d episodis d\'aquesta i d\'altres sèries no s\'enregistraran a causa d\'un conflicte. + + + S\'han programat %1$d enregistraments de la sèrie %2$s. No s\'enregistrarà un episodi d\'una altra sèrie a causa d\'un conflicte. + S\'ha programat %1$d enregistrament de la sèrie %2$s. No s\'enregistrarà un episodi d\'una altra sèrie a causa d\'un conflicte. + + + S\'han programat %1$d enregistraments de la sèrie %2$s. No s\'enregistraran %3$d episodis d\'altres sèries a causa d\'un conflicte. + S\'ha programat %1$d enregistrament de la sèrie %2$s. No s\'enregistraran %3$d episodis d\'altres sèries a causa d\'un conflicte. + "El programa enregistrat no s\'ha trobat." "Enregistraments relacionats" - "(Cap descripció del programa)" %1$d enregistraments %1$d enregistrament @@ -334,6 +339,7 @@ "Vols aturar l\'enregistrament de la sèrie?" "Els episodis enregistrats continuaran estant disponibles a la biblioteca de DVR." "Atura" + "En aquest moment no s\'està emetent cap episodi en directe." "No hi ha episodis disponibles.\nS\'enregistraran quan estiguin disponibles." (%1$d minuts) @@ -345,4 +351,5 @@ "Avui, %1$s" "Demà, %1$s" "Puntuació" + "Programes enregistrats" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 6dac1751..c69c38f7 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Ovládání přehrávání" - "Poslední kanály" + "Kanály" "Možnosti TV" - "Možnosti PIP" "Ovládací prvky přehrávání pro tento kanál nejsou k dispozici" "Přehrát nebo pozastavit" "Přetočit vpřed" @@ -30,42 +29,24 @@ "Další" "Předchozí" "Programový průvodce" - "Jsou dostupné nové kanály" + "Dostupné nové kanály" "Spustit aplikaci %1$s" "Skryté titulky" "Režim zobrazení" - "PIP" - "Zapnuto" - "Vypnuto" + "Obraz v obraze" "Vícekanál. zvuk" "Další kanály" "Nastavení" - "Zdroj" - "Zaměnit" - "Zapnuto" - "Vypnuto" - "Zvuk" - "Hlavní" - "Okno PIP" - "Rozvržení" - "Vpravo dole" - "Vpravo nahoře" - "Vlevo nahoře" - "Vlevo dole" - "Vedle sebe" - "Rozměry" - "Velké" - "Malé" - "Zdroj vstupu" "TV (anténa/kabel)" "Žádné informace o programu" "Žádné informace" "Blokovaný kanál" - "Neznámý jazyk" + "Neznámý jazyk" + "Skryté titulky: %1$d" "Skryté titulky" "Vypnuto" "Nastavit formátování" - "Zadání sys. předvoleb pro skryté titulky" + "Systémová nastavení pro skryté titulky" "Režim zobrazení" "Vícekanál. zvuk" "mono" @@ -83,6 +64,7 @@ "SD" "Seskupit podle" "Tento program je blokován." + "Tento program nemá hodnocení" "Tento program má hodnocení %1$s." "Vstup nepodporuje automatické vyhledávání" "Nelze spustit automatické vyhledávání vstupu %s" @@ -94,7 +76,6 @@ Byl přidán %1$d kanál "Bylo přidáno 0 kanálů" - "Tuner" "Rodičovská ochrana" "Zapnuto" "Vypnuto" @@ -110,6 +91,8 @@ "Ostatní země" "Žádný" "Žádný" + "Bez hodnocení" + "Blokovat programy bez hodnocení" "Žádné" "Vysoké omezení" "Střední omezení" @@ -126,6 +109,7 @@ "Chcete-li sledovat tento kanál, zadejte kód PIN." "Chcete-li sledovat tento program, zadejte kód PIN." "Tento program má hodnocení %1$s. Chcete-li jej sledovat, zadejte kód PIN." + "Tento program nemá hodnocení. Chcete-li ho sledovat, zadejte kód PIN." "Zadání kódu PIN" "Chcete-li nastavit rodičovskou kontrolu, vytvořte kód PIN." "Zadejte nový PIN" @@ -139,22 +123,31 @@ "Kód PIN byl zadán chybně. Zkuste to znovu." "Kód PIN nesouhlasí. Zkuste to znovu." + "Zadejte PSČ." + "Aplikace Televize online vám na základě PSČ poskytne kompletního programového průvodce televizními kanály." + "Zadejte PSČ" + "Neplatné PSČ" "Nastavení" "Upravit seznam kanálů" "Vyberte kanály pro programového průvodce" "Zdroje kanálů" "K dispozici jsou nové kanály" "Rodičovská ochrana" + "Časový posun" + "Umožňuje nahrávat při sledování, abyste mohli živě vysílané programy pozastavit nebo přetočit zpět.\nUpozornění: Funkce intenzivně využívá interní úložiště, a může tak snížit jeho životnost." "Licence open source" - "Licence open source" + "Odeslat zpětnou vazbu" "Verze" "Chcete-li tento kanál sledovat, stiskněte šipku vpravo a zadejte kód PIN." "Chcete-li tento program sledovat, stiskněte šipku vpravo a zadejte kód PIN." + "Tento program nemá hodnocení.\nChcete-li ho sledovat, stiskněte šipku vpravo a zadejte kód PIN." "Tento program má hodnocení %1$s.\nChcete-li tento program sledovat, stiskněte šipku vpravo a zadejte kód PIN." "Chcete-li tento kanál sledovat, použijte výchozí aplikaci Live TV." "Chcete-li tento program sledovat, použijte výchozí aplikaci Live TV." + "Tento program nemá hodnocení.\nChcete-li ho sledovat, použijte výchozí aplikaci Live TV." "Tento program má hodnocení %1$s.\nChcete-li tento program sledovat, použijte výchozí aplikaci Live TV." "Program je blokován" + "Tento program nemá hodnocení" "Tento program má hodnocení %1$s." "Pouze zvuk" "Slabý signál" @@ -189,8 +182,6 @@ "Chcete-li získat přístup k nabídce TV, ""stiskněte SELECT""." "Nebyl nalezen žádný televizní vstup." "Televizní vstup nebyl nalezen." - "Funkce PIP není podporována." - "Není k dispozici vstup, který lze zobrazovat pomocí PIP." "Typ tuneru není vhodný. Pro TV vstup typu tuneru spusťte aplikaci Televize online." "Ladění se nezdařilo." "Aplikace potřebná k provedení této akce nebyla nalezena." @@ -244,6 +235,8 @@ %1$d naplánovaných nahrávek %1$d naplánovaná nahrávka + "Zrušit nahrávání" + "Zastavit nahrávání" "Přehrát" "Přehrát od začátku" "Pokračovat v přehrávání" @@ -278,9 +271,6 @@ "Když naplánujete nahrávání příliš mnoha programů, budou nahrány pouze programy s vyšší prioritou." "Uložit" "Jednorázová nahrávání mají nejvyšší prioritu" - "Zrušit" - "Zrušit" - "Zapomenout" "Zastavit" "Zobrazit plán nahrávání" "Pouze tento program" @@ -290,25 +280,28 @@ "Místo toho nahrát tento program" "Zrušit toto nahrávání" "Sledovat" + "Smazat nahraný obsah…" "Lze nahrát" "Nahrávání je naplánováno" "Konflikt nahrávání" "Nahrávání" "Nahrávání se nezdařilo" - "Načítání programů za účelem vytvoření plánů nahrávání" - "Načítání programů" - - + "Načítání programů" + "Zobrazit poslední nahrávky" + "Program %1$s se nepodařilo nahrát celý." + "Programy %1$s%2$s se nepodařilo nahrát celé." + "Programy %1$s, %2$s%3$s se nepodařilo nahrát celé." + "Nahrávání programu %1$s nebylo dokončeno z důvodu nedostatku místa v úložišti." + "Nahrávání programů %1$s%2$s nebylo dokončeno z důvodu nedostatku místa v úložišti." + "Nahrávání programů %1$s, %2$s%3$s nebylo dokončeno z důvodu nedostatku místa v úložišti." "DVR potřebuje víc místa" - "Programy bude možné nahrát pomocí DVR. Ve vašem zařízení však momentálně není dost místa, a DVR proto nebude fungovat. Zapojte externí úložiště o velikosti minimálně %1$s GB a podle pokynů jej naformátujte jako úložiště zařízení." + "Programy bude možné nahrát pomocí DVR. Ve vašem zařízení však momentálně není dost místa, a DVR proto nebude fungovat. Zapojte externí úložiště o velikosti minimálně %1$d GB a podle pokynů jej naformátujte jako úložiště zařízení." + "Nedostatek místa" + "Tento program nebude nahrán z důvodu nedostatku místa. Zkuste smazat část nahraného obsahu." "Úložiště není dostupné" - "Část úložiště, které využívá DVR, není dostupná. Před opětovnou aktivací DVR připojte externí disk. Pokud úložiště již není k dispozici, můžete jej zapomenout." - "Zapomenout úložiště?" - "Veškerý nahraný obsah a plány nahrávání budou smazány." "Zastavit nahrávání?" "Nahraný obsah bude uložen." - - + "Nahrávání pořadu %1$s bude zastaveno, protože je v konfliktu s tímto programem. Nahraný obsah bude uložen." "Nahrávání je naplánováno, ale obsahuje konflikty" "Nahrávání bylo zahájeno, ale obsahuje konflikty" "Nahraje se program %1$s." @@ -328,17 +321,39 @@ "Nahrávání stejného programu již bylo naplánováno na %1$s." "Již nahráno" "Tento program již byl nahrán. Naleznete jej v knihovně DVR." - - - - - - - - + "Je naplánováno nahrávání pořadu" + + Byla naplánována %1$d nahrávání pořadu %2$s. + Bylo naplánováno %1$d nahrávání pořadu %2$s. + Bylo naplánováno %1$d nahrávání pořadu %2$s. + Bylo naplánováno %1$d nahrávání pořadu %2$s. + + + Byla naplánována %1$d nahrávání pořadu %2$s. Z důvodu konfliktů některá nahrávání nebudou provedena (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů některá nahrávání nebudou provedena (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů některá nahrávání nebudou provedena (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nahrávání nebude provedeno. + + + Byla naplánována %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody tohoto pořadu a jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody tohoto pořadu a jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody tohoto pořadu a jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody tohoto pořadu a jiných pořadů (celkem %3$d). + + + Byla naplánována %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebude nahrána 1 epizoda jiného pořadu. + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebude nahrána 1 epizoda jiného pořadu. + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebude nahrána 1 epizoda jiného pořadu. + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebude nahrána 1 epizoda jiného pořadu. + + + Byla naplánována %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody jiných pořadů (celkem %3$d). + Bylo naplánováno %1$d nahrávání pořadu %2$s. Z důvodu konfliktů nebudou nahrány epizody jiných pořadů (celkem %3$d). + "Nahraný program nebyl nalezen." "Související nahrávky" - "(Žádný popis programu)" %1$d nahrávky %1$d nahrávky @@ -362,6 +377,7 @@ "Zastavit nahrávání série?" "Nahrané epizody zůstanou dostupné v knihovně DVR." "Zastavit" + "Momentálně nejsou vysílány žádné epizody." "Nejsou k dispozici žádné epizody.\nEpizody budou nahrány, až budou k dispozici." (%1$d minuty) @@ -375,4 +391,5 @@ "Dnes v %1$s" "Zítra v %1$s" "Skóre" + "Nahrané programy" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index b053e77d..352f4c7a 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Afspilningsknapper" - "Seneste kanaler" - "Tv-indstillinger" - "PIP-muligheder" + "Kanaler" + "Indstillinger for tv" "Afspilningsstyring er ikke tilgængeligt på denne kanal" "Afspil, eller sæt på pause" "Spol frem" @@ -30,38 +29,20 @@ "Næste" "Forrige" "Programguide" - "Der er nye tilgængelige kanaler" + "Nye tilgængelige kanaler" "Åbn %1$s" "Undertekster" "Format" - "PIP" - "Til" - "Fra" + "Integreret billede" "Flere lydspor" "Få flere kanaler" "Indstillinger" - "Kilde" - "Skift" - "Til" - "Fra" - "Lyd" - "Primær" - "PIP-vindue" - "Layout" - "Nederst til højre" - "Øverst til højre" - "Øverst til venstre" - "Nederst til venstre" - "Side om side" - "Størrelse" - "Stor" - "Lille" - "Inputkilde" "Tv (antenne/kabel)" "Ingen programoplysninger" "Ingen oplysninger" "Blokeret kanal" - "Ukendt sprog" + "Ukendt sprog" + "Undertekster %1$d" "Undertekster" "Fra" "Tilpas formatering" @@ -83,6 +64,7 @@ "SD" "Gruppér efter" "Dette program er blokeret" + "Dette program er ikke klassificeret" "Dette program er klassificereret som %1$s" "Dette input understøtter ikke automatisk scanning" "Automatisk scanning for \"%s\" kunne ikke startes" @@ -92,7 +74,6 @@ %1$d kanaler blev tilføjet "Ingen kanaler tilføjet" - "Tuner" "Børnesikring" "Til" "Fra" @@ -108,6 +89,8 @@ "Andre lande" "Ingen" "Ingen" + "Ikke klassificeret" + "Bloker programmer uden klassificering" "Ingen" "Høje begrænsninger" "Medium begrænsninger" @@ -124,6 +107,7 @@ "Angiv din pinkode for at se denne kanal" "Angiv din pinkode for at se dette program" "Dette program er klassificeret som %1$s. Du skal indtaste din pinkode for at se programmet" + "Dette program er ikke klassificeret. Indtast din pinkode for at se programmet" "Angiv din pinkode" "Angiv en pinkode for at aktivere børnesikring" "Angiv ny pinkode" @@ -135,22 +119,31 @@ "Pinkoden var forkert. Prøv igen." "Prøv igen. Pinkoden var forkert" + "Angiv dit postnummer." + "Appen Tv-kanaler bruger postnummeret til at levere en komplet tv-guide til tv-kanalerne." + "Angiv dit postnummer" + "Ugyldigt postnummer" "Indstillinger" "Tilpas kanallisten" "Vælg kanaler til din programoversigt" "Kanalkilder" - "Der er nye tilgængelige kanaler" + "Nye tilgængelige kanaler" "Børnesikring" + "Timeshift" + "Optag en direkte udsendelse, mens du ser den, så du kan sætte den på pause eller spole tilbage i den.\nAdvarsel! Denne intensive brug af den interne lagerplads kan reducere lagerpladsens levetid." "Open source-licenser" - "Open source-licenser" + "Send feedback" "Version" "Se denne kanal ved at trykke på højreknappen og angive din pinkode" "Se dette program ved at trykke på højreknappen og angive din pinkode" + "Dette program er ikke klassificeret.\nDu kan se programmet ved at trykke på højreknappen og indtaste din pinkode" "Dette program er klassificereret som %1$s.\nSe dette program ved at trykke på højreknappen og angive din pinkode" "Se denne kanal ved hjælp af standardappen til direkte tv." "Se dette program ved hjælp af standardappen til direkte tv." + "Dette program er ikke klassificeret.\nDu kan se programmet ved at bruge standardappen til direkte tv." "Dette program er klassificeret som %1$s.\nSe dette program ved hjælp af standardappen til direkte tv." "Programmet er blokeret" + "Dette program er ikke klassificeret" "Dette program er klassificereret som %1$s" "Kun lyd" "Svagt signal" @@ -168,11 +161,11 @@ %1$d kanaler "Der er ingen tilgængelige kanaler" - "Nye" + "Ny" "Ikke konfigureret" "Få flere kilder" - "Gennemse apps, der viser tv-kanaler" - "Der er nye tilgængelige kanalkilder" + "Find apps med tv-kanaler" + "Nye tilgængelige kanalkilder" "Du kan få kanaler fra nye kanalkilder.\nKonfigurer dem nu, eller gør det senere i indstillingen for kanalkilder." "Konfigurer nu" "OK, det er forstået" @@ -181,8 +174,6 @@ "Tryk på VÆLG"" for at få adgang til TV-menuen." "Der blev ikke fundet noget tv-input" "Tv-inputtet blev ikke fundet" - "PIP understøttes ikke" - "Der er intet tilgængeligt input, som kan vises med PIP" "Tunertypen er uegnet. Åbn appen Tv-kanaler for at få tv-input fra tunertypen." "Tuningen mislykkedes" "Der blev ikke fundet nogen app, der kan håndtere denne handling." @@ -226,6 +217,8 @@ %1$d planlagt optagelse %1$d planlagte optagelser + "Annuller optagelse" + "Stop optagelse" "Se" "Afspil fra begyndelsen" "Genoptag afspilning" @@ -258,9 +251,6 @@ "Når der er for mange programmer at optage på samme tid, er det kun programmer med højere prioritet, der optages." "Gem" "Engangsoptagelser har højest prioritet" - "Annuller" - "Annuller" - "Glem" "Stop" "Tidsplan for optagelse" "Dette ene program" @@ -270,25 +260,28 @@ "Optag dette program i stedet for" "Annuller denne optagelse" "Se nu" + "Slet optagelser…" "Kan optages" "Optagelse er planlagt" "Modstridende optagelser" "Optager" "Optagelsen mislykkedes" - "Læser programmer for at oprette tidsplaner for optagelse" - "Læser programmer" - - + "Læser programmer" + "Se de seneste optagelser" + "Optagelsen af %1$s er ikke fuldført." + "Optagelserne af %1$s og %2$s er ikke fuldført." + "Optagelserne af %1$s, %2$s og %3$s er ikke fuldført." + "Optagelsen af %1$s blev ikke fuldført, da der ikke var nok lagerplads." + "Optagelserne af %1$s og %2$s blev ikke fuldført, da der ikke var nok lagerplads." + "Optagelserne af %1$s, %2$s og %3$s blev ikke fuldført, da der ikke var nok lagerplads." "DVR kræver mere lagerplads" - "Du kan optage programmer med DVR. Der er dog ikke længere nok lagerplads på din enhed til at DVR kan fungere. Tilslut et eksternt drev på mindst %1$s GB, og følg vejledningen i, hvordan du formaterer det som internt lager." + "Du kan optage programmer med DVR. Der er dog ikke længere nok lagerplads på din enhed til at DVR kan fungere. Tilslut et eksternt drev på mindst %1$d GB, og følg vejledningen i, hvordan du formaterer det som internt lager." + "Der er ikke nok lagerplads" + "Dette program kan ikke optages, fordi der ikke er nok lagerplads. Prøv at slette nogle eksisterende optagelser." "Lager mangler" - "Noget af det lager, der bruges af DVR, mangler. Tilslut det eksterne drev, du brugte før, for at genaktivere DVR. Alternativt kan du vælge at glemme lageret, hvis det ikke længere er tilgængeligt." - "Vil du glemme lageret?" - "Du mister alt dit optagede indhold og dine tidsplaner." "Skal optagelsen stoppes?" "Det optagede indhold gemmes." - - + "Optagelsen af %1$s stoppes, fordi den falder sammen med dette program. Det indhold, der er optaget, vil blive gemt." "Optagelsen er planlagt, men der er konflikter" "Optagelsen er startet, men der er konflikter" "%1$s optages." @@ -306,17 +299,29 @@ "En optagelse af dette program er allerede planlagt kl. %1$s." "Det er allerede optaget" "Dette program er allerede optaget. Du kan finde det i DVR-samlingen." - - - - - - - - + "Optagelsen er planlagt" + + Der er planlagt %1$d optagelse af %2$s. + Der er planlagt %1$d optagelser af %2$s. + + + Der er planlagt %1$d optagelse af %2$s. %3$d optages ikke på grund af konflikter. + Der er planlagt %1$d optagelser af %2$s. %3$d af dem optages ikke på grund af konflikter. + + + Der er planlagt %1$d optagelse af %2$s. %3$d afsnit af denne og andre serier optages ikke på grund af konflikter. + Der er planlagt %1$d optagelser af %2$s. %3$d afsnit af denne og andre serier optages ikke på grund af konflikter. + + + Der er planlagt %1$d optagelse af %2$s. 1 afsnit af en anden serie optages ikke på grund af konflikter. + Der er planlagt %1$d optagelser af %2$s. 1 afsnit af en anden serie optages ikke på grund af konflikter. + + + Der er planlagt %1$d optagelse af %2$s. %3$d afsnit af andre serier optages ikke på grund af konflikter. + Der er planlagt %1$d optagelser af %2$s. %3$d afsnit af andre serier optages ikke på grund af konflikter. + "Det optagede program blev ikke fundet." "Relaterede optagelser" - "(Ingen programbeskrivelse)" %1$d optagelse %1$d optagelser @@ -336,6 +341,7 @@ "Vil du stoppe optagelsen af serien?" "Optagede afsnit kan findes i DVR-samlingen." "Stop" + "Der optages ingen afsnit lige nu." "Der er ingen tilgængelige tv-serier.\nDe optages, så snart de er tilgængelige.." (%1$d minut) @@ -347,4 +353,5 @@ "%1$s i dag" "%1$s i morgen" "Resultat" + "Optagede programmer" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index b4cc6c0f..32a1b486 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -20,9 +20,8 @@ "Mono" "Stereo" "Wiedergabesteuerung" - "Letzte Kanäle" + "Kanäle" "TV-Optionen" - "PIP-Optionen" "Wiedergabesteuerung für diesen Kanal nicht verfügbar" "Abspielen oder pausieren" "Vorspulen" @@ -35,37 +34,19 @@ "Untertitel" "Anzeigemodus" "PIP" - "An" - "Aus" "Multi-Audio" "Mehr Kanäle erhalten" "Einstellungen" - "Quelle" - "Wechseln" - "An" - "Aus" - "Ton" - "Hauptoption" - "PIP-Fenster" - "Layout" - "Unten rechts" - "Oben rechts" - "Oben links" - "Unten links" - "Nebeneinander" - "Größe" - "Groß" - "Klein" - "Eingangsquelle" "TV (Antenne/Kabel)" "Keine Programminformationen" "Keine Informationen" "Blockierter Kanal" - "Unbekannte Sprache" + "Unbekannte Sprache" + "Untertitel für %1$d" "Untertitel" "Aus" "Untertitel anpassen" - "Systemübergr. Einst. für Untertitel festlegen" + "Systemweite Einstellungen für Untertitel festlegen" "Anzeigemodus" "Multi-Audio" "Mono" @@ -83,6 +64,7 @@ "SD" "Gruppieren nach" "Diese Sendung ist blockiert." + "Diese Sendung hat keine Altersempfehlung" "Diese Sendung wurde als \"%1$s\" eingestuft." "Der Eingang unterstützt keine automatische Suche." "Automatische Suche für \"%s\" kann nicht gestartet werden." @@ -92,7 +74,6 @@ %1$d Kanal hinzugefügt "Keine Kanäle hinzugefügt" - "Tuner" "Jugendschutz" "An" "Aus" @@ -108,6 +89,8 @@ "Andere Länder" "Keiner" "Ohne" + "Ohne Altersempfehlung" + "Sendungen ohne Altersempfehlung blockieren" "Ohne" "Hohe Einschränkung" "Mäßige Einschränkung" @@ -124,40 +107,50 @@ "Zum Ansehen dieses Kanals PIN eingeben" "Zum Ansehen dieses Programms PIN eingeben" "Diese Sendung wurde als \"%1$s\" eingestuft. Gib deinen PIN ein, um sie anzusehen" + "Diese Sendung hat keine Altersempfehlung. Gib deine PIN ein, um sie anzusehen." "PIN eingeben" - "Erstellen Sie eine PIN, um den Jugendschutz einzurichten." + "Erstelle eine PIN, um den Jugendschutz einzurichten." "Neue PIN eingeben" "PIN bestätigen" "Aktuelle PIN eingeben" - Sie haben die PIN zum fünften Mal falsch eingegeben.\nVersuchen Sie es in %1$d Sekunden noch einmal. - Sie haben die PIN zum fünften Mal falsch eingegeben.\nVersuchen Sie es in %1$d Sekunde noch einmal. + Du hast die PIN zum fünften Mal falsch eingegeben.\nVersuche es in %1$d Sekunden noch einmal. + Du hast die PIN zum fünften Mal falsch eingegeben.\nVersuche es in %1$d Sekunde noch einmal. - "Diese PIN war falsch. Versuchen Sie es erneut." - "Die PIN stimmt nicht. Bitte versuchen Sie es erneut." + "Diese PIN war falsch. Versuche es erneut." + "Die PIN stimmt nicht. Bitte versuche es erneut." + "Gib deine Postleitzahl ein." + "Die Live TV App verwendet die Postleitzahl, um dir die vollständige Programmübersicht der TV-Sender zur Verfügung zu stellen." + "Postleitzahl eingeben" + "Ungültige Postleitzahl" "Einstellungen" "Kanalliste anpassen" "Kanäle für Programmübersicht auswählen" "Kanalquellen" "Neue Kanäle verfügbar" "Jugendschutzeinstellungen" + "Zeitversetztes Fernsehen" + "Wenn du Programme aufnimmst, während du sie dir live ansiehst, kannst du sie pausieren oder zurückspulen.\nWarnung: Dies führt durch die intensive Speichernutzung möglicherweise zu einer verringerten Lebensdauer des internen Speichers." "Open-Source-Lizenzen" - "Open-Source-Lizenzen" + "Feedback geben" "Version" - "Um sich diesen Kanal anzusehen, drücken Sie rechts und geben Sie die PIN ein." - "Um sich dieses Programm anzusehen, drücken Sie rechts und geben Sie die PIN ein." - "Diese Sendung wurde als \"%1$s\" eingestuft.\nUm sich diese Sendung anzusehen, drücken Sie rechts und geben Sie Ihre PIN ein." - "Um sich diesen Kanal anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen." - "Um sich dieses Programm anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen." - "Diese Sendung wurde als \"%1$s\" eingestuft.\nUm sich diese Sendung anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen." + "Um sich diesen Kanal anzusehen, drücke rechts und gib die PIN ein." + "Um sich dieses Programm anzusehen, drücke rechts und gib die PIN ein." + "Diese Sendung hat keine Altersempfehlung.\nUm sie anzusehen, drücke rechts und gib deine PIN ein." + "Diese Sendung wurde als \"%1$s\" eingestuft.\nUm sich diese Sendung anzusehen, drücke rechts und gib deine PIN ein." + "Um sich diesen Kanal anzusehen, verwendest du die Standard-App für die Liveverfolgung von TV-Sendungen." + "Um sich dieses Programm anzusehen, verwendest du die Standard-App für die Liveverfolgung von TV-Sendungen." + "Diese Sendung hat keine Altersempfehlung.\nUm sie anzusehen, verwende die Standard-App für die Liveverfolgung von TV-Sendungen." + "Diese Sendung wurde als \"%1$s\" eingestuft.\nUm sich diese Sendung anzusehen, verwendest du die Standard-App für die Liveverfolgung von TV-Sendungen." "Das Programm wurde blockiert." + "Diese Sendung hat keine Altersempfehlung" "Diese Sendung wurde als \"%1$s\" eingestuft." "Nur Audio" "Schwaches Signal" "Keine Internetverbindung" - Dieser Kanal kann bis %1$s nicht wiedergegeben werden, weil andere Kanäle aufgenommen werden. \n\nDrücken Sie rechts, um den Aufnahmeplan anzupassen. - Dieser Kanal kann bis %1$s nicht wiedergegeben werden, weil ein anderer Kanal aufgenommen wird. \n\nDrücken Sie rechts, um den Aufnahmeplan anzupassen. + Dieser Kanal kann bis %1$s nicht wiedergegeben werden, weil andere Kanäle aufgenommen werden. \n\nDrücke rechts, um den Aufnahmeplan anzupassen. + Dieser Kanal kann bis %1$s nicht wiedergegeben werden, weil ein anderer Kanal aufgenommen wird. \n\nDrücke rechts, um den Aufnahmeplan anzupassen. "Kein Titel" "Kanal blockiert" @@ -173,25 +166,23 @@ "Weitere Quellen suchen" "Nach Apps suchen, die Live-TV anbieten" "Neue Kanalquellen verfügbar" - "Neue Kanalquellen bieten weitere Kanäle an.\nDiese können Sie entweder jetzt oder später über die Einstellung \"Kanalquellen\" einrichten." + "Neue Kanalquellen bieten weitere Kanäle an.\nDiese kannst du entweder jetzt oder später über die Einstellung \"Kanalquellen\" einrichten." "Jetzt einrichten" "OK" - "Drücken Sie die Auswahltaste"", um auf das TV-Menü zuzugreifen." + "Drücke die Auswahltaste"", um auf das TV-Menü zuzugreifen." "Kein TV-Eingang gefunden" "TV-Eingang konnte nicht gefunden werden." - "PIP wird nicht unterstützt." - "Es ist kein Eingang für die PIP-Anzeige verfügbar." "Tunertyp ungeeignet, bitte Live TV App für TV-Eingang des Tunertyps starten" "Fehler beim Einstellen" "Für diese Aktion wurde keine App gefunden." - "Alle Quellkanäle sind ausgeblendet.\nWählen Sie mindestens einen Kanal aus, den Sie anschauen möchten." + "Alle Quellkanäle sind ausgeblendet.\nWähle mindestens einen Kanal aus, den du anschauen möchtest." "Das Video ist unerwarteterweise nicht verfügbar." - "Die Taste \"Zurück\" gilt für das verbundene Gerät. Zum Beenden drücken Sie auf \"Startseite\"." + "Die Taste \"Zurück\" gilt für das verbundene Gerät. Zum Beenden drücke auf \"Startseite\"." "Live TV benötigt einen Lesezugriff für die Kanalliste." "Quellen einrichten" - "Bei Live-TV wird die Nutzererfahrung mit klassischen Fernsehsendern mit der von Streaming-Kanälen von Apps kombiniert.\n\nRichten Sie zuerst die bereits installierten Kanalquellen ein. Sie können auch im Google Play Store nach weiteren Apps suchen, die Live-TV anbieten." + "Bei Live-TV wird die Nutzererfahrung mit klassischen Fernsehsendern mit der von Streaming-Kanälen von Apps kombiniert.\n\nRichte zuerst die bereits installierten Kanalquellen ein. Du kannst auch im Google Play Store nach weiteren Apps suchen, die Live-TV anbieten." "Aufnahmen und Zeitpläne" "10 Minuten" "30 Minuten" @@ -226,6 +217,8 @@ %1$d geplante Aufnahmen %1$d geplante Aufnahme + "Aufnahme abbrechen" + "Aufnahme beenden" "Ansehen" "Von Anfang an abspielen" "Wiedergabe fortsetzen" @@ -236,7 +229,7 @@ "Aufnahmeplan" "Weitere Infos" "Aufnahmen löschen" - "Wählen Sie die Folgen aus, die gelöscht werden sollen. Eine Wiederherstellung ist nicht möglich." + "Wähle die Folgen aus, die gelöscht werden sollen. Eine Wiederherstellung ist nicht möglich." "Es sind keine Aufnahmen zum Löschen vorhanden." "Angesehene Folgen auswählen" "Alle Folgen auswählen" @@ -258,9 +251,6 @@ "Wenn zu viele Programme gleichzeitig aufgenommen werden, werden nur die Programme mit höherer Priorität aufgenommen." "Speichern" "Einmalige Aufnahmen haben höchste Priorität" - "Abbrechen" - "Abbrechen" - "Entfernen" "Beenden" "Aufnahmeplan ansehen" "Nur diese Folge" @@ -270,25 +260,28 @@ "Stattdessen diese Sendung aufnehmen" "Diesen Aufnahmeplan abbrechen" "Jetzt ansehen" + "Aufnahmen löschen…" "Kann aufgenommen werden" "Aufnahme geplant" "Konflikt bei der Aufnahme" "Aufnahme" "Aufnahme fehlgeschlagen" - "Sendungen werden gelesen, um Aufnahmepläne zu erstellen" - "Sendungen werden gelesen" - - + "Sendungen werden gelesen" + "Letzte Aufnahmen ansehen" + "Die Aufnahme %1$s ist nicht abgeschlossen." + "Die Aufnahmen %1$s und %2$s sind nicht abgeschlossen." + "Die Aufnahmen %1$s, %2$s und %3$s sind nicht abgeschlossen." + "Die Aufnahme von %1$s konnte nicht abgeschlossen werden, weil nicht genügend Speicher vorhanden ist." + "Die Aufnahmen von %1$s und %2$s konnten nicht abgeschlossen werden, weil nicht genügend Speicher vorhanden ist." + "Die Aufnahmen von %1$s, %2$s und %3$s konnten nicht abgeschlossen werden, weil nicht genügend Speicher vorhanden ist." "DVR benötigt mehr Speicher" - "Sie können mit DVR Sendungen aufnehmen, jedoch ist auf Ihrem Gerät momentan nicht ausreichend Speicherplatz vorhanden. Schließen Sie ein externes Laufwerk mit mindestens %1$s GB freiem Speicher an und folgen Sie der Anleitung zur Formatierung als Gerätespeicher." + "Du kannst mit DVR Sendungen aufnehmen, jedoch ist auf deinem Gerät momentan nicht ausreichend Speicherplatz vorhanden. Schließ ein externes Laufwerk mit mindestens %1$d GB freiem Speicher an und folge der Anleitung zur Formatierung als Gerätespeicher." + "Nicht genug Speicherplatz" + "Diese Inhalte werden nicht aufgenommen, weil zu wenig Speicherplatz verfügbar ist. Du kannst Speicherplatz freigeben, indem du ein paar vorhandene Aufnahmen löschst." "Speicher nicht verfügbar" - "Ein Teil des DVR-Speichers ist nicht verfügbar. Um DVR neu zu aktivieren, stellen Sie eine Verbindung zu dem externen Gerät her, das Sie zuvor verwendet haben. Sie können den Speicher auch entfernen, wenn er nicht mehr verfügbar ist." - "Speicher entfernen?" - "Alle aufgenommenen Inhalte und Aufnahmepläne gehen verloren." "Aufnahme beenden?" "Die aufgenommenen Inhalte werden gespeichert." - - + "Die Aufzeichnung von %1$s wird aufgrund eines Konflikts mit diesem Programm beendet. Der aufgezeichnete Inhalt wird gespeichert." "Aufnahme geplant, aber es liegen Konflikte vor" "Aufnahme wurde gestartet, aber es liegen Konflikte vor" "%1$s wird aufgenommen." @@ -300,23 +293,35 @@ Teile von %1$s, %2$s und %3$d weiteren geplanten Aufnahmen werden nicht aufgenommen. Teile von %1$s, %2$s und %4$d weiteren geplanten Aufnahme werden nicht aufgenommen. - "Was möchten Sie aufnehmen?" - "Wie lange möchten Sie aufnehmen?" + "Was möchtest du aufnehmen?" + "Wie lange möchtest du aufnehmen?" "Schon dem Aufnahmeplan hinzugefügt" "Diese Sendung wurde dem Aufnahmeplan schon hinzugefügt und wird um %1$s aufgenommen." "Schon aufgenommen" "Diese Sendung wurde schon aufgenommen. Sie ist in der DVR-Bibliothek verfügbar." - - - - - - - - + "Serienaufnahme geplant" + + %1$d Aufnahmen wurden für %2$s geplant. + %1$d Aufnahme wurde für %2$s geplant. + + + %1$d Aufnahmen wurden für %2$s geplant. %3$d davon werden wegen Konflikten nicht aufgezeichnet. + %1$d Aufnahme wurde für %2$s geplant. Sie wird wegen Konflikten nicht aufgezeichnet. + + + %1$d Aufnahmen wurden für %2$s geplant. %3$d Folgen dieser Serie und anderer Serien werden wegen Konflikten nicht aufgezeichnet. + %1$d Aufnahme wurde für %2$s geplant. %3$d Folge dieser oder einer anderen Serie wird wegen Konflikten nicht aufgezeichnet. + + + %1$d Aufnahmen wurden für %2$s geplant. 1 Folge einer anderen Serie wird wegen Konflikten nicht aufgezeichnet. + %1$d Aufnahme wurde für %2$s geplant. 1 Folge einer anderen Serie wird wegen Konflikten nicht aufgezeichnet. + + + %1$d Aufnahmen wurden für %2$s geplant. %3$d Folgen anderer Serien werden wegen Konflikten nicht aufgezeichnet. + %1$d Aufnahme wurde für %2$s geplant. %3$d Folge einer anderen Serie wird wegen Konflikten nicht aufgezeichnet. + "Aufgenommenes Programm wurde nicht gefunden." "Ähnliche Aufnahmen" - "(Keine Programmbeschreibung)" %1$d Aufnahmen %1$d Aufnahme @@ -333,9 +338,10 @@ "Serieneinstellungen" "Serienaufnahme starten" "Serienaufnahme beenden" - "Sie möchten die Serienaufnahme beenden?" + "Du möchtest die Serienaufnahme beenden?" "Die aufgenommenen Folgen werden in der DVR-Bibliothek gespeichert." "Beenden" + "Momentan werden keine Folgen ausgestrahlt." "Keine Folgen verfügbar.\nSie werden aufgenommen, sobald sie verfügbar sind." (%1$d Minuten) @@ -347,4 +353,5 @@ "Heute %1$s" "Morgen %1$s" "Punkte" + "Aufgenommene Programme" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 9e8e042c..f9967894 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -20,9 +20,8 @@ "μονοφων." "στερεοφ." "Στοιχ. ελέγ. αναπαραγωής" - "Πρόσφατα κανάλια" + "Κανάλια" "Επιλογές TV" - "Επιλογές PIP" "Τα στοιχεία ελέγχου αναπαραγωγής δεν είναι διαθέσιμα γι\' αυτό το κανάλι" "Αναπαραγωγή ή παύση" "Γρήγορη προώθηση" @@ -35,37 +34,19 @@ "Υπότιτλοι" "Τρόπος εμφάν." "PIP" - "Ενεργό" - "Ανενεργό" "Πολλαπλός ήχος" "Περισσότ. κανάλια" "Ρυθμίσεις" - "Πηγή" - "Ανταλλαγή" - "Ενεργό" - "Ανενεργό" - "Ήχος" - "Κύριο" - "Παράθυρο PIP" - "Διάταξη" - "Κάτω δεξιά" - "Επάνω δεξιά" - "Επάνω αριστερά" - "Κάτω αριστερά" - "Δίπλα" - "Μέγεθος" - "Μεγάλο" - "Μικρό" - "Πηγή εισόδου" "Τηλεόραση (κεραία/καλωδιακή)" "Δεν υπάρχουν πληροφορίες προγράμματος" "Δεν υπάρχουν πληροφορίες" "Αποκλεισμένο κανάλι" - "Άγνωστη γλώσσα" + "Άγνωστη γλώσσα" + "Υπότιτλοι %1$d" "Υπότιτλοι" "Ανενεργό" "Προσ. μορφοποίησης" - "Ορίστε προτ. συστήματος για υπότιτλους" + "Προτιμήσεις συστήματος για υπότιτλους" "Τρόπος εμφάν." "Πολλαπλός ήχος" "μονοφωνικός ήχος" @@ -83,6 +64,7 @@ "SD" "Ομαδοποίηση κατά" "Αυτό το πρόγραμμα αποκλείστηκε" + "Αυτό το πρόγραμμα δεν έχει αξιολόγηση" "Αυτό το πρόγραμμα αξιολογήθηκε ως %1$s." "Η είσοδος δεν υποστηρίζει την αυτόματη σάρωση" "Αδύνατη η εκκίνηση της αυτόματης σάρωσης για \"%s\"" @@ -92,7 +74,6 @@ Προστέθηκε %1$d κανάλι "Δεν προστέθηκαν κανάλια" - "Δέκτης" "Γονικός έλεγχος" "Ενεργό" "Ανενεργό" @@ -108,6 +89,8 @@ "Άλλες χώρες" "Κανένα" "Κανένα" + "Χωρίς αξιολόγηση" + "Αποκλ. προγραμ. χωρίς αξιολόγ." "Κανένα" "Υψηλοί περιορισμοί" "Μεσαίοι περιορισμοί" @@ -124,6 +107,7 @@ "Εισαγάγετε PIN για να δείτε αυτό το κανάλι" "Εισαγάγετε PIN για να δείτε αυτό το πρόγραμμα" "Αυτό το πρόγραμμα αξιολογήθηκε ως %1$s. Εισαγάγετε το PIN σας για το παρακολουθήσετε" + "Αυτό το πρόγραμμα δεν έχει αξιολόγηση. Εισαγάγετε το PIN για να το παρακολουθήσετε" "Εισαγάγετε το PIN σας" "Για να ορίσετε γονικούς ελέγχους, δημιουργήστε ένα PIN" "Εισαγωγή νέου κωδικού PIN" @@ -135,22 +119,31 @@ "Λάθος PIN. Δοκιμάστε ξανά." "Δοκιμάστε ξανά, δεν υπάρχει αντιστοιχία PIN" + "Εισαγωγή ταχυδρομικού κώδικα" + "Η εφαρμογή \"Ζωντανά κανάλια\" θα χρησιμοποιεί τον ταχυδρομικό κώδικα για να παρέχει έναν πλήρη οδηγό προγράμματος για τα τηλεοπτικά κανάλια." + "Εισαγωγή ταχυδρομικού κώδικα" + "Μη έγκυρος ταχυδρομ. κώδικας" "Ρυθμίσεις" "Προσαρμογή λίστας καναλιών" "Επιλέξτε κανάλια για τον οδηγό προγράμματος" "Πηγές καναλιών" "Νέα διαθέσιμα κανάλια" "Γονικοί έλεγχοι" + "Χρονική μετατόπιση" + "Κάντε εγγραφή ενώ παρακολουθείτε, για να μπορείτε να κάνετε παύση ή επαναφορά σε ζωντανά προγράμματα.\nΠροειδοποίηση: Αυτή η λειτουργία μπορεί να μειώσει τη χωρητικότητα του εσωτερικού αποθηκευτικού χώρου μέσω της εντατικής χρήσης του." "Άδειες λογισμικού ανοικτού κώδικα" - "Άδειες λογισμικού ανοικτού κώδικα" + "Αποστολή σχολίων" "Έκδοση" "Για να παρακολουθήσετε αυτό το κανάλι, πατήστε το δεξιά και εισαγάγετε το PIN σας" "Για να παρακολουθήσετε αυτό το πρόγραμμα, πατήστε δεξιά και εισαγάγετε το PIN σας" + "Αυτό το πρόγραμμα δεν έχει αξιολόγηση.\nΓια να το παρακολουθήσετε, πατήστε το στοιχείο \"Δεξιά\" και εισαγάγετε το PIN" "Αυτό το πρόγραμμα αξιολογήθηκε ως %1$s.\nΓια να παρακολουθήσετε αυτό το πρόγραμμα, πατήστε δεξιά και εισαγάγετε το PIN σας" "Για να παρακολουθήσετε αυτό το κανάλι, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης." "Για να παρακολουθήσετε αυτό το πρόγραμμα, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης." + "Αυτό το πρόγραμμα δεν έχει αξιολόγηση.\nΓια να παρακολουθήσετε αυτό το πρόγραμμα, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης." "Αυτό το πρόγραμμα χαρακτηρίστηκε %1$s.\nΓια να παρακολουθήσετε αυτό το πρόγραμμα, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης." "Το πρόγραμμα αποκλείστηκε" + "Αυτό το πρόγραμμα δεν έχει αξιολόγηση" "Αυτό το πρόγραμμα αξιολογήθηκε ως %1$s." "Μόνο ήχος" "Ασθενές σήμα" @@ -171,7 +164,7 @@ "Νέα" "Δεν ρυθμίστηκε" "Λήψη περισσότερων πηγών" - "Περιήγηση σε εφαρμογές που προσφέρουν ζωντανά κανάλια" + "Δείτε εφαρμογές με ζωντανά κανάλια" "Υπάρχουν νέες διαθέσιμες πηγές καναλιών" "Οι νέες πηγές καναλιών προσφέρουν κανάλια.\nΡυθμίστε τες τώρα ή αργότερα μέσω των ρυθμίσεων πηγών καναλιών." "Ρύθμιση τώρα" @@ -181,8 +174,6 @@ "Πατήστε το πλήκτρο SELECT"" για να μεταβείτε στο μενού της τηλεόρασης." "Δεν βρέθηκε είσοδος τηλεόρασης" "Δεν είναι δυνατή η εύρεση εισόδου τηλεόρασης" - "Το PIP δεν υποστηρίζεται" - "Δεν υπάρχει διαθέσιμη είσοδος για εμφάνιση με PIP" "Ακατάλληλος τύπος δέκτη. Εκκινήστε την εφαρμογή Κανάλια ζωντανά για είσοδο τύπου δέκτη τηλεόρασης." "Αποτυχία συντονισμού" "Δεν βρέθηκε εφαρμογή για τη διαχείριση αυτής της ενέργειας." @@ -226,6 +217,8 @@ %1$d προγραμματ. εγγραφές %1$d προγραμματ. εγγραφή + "Ακύρωση εγγραφής" + "Διακοπή εγγραφής" "Παρακολούθηση" "Αναπαραγωγή από την αρχή" "Συνέχ. αναπαραγωγής" @@ -258,9 +251,6 @@ "Όταν υπάρχουν πολλά προγράμματα για εγγραφή την ίδια ώρα, θα γίνει εγγραφή μόνο των προγραμμάτων με τις υψηλότερες προτεραιότητες." "Αποθήκευση" "Οι εγγραφές μίας φοράς έχουν την υψηλότερη προτεραιότητα" - "Ακύρωση" - "Ακύρωση" - "Διαγραφή" "Διακοπή" "Προβολή προγραμματισ. εγγραφών" "Αυτό το συγκεκριμένο επεισόδιο" @@ -270,25 +260,28 @@ "Αντί αυτού, εγγρ. αυτού του πρ." "Ακύρωση αυτής της εγγραφής" "Προβολή τώρα" + "Διαγραφή εγγραφών…" "Με δυνατότητα εγγραφής" "Η εγγραφή προγραμματίστηκε" "Διένεξη εγγραφής" "Εγγραφή" "Αποτυχία εγγραφής" - "Γίνεται ανάγνωση προγραμμάτων για δημιουργία προγραμματισμών εγγραφής" - "Ανάγνωση προγραμμάτων" - - + "Ανάγνωση προγραμμάτων" + "Προβολή πρόσφατων εγγραφών" + "Η εγγραφή του προγράμματος %1$s δεν είναι ολοκληρωμένη." + "Οι εγγραφές των προγραμμάτων %1$s και %2$s δεν είναι ολοκληρωμένες." + "Οι εγγραφές των προγραμμάτων %1$s, %2$s και %3$s δεν είναι ολοκληρωμένες." + "Η εγγραφή του προγράμματος %1$s δεν ολοκληρώθηκε, λόγω ανεπαρκούς αποθηκευτικού χώρου." + "Οι εγγραφές των προγραμμάτων %1$s και %2$s δεν ολοκληρώθηκαν, λόγω ανεπαρκούς αποθηκευτικού χώρου." + "Οι εγγραφές των προγραμμάτων %1$s, %2$s και %3$s δεν ολοκληρώθηκαν, λόγω ανεπαρκούς αποθηκευτικού χώρου." "Το DVR χρειάζεται περισσότερο αποθηκευτικό χώρο" - "Θα μπορείτε να εγγράψετε προγράμματα με το DVR. Ωστόσο, αυτήν τη στιγμή δεν υπάρχει αρκετός αποθηκευτικός χώρος στη συσκευή σας έτσι ώστε να λειτουργήσει το DVR. Συνδέστε έναν εξωτερικό δίσκο με χωρητικότητα %1$s GB ή μεγαλύτερη και ακολουθήστε τα βήματα για να τον μορφοποιήσετε και να τον ορίσετε ως αποθηκευτικό χώρο της συσκευής." + "Θα μπορείτε να εγγράψετε προγράμματα με το DVR. Ωστόσο, αυτήν τη στιγμή δεν υπάρχει αρκετός αποθηκευτικός χώρος στη συσκευή σας έτσι ώστε να λειτουργήσει το DVR. Συνδέστε έναν εξωτερικό δίσκο με χωρητικότητα %1$d GB ή μεγαλύτερη και ακολουθήστε τα βήματα για να τον μορφοποιήσετε και να τον ορίσετε ως αποθηκευτικό χώρο της συσκευής." + "Δεν υπάρχει αρκετός αποθηκευτικός χώρος" + "Αυτό το πρόγραμμα δεν θα εγγραφεί, επειδή δεν υπάρχει αρκετός αποθηκευτικός χώρος. Δοκιμάστε να διαγράψετε ορισμένες υπάρχουσες εγγραφές." "Ο αποθηκευτικός χώρος λείπει" - "Κάποιο τμήμα του αποθηκευτικού χώρου που χρησιμοποιείται από το DVR λείπει. Συνδέστε τον εξωτερικό δίσκο που χρησιμοποιήσατε στο παρελθόν για να ενεργοποιήσετε εκ νέου το DVR. Εναλλακτικά, μπορείτε να επιλέξετε να διαγράψετε τον αποθηκευτικό χώρο αν δεν είναι πλέον διαθέσιμος." - "Να διαγραφεί ο αποθηκευτικός χώρος;" - "Όλο το εγγεγραμμένο περιεχόμενο και τα χρονοδιαγράμματα θα χαθούν." "Να διακοπεί η εγγραφή;" "Θα γίνει αποθήκευση του περιεχομένου που έχει εγγραφεί." - - + "Η εγγραφή του προγράμματος %1$s θα τερματιστεί καθώς βρίσκεται σε διένεξη με αυτό το πρόγραμμα. Το περιεχόμενο που έχει εγγραφεί θα αποθηκευτεί." "Η εγγραφή προγραμματίστηκε, αλλά έχει διενέξεις" "Η εγγραφή ξεκίνησε αλλά υπάρχουν διενέξεις" "Θα γίνει εγγραφή του προγράμματος %1$s." @@ -306,17 +299,29 @@ "Το ίδιο πρόγραμμα έχει προγραμματιστεί για εγγραφή στις %1$s." "Έχει ήδη εγγραφεί" "Αυτό το πρόγραμμα έχει ήδη εγγραφεί. Είναι διαθέσιμο στη βιβλιοθήκη DVR." - - - - - - - - + "Η εγγραφή της σειράς προγραμματίστηκε" + + %1$d εγγραφές έχουν προγραμματιστεί για τη σειρά %2$s. + %1$d εγγραφή έχει προγραμματιστεί για τη σειρά %2$s. + + + %1$d εγγραφές έχουν προγραμματιστεί για τη σειρά %2$s. %3$d από αυτές δεν θα εγγραφούν εξαιτίας διενέξεων. + %1$d εγγραφή έχει προγραμματιστεί για τη σειρά %2$s. Δεν θα γίνει η εγγραφή εξαιτίας διενέξεων. + + + %1$d εγγραφές έχουν προγραμματιστεί για τη σειρά %2$s. %3$d επεισόδια αυτής της σειράς και άλλης σειράς δεν θα εγγραφούν εξαιτίας διενέξεων. + %1$d εγγραφή έχει προγραμματιστεί για τη σειρά %2$s. %3$d επεισόδια αυτής της σειράς και άλλης σειράς δεν θα εγγραφούν εξαιτίας διενέξεων. + + + %1$d εγγραφές έχουν προγραμματιστεί για τη σειρά %2$s. 1 επεισόδιο άλλης σειράς δεν θα εγγραφεί εξαιτίας διενέξεων. + %1$d εγγραφή έχει προγραμματιστεί για τη σειρά %2$s. 1 επεισόδιο άλλης σειράς δεν θα εγγραφεί εξαιτίας διενέξεων. + + + %1$d εγγραφές έχουν προγραμματιστεί για τη σειρά %2$s. %3$d επεισόδια άλλης σειράς δεν θα εγγραφούν εξαιτίας διενέξεων. + %1$d εγγραφή έχει προγραμματιστεί για τη σειρά %2$s. %3$d επεισόδια άλλης σειράς δεν θα εγγραφούν εξαιτίας διενέξεων. + "Το εγγεγραμμένο πρόγραμμα δεν βρέθηκε." "Σχετικές εγγραφές" - "(Καμία περιγραφή προγράμματος)" %1$d εγγραφές %1$d εγγραφή @@ -336,6 +341,7 @@ "Διακοπή εγγραφής σειράς;" "Τα επεισόδια που έχουν εγγραφεί θα εξακολουθήσουν να είναι διαθέσιμα στη βιβλιοθήκη DVR." "Διακοπή" + "Δεν προβάλλονται επεισόδια ζωντανά αυτήν τη στιγμή." "Δεν υπάρχουν διαθέσιμα επεισόδια.\nΗ εγγραφή τους θα πραγματοποιηθεί όταν θα είναι διαθέσιμα." (%1$d λεπτά) @@ -347,4 +353,5 @@ "%1$s σήμερα" "%1$s αύριο" "Μουσική επένδυση" + "Εγγεγραμμένα προγράμματα" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index bda4e1ea..04a53da4 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Play controls" - "Recent channels" + "Channels" "TV options" - "PIP options" "Play controls unavailable for this channel" "Play or pause" "Fast-forward" @@ -35,33 +34,15 @@ "Closed captions" "Display mode" "PIP" - "On" - "Off" "Multi-audio" "Get more channels" "Settings" - "Source" - "Swap" - "On" - "Off" - "Sound" - "Main" - "PIP window" - "Layout" - "Bottom right" - "Top right" - "Top left" - "Bottom left" - "Side by side" - "Size" - "Big" - "Small" - "Input source" "TV (aerial/cable)" "No programme information" "No information" "Blocked channel" - "Unknown language" + "Unknown language" + "Closed captions %1$d" "Closed captions" "Off" "Customise formatting" @@ -83,6 +64,7 @@ "SD" "Group by" "This programme is blocked" + "This program is unrated" "This programme is rated %1$s" "The input doesn\'t support auto-scan" "Unable to start auto-scan for \'%s\'" @@ -92,7 +74,6 @@ %1$d channel added "No channels added" - "Tuner" "Parental controls" "On" "Off" @@ -108,6 +89,8 @@ "Other countries" "None" "None" + "Unrated" + "Block unrated programs" "None" "High restrictions" "Medium restrictions" @@ -124,6 +107,7 @@ "Enter your PIN to watch this channel" "Enter your PIN to watch this programme" "This programme is rated %1$s. Enter your PIN to watch this programme" + "This program is unrated. Enter your PIN to watch this program" "Enter your PIN" "To set parental controls, create a PIN" "Enter new PIN" @@ -135,22 +119,31 @@ "That PIN was wrong. Try again." "Try again, PIN doesn\'t match" + "Enter your postcode." + "Live TV app will use the postcode to provide a complete programme guide for the TV channels." + "Enter your postcode" + "Invalid postcode" "Settings" "Customise channel list" "Choose channels for your programme guide" "Channel sources" "New channels available" "Parental controls" + "Timeshift" + "Record while watching so that you can pause or rewind live programmes.\nWarning: This may decrease the life of the internal storage by the intensive use of the storage." "Open-source licences" - "Open-source licences" + "Send feedback" "Version" "To watch this channel, press Right and enter your PIN" "To watch this program, press Right and enter your PIN" + "This program is unrated.\nTo watch this program, press Right and enter your PIN" "This programme is rated %1$s.\nTo watch this programme, press Right and enter your PIN." "To watch this channel, use the default Live TV app." "To watch this programme, use the default Live TV app." + "This program is unrated.\nTo watch this program, use the default Live TV app." "This programme is rated %1$s.\nTo watch this programme, use the default Live TV app." "Programme is blocked" + "This program is unrated" "This programme is rated %1$s" "Audio only" "Weak signal" @@ -181,8 +174,6 @@ "Press SELECT"" to access the TV menu." "No TV input found" "Cannot find the TV input" - "PIP is not supported" - "There is no available input which can be shown with PIP" "Tuner type not suitable. Please launch Live TV app for tuner type TV input." "Tune failed" "No app was found to handle this action." @@ -226,6 +217,8 @@ %1$d recordings scheduled %1$d recording scheduled + "Cancel recording" + "Stop recording" "Watch" "Play from beginning" "Resume playing" @@ -258,9 +251,6 @@ "When there are too many programmes to be recorded at the same time, only the ones with higher priorities will be recorded." "Save" "One-time recordings have the highest priority" - "Cancel" - "Cancel" - "Forget" "Stop" "View recording schedule" "This single programme" @@ -270,25 +260,28 @@ "Record this one instead" "Cancel this recording" "Watch now" + "Delete recordings…" "Recordable" "Recording scheduled" "Recording conflict" "Recording" "Recording failed" - "Reading programs to create recording schedules" - "Reading programmes" - - + "Reading programmes" + "View recent recordings" + "The recording of %1$s is incomplete." + "The recordings of %1$s and %2$s are incomplete." + "The recordings of %1$s, %2$s and %3$s are incomplete." + "The recording of %1$s didn\'t complete due to insufficient storage." + "The recordings of %1$s and %2$s didn\'t complete due to insufficient storage." + "The recordings of %1$s, %2$s and %3$s didn\'t complete due to insufficient storage." "DVR needs more storage" - "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$sGB or larger and follow the steps to format it as device storage." + "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage." + "Not enough storage" + "This programme will not be recorded because there is not enough storage. Try deleting some existing recordings." "Missing storage" - "Some of the storage used by DVR is missing. Please connect the external drive that you used before to re-enable DVR. Alternatively, you can choose to forget the storage if it\'s no longer available." - "Forget storage?" - "All your recorded content and schedules will be lost." "Stop recording?" "The recorded content will be saved." - - + "The recording of %1$s will be stopped because it conflicts with this programme. The recorded content will be saved." "Recording scheduled but has conflicts" "Recording has started but has conflicts" "%1$s will be recorded." @@ -306,17 +299,29 @@ "The same programme has already been scheduled to be recorded at %1$s." "Already recorded" "This programme has already been recorded. It’s available in the DVR library." - - - - - - - - + "Series recording scheduled" + + %1$d recordings have been scheduled for %2$s. + %1$d recording has been scheduled for %2$s. + + + %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. It will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + "Recorded programme not found." "Related recordings" - "(No programme description)" %1$d recordings %1$d recording @@ -336,6 +341,7 @@ "Stop series recording?" "Recorded episodes will remain available in the DVR library." "Stop" + "No episodes are on air now." "No episodes are available.\nThey will be recorded once they are available." (%1$d minutes) @@ -347,4 +353,5 @@ "%1$s today" "%1$s tomorrow" "Score" + "Recorded Programmes" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index bda4e1ea..04a53da4 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Play controls" - "Recent channels" + "Channels" "TV options" - "PIP options" "Play controls unavailable for this channel" "Play or pause" "Fast-forward" @@ -35,33 +34,15 @@ "Closed captions" "Display mode" "PIP" - "On" - "Off" "Multi-audio" "Get more channels" "Settings" - "Source" - "Swap" - "On" - "Off" - "Sound" - "Main" - "PIP window" - "Layout" - "Bottom right" - "Top right" - "Top left" - "Bottom left" - "Side by side" - "Size" - "Big" - "Small" - "Input source" "TV (aerial/cable)" "No programme information" "No information" "Blocked channel" - "Unknown language" + "Unknown language" + "Closed captions %1$d" "Closed captions" "Off" "Customise formatting" @@ -83,6 +64,7 @@ "SD" "Group by" "This programme is blocked" + "This program is unrated" "This programme is rated %1$s" "The input doesn\'t support auto-scan" "Unable to start auto-scan for \'%s\'" @@ -92,7 +74,6 @@ %1$d channel added "No channels added" - "Tuner" "Parental controls" "On" "Off" @@ -108,6 +89,8 @@ "Other countries" "None" "None" + "Unrated" + "Block unrated programs" "None" "High restrictions" "Medium restrictions" @@ -124,6 +107,7 @@ "Enter your PIN to watch this channel" "Enter your PIN to watch this programme" "This programme is rated %1$s. Enter your PIN to watch this programme" + "This program is unrated. Enter your PIN to watch this program" "Enter your PIN" "To set parental controls, create a PIN" "Enter new PIN" @@ -135,22 +119,31 @@ "That PIN was wrong. Try again." "Try again, PIN doesn\'t match" + "Enter your postcode." + "Live TV app will use the postcode to provide a complete programme guide for the TV channels." + "Enter your postcode" + "Invalid postcode" "Settings" "Customise channel list" "Choose channels for your programme guide" "Channel sources" "New channels available" "Parental controls" + "Timeshift" + "Record while watching so that you can pause or rewind live programmes.\nWarning: This may decrease the life of the internal storage by the intensive use of the storage." "Open-source licences" - "Open-source licences" + "Send feedback" "Version" "To watch this channel, press Right and enter your PIN" "To watch this program, press Right and enter your PIN" + "This program is unrated.\nTo watch this program, press Right and enter your PIN" "This programme is rated %1$s.\nTo watch this programme, press Right and enter your PIN." "To watch this channel, use the default Live TV app." "To watch this programme, use the default Live TV app." + "This program is unrated.\nTo watch this program, use the default Live TV app." "This programme is rated %1$s.\nTo watch this programme, use the default Live TV app." "Programme is blocked" + "This program is unrated" "This programme is rated %1$s" "Audio only" "Weak signal" @@ -181,8 +174,6 @@ "Press SELECT"" to access the TV menu." "No TV input found" "Cannot find the TV input" - "PIP is not supported" - "There is no available input which can be shown with PIP" "Tuner type not suitable. Please launch Live TV app for tuner type TV input." "Tune failed" "No app was found to handle this action." @@ -226,6 +217,8 @@ %1$d recordings scheduled %1$d recording scheduled + "Cancel recording" + "Stop recording" "Watch" "Play from beginning" "Resume playing" @@ -258,9 +251,6 @@ "When there are too many programmes to be recorded at the same time, only the ones with higher priorities will be recorded." "Save" "One-time recordings have the highest priority" - "Cancel" - "Cancel" - "Forget" "Stop" "View recording schedule" "This single programme" @@ -270,25 +260,28 @@ "Record this one instead" "Cancel this recording" "Watch now" + "Delete recordings…" "Recordable" "Recording scheduled" "Recording conflict" "Recording" "Recording failed" - "Reading programs to create recording schedules" - "Reading programmes" - - + "Reading programmes" + "View recent recordings" + "The recording of %1$s is incomplete." + "The recordings of %1$s and %2$s are incomplete." + "The recordings of %1$s, %2$s and %3$s are incomplete." + "The recording of %1$s didn\'t complete due to insufficient storage." + "The recordings of %1$s and %2$s didn\'t complete due to insufficient storage." + "The recordings of %1$s, %2$s and %3$s didn\'t complete due to insufficient storage." "DVR needs more storage" - "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$sGB or larger and follow the steps to format it as device storage." + "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage." + "Not enough storage" + "This programme will not be recorded because there is not enough storage. Try deleting some existing recordings." "Missing storage" - "Some of the storage used by DVR is missing. Please connect the external drive that you used before to re-enable DVR. Alternatively, you can choose to forget the storage if it\'s no longer available." - "Forget storage?" - "All your recorded content and schedules will be lost." "Stop recording?" "The recorded content will be saved." - - + "The recording of %1$s will be stopped because it conflicts with this programme. The recorded content will be saved." "Recording scheduled but has conflicts" "Recording has started but has conflicts" "%1$s will be recorded." @@ -306,17 +299,29 @@ "The same programme has already been scheduled to be recorded at %1$s." "Already recorded" "This programme has already been recorded. It’s available in the DVR library." - - - - - - - - + "Series recording scheduled" + + %1$d recordings have been scheduled for %2$s. + %1$d recording has been scheduled for %2$s. + + + %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. It will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + "Recorded programme not found." "Related recordings" - "(No programme description)" %1$d recordings %1$d recording @@ -336,6 +341,7 @@ "Stop series recording?" "Recorded episodes will remain available in the DVR library." "Stop" + "No episodes are on air now." "No episodes are available.\nThey will be recorded once they are available." (%1$d minutes) @@ -347,4 +353,5 @@ "%1$s today" "%1$s tomorrow" "Score" + "Recorded Programmes" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index bda4e1ea..04a53da4 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Play controls" - "Recent channels" + "Channels" "TV options" - "PIP options" "Play controls unavailable for this channel" "Play or pause" "Fast-forward" @@ -35,33 +34,15 @@ "Closed captions" "Display mode" "PIP" - "On" - "Off" "Multi-audio" "Get more channels" "Settings" - "Source" - "Swap" - "On" - "Off" - "Sound" - "Main" - "PIP window" - "Layout" - "Bottom right" - "Top right" - "Top left" - "Bottom left" - "Side by side" - "Size" - "Big" - "Small" - "Input source" "TV (aerial/cable)" "No programme information" "No information" "Blocked channel" - "Unknown language" + "Unknown language" + "Closed captions %1$d" "Closed captions" "Off" "Customise formatting" @@ -83,6 +64,7 @@ "SD" "Group by" "This programme is blocked" + "This program is unrated" "This programme is rated %1$s" "The input doesn\'t support auto-scan" "Unable to start auto-scan for \'%s\'" @@ -92,7 +74,6 @@ %1$d channel added "No channels added" - "Tuner" "Parental controls" "On" "Off" @@ -108,6 +89,8 @@ "Other countries" "None" "None" + "Unrated" + "Block unrated programs" "None" "High restrictions" "Medium restrictions" @@ -124,6 +107,7 @@ "Enter your PIN to watch this channel" "Enter your PIN to watch this programme" "This programme is rated %1$s. Enter your PIN to watch this programme" + "This program is unrated. Enter your PIN to watch this program" "Enter your PIN" "To set parental controls, create a PIN" "Enter new PIN" @@ -135,22 +119,31 @@ "That PIN was wrong. Try again." "Try again, PIN doesn\'t match" + "Enter your postcode." + "Live TV app will use the postcode to provide a complete programme guide for the TV channels." + "Enter your postcode" + "Invalid postcode" "Settings" "Customise channel list" "Choose channels for your programme guide" "Channel sources" "New channels available" "Parental controls" + "Timeshift" + "Record while watching so that you can pause or rewind live programmes.\nWarning: This may decrease the life of the internal storage by the intensive use of the storage." "Open-source licences" - "Open-source licences" + "Send feedback" "Version" "To watch this channel, press Right and enter your PIN" "To watch this program, press Right and enter your PIN" + "This program is unrated.\nTo watch this program, press Right and enter your PIN" "This programme is rated %1$s.\nTo watch this programme, press Right and enter your PIN." "To watch this channel, use the default Live TV app." "To watch this programme, use the default Live TV app." + "This program is unrated.\nTo watch this program, use the default Live TV app." "This programme is rated %1$s.\nTo watch this programme, use the default Live TV app." "Programme is blocked" + "This program is unrated" "This programme is rated %1$s" "Audio only" "Weak signal" @@ -181,8 +174,6 @@ "Press SELECT"" to access the TV menu." "No TV input found" "Cannot find the TV input" - "PIP is not supported" - "There is no available input which can be shown with PIP" "Tuner type not suitable. Please launch Live TV app for tuner type TV input." "Tune failed" "No app was found to handle this action." @@ -226,6 +217,8 @@ %1$d recordings scheduled %1$d recording scheduled + "Cancel recording" + "Stop recording" "Watch" "Play from beginning" "Resume playing" @@ -258,9 +251,6 @@ "When there are too many programmes to be recorded at the same time, only the ones with higher priorities will be recorded." "Save" "One-time recordings have the highest priority" - "Cancel" - "Cancel" - "Forget" "Stop" "View recording schedule" "This single programme" @@ -270,25 +260,28 @@ "Record this one instead" "Cancel this recording" "Watch now" + "Delete recordings…" "Recordable" "Recording scheduled" "Recording conflict" "Recording" "Recording failed" - "Reading programs to create recording schedules" - "Reading programmes" - - + "Reading programmes" + "View recent recordings" + "The recording of %1$s is incomplete." + "The recordings of %1$s and %2$s are incomplete." + "The recordings of %1$s, %2$s and %3$s are incomplete." + "The recording of %1$s didn\'t complete due to insufficient storage." + "The recordings of %1$s and %2$s didn\'t complete due to insufficient storage." + "The recordings of %1$s, %2$s and %3$s didn\'t complete due to insufficient storage." "DVR needs more storage" - "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$sGB or larger and follow the steps to format it as device storage." + "You will be able to record programmes with DVR. At the moment there is not enough storage on your device for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage." + "Not enough storage" + "This programme will not be recorded because there is not enough storage. Try deleting some existing recordings." "Missing storage" - "Some of the storage used by DVR is missing. Please connect the external drive that you used before to re-enable DVR. Alternatively, you can choose to forget the storage if it\'s no longer available." - "Forget storage?" - "All your recorded content and schedules will be lost." "Stop recording?" "The recorded content will be saved." - - + "The recording of %1$s will be stopped because it conflicts with this programme. The recorded content will be saved." "Recording scheduled but has conflicts" "Recording has started but has conflicts" "%1$s will be recorded." @@ -306,17 +299,29 @@ "The same programme has already been scheduled to be recorded at %1$s." "Already recorded" "This programme has already been recorded. It’s available in the DVR library." - - - - - - - - + "Series recording scheduled" + + %1$d recordings have been scheduled for %2$s. + %1$d recording has been scheduled for %2$s. + + + %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. It will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + "Recorded programme not found." "Related recordings" - "(No programme description)" %1$d recordings %1$d recording @@ -336,6 +341,7 @@ "Stop series recording?" "Recorded episodes will remain available in the DVR library." "Stop" + "No episodes are on air now." "No episodes are available.\nThey will be recorded once they are available." (%1$d minutes) @@ -347,4 +353,5 @@ "%1$s today" "%1$s tomorrow" "Score" + "Recorded Programmes" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 14e9f506..7e1c79dc 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -20,9 +20,8 @@ "mono" "estéreo" "Controles de reproducción" - "Recientes" + "Canales" "Opciones de TV" - "Opciones de PIP" "Los controles de reproducción no están disponibles en este canal." "Reproducir o pausar" "Avanzar" @@ -35,37 +34,19 @@ "Subtítulos" "Modo pantalla" "PIP" - "Activada" - "Desactivada" "Varios audios" "Obtener canales" "Configuración" - "Fuente" - "Cambiar" - "Activada" - "Desactivada" - "Sonido" - "Principal" - "Ventana PIP" - "Diseño" - "Abajo derecha" - "Arriba derecha" - "Arriba izqda." - "Abajo izquierda" - "Lado a lado" - "Tamaño" - "Grande" - "Pequeña" - "Fuente de entrada" "Televisión (antena/cable)" "No hay información del programa" "Sin información" "Canal bloqueado" - "Idioma desconocido" + "Idioma desconocido" + "Subtítulos opcionales en %1$d" "Subtítulos opcionales" "Desactivado" "Personalizar formato" - "Preferencias sistema para los subtítulos" + "Preferencias del sistema para los subtítulos" "Modo de pantalla" "Varios audios" "mono" @@ -83,6 +64,7 @@ "SD" "Agrupar por" "Este programa está bloqueado." + "Este programa no está clasificado" "Calificación de este programa: %1$s" "La entrada no admite la búsqueda automática." "No se puede iniciar la búsqueda automática de \"%s\"." @@ -92,7 +74,6 @@ Se agregó %1$d canal "No se agregaron canales." - "Sintonizador" "Control. parentales" "Activado" "Desactivado" @@ -108,6 +89,8 @@ "Otros países" "Ninguno" "Ninguno" + "Sin clasificar" + "Bloquear progr. sin clasificar" "Ninguno" "Restricciones altas" "Restricciones medias" @@ -124,6 +107,7 @@ "Ingresa tu PIN para ver este canal" "Ingresa tu PIN para ver este programa" "Este programa está clasificado como %1$s. Ingresa tu PIN para mirarlo." + "Este programa no está clasificado. Ingresa tu PIN para verlo" "Ingresa tu PIN" "Para configurar controles parentales, debes crear un PIN" "Ingresar nuevo PIN" @@ -135,22 +119,31 @@ "El PIN es incorrecto. Vuelve a intentarlo." "Inténtalo de nuevo, el PIN no coincide." + "Ingresa tu código postal" + "La app de Live Channels usará el código postal para brindar la guía completa de programas de los canales de TV." + "Ingresa tu código postal" + "Código postal no válido" "Configuración" "Personalizar lista de canales" "Seleccionar canales de la guía de programas" "Fuentes de canales" "Nuevos canales disponibles" "Controles parentales" + "Cambio de tiempo" + "Graba el contenido mientras lo miras para poder pausar o retroceder los programas en vivo.\nAdvertencia: Es posible que esta función reduzca la vida útil del almacenamiento interno debido a su uso intensivo." "Licencias de código abierto" - "Licencias de código abierto" + "Envía comentarios" "Versión" "Para mirar este canal, presiona la tecla hacia la derecha e ingresa el PIN." "Para mirar este programa, presiona la tecla hacia la derecha e ingresa el PIN." + "Este programa no está clasificado.\nPara verlo, presiona Derecha y, luego, ingresa tu PIN" "Este programa se calificó como %1$s.\nPara mirarlo, presiona la tecla hacia la derecha e ingresa el PIN." "Para mirar este canal, usa la app de TV en vivo predeterminada." "Para mirar este programa, usa la app de TV en vivo predeterminada." + "Este programa no está clasificado.\nPara verlo, usa la app predeterminada de TV en vivo." "Este programa está calificado como %1$s.\nPara mirarlo, usa la app de TV en vivo predeterminada." "El programa está bloqueado." + "Este programa no está clasificado" "Calificación de este programa: %1$s" "Solo audio" "Señal débil" @@ -171,7 +164,7 @@ "Nueva" "Sin configurar" "Obtener más fuentes" - "Explorar apps que ofrecen Canales en vivo" + "Ver apps que ofrecen Canales en vivo" "Nuevas fuentes de canales disponibles" "Nuevas fuentes de canales con canales disponibles.\nPuedes configurarlas ahora o más tarde en la configuración de fuentes de canales." "Configurar ahora" @@ -181,8 +174,6 @@ "Presiona SELECCIONAR"" para acceder al menú de la televisión." "No se encontró ninguna entrada de TV." "No se puede encontrar la entrada de TV." - "No se admite PIP." - "No hay entradas disponibles que se puedan mostrar con PIP." "Tipo de sintonizador no admitido. Abre la aplicación Canales en vivo para el tipo de sintonizador de entrada de TV." "Error al sintonizar" "No se encontró ninguna aplicación que pueda realizar esta acción." @@ -226,6 +217,8 @@ %1$d grabaciones programadas %1$d grabación programada + "Cancelar grabación" + "Detener grabación" "Mirar" "Reproducir desde comienzo" "Reanudar reproducción" @@ -258,9 +251,6 @@ "Cuando hay demasiados programas para grabar al mismo tiempo, solo se grabarán los que tengan mayor prioridad." "Guardar" "Las grabaciones únicas tienen mayor prioridad" - "Cancelar" - "Cancelar" - "Borrar" "Detener" "Ver cronograma de grabación" "Solo este programa" @@ -270,25 +260,28 @@ "Grabar este programa en su lugar" "Cancelar esta grabación" "Mirar ahora" + "Borrar grabaciones…" "Se puede grabar" "Grabación programada" "Error de grabación" "Grabando" "Se produjo un error al grabar" - "Leyendo programas para crear programaciones de grabación" - "Leyendo programas" - - + "Leyendo programas" + "Ver grabaciones recientes" + "No se completó la grabación de %1$s" + "No se completó la grabación de %1$s y %2$s" + "No se completó la grabación de %1$s, %2$s y %3$s" + "No se completó la grabación de %1$s por falta de espacio de almacenamiento." + "No se completó la grabación de %1$s y %2$s por falta de espacio de almacenamiento." + "No se completó la grabación de %1$s, %2$s y %3$s por falta de espacio de almacenamiento." "El DVR necesita más espacio de almacenamiento" - "Si bien puedes grabar programas con el DVR, no hay espacio de almacenamiento suficiente en tu dispositivo para usar esta opción. Conecta una unidad externa de %1$s GB como mínimo y sigue los pasos para formatearla como almacenamiento del dispositivo." + "Si bien puedes grabar programas con el DVR, no hay espacio de almacenamiento suficiente en tu dispositivo para usar esta opción. Conecta una unidad externa de %1$d GB como mínimo y sigue los pasos para formatearla como almacenamiento del dispositivo." + "No hay suficiente espacio de almacenamiento" + "No se grabará este programa porque no hay suficiente espacio de almacenamiento. Intenta borrar algunas grabaciones existentes." "Falta almacenamiento" - "Falta parte del almacenamiento que se usa para DVR. Conecta la unidad externa que usaste anteriormente para volver a habilitar esta función. También puedes borrar el almacenamiento si ya no está disponible." - "¿Borrar almacenamiento?" - "Se perderán todas las programaciones y los contenidos grabados." "¿Deseas detener la grabación?" "Se guardará el contenido grabado." - - + "Se detendrá la grabación de %1$s porque entra en conflicto con este programa. Sin embargo, se guardará el contenido grabado." "Grabación programada con conflictos" "Comenzó la grabación, pero tiene problemas" "Se grabará %1$s." @@ -306,17 +299,29 @@ "El mismo programa se grabará a las %1$s." "Programa grabado" "Este programa ya está grabado. Está disponible en la biblioteca de DVR." - - - - - - - - + "Se programó la grabación de series" + + Se programaron %1$d grabaciones de %2$s. + Se programó %1$d grabación de %2$s. + + + Se programaron %1$d grabaciones de %2$s. No se grabarán %3$d de ellas debido a algunos conflictos. + Se programó %1$d grabación de %2$s. No podrá completarse debido a algunos conflictos. + + + Se programaron %1$d grabaciones de %2$s. No se grabarán %3$d episodios de esta serie y de otras debido a algunos conflictos. + Se programó %1$d grabación de %2$s. No se grabarán %3$d episodios de esta serie y de otras debido a algunos conflictos. + + + Se programaron %1$d grabaciones de %2$s. No se grabará 1 episodio de otra serie debido a algunos conflictos. + Se programó %1$d grabación de %2$s. No se grabará 1 episodio de otra serie debido a algunos conflictos. + + + Se programaron %1$d grabaciones de %2$s. No se grabarán %3$d episodios de otra serie debido a algunos conflictos. + Se programó %1$d grabación de %2$s. No se grabarán %3$d episodios de otra serie debido a algunos conflictos. + "No se encontró el programa grabado." "Grabaciones relacionadas" - "(Sin descripción del programa)" %1$d grabaciones %1$d grabación @@ -336,6 +341,7 @@ "¿Detener grabación de la serie?" "Los episodios grabados estarán disponibles en la biblioteca de DVR." "Detener" + "No hay episodios en vivo disponibles en este momento." "No hay episodios disponibles.\nSe grabarán cuando lo estén." (%1$d minutos) @@ -347,4 +353,5 @@ "Hoy: %1$s" "Mañana: %1$s" "Puntuación" + "Programas grabados" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index badab6a4..b5d00be6 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -20,9 +20,8 @@ "mono" "estéreo" "Controles de reproducción" - "Canales recientes" + "Canales" "Opciones de TV" - "Opciones de PIP" "Controles de reproducción no disponibles en este canal" "Reproducir o pausar" "Avance rápido" @@ -35,33 +34,15 @@ "Subtítulos" "Modo de pantalla" "PIP" - "Activado" - "Desactivado" "Varios audios" "Más canales" "Ajustes" - "Fuente" - "Cambiar" - "Activado" - "Desactivado" - "Sonido" - "Principal" - "Ventana PIP" - "Diseño" - "Abajo derecha" - "Arriba derecha" - "Arriba izquierda" - "Abajo izquierda" - "En paralelo" - "Dimensiones" - "Grande" - "Pequeño" - "Fuente de entrada" "TV (antena/cable)" "No hay información del programa" "Sin información" "Canal bloqueado" - "Idioma desconocido" + "Idioma desconocido" + "Subtítulos en %1$d" "Subtítulos" "Desactivados" "Personalizar formato" @@ -73,7 +54,7 @@ "envolvente 5.1" "envolvente 7.1" "%d canales" - "Personalizar lista" + "Personalizar lista de canales" "Seleccionar grupo" "Anular selección" "Agrupar por" @@ -83,6 +64,7 @@ "SD" "Agrupar por" "Este programa está bloqueado" + "Este programa no se ha clasificado" "Este programa se ha clasificado como %1$s" "La entrada no admite la búsqueda automática" "No se puede iniciar la búsqueda automática de %s" @@ -92,8 +74,7 @@ %1$d canal añadido "No se han añadido canales" - "Sintonizador" - "Controles parentales" + "Control parental" "Activados" "Desactivados" "Canales bloqueados" @@ -108,6 +89,8 @@ "Otros países" "Ninguno" "Ninguna" + "Sin clasificar" + "Bloquear progr. sin clasificar" "Ninguna" "Restricciones altas" "Restricciones medias" @@ -124,8 +107,9 @@ "Introduce el PIN para ver este canal" "Introduce el PIN para ver este programa" "Este programa está clasificado como %1$s. Introduce el PIN para verlo" + "Este programa no se ha clasificado. Introduce tu PIN para verlo" "Introduce el número PIN" - "Para establecer controles parentales, debes crear un PIN" + "Para establecer control parental, debes crear un PIN" "Introduce el nuevo PIN" "Confirma el número PIN" "Introduce el número PIN" @@ -135,22 +119,31 @@ "Ese PIN era incorrecto. Vuelve a intentarlo." "Vuelve a intentarlo, el PIN no coincide" + "Introduce tu código postal" + "La aplicación TV en directo se basa en el código postal para ofrecer una programación completa de los canales de televisión disponibles." + "Introduce tu código postal" + "El código postal no es válido" "Ajustes" "Personalizar lista de canales" - "Seleccionar canales de la programación" + "Seleccionar canales para la guía de programas" "Fuentes de canales" "Nuevos canales disponibles" "Control parental" - "Licencias de software libre" - "Licencias software libre" + "Cambio de tiempo" + "Graba el contenido mientras lo ves para poder pausar o rebobinar los programas en directo.\nAdvertencia: Es posble que esta función reduzca la duración del almacenamiento interno debido a su uso intensivo de este." + "Licencias de código abierto" + "Enviar sugerencias" "Versión" "Para ver este canal, pulsa la tecla hacia la derecha e introduce el número PIN" "Para ver este programa, pulsa la tecla hacia la derecha e introduce el número PIN" + "Este programa no se ha clasificado.\nPara verlo, pulsa Derecha e introduce tu PIN" "Este programa se ha clasificado como %1$s.\nPara verlo, pulsa la tecla hacia la derecha e introduce el número PIN." "Para ver este canal, utiliza la aplicación de TV en directo predeterminada." "Para ver este programa, utiliza la aplicación de TV en directo predeterminada." + "Este programa no se ha clasificado.\nPara verlo, utiliza la aplicación de TV en directo predeterminada." "Este programa está clasificado como %1$s.\nPara verlo, utiliza la aplicación de TV en directo predeterminada." "El programa está bloqueado" + "Este programa no se ha clasificado" "Este programa se ha clasificado como %1$s" "Solo audio" "Señal débil" @@ -171,7 +164,7 @@ "Nuevas" "Sin configurar" "Obtener más fuentes" - "Explorar aplicaciones que ofrecen TV en directo" + "Ver aplicaciones de TV en directo" "Nuevas fuentes de canales disponibles" "Hay canales disponibles en las nuevas fuentes de canales.\nPuedes configurarlos ahora o más tarde en los ajustes correspondientes." "Configurar ahora" @@ -181,8 +174,6 @@ "Pulsa SELECCIONAR"" para acceder al menú de la TV." "No se han encontrado entradas de TV" "No se puede encontrar la entrada de TV" - "PIP no admitido" - "No hay entradas disponibles que se puedan mostrar con PIP" "Tipo de sintonizador inadecuado. Abre Canales en directo para seleccionar la entrada de TV del tipo de sintonizador." "Error al sintonizar" "No se ha encontrado ninguna aplicación que pueda realizar esta acción." @@ -226,6 +217,8 @@ %1$d grabaciones programadas %1$d grabación programada + "Cancelar grabación" + "Detener grabación" "Ver" "Reproducir desde inicio" "Reanudar reproducción" @@ -258,9 +251,6 @@ "Cuando haya demasiados programas que grabar al mismo tiempo, solo se grabarán aquellos con mayor prioridad." "Guardar" "Las grabaciones únicas son las que tienen mayor prioridad" - "Cancelar" - "Cancelar" - "Olvidar" "Detener" "Ver programación de grabación" "Este programa" @@ -270,25 +260,28 @@ "Grabar esta en su lugar" "Cancelar esta grabación" "Ver ahora" + "Eliminar grabaciones…" "Se puede grabar" "Grabación programada" "Problema de grabación" "Grabación" "No se ha podido grabar" - "Leyendo programas para crear programaciones de grabación" - "Leyendo programas" - - + "Leyendo programas" + "Ver grabaciones recientes" + "La grabación de %1$s está incompleta." + "Las grabaciones de %1$s y %2$s están incompletas." + "Las grabaciones de %1$s, %2$s y %3$s están incompletas." + "No se ha completado la grabación de %1$s porque no hay espacio de almacenamiento suficiente." + "No se han completado las grabaciones de %1$s y %2$s porque no hay espacio de almacenamiento suficiente." + "No se han completado las grabaciones de %1$s, %2$s y %3$s porque no hay espacio de almacenamiento suficiente." "Se necesita más almacenamiento para el DVR" - "Puedes grabar programas con el DVR, pero no tienes suficiente espacio de almacenamiento en el dispositivo para que el DVR funcione. Conecta una unidad externa que tenga %1$s GB como mínimo y sigue los pasos para formatearlo como almacenamiento del dispositivo." + "Puedes grabar programas con el DVR, pero no tienes suficiente espacio de almacenamiento en el dispositivo para que el DVR funcione. Conecta una unidad externa que tenga %1$d GB como mínimo y sigue los pasos para formatearlo como almacenamiento del dispositivo." + "No hay suficiente espacio de almacenamiento" + "Este programa no se grabará porque no hay espacio de almacenamiento suficiente. Borra algunas grabaciones." "No se puede acceder al almacenamiento" - "No se puede acceder a parte del almacenamiento utilizado por el DVR. Para volver a habilitarlo, conecta la unidad externa que has utilizado anteriormente. También puedes indicar que se olvide el almacenamiento si ya no está disponible." - "¿Olvidar almacenamiento?" - "Se perderán todo el contenido grabado y las programaciones." "¿Detener grabación?" "El contenido grabado se guardará." - - + "La grabación de %1$s se detendrá porque entra en conflicto con este programa. El contenido grabado se guardará." "Grabación programada con conflictos" "La grabación se ha iniciado, pero tiene conflictos" "Se grabará %1$s." @@ -306,17 +299,29 @@ "Ya se ha programado la grabación del mismo programa para esta hora: %1$s." "Ya se ha grabado" "Este programa ya se ha grabado y está disponible en la colección del DVR." - - - - - - - - + "Grabación de series programada" + + Se han programado %1$d grabaciones para %2$s. + Se ha programado %1$d grabación para %2$s. + + + Se han programado %1$d grabaciones para %2$s. Episodios que no se grabarán debido a conflictos: %3$d. + Se ha programado %1$d grabación para %2$s. Debido a conflictos, no se grabará. + + + Se han programado %1$d grabaciones para %2$s. Episodios de esta y otras series que no se grabarán debido a conflictos: %3$d. + Se ha programado %1$d grabación para %2$s. Episodios de esta y otras series que no se grabarán debido a conflictos: %3$d. + + + Se han programado %1$d grabaciones para %2$s. Debido a conflictos, no se grabará 1 episodio de otra serie. + Se ha programado %1$d grabación para %2$s. Debido a conflictos, no se grabará 1 episodio de otra serie. + + + Se han programado %1$d grabaciones para %2$s. Episodios de otras series que no se grabarán debido a conflictos: %3$d. + Se ha programado %1$d grabación para %2$s. Episodios de otras series que no se grabarán debido a conflictos: %3$d. + "No se ha encontrado el programa grabado." "Grabaciones relacionadas" - "(No hay ninguna descripción)" %1$d grabaciones %1$d grabación @@ -336,6 +341,7 @@ "¿Detener la grabación de series?" "Los episodios grabados seguirán estando disponibles en la colección del DVR." "Detener" + "No se está emitiendo ningún episodio." "No hay episodios disponibles.\nSe grabarán cuando estén disponibles." (%1$d minutos) @@ -347,4 +353,5 @@ "Hoy de %1$s" "Mañana de %1$s" "Puntuación" + "Programas grabados" diff --git a/res/values-et-rEE-v23/strings.xml b/res/values-et-rEE-v23/strings.xml new file mode 100644 index 00000000..e981deeb --- /dev/null +++ b/res/values-et-rEE-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Kanalid" + diff --git a/res/values-et-rEE/arrays.xml b/res/values-et-rEE/arrays.xml new file mode 100644 index 00000000..2d1d11fd --- /dev/null +++ b/res/values-et-rEE/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Tavaline" + "Täis" + "Suumimine" + + + "Kõik kanalid" + "Pere/lapsed" + "Sport" + "Ostud" + "Filmid" + "Komöödia" + "Reisimine" + "Draama" + "Haridus" + "Loomad/loodus" + "Uudised" + "Mängud" + "Kunstid" + "Meelelahutus" + "Elustiil" + "Muusika" + "Esilinastus" + "Tehnika/teadus" + + + "Reaalajakanalid" + "Lihtne viis sisu avastamiseks" + "Rakenduste allalaadimine ja kanalite hankimine" + "Kanalivaliku kohandamine" + + + "Vaadake oma rakendustest sisu, nagu vaataksite telekanaleid." + "Sirvige oma rakendustes sisu tuttava kava ja kasutajasõbraliku liidesega (\njust nagu telekanalite puhul)." + "Lisage rohkem kanaleid, installides rakendusi, mis pakuvad reaalajakanaleid. \nLeidke ühilduvaid rakendusi Google Play poest, kasutades TV-menüüs olevat linki." + "Seadistage kanaliloendi kohandamiseks oma äsja installitud kanaliallikad. \nAlustamiseks tehke menüüs Seaded valik Kanaliallikad." + + diff --git a/res/values-et-rEE/rating_system_strings.xml b/res/values-et-rEE/rating_system_strings.xml new file mode 100644 index 00000000..5cee5abd --- /dev/null +++ b/res/values-et-rEE/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Saated võivad sisaldada alla 15-aastastele lastele sobimatut materjali ja otsuse sobilikkuse kohta peaks langetama lapsevanem." + "Saated võivad sisaldada alla 19-aastastele sobimatut materjali, mistõttu ei ole need sobilikud alla 19-aastastele noortele." + "Kahemõttelise sisuga dialoog" + "Vulgaarne tekst" + "Seksuaalne sisu" + "Vägivald" + "Väljamõeldud vägivald" + "See saade on sobilik kõikidele lastele." + "See saade on mõeldud vähemalt 7-aastastele lastele." + "Enamik vanemaid peaks seda saadet sobilikuks igas vanuses lastele." + "See saade sisaldab materjale, mida vanemad võivad pidada sobimatuks lastele. Vanemad soovivad tõenäoliselt seda vaadata koos lastega." + "Selle saate sisu on paljude vanemate arvates sobimatu alla 14-aastastele lastele." + "See saade on mõeldud täiskasvanutele ja võib seega olla sobimatu alla 17-aastastele lastele." + "Filmi hinnangud" + "Üldised vaatajaskonnad. Keelatud on sisu, mida vanemad ei lubaks lastel vaadata." + "Soovitatav on vanemlik järelevalve. Võib sisaldada materjali, mida lapsevanemad ei soovi väikestele lastele näidata." + "Vanemlik järelevalve on tungivalt soovitatav. Teatud materjal võib olla eelteismeliste jaoks sobimatu." + "Piiratud, sisaldab teatud määral täiskasvanutele mõeldud materjali. Vanematel soovitatakse enne väikese lapsega filmi vaatamist selle kohta rohkem teavet hankida." + "17-aastastele ja noorematele keelatud. Selgelt täiskasvanutele mõeldud sisu. Lastele keelatud." + diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml new file mode 100644 index 00000000..77596a47 --- /dev/null +++ b/res/values-et-rEE/strings.xml @@ -0,0 +1,357 @@ + + + + + "mono" + "stereo" + "Esituse juhtnupud" + "Kanalid" + "TV-valikud" + "Esituse juhtelemendid ei ole selle kanali puhul saadaval" + "Esitamine või peatamine" + "Edasikerimine" + "Tagasikerimine" + "Järgmine" + "Eelmine" + "Saatekava" + "Saadaval on uued kanalid" + "Ava %1$s" + "Subtiitrid" + "Kuvarežiim" + "PIP" + "Multiaudio" + "Hangi kanaleid" + "Seaded" + "TV (antenn/kaabel)" + "Programmiteavet pole" + "Teave puudub" + "Blokeeritud kanal" + "Tundmatu keel" + "Subtiitrid %1$d" + "Subtiitrid" + "Väljas" + "Vormingu kohandamine" + "Määrake subtiitrite süsteemiül. eelist." + "Kuvarežiim" + "Multiaudio" + "mono" + "stereo" + "Ruumiline heli 5.1" + "Ruumiline heli 7.1" + "%d kanalit" + "Kanaliloendi kohandam." + "Rühma valimine" + "Rühma valiku tühist." + "Rühmitamisalus:" + "Kanali allikas" + "HD/SD" + "HD" + "SD" + "Rühmitamisalus:" + "See programm on blokeeritud" + "Seda programmi ei ole hinnatud." + "Selle saate reiting on %1$s" + "Sisend ei toeta automaatset skannimist" + "Sisendi „%s” automaatset skannimist ei saa alustada" + "Subtiitrite süsteemiüleseid eelistusi ei saa käivitada." + + Lisatud on %1$d kanalit + Lisatud on %1$d kanal + + "Ühtegi kanalit ei lisatud" + "Van. järelevalve" + "Sees" + "Väljas" + "Blokeeritud kanalid" + "Blokeeri kõik" + "Deblokeeri kõik" + "Peidetud kanalid" + "Programmipiirangud" + "Muuda PIN-koodi" + "Hinnangusüsteemid" + "Hinnangud" + "Kuva hinnangusüsteemid" + "Muud riigid" + "Puudub" + "Puudub" + "Hindamata" + "Hindamata progr. blokeerimine" + "Puudub" + "Suured piirangud" + "Keskmised piirangud" + "Väikesed piirangud" + "Kohandatud" + "Sisu sobib lastele" + "Sisu sobib vanematele lastele" + "Sisu sobib teismelistele" + "Käsitsi määratud piirangud" + + + "%1$s & alamreitingud" + "Alamreitingud" + "Kanali vaatamiseks sisestage PIN-kood" + "Saate vaatamiseks sisestage PIN-kood" + "Selle saate reiting on %1$s. Selle saate vaatamiseks sisestage oma PIN-kood" + "Seda programmi ei ole hinnatud. Programmi vaatamiseks sisestage oma PIN-kood." + "Sisestage PIN-kood" + "Vanemliku järelevalve määramiseks looge PIN-kood" + "Sisestage uus PIN-kood" + "Kinnitage PIN-kood" + "Sisestage praegune PIN-kood" + + Sisestasite vale PIN-koodi viis korda.\nProovige uuesti %1$d sekundi pärast. + Sisestasite vale PIN-koodi viis korda.\nProovige uuesti %1$d sekundi pärast. + + "See PIN-kood oli vale. Proovige uuesti." + "Proovige uuesti, PIN-kood pole õige" + "Sisestage sihtnumber." + "Rakendus Reaalajakanalid kasutab telekanalitele täieliku saatekava pakkumiseks sihtnumbrit." + "Sisestage sihtnumber" + "Sobimatu sihtnumber" + "Seaded" + "Kohanda kanaliloendit" + "Kanalite valimine saatekava jaoks" + "Kanali allikad" + "Saadaval on uued kanalid" + "Vanemlik järelevalve" + "Ajanihe" + "Salvestage vaatamise ajal, et saaksite otseülekande programme peatada või tagasi kerida.\nHoiatus: see võib vähendada sisemise salvestusruumi kasutusaega, kuna salvestusruumi kasutatakse intensiivselt." + "Avatud lähtekoodi litsentsid" + "Tagasiside saatmine" + "Versioon" + "Kanali vaatamiseks vajutage paremale ja sisestage PIN-kood" + "Saate vaatamiseks vajutage paremale ja sisestage PIN-kood" + "Seda programmi ei ole hinnatud.\nSelle programmi vaatamiseks vajutage valikut Paremale ja sisestage oma PIN-kood" + "Saate reiting on %1$s\nSaate vaatamiseks vajutage valikut Paremale ja sisestage PIN-kood." + "Selle kanali vaatamiseks kasutage otsesaadete vaikerakendust." + "Selle saate vaatamiseks kasutage otsesaadete vaikerakendust." + "Seda programmi ei ole hinnatud.\nSelle programmi vaatamiseks kasutage Live TV vaikerakendust." + "Selle saate reiting on %1$s.\nSelle saate vaatamiseks kasutage otsesaadete vaikerakendust." + "Saade on blokeeritud" + "Seda programmi ei ole hinnatud." + "Selle saate reiting on %1$s" + "Ainult heli" + "Nõrk signaal" + "Interneti-ühendus puudub" + + Seda kanalit ei saa kuni kellani %1$s esitada, kuna salvestatakse teisi kanaleid. \n\nSalvestamise ajakava kohandamiseks vajutage paremnoolt. + Seda kanalit ei saa kuni kellani %1$s esitada, kuna salvestatakse teist kanalit. \n\nSalvestamise ajakava kohandamiseks vajutage paremnoolt. + + "Pealkiri puudub" + "Kanal on blokeeritud" + "Uus" + "Allikad" + + %1$d kanalit + %1$d kanal + + "Ühtegi kanalit pole saadaval" + "Uus" + "Pole seadistatud" + "Hankige rohkem allikaid" + "Sirvige rakendusi, mis pakuvad reaalajakanaleid" + "Saadaval on uued kanaliallikad" + "Uutel kanaliallikatel on kanaleid pakkuda.\nSeadistage need kohe või tehke seda hiljem kanaliallikate seadetes." + "Seadista kohe" + "OK, selge" + + + "Teleri menüüle juurdepääsemiseks ""vajutage nuppu SELECT""." + "TV-sisendit ei leitud" + "TV-sisendit ei õnnestu leida" + "Sobimatu tuuneri tüüp. Käivitage tuuneri tüübi TV-sisendi jaoks rakendus Otseülekande kanalid." + "Häälestamine ebaõnnestus" + "Selle toimingu käsitlemiseks ei leitud ühtegi rakendust." + "Kõik allikakanalid on peidetud.\nValige vaatamiseks vähemalt üks kanal." + "Video pole ootamatult saadaval" + "Klahv TAGASI on mõeldud ühendatud seadme jaoks. Väljumiseks vajutage nuppu AVAEKRAAN." + "Reaalajas kanalid vajavad telekavade lugemiseks luba." + "Allikate seadistamine" + "Reaalajakanalid ühendavad tavaliste telekanalite kasutuskogemuse ja rakendustes kanalite voogesitamise. \n\nAlustamiseks seadistage juba installitud kanaliallikad. Võite ka sirvida Google Play poodi, et hankida rakendusi, mis pakuvad reaalajakanaleid." + "Salvestised ja ajakavad" + "10 minutit" + "30 minutit" + "1 tund" + "3 tundi" + "Hiljutised" + "Ajakavas´" + "Seeria" + "Muud" + "Kanalit ei saa salvestada." + "Programmi ei saa salvestada." + "%1$s on salvestamiseks ajastatud" + "Saate %1$s salvestamine praegusest kuni %2$s" + "Kogu ajakava" + + Järgmised %1$d päeva + Järgmine %1$d päev + + + %1$d minutit + %1$d minut + + + %1$d uut salvestust + %1$d uus salvestus + + + %1$d salvestust + %1$d salvestus + + + %1$d salvestust on ajakavas + %1$d salvestus on ajakavas + + "Loobu salvestamisest" + "Peata salvestamine" + "Käekell" + "Esita algusest" + "Jätka esitust" + "Kustuta" + "Kustuta salvestised" + "Jätka" + "%1$s. hooaeg" + "Kuva ajakava" + "Lisateave" + "Salvestuste kustutamine" + "Valige jaod, mille soovite kustutada. Pärast kustutamist ei saa neid enam taastada." + "Kustutamiseks pole salvestusi." + "Vali vaadatud jaod" + "Vali kõik jaod" + "Tühista kõigi jagude valik" + "%1$d %2$d-st minutist on vaadatud" + "%1$d %2$d-st sekundist on vaadatud" + "Pole kunagi vaadatud" + + %1$d %2$d-st jaost kustutati + %1$d %2$d-st jaost kustutati + + "Prioriteet" + "Kõige kõrgem" + "Kõige madalam" + "Nr %1$d" + "Kanalid" + "Kõik" + "Prioriteedi valimine" + "Kui samal ajal salvestamiseks on liiga palju programme, salvestatakse ainult prioriteetsed programmid." + "Salvesta" + "Ühekordsete salvestiste prioriteet on kõige kõrgem" + "Peata" + "Kuva salvestamise ajakava" + "Ainult see saade" + "praegu kuni %1$s" + "Kogu seeria …" + "Lisa ikkagi ajakavva" + "Salvesta hoopis see" + "Tühista see salvestus" + "Kuva nüüd" + "Kustuta salvestised …" + "Salvestatav" + "Salvestamine on ajastatud" + "Konflikt salvestamisel" + "Salvestamine" + "Salvestamine ebaõnnestus" + "Programmide lugemine" + "Kuva hiljutised salvestised" + "Saate %1$s salvestamine on pooleli." + "Saadete %1$s ja %2$s salvestamine on pooleli." + "Saadete %1$s, %2$s ja %3$s salvestamine on pooleli." + "Ebapiisava salvestusruumi tõttu ei viidud saate %1$s salvestamist lõpule." + "Ebapiisava salvestusruumi tõttu ei viidud saadete %1$s ja %2$s salvestamist lõpule." + "Ebapiisava salvestusruumi tõttu ei viidud saadete %1$s, %2$s ja %3$s salvestamist lõpule." + "DVR vajab rohkem salvestusruumi" + "Saateid saate salvestada DVR-iga. Praegu pole teie seadmes DVR-i töötamiseks siiski piisavalt salvestusruumi. Ühendage väline ketas, mille maht on vähemalt %1$d GB, ja järgige juhiseid selle vormindamiseks salvestusseadmena." + "Pole piisavalt mälu" + "Programmi ei salvestata, kuna salvestusruumi on liiga vähe. Kustutage mõned olemasolevad salvestised." + "Puuduv salvestusruum" + "Kas peatada salvestamine?" + "Salvestatud sisu talletatakse." + "Saate %1$s salvestamine peatatakse, kuna see on selle saatega konfliktis. Salvestatud sisu salvestatakse." + "Salvestamine on ajastatud, ent ilmnesid vastuolud" + "Salvestamine on alanud, kuid ilmnesid vastuolud" + "Saadet %1$s salvestatakse." + "Kanalit %1$s salvestatakse." + "Saate %1$s teatud osi ei salvestata." + "Saadete %1$s ja %2$s teatud osi ei salvestata." + "Saadete %1$s, %2$s ja veel ühe ajakava teatud osi ei salvestata." + + Saadete %1$s, %2$s ja veel %3$d ajakavade teatud osi ei salvestata. + Saadete %1$s, %2$s ja veel %3$d ajakava teatud osi ei salvestata. + + "Mida soovite salvestada?" + "Kui kaua soovite salvestada?" + "Juba ajakavas" + "Sama saate salvestus on juba lisatud ajakavva algusega %1$s." + "Juba salvestatud" + "See saade on juba salvestatud. See on saadaval DVR-i kogus." + "Sarja salvestamine lisati ajakavasse" + + Seeria %2$s puhul lisati ajakavasse %1$d salvestist. + Seeria %2$s puhul lisati ajakavasse %1$d salvestis. + + + Seeria %2$s puhul lisati ajakavasse %1$d salvestist. Konfliktide tõttu ei salvestata neist %3$d. + Seeria %2$s puhul lisati ajakavasse %1$d salvestis. Konfliktide tõttu seda ei salvestata. + + + Seeria %2$s puhul lisati ajakavasse %1$d salvestist. Konfliktide tõttu ei salvestata selle seeria ja muude seeriate %3$d jagu. + Seeria %2$s puhul lisati ajakavasse %1$d salvestis. Konfliktide tõttu ei salvestata selle seeria ja muude seeriate %3$d jagu. + + + Seeria %2$s puhul lisati ajakavasse %1$d salvestist. Konfliktide tõttu ei salvestata muu seeria 1 jagu. + Seeria %2$s puhul lisati ajakavasse %1$d salvestis. Konfliktide tõttu ei salvestata muu seeria 1 jagu. + + + Seeria %2$s puhul lisati ajakavasse %1$d salvestist. Konfliktide tõttu ei salvestata muu seeria %3$d jagu. + Seeria %2$s puhul lisati ajakavasse %1$d salvestis. Konfliktide tõttu ei salvestata muu seeria %3$d jagu. + + "Salvestatud programmi ei leitud." + "Seotud salvestised" + + %1$d salvestist + %1$d salvestis + + " / " + "%1$s on salvestamise ajakavast eemaldatud" + "Salvestatakse tuuneri konfliktide tõttu osaliselt." + "Ei salvestata tuuneri konfliktide tõttu." + "Ajakavas ei ole veel salvestusi.\nSalvestuse saate ajakavva lisada saatekavas." + + %1$d salvestamise konflikti + %1$d salvestamise konflikt + + "Seeria seaded" + "Alusta seeria salvestam." + "Peata seeria salvestamine" + "Kas peatada seeria salvestamine?" + "Salvestatud jaod jäävad saadavale DVR-i kogusse." + "Peata" + "Ühtegi jagu pole praegu eetris." + "Ükski osa pole saadaval.\nNeed salvestatakse siis, kui need kättesaadavaks muutuvad." + + (%1$d minutit) + (%1$d minut) + + "Täna" + "Homme" + "Eile" + "Täna vahemikus %1$s" + "Homme vahemikus %1$s" + "Tulemus" + "Salvestatud programmid" + diff --git a/res/values-et-v23/strings.xml b/res/values-et-v23/strings.xml deleted file mode 100644 index e981deeb..00000000 --- a/res/values-et-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Kanalid" - diff --git a/res/values-et/arrays.xml b/res/values-et/arrays.xml deleted file mode 100644 index 2d1d11fd..00000000 --- a/res/values-et/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Tavaline" - "Täis" - "Suumimine" - - - "Kõik kanalid" - "Pere/lapsed" - "Sport" - "Ostud" - "Filmid" - "Komöödia" - "Reisimine" - "Draama" - "Haridus" - "Loomad/loodus" - "Uudised" - "Mängud" - "Kunstid" - "Meelelahutus" - "Elustiil" - "Muusika" - "Esilinastus" - "Tehnika/teadus" - - - "Reaalajakanalid" - "Lihtne viis sisu avastamiseks" - "Rakenduste allalaadimine ja kanalite hankimine" - "Kanalivaliku kohandamine" - - - "Vaadake oma rakendustest sisu, nagu vaataksite telekanaleid." - "Sirvige oma rakendustes sisu tuttava kava ja kasutajasõbraliku liidesega (\njust nagu telekanalite puhul)." - "Lisage rohkem kanaleid, installides rakendusi, mis pakuvad reaalajakanaleid. \nLeidke ühilduvaid rakendusi Google Play poest, kasutades TV-menüüs olevat linki." - "Seadistage kanaliloendi kohandamiseks oma äsja installitud kanaliallikad. \nAlustamiseks tehke menüüs Seaded valik Kanaliallikad." - - diff --git a/res/values-et/rating_system_strings.xml b/res/values-et/rating_system_strings.xml deleted file mode 100644 index 5cee5abd..00000000 --- a/res/values-et/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Saated võivad sisaldada alla 15-aastastele lastele sobimatut materjali ja otsuse sobilikkuse kohta peaks langetama lapsevanem." - "Saated võivad sisaldada alla 19-aastastele sobimatut materjali, mistõttu ei ole need sobilikud alla 19-aastastele noortele." - "Kahemõttelise sisuga dialoog" - "Vulgaarne tekst" - "Seksuaalne sisu" - "Vägivald" - "Väljamõeldud vägivald" - "See saade on sobilik kõikidele lastele." - "See saade on mõeldud vähemalt 7-aastastele lastele." - "Enamik vanemaid peaks seda saadet sobilikuks igas vanuses lastele." - "See saade sisaldab materjale, mida vanemad võivad pidada sobimatuks lastele. Vanemad soovivad tõenäoliselt seda vaadata koos lastega." - "Selle saate sisu on paljude vanemate arvates sobimatu alla 14-aastastele lastele." - "See saade on mõeldud täiskasvanutele ja võib seega olla sobimatu alla 17-aastastele lastele." - "Filmi hinnangud" - "Üldised vaatajaskonnad. Keelatud on sisu, mida vanemad ei lubaks lastel vaadata." - "Soovitatav on vanemlik järelevalve. Võib sisaldada materjali, mida lapsevanemad ei soovi väikestele lastele näidata." - "Vanemlik järelevalve on tungivalt soovitatav. Teatud materjal võib olla eelteismeliste jaoks sobimatu." - "Piiratud, sisaldab teatud määral täiskasvanutele mõeldud materjali. Vanematel soovitatakse enne väikese lapsega filmi vaatamist selle kohta rohkem teavet hankida." - "17-aastastele ja noorematele keelatud. Selgelt täiskasvanutele mõeldud sisu. Lastele keelatud." - diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml deleted file mode 100644 index 83022b8e..00000000 --- a/res/values-et/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "mono" - "stereo" - "Esituse juhtnupud" - "Viimas. kanalid" - "TV-valikud" - "PIP-valikud" - "Esituse juhtelemendid ei ole selle kanali puhul saadaval" - "Esitamine või peatamine" - "Edasikerimine" - "Tagasikerimine" - "Järgmine" - "Eelmine" - "Saatekava" - "Saadaval on uued kanalid" - "Ava %1$s" - "Subtiitrid" - "Kuvarežiim" - "PIP" - "Sees" - "Väljas" - "Multiaudio" - "Hangi kanaleid" - "Seaded" - "Allikas" - "Vaheta" - "Sees" - "Väljas" - "Heli" - "Peamine" - "PIP-aken" - "Paigutus" - "All paremal" - "Ülal paremal" - "Ülal vasakul" - "All vasakul" - "Kõrvuti" - "Suurus" - "Suur" - "Väike" - "Sisendallikas" - "TV (antenn/kaabel)" - "Programmiteavet pole" - "Teave puudub" - "Blokeeritud kanal" - "Tundmatu keel" - "Subtiitrid" - "Väljas" - "Vormingu kohandamine" - "Määrake subtiitrite süsteemiül. eelist." - "Kuvarežiim" - "Multiaudio" - "mono" - "stereo" - "Ruumiline heli 5.1" - "Ruumiline heli 7.1" - "%d kanalit" - "Kanaliloendi kohandam." - "Rühma valimine" - "Rühma valiku tühist." - "Rühmitamisalus:" - "Kanali allikas" - "HD/SD" - "HD" - "SD" - "Rühmitamisalus:" - "See programm on blokeeritud" - "Selle saate reiting on %1$s" - "Sisend ei toeta automaatset skannimist" - "Sisendi „%s” automaatset skannimist ei saa alustada" - "Subtiitrite süsteemiüleseid eelistusi ei saa käivitada." - - Lisatud on %1$d kanalit - Lisatud on %1$d kanal - - "Ühtegi kanalit ei lisatud" - "Tuuner" - "Van. järelevalve" - "Sees" - "Väljas" - "Blokeeritud kanalid" - "Blokeeri kõik" - "Deblokeeri kõik" - "Peidetud kanalid" - "Programmipiirangud" - "Muuda PIN-koodi" - "Hinnangusüsteemid" - "Hinnangud" - "Kuva hinnangusüsteemid" - "Muud riigid" - "Puudub" - "Puudub" - "Puudub" - "Suured piirangud" - "Keskmised piirangud" - "Väikesed piirangud" - "Kohandatud" - "Sisu sobib lastele" - "Sisu sobib vanematele lastele" - "Sisu sobib teismelistele" - "Käsitsi määratud piirangud" - - - "%1$s & alamreitingud" - "Alamreitingud" - "Kanali vaatamiseks sisestage PIN-kood" - "Saate vaatamiseks sisestage PIN-kood" - "Selle saate reiting on %1$s. Selle saate vaatamiseks sisestage oma PIN-kood" - "Sisestage PIN-kood" - "Vanemliku järelevalve määramiseks looge PIN-kood" - "Sisestage uus PIN-kood" - "Kinnitage PIN-kood" - "Sisestage praegune PIN-kood" - - Sisestasite vale PIN-koodi viis korda.\nProovige uuesti %1$d sekundi pärast. - Sisestasite vale PIN-koodi viis korda.\nProovige uuesti %1$d sekundi pärast. - - "See PIN-kood oli vale. Proovige uuesti." - "Proovige uuesti, PIN-kood pole õige" - "Seaded" - "Kohanda kanaliloendit" - "Kanalite valimine saatekava jaoks" - "Kanali allikad" - "Saadaval on uued kanalid" - "Vanemlik järelevalve" - "Avatud lähtekoodi litsentsid" - "Avatud lähtekoodi litsentsid" - "Versioon" - "Kanali vaatamiseks vajutage paremale ja sisestage PIN-kood" - "Saate vaatamiseks vajutage paremale ja sisestage PIN-kood" - "Saate reiting on %1$s\nSaate vaatamiseks vajutage valikut Paremale ja sisestage PIN-kood." - "Selle kanali vaatamiseks kasutage otsesaadete vaikerakendust." - "Selle saate vaatamiseks kasutage otsesaadete vaikerakendust." - "Selle saate reiting on %1$s.\nSelle saate vaatamiseks kasutage otsesaadete vaikerakendust." - "Saade on blokeeritud" - "Selle saate reiting on %1$s" - "Ainult heli" - "Nõrk signaal" - "Interneti-ühendus puudub" - - Seda kanalit ei saa kuni kellani %1$s esitada, kuna salvestatakse teisi kanaleid. \n\nSalvestamise ajakava kohandamiseks vajutage paremnoolt. - Seda kanalit ei saa kuni kellani %1$s esitada, kuna salvestatakse teist kanalit. \n\nSalvestamise ajakava kohandamiseks vajutage paremnoolt. - - "Pealkiri puudub" - "Kanal on blokeeritud" - "Uus" - "Allikad" - - %1$d kanalit - %1$d kanal - - "Ühtegi kanalit pole saadaval" - "Uus" - "Pole seadistatud" - "Hankige rohkem allikaid" - "Sirvige rakendusi, mis pakuvad reaalajakanaleid" - "Saadaval on uued kanaliallikad" - "Uutel kanaliallikatel on kanaleid pakkuda.\nSeadistage need kohe või tehke seda hiljem kanaliallikate seadetes." - "Seadista kohe" - "OK, selge" - - - "Teleri menüüle juurdepääsemiseks ""vajutage nuppu SELECT""." - "TV-sisendit ei leitud" - "TV-sisendit ei õnnestu leida" - "PIP-d ei toetata" - "Pole ühtegi sisendit, mida saab näidata koos PIP-ga" - "Sobimatu tuuneri tüüp. Käivitage tuuneri tüübi TV-sisendi jaoks rakendus Otseülekande kanalid." - "Häälestamine ebaõnnestus" - "Selle toimingu käsitlemiseks ei leitud ühtegi rakendust." - "Kõik allikakanalid on peidetud.\nValige vaatamiseks vähemalt üks kanal." - "Video pole ootamatult saadaval" - "Klahv TAGASI on mõeldud ühendatud seadme jaoks. Väljumiseks vajutage nuppu AVAEKRAAN." - "Reaalajas kanalid vajavad telekavade lugemiseks luba." - "Allikate seadistamine" - "Reaalajakanalid ühendavad tavaliste telekanalite kasutuskogemuse ja rakendustes kanalite voogesitamise. \n\nAlustamiseks seadistage juba installitud kanaliallikad. Võite ka sirvida Google Play poodi, et hankida rakendusi, mis pakuvad reaalajakanaleid." - "Salvestised ja ajakavad" - "10 minutit" - "30 minutit" - "1 tund" - "3 tundi" - "Hiljutised" - "Ajakavas´" - "Seeria" - "Muud" - "Kanalit ei saa salvestada." - "Programmi ei saa salvestada." - "%1$s on salvestamiseks ajastatud" - "Saate %1$s salvestamine praegusest kuni %2$s" - "Kogu ajakava" - - Järgmised %1$d päeva - Järgmine %1$d päev - - - %1$d minutit - %1$d minut - - - %1$d uut salvestust - %1$d uus salvestus - - - %1$d salvestust - %1$d salvestus - - - %1$d salvestust on ajakavas - %1$d salvestus on ajakavas - - "Käekell" - "Esita algusest" - "Jätka esitust" - "Kustuta" - "Kustuta salvestised" - "Jätka" - "%1$s. hooaeg" - "Kuva ajakava" - "Lisateave" - "Salvestuste kustutamine" - "Valige jaod, mille soovite kustutada. Pärast kustutamist ei saa neid enam taastada." - "Kustutamiseks pole salvestusi." - "Vali vaadatud jaod" - "Vali kõik jaod" - "Tühista kõigi jagude valik" - "%1$d %2$d-st minutist on vaadatud" - "%1$d %2$d-st sekundist on vaadatud" - "Pole kunagi vaadatud" - - %1$d %2$d-st jaost kustutati - %1$d %2$d-st jaost kustutati - - "Prioriteet" - "Kõige kõrgem" - "Kõige madalam" - "Nr %1$d" - "Kanalid" - "Kõik" - "Prioriteedi valimine" - "Kui samal ajal salvestamiseks on liiga palju programme, salvestatakse ainult prioriteetsed programmid." - "Salvesta" - "Ühekordsete salvestiste prioriteet on kõige kõrgem" - "Tühista" - "Tühista" - "Unusta" - "Peata" - "Kuva salvestamise ajakava" - "Ainult see saade" - "praegu kuni %1$s" - "Kogu seeria …" - "Lisa ikkagi ajakavva" - "Salvesta hoopis see" - "Tühista see salvestus" - "Kuva nüüd" - "Salvestatav" - "Salvestamine on ajastatud" - "Konflikt salvestamisel" - "Salvestamine" - "Salvestamine ebaõnnestus" - "Programmide lugemine salvestusajakavade loomiseks" - "Programmide lugemine" - - - "DVR vajab rohkem salvestusruumi" - "Saateid saate salvestada DVR-iga. Praegu pole teie seadmes DVR-i töötamiseks siiski piisavalt salvestusruumi. Ühendage väline ketas, mille maht on vähemalt %1$s GB, ja järgige juhiseid selle vormindamiseks salvestusseadmena." - "Puuduv salvestusruum" - "Osa DVR-i kasutatavast salvestusruumist on puudu. DVR-i uuesti lubamiseks ühendage väline ketas, mida varem kasutasite. Teise võimalusena saate salvestusruumi unustada, kui see enam saadaval pole." - "Kas unustada salvestusruum?" - "Kogu teie salvestatud sisu ja ajakavad lähevad kaotsi." - "Kas peatada salvestamine?" - "Salvestatud sisu talletatakse." - - - "Salvestamine on ajastatud, ent ilmnesid vastuolud" - "Salvestamine on alanud, kuid ilmnesid vastuolud" - "Saadet %1$s salvestatakse." - "Kanalit %1$s salvestatakse." - "Saate %1$s teatud osi ei salvestata." - "Saadete %1$s ja %2$s teatud osi ei salvestata." - "Saadete %1$s, %2$s ja veel ühe ajakava teatud osi ei salvestata." - - Saadete %1$s, %2$s ja veel %3$d ajakavade teatud osi ei salvestata. - Saadete %1$s, %2$s ja veel %3$d ajakava teatud osi ei salvestata. - - "Mida soovite salvestada?" - "Kui kaua soovite salvestada?" - "Juba ajakavas" - "Sama saate salvestus on juba lisatud ajakavva algusega %1$s." - "Juba salvestatud" - "See saade on juba salvestatud. See on saadaval DVR-i kogus." - - - - - - - - - "Salvestatud programmi ei leitud." - "Seotud salvestised" - "(Programmi kirjeldust pole)" - - %1$d salvestist - %1$d salvestis - - " / " - "%1$s on salvestamise ajakavast eemaldatud" - "Salvestatakse tuuneri konfliktide tõttu osaliselt." - "Ei salvestata tuuneri konfliktide tõttu." - "Ajakavas ei ole veel salvestusi.\nSalvestuse saate ajakavva lisada saatekavas." - - %1$d salvestamise konflikti - %1$d salvestamise konflikt - - "Seeria seaded" - "Alusta seeria salvestam." - "Peata seeria salvestamine" - "Kas peatada seeria salvestamine?" - "Salvestatud jaod jäävad saadavale DVR-i kogusse." - "Peata" - "Ükski osa pole saadaval.\nNeed salvestatakse siis, kui need kättesaadavaks muutuvad." - - (%1$d minutit) - (%1$d minut) - - "Täna" - "Homme" - "Eile" - "Täna vahemikus %1$s" - "Homme vahemikus %1$s" - "Tulemus" - diff --git a/res/values-eu-rES-v23/strings.xml b/res/values-eu-rES-v23/strings.xml new file mode 100644 index 00000000..86f40b71 --- /dev/null +++ b/res/values-eu-rES-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Kanalak" + diff --git a/res/values-eu-rES/arrays.xml b/res/values-eu-rES/arrays.xml new file mode 100644 index 00000000..89b32135 --- /dev/null +++ b/res/values-eu-rES/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Normala" + "Osoa" + "Zooma" + + + "Kanal guztiak" + "Familia/Umeak" + "Kirolak" + "Erosketak" + "Filmak" + "Komedia" + "Bidaiak" + "Drama" + "Hezkuntza" + "Animaliak/Basabizitza" + "Albisteak" + "Jokoak" + "Artea" + "Ikuskizunak" + "Bizimodua" + "Musika" + "Onenak" + "Zientzia/Teknologia" + + + "Telebista zuzenean" + "Edukia ezagutzeko modu erraza" + "Deskargatu aplikazioak eta lortu kanal gehiago" + "Pertsonalizatu kanalen antolakuntza" + + + "Ikusi aplikazioetako edukia, telebistan kanalak ikusten dituzun moduan." + "Arakatu aplikazioetako edukia gida ezagun eta interfaze lagungarri batekin\n, telebistako kanalak bezala." + "Kanalak gehitzeko, instalatu zuzeneko kanalak eskaintzen dituzten aplikazioak.\nBilatu aplikazio bateragarriak Google Play Store dendan, telebistako menuko estekaren bidez." + "Konfiguratu instalatu berri dituzun kanal-iturburuak kanalen zerrenda pertsonalizatzeko.\nLehen urratsak emateko, aukeratu kanal-iturburuak Ezarpenak menuan." + + diff --git a/res/values-eu-rES/rating_system_strings.xml b/res/values-eu-rES/rating_system_strings.xml new file mode 100644 index 00000000..ae0d84d4 --- /dev/null +++ b/res/values-eu-rES/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Programako materiala desegokia izan liteke 15 urtetik beherakoentzat. Gurasoek erabaki beharko dute beren umeek erabil dezaketen ala ez." + "Programako materiala desegokia izan liteke 19 urtetik beherakoentzat. Beraz, ez da egokitzat jotzen adin horietako gaztetxoentzat." + "Elkarrizketa iradokitzailea" + "Hizkera zakarra" + "Eduki sexuala" + "Indarkeria" + "Fantasiazko indarkeria" + "Haur orok ikusteko diseinatuta dago programa." + "Zazpi urteko baino gehiagoko haurrek ikusteko diseinatuta dago programa." + "Guraso gehienek egokitzat joko lukete programa edozer adineko haurrentzat." + "Programaren edukia desegokitzat jo dezakete gurasoek gazteenentzat. Guraso askok haur gazteekin batera ikusi nahiko dute." + "Programaren edukia desegokitzat jo dezakete gurasoek 14 urte baino gutxiagoko haurrentzat." + "Helduentzat berariaz diseinatuta dago programa hau; beraz, baliteke desegokia izatea 17 urte baino gutxiagoko haurrentzat." + "Filmen balorazioak" + "Ikusle guztientzat: gurasoak ez dira irainduta sentituko haurrek ikusten dutelako." + "Gurasoak bertan egotea gomendatzen da. Gurasoek haur txikiek ikustea nahi ez duten edukia egon daiteke." + "Gurasoentzako abisu garrantzitsua: baliteke zenbait eduki desegokia izatea nerabeak baino gazteagoak diren haurrentzat." + "Murriztuta. Helduentzako zenbait eduki du. Haurrak eraman aurretik, filmari buruzko informazioa lortzea gomendatzen zaie gurasoei." + "Ezin da sartu 17 urtetik beherako inor. Helduentzat soilik. Ezin da sartu haurrik." + diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml new file mode 100644 index 00000000..ca3d61a6 --- /dev/null +++ b/res/values-eu-rES/strings.xml @@ -0,0 +1,357 @@ + + + + + "mono" + "estereo" + "Erreprodukzioa kontrolatzeko aukerak" + "Kanalak" + "Telebistaren aukerak" + "Erreprodukzioa kontrolatzeko aukerak ez daude erabilgarri kanal honetan" + "Erreproduzitu edo pausatu" + "Aurreratu" + "Atzeratu" + "Hurrengoa" + "Aurrekoa" + "Telesaioen gida" + "Kanal berriak daude" + "Ireki %1$s" + "Azpitituluak" + "Bistaratzeko modua" + "Pantaila txikia" + "Audio anitza" + "Kanal gehiago" + "Ezarpenak" + "Telebista (antena/digitala)" + "Ez dago telesaioei buruzko informaziorik" + "Ez dago informaziorik" + "Blokeatutako kanala" + "Hizkuntza ezezaguna" + "Azpitituluak - %1$d" + "Azpitituluak" + "Desaktibatuta" + "Pertsonalizatu formatua" + "Zehaztu azpitituluen hobespen orokorrak" + "Bistaratzeko modua" + "Audio anitza" + "monoa" + "estereoa" + "5.1 inguratzailea" + "7.1 inguratzailea" + "%d kanal" + "Pertsonalizatu zerrenda" + "Hautatu taldea" + "Desautatu taldea" + "Taldekatzeko irizpidea" + "Kanalen iturburua" + "Bereizmen handia eta estandarra" + "Bereizmen handia" + "Bereizmen estandarra" + "Taldekatzeko irizpidea" + "Telesaio hau blokeatuta dago" + "Programa honek ez du adin-sailkapenik" + "Telesaioa \"%1$s\" gisa sailkatu da" + "Sarrerak ez du automatikoki sintonizatzeko aukera onartzen" + "Ezin da hasi \"%s\" automatikoki sintonizatzen" + "Ezin dira ireki sistemaren azpitituluen hobespenak." + + %1$d kanal gehitu dira + %1$d kanal gehitu da + + "Ez da gehitu kanalik" + "Gurasoen ezarpenak" + "Aktibatuta" + "Desaktibatuta" + "Kanalak blokeatuta" + "Blokeatu guztiak" + "Desblokeatu guztiak" + "Ezkutuko kanalak" + "Telesaioen murrizk." + "Aldatu PIN kodea" + "Sailkapen-sistemak" + "Balorazioak" + "Sailkapen-sistema guztiak" + "Beste herrialdeak" + "Bat ere ez" + "Bat ere ez" + "Ez du adin-sailkapenik" + "Blokeatu adin-sailkapenik gabeko programak" + "Bat ere ez" + "Murrizketa asko" + "Erdi-mailako murriztapenak" + "Murriztapen gutxi" + "Pertsonalizatua" + "Edukia egokia da umeentzat" + "Edukia egokia da beste ume batzuentzat" + "Edukia egokia da nerabeentzat" + "Eskuzko murrizketak" + + + "%1$s eta azpisailkapenak" + "Azpisailkapenak" + "Kanal hau ikusi nahi baduzu, idatzi PIN kodea." + "Telesaio hau ikusi nahi baduzu, idatzi PIN kodea." + "Programaren balorazioa: %1$s. Idatzi PIN kodea programa ikusteko." + "Programa honek ez du adin-sailkapenik. Programa ikusteko, idatzi PIN kodea." + "Idatzi PIN kodea" + "Gurasoen kontrol-aukerak zehazteko, sortu PIN kode bat" + "Idatzi PIN kode berria" + "Berretsi PIN kodea" + "Idatzi uneko PIN kodea" + + Bost aldiz oker idatzi duzu PIN kodea.\nSaiatu berriro %1$d segundo barru. + Bost aldiz oker idatzi duzu PIN kodea.\nSaiatu berriro %1$d segundo barru. + + "PIN kodea ez da zuzena. Saiatu berriro." + "PIN kodeak ez datoz bat. Saiatu berriro." + "Idatzi posta-kodea." + "\"Telebista zuzenean\" aplikazioak posta-kodea erabiliko du telebistako kanalen programen gida osoa eskaintzeko." + "Idatzi posta-kodea" + "Posta-kodeak ez du balio" + "Ezarpenak" + "Pertsonalizatu zerrenda" + "Aukeratu telesaioen gidako kanalak" + "Kanalen iturburuak" + "Kanal berriak daude eskuragarri" + "Gurasoen ezarpenak" + "Zuzeneko igorpenetan erreprodukzioa kontrolatzeko aukera" + "Grabatu ikusten ari zarena, zuzeneko igorpenak unean bertan pausatu edo atzeratu ahal izateko.\nAbisua: hori egiteko memoria asko erabiltzen denez, aukera hau erabiltzeak barneko memoriaren iraupena murritz lezake." + "Kode irekiko lizentziak" + "Bidali iritzia" + "Bertsioa" + "Kanal hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea." + "Telesaio hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea." + "Programa honek ez du adin-sailkapenik.\nPrograma hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea." + "Telesaioa \"%1$s\" gisa sailkatu da.\nIkusi ahal izateko, sakatu Eskuinera tekla eta idatzi PIN kodea." + "Kanala ikusteko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." + "Telesaioa ikusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." + "Programa honek ez du adin-sailkapenik.\nPrograma hau ikusteko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." + "Telesaioa %1$s gisa sailkatu da.\nIkusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." + "Telesaioa blokeatuta dago" + "Programa honek ez du adin-sailkapenik" + "Telesaioa \"%1$s\" gisa sailkatu da" + "Audioa soilik" + "Seinale ahula" + "Ez zaude Internetera konektatuta" + + Kanal hau ezin da erreproduzitu %1$s arte, beste kanal batzuk grabatzen ari garelako. \n\nGrabaketaren orduak aldatzeko, sakatu Eskuinera tekla. + Kanal hau ezin da erreproduzitu %1$s arte, beste kanal bat ari garelako grabatzen. \n\nGrabaketaren orduak aldatzeko, sakatu Eskuinera tekla. + + "Izenik ez" + "Kanala blokeatuta dago" + "Berriak" + "Iturburuak" + + %1$d kanal + %1$d kanal + + "Ez dago kanalik erabilgarri" + "Berriak" + "Konfiguratu gabe" + "Lortu iturburu gehiago" + "Arakatu edukia zuzenean igortzen duten kanalak eskaintzen dituzten aplikazioak" + "Kanalen iturburu berriak daude erabilgarri" + "Beste kanal batzuk eskaintzen dituzten iturburu berriak dituzu eskura.\nKonfigura itzazu oraintxe bertan edo egin ezazu beste uneren batean kanalen iturburuen ezarpenera joanda." + "Konfiguratu" + "Ados" + + + "Telebistaren menua atzitzeko, sakatu HAUTATU" + "Ez da telebista-sarrerarik aurkitu" + "Ezin da aurkitu telebista-sarrera" + "Sintonizagailua ez da egokia. Abiarazi Telebista zuzenean aplikazioa sintonizagailua telebistaren sarrera gisa erabiltzeko." + "Ezin izan da sintonizatu" + "Ez da aurkitu ekintza gauza dezakeen aplikaziorik." + "Iturburuko kanal guztiak ezkutuan daude.\nHautatu gutxienez kanal bat ikusteko." + "Bideoa ez dago erabilgarri" + "Atzera tekla konektatutako gailuari dagokio. Irteteko, sakatu Hasiera botoia." + "Telebista zuzenean aplikazioak baimena behar du telebistako programazioa irakurtzeko." + "Konfiguratu iturburuak" + "Telebista zuzenean zerbitzuarekin, aplikazioek zuzenean erreproduzitzen dituzten kanalak ohiko telebistaren moduan ikus ditzakezu. \n\nLehen urratsak emateko, konfiguratu instalatutako kanal-iturburuak. Bestela, arakatu Google Play Store denda zuzeneko kanalak eskaintzen dituzten aplikazio gehiago aurkitzeko." + "Grabaketak eta grabaketen programazioa" + "10 minutu" + "30 minutu" + "1 ordu" + "3 ordu" + "Azkenak" + "Programatutakoak" + "Telesailak" + "Beste batzuk" + "Ezin da grabatu kanal hau." + "Ezin da grabatu programa hau." + "%1$s grabatzeko programatu da" + "%1$s grabatuko da %2$s arte" + "Programazio osoa" + + Beste %1$d egun + Beste %1$d egun + + + %1$d minutu + %1$d minutu + + + %1$d grabaketa berri + %1$d grabaketa berri + + + %1$d grabaketa + %1$d grabaketa + + + %1$d grabaketa antolatuta + %1$d grabaketa antolatuta + + "Ez grabatu" + "Gelditu grabaketa" + "Ikusi" + "Erreproduzitu hasieratik" + "Berrekin" + "Ezabatu" + "Ezabatu grabaketak" + "Berrekin" + "%1$s. denboraldia" + "Ikusi agenda" + "Irakurri gehiago" + "Ezabatu grabaketak" + "Hautatu ezabatu nahi dituzun atalak. Ezabatu ondoren, ezingo dituzu berreskuratu." + "Ez dago ezaba daitekeen grabaketarik." + "Hautatu ikusitako atalak" + "Hautatu atal guztiak" + "Ezabatu atal guztiak" + "%1$d/%2$d minutu ikusi dituzu" + "%1$d/%2$d segundo ikusi dituzu" + "Inoiz ikusi gabe" + + %1$d/%2$d atal ezabatu dira + %1$d/%2$d atal ezabatu da + + "Lehentasuna" + "Handiena" + "Txikiena" + "%1$d.a" + "Kanalak" + "Edozein" + "Aukeratu lehentasuna" + "Aldi berean programa gehiegi grabatu behar badira, lehentasun handieneko programak bakarrik grabatuko dira." + "Gorde" + "Grabaketa solteek dute lehentasun handiena" + "Gelditu" + "Ikusi grabaketen agenda" + "Programa hau bakarrik" + "orain – %1$s" + "Telesail osoa…" + "Programatu, hala ere" + "Grabatu beste hau haren ordez" + "Utzi grabaketa hau bertan behera" + "Ikusi" + "Ezabatu grabaketak…" + "Graba daiteke" + "Grabatzeko programatuta" + "Grabatzeko gatazka" + "Grabatzen" + "Ezin izan da grabatu" + "Programazioa irakurtzen" + "Ikusi azken grabaketak" + "Ezin izan da osorik grabatu %1$s." + "Ezin izan dira osorik grabatu %1$s eta %2$s." + "Ezin izan dira osorik grabatu %1$s, %2$s eta %3$s." + "Ezin izan da osorik grabatu %1$s, memoria agortu delako." + "Ezin izan dira osorik grabatu %1$s eta %2$s, memoria agortu delako." + "Ezin izan dira osorik grabatu %1$s, %2$s eta %3$s, memoria agortu delako." + "Bideo-grabagailu digitalak ez dauka behar adina memoria erabilgarri" + "Bideo-grabagailu digitalarekin programak grabatu ahal izango dituzu. Dena dela, une honetan ez daukazu bideo-grabagailua erabili ahal izateko behar adina memoria erabilgarri. Konektatu %1$d GB edo gehiago dituen unitate aldagarri bat eta formatea ezazu gailuaren memoria gisa erabiltzeko." + "Ez dago behar adina toki" + "Ezingo dugu grabatu telesaio hau, ez delako behar adina toki geratzen. Ezabatu grabaketa batzuk tokia egiteko." + "Memoria-unitatea falta da" + "Grabaketa gelditu nahi duzu?" + "Grabatutako edukia gordeta geratuko da." + "%1$s grabatzeari utziko zaio programa honekin gatazkan dagoelako. Gorde egingo da grabatutako edukia." + "Grabatzeko programatu da baina gatazkan dago beste grabaketa batzuekin" + "Grabatzen hasi da, baina gatazkak ditu" + "%1$s grabatuko da." + "%1$s grabatzen ari da." + "%1$s ez da osorik grabatuko." + "%1$s eta %2$s ez dira osorik grabatuko." + "%1$s, %2$s eta grabatzeko programatuta dagoen beste saio bat ez dira osorik grabatuko." + + %1$s, %2$s eta grabatzeko programatuta dauden beste %3$d saio ez dira osorik grabatuko. + %1$s, %2$s eta grabatzeko programatuta dagoen beste %3$d saio ez dira osorik grabatuko. + + "Zer grabatu nahi duzu?" + "Zenbat denboran grabatu nahi duzu?" + "Programatuta dago dagoeneko" + "Programa hau bera grabatzeko programatu duzu dagoeneko (%1$s)." + "Grabatuta dago dagoeneko" + "Programa hau grabatuta daukazu dagoeneko. DVR liburutegian duzu ikusgai." + "Telesaila grabatzeko antolatuta dago" + + %1$d grabaketa antolatu dira (%2$s). + %1$d grabaketa antolatu da (%2$s). + + + %1$d grabaketa antolatu dira (%2$s). Haietako %3$d ezin izango dira grabatu gatazkak daudelako. + %1$d grabaketa antolatu da (%2$s). Ezin izango da grabatu gatazkak daudelako. + + + %1$d grabaketa antolatu dira (%2$s). Telesail horren eta beste batzuen %3$d atal ezin izango dira grabatu gatazkak daudelako. + %1$d grabaketa antolatu da (%2$s). Telesail horren eta beste batzuen %3$d atal ezin izango dira grabatu gatazkak daudelako. + + + %1$d grabaketa antolatu dira (%2$s). Beste telesail baten atal bat ezin izango da grabatu gatazkak daudelako. + %1$d grabaketa antolatu da (%2$s). Beste telesail baten atal bat ezin izango da grabatu gatazkak daudelako. + + + %1$d grabaketa antolatu dira (%2$s). Beste telesail batzuen %3$d atal ezin izango dira grabatu gatazkak daudelako. + %1$d grabaketa antolatu da (%2$s). Beste telesail batzuen %3$d atal ezin izango dira grabatu gatazkak daudelako. + + "Ez da aurkitu grabatutako programa." + "Erlazionatutako grabaketak" + + %1$d grabaketa + %1$d grabaketa + + " / " + "%1$s kendu egin da programatutako grabaketen zerrendatik" + "Partzialki grabatuko da, sintonizadoreak gatazkak baititu." + "Ez da grabatuko, sintonizadoreak gatazkak baititu." + "Oraindik ez duzu programatu grabaketarik.\nProgramazio-gidan programa ditzakezu grabaketak." + + %1$d grabaketa-gatazka + %1$d grabaketa-gatazka + + "Seriearen ezarpenak" + "Hasi seriea grabatzen" + "Utzi seriea grabatzeari" + "Seriea grabatzeari utzi nahi diozu?" + "DVR liburutegian gordeta geratuko dira grabatutako atalak." + "Gelditu" + "Une honetan ez dira ari atalik igortzen." + "Ez dago atalik ikusgai.\nIkusgai ezartzen dituztenean grabatuko ditugu." + + (%1$d minutu) + (%1$d minutu) + + "Gaur" + "Bihar" + "Atzo" + "Gaur, %1$s" + "Bihar, %1$s" + "Puntuazioa" + "Grabatutako programak" + diff --git a/res/values-eu-v23/strings.xml b/res/values-eu-v23/strings.xml deleted file mode 100644 index 86f40b71..00000000 --- a/res/values-eu-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Kanalak" - diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml deleted file mode 100644 index 89b32135..00000000 --- a/res/values-eu/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Normala" - "Osoa" - "Zooma" - - - "Kanal guztiak" - "Familia/Umeak" - "Kirolak" - "Erosketak" - "Filmak" - "Komedia" - "Bidaiak" - "Drama" - "Hezkuntza" - "Animaliak/Basabizitza" - "Albisteak" - "Jokoak" - "Artea" - "Ikuskizunak" - "Bizimodua" - "Musika" - "Onenak" - "Zientzia/Teknologia" - - - "Telebista zuzenean" - "Edukia ezagutzeko modu erraza" - "Deskargatu aplikazioak eta lortu kanal gehiago" - "Pertsonalizatu kanalen antolakuntza" - - - "Ikusi aplikazioetako edukia, telebistan kanalak ikusten dituzun moduan." - "Arakatu aplikazioetako edukia gida ezagun eta interfaze lagungarri batekin\n, telebistako kanalak bezala." - "Kanalak gehitzeko, instalatu zuzeneko kanalak eskaintzen dituzten aplikazioak.\nBilatu aplikazio bateragarriak Google Play Store dendan, telebistako menuko estekaren bidez." - "Konfiguratu instalatu berri dituzun kanal-iturburuak kanalen zerrenda pertsonalizatzeko.\nLehen urratsak emateko, aukeratu kanal-iturburuak Ezarpenak menuan." - - diff --git a/res/values-eu/rating_system_strings.xml b/res/values-eu/rating_system_strings.xml deleted file mode 100644 index ae0d84d4..00000000 --- a/res/values-eu/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Programako materiala desegokia izan liteke 15 urtetik beherakoentzat. Gurasoek erabaki beharko dute beren umeek erabil dezaketen ala ez." - "Programako materiala desegokia izan liteke 19 urtetik beherakoentzat. Beraz, ez da egokitzat jotzen adin horietako gaztetxoentzat." - "Elkarrizketa iradokitzailea" - "Hizkera zakarra" - "Eduki sexuala" - "Indarkeria" - "Fantasiazko indarkeria" - "Haur orok ikusteko diseinatuta dago programa." - "Zazpi urteko baino gehiagoko haurrek ikusteko diseinatuta dago programa." - "Guraso gehienek egokitzat joko lukete programa edozer adineko haurrentzat." - "Programaren edukia desegokitzat jo dezakete gurasoek gazteenentzat. Guraso askok haur gazteekin batera ikusi nahiko dute." - "Programaren edukia desegokitzat jo dezakete gurasoek 14 urte baino gutxiagoko haurrentzat." - "Helduentzat berariaz diseinatuta dago programa hau; beraz, baliteke desegokia izatea 17 urte baino gutxiagoko haurrentzat." - "Filmen balorazioak" - "Ikusle guztientzat: gurasoak ez dira irainduta sentituko haurrek ikusten dutelako." - "Gurasoak bertan egotea gomendatzen da. Gurasoek haur txikiek ikustea nahi ez duten edukia egon daiteke." - "Gurasoentzako abisu garrantzitsua: baliteke zenbait eduki desegokia izatea nerabeak baino gazteagoak diren haurrentzat." - "Murriztuta. Helduentzako zenbait eduki du. Haurrak eraman aurretik, filmari buruzko informazioa lortzea gomendatzen zaie gurasoei." - "Ezin da sartu 17 urtetik beherako inor. Helduentzat soilik. Ezin da sartu haurrik." - diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml deleted file mode 100644 index 19a00433..00000000 --- a/res/values-eu/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "mono" - "estereo" - "Erreprodukzioa kontrolatzeko aukerak" - "Azken kanalak" - "Telebistaren aukerak" - "Pantaila txikia" - "Erreprodukzioa kontrolatzeko aukerak ez daude erabilgarri kanal honetan" - "Erreproduzitu edo pausatu" - "Aurreratu" - "Atzeratu" - "Hurrengoa" - "Aurrekoa" - "Telesaioen gida" - "Kanal berriak daude" - "Ireki %1$s" - "Azpitituluak" - "Bistaratzeko modua" - "Pantaila txikia" - "Aktibatuta" - "Desaktibatuta" - "Audio anitza" - "Kanal gehiago" - "Ezarpenak" - "Iturburua" - "Aldatu" - "Aktibatuta" - "Desaktibatuta" - "Soinua" - "Nagusia" - "Pantaila txikia" - "Diseinua" - "Behean eskuinean" - "Goian eskuinean" - "Goian ezkerrean" - "Behean ezkerrean" - "Alboz albo" - "Tamaina" - "Handia" - "Txikia" - "Sarrera-iturburua" - "Telebista (antena/digitala)" - "Ez dago telesaioei buruzko informaziorik" - "Ez dago informaziorik" - "Blokeatutako kanala" - "Hizkuntza ezezaguna" - "Azpitituluak" - "Desaktibatuta" - "Pertsonalizatu formatua" - "Zehaztu azpitituluen hobespen orokorrak" - "Bistaratzeko modua" - "Audio anitza" - "monoa" - "estereoa" - "5.1 inguratzailea" - "7.1 inguratzailea" - "%d kanal" - "Pertsonalizatu zerrenda" - "Hautatu taldea" - "Desautatu taldea" - "Taldekatzeko irizpidea" - "Kanalen iturburua" - "Bereizmen handia eta estandarra" - "Bereizmen handia" - "Bereizmen estandarra" - "Taldekatzeko irizpidea" - "Telesaio hau blokeatuta dago" - "Telesaioa \"%1$s\" gisa sailkatu da" - "Sarrerak ez du automatikoki sintonizatzeko aukera onartzen" - "Ezin da hasi \"%s\" automatikoki sintonizatzen" - "Ezin dira ireki sistemaren azpitituluen hobespenak." - - %1$d kanal gehitu dira - %1$d kanal gehitu da - - "Ez da gehitu kanalik" - "Sintonizadorea" - "Gurasoen ezarpenak" - "Aktibatuta" - "Desaktibatuta" - "Kanalak blokeatuta" - "Blokeatu guztiak" - "Desblokeatu guztiak" - "Ezkutuko kanalak" - "Telesaioen murrizk." - "Aldatu PIN kodea" - "Sailkapen-sistemak" - "Balorazioak" - "Sailkapen-sistema guztiak" - "Beste herrialdeak" - "Bat ere ez" - "Bat ere ez" - "Bat ere ez" - "Murrizketa asko" - "Erdi-mailako murriztapenak" - "Murriztapen gutxi" - "Pertsonalizatua" - "Edukia egokia da umeentzat" - "Edukia egokia da beste ume batzuentzat" - "Edukia egokia da nerabeentzat" - "Eskuzko murrizketak" - - - "%1$s eta azpisailkapenak" - "Azpisailkapenak" - "Kanal hau ikusi nahi baduzu, idatzi PIN kodea." - "Telesaio hau ikusi nahi baduzu, idatzi PIN kodea." - "Programaren balorazioa: %1$s. Idatzi PIN kodea programa ikusteko." - "Idatzi PIN kodea" - "Gurasoen kontrol-aukerak zehazteko, sortu PIN kode bat" - "Idatzi PIN kode berria" - "Berretsi PIN kodea" - "Idatzi uneko PIN kodea" - - Bost aldiz oker idatzi duzu PIN kodea.\nSaiatu berriro %1$d segundo barru. - Bost aldiz oker idatzi duzu PIN kodea.\nSaiatu berriro %1$d segundo barru. - - "PIN kodea ez da zuzena. Saiatu berriro." - "PIN kodeak ez datoz bat. Saiatu berriro." - "Ezarpenak" - "Pertsonalizatu zerrenda" - "Aukeratu telesaioen gidako kanalak" - "Kanalen iturburuak" - "Kanal berriak daude eskuragarri" - "Gurasoen ezarpenak" - "Kode irekiko lizentziak" - "Kode irekiko lizentziak" - "Bertsioa" - "Kanal hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea." - "Telesaio hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea." - "Telesaioa \"%1$s\" gisa sailkatu da.\nIkusi ahal izateko, sakatu Eskuinera tekla eta idatzi PIN kodea." - "Kanala ikusteko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." - "Telesaioa ikusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." - "Telesaioa %1$s gisa sailkatu da.\nIkusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia." - "Telesaioa blokeatuta dago" - "Telesaioa \"%1$s\" gisa sailkatu da" - "Audioa soilik" - "Seinale ahula" - "Ez zaude Internetera konektatuta" - - Kanal hau ezin da erreproduzitu %1$s arte, beste kanal batzuk grabatzen ari garelako. \n\nGrabaketaren orduak aldatzeko, sakatu Eskuinera tekla. - Kanal hau ezin da erreproduzitu %1$s arte, beste kanal bat ari garelako grabatzen. \n\nGrabaketaren orduak aldatzeko, sakatu Eskuinera tekla. - - "Izenik ez" - "Kanala blokeatuta dago" - "Berriak" - "Iturburuak" - - %1$d kanal - %1$d kanal - - "Ez dago kanalik erabilgarri" - "Berriak" - "Konfiguratu gabe" - "Lortu iturburu gehiago" - "Arakatu edukia zuzenean igortzen duten kanalak eskaintzen dituzten aplikazioak" - "Kanalen iturburu berriak daude erabilgarri" - "Beste kanal batzuk eskaintzen dituzten iturburu berriak dituzu eskura.\nKonfigura itzazu oraintxe bertan edo egin ezazu beste uneren batean kanalen iturburuen ezarpenera joanda." - "Konfiguratu" - "Ados" - - - "Telebistaren menua atzitzeko, sakatu HAUTATU" - "Ez da telebista-sarrerarik aurkitu" - "Ezin da aurkitu telebista-sarrera" - "Ez da pantaila txikia erabiltzea onartzen" - "Ez dago pantaila txikian erakuts daitekeen sarrerarik" - "Sintonizagailua ez da egokia. Abiarazi zuzeneko kanalen aplikazioa sintonizagailua telebistaren sarrera gisa erabiltzeko." - "Ezin izan da sintonizatu" - "Ez da aurkitu ekintza gauza dezakeen aplikaziorik." - "Iturburuko kanal guztiak ezkutuan daude.\nHautatu gutxienez kanal bat ikusteko." - "Bideoa ez dago erabilgarri" - "Atzera tekla konektatutako gailuari dagokio. Irteteko, sakatu Hasiera botoia." - "Zuzeneko kanalak aplikazioak baimena behar du telebistako programazioa irakurtzeko." - "Konfiguratu iturburuak" - "Telebista zuzenean zerbitzuarekin, aplikazioek zuzenean erreproduzitzen dituzten kanalak ohiko telebistaren moduan ikus ditzakezu. \n\nLehen urratsak emateko, konfiguratu instalatutako kanal-iturburuak. Bestela, arakatu Google Play Store denda zuzeneko kanalak eskaintzen dituzten aplikazio gehiago aurkitzeko." - "Grabaketak eta grabaketen programazioa" - "10 minutu" - "30 minutu" - "1 ordu" - "3 ordu" - "Azkenak" - "Programatutakoak" - "Telesailak" - "Beste batzuk" - "Ezin da grabatu kanal hau." - "Ezin da grabatu programa hau." - "%1$s grabatzeko programatu da" - "%1$s grabatuko da %2$s arte" - "Programazio osoa" - - Beste %1$d egun - Beste %1$d egun - - - %1$d minutu - %1$d minutu - - - %1$d grabaketa berri - %1$d grabaketa berri - - - %1$d grabaketa - %1$d grabaketa - - - %1$d grabaketa antolatuta - %1$d grabaketa antolatuta - - "Ikusi" - "Erreproduzitu hasieratik" - "Berrekin" - "Ezabatu" - "Ezabatu grabaketak" - "Berrekin" - "%1$s. denboraldia" - "Ikusi agenda" - "Irakurri gehiago" - "Ezabatu grabaketak" - "Hautatu ezabatu nahi dituzun atalak. Ezabatu ondoren, ezingo dituzu berreskuratu." - "Ez dago ezaba daitekeen grabaketarik." - "Hautatu ikusitako atalak" - "Hautatu atal guztiak" - "Ezabatu atal guztiak" - "%1$d/%2$d minutu ikusi dituzu" - "%1$d/%2$d segundo ikusi dituzu" - "Inoiz ikusi gabe" - - %1$d/%2$d atal ezabatu dira - %1$d/%2$d atal ezabatu da - - "Lehentasuna" - "Handiena" - "Txikiena" - "%1$d.a" - "Kanalak" - "Edozein" - "Aukeratu lehentasuna" - "Aldi berean programa gehiegi grabatu behar badira, lehentasun handieneko programak bakarrik grabatuko dira." - "Gorde" - "Grabaketa solteek dute lehentasun handiena" - "Utzi" - "Utzi" - "Ahaztu" - "Gelditu" - "Ikusi grabaketen agenda" - "Programa hau bakarrik" - "orain – %1$s" - "Telesail osoa…" - "Programatu, hala ere" - "Grabatu beste hau haren ordez" - "Utzi grabaketa hau bertan behera" - "Ikusi" - "Graba daiteke" - "Grabatzeko antolatuta" - "Grabatzeko gatazka" - "Grabatzen" - "Ezin izan da grabatu" - "Programak irakurtzen ari gara grabaketa-ordutegiak sortzeko" - "Programazioa irakurtzen" - - - "Bideo-grabagailu digitalak ez dauka behar adina memoria erabilgarri" - "Bideo-grabagailu digitalarekin programak grabatu ahal izango dituzu. Dena dela, une honetan ez daukazu bideo-grabagailua erabili ahal izateko behar adina memoria erabilgarri. Konektatu %1$s GB edo gehiago dituen unitate aldagarri bat eta formatea ezazu gailuaren memoria gisa erabiltzeko." - "Memoria-unitatea falta da" - "Bideo-grabagailu digitalak erabili duen memoria-unitateren bat falta da. Bideo-grabagailu digitala gaitu ahal izateko, konektatu aurrez erabilitako unitate aldagarria. Memoria-unitate hura eskura ez baduzu, berriz, aukera ezazu unitatea ahazteko aukera." - "Memoria-unitate hau ahaztea nahi duzu?" - "Grabatuta edo programatuta duzun eduki guztia galduko da." - "Grabaketa gelditu nahi duzu?" - "Grabatutako edukia gordeta geratuko da." - - - "Grabatzeko programatu da baina gatazkan dago beste grabaketa batzuekin" - "Grabatzen hasi da, baina gatazkak ditu" - "%1$s grabatuko da." - "%1$s grabatzen ari da." - "%1$s ez da osorik grabatuko." - "%1$s eta %2$s ez dira osorik grabatuko." - "%1$s, %2$s eta grabatzeko programatuta dagoen beste saio bat ez dira osorik grabatuko." - - %1$s, %2$s eta grabatzeko programatuta dauden beste %3$d saio ez dira osorik grabatuko. - %1$s, %2$s eta grabatzeko programatuta dagoen beste %3$d saio ez dira osorik grabatuko. - - "Zer grabatu nahi duzu?" - "Zenbat denboran grabatu nahi duzu?" - "Programatuta dago dagoeneko" - "Programa hau bera grabatzeko programatu duzu dagoeneko (%1$s)." - "Grabatuta dago dagoeneko" - "Programa hau grabatuta daukazu dagoeneko. DVR liburutegian duzu ikusgai." - - - - - - - - - "Ez da aurkitu grabatutako programa." - "Erlazionatutako grabaketak" - "(Ez dago programaren azalpenik)" - - %1$d grabaketa - %1$d grabaketa - - " / " - "%1$s kendu egin da programatutako grabaketen zerrendatik" - "Partzialki grabatuko da, sintonizadoreak gatazkak baititu." - "Ez da grabatuko, sintonizadoreak gatazkak baititu." - "Oraindik ez duzu programatu grabaketarik.\nProgramazio-gidan programa ditzakezu grabaketak." - - %1$d grabaketa-gatazka - %1$d grabaketa-gatazka - - "Seriearen ezarpenak" - "Hasi seriea grabatzen" - "Utzi seriea grabatzeari" - "Seriea grabatzeari utzi nahi diozu?" - "DVR liburutegian gordeta geratuko dira grabatutako atalak." - "Gelditu" - "Ez dago atalik ikusgai.\nIkusgai ezartzen dituztenean grabatuko ditugu." - - (%1$d minutu) - (%1$d minutu) - - "Gaur" - "Bihar" - "Atzo" - "Gaur, %1$s" - "Bihar, %1$s" - "Puntuazioa" - diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 7dd59c28..752f93e0 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -20,9 +20,8 @@ "مونو" "استریو" "کنترل‌های پخش" - "کانال‌های اخیر" + "کانال‌ها" "گزینه‌‌ تلویزیون" - "‏گزینه‌های PIP" "دسترسی به کنترل‌های پخش برای این کانال امکان‌پذیر نیست" "پخش یا مکث" "جلو بردن سریع" @@ -35,33 +34,15 @@ "زیرنویس‌ها" "حالت نمایش" "تصویر در تصویر" - "روشن" - "خاموش" "چند صدایی" "دریافت کانا‌ل‌های بیشتر" "تنظیمات" - "منبع" - "تعویض" - "روشن" - "خاموش" - "صدا" - "اصلی" - "پنجره تصویردرتصویر" - "طرح‌بندی" - "پایین سمت راست" - "بالا سمت راست" - "بالا سمت چپ" - "پایین سمت چپ" - "پهلو به پهلو" - "اندازه" - "بزرگ" - "کوچک" - "منبع ورودی" "تلویزیون (آنتنی/کابلی)" "هیچ اطلاعات برنامه‌ای وجود ندارد" "هیچ اطلاعاتی موجود نیست" "کانال مسدود شده" - "زبان نامشخص" + "زبان نامشخص" + "‏زیرنویس %1$d" "زیرنویس" "خاموش" "سفارشی کردن قالب‌بندی" @@ -83,6 +64,7 @@ "وضوح استاندارد" "گروه‌بندی براساس" "این برنامه مسدود است" + "این برنامه رتبه‌بندی نشده است" "رتبه‌بندی این برنامه %1$s است." "ورودی از اسکن خودکار پشتیبانی نمی‌کند" "اسکن خودکار «%s» شروع نمی‌شود" @@ -92,7 +74,6 @@ ‏%1$d کانال اضافه شد "هیچ کانالی اضافه نشد" - "تیونر" "کنترل‌های والدین" "روشن" "خاموش" @@ -108,6 +89,8 @@ "کشورهای دیگر" "هیچ‌‌کدام" "هیچ‌‌کدام" + "بدون رتبه‌بندی" + "مسدود کردن برنامه‌های رتبه‌بندی‌نشده" "هیچ‌‌کدام" "محدودیت‌های زیاد" "محدودیت‌های متوسط" @@ -124,6 +107,7 @@ "برای تماشای این کانال، پین خودتان را وارد کنید" "برای تماشای این برنامه، پین خودتان را وارد کنید" "به این برنامه %1$s رتبه داده شده است. برای تماشای این برنامه کد پین را وارد کنید" + "این برنامه رتبه‌بندی نشده است. برای تماشای این برنامه باید پین خود را وارد کنید." "پین خودتان را وارد کنید" "برای تنظیم کنترل‌های والدین، پین ایجاد کنید" "پین جدید را وارد کنید" @@ -135,22 +119,31 @@ "این پین اشتباه بود. دوباره امتحان کنید." "دوباره امتحان کنید، پین منطبق نیست" + "زیپ‌کدتان را وارد کنید." + "برنامه «کانال‌های زنده» از زیپ‌کد جهت ارائه یک راهنمای برنامه کامل برای کانال‌های تلویزیونی استفاده می‌کند." + "زیپ‌کدتان را وارد کنید" + "زیپ‌کد نامعتبر است" "تنظیمات" "سفارشی کردن فهرست کانال‌ها" "کانال‌ها را برای راهنمای برنامه‌تان انتخاب کنید" "منابع کانال" "کانال‌های جدید در دسترس است" "کنترل‌های والدین" + "تایم شیفت" + "درحین تماشا ضبط کنید، به‌این‌ترتیب می‌توانید برنامه‌های زنده را موقتاً متوقف کنید یا به عقب برگردانید.\nاخطار: این قابلیت ممکن است به دلیل استفاده بیش‌ازحد از فضای ذخیره‌سازی، باعث کاهش عمرمفید حافظه داخلی شود." "مجوزهای منبع آزاد" - "مجوزهای منبع آزاد" + "ارسال بازخورد" "نسخه" "برای مشاهده این کانال، راست را فشار دهید و پین خودتان را وارد کنید" "برای مشاهده این برنامه، راست را فشار دهید و پین خودتان را وارد کنید" + "‏این برنامه رتبه‌بندی نشده است.\nبرای تماشای این برنامه، Right (راست) را فشار دهید و پین خود را وارد کنید" "‏رتبه‌بندی این برنامه %1$s است.\nبرای تماشای این برنامه، Right (راست) را فشار دهید و پین خودتان را وارد کنید." "برای تماشای این کانال، از برنامه «پخش مستقیم» پیش‌فرض استفاده کنید." "برای تماشای این برنامه، از برنامه «پخش مستقیم» پیش‌فرض استفاده کنید." + "این برنامه رتبه‌بندی نشده است.\nبرای تماشای این برنامه، از برنامه «پخش مستقیم» پیش‌فرض استفاده کنید." "به این برنامه رتبه %1$s داده شده است.\n برای تماشای این برنامه، از برنامه «پخش مستقیم» استفاده کنید." "برنامه مسدود شده است" + "این برنامه رتبه‌بندی نشده است" "رتبه‌بندی این برنامه %1$s است." "فقط صدا" "سیگنال ضعیف است" @@ -181,8 +174,6 @@ "‏برای دسترسی به منوی تلویزیون، ""SELECT (انتخاب) را فشار دهید""." "هیچ ورودی تلویزیون یافت نشد" "نمی‌توان ورودی تلویزیون پیدا کرد" - "تصویر در تصویر پشتیبانی نمی‌شود." - "ورودی قابل نمایش با تصویر در تصویر، در دسترس نیست." "نوع تیونر مناسب نیست. لطفاً برنامه‌ «کانال‌های مستقیم» را برای ورودی تلویزیون نوع تیونر، راه‌اندازی کنید." "تنظیم نشد" "برنامه‌ای برای انجام این اقدام پیدا نشد." @@ -226,6 +217,8 @@ ‏%1$d ضبط زمان‌بندی‌شده ‏%1$d ضبط زمان‌بندی‌شده + "لغو ضبط" + "توقف ضبط" "ساعت مچی" "پخش از اول" "ازسرگیری پخش" @@ -258,9 +251,6 @@ "اگر برنامه‌های زیادی برای ضبط هم‌زمان وجود داشته باشد، فقط برنامه‌های با بیشترین اولویت ضبط خواهند شد." "ذخیره" "ضبط‌های تکی بالاترین اولویت را دارند" - "لغو" - "لغو" - "فراموش شود" "توقف" "مشاهده زمان‌بندی ضبط" "فقط همین برنامه" @@ -270,25 +260,28 @@ "درعوض این مورد ضبط شود" "لغو این ضبط" "اکنون تماشا کنید" + "درحال حذف موارد ضبط‌شده…" "قابل ضبط" "ضبط برنامه‌ریزی‌شده" "ضبط متناقض با زمان‌بندی" "درحال ضبط" "ضبط ناموفق بود" - "درحال خواندن برنامه‌ها برای ایجاد زمان‌بندی ضبط" - "درحال خواندن برنامه‌ها" - - + "درحال خواندن برنامه‌ها" + "مشاهده موارد ضبط‌شده اخیر" + "ضبط %1$s کامل نشده است." + "ضبط %1$s و %2$s کامل نشده است." + "ضبط %1$s، %2$s و %3$s کامل نشده است." + "به دلیل حافظه ناکافی، ضبط %1$s کامل نشد." + "به دلیل حافظه ناکافی، ضبط %1$s و%2$s کامل نشد." + "به دلیل حافظه ناکافی، ضبط %1$s، %2$s و %3$s کامل نشد." "‏DVR به فضای بیشتری نیاز دارد" - "‏با DVR می‌توانید برنامه‌ها را ضبط کنید. اما اکنون فضای ذخیره‌سازی کافی در دستگاهتان وجود ندارد و DVR کار نمی‌کند. لطفاً درایو خارجی‌‌ای با حجم %1$s گیگابایت یا بیشتر متصل کنید و برای قالب‌بندی آن‌ به‌عنوان حافظه دستگاه این مراحل را دنبال کنید." + "‏با DVR می‌توانید برنامه‌ها را ضبط کنید. اما اکنون فضای ذخیره‌سازی کافی در دستگاهتان وجود ندارد و DVR کار نمی‌کند. لطفاً درایو خارجی‌‌ای با حجم %1$d گیگابایت یا بیشتر متصل کنید و برای قالب‌بندی آن‌ به‌عنوان حافظه دستگاه این مراحل را دنبال کنید." + "حافظه ذخیره‌سازی کافی نیست" + "این برنامه ضبط نخوادهد شد، زیرا حافظه ذخیره‌سازی وجود ندارد. برخی از موارد ضبط‌‌شده قبلی را حذف کنید." "حافظه دردسترس نیست" - "‏مقداری از حافظه‌ای که توسط DVR استفاده می‌شود از بین می‌رود. لطفاً برای فعال‌سازی مجدد DVR، درایو خارجی را که قبلاً‌ استفاده کردید متصل کنید. یا اگر این حافظه دیگر دردسترس نیست می‌توانید انتخاب کنید فراموش شود." - "حافظه فراموش شود؟" - "همه زمان‌بندی‌ها و محتوای ضبط‌شده‌ شما از دست می‌رود." "ضبط متوقف شود؟" "محتوای ضبط‌شده ذخیره خواهد شد." - - + "ضبط %1$s به دلیل تناقض با این برنامه متوقف خواهد شد. محتوای ضبط‌شده ذخیره می‌شود." "ضبط، زمان‌بندی شده است اما متناقض است" "ضبط شروع شده است اما متناقض است" "%1$s ضبط خواهد شد." @@ -306,17 +299,29 @@ "این برنامه قبلاً‌ برای ضبط در %1$s زمان‌بندی شده است." "قبلاً‌ ضبط شده است" "‏این برنامه قبلاً‌ ضبط شده است و در کتابخانه DVR موجود است." - - - - - - - - + "ضبط مجموعه زمان‌بندی شد" + + %1$d ضبط برای %2$s زمان‌بندی شده است. + %1$d ضبط برای %2$s زمان‌بندی شده است. + + + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d از آن‌ها به دلیل تناقض ضبط نخواهد شد. + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d از آن‌ها به دلیل تناقض ضبط نخواهد شد. + + + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d قسمت این مجموعه و مجموعه‌های دیگر به دلیل تناقض ضبط نخواهند شد. + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d قسمت این مجموعه و مجموعه‌های دیگر به دلیل تناقض ضبط نخواهند شد. + + + %1$d ضبط برای %2$s زمان‌بندی شده است. ۱ قسمت مجموعه‌های دیگر به دلیل تناقض ضبط نخواهد شد. + %1$d ضبط برای %2$s زمان‌بندی شده است. ۱ قسمت مجموعه‌های دیگر به دلیل تناقض ضبط نخواهد شد. + + + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d قسمت مجموعه‌های دیگر به دلیل تناقض ضبط نخواهند شد. + %1$d ضبط برای %2$s زمان‌بندی شده است. %3$d قسمت مجموعه‌های دیگر به دلیل تناقض ضبط نخواهند شد. + "برنامه ضبط‌شده پیدا نشد." "ضبط‌های مرتبط" - "(بدون شرح برنامه)" ‏%1$d ضبط ‏%1$d ضبط @@ -336,6 +341,7 @@ "ضبط مجموعه متوقف شود؟" "‏قسمت‌های ضبط‌شده در کتابخانه DVR دردسترس باقی می‌ماند." "توقف" + "درحال‌حاضر قسمتی پخش مستقیم نمی‌شود." "هیچ قسمتی موجود نیست.\nهر قسمتی در دسترس قرار بگیرد ضبط خواهد شد." ‏(%1$d دقیقه) @@ -347,4 +353,5 @@ "%1$s امروز" "%1$s فردا" "امتیاز" + "برنامه‌های ضبط‌شده" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index b6f5fb7e..eba0cc63 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Toistosäätimet" - "Viim. kanavat" + "Kanavat" "TV-asetukset" - "PIP-asetukset" "Toistosäätimet eivät ole käytettävissä tällä kanavalla." "Toisto tai keskeytys" "Kelaa eteenpäin" @@ -35,33 +34,15 @@ "Tekstitykset" "Näyttötila" "PIP" - "Käytössä" - "Ei käytössä" "Moniääni" "Lisää kanavia" "Asetukset" - "Lähde" - "Vaihda" - "Käytössä" - "Ei käytössä" - "Ääni" - "Ensisijainen" - "PIP-ikkuna" - "Asettelu" - "Alaoikealla" - "Yläoikealla" - "Ylävasemmalla" - "Alavasemmalla" - "Vierekkäin" - "Mitat" - "Suuri" - "Pieni" - "Syötteen lähde" "TV (antenni/kaapeli)" "Ei ohjelmatietoja" "Ei tietoja" "Estetty kanava" - "Tuntematon kieli" + "Tuntematon kieli" + "Tekstitykset %1$d" "Tekstitykset" "Ei käytössä" "Muokkaa muotoilua" @@ -83,6 +64,7 @@ "SD" "Ryhmittely:" "Ohjelma on estetty." + "Ohjelmalla ei ole ikärajaa." "Tämän ohjelman luokitus on %1$s." "Tulo ei tue automaattista skannausta." "Tulon %s automaattista skannausta ei voi käynnistää" @@ -92,7 +74,6 @@ %1$d kanava lisätty "Kanavia ei lisätty" - "Viritin" "Lapsilukko" "Käytössä" "Ei käytössä" @@ -108,6 +89,8 @@ "Muut maat" "Ei mitään" "Ei mitään" + "Ei ikärajaa" + "Estä ikärajattomat ohjelmat" "Ei mitään" "Suuret rajoitukset" "Keskim. rajoitukset" @@ -124,6 +107,7 @@ "Anna PIN-koodi, jotta voit katsella tätä kanavaa" "Anna PIN-koodi, jotta voit katsella tätä ohjelmaa" "Tämän ohjelman ikärajoitus on %1$s. Anna PIN-koodi, jos haluat katsoa tätä ohjelmaa." + "Tälle ohjelmalle ei ole määritetty ikärajaa. Anna PIN-koodi, jos haluat katsoa sen." "Anna PIN-koodi" "Aseta lapsilukko luomalla PIN-koodi" "Anna uusi PIN-koodi" @@ -135,22 +119,31 @@ "Väärä PIN-koodi. Yritä uudelleen." "PIN-koodit ovat erilaiset, yritä uudelleen" + "Anna postinumero" + "Live-kanavat-sovellus tarjoaa postinumeron avulla kaikkien alueellasi näkyvien TV-kanavien ohjelmaoppaan." + "Anna postinumero" + "Virheellinen postinumero" "Asetukset" "Muokkaa kanavaluetteloa" "Valitse kanavat ohjelmaoppaaseen" "Kanavalähteet" "Uusia kanavia saatavilla" "Lapsilukko" + "Ajansiirto" + "Tallentaa katsottavaa ohjelmaa, jolloin lähetettävän ohjelman voi keskeyttää tai sitä voi kelata.\nVaroitus: tämä voi lyhentää sisäisen tallennustilan käyttöikää runsaan käytön vuoksi." "Avoimen lähdekoodin käyttöluvat" - "Avoimen lähdekoodin käyttöluvat" + "Lähetä palautetta" "Versio" "Katsele tätä kanavaa painamalla näppäimellä oikealle ja antamalla PIN-koodi" "Katsele tämä ohjelma painamalla näppäimellä oikealle ja antamalla PIN-koodi" + "Tälle ohjelmalle ei ole määritetty ikärajaa.\nPaina oikeaa painiketta ja anna PIN-koodi, jos haluat katsoa sen." "Tämän ohjelman luokitus on %1$s.\nKatso ohjelma painamalla näppäimellä oikealle ja antamalla PIN-koodi." "Katsele tätä kanavaa TV-lähetyksien oletussovelluksessa." "Katsele tätä ohjelmaa TV-lähetyksien oletussovelluksessa." + "Tälle ohjelmalle ei ole määritetty ikärajaa.\nKäytä suoran TV-sisällön oletussovellusta, jos haluat katsoa sen." "Tämän ohjelman ikärajoitus on %1$s.\nKatsele tätä ohjelmaa TV-lähetyksien oletussovelluksessa." "Ohjelma on estetty" + "Ohjelmalla ei ole ikärajaa." "Tämän ohjelman luokitus on %1$s." "Vain ääni" "Heikko signaali" @@ -181,8 +174,6 @@ "Avaa TV-valikko ""painamalla VALITSE""." "TV-tuloa ei löytynyt." "TV-tuloa ei löydy." - "PIP:tä ei tueta." - "Käytettävissä ei ole syötettä, joka voitaisiin näyttää PIP:ssä." "Viritintä ei tueta. Voit käyttää viritintä käynnistämällä Live-kanavat-sovelluksen." "Viritys epäonnistui." "Tätä toimintoa käsittelevää sovellusta ei löydy." @@ -226,6 +217,8 @@ %1$d tallenn. ajastettu %1$d tallennus ajastettu + "Peruuta tallennus" + "Lopeta tallennus" "Katso" "Toista alusta" "Jatka toistoa" @@ -258,9 +251,6 @@ "Jos ohjelmia on ajastettu enemmän kuin samalla kertaa voidaan tallentaa, vain tärkeimmät ohjelmat tallennetaan." "Tallenna" "Kertaluotoiset tallennukset ovat tärkeimpiä." - "Peruuta" - "Peruuta" - "Unohda" "Lopeta" "Näytä tallennusaikataulu" "Vain tämä jakso" @@ -270,25 +260,28 @@ "Nauhoita tämä sen sijaan" "Peruuta tämä nauhoitus" "Katso nyt" + "Poista tallenteita…" "Tallennettavissa" "Tallennus ajastettu" "Tallennusristiriita" "Tallennetaan" "Nauhoitus epäonnistui" - "Luetaan ohjelmatietoja tallennusaikataulujen luomista varten." - "Luetaan ohjelmatietoja" - - + "Luetaan ohjelmatietoja" + "Näytä uusimmat tallenteet" + "Kohdetta %1$s ei nauhoitettu loppuun." + "Kohteita %1$s ja %2$s ei nauhoitettu loppuun." + "Kohteita %1$s, %2$s ja %3$s ei nauhoitettu loppuun." + "Kohdetta %1$s ei nauhoitettu loppuun, koska tallennustila ei riitä." + "Kohteita %1$s ja %2$s ei nauhoitettu loppuun, koska tallennustila ei riitä." + "Kohteita %1$s, %2$s ja %3$s ei nauhoitettu loppuun, koska tallennustila ei riitä." "DVR tarvitsee lisää tilaa" - "Voit tallentaa ohjelmia DVR:llä. Laitteellasi ei kuitenkaan ole tarpeeksi tilaa DVR:n käyttöön. Yhdistä vähintään %1$s Gt:n kokoinen ulkoinen tallennuslaite ja alusta se laitteen tallennustilaksi ohjeiden mukaisesti." + "Voit tallentaa ohjelmia DVR:llä. Laitteellasi ei kuitenkaan ole tarpeeksi tilaa DVR:n käyttöön. Yhdistä vähintään %1$d Gt:n kokoinen ulkoinen tallennuslaite ja alusta se laitteen tallennustilaksi ohjeiden mukaisesti." + "Tallennustila ei riitä" + "Tätä ohjelmaa ei tallenneta, koska tallennustila ei riitä. Kokeile poistaa joitakin nykyisiä tallenteita." "Tallennustila puuttuu" - "Osa DVR:n käytössä olleesta tallennustilasta puuttuu. Palauta DVR käyttöön liittämällä aiemmin käyttämäsi ulkoinen asema. Voit myös unohtaa tallennustilan, jos asema ei ole enää käytettävissä." - "Unohdetaanko tallennustila?" - "Kaikki tallennettu sisältö ja aikataulut menetetään." "Lopetetaanko tallennus?" "Tallennettu sisältö lisätään kirjastoon." - - + "Ohjelman %1$s nauhoitus pysäytetään, koska se on ristiriidassa tämän ohjelman kanssa. Nauhoitettu sisältö tallennetaan." "Tallennus ajastettu – ristiriitoja havaittu" "Tallennus käynnissä – ristiriitoja havaittu" "%1$s tallennetaan" @@ -306,17 +299,29 @@ "Sama ohjelma on jo ajastettu nauhoitettavaksi klo %1$s." "Jo nauhoitettu" "Tämä ohjelma on jo nauhoitettu. Se on käytettävissä DVR-kirjastossa." - - - - - - - - + "Sarjan nauhoitus ajastettu" + + %1$d sarjan %2$s nauhoitusta lisätty. + %1$d sarjan %2$s nauhoitus lisätty. + + + %1$d sarjan %2$s nauhoitusta lisätty. %3$d niistä jätetään nauhoittamatta ristiriidan takia. + %1$d sarjan %2$s nauhoitus lisätty. Se jätetään nauhoittamatta ristiriidan takia. + + + %1$d sarjan %2$s nauhoitusta lisätty. Tämän sarjan ja muiden sarjojen %3$d jaksoa jätetään nauhoittamatta ristiriidan takia. + %1$d sarjan %2$s nauhoitus lisätty. Tämän sarjan ja muiden sarjojen %3$d jaksoa jätetään nauhoittamatta ristiriidan takia. + + + %1$d sarjan %2$s nauhoitusta lisätty. Yksi toisen sarjan jakso jätetään nauhoittamatta ristiriidan takia. + %1$d sarjan %2$s nauhoitus lisätty. Yksi toisen sarjan jakso jätetään nauhoittamatta ristiriidan takia. + + + %1$d sarjan %2$s nauhoitusta lisätty. Muiden sarjojen %3$d jaksoa jätetään nauhoittamatta ristiriidan takia. + %1$d sarjan %2$s nauhoitus lisätty. Muiden sarjojen %3$d jaksoa jätetään nauhoittamatta ristiriidan takia. + "Tallennettua ohjelmaa ei löytynyt." "Aiheeseen liittyvät tallenteet" - "(Ohjelmalla ei ole kuvausta.)" %1$d tallennetta %1$d tallenne @@ -336,6 +341,7 @@ "Lopetetaanko sarjan tallennus?" "Tallennettuja osia voi edelleen katsella DVR:n kirjastossa." "Lopeta" + "Jaksoja ei ole tällä hetkellä saatavilla." "Yhtään jaksoa ei ole saatavilla.\nNe nauhoitetaan, kun ne ovat saatavilla." (%1$d minuuttia) @@ -347,4 +353,5 @@ "%1$s tänään" "%1$s huomenna" "Arvosana" + "Tallennetut ohjelmat" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 1fc794e3..80fac2d6 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -20,9 +20,8 @@ "mono" "stéréo" "Commandes de lecture" - "Chaînes récentes" + "Chaînes" "Options télé" - "Options IDI" "Les commandes de lecture ne sont pas disponibles pour cette chaîne" "Lire ou mettre en pause" "Avance rapide" @@ -35,33 +34,15 @@ "Sous-titres" "Mode d\'affichage" "IDI" - "Activé" - "Désactivé" "Multi-audio" "Plus de chaînes" "Paramètres" - "Source" - "Basculer" - "Activé" - "Désactivé" - "Son" - "Principale" - "Fenêtre IDI" - "Disposition" - "En bas à droite" - "En haut à droite" - "En haut à gauche" - "En bas à gauche" - "Côte à côte" - "Taille" - "Grande" - "Petite" - "Source de l\'entrée" "Télévision (antenne/câble)" "Aucune information sur le programme" "Pas d\'information" "Chaîne bloquée" - "Langue : indéterminée" + "Langue indéterminée" + "Sous-titres en %1$d" "Sous-titres" "Désactivé" "Personnaliser format" @@ -83,6 +64,7 @@ "SD" "Grouper par" "Ce programme est bloqué" + "Ce programme n\'est pas classé" "Ce programme est classé « %1$s »" "L\'entrée ne prend pas en charge la recherche automatique" "Impossible de lancer la recherche automatique pour « %s »." @@ -92,7 +74,6 @@ %1$d chaînes ajoutées "Aucune chaîne ajoutée" - "Syntoniseur" "Contrôle parental" "Activer" "Désactivé" @@ -108,7 +89,9 @@ "Autres pays" "Aucune" "Aucun" - "Aucune" + "Non classé" + "Bloquer programmes non classés" + "Aucun" "Restrictions élevées" "Restrictions moy." "Restrictions faibles" @@ -124,6 +107,7 @@ "Entrez votre NIP pour regarder cette chaîne" "Entrez votre NIP pour regarder ce programme" "Ce programme est classé %1$s. Entrez votre NIP pour regarder ce programme" + "Ce programme n\'est pas classé. Entrez votre NIP pour le regarder" "Entrez votre NIP" "Créez un NIP pour régler les contrôles parentaux" "Entrez le nouveau NIP" @@ -135,22 +119,31 @@ "Ce NIP est incorrect. Veuillez réessayer." "Le NIP est incorrect. Veuillez réessayer." + "Entrez votre code postal." + "L\'application Télé en direct utilisera le code postal pour vous présenter un guide complet des stations de télévision dans votre région." + "Entrez votre code postal" + "Code postal non valide" "Paramètres" "Personnaliser la liste chaînes" "Choisir des chaînes pour le guide des programmes" "Sources des chaînes" "De nouvelles chaînes sont disponibles" "Contrôles parentaux" + "Décalage temporel" + "Enregistrez des programmes en direct tout en les regardant afin de pouvoir les mettre en pause ou les rembobiner.\nAvertissement : Cette opération peut réduire la durée de vie de la mémoire de stockage interne en raison de son utilisation intensive." "Licences de logiciels libres" - "Licences de logiciels libres" + "Envoyer un commentaire" "Version" "Pour regarder cette chaîne, touchez la droite, puis entrez votre NIP." "Pour regarder ce programme, touchez la droite, puis entrez votre NIP." + "Ce programme n\'est pas classé.\nPour le regarder, appuyez sur le bouton Droite, puis entrez votre NIP" "Ce programme est classé « %1$s ». \nPour le regarder, touchez la droite, puis entrez votre NIP." "Pour regarder cette chaîne, utilisez l\'application de télévision en direct par défaut." "Pour regarder ce programme, utilisez l\'application de télévision en direct par défaut." + "Ce programme n\'est pas classé.\nPour le regarder, utilisez l\'application de télévision en direct par défaut." "Ce programme est classé %1$s.\nPour le regarder, utilisez l\'application de télévision en direct par défaut." "Programme bloqué" + "Ce programme n\'est pas classé" "Ce programme est classé « %1$s »" "Audio uniquement" "Signal faible" @@ -181,8 +174,6 @@ "Appuyez sur la touche ""Sélectionner"" pour accéder au menu Télévision." "Aucune entrée trouvée" "Entrée introuvable" - "Le mode Image dans image n\'est pas pris en charge" - "Aucune entrée ne pouvant être affichée en mode IDI" "Le type d\'entrée ne convient pas. Veuillez lancer l\'application Chaînes en direct pour une entrée de type syntoniseur." "Échec des réglages" "Aucune application pouvant gérer cette action n\'a été trouvée." @@ -190,7 +181,7 @@ "Indisponibilité inattendue de la vidéo" "La touche RETOUR concerne l\'appareil connecté. Appuyez sur le bouton ACCUEIL pour quitter." "Les chaînes en direct ont besoin de l\'autorisation nécessaire pour lire les programmes télé." - "Configurez vos sources" + "Configurer vos sources" "Avec Télé en direct, diffusez les chaînes proposées par des applications comme s\'il s\'agissait de chaînes de télévision traditionnelles. \n\nCommencez par configurer les sources de chaînes déjà installées. Vous pouvez également parcourir la boutique Google Play Store et rechercher d\'autres applications qui proposent des chaînes en direct." "Enregistrements et horaires" "10 minutes" @@ -226,6 +217,8 @@ %1$d enreg. programmé %1$d enreg. programmés + "Annuler l\'enregistrement" + "Arrêter l\'enregistrement" "Regarder" "Lire du début" "Reprendre la lecture" @@ -258,9 +251,6 @@ "Quand il y a trop de programmes à enregistrer en même temps, seuls ceux aux priorités les plus élevées sont enregistrés." "Enregistrer" "Les enregistrements ponctuels ont la plus haute priorité" - "Annuler" - "Annuler" - "Supprimer" "Arrêter" "Voir le programme d\'enregistrement" "Uniquement ce programme" @@ -270,25 +260,28 @@ "Enregistrez celui-ci à la place" "Annuler cet enregistrement" "Regarder" + "Supprimer des enregistrements" "Enregistrable" "Enregistrement programmé" "Conflit d\'enregistrement" "Enregistrement en cours" "Échec de l\'enregistrement" - "Lecture des programmes pour créer des horaires d\'enregistrement…" - "Lecture des programmes en cours…" - - + "Lecture des programmes en cours…" + "Afficher les enregistrements récents" + "L\'enregistrement de %1$s est incomplet." + "Les enregistrements de %1$s et %2$s sont incomplets." + "Les enregistrements de %1$s, %2$s et %3$s sont incomplets." + "L\'enregistrement de %1$s n\'a pas été terminé à cause d\'un espace de stockage insuffisant." + "L\'enregistrement de %1$s et %2$s n\'a pas été terminé à cause d\'un espace de stockage insuffisant." + "L\'enregistrement de %1$s, %2$s et %3$s n\'a pas été terminé à cause d\'un espace de stockage insuffisant." "Le magnétoscope numérique a besoin de plus d\'espace" - "Vous pourrez enregistrer des programmes avec le magnétoscope numérique. Toutefois, l\'espace de stockage est insuffisant sur votre appareil pour que le magnétoscope numérique puisse fonctionner actuellement. Veuillez brancher un disque externe d\'au moins %1$s Go ou suivez les étapes pour le formater en tant qu\'espace de stockage de l\'appareil." + "Vous pourrez enregistrer des programmes avec le magnétoscope numérique. Toutefois, l\'espace de stockage est insuffisant sur votre appareil pour que le magnétoscope numérique puisse fonctionner actuellement. Veuillez brancher un disque externe d\'au moins %1$d Go ou suivez les étapes pour le formater en tant qu\'espace de stockage de l\'appareil." + "Espace de stockage insuffisant" + "Ce programme ne sera pas enregistré, car l\'espace de stockage disponible est insuffisant. Essayez de supprimer certains enregistrements." "Espace de stockage manquant" - "Une partie de l\'espace de stockage utilisé par le magnétoscope numérique est manquante. Veuillez connecter de nouveau le disque externe que vous avez utilisé auparavant afin de réactiver le magnétoscope numérique. Vous pouvez également supprimer cet espace de stockage s\'il n\'est plus disponible." - "Supprimer l\'espace de stockage?" - "Tous vos contenus enregistrés et vos enregistrements planifiés seront perdus." "Arrêter l\'enregistrement?" "Le contenu enregistré sera gardé." - - + "L\'enregistrement de « %1$s » sera interrompu, car il crée un conflit avec ce programme. Le contenu enregistré sera conservé." "Enregistrement planifié, mais d\'autres enregistrements sont prévus en même temps." "L\'enregistrement a commencé, mais d\'autres enregistrements sont prévus en même temps" "Le programme « %1$s » sera enregistré." @@ -306,17 +299,29 @@ "Vous avez déjà planifié l\'enregistrement de ce programme à %1$s." "Déjà enregistré" "Ce programme a déjà été enregistré. Il est accessible dans la bibliothèque du magnétoscope numérique." - - - - - - - - + "L\'enregistrement de la série est programmé" + + %1$d enregistrement a été programmé pour %2$s. + %1$d enregistrements ont été programmés pour %2$s. + + + %1$d enregistrement a été programmé pour %2$s. L\'enregistrement (%3$d) ne sera pas effectué en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d d\'entre eux ne seront pas effectués en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. %3$d épisodes de cette série et d\'une autre série ne seront pas enregistrés en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d épisodes de cette série et d\'une autre série ne seront pas enregistrés en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. Un épisode d\'une autre série ne sera pas enregistré en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. Un épisode d\'une autre série ne sera pas enregistré en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. %3$d épisodes d\'une autre série ne seront pas enregistrés en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d épisodes d\'une autre série ne seront pas enregistrés en raison de conflits. + "Programme enregistré non trouvé." "Enregistrements connexes" - "(Programme sans description)" %1$d enregistrement %1$d enregistrements @@ -336,6 +341,7 @@ "Arrêter l\'enregistrement de la série?" "Les épisodes enregistrés resteront accessibles dans la bibliothèque du magnétoscope numérique." "Arrêter" + "Aucun épisode n\'est diffusé en ce moment." "Aucun épisode.\nLes épisodes seront enregistrés lorsqu\'ils seront diffusés." (%1$d minute) @@ -347,4 +353,5 @@ "Aujourd\'hui, %1$s" "Demain, %1$s" "Note" + "Programmes enregistrés" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index c140aa62..d290b144 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -20,9 +20,8 @@ "mono" "stéréo" "Commandes de lecture" - "Chaînes récentes" + "Chaînes" "Options TV" - "Options PIP" "Commandes de lecture indisponibles pour cette chaîne." "Lire ou mettre en pause" "Avance rapide" @@ -35,36 +34,18 @@ "Sous-titres" "Mode d\'affichage" "Mode PIP" - "Activé" - "Désactivé" "Multi-audio" "Plus de chaînes" "Paramètres" - "Source" - "Permuter" - "Activé" - "Désactivé" - "Son" - "Principale" - "Fenêtre mode PIP" - "Mise en page" - "En bas à droite" - "En haut à droite" - "En haut à gauche" - "En bas à gauche" - "Côte à côte" - "Taille" - "Grande" - "Petite" - "Source d\'entrée" "Téléviseur (antenne/câble)" "Aucune information sur le programme." "Aucune information" "Chaîne bloquée" - "Langue inconnue" + "Langue inconnue" + "Sous-titres %1$d" "Sous-titres" "Désactivé" - "Personnaliser le formatage" + "Personnaliser le format" "Configurer les paramètres du système pour les sous-titres" "Mode d\'affichage" "Multi-audio" @@ -83,6 +64,7 @@ "SD" "Grouper par" "Ce programme est bloqué." + "Ce programme n\'a pas de classification" "Classification de ce programme : %1$s" "L\'entrée n\'est pas compatible avec la recherche automatique." "Impossible de lancer la recherche automatique pour \"%s\"." @@ -92,7 +74,6 @@ %1$d chaînes ont bien été ajoutées. "Aucune chaîne ajoutée." - "Tuner" "Contrôle parental" "Activé" "Désactivé" @@ -108,6 +89,8 @@ "Autres pays" "Aucune" "Aucun" + "Aucune classification" + "Bloquer si sans classification" "Aucun" "Restrictions élevées" "Restrictions moyennes" @@ -124,6 +107,7 @@ "Saisir votre code d\'accès pour regarder cette chaîne" "Saisir votre code d\'accès pour regarder ce programme" "Classification de ce programme : %1$s. Saisissez votre code pour le regarder." + "Ce programme n\'a pas de classification. Saisissez votre code pour le regarder." "Saisir votre code d\'accès" "Créer un code d\'accès pour paramétrer le contrôle parental" "Saisir le nouveau code d\'accès" @@ -135,22 +119,31 @@ "Ce code d\'accès est incorrect. Veuillez réessayer." "Le code est incorrect. Veuillez réessayer." + "Saisissez votre code postal." + "L\'application TV en direct utilise le code postal pour fournir un guide des programmes complet pour les chaînes de télévision." + "Saisissez votre code postal" + "Code postal incorrect" "Paramètres" "Personnaliser liste chaînes" "Choisir des chaînes pour le guide des programmes" "Sources des chaînes" "Nouvelles chaînes disponibles" "Contrôle parental" + "Contrôle du direct" + "Enregistrez des programmes en direct tout en les regardant afin de pouvoir les mettre en pause ou les rembobiner.\nAvertissement : Cette opération peut réduire la durée de vie de la mémoire de stockage interne en raison de son utilisation intensive." "Licences Open Source" - "Licences Open Source" + "Envoyer des commentaires" "Version" "Pour regarder cette chaîne, appuyez sur le bouton droit, puis saisissez votre code d\'accès." "Pour regarder ce programme, appuyez sur la droite, puis saisissez votre code d\'accès." + "Ce programme n\'a pas de classification.\nPour le regarder, appuyez sur la touche droite, puis saisissez votre code." "Classification de ce programme : %1$s.\nPour le regarder, appuyez sur la touche droite, puis saisissez votre code d\'accès." "Pour regarder cette chaîne, utilisez l\'application de télévision en direct par défaut." "Pour regarder ce programme, utilisez l\'application de télévision en direct par défaut." + "Ce programme n\'a pas de classification.\nPour le regarder, utilisez l\'application de télévision en direct par défaut." "Classification de ce programme : %1$s.\nPour regarder ce programme, utilisez l\'application de télévision en direct par défaut." "Programme bloqué" + "Ce programme n\'a pas de classification" "Classification de ce programme : %1$s" "Audio uniquement" "Signal faible" @@ -181,8 +174,6 @@ "Appuyez sur SÉLECTIONNER"" pour accéder au menu de la télévision." "Aucune entrée TV trouvée." "Entrée TV introuvable." - "Le mode PIP n\'est pas compatible." - "Aucune entrée disponible ne peut être affichée en mode PIP." "Type de tuner non adapté. Veuillez lancer l\'application Chaînes en direct pour l\'entrée TV de type tuner." "Échec des réglages." "Aucune application trouvée pour gérer cette action." @@ -190,7 +181,7 @@ "Indisponibilité inattendue de la vidéo" "La touche RETOUR concerne l\'appareil connecté. Appuyez sur le bouton ACCUEIL pour quitter." "L\'application Chaînes en direct a besoin d\'une autorisation pour accéder au programme TV." - "Configurez vos sources." + "Configurez vos sources" "Avec TV en direct, regardez en streaming les chaînes proposées par des applications comme s\'il s\'agissait de chaînes de télévision traditionnelles. \n\nCommencez par configurer les sources de chaînes déjà installées. Vous pouvez également parcourir le Google Play Store et rechercher d\'autres applications qui proposent des chaînes en direct." "Enregistrements et plannings" "10 minutes" @@ -226,6 +217,8 @@ %1$d enregistr. planifié %1$d enregistr. planifiés + "Annuler l\'enregistrement" + "Arrêter enregistrement" "Regarder" "Lire depuis le début" "Reprendre la lecture" @@ -258,9 +251,6 @@ "Lorsqu\'il y a trop de programmes à enregistrer simultanément, seuls les programmes aux priorités les plus élevées sont enregistrés." "Enregistrer" "Les enregistrements ponctuels ont la plus haute priorité." - "Annuler" - "Annuler" - "Supprimer" "Arrêter" "Voir planning d\'enregistrement" "Ce programme uniquement" @@ -270,25 +260,28 @@ "Enregistrer ce programme" "Annuler cet enregistrement" "Regarder" + "Supprimer des enregistrements" "Enregistrable" "Enregistrement programmé" "Conflit d\'enregistrement" "Enregistrement…" "Échec de l\'enregistrement" - "Lecture des programmes pour créer des plannings d\'enregistrement…" - "Lecture des programmes…" - - + "Lecture des programmes…" + "Afficher les enregistrements récents" + "L\'enregistrement de \"%1$s\" est incomplet" + "Les enregistrements de \"%1$s\" et de \"%2$s\" sont incomplets" + "Les enregistrements de \"%1$s\", de \"%2$s\" et de \"%3$s\" sont incomplets" + "L\'enregistrement de \"%1$s\" n\'a pas été effectué en raison d\'un espace de stockage insuffisant." + "Les enregistrements de \"%1$s\" et de \"%2$s\" n\'ont pas été effectués en raison d\'un espace de stockage insuffisant." + "Les enregistrements de \"%1$s\", de \"%2$s\" et de \"%3$s\" n\'ont pas été effectués en raison d\'un espace de stockage insuffisant." "Le magnétoscope numérique a besoin de plus d\'espace" - "Vous pourrez enregistrer des programmes avec le magnétoscope numérique. Toutefois, l\'espace de stockage est insuffisant sur votre appareil pour que le magnétoscope numérique puisse fonctionner actuellement. Veuillez brancher un disque externe d\'au moins %1$s Go ou suivre les étapes pour le formater en tant qu\'espace de stockage de l\'appareil." + "Vous pourrez enregistrer des programmes avec le magnétoscope numérique. Toutefois, l\'espace de stockage est insuffisant sur votre appareil pour que le magnétoscope numérique puisse fonctionner actuellement. Veuillez brancher un disque externe d\'au moins %1$d Go ou suivre les étapes pour le formater en tant qu\'espace de stockage de l\'appareil." + "Espace de stockage insuffisant" + "Ce programme ne sera pas enregistré, car l\'espace de stockage disponible est insuffisant. Essayez de supprimer certains enregistrements." "Espace de stockage manquant" - "Une partie de l\'espace de stockage utilisé par le magnétoscope numérique est manquante. Veuillez connecter de nouveau le disque externe que vous avez utilisé auparavant afin de réactiver le magnétoscope numérique. Vous pouvez également supprimer cet espace de stockage s\'il n\'est plus disponible." - "Supprimer l\'espace de stockage ?" - "Tous vos contenus enregistrés et vos enregistrements planifiés seront perdus." "Arrêter l\'enregistrement ?" "Le contenu enregistré sera sauvegardé." - - + "L\'enregistrement de \"%1$s\" va être interrompu, car il crée un conflit avec ce programme. Le contenu enregistré sera conservé." "Enregistrement planifié, mais d\'autres enregistrements sont prévus en même temps." "L\'enregistrement a commencé, mais présente des conflits" "Le programme \"%1$s\" sera enregistré." @@ -306,17 +299,29 @@ "Vous avez déjà planifié l\'enregistrement de ce programme à %1$s." "Déjà enregistré" "Ce programme a déjà été enregistré. Il est disponible dans la bibliothèque DVR." - - - - - - - - + "Enregistrement de la série programmé" + + %1$d enregistrement a été programmé pour %2$s. + %1$d enregistrements ont été programmés pour %2$s. + + + %1$d enregistrement a été programmé pour %2$s. L\'enregistrement (%3$d) ne sera pas effectué en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d enregistrements ne seront pas effectués en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. %3$d épisodes de cette série et d\'une autre série ne seront pas enregistrés en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d épisodes de cette série et d\'une autre série ne seront pas enregistrés en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. 1 épisode d\'une autre série ne sera pas enregistré en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. 1 épisode d\'une autre série ne sera pas enregistré en raison de conflits. + + + %1$d enregistrement a été programmé pour %2$s. %3$d épisodes d\'une autre série ne seront pas enregistrés en raison de conflits. + %1$d enregistrements ont été programmés pour %2$s. %3$d épisodes d\'une autre série ne seront pas enregistrés en raison de conflits. + "Programme enregistré introuvable." "Enregistrements associés" - "(Programme sans description)" %1$d enregistrement %1$d enregistrements @@ -336,6 +341,7 @@ "Arrêter l\'enregistrement de la série ?" "Les épisodes enregistrés resteront disponibles dans la bibliothèque DVR." "Arrêter" + "Aucun épisode n\'est diffusé actuellement." "Aucun épisode disponible.\nLes épisodes seront enregistrés lorsqu\'ils seront disponibles." (%1$d minute) @@ -347,4 +353,5 @@ "%1$s aujourd\'hui" "%1$s demain" "Score" + "Programmes enregistrés" diff --git a/res/values-gl-rES-v23/strings.xml b/res/values-gl-rES-v23/strings.xml new file mode 100644 index 00000000..7cb59c66 --- /dev/null +++ b/res/values-gl-rES-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Canles" + diff --git a/res/values-gl-rES/arrays.xml b/res/values-gl-rES/arrays.xml new file mode 100644 index 00000000..0bbc3853 --- /dev/null +++ b/res/values-gl-rES/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Normal" + "Completa" + "Zoom" + + + "Todas as canles" + "Familiar/infantil" + "Deportes" + "Compras" + "Películas" + "Comedia" + "Viaxes" + "Drama" + "Educación" + "Animais/vida salvaxe" + "Noticias" + "Xogos" + "Arte" + "Entretemento" + "Estilo de vida" + "Música" + "Estrea" + "Ciencia e tecnoloxía" + + + "TV en directo" + "Unha forma sinxela de descubrir contido" + "Descarga aplicacións e obtén máis canles" + "Personaliza a túa lista de canles" + + + "Visualiza contido das túas aplicacións igual que nas canles da televisión." + "Examina o contido das túas aplicacións cunha guía familiar e unha interface intuitiva, \nigual que as canles da televisión." + "Engade máis canles ao instalar aplicacións que proporcionan canles en directo. \nUtiliza a ligazón do menú da televisión para buscar aplicacións compatibles en Google Play Store." + "Configura as fontes de canles instaladas recentemente para personalizar a túa lista de canles. \nPara comezar, selecciona Fontes de canles no menú Configuración." + + diff --git a/res/values-gl-rES/rating_system_strings.xml b/res/values-gl-rES/rating_system_strings.xml new file mode 100644 index 00000000..20456b34 --- /dev/null +++ b/res/values-gl-rES/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Os programas poden conter material inadecuado para os menores de 15 anos e, polo tanto, deberían quedar a discreción dos pais." + "Os programas poden conter material inadecuado para os menores de 19 anos e, polo tanto, non son adecuados para rapaces de menos de 19 anos." + "Diálogo insinuante" + "Linguaxe vulgar" + "Contido sexual" + "Violencia" + "Violencia ficticia" + "Este programa está deseñado para ser adecuado para todos os nenos." + "Este programa está deseñado para nenos de 7 anos ou máis." + "A maioría dos pais poden considerar que este programa é adecuado para todas as idades." + "Este programa contén material que os pais poden considerar inadecuado para os nenos pequenos. É posible que moitos pais queiran velo cos seus fillos." + "Este programa contén material que moitos pais poden considerar inadecuado para menores de 14 anos." + "Este programa está especificamente deseñado para adultos e, polo tanto, pode ser inadecuado para menores de 17 anos." + "Clasificacións de filmes" + "Público xeral. Todo o contido que poidan ver os nenos é do agrado dos pais." + "Recoméndase a supervisión parental. É posible que conteña material que aos pais non lles gustaría que visen os seus fillos pequenos." + "Advírtese encarecidamente aos pais. É posible que algún material non sexa adecuado para preadolescentes." + "Restrinxido. Contén algún material para adultos. Os pais deben obter máis información sobre a película antes de levar os seus fillos pequenos a vela." + "Non se admiten usuarios menores de 17 anos. Destinado claramente para adultos. Non se admiten nenos." + diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml new file mode 100644 index 00000000..1eea83a6 --- /dev/null +++ b/res/values-gl-rES/strings.xml @@ -0,0 +1,357 @@ + + + + + "mono" + "estéreo" + "Controis de reprodución" + "Canles" + "Opcións de TV" + "Os controis de reprodución non están dispoñibles nesta canle" + "Reprodución ou pausa" + "Avance rápido" + "Rebobinado" + "Seguinte" + "Anterior" + "Guía de programas" + "Novas canles dispoñib." + "Abrir %1$s" + "Subtítulos" + "Modo visualiz." + "PIP" + "Multiaudio" + "Obter máis canles" + "Configuración" + "TV (antena/cable)" + "Non hai información do programa" + "Non hai información" + "Canle bloqueada" + "Idioma descoñecido" + "Subtítulos en %1$d" + "Subtítulos" + "Desactivado" + "Personalizar formato" + "Definir pref. do sistema para subtítulos" + "Modo visualiz." + "Multiaudio" + "mono" + "estéreo" + "Surround 5.1" + "Surround 7.1" + "%d canles" + "Personalizar lista" + "Seleccionar grupo" + "Anular a selección" + "Agrupar por" + "Fonte de canles" + "Alta definición/definición estándar" + "Alta definición" + "Definición estándar" + "Agrupar por" + "Este programa está bloqueado" + "Este programa está sen clasificar" + "Este programa está clasificado como %1$s." + "A entrada non admite a busca automática" + "Non é posible iniciar a busca automática para \"%s\"" + "Non se poden iniciar as preferencias de todo o sistema para os subtítulos." + + Engadíronse %1$d canles + Engadiuse %1$d canle + + "Non se engadiron canles" + "Controis parentais" + "Activados" + "Desactivados" + "Canles bloqueadas" + "Bloquear todo" + "Desbloquear todo" + "Canles ocultas" + "Restricións prog." + "Cambiar PIN" + "Sistemas clasific." + "Valoracións" + "Ver sistemas clasif." + "Outros países" + "Ningunha" + "Ningunha" + "Sen clasificar" + "Bloquear progr. sen clasificar" + "Ningún" + "Restricións altas" + "Restricións medias" + "Restricións baixas" + "Personalizada" + "Contido adecuado para nenos" + "Contido adecuado para nenos máis vellos" + "Contido adecuado para adolescentes" + "Restricións manuais" + + + "%1$s e subclasific." + "Subclasificacións" + "Introduce o teu PIN para ver esta canle" + "Introduce o teu PIN para ver este programa" + "Este programa está clasificado como %1$s. Para velo, introduce o teu PIN" + "Este programa está sen clasificar. Para velo, introduce o PIN" + "Introduce o teu PIN" + "Para definir controis parentais, crea un PIN" + "Introduce o novo PIN" + "Confirma o teu PIN" + "Introduce o teu PIN actual" + + Introduciches 5 veces un PIN incorrecto.\nTéntao de novo en %1$d segundos. + Introduciches 5 veces un PIN incorrecto.\nTéntao de novo en %1$d segundo. + + "О PIN era incorrecto. Téntao de novo." + "Téntao de novo. O PIN non coincide." + "Introduce o teu código postal." + "A aplicación TV en directo usará o teu código postal para ofrecer unha guía de programas completa para as canles da televisión." + "Introduce o teu código postal" + "O código postal non é válido" + "Configuración" + "Personalizar lista de canles" + "Selecciona canles para a túa guía de programas" + "Fontes de canles" + "Novas canles dispoñibles" + "Controis parentais" + "Modo en diferido" + "Grava o contido mentres o ves para poder pausar ou rebobinar os programas en directo.\nAdvertencia: Con esta acción pode reducirse a duración do almacenamento interno debido ao seu uso intensivo." + "Licenzas de código aberto" + "Enviar comentarios" + "Versión" + "Para ver esta canle, preme na tecla cara á dereita e introduce o PIN" + "Para ver esta programa, preme na tecla cara á dereita e introduce o PIN" + "Este programa está sen clasificar.\nPara velo, preme na tecla cara á dereita e introduce o PIN" + "Este programa está clasificado como %1$s.\nPara ver esta programa, preme na tecla cara á dereita e introduce o PIN." + "Para ver esta canle, utiliza a aplicación predeterminada TV en directo." + "Para ver este programa, utiliza a aplicación predeterminada TV en directo." + "Este programa está sen clasificar.\nPara velo, utiliza a aplicación predeterminada de televisión en directo." + "Este programa está clasificado como %1$s.\nPara velo, utiliza a aplicación predeterminada TV en directo." + "O programa está bloqueado" + "Este programa está sen clasificar" + "Este programa está clasificado como %1$s." + "Só audio" + "Sinal feble" + "Non hai conexión a Internet" + + Esta canle non se pode reproducir ata as %1$s porque se están gravando outras canles. \n\nPreme na tecla cara á dereita para axustar o programa de gravación. + Esta canle non se pode reproducir ata as %1$s porque se está gravando outra canle. \n\nPreme na tecla cara á dereita para axustar o programa de gravación. + + "Sen título" + "Canle bloqueada" + "Novas" + "Fontes" + + %1$d canles + %1$d canle + + "Non hai canles dispoñibles" + "Novo" + "Sen configurar" + "Obter máis fontes" + "Examina aplicacións que ofrecen canles en directo" + "Novas fontes de canles dispoñibles" + "Hai novas fontes de canles que teñen canles para ofrecer.\nConfigúraas agora ou faino máis tarde nos axustes das fontes de canles." + "Configurar agora" + "De acordo" + + + "Preme SELECCIONAR"" para acceder ao menú da televisión." + "Non se atopou ningunha entrada de televisión" + "Non se pode atopar a entrada de televisión" + "Tipo de sintonizador non adecuado. Inicia a aplicación Canles en directo para a entrada de TV do tipo de sintonizador." + "Erro de sintonización" + "Non se encontrou ningunha aplicación para procesar esta acción." + "Todas as canles de orixe están ocultas.\nSelecciona polo menos unha canle para ver." + "O vídeo deixou de estar dispoñible de forma inesperada" + "A tecla Atrás aplícase ao dispositivo conectado. Preme o botón de inicio para saír." + "As canles en directo necesitan permiso para ler a programación da televisión." + "Configura as túas fontes" + "As canles en directo combinan a experiencia das canles de televisión tradicionais coas canles de emisión que proporcionan as aplicacións. \n\nComeza configurando as fontes de canles que xa están instaladas ou ben examina Google Play Store para obter máis aplicacións que ofrezan canles en directo." + "Gravacións e programacións" + "10 minutos" + "30 minutos" + "1 hora" + "3 horas" + "Recentes" + "Programados" + "Series" + "Outros" + "Non se pode gravar a canle." + "Non se pode gravar o programa." + "Programouse a gravación de %1$s" + "Gravarase %1$s desde agora ata as %2$s" + "Programación completa" + + %1$d días + %1$d día + + + %1$d minutos + %1$d minuto + + + %1$d gravacións novas + %1$d gravación nova + + + %1$d gravacións + %1$d gravación + + + %1$d gravacións programadas + %1$d gravación programada + + "Cancelar gravación" + "Deter gravación" + "Visualizar" + "Reproducir desde o inicio" + "Retomar reprodución" + "Eliminar" + "Eliminar gravacións" + "Retomar" + "Tempada %1$s" + "Ver programac." + "Máis información" + "Eliminar gravacións" + "Selecciona os vídeos que queres eliminar e ten en conta que non se poden recuperar unha vez eliminados." + "Non hai gravacións para eliminar." + "Seleccionar episodios vistos" + "Seleccionar todos os episodios" + "Desmarcar todos os episodios" + "%1$d de %2$d minutos vistos" + "%1$d de %2$d segundos vistos" + "Gravacións nunca vistas" + + Elimináronse %1$d de %2$d episodios + Eliminouse %1$d de %2$d episodio + + "Prioridade" + "Máis alta" + "Máis baixa" + "Non. %1$d" + "Canles" + "Calquera" + "Seleccionar prioridade" + "Cando hai demasiados programas para gravar á vez, só se gravarán os programas con máis prioridade." + "Gardar" + "As gravacións realizadas unha soa vez teñen máis prioridade" + "Deter" + "Ver programación de gravación" + "Só este programa" + "agora - %1$s" + "Toda a serie…" + "Programar de todas formas" + "Gravar isto no seu lugar" + "Cancelar esta gravación" + "Visualizar agora" + "Eliminar gravacións..." + "Gravable" + "Gravación programada" + "Conflito de gravación" + "Gravando" + "Erro na gravación" + "Lendo programas" + "Ver gravacións recentes" + "A gravación de %1$s non está completa." + "As gravacións de %1$s e %2$s non están completas." + "As gravacións de %1$s, %2$s e %3$s non están completas." + "A gravación de %1$s non se completou por falta de almacenamento suficiente." + "As gravacións de %1$s e %2$s non se completaron por falta de almacenamento suficiente." + "As gravacións de %1$s, %2$s e %3$s non se completaron por falta de almacenamento suficiente." + "O DVR precisa máis almacenamento" + "Poderás gravar programas con DVR. Non obstante, o teu dispositivo non ten almacenamento suficiente para que funcione DVR. Conecta unha unidade externa de %1$d GB ou máis e sigue os pasos para formatala como almacenamento do dispositivo." + "Non hai almacenamento suficiente" + "Este programa non se gravará porque non hai almacenamento suficiente. Proba a eliminar algunhas gravacións." + "Falta o almacenamento" + "Queres deter a gravación?" + "Gardarase o contido gravado." + "Deterase a gravación de %1$s porque supón un conflito con este programa. Gardarase o contido gravado." + "Programouse a gravación, pero presenta conflitos" + "Iniciouse a gravación pero presenta conflitos" + "Gravarase %1$s." + "Estase gravando %1$s." + "Non se gravarán algunhas partes de %1$s." + "Non se gravarán algunhas partes de %1$s e %2$s." + "Non se gravarán algunhas partes de %1$s, %2$s e unha programación máis." + + Non se gravarán algunhas partes de %1$s, %2$s e %3$d programacións máis. + Non se gravarán algunhas partes de %1$s, %2$s e %3$d programación máis. + + "Que che gustaría gravar?" + "Canto tempo queres gravar?" + "Xa se programou" + "Xa se programou a gravación do mesmo programa para a seguinte hora: %1$s." + "Xa está gravado" + "Este programa xa está gravado e está dispoñible na mediateca de DVR." + "Programouse a gravación da serie" + + Programáronse %1$d gravacións para %2$s. + Programouse %1$d gravación para %2$s. + + + Programáronse %1$d gravacións para %2$s. Non se gravarán %3$d delas por causa dun conflito. + Programouse %1$d gravación para %2$s. Non se gravará por causa dun conflito. + + + Programáronse %1$d gravacións para %2$s. Non se gravarán %3$d episodios destas series nin doutras por causa dun conflito. + Programouse %1$d gravación para %2$s. Non se gravarán %3$d episodios destas series nin doutras por causa dun conflito. + + + Programáronse %1$d gravacións para %2$s. Non se gravará 1 episodio doutras series por causa dun conflito. + Programouse %1$d gravación para %2$s. Non se gravará 1 episodio doutras series por causa dun conflito. + + + Programáronse %1$d gravacións para %2$s. Non se gravarán %3$d episodios doutras series por causa dun conflito. + Programouse %1$d gravación para %2$s. Non se gravarán %3$d episodios doutras series por causa dun conflito. + + "Non se atopou o programa gravado." + "Gravacións relacionadas" + + %1$d gravacións + %1$d gravación + + " / " + "Eliminouse %1$s da programación de gravación" + "Gravarase parcialmente debido aos conflitos co sintonizador." + "Non se gravará debido aos conflitos co sintonizador." + "Aínda non hai gravacións programadas.\nPodes programalas desde a guía de programas." + + %1$d conflitos de gravación + %1$d conflito de gravación + + "Configuración da serie" + "Iniciar gravación serie" + "Deter gravación da serie" + "Queres deter a gravación da serie?" + "Os episodios gravados seguirán dispoñibles na mediateca de DVR." + "Deter" + "Agora non hai episodios en directo." + "Non hai episodios dispoñibles.\nGravaranse unha vez que estean dispoñibles." + + (%1$d minutos) + (%1$d minuto) + + "Hoxe" + "Mañá" + "Onte" + "%1$s hoxe" + "%1$s mañá" + "Puntuación" + "Programas gravados" + diff --git a/res/values-gl-v23/strings.xml b/res/values-gl-v23/strings.xml deleted file mode 100644 index 7cb59c66..00000000 --- a/res/values-gl-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Canles" - diff --git a/res/values-gl/arrays.xml b/res/values-gl/arrays.xml deleted file mode 100644 index 0bbc3853..00000000 --- a/res/values-gl/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Normal" - "Completa" - "Zoom" - - - "Todas as canles" - "Familiar/infantil" - "Deportes" - "Compras" - "Películas" - "Comedia" - "Viaxes" - "Drama" - "Educación" - "Animais/vida salvaxe" - "Noticias" - "Xogos" - "Arte" - "Entretemento" - "Estilo de vida" - "Música" - "Estrea" - "Ciencia e tecnoloxía" - - - "TV en directo" - "Unha forma sinxela de descubrir contido" - "Descarga aplicacións e obtén máis canles" - "Personaliza a túa lista de canles" - - - "Visualiza contido das túas aplicacións igual que nas canles da televisión." - "Examina o contido das túas aplicacións cunha guía familiar e unha interface intuitiva, \nigual que as canles da televisión." - "Engade máis canles ao instalar aplicacións que proporcionan canles en directo. \nUtiliza a ligazón do menú da televisión para buscar aplicacións compatibles en Google Play Store." - "Configura as fontes de canles instaladas recentemente para personalizar a túa lista de canles. \nPara comezar, selecciona Fontes de canles no menú Configuración." - - diff --git a/res/values-gl/rating_system_strings.xml b/res/values-gl/rating_system_strings.xml deleted file mode 100644 index 20456b34..00000000 --- a/res/values-gl/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Os programas poden conter material inadecuado para os menores de 15 anos e, polo tanto, deberían quedar a discreción dos pais." - "Os programas poden conter material inadecuado para os menores de 19 anos e, polo tanto, non son adecuados para rapaces de menos de 19 anos." - "Diálogo insinuante" - "Linguaxe vulgar" - "Contido sexual" - "Violencia" - "Violencia ficticia" - "Este programa está deseñado para ser adecuado para todos os nenos." - "Este programa está deseñado para nenos de 7 anos ou máis." - "A maioría dos pais poden considerar que este programa é adecuado para todas as idades." - "Este programa contén material que os pais poden considerar inadecuado para os nenos pequenos. É posible que moitos pais queiran velo cos seus fillos." - "Este programa contén material que moitos pais poden considerar inadecuado para menores de 14 anos." - "Este programa está especificamente deseñado para adultos e, polo tanto, pode ser inadecuado para menores de 17 anos." - "Clasificacións de filmes" - "Público xeral. Todo o contido que poidan ver os nenos é do agrado dos pais." - "Recoméndase a supervisión parental. É posible que conteña material que aos pais non lles gustaría que visen os seus fillos pequenos." - "Advírtese encarecidamente aos pais. É posible que algún material non sexa adecuado para preadolescentes." - "Restrinxido. Contén algún material para adultos. Os pais deben obter máis información sobre a película antes de levar os seus fillos pequenos a vela." - "Non se admiten usuarios menores de 17 anos. Destinado claramente para adultos. Non se admiten nenos." - diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml deleted file mode 100644 index 7ce0ac2d..00000000 --- a/res/values-gl/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "mono" - "estéreo" - "Controis de reprodución" - "Canles recentes" - "Opcións de TV" - "Opcións de PIP" - "Os controis de reprodución non están dispoñibles nesta canle" - "Reprodución ou pausa" - "Avance rápido" - "Rebobinado" - "Seguinte" - "Anterior" - "Guía de programas" - "Novas canles dispoñib." - "Abrir %1$s" - "Subtítulos" - "Modo visualiz." - "PIP" - "Activado" - "Desactivado" - "Multiaudio" - "Obter máis canles" - "Configuración" - "Fonte" - "Cambiar" - "Activado" - "Desactivado" - "Son" - "Principal" - "Ventá de PIP" - "Deseño" - "Abaixo dereita" - "Arriba dereita" - "Arriba esquerda" - "Abaixo esquerda" - "En paralelo" - "Tamaño" - "Grande" - "Pequeno" - "Fonte de entrada" - "TV (antena/cable)" - "Non hai información do programa" - "Non hai información" - "Canle bloqueada" - "Idioma descoñecido" - "Subtítulos" - "Desactivado" - "Personalizar formato" - "Definir pref. do sistema para subtítulos" - "Modo visualiz." - "Multiaudio" - "mono" - "estéreo" - "Surround 5.1" - "Surround 7.1" - "%d canles" - "Personalizar lista" - "Seleccionar grupo" - "Anular a selección" - "Agrupar por" - "Fonte de canles" - "Alta definición/definición estándar" - "Alta definición" - "Definición estándar" - "Agrupar por" - "Este programa está bloqueado" - "Este programa está clasificado como %1$s." - "A entrada non admite a busca automática" - "Non é posible iniciar a busca automática para \"%s\"" - "Non se poden iniciar as preferencias de todo o sistema para os subtítulos." - - Engadíronse %1$d canles - Engadiuse %1$d canle - - "Non se engadiron canles" - "Sintonizador" - "Controis parentais" - "Activados" - "Desactivados" - "Canles bloqueadas" - "Bloquear todo" - "Desbloquear todo" - "Canles ocultas" - "Restricións prog." - "Cambiar PIN" - "Sistemas clasific." - "Valoracións" - "Ver sistemas clasif." - "Outros países" - "Ningunha" - "Ningunha" - "Ningún" - "Restricións altas" - "Restricións medias" - "Restricións baixas" - "Personalizada" - "Contido adecuado para nenos" - "Contido adecuado para nenos máis vellos" - "Contido adecuado para adolescentes" - "Restricións manuais" - - - "%1$s e subclasific." - "Subclasificacións" - "Introduce o teu PIN para ver esta canle" - "Introduce o teu PIN para ver este programa" - "Este programa está clasificado como %1$s. Para velo, introduce o teu PIN" - "Introduce o teu PIN" - "Para definir controis parentais, crea un PIN" - "Introduce o novo PIN" - "Confirma o teu PIN" - "Introduce o teu PIN actual" - - Introduciches 5 veces un PIN incorrecto.\nTéntao de novo en %1$d segundos. - Introduciches 5 veces un PIN incorrecto.\nTéntao de novo en %1$d segundo. - - "О PIN era incorrecto. Téntao de novo." - "Téntao de novo. O PIN non coincide." - "Configuración" - "Personalizar lista de canles" - "Selecciona canles para a túa guía de programas" - "Fontes de canles" - "Novas canles dispoñibles" - "Controis parentais" - "Licenzas de código aberto" - "Licenzas de código aberto" - "Versión" - "Para ver esta canle, preme na tecla cara á dereita e introduce o PIN" - "Para ver esta programa, preme na tecla cara á dereita e introduce o PIN" - "Este programa está clasificado como %1$s.\nPara ver esta programa, preme na tecla cara á dereita e introduce o PIN." - "Para ver esta canle, utiliza a aplicación predeterminada TV en directo." - "Para ver este programa, utiliza a aplicación predeterminada TV en directo." - "Este programa está clasificado como %1$s.\nPara velo, utiliza a aplicación predeterminada TV en directo." - "O programa está bloqueado" - "Este programa está clasificado como %1$s." - "Só audio" - "Sinal feble" - "Non hai conexión a Internet" - - Esta canle non se pode reproducir ata as %1$s porque se están gravando outras canles. \n\nPreme na tecla cara á dereita para axustar o programa de gravación. - Esta canle non se pode reproducir ata as %1$s porque se está gravando outra canle. \n\nPreme na tecla cara á dereita para axustar o programa de gravación. - - "Sen título" - "Canle bloqueada" - "Novas" - "Fontes" - - %1$d canles - %1$d canle - - "Non hai canles dispoñibles" - "Novo" - "Sen configurar" - "Obter máis fontes" - "Examina aplicacións que ofrecen canles en directo" - "Novas fontes de canles dispoñibles" - "Hai novas fontes de canles que teñen canles para ofrecer.\nConfigúraas agora ou faino máis tarde nos axustes das fontes de canles." - "Configurar agora" - "De acordo" - - - "Preme SELECCIONAR"" para acceder ao menú da televisión." - "Non se atopou ningunha entrada de televisión" - "Non se pode atopar a entrada de televisión" - "PIP non é compatible" - "Non hai ningunha entrada que se poida mostrar con PIP" - "Tipo de sintonizador non adecuado. Inicia a aplicación Canles en directo para a entrada de TV do tipo de sintonizador." - "Erro de sintonización" - "Non se encontrou ningunha aplicación para procesar esta acción." - "Todas as canles de orixe están ocultas.\nSelecciona polo menos unha canle para ver." - "O vídeo deixou de estar dispoñible de forma inesperada" - "A tecla Atrás aplícase ao dispositivo conectado. Preme o botón de inicio para saír." - "As canles en directo necesitan permiso para ler a programación da televisión." - "Configura as túas fontes" - "As canles en directo combinan a experiencia das canles de televisión tradicionais coas canles de emisión que proporcionan as aplicacións. \n\nComeza configurando as fontes de canles que xa están instaladas ou ben examina Google Play Store para obter máis aplicacións que ofrezan canles en directo." - "Gravacións e programacións" - "10 minutos" - "30 minutos" - "1 hora" - "3 horas" - "Recentes" - "Programados" - "Series" - "Outros" - "Non se pode gravar a canle." - "Non se pode gravar o programa." - "Programouse a gravación de %1$s" - "Gravarase %1$s desde agora ata as %2$s" - "Programación completa" - - %1$d días - %1$d día - - - %1$d minutos - %1$d minuto - - - %1$d gravacións novas - %1$d gravación nova - - - %1$d gravacións - %1$d gravación - - - %1$d gravacións programadas - %1$d gravación programada - - "Visualizar" - "Reproducir desde o inicio" - "Retomar reprodución" - "Eliminar" - "Eliminar gravacións" - "Retomar" - "Tempada %1$s" - "Ver programac." - "Máis información" - "Eliminar gravacións" - "Selecciona os vídeos que queres eliminar e ten en conta que non se poden recuperar unha vez eliminados." - "Non hai gravacións para eliminar." - "Seleccionar episodios vistos" - "Seleccionar todos os episodios" - "Desmarcar todos os episodios" - "%1$d de %2$d minutos vistos" - "%1$d de %2$d segundos vistos" - "Gravacións nunca vistas" - - Elimináronse %1$d de %2$d episodios - Eliminouse %1$d de %2$d episodio - - "Prioridade" - "Máis alta" - "Máis baixa" - "Non. %1$d" - "Canles" - "Calquera" - "Seleccionar prioridade" - "Cando hai demasiados programas para gravar á vez, só se gravarán os programas con máis prioridade." - "Gardar" - "As gravacións realizadas unha soa vez teñen máis prioridade" - "Cancelar" - "Cancelar" - "Borrar" - "Deter" - "Ver programación de gravación" - "Só este programa" - "agora - %1$s" - "Toda a serie…" - "Programar de todas formas" - "Gravar isto no seu lugar" - "Cancelar esta gravación" - "Visualizar agora" - "Gravable" - "Gravación programada" - "Conflito de gravación" - "Gravando" - "Erro na gravación" - "Lendo programas para crear programacións de gravacións" - "Lendo programas" - - - "O DVR precisa máis almacenamento" - "Poderás gravar programas con DVR. Non obstante, o teu dispositivo non ten almacenamento suficiente para que funcione DVR. Conecta unha unidade externa de %1$s GB ou máis e sigue os pasos para formatala como almacenamento do dispositivo." - "Falta o almacenamento" - "Falta parte do almacenamento que utiliza DVR. Conecta a unidade externa que utilizaches antes para volver activar DVR. Tamén podes decidir borrar o almacenamento se xa non está dispoñible." - "Queres borrar o almacenamento?" - "Perderase toda a programación e o contido gravados." - "Queres deter a gravación?" - "Gardarase o contido gravado." - - - "Programouse a gravación, pero presenta conflitos" - "Iniciouse a gravación pero presenta conflitos" - "Gravarase %1$s." - "Estase gravando %1$s." - "Non se gravarán algunhas partes de %1$s." - "Non se gravarán algunhas partes de %1$s e %2$s." - "Non se gravarán algunhas partes de %1$s, %2$s e unha programación máis." - - Non se gravarán algunhas partes de %1$s, %2$s e %3$d programacións máis. - Non se gravarán algunhas partes de %1$s, %2$s e %3$d programación máis. - - "Que che gustaría gravar?" - "Canto tempo queres gravar?" - "Xa se programou" - "Xa se programou a gravación do mesmo programa para a seguinte hora: %1$s." - "Xa está gravado" - "Este programa xa está gravado e está dispoñible na mediateca de DVR." - - - - - - - - - "Non se atopou o programa gravado." - "Gravacións relacionadas" - "(Non hai descrición do programa)" - - %1$d gravacións - %1$d gravación - - " / " - "Eliminouse %1$s da programación de gravación" - "Gravarase parcialmente debido aos conflitos co sintonizador." - "Non se gravará debido aos conflitos co sintonizador." - "Aínda non hai gravacións programadas.\nPodes programalas desde a guía de programas." - - %1$d conflitos de gravación - %1$d conflito de gravación - - "Configuración da serie" - "Iniciar gravación serie" - "Deter gravación da serie" - "Queres deter a gravación da serie?" - "Os episodios gravados seguirán dispoñibles na mediateca de DVR." - "Deter" - "Non hai episodios dispoñibles.\nGravaranse unha vez que estean dispoñibles." - - (%1$d minutos) - (%1$d minuto) - - "Hoxe" - "Mañá" - "Onte" - "%1$s hoxe" - "%1$s mañá" - "Puntuación" - diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml new file mode 100644 index 00000000..d635088f --- /dev/null +++ b/res/values-gu-rIN/strings.xml @@ -0,0 +1,356 @@ + + + + + "મૉનો" + "સ્ટીરિઓ" + "ચલાવવાનાં નિયંત્રણો" + "ચૅનલો" + "TV વિકલ્પો" + "આ ચૅનલો માટે ચલાવવાનાં નિયંત્રણો ઉપલબ્ધ નથી" + "ચલાવો અથવા થોભાવો" + "ઝડપી ફોરવર્ડ કરો" + "રીવાઇન્ડ કરો" + "આગલું" + "પહેલાંનું" + "પ્રોગ્રામ માર્ગદર્શિકા" + "નવી ચૅનલો ઉપલબ્ધ છે" + "%1$s ખોલો" + "ઉપશીર્ષક" + "પ્રદર્શન મોડ" + "PIP" + "બહુ-ઑડિઓ" + "વધુ ચૅનલો મેળવો" + "સેટિંગ્સ" + "TV (એન્ટેના/કેબલ)" + "કોઈ પ્રોગ્રામ માહિતી નથી" + "કોઈ માહિતી નથી" + "અવરોધિત કરેલ ચૅનલ" + "અજાણ ભાષા" + "ઉપશીર્ષક %1$d" + "ઉપશીર્ષક" + "બંધ" + "કસ્ટમાઇઝ ફૉર્મેટિંગ" + "ઉપશીર્ષક માટે સિસ્ટમ-વ્યાપી પસંદગીઓ સેટ કરો" + "પ્રદર્શન મોડ" + "બહુ-ઑડિઓ" + "મૉનો" + "સ્ટીરિઓ" + "5.1 સરાઉન્ડ" + "7.1 સરાઉન્ડ" + "%d ચૅનલ" + "ચૅનલની સૂચિ કસ્ટમાઇઝ કરો" + "જૂથ પસંદ કરો" + "જૂથની પસંદગી દૂર કરો" + "આ દ્વારા જૂથબદ્ધ કરો" + "ચૅનલ સ્રોત" + "HD/SD" + "HD" + "SD" + "આ દ્વારા જૂથબદ્ધ કરો" + "આ પ્રોગ્રામ અવરોધિત કરેલ છે" + "આ પ્રોગ્રામ અનરેટેડ છે" + "આ પ્રોગ્રામને %1$s રેટ કરેલ છે" + "ઇનપુટ સ્વતઃ સ્કૅનને સમર્થન આપતું નથી" + "\'%s\' માટે સ્વતઃ સ્કૅન શરૂ કરવામાં અસમર્થ" + "ઉપશીર્ષક માટે સિસ્ટમ-વ્યાપી પસંદગીઓ શરૂ કરવામાં અસમર્થ." + + %1$d ચૅનલ ઉમેરી + %1$d ચૅનલ ઉમેરી + + "કોઈ ચૅનલો ઉમેરી નથી" + "માતા પિતા યોગ્ય નિયંત્રણો" + "ચાલુ" + "બંધ" + "અવરોધિત કરેલ ચૅનલો" + "તમામને અવરોધિત કરો" + "તમામને અનાવરોધિત કરો" + "છુપાયેલ ચૅનલો" + "પ્રોગ્રામ પ્રતિબંધો" + "PIN બદલો" + "રેટિંગ સિસ્ટમ" + "રેટિંગ" + "તમામ રેટિંંગ સિસ્ટમ જુઓ" + "અન્ય દેશો" + "કોઈ નહીં" + "કોઈ નહીં" + "અનરેટેડ" + "અનરેટેડ પ્રોગ્રામ બ્લૉક કરો" + "કોઈ નહીં" + "વધુ પ્રતિબંધો" + "મધ્યમ પ્રતિબંધો" + "ઓછા પ્રતિબંધો" + "કસ્ટમ" + "સામગ્રી બાળકો માટે યોગ્ય છે" + "સામગ્રી મોટા બાળકો માટે યોગ્ય છે" + "સામગ્રી કિશોરો માટે યોગ્ય છે" + "મેન્યુઅલ પ્રતિબંધો" + "વર્ણનોને રેટ કરવા માટે સ્રોતો" + "%1$s અને પેટા-રેટિંગ" + "પેટા-રેટિંગ" + "આ ચૅનલ જોવા માટે તમારો PIN દાખલ કરો" + "આ પ્રોગ્રામ જોવા માટે તમારો PIN દાખલ કરો" + "આ પ્રોગ્રામ %1$s રેટ કરેલ છે. આ પ્રોગ્રામ જોવા માટે તમારો PIN દાખલ કરો" + "આ પ્રોગ્રામ અનરેટેડ છે. આ પ્રોગ્રામ જોવા માટે તમારો પિન દાખલ કરો" + "તમારો PIN દાખલ કરો" + "માતા પિતા યોગ્ય નિયંત્રણો સેટ કરવા માટે, એક PIN બનાવો" + "નવો PIN દાખલ કરો" + "તમારા PIN ની પુષ્ટિ કરો" + "તમારો વર્તમાન PIN દાખલ કરો" + + તમે 5 વખત ખોટો PIN દાખલ કર્યો.\n%1$d સેકન્ડમાંં ફરી પ્રયાસ કરો. + તમે 5 વખત ખોટો PIN દાખલ કર્યો.\n%1$d સેકન્ડમાંં ફરી પ્રયાસ કરો. + + "તે PIN ખોટો હતો. ફરી પ્રયાસ કરો." + "ફરીથી પ્રયાસ કરો, PIN મેળ ખાતો નથી" + "તમારો પિન કોડ દાખલ કરો." + "TV ચૅનલો માટે એક સંપૂર્ણ પ્રોગ્રામ માર્ગદર્શિકા પ્રદાન કરવા માટે લાઇવ ચૅનલ ઍપ્લિકેશન પિન કોડનો ઉપયોગ કરશે." + "તમારો પિન કોડ દાખલ કરો" + "અમાન્ય પિન કોડ" + "સેટિંગ્સ" + "ચૅનલની સૂચિ કસ્ટમાઇઝ કરો" + "તમારી પ્રોગ્રામ માર્ગદર્શિકા માટે ચૅનલો પસંદ કરો" + "ચૅનલ સ્રોત" + "નવી ચૅનલો ઉપલબ્ધ છે" + "માતા પિતા યોગ્ય નિયંત્રણો" + "સમય અંતરાલ" + "જોતી વખતે રેકોર્ડ કરો, જેથી તમે લાઇવ પ્રોગ્રામને થોભાવી અથવા રીવાઇન્ડ કરી શકો છો.\nચેતવણી: સ્ટૉરેજનાં સઘન ઉપયોગને કારણે આ આંતરિક સ્ટૉરેજની આવરદાને ઘટાડી શકે છે." + "ખુલ્લા સ્ત્રોત લાઇસન્સ" + "પ્રતિસાદ મોકલો" + "સંસ્કરણ" + "આ ચૅનલ જોવા માટે, જમણે દબાવો અને તમારો PIN દાખલ કરો." + "આ પ્રોગ્રામ જોવા માટે, જમણે દબાવો અને તમારો PIN દાખલ કરો." + "આ પ્રોગ્રામ અનરેટેડ છે.\nઆ પ્રોગ્રામ જોવા માટે, જમણે દબાવો અને તમારો પિન દાખલ કરો" + "આ પ્રોગ્રામને %1$s રેેેટ કરેલ છે.\nઆ પ્રોગ્રામ જોવા માટે, જમણે દબાવો અને તમારો PIN દાખલ કરો." + "આ ચૅનલ જોવા માટે, ડિફૉલ્ટ લાઇવ TV ઍપ્લિકેશનનો ઉપયોગ કરો." + "આ પ્રોગ્રામ જોવા માટે, ડિફૉલ્ટ લાઇવ TV ઍપ્લિકેશનનો ઉપયોગ કરો." + "આ પ્રોગ્રામ અનરેટેડ છે.\nઆ પ્રોગ્રામ જોવા માટે, ડિફૉલ્ટ લાઇવ TV ઍપનો ઉપયોગ કરો." + "આ પ્રોગ્રામને %1$s રેેેટ કરેલ છે.\nઆ પ્રોગ્રામ જોવા માટે, ડિફૉલ્ટ લાઇવ TV ઍપ્લિકેશનનો ઉપયોગ કરો." + "પ્રોગ્રામ અવરોધિત કરેલ છે" + "આ પ્રોગ્રામ અનરેટેડ છે" + "આ પ્રોગ્રામ %1$s રેટ કરેલ છે" + "ફક્ત ઑડિઓ" + "નબળા સંકેત" + "કોઈ ઇન્ટરનેટ કનેક્શન નથી" + + આ ચૅનલ %1$s સુધી ચલાવી શકાશે નહીં કારણ કે અન્ય ચૅનલો રેકોર્ડ થઈ રહી છે. \n\nરેકોર્ડિંગ શેડ્યૂલ ગોઠવવા માટે જમણે દબાવો. + આ ચૅનલ %1$s સુધી ચલાવી શકાશે નહીં કારણ કે અન્ય ચૅનલો રેકોર્ડ થઈ રહી છે. \n\nરેકોર્ડિંગ શેડ્યૂલ ગોઠવવા માટે જમણે દબાવો. + + "કોઈ શીર્ષક નથી" + "ચૅનલ અવરોધિત કરી" + "નવું" + "સ્રોતો" + + %1$d ચૅનલ + %1$d ચૅનલ + + "કોઈ ચૅનલો ઉપલબ્ધ નથી" + "નવું" + "સેટ કરેલ નથી" + "વધુ સ્રોત મેળવો" + "લાઇવ ચૅનલો ઑફર કરતી ઍપ્લિકેશનો બ્રાઉઝ કરો" + "નવી ચૅનલનાં સ્રોત ઉપલબ્ધ છે" + "નવા ચૅનલ સ્રોત પાસે ઑફર કરવા માટે ચૅનલો છે.\nતેમને હમણાં જ સેટ કરો અથવા ચૅનલ સ્રોત સેટિંગમાં પછીથી આ કરી શકો છો." + "હવે સેટ કરો" + "બરાબર, સમજાઇ ગયું" + + + "TV મેનૂને ઍક્સેસ કરવા માટે ""પસંદ કરો દબાવો""." + "કોઈ TV ઇનપુટ મળ્યું નથી" + "TV ઇનપુટ શોધી શકાતું નથી" + "ટ્યૂનર પ્રકાર યોગ્ય નથી. કૃપા કરીને ટ્યૂનર પ્રકાર TV ઇનપુટ માટે લાઇવ ચૅનલો લોંચ કરો." + "ટ્યૂન કરવાનું નિષ્ફળ થયું" + "આ ક્રિયાને હેન્ડલ કરવા માટે કોઈ ઍપ્લિકેશન મળી નહીં." + "તમામ સ્રોત ચૅનલો છુપાયેલા છે.\nજોવા માટે ઓછામાં ઓછી એક ચૅનલ પસંદ કરો." + "વિડિઓ અનપેક્ષિત રીતે અનુપલબ્ધ છે" + "પાછળ કી કનેક્ટ કરેલ ઉપકરણ માટે છે. બહાર નીકળવા માટે હોમ બટન દબાવો." + "TVની સૂચિ વાંચવા માટે લાઇવ ચૅનલોને પરવાનગીની જરૂર છે." + "તમારા સ્રોત સેટઅપ કરો" + "લાઇવ ચૅનલો પરંપરાગત TV ચૅનલોનાં અનુભવને ઍપ્લિકેશનો દ્વારા પ્રદાન કરેક સ્ટ્રીમિંગ ચૅનલો સાથે ભેગો કરે છે. \n\nપહેલેથી ઇન્સ્ટૉલ કરેલ હોય તે ચૅનલ સ્રોતોને સેટ કરીને પ્રારંભ કરો. અથવા Google Play સ્ટોર બ્રાઉઝ કરો." + "રેકોર્ડિંગ અને શેડ્યૂલ" + "10 મિનિટ" + "30 મિનિટ" + "1 કલાક" + "3 કલાક" + "તાજેતરના" + "શેડ્યૂલ કરેલ" + "શ્રૃંખલા" + "અન્ય" + "ચૅનલ રેકોર્ડ કરી શકાતી નથી." + "પ્રોગ્રામ રેકોર્ડ કરી શકાતો નથી." + "%1$s રેકોર્ડ થવા માટે શેડ્યૂલ કરવામાં આવ્યો છે" + "હવેથી %2$s સુધી %1$s રેકોર્ડ કરાશે" + "સંપૂર્ણ શેડ્યૂલ" + + આગલાં %1$d દિવસ + આગલાં %1$d દિવસ + + + %1$d મિનિટ + %1$d મિનિટ + + + %1$d નવા રેકોર્ડિંગ + %1$d નવા રેકોર્ડિંગ + + + %1$d રેકોર્ડિંગ + %1$d રેકોર્ડિંગ + + + %1$d રેકોર્ડિંગ શેડ્યૂલ કર્યા + %1$d રેકોર્ડિંગ શેડ્યૂલ કર્યા + + "રેકોર્ડિંગ રદ કરો" + "રેકોર્ડિંગ રોકો" + "ઘડિયાળ" + "શરૂઆતથી ચલાવો" + "ચલાવવાનું ફરી શરૂ કરો" + "કાઢી નાખો" + "રેકોર્ડિંગ કાઢી નાખો" + "ફરી શરૂ કરો" + "સિઝન %1$s" + "શેડ્યૂલ જુઓ" + "વધુ વાંચો" + "રેકોર્ડિંગ કાઢી નાખો" + "તમે કાઢી નાખવા માગતા હોય તે એપિસોડ પસંદ કરો. એક વાર કાઢી નાખ્યા પછી તે પુનઃપ્રાપ્ત કરી શકાશે નહીં." + "કાઢી શકાય તેવું કોઈ રેકોર્ડિંગ નથી." + "જોયેલા એપિસોડ પસંદ કરો" + "તમામ એપિસોડ પસંદ કરો" + "તમામા એપિસોડની પસંદગી દૂર કરો" + "%2$d માંથી %1$d મિનિટની રેકોર્ડિંગ જોઈ" + "%2$d માંથી %1$d સેકન્ડની રેકોર્ડિંગ જોઈ" + "ક્યારેય જોઈ નથી" + + %2$dમાંથી %1$d એપિસોડ કાઢી નાખ્યાં + %2$dમાંથી %1$d એપિસોડ કાઢી નાખ્યાં + + "પ્રાધાન્યતા" + "સૌથી ઉચ્ચ" + "ન્યૂનતમ" + "નં. %1$d" + "ચૅનલો" + "કોઈપણ" + "પ્રાધાન્યતા પસંદ કરો" + "જ્યારે એક જ સમયે રેકોર્ડ કરવા માટે ઘણાં બધા પ્રોગ્રામ હોય છે, ત્યારે વધુ પ્રાધાન્યતાવાળા પ્રોગ્રામ જ રેકોર્ડ કરાશે." + "સાચવો" + "એક-વારની રેકોર્ડિંગની સૌથી વધુ પ્રાધાન્યતા હોય છે" + "રોકો" + "રેકોર્ડિંગ શેડ્યૂલ જુઓ" + "આ એકલ પ્રોગ્રામ" + "હવેથી - %1$s" + "સમગ્ર શ્રૃંખલા…" + "કોઈપણ રીતે શેડ્યૂલ કરો" + "તેને બદલે આ રેકોર્ડ કરો" + "આ રેકોર્ડિંગ રદ કરો" + "હમણાં જુઓ" + "રેકોર્ડિંગ કાઢી નાખો…" + "રેકોર્ડ કરવા યોગ્ય" + "રેકોર્ડિંગ શેડ્યૂલ કર્યું" + "રેકોર્ડિંગ વિરોધ" + "રેકોર્ડ કરી રહ્યું છે" + "રેકોર્ડ કરવાનું નિષ્ફળ થયું" + "પ્રોગ્રામની માહિતી વાંચી રહી છે" + "તાજેતરનું રેકોર્ડિંગ જુઓ" + "%1$sનું રેકોર્ડિંગ અધૂરું છે." + "%1$s અને %2$sનું રેકોર્ડિંગ અધૂરું છે." + "%1$s, %2$s અને %3$sનું રેકોર્ડિંગ અધૂરું છે." + "અપર્યાપ્ત સ્ટોરેજને કારણે %1$sનું રેકોર્ડિંગ પૂર્ણ થયું નથી." + "અપર્યાપ્ત સ્ટોરેજને કારણે %1$s અને %2$sનું રેકોર્ડિંગ પૂર્ણ થયું નથી." + "અપર્યાપ્ત સ્ટોરેજને કારણે %1$s, %2$s અને %3$sનું રેકોર્ડિંગ પૂર્ણ થયું નથી." + "DVRને વધુ સ્ટોરેજની જરૂર છે" + "તમે DVR વડે પ્રોગ્રામ રેકોર્ડ કરી શકશો. જોકે, હમણાં તમારા ઉપકરણ પર DVR કાર્ય કરી શકે તે માટે પર્યાપ્ત સ્ટોરેજ નથી. કૃપા કરીને %1$dGB અથવા તેનાથી મોટી બાહ્યાં ડ્રાઇવ કનેક્ટ કરો અને તેને ઉપકરણ સ્ટોરેજ તરીકે ફૉર્મેટ કરવા માટે પગલાં અનુસરો." + "પર્યાપ્ત સ્ટોરેજ ઉપલબ્ધ નથી" + "પર્યાપ્ત સ્ટોરેજ ન હોવાને કારણે આ પ્રોગ્રામ રેકોર્ડ કરાશે નહીં. કેટલીક અસ્તિત્વમાંની રેકોર્ડિંગને કાઢી નાખીને ફરી પ્રયાસ કરો." + "સ્ટોરેજ ખૂટે છે" + "રેકોર્ડિંગ રોકીએ?" + "રેકોર્ડ કરેલ સામગ્રી સાચવવામાં આવશે." + "%1$sનું રેકોર્ડિંગ બંધ થશે કારણ કે તે આ પ્રોગ્રામ સાથે વિરોધાભાસી છે. રેકોર્ડ કરેલ સામગ્રી સાચવવામાં આવશે." + "રેકોર્ડિંગ શેડ્યૂલ કરી પણ તે વિરોધાભાસી છે" + "રેકોર્ડિંગ શરૂ થઈ ગઈ છે પરંતુ તે વિરોધાભાસી છે" + "%1$s રેકોર્ડ કરવામાં આવશે." + "%1$s રેકોર્ડ થઈ રહી છે." + "%1$sનાં કેટલાક ભાગો રેકોર્ડ કરવામાં આવશે નહીં." + "%1$s અને %2$sનાં કેટલાક ભાગો રેકોર્ડ કરવામાં આવશે નહીં." + "%1$s, %2$s અને વધુ એક શેડ્યૂલનાં કેટલાક ભાગો રેકોર્ડ કરવામાં આવશે નહીં." + + %1$s, %2$s અને વધુ %3$d શેડ્યૂલનાં ભાગો રેકોર્ડ કરવામાં આવશે નહીં. + %1$s, %2$s અને વધુ %3$d શેડ્યૂલનાં ભાગો રેકોર્ડ કરવામાં આવશે નહીં. + + "તમે શું રેકોર્ડ કરવા માગો છો?" + "તમે કેટલી વાર સુધી રેકોર્ડ કરવા માગો છો?" + "પહેલેથી શેડ્યૂલ કરેલ છે" + "આ જ પ્રોગ્રામ %1$s વાગ્યે રેકોર્ડ કરવા માટે શેડ્યૂલ કરેલ છે." + "પહેલેથી રેકોર્ડ કરેલ છે" + "આ પ્રોગ્રામ પહેલેથી રેકોર્ડ કરેલો છે. તે DVR લાઇબ્રેરીમાં ઉપલબ્ધ છે." + "શ્રૃંખલા રેકોર્ડિંગ શેડ્યૂલ કરેલ છે" + + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. + + + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે તેમાંથી %3$dને રેકોર્ડ કરવામાં આવશે નહીં. + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે તેમાંથી %3$dને રેકોર્ડ કરવામાં આવશે નહીં. + + + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે આ શ્રૃંખલા અને અન્ય શ્રૃંખલાનાં %3$d એપિસોડ રેકોર્ડ કરવામાં આવશે નહીં. + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે આ શ્રૃંખલા અને અન્ય શ્રૃંખલાનાં %3$d એપિસોડ રેકોર્ડ કરવામાં આવશે નહીં. + + + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે અન્ય શ્રૃંખલાનો 1 એપિસોડ રેકોર્ડ કરાશે નહીં. + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે અન્ય શ્રૃંખલાનો 1 એપિસોડ રેકોર્ડ કરાશે નહીં. + + + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે અન્ય શ્રૃંખલાનાં %3$d એપિસોડ રેકોર્ડ કરાશે નહીં. + %1$d રેકોર્ડિંગ %2$s માટે શેડ્યૂલ કરવામાં આવ્યાં છે. વિરોધને કારણે અન્ય શ્રૃંખલાનાં %3$d એપિસોડ રેકોર્ડ કરાશે નહીં. + + "રેકોર્ડ કરેલ પ્રોગ્રામ મળ્યોં નથી." + "સંબંધિત રેકોર્ડિંગ" + + %1$d રેકોર્ડિંગ + %1$d રેકોર્ડિંગ + + " / " + "%1$sને રેકોર્ડિંગ શેડ્યૂલથી દૂર કરાયો" + "ટ્યૂનર વિરોધને કારણે તે આંશિક રીતે રોકોર્ડ કરાશે." + "ટ્યૂનર વિરોધને કારણે તે રોકોર્ડ કરાશે નહીં." + "હજી સુધી શેડ્યૂલ કરેલ કોઈ રેકોર્ડિંગ નથી.\nતમે પ્રોગ્રામ માર્ગદર્શિકાથી રેકોર્ડિંગને શેડ્યૂલ કરી શકો છો." + + %1$d રેકોર્ડિંગ વિરોધ + %1$d રેકોર્ડિંગ વિરોધ + + "શ્રૃંખલા સેટિંગ્સ" + "શ્રૃંખલા રેકોર્ડ કરવાનું શરૂ કરો" + "શ્રૃંખલા રેકોર્ડ કરવાનું રોકો" + "શ્રૃંખલા રેકોર્ડ કરવાનું રોકીએ?" + "રેકોર્ડ કરેલા એપિસોડ DVR લાઇબ્રેરીમાં ઉપલબ્ધ રહેશે." + "રોકો" + "હમણાં કોઈ એપિસોડ પ્રસારિત થઈ રહ્યાં નથી." + "કોઈ એપિસોડ ઉપલબ્ધ નથી.\nતે ઉપલબ્ધ હશે ત્યારે રેકોર્ડ કરવામાં આવશે." + + (%1$d મિનિટ) + (%1$d મિનિટ) + + "આજે" + "આવતીકાલે" + "ગઈકાલે" + "%1$s આજે" + "%1$s આવતી કાલે" + "સ્કોર" + "રેકોર્ડ કરેલ પ્રોગ્રામ" + diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index d6db8677..146bfa06 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -20,9 +20,8 @@ "मोनो" "स्टीरियो" "चलाने के नियंत्रण" - "हाल ही के चैनल" + "चैनल" "टीवी विकल्प" - "PIP विकल्‍प" "इस चैनल के लिए चलाने के नियंत्रण अनुपलब्‍ध हैं" "चलाएं या रोकें" "फ़ास्ट फ़ॉरवर्ड करें" @@ -35,33 +34,15 @@ "उपशीर्षक" "प्रदर्शन मोड" "PIP" - "चालू" - "बंद" "एकाधिक-ऑडियो" "अधिक चैनल पाएं" "सेटिंग" - "स्रोत" - "स्वैप करें" - "चालू" - "बंद" - "ध्वनि" - "मुख्‍य" - "PIP विंडो" - "लेआउट" - "नीचे दाएं" - "ऊपर दाएं" - "ऊपर बाएं" - "नीचे बाएं" - "साथ-साथ" - "आकार" - "बड़ा" - "छोटा" - "इनपुट स्रोत" "टीवी (एंटिना/केबल)" "कोई कार्यक्रम जानकारी नहीं" "कोई सूचना नहीं" "अवरोधित चैनल" - "अज्ञात भाषा" + "अज्ञात भाषा" + "बंद कैप्शन %1$d" "उपशीर्षक" "बंद" "प्रारूपण कस्टमाइज़ करें" @@ -83,6 +64,7 @@ "SD" "इसके द्वारा समूहीकृत" "यह कार्यक्रम अवरोधित है" + "इस कार्यक्रम को रेट नहीं किया गया है" "इस कार्यक्रम को %1$s रेट किया गया है" "इनपुट ऑटो-स्कैन का समर्थन नहीं करता है" "\'%s\' के लिए स्वतः स्कैन प्रारंभ करने में असमर्थ" @@ -92,7 +74,6 @@ %1$d चैनल जोड़े गए "कोई चैनल नहीं जोड़ा गया" - "ट्यूनर" "अभिभावकीय नियंत्रण" "चालू" "बंद" @@ -108,6 +89,8 @@ "अन्य देश" "कोई नहीं" "कोई नहीं" + "रेट नहीं किया गया" + "रेट नहीं किए गए कार्यक्रम ब्लॉक करें" "कोई नहीं" "उच्च प्रतिबंध" "मध्यम प्रतिबंध" @@ -124,6 +107,7 @@ "इस चैनल को देखने के लिए अपना पिन डालें" "इस कार्यक्रम को देखने के लिए अपना पिन डालें" "इस कार्यक्रम को %1$s रेट किया गया है. यह कार्यक्रम देखने के लिए अपना पिन डालें" + "इस कार्यक्रम को रेट नहीं किया गया है. इसे देखने के लिए अपना पिन डालें" "अपना पिन डालें" "अभिभावकीय नियंत्रण सेट करने के लिए, पिन बनाएं" "नया पिन डालें" @@ -135,22 +119,31 @@ "वह पिन गलत था. पुन: प्रयास करें." "फिर से प्रयास करें, पिन का मिलान नहीं हुआ" + "अपना ज़िप कोड डालें." + "लाइव चैनल ऐप्लिकेशन ज़िप कोड का उपयोग करके टीवी चैनल के लिए पूरी कार्यक्रम मार्गदर्शिका देगा." + "अपना ज़िप कोड डालें" + "अमान्य ज़िप कोड" "सेटिंग" "चैनल सूची कस्टमाइज़ करें" "कार्यक्रम मार्गदर्शिका के लिए चैनल चुनें" "चैनल के स्रोत" "नए चैनल उपलब्‍ध हैं" "अभिभावकीय नियंत्रण" + "टाइमशिफ़्ट" + "देखते समय रिकॉर्ड करें ताकि आप लाइव कार्यक्रमों को रोक सकें या उन्हें रिवाइंड कर पाएं.\nचेतावनी: यह जगह का बहुत ज़्यादा उपयोग करके आंतरिक जगह का जीवनकाल घटा सकता है." "ओपन सोर्स लाइसेंस" - "ओपन सोर्स लाइसेंस" + "फ़ीडबैक भेजें" "वर्शन" "इस चैनल को देखने के लिए, दाईं ओर दबाएं और अपना पिन डालें" "इस कार्यक्रम को देखने के लिए, दाईं ओर दबाएं और अपना पिन डालें" + "इस कार्यक्रम को रेट नहीं किया गया है.\nइसे देखने के लिए, कृपया दायां दबाएं और अपना पिन डालें" "इस कार्यक्रम को %1$s रेट किया गया है.\nइस कार्यक्रम को देखने के लिए, Right दबाएं और अपना पिन डालें." "इस चैनल को देखने के लिए, डिफ़ॉल्ट लाइव टीवी ऐप का उपयोग करें." "इस कार्यक्रम को देखने के लिए, डिफ़ॉल्ट लाइव टीवी ऐप का उपयोग करें." + "इस कार्यक्रम को रेट नहीं किया गया है.\nइसे देखने के लिए, डिफ़ॉल्ट Live TV ऐप्लिकेशन का उपयोग करें." "इस कार्यक्रम को %1$s रेट किया गया है.\nइस कार्यक्रम को देखने के लिए, डिफ़ॉल्ट लाइव टीवी ऐप का उपयोग करें." "यह कार्यक्रम बंद कर दिया गया है" + "इस कार्यक्रम को रेट नहीं किया गया है" "इस कार्यक्रम को %1$s रेट किया गया है" "केवल ऑडियो" "कमज़ोर सिग्नल" @@ -181,8 +174,6 @@ "टीवी मेनू ऐक्‍सेस करने के लिए ""चुनें"" दबाएं." "कोई टीवी इनपुट नहीं मिला" "टीवी इनपुट नहीं मिल पा रहा है" - "PIP समर्थित नहीं है" - "कोई उपलब्ध इनपुट नहीं है जिसे PIP के साथ दिखाया जा सके" "ट्यूनर प्रकार उपयुक्‍त नहीं है. कृपया ट्यूनर प्रकार टीवी इनपुट के लिए Live TV ऐप लॉन्‍च करें." "ट्यून विफल रहा" "यह कार्रवाई प्रबंधित करने के लिए कोई ऐप नहीं मिला." @@ -226,6 +217,8 @@ %1$d रिकॉर्डिंग शेड्यूल की गईं %1$d रिकॉर्डिंग शेड्यूल की गईं + "रिकॉर्डिंग रद्द करें" + "रिकॉर्डिंग बंद करें" "देखें" "शुरू से चलाएं" "चलाना फिर शुरू करें" @@ -258,9 +251,6 @@ "जब एक ही समय पर कई सारे कार्यक्रम रिकॉर्ड करने हों, तो केवल उच्च प्राथमिकता वाले कार्यक्रम रिकॉर्ड किए जाएंगे." "सहेजें" "एक बार की रिकॉर्डिंग को उच्च प्राथमिकता दी जाती है" - "रद्द करें" - "रद्द करें" - "भूल जाएं" "रोकें" "रिकॉर्डिंग शेड्यूल देखें" "यह एक ही कार्यक्रम" @@ -270,25 +260,28 @@ "उसके बजाय इसे रिकॉर्ड करें" "यह रिकॉर्डिंग रद्द करें" "अभी देखें" + "रिकॉर्डिंग हटाएं…" "रिकॉर्ड करने योग्य" "रिकॉर्डिंग शेड्यूल की गई" "रिकॉर्डिंग संबंधी विरोध" "रिकॉर्ड हो रहा है" "रिकॉर्डिंग विफल रही" - "रिकॉर्डिंग शेड्यूल बनाने के लिए कार्यक्रम पढ़े जा रहे हैं" - "प्रोग्राम पढ़े जा रहे हैं" - - + "कार्यक्रम पढ़े जा रहे हैं" + "हाल ही की रिकॉर्डिंग देखें" + "%1$s की रिकॉर्डिंग अधूरी है." + "%1$s और %2$s की रिकॉर्डिंग अधूरी है." + "%1$s, %2$s और %3$s की रिकॉर्डिंग अधूरी है." + "पर्याप्त जगह नहीं होने के कारण %1$s की रिकॉर्डिंग पूरी नहीं हुई." + "पर्याप्त जगह नहीं होने के कारण %1$s और %2$s की रिकॉर्डिंग पूरी नहीं हुई." + "पर्याप्त जगह नहीं होने के कारण %1$s, %2$s और %3$s की रिकॉर्डिंग पूरी नहीं हुई." "DVR को अधिक जगह की आवश्यकता है" - "आप DVR से प्रोग्राम रिकॉर्ड कर पाएंगे. हालांकि इस समय आपके डिवाइस पर DVR के काम करने के लिए पर्याप्त जगह नहीं है. कृपया एक बाहरी डिवाइस कनेक्ट करें जिसमें %1$s GB या उससे अधिक जगह हो और उसे डिवाइस जगह के रूप में फ़ॉर्मेट करने के चरणों का अनुसरण करें." + "आप DVR से प्रोग्राम रिकॉर्ड कर पाएंगे. हालांकि इस समय आपके डिवाइस पर DVR के काम करने के लिए पर्याप्त जगह नहीं है. कृपया एक बाहरी डिवाइस कनेक्ट करें जिसमें %1$d GB या उससे अधिक जगह हो और उसे डिवाइस जगह के रूप में फ़ॉर्मेट करने के चरणों का अनुसरण करें." + "पर्याप्‍त जगह नहीं है" + "इस कार्यक्रम को रिकॉर्ड नहीं किया जाएगा क्योंकि पर्याप्त जगह उपलब्ध नहीं है. कुछ मौजूदा रिकॉर्डिंग हटाकर देखें." "जगह मिल नहीं रही है" - "DVR द्वारा उपयोग की गई कुछ जगह मिल नहीं रही है. कृपया DVR को दोबारा सक्षम करने से पहले अपनी उपयोग की हुई बाहरी डिस्क कनेक्ट करें. वैकल्पिक रूप से, यदि जगह अब उपलब्ध नहीं है तो आप उसे भूल जाना चुन सकते हैं." - "जगह को भूल जाएं?" - "आपकी रिकॉर्ड की हुई सभी सामग्री और शेड्यूल खो जाएंगे." "रिकॉर्डिंग बंद करें?" "रिकॉर्ड की गई सामग्री सहेज ली जाएगी." - - + "%1$s की रिकॉर्डिंग रोक दी जाएगी क्योंकि यह इस प्रोग्राम का विरोध करता है. रिकॉर्ड की गई सामग्री सहेज ली जाएगी." "रिकॉर्डिंग शेड्यूल की गई लेकिन विरोध मौजूद हैं" "रिकॉर्डिंग शुरू हो गई है लेकिन उसमें विरोध मौजूद हैं" "%1$s रिकॉर्ड किया जाएगा." @@ -306,17 +299,29 @@ "इसी कार्यक्रम को %1$s बजे रिकॉर्ड करने के लिए पहले ही शेड्यूल किया जा चुका है." "पहले ही रिकॉर्ड हो चुका है" "यह कार्यक्रम पहले ही रिकॉर्ड हो चुका है. वह DVR लाइब्रेरी में उपलब्ध है." - - - - - - - - + "सीरीज़ रिकॉर्डिंग शेड्यूल की गई" + + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. + + + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. अन्य शेड्यूल से विरोधों के कारण उनमें से %3$d को रिकॉर्ड नहीं किया जाएगा. + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. अन्य शेड्यूल से विरोधों के कारण उनमें से %3$d को रिकॉर्ड नहीं किया जाएगा. + + + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. अन्य शेड्यूल और दूसरे सीरीज़ के एपिसोड से विरोधों के कारण इस सीरीज़ और अन्य सीरीज़ के %3$d एपिसोड रिकॉर्ड नहीं किए जाएंगे. + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. अन्य शेड्यूल और दूसरे सीरीज़ के एपिसोड से विरोधों के कारण इस सीरीज़ और अन्य सीरीज़ के %3$d एपिसोड रिकॉर्ड नहीं किए जाएंगे. + + + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. इस सीरीज़ से विरोधों के कारण अन्य सीरीज़ का 1 एपिसोड रिकॉर्ड नहीं किया जाएगा. + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. इस सीरीज़ से विरोधों के कारण अन्य सीरीज़ का 1 एपिसोड रिकॉर्ड नहीं किया जाएगा. + + + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. इस सीरीज़ से विरोधों के कारण अन्य सीरीज़ के %3$d एपिसोड रिकॉर्ड नहीं किए जाएंगे. + %2$s के लिए %1$d रिकॉर्डिंग शेड्यूल कर दी गई हैं. इस सीरीज़ से विरोधों के कारण अन्य सीरीज़ के %3$d एपिसोड रिकॉर्ड नहीं किए जाएंगे. + "रिकॉर्ड किया गया प्रोग्राम नहीं मिला." "संबंधित रिकॉर्डिंग" - "(कोई कार्यक्रम वर्णन नहीं)" %1$d रिकॉर्डिंग %1$d रिकॉर्डिंग @@ -336,6 +341,7 @@ "श्रृंखला की रिकॉर्डिंग रोकें?" "रिकॉर्ड किए गए एपिसोड DVR लाइब्रेरी में उपलब्ध रहेंगे." "रोकें" + "इस समय कोई भी एपिसोड प्रसारित नहीं हो रहा है." "कोई भी एपिसोड उपलब्ध नहीं है.\nएपिसोड उपलब्ध होने पर उन्हें रिकॉर्ड कर लिया जाएगा." (%1$d मिनट) @@ -347,4 +353,5 @@ "%1$s आज" "%1$s कल" "स्कोर" + "रिकॉर्ड किए गए कार्यक्रम" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 596a3f96..a4f000eb 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Kontrole reprodukcije" - "Nedavni kanali" + "Kanali" "TV opcije" - "Opcije PIP-a" "Kontrole reprodukcije nisu dostupne za ovaj kanal" "Reprodukcija ili pauziranje" "Brzo unaprijed" @@ -35,33 +34,15 @@ "Titlovi" "Način prikaza" "PIP" - "Uključeno" - "Isključeno" "Multiaudio" "Više kanala" "Postavke" - "Izvor" - "Zamijeni" - "Uključeno" - "Isključeno" - "Zvuk" - "Glavni" - "Prozor PIP-a" - "Raspored" - "Pri dnu desno" - "Pri vrhu desno" - "Pri vrhu lijevo" - "Pri dnu lijevo" - "Jedno uz drugo" - "Veličina" - "Velik" - "Malen" - "Izvor ulaza" "TV (antena/kabel)" "Nema podataka o programu" "Nema informacija" "Blokirani kanal" - "Nepoznati jezik" + "Nepoznati jezik" + "Titlovi %1$d" "Titlovi" "Isključeno" "Prilagodi format" @@ -83,6 +64,7 @@ "SD" "Grupiraj po" "Program je blokiran" + "Ovaj je program neocijenjen" "Program ima ocjenu %1$s" "Ulaz ne podržava automatsko pretraživanje" "Nije moguće pokretanje automatskog pretraživanja za \"%s\"" @@ -93,7 +75,6 @@ Dodano je %1$d kanala "Kanali nisu dodani" - "Tražilo" "Roditeljski nadzor" "Uključeno" "Isključeno" @@ -109,6 +90,8 @@ "Ostale zemlje" "Ništa" "Ništa" + "Neocijenjeno" + "Blokiraj neocijenjene programe" "Ništa" "Visoka ograničenja" "Srednja ograničenja" @@ -125,6 +108,7 @@ "Unesite PIN da biste gledali ovaj kanal" "Unesite PIN da biste gledali ovaj program" "Ovaj program ima ocjenu %1$s. Da biste ga gledali, unesite PIN" + "Ovaj je program neocijenjen. Da biste ga gledali, unesite PIN" "Unesite PIN" "Da biste postavili roditeljski nadzor, stvorite PIN" "Unesite novi PIN" @@ -137,22 +121,31 @@ "PIN je pogrešan. Pokušajte ponovno." "Pokušaj ponovo, PIN se ne podudara" + "Unesite svoj poštanski broj." + "Na temelju poštanskog broja aplikacija TV kanali uživo pružit će vam potpun programski vodič za TV kanale." + "Unesite svoj poštanski broj" + "Poštanski broj nije važeći" "Postavke" "Prilagodite popis kanala" "Odaberite kanale za programski vodič" "Izvori kanala" "Dostupni su novi kanali" "Roditeljski nadzor" + "Vremenski pomak" + "Snimajte tijekom gledanja da biste mogli pauzirati ili premotavati emisije uživo.\nUpozorenje: time se može skratiti trajanje interne pohrane zbog njezine intenzivne upotrebe." "Licence otvorenog izvornog koda" - "Licence otvorenog izvornog koda" + "Pošaljite povratne informacije" "Verzija" "Da biste gledali ovaj kanal, pritisnite desno pa unesite PIN" "Da biste gledali ovaj program, pritisnite desno pa unesite PIN" + "Ovaj je program neocijenjen.\nDa biste ga gledali, pritisnite desno i unesite PIN" "Ovaj program ima ocjenu %1$s.\nDa biste ga gledali, pritisnite desni gumb pa unesite PIN." "Da biste gledali ovaj kanal, upotrijebite zadanu aplikaciju za TV uživo." "Da biste gledali ovaj program, upotrijebite zadanu aplikaciju za TV uživo." + "Ovaj je program neocijenjen.\nDa biste ga gledali, upotrijebite zadanu aplikaciju za TV uživo." "Ovaj program ima ocjenu %1$s.\nDa biste ga gledali, upotrijebite zadanu aplikaciju za TV uživo." "Program je blokiran" + "Ovaj je program neocijenjen" "Program ima ocjenu %1$s" "Samo zvuk" "Slab signal" @@ -185,8 +178,6 @@ "Pritisnite ODABERITE"" da biste pristupili izborniku TV-a." "TV ulaz nije pronađen" "TV ulaz nije pronađen" - "PIP nije podržan" - "Nema dostupnog ulaza koji se može prikazati putem PIP-a" "Vrsta prijemnika nije prikladna. Pokrenite aplikaciju Kanali uživo ako želite koristiti prijemnik kao TV ulaz." "Traženje kanala nije uspjelo" "Nije pronađena nijedna aplikacija koja može provesti tu radnju." @@ -235,6 +226,8 @@ %1$d snimanja zakazana %1$d snimanja zakazano + "Otkaži snimanje" + "Zaustavi snimanje" "Gledaj" "Reproduciraj od početka" "Nastavi reprodukciju" @@ -268,9 +261,6 @@ "Kada će se se istovremeno trebati snimati previše emisija, snimit će se samo one s najvišim prioritetom." "Spremi" "Jednokratna snimanja imaju najviši prioritet" - "Odustani" - "Odustani" - "Zaboravi" "Zaustavi" "Prikaz rasporeda snimanja" "Samo jedna epizoda" @@ -280,25 +270,28 @@ "Snimi ovo" "Otkaži snimanje" "Pogledajte odmah" + "Brisanje snimki…" "Snimanje moguće" "Snimanje programirano" "Sukob rasporeda snimanja" "Snimanje" "Snimanje nije uspjelo" - "Čitanje emisija za izradu rasporeda snimanja" - "Čitanje emisija" - - + "Čitanje emisija" + "Prikaži nedavne snimke" + "Snimka sadržaja %1$s nije dovršena." + "Snimke sadržaja %1$s i %2$s nisu dovršene." + "Snimke sadržaja %1$s, %2$s i %3$s nisu dovršene." + "Snimanje sadržaja %1$s nije dovršeno jer nema dovoljno mjesta u pohrani." + "Snimanje sadržaja %1$s i %2$s nije dovršeno jer nema dovoljno mjesta u pohrani." + "Snimanje sadržaja %1$s, %2$s i %3$s nije dovršeno jer nema dovoljno mjesta u pohrani." "DVR treba više prostora za pohranu" - "Moći ćete snimati programe DVR-om. No na vašem uređaju trenutačno nema dovoljno prostora za pohranu da bi DVR funkcionirao. Priključite vanjski disk od najmanje %1$s GB i formatirajte ga kao pohranu uređaja prema uputama." + "Moći ćete snimati programe DVR-om. No na vašem uređaju trenutačno nema dovoljno prostora za pohranu da bi DVR funkcionirao. Priključite vanjski disk od najmanje %1$d GB i formatirajte ga kao pohranu uređaja prema uputama." + "Nema dovoljno prostora za pohranu" + "Emisija se neće snimiti jer nema dovoljno prostora za pohranu. Izbrišite neke snimljene emisije." "Pohrana nedostaje" - "Nedostaje dio pohrane kojom se koristi DVR. Da biste ponovo omogućili DVR, povežite ga s vanjskim diskom koji ste upotrebljavali prethodno. Ako ta pohrana više nije dostupna, možete odabrati da je uređaj zaboravi." - "Zaboraviti pohranu?" - "Izgubit ćete sve snimljene sadržaje i programirana snimanja." "Želite li zaustaviti snimanje?" "Snimljeni će se sadržaj spremiti." - - + "Snimanje sadržaja %1$s zaustavit će se zbog sukoba s ovim programom. Snimljeni sadržaj ostat će spremljen." "Snimanje je programirano, ali ima sukoba" "Snimanje je započelo, ali ima sukoba" "Snimit će se %1$s." @@ -317,17 +310,34 @@ "Snimanje tog programa već je programirano za %1$s." "Već snimljeno" "Taj je program već snimljen. Dostupan je u zbirci DVR-a." - - - - - - - - + "Snimanje serije je zakazano" + + %1$d snimanje zakazano je za seriju %2$s. + %1$d snimanja zakazana su za seriju %2$s. + %1$d snimanja zakazano je za seriju %2$s. + + + %1$d snimanje zakazano je za seriju %2$s. %3$d od njih neće se snimiti zbog sukoba. + %1$d snimanja zakazana su za seriju %2$s. %3$d od njih neće se snimiti zbog sukoba. + %1$d snimanja zakazano je za seriju %2$s. %3$d od njih neće se snimiti zbog sukoba. + + + %1$d snimanje zakazano je za seriju %2$s. %3$d epizoda/e te serije i druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazana su za seriju %2$s. %3$d epizoda/e te serije i druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazano je za seriju %2$s. %3$d epizoda/e te serije i druge serije neće se snimiti zbog sukoba. + + + %1$d snimanje zakazano je za seriju %2$s. 1 epizoda neke druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazana su za seriju %2$s. 1 epizoda neke druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazano je za seriju %2$s. 1 epizoda neke druge serije neće se snimiti zbog sukoba. + + + %1$d snimanje zakazano je za seriju %2$s. %3$d epizoda/e neke druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazana su za seriju %2$s. %3$d epizoda/e neke druge serije neće se snimiti zbog sukoba. + %1$d snimanja zakazano je za seriju %2$s. %3$d epizoda/e neke druge serije neće se snimiti zbog sukoba. + "Snimljeni program nije pronađen." "Povezane snimke" - "(Nema opisa programa)" %1$d snimka %1$d snimke @@ -349,6 +359,7 @@ "Želite li zaustaviti snimanje serije?" "Snimljene epizode ostat će dostupne u zbirci DVR-a." "Zaustavi" + "Trenutačno se ne emitira nijedna epizoda." "Nije dostupna nijedna epizoda.\nEpizode će se snimiti kada budu dostupne." (%1$d minuta) @@ -361,4 +372,5 @@ "%1$s danas" "%1$s sutra" "Rezultat" + "Snimljene emisije" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index d007a135..f28b3140 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -20,9 +20,8 @@ "monó" "sztereó" "Lejátszásvezérlők" - "Legutóbbiak" + "Csatornák" "Tv beállításai" - "PIP-beállítások" "A lejátszási vezérlők nem érhetők el ennél a csatornánál" "Lejátszás vagy szüneteltetés" "Előretekerés" @@ -35,33 +34,15 @@ "Feliratok" "Megjelenítés" "PIP" - "Bekapcsolva" - "Kikapcsolva" "Több hangsáv" "További csatornák" "Beállítások" - "Forrás" - "Csere" - "Bekapcsolva" - "Kikapcsolva" - "Hang" - "Elsődleges" - "PIP-ablak" - "Elrendezés" - "Lent jobbra" - "Fent jobbra" - "Fent balra" - "Lent balra" - "Egymás mellett" - "Méret" - "Nagy" - "Kicsi" - "Bemeneti forrás" "TV (antenna/kábel)" "Nincsenek műsorinformációk" "Nincs információ." "Letiltott csatorna" - "Ismeretlen nyelv" + "Ismeretlen nyelv" + "Feliratok (%1$d)" "Feliratok" "Kikapcsolva" "Személyre szabás" @@ -83,6 +64,7 @@ "SD" "Csoportosítás" "A műsor le van tiltva." + "Ez a műsor nem rendelkezik besorolással" "A műsor besorolása: %1$s." "A bemenet nem támogatja az automatikus keresést" "Nem lehet elindítani a(z) „%s” automatikus keresését" @@ -92,7 +74,6 @@ %1$d csatorna hozzáadva "Nincs csatorna hozzáadva" - "Tuner" "Szülői felügyelet" "Bekapcsolva" "Kikapcsolva" @@ -108,6 +89,8 @@ "Más országok" "Nincs" "Nincs" + "Nincs besorolás" + "Nem besorolt műsorok letiltása" "Nincs" "Magas korlátozások" "Közepes korlátozások" @@ -124,33 +107,43 @@ "A csatorna megtekintéséhez PIN-kód szükséges" "A műsor megtekintéséhez PIN-kód szükséges" "A műsor besorolása: %1$s. A megtekintéséhez adja meg PIN-kódját." + "Ez a műsor nem rendelkezik besorolással. Adja meg PIN-kódját a műsor megtekintéséhez" "PIN-kód megadása" - "Szülői felügyelet beállításához hozzon létre egy PIN kódot." + "Szülői felügyelet beállításához hozzon létre egy PIN-kódot." "Új PIN-kód megadása" "PIN-kód megerősítése" - "Adja meg a jelenlegi PIN kódot" + "Adja meg a jelenlegi PIN-kódot" Helytelen PIN-kódot adott meg ötször egymás után.\nPróbálja újra %1$d másodperc múlva. Helytelen PIN-kódot adott meg ötször egymás után.\nPróbálja újra %1$d másodperc múlva. "A PIN-kód helytelen, próbálja újra." "A PIN-kód nem egyezik, próbálja újra" + "Irányítószám megadása" + "Az Élő Csatornák alkalmazás az irányítószám használatával megjeleníti a csatornák teljes tévéműsorát." + "Adja meg irányítószámát" + "Érvénytelen irányítószám" "Beállítások" "Csatornalista testreszabása" "Válasszon csatornákat a műsorfüzetbe" "Csatornaforrások" "Új csatornák állnak rendelkezésre" "Szülői felügyelet" + "Időeltolás" + "A nézésük közben felveheti a műsorokat, így szüneteltetheti vagy visszatekerheti őket.\nFigyelmeztetés: A belső tárhely intenzív használata miatt ez a funkció csökkentheti a tárhely élettartamát." "Nyílt forráskódú licencek" - "Nyílt forráskódú licencek" + "Visszajelzés küldése" "Verzió" - "A csatorna megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot" - "A műsor megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot" - "A műsor besorolása: %1$s.\nA megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot." + "A csatorna megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN-kódot" + "A műsor megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN-kódot" + "Ez a műsor nem rendelkezik besorolással.\nA műsor megtekintéséhez nyomja meg a Jobb gombot, majd adja meg a PIN-kódot" + "A műsor besorolása: %1$s.\nA megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN-kódot." "A csatorna megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást." "A program megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást." + "Ez a műsor nem rendelkezik besorolással.\nA műsor megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást." "A program besorolása: %1$s.\nA megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást." "A műsor le van tiltva" + "Ez a műsor nem rendelkezik besorolással" "A műsor besorolása: %1$s." "Csak hang" "Gyenge jel" @@ -181,8 +174,6 @@ "Nyomja meg a KIVÁLASZTÁS"" gombot a tévé menüjének eléréséhez." "Nem található tévébemenet." "A tévébemenet nem található." - "A kép a képben funkció nem támogatott." - "Nincs elérhető bemeneti adás, amely megjeleníthető lenne a kép a képben (picture in picture, PIP) funkcióval." "A tuner típusa nem megfelelő. Indítsa el a tévé bemeneti tunertípusának megfelelő Élő csatornák alkalmazást." "Tunerhiba" "Nincs megfelelő alkalmazás a művelet végrehajtásához." @@ -226,6 +217,8 @@ %1$d rögzítés beütemezve %1$d rögzítés beütemezve + "Felvétel megszakítása" + "Felvétel leállítása" "Megtekintés" "Lejátszás az elejétől" "Lejátszás folytatása" @@ -258,9 +251,6 @@ "Ha túl sok rögzítendő műsort állít be ugyanarra az időtartamra, kizárólag a magasabb prioritással rendelkezőket rögzíti a rendszer." "Mentés" "Az egyszeri rögzítések rendelkeznek a legmagasabb prioritással" - "Mégse" - "Mégse" - "Elfelejt" "Leállítás" "Rögzítési ütemterv megtekintése" "Ezt az egy műsort" @@ -270,25 +260,28 @@ "Inkább ez legyen rögzítve" "A rögzítés törlése" "Nézze meg most" + "Felvételek törlése…" "Felvehető" "A felvétel beállítva" "Ütközés más felvétellel" "Rögzítés" "A felvétel nem sikerült" - "Műsorok beolvasása a rögzítési ütemterv kialakításához" - "Műsorok beolvasása" - - + "Műsorok beolvasása" + "A legutóbbi felvételek megtekintése" + "A(z) %1$s felvétele megszakadt." + "A következők felvétele megszakadt: %1$s és %2$s." + "A következők felvétele megszakadt: %1$s, %2$s és %3$s." + "A(z) %1$s felvétele nem sikerült, mert nincs elég tárhely." + "A következők felvétele a kevés tárhely miatt nem sikerült: %1$s és %2$s." + "A következők felvétele a kevés tárhely miatt nem sikerült: %1$s, %2$s és %3$s." "A DVR számára több tárhely szükséges" - "A DVR segítségével műsorokat vehet fel. Azonban eszközén nincs elég szabad tárhely a DVR működéséhez. Csatlakoztasson egy legalább %1$s GB tárhellyel rendelkező külső meghajtót, majd kövesse az utasításokat, hogy az eszköz tárhelyévé formázhassa." + "A DVR segítségével műsorokat vehet fel. Azonban eszközén nincs elég szabad tárhely a DVR működéséhez. Csatlakoztasson egy legalább %1$d GB tárhellyel rendelkező külső meghajtót, majd kövesse az utasításokat, hogy az eszköz tárhelyévé formázhassa." + "Nincs elegendő tárhely" + "Ez a műsor nem lesz felvéve, mivel nincs elegendő tárhely. Próbáljon meg törölni néhány meglévő felvételt." "Hiányzó tárhely" - "A DVR által használt tárhely bizonyos része hiányzik. Kérjük, csatlakoztassa a korábban használt külső meghajtót a DVR ismételt engedélyezéséhez. Másik megoldásként megadhatja a rendszernek, hogy ne emlékezzen többé erre a tárhelyre, ha már nem áll a rendelkezésére." - "A rendszer ne emlékezzen többé erre a tárhelyre?" - "Az összes rögzített tartalom és ütemterv elvész." "Leállítja a rögzítést?" "A rögzített tartalmat elmenti a rendszer." - - + "A(z) %1$s felvétele le fog állni, mert ütközik ezzel a programmal. A felvett tartalmat az alkalmazás menteni fogja." "A rögzítést beállította, de az ütközik más műsorokkal" "A rögzítés elindult, de az ütközik más műsorokkal" "A(z) %1$s rögzítve lesz." @@ -306,17 +299,29 @@ "Ugyanennek a műsornak a rögzítése már be van ütemezve a következő időpontban: %1$s." "Már készült róla felvétel" "Erről a műsorról már készült felvétel, amely a DVR könyvtárban található." - - - - - - - - + "Sorozatfelvétel ütemezve" + + %1$d felvétel van ütemezve a következőhöz: %2$s. + %1$d felvétel van ütemezve a következőhöz: %2$s. + + + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer nem rögzít %3$d felvételt. + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer nem rögzíti a felvételt. + + + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer %3$d epizódot nem rögzít ebből, valamint más sorozatokból. + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer %3$d epizódot nem rögzít ebből, valamint más sorozatokból. + + + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer 1 epizódot nem rögzít más sorozatokból. + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer 1 epizódot nem rögzít más sorozatokból. + + + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer %3$d epizódot nem rögzít más sorozatokból. + %1$d felvétel van ütemezve a következőhöz: %2$s. Ütközés miatt a rendszer %3$d epizódot nem rögzít más sorozatokból. + "A rögzített program nem található." "Kapcsolódó felvételek" - "(Nincs programleírás)" %1$d felvétel %1$d felvétel @@ -336,6 +341,7 @@ "Leállítja a sorozat rögzítését?" "A rögzített részek továbbra is hozzáférhetők lesznek a DVR könyvtárban." "Leállítás" + "Egyetlen epizódot sem közvetítenek jelenleg." "Nincs rendelkezésre álló epizód.\nAz epizódok a megjelenésüket követően lesznek rögzítve." (%1$d perc) @@ -347,4 +353,5 @@ "Ma: %1$s" "Holnap: %1$s" "Pontszám" + "Felvett műsorok" diff --git a/res/values-hy-rAM-v23/strings.xml b/res/values-hy-rAM-v23/strings.xml new file mode 100644 index 00000000..774fbd3d --- /dev/null +++ b/res/values-hy-rAM-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Ալիքներ" + diff --git a/res/values-hy-rAM/arrays.xml b/res/values-hy-rAM/arrays.xml new file mode 100644 index 00000000..5c6f0bfd --- /dev/null +++ b/res/values-hy-rAM/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Սովորական" + "Լիցքավորված" + "Խոշորացնել" + + + "Բոլոր ալիքները" + "Ընտանիք/Երեխաներ" + "Սպորտ" + "Գնումներ" + "Ֆիլմեր" + "Կատակերգություն" + "Ճամփորդություն" + "Դրամա" + "Կրթություն" + "Կենդ./Վայրի բնություն" + "Նորություններ" + "Համակարգչային խաղեր" + "Արվեստ" + "Ժամանց" + "Ապրելակերպ" + "Երաժշտություն" + "Նորույթներ" + "Տեխնիկա/Գիտություն" + + + "Ուղիղ եթեր" + "Բովանդակության հայտնաբերման պարզ եղանակ" + "Ներբեռնեք հավելվածներ, դիտեք էլ ավելի ալիքներ" + "Անհատականացրեք ալիքների դասավորությունը" + + + "Դիտեք բովանդակությունը հավելվածներից՝ ինչպես կդիտեիք հեռուստաալիքները հեռուստացույցի միջոցով:" + "Հավելվածներից բովանդակությունը դիտեք հարմար ուղեցույցի և միջերեսի օգնությամբ՝ \nինչպես կդիտեիք հեռուստաալիքները հեռուստացույցի միջոցով:" + "Ավելացրեք ալիքներ՝ ուղիղ եթեր առաջարկող հավելվածներ տեղադրելու միջոցով: \nGoogle Play Խանութում գտեք համատեղելի հավելվածներ հեռուստացույցի ընտրացանկում պարունակվող հղման օգնությամբ:" + "Կարգավորեք նոր տեղադրած ալիքների աղբյուրները՝ ձեր ալիքների ցանկը անհատականացնելու համար: \nՍկսելու համար Կարգավորումների ընտրացանկում ընտրեք Ալիքների աղբյուրները:" + + diff --git a/res/values-hy-rAM/rating_system_strings.xml b/res/values-hy-rAM/rating_system_strings.xml new file mode 100644 index 00000000..8c69ca7e --- /dev/null +++ b/res/values-hy-rAM/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Ծրագրերը կարող են պարունակել 15-ից փոքր լսարանի համար չնախատեսված նյութեր, այդ իսկ պատճառով դրանց դիտումը թույլատրվում է միայն ծնողի հայեցողությամբ:" + "Ծրագրերը կարող են պարունակել 19-ից փոքր լսարանի համար չնախատեսված նյութեր, այդ իսկ պատճառով դրանք պիտանի չեն 19 տարեկանը չլրացած երիտասարդների համար:" + "Անպարկեշտ խոսքեր" + "Կոպիտ բառեր" + "Սեռական բովանդակություն" + "Բռնության տեսարաններ" + "Բռնության տեսարաններ հրեշների մասնակցությամբ" + "Այս ծրագիրը հարմար է բոլոր տարիքի երեխաների կողմից դիտելու համար:" + "Այս ծրագիրը նախատեսված է 7 և բարձր տարիքի երեխաների համար:" + "Շատ ծնողների կարծիքով այս ծրագիրը հարմար է ցանկացած տարիքի համար:" + "Այս ծրագիրը պարունակում է որոշ նյութեր, որոնք շատ ծնողների կարծիքով անցանկալի են փոքր երեխաների համար: Հնարավոր է՝ շատ ծնողներ ցանկանան դիտել այն իրենց փոքր երեխաների հետ:" + "Այս ծրագիրը պարունակում է որոշ նյութեր, որոնք շատ ծնողների կարծիքով անցանկալի են 14 տարեկանից ցածր երեխաների համար:" + "Այս ծրագիրը հատուկ ստեղծվել է մեծահասակների կողմից դիտելու համար, ուստի կարող է պարունակել անթույլատրելի բովանդակություն՝ մինչև 17 տարեկան երեխաների համար:" + "Ֆիլմի վարկանիշները" + "Առանց տարիքային սահմանափակումների: Ծնողներիը կարող են դիտել երեխաների հետ:" + "Խորհուրդ է տրվում իրականացնել ծնողական վերահսկողություն: Կարող է պարունակել նյութեր, որոնց դիտումը փոքր երեխաների կողմից, հնարավոր է, ծնողների համար ցանկալի չլինի:" + "Ծնողները խստիվ զգուշացվում են: Որոշ նյութեր կարող են անցանկալի լինել նախապատանեկան տարիքի երեխաների կողմից դիտելու համար:" + "Ունի տարիքային սահմանափակում, քանի որ պարունակում է մեծահասակների համար նախատեսված նյութեր: Ծնողներին խորհուրդ է տրվում ավելի շատ տեղեկություններ ստանալ ֆիլմի մասին՝ նախքան իրենց փոքր երեխաների հետ այն դիտելը:" + "17 տարեկանից փոքր անձանց կողմից դիտումն արգելված է: Նախատեսված է միայն չափահասների համար: Երեխաների մուտքն արգելված է:" + diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml new file mode 100644 index 00000000..a723a2ae --- /dev/null +++ b/res/values-hy-rAM/strings.xml @@ -0,0 +1,357 @@ + + + + + "մոնո" + "ստերեո" + "Նվագարկման կառավար" + "Հեռուստաալիքներ" + "Հեռ. ընտրանքներ" + "Նվագարկման կառավարներն այս ալիքում անհասանելի են" + "Նվագարկել կամ դադարեցնել" + "Արագ առաջանցում" + "Հետանցում" + "Հաջորդը" + "Նախորդը" + "Ծրագրի ուղեցույց" + "Նոր ալիքներ" + "Բացել %1$s-ը" + "Փակ խորագրեր" + "Ցուցադրման ռեժիմ" + "PIP" + "Բազմաուդիո" + "Ավելացնել ալիքներ" + "Կարգավորումներ" + "Հեռուստացույց (ալեհավաք/մալուխ)" + "Ծրագրի մասին տեղեկություններ չկան" + "Տեղեկություն չկա" + "Ալիքն արգելափակված է:" + "Անհայտ լեզու" + "%1$d ենթագրեր" + "Փակ ենթագրեր" + "Անջատված է" + "Հարմարացնել ձևաչափը" + "Սահմանել համակարգի խորագրերի պարամետրեր" + "Ցուցադրման ռեժիմ" + "Բազմաուդիո" + "Մոնո" + "Ստերեո" + "5.1 ծավալային ձայն" + "7.1 ծավալային ձայն" + "%d հեռուստաալիք" + "Հարմարեցնել ցանկը" + "Ընտրել խումբը" + "Ապընտրել խումբը" + "Խմբավորել" + "Ալիք աղբյուրը" + "HD/SD" + "HD" + "SD" + "Խմբավորել" + "Այս ծրագիրը արգելափակված է" + "Ծրագիրը գնահատված չէ" + "Այս ծրագրի վարկանիշը %1$s է" + "Մուտքը ինքնասկանավարում չի աջակցում" + "Չի հաջողվում սկսել «%s»-ի ավտոմատ սկանավորումը" + "Չհաջողվեց բացել խորագրերի համակարգի կարգավորումները:" + + Ավելացվեց %1$d ալիք + Ավելացվեց %1$d ալիք + + "Ալիքներ չեն ավելացվել" + "Ծնողական հսկում" + "Միացված է" + "Անջատված է" + "Արգելափակված են" + "Արգելափակել բոլորը" + "Ապաարգելափակել բոլորը" + "Թաքնված ալիքներ" + "Ծր. սահմանափակումներ" + "Փոխել PIN-ը" + "Գն. համակարգեր" + "Վարկանիշ" + "Բոլոր գն. համակարգերը" + "Այլ երկրներ" + "Չկա" + "Չկա" + "Չգնահատված" + "Արգելափակել չգնահատված ծրագրերը" + "Չկա" + "Խիստ սահմանափակում" + "Միջ. սահմանափակումներ" + "Թույլ սահմանափակում" + "Հատուկ" + "Բովանդակությունը հարմար է երեխաների համար" + "Բարձր տարիքային խմբի երեխաների համար" + "Բովանդակությունը հարմար է անչափահասների համար" + "Ձեռքով կարգավորման սահմանափակումներ" + + + "%1$s և ենթավարկանիշեր" + "Ենթավարկանիշեր" + "Այս ալիքը դիտելու համար մուտքագրեք PIN-ը" + "Այս ծրագիրը դիտելու համա մուտքագրեք PIN-ը" + "Այս ծրագիրն ունի տարիքային սահմանափակում՝ %1$s: Այն դիտելու համար մուտքագրեք PIN կոդը" + "Ծրագիրը գնահատված չէ։ Մուտքագրեք PIN-ը՝ այն դիտելու համար" + "Մուտքագրեք ձեր PIN-ը" + "Ծնողական վերահսկումը կարգավորելու համար PIN կոդ նախադրեք" + "Մուտքագրեք նոր PIN" + "Հաստատեք ձեր PIN-ը" + "Մուտքագրեք ձեր ներկայիս PIN-ը" + + Դուք 5 անգամ մուտքագրել եք սխալ PIN կոդ:\nՓորձեք նորից %1$d վայրկյանից: + Դուք 5 անգամ մուտքագրել եք սխալ PIN կոդ:\nՓորձեք նորից %1$d վայրկյանից: + + "PIN-ը սխալ էր: Կրկին փորձեք:" + "Փորձեք կրկին, PIN-ը չի համապատասխանում" + "Մուտքագրեք փոստային դասիչը:" + "Ուղիղ եթեր հավելվածը փոստային դասիչը կօգտագործի հեռուստաալիքների ամբողջական ցանկը տրամադրելու համար։" + "Մուտքագրեք փոստային դասիչը" + "Փոստային դասիչը սխալ է" + "Կարգավորումներ" + "Հարմարեցնել ալիքների ցանկը" + "Ընտրեք ալիքներ հեռուստահաղորդումների ծրագրի համար" + "Ալիքների աղբյուրներ" + "Առկա են նոր ալիքներ" + "Ծնողական վերահսկողություն" + "Ժամանակի կառավար" + "Ձայնագրեք դիտելիս և կկարողանաք կանգնեցնել կամ հետարկել ուղիղ եթերով հեռարձակվող հաղորդումները:\nԶգուշացում. սա կարող է նվազեցնել ներքին հիշողության օգտագործման ժամկետը, եթե ինտենսիվորեն օգտագործեք հիշողությունը:" + "Բաց կոդով ծրագրակազմի արտոնագրեր" + "Կարծիք հայտնել" + "Տարբերակ" + "Այս կապուղին դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը" + "Այս ծրագիրը դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը" + "Ծրագիրը գնահատված չէ։\nԱյն դիտելու համար սեղմեք «Աջ» կոճակը և մուտքագրեք ձեր PIN-ը" + "Այս ծրագրի վարկանիշը %1$s է:\nԱյն դիտելու համար սեղմեք Աջ և մուտքագրեք PIN կոդը:" + "Այս ալիքը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:" + "Այս հաղորդումը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:" + "Ծրագիրը գնահատված չէ։\nԱյն դիտելու համար օգտագործեք կանխադրված «Ուղիղ եթեր» հավելվածը" + "Այս հաղորդումը գնահատվել է որպես %1$s:\nԱյս հաղորդումը դիտելու համար օգտագործեք Live TV կենդանի կանխադրված հավելվածը:" + "Ծրագիրն արգելափակված է" + "Ծրագիրը գնահատված չէ" + "Այս ծրագրի վարկանիշը %1$s է" + "Միայն աուդիո" + "Թույլ ազդանշան" + "Համացանցի կապակցում չկա" + + This channel can\'t be played until %1$s because other channels are being recorded. \n\nPress Right to adjust recording schedule. + Այս ալիքը հնարավոր չէ ցուցադրել մինչև %1$s-ը, քանի որ ներկայումս տեսագրվում են այլ ալիքներ: \n\nՏեսագրման ժամանակացույցը կարգավորելու համար սեղմեք Աջ կոճակը: + + "Անվերնագիր" + "Ալիքն արգելափակված է" + "Նոր" + "Աղբյուրներ" + + %1$d ալիք + %1$d ալիք + + "Ալիքներ չկան" + "Նոր" + "Կարգավորված չէ" + "Ավելացնել այլ աղբյուրներ" + "Որոնեք ուղիղ եթեր առաջարկող հավելվածներ" + "Հասանելի են ալիքների նոր աղբյուրներ" + "Ալիքների նոր աղբյուրները պարունակում են նոր ալիքներ:\nԿարգավորեք դրանք հիմա կամ ավելի ուշ՝ ալիքների աղբյուրների կարգավորումների միջոցով:" + "Կարգավորել հիմա" + "Լավ, հասկացա" + + + "Հեռուստացույցի ընտրացանկից օգտվելու համար ""սեղմեք ԸՆՏՐԵԼ"":" + "Հեռուստացույցի մուտք չի գտնվել" + "Հեռուստացույցի մուտքը տեղադրված չէ" + "Կարգավորիչի մուտքը համապատասխան չէ: Գործարկեք «Ուղիղ եթեր» հավելվածը, եթե օգտագործում եք հեռուստացույցի մուտք:" + "Կարգավորումը չհաջողվեց" + "Այս գործողությունը կատարելու համար ոչ մի հավելված չի գտնվել" + "Բոլոր ալիքները թաքնված են:\nԸնտրեք առնվազն մեկ ալիք:" + "Անսպասելի սխալ առաջացավ տեսանյութի ցուցադրման ժամանակ" + "«Հետ» կոճակը միացված սարքի համար է: Դուս գալու համար սեղմեք «Գլխավոր» կոճակը:" + "Հեռուստատեսային ծրագրերը կարդալու համար ուղիղ եթերին թույլտվություն է անհրաժեշտ:" + "Կարգավորեք աղբյուրները" + "Ուղիղ եթերը համատեղում է ավանդական հեռուստաալիքները և հավելվածների կողմից հոսքային եղանակով հեռարձակվող ալիքները: \n\nՍկսեք արդեն իսկ տեղադրված ալիքների աղբյուրները կարգավորելուց կամ Google Play Խանութում որոնեք ուղիղ եթեր առաջարկող այլ հավելվածներ:" + "Տեսագրում և ժամանակացույցեր" + "10 րոպե" + "30 րոպե" + "1 ժամ" + "3 ժամ" + "Վերջինները" + "Ծրագրավորված" + "Սերիալներ" + "Այլ" + "Հնարավոր չէ տեսագրել այս հեռուստաալիքը:" + "Հնարավոր չէ տեսագրել այս ծրագիրը:" + "%1$s ծրագրի տեսագրումը ծրագրավորվեց" + "%1$s ծրագրի տեսագրում այս պահից մինչև %2$s" + "Ամբողջական ժամանակացույց" + + Next %1$d days + %1$d օր + + + %1$d minutes + %1$d րոպե + + + %1$d new recordings + %1$d նոր տեսագրություն + + + %1$d recordings + %1$d տեսագրություն + + + %1$d recordings scheduled + Ծրագրավորվել է %1$d տեսագրություն + + "Չեղարկել տեսագրումը" + "Դադարեցնել տեսագրումը" + "Դիտել" + "Նվագարկել սկզբից" + "Վերսկսել նվագարկումը" + "Ջնջել" + "Ջնջել տեսագրությունները" + "Շարունակել" + "Եթերաշրջան %1$s" + "Ժամանակացույց" + "Կարդալ ավելին" + "Ջնջել տեսագրումները" + "Ընտրեք դրվագները, որոնք ցանկանում եք ջնջել: Ջնջելուց հետո դրանք այլևս հնարավոր չի լինի տեսագրել:" + "Ջնջելու համար տեսագրումներ չկան" + "Նշել դիտված դրվագները" + "Նշել բոլոր դրվագները" + "Ապանշել բոլոր դրվագները" + "%2$d րոպեից դիտվել է %1$d-ը" + "%2$d վայրկյանից դիտվել է %1$d-ը" + "Երբեք չդիտված" + + %1$d of %2$d episodes are deleted + %2$d դրվագներից %1$d-ը ջնջվել է + + "Առաջնահերթություն" + "Ամենաբարձր" + "Ամենացածր" + "Ոչ: %1$d" + "Հեռուստաալիքներ" + "Ցանկացած" + "Ընտրեք առաջնահերթությունը" + "Եթե միաժամանակ ծրագրավորված է չափազանց շատ ծրագրերի տեսագրում, ապա կտեսագրվեն միայն բարձր առաջնահերթություն ունեցող ծրագրերը:" + "Պահել" + "Միանգամյա տեսագրումն ունի ամենաբարձր առաջնահերթությունը" + "Դադար" + "Դիտել տեսագրման ժամանակացույցը" + "Միայն այս ծրագիրը" + "այժմ - %1$s" + "Ամբողջ սերիալը…" + "Ծրագրավորել" + "Փոխարենը տեսագրել այս ծրագիրը" + "Չեղարկել այս տեսագրումը" + "Դիտել հիմա" + "Ջնջել տեսագրությունները…" + "Հնարավոր է տեսագրել" + "Տեսագրումը ծրագրավորված է" + "Տեսագրման հակասություն" + "Տեսագրում" + "Չհաջողվեց տեսագրել" + "Ծրագրերի ընթերցում" + "Դիտել վերջին տեսագրությունները" + "%1$s ծրագրի տեսագրումը չի ավարտվել:" + "%1$s և %2$s ծրագրերի տեսագրումը չի ավարտվել:" + "%1$s, %2$s և %3$s ծրագրերի տեսագրումը չի ավարտվել:" + "%1$s ծրագրի տեսագրումը չի ավարտվել, քանի որ հիշողությունը բավարար չէ:" + "%1$s և %2$s ծրագրերի տեսագրումը չի ավարտվել, քանի որ հիշողությունը բավարար չէ:" + "%1$s, %2$s և %3$s ծրագրերի տեսագրումը չի ավարտվել, քանի որ հիշողությունը բավարար չէ:" + "DVR-ին ավելի շատ հիշողություն է անհրաժեշտ" + "Դուք կկարողանաք տեսագրել ծրագրեր DVR-ի օգնությամբ: Սակայն այս պահին ձեր սարքում DVR-ի աշխատանքի համար անհրաժեշտ բավականաչափ հիշողություն չկա: Միացեք առնվազն %1$dԳԲ հիշողություն ունեցող արտաքին սարք և հետևեք ցուցումներին՝ այն որպես սարքի հիշողություն ձևաչափելու համար:" + "Բավականաչափ հիշողություն չկա" + "Այս ծրագիրը չի տեսագրվի բավականաչափ հիշողություն բացակայության պատճառով: Փորձեք ջնջել առկա տեսագրություններից մի քանիսը:" + "Հիշողությունն անհասանելի է" + "Դադարեցնե՞լ տեսագրումը:" + "Տեսագրված բովանդակությունը կպահվի:" + "%1$s ծրագրի տեսագրումը կդադարեցվի, քանի որ այն ունի հակասություններ այս ծրագրի հետ: Արդեն իսկ տեսագրված բովանդակությունը կպահվի:" + "Տեսագրումը ծրագրավորվեց, սակայն այն հակասություններ ունի" + "Տեսագրումը սկսվել է, սակայն որոշ հակասություններ ունի" + "%1$s ծրագիրը կտեսագրվի:" + "%1$s ալիքը տեսագրվում է:" + "%1$s ծրագրի որոշ հատվածներ չեն տեսագրվի:" + "%1$s և %2$s ծրագրերի որոշ հատվածներ չեն տեսագրվի:" + "%1$s, %2$s ու ևս մեկ ծրագրի որոշ հատվածներ չեն տեսագրվի:" + + Some parts of %1$s, %2$s and %3$d more schedules will not be recorded. + %1$s, %2$s ու ևս %3$d ծրագրի որոշ հատվածներ չեն տեսագրվի: + + "Ի՞նչ եք ցանկանում տեսագրել:" + "Նշեք տեսագրելու տևողությունը" + "Արդեն ծրագրավորվել է" + "Միևնույն ծրագրի տեսագրումն արդեն ծրագրավորվել է %1$s-ին:" + "Արդեն տեսագրվել է" + "Այս ծրագիրն արդեն տեսագրվել է: Այն հասանելի է DVR դարանում:" + "Սերիալի տեսագրումը ծրագրավորված է" + + %1$d recordings have been scheduled for %2$s. + %2$s-ի համար ծրագրավորված է %1$d տեսագրում: + + + %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. + %2$s-ի համար ծրագրավորված է %1$d տեսագրում: Այդ տեսագրումներից %3$d-ը հակասությունների պատճառով չեն կատարվի: + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + %2$s-ի համար ծրագրավորված է %1$d տեսագրում: Այս սերիալի %3$d դրվագներ և մյուս սերիալը հակասությունների պատճառով չեն տեսագրվի: + + + %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + %2$s-ի համար ծրագրավորված է %1$d տեսագրում: Մյուս սերիալի 1 դրվագ հակասությունների պատճառով չի տեսագրվի: + + + %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + %2$s-ի համար ծրագրավորված է %1$d տեսագրում: Մյուս սերիալի %3$d դրվագներ հակասությունների պատճառով չեն տեսագրվի: + + "Տեսագրված ծրագիրը չի գտնվել:" + "Առնչվող տեսագրություններ" + + %1$d recordings + %1$d տեսագրություն + + " / " + "%1$s ծրագիրը հեռացվել է տեսագրման ժամանակացույցից" + "Ընդունիչների հակասությունների պատճառով կտեսագրվի մասամբ:" + "Ընդունիչների հակասությունների պատճառով չի տեսագրվի:" + "Ծրագրավորված տեսագրումներ դեռ չկան:\nՏեսագրումը կարող եք ծրագրավորել հեռուստահաղորդումների ցանկից:" + + %1$d recording conflicts + Տեսագրման %1$d հակասություն + + "Սերիալների կարգավորումներ" + "Սկսել սերիալի տեսագրումը" + "Դադարեցնել սերիալի տեսագրումը" + "Դադարեցնե՞լ սերիալի տեսագրումը:" + "Տեսագրված դրվագները հասանելի կմնան միայն DVR դարանում:" + "Դադար" + "Այս պահին եթեր հեռարձակվող դրվագներ չկան:" + "Հասանելի դրվագներ չկան:\nԴրանք կտեսագրվեն հասանելի դառնալուց հետո:" + + (%1$d minutes) + (%1$d րոպե) + + "Այսօր" + "Վաղը" + "Երեկ" + "Այսօր, %1$s" + "Վաղը, %1$s" + "Գնահատական" + "Տեսագրած ծրագրեր" + diff --git a/res/values-hy-v23/strings.xml b/res/values-hy-v23/strings.xml deleted file mode 100644 index 774fbd3d..00000000 --- a/res/values-hy-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Ալիքներ" - diff --git a/res/values-hy/arrays.xml b/res/values-hy/arrays.xml deleted file mode 100644 index 5c6f0bfd..00000000 --- a/res/values-hy/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Սովորական" - "Լիցքավորված" - "Խոշորացնել" - - - "Բոլոր ալիքները" - "Ընտանիք/Երեխաներ" - "Սպորտ" - "Գնումներ" - "Ֆիլմեր" - "Կատակերգություն" - "Ճամփորդություն" - "Դրամա" - "Կրթություն" - "Կենդ./Վայրի բնություն" - "Նորություններ" - "Համակարգչային խաղեր" - "Արվեստ" - "Ժամանց" - "Ապրելակերպ" - "Երաժշտություն" - "Նորույթներ" - "Տեխնիկա/Գիտություն" - - - "Ուղիղ եթեր" - "Բովանդակության հայտնաբերման պարզ եղանակ" - "Ներբեռնեք հավելվածներ, դիտեք էլ ավելի ալիքներ" - "Անհատականացրեք ալիքների դասավորությունը" - - - "Դիտեք բովանդակությունը հավելվածներից՝ ինչպես կդիտեիք հեռուստաալիքները հեռուստացույցի միջոցով:" - "Հավելվածներից բովանդակությունը դիտեք հարմար ուղեցույցի և միջերեսի օգնությամբ՝ \nինչպես կդիտեիք հեռուստաալիքները հեռուստացույցի միջոցով:" - "Ավելացրեք ալիքներ՝ ուղիղ եթեր առաջարկող հավելվածներ տեղադրելու միջոցով: \nGoogle Play Խանութում գտեք համատեղելի հավելվածներ հեռուստացույցի ընտրացանկում պարունակվող հղման օգնությամբ:" - "Կարգավորեք նոր տեղադրած ալիքների աղբյուրները՝ ձեր ալիքների ցանկը անհատականացնելու համար: \nՍկսելու համար Կարգավորումների ընտրացանկում ընտրեք Ալիքների աղբյուրները:" - - diff --git a/res/values-hy/rating_system_strings.xml b/res/values-hy/rating_system_strings.xml deleted file mode 100644 index 8c69ca7e..00000000 --- a/res/values-hy/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Ծրագրերը կարող են պարունակել 15-ից փոքր լսարանի համար չնախատեսված նյութեր, այդ իսկ պատճառով դրանց դիտումը թույլատրվում է միայն ծնողի հայեցողությամբ:" - "Ծրագրերը կարող են պարունակել 19-ից փոքր լսարանի համար չնախատեսված նյութեր, այդ իսկ պատճառով դրանք պիտանի չեն 19 տարեկանը չլրացած երիտասարդների համար:" - "Անպարկեշտ խոսքեր" - "Կոպիտ բառեր" - "Սեռական բովանդակություն" - "Բռնության տեսարաններ" - "Բռնության տեսարաններ հրեշների մասնակցությամբ" - "Այս ծրագիրը հարմար է բոլոր տարիքի երեխաների կողմից դիտելու համար:" - "Այս ծրագիրը նախատեսված է 7 և բարձր տարիքի երեխաների համար:" - "Շատ ծնողների կարծիքով այս ծրագիրը հարմար է ցանկացած տարիքի համար:" - "Այս ծրագիրը պարունակում է որոշ նյութեր, որոնք շատ ծնողների կարծիքով անցանկալի են փոքր երեխաների համար: Հնարավոր է՝ շատ ծնողներ ցանկանան դիտել այն իրենց փոքր երեխաների հետ:" - "Այս ծրագիրը պարունակում է որոշ նյութեր, որոնք շատ ծնողների կարծիքով անցանկալի են 14 տարեկանից ցածր երեխաների համար:" - "Այս ծրագիրը հատուկ ստեղծվել է մեծահասակների կողմից դիտելու համար, ուստի կարող է պարունակել անթույլատրելի բովանդակություն՝ մինչև 17 տարեկան երեխաների համար:" - "Ֆիլմի վարկանիշները" - "Առանց տարիքային սահմանափակումների: Ծնողներիը կարող են դիտել երեխաների հետ:" - "Խորհուրդ է տրվում իրականացնել ծնողական վերահսկողություն: Կարող է պարունակել նյութեր, որոնց դիտումը փոքր երեխաների կողմից, հնարավոր է, ծնողների համար ցանկալի չլինի:" - "Ծնողները խստիվ զգուշացվում են: Որոշ նյութեր կարող են անցանկալի լինել նախապատանեկան տարիքի երեխաների կողմից դիտելու համար:" - "Ունի տարիքային սահմանափակում, քանի որ պարունակում է մեծահասակների համար նախատեսված նյութեր: Ծնողներին խորհուրդ է տրվում ավելի շատ տեղեկություններ ստանալ ֆիլմի մասին՝ նախքան իրենց փոքր երեխաների հետ այն դիտելը:" - "17 տարեկանից փոքր անձանց կողմից դիտումն արգելված է: Նախատեսված է միայն չափահասների համար: Երեխաների մուտքն արգելված է:" - diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml deleted file mode 100644 index d10652e1..00000000 --- a/res/values-hy/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "մոնո" - "ստերեո" - "Նվագարկման կառավար" - "Վերջինները" - "Հեռ. ընտրանքներ" - "PIP ընտրանքներ" - "Նվագարկման կառավարներն այս ալիքում անհասանելի են" - "Նվագարկել կամ դադարեցնել" - "Արագ առաջանցում" - "Հետանցում" - "Հաջորդը" - "Նախորդը" - "Ծրագրի ուղեցույց" - "Նոր ալիքներ" - "Բացել %1$s-ը" - "Փակ խորագրեր" - "Ցուցադրման ռեժիմ" - "PIP" - "Միացված է" - "Անջատված է" - "Բազմաուդիո" - "Ավելացնել ալիքներ" - "Կարգավորումներ" - "Աղբյուր" - "Փոխել" - "Միացված է" - "Անջատված է" - "Ձայն" - "Հիմնական" - "PIP պատուհան" - "Դասավորություն" - "Ստորին աջ" - "Վերին աջ" - "Վերին ձախ" - "Ստորին ձախ" - "Կողք կողքի" - "Չափը" - "Մեծ" - "Փոքր" - "Աղբյուր" - "Հեռուստացույց (ալեհավաք/մալուխ)" - "Ծրագրի մասին տեղեկություններ չկան" - "Տեղեկություն չկա" - "Ալիքն արգելափակված է:" - "Անհայտ լեզու" - "Փակ ենթագրեր" - "Անջատված է" - "Հարմարացնել ձևաչափը" - "Սահմանել համակարգի խորագրերի պարամետրեր" - "Ցուցադրման ռեժիմ" - "Բազմաուդիո" - "Մոնո" - "Ստերեո" - "5.1 ծավալային ձայն" - "7.1 ծավալային ձայն" - "%d հեռուստաալիք" - "Հարմարեցնել ցանկը" - "Ընտրել խումբը" - "Ապընտրել խումբը" - "Խմբավորել" - "Ալիք աղբյուրը" - "HD/SD" - "HD" - "SD" - "Խմբավորել" - "Այս ծրագիրը արգելափակված է" - "Այս ծրագրի վարկանիշը %1$s է" - "Մուտքը ինքնասկանավարում չի աջակցում" - "Չի հաջողվում սկսել «%s»-ի ավտոմատ սկանավորումը" - "Չհաջողվեց բացել խորագրերի համակարգի կարգավորումները:" - - Ավելացվեց %1$d ալիք - Ավելացվեց %1$d ալիք - - "Ալիքներ չեն ավելացվել" - "Tuner" - "Ծնողական հսկում" - "Միացված է" - "Անջատված է" - "Արգելափակված են" - "Արգելափակել բոլորը" - "Ապաարգելափակել բոլորը" - "Թաքնված ալիքներ" - "Ծր. սահմանափակումներ" - "Փոխել PIN-ը" - "Գն. համակարգեր" - "Վարկանիշ" - "Բոլոր գն. համակարգերը" - "Այլ երկրներ" - "Չկա" - "Չկա" - "Չկա" - "Խիստ սահմանափակում" - "Միջ. սահմանափակումներ" - "Թույլ սահմանափակում" - "Հատուկ" - "Բովանդակությունը հարմար է երեխաների համար" - "Բարձր տարիքային խմբի երեխաների համար" - "Բովանդակությունը հարմար է անչափահասների համար" - "Ձեռքով կարգավորման սահմանափակումներ" - - - "%1$s և ենթավարկանիշեր" - "Ենթավարկանիշեր" - "Այս ալիքը դիտելու համար մուտքագրեք PIN-ը" - "Այս ծրագիրը դիտելու համա մուտքագրեք PIN-ը" - "Այս ծրագիրն ունի տարիքային սահմանափակում՝ %1$s: Այն դիտելու համար մուտքագրեք PIN կոդը" - "Մուտքագրեք ձեր PIN-ը" - "Ծնողական վերահսկումը կարգավորելու համար PIN կոդ նախադրեք" - "Մուտքագրեք նոր PIN" - "Հաստատեք ձեր PIN-ը" - "Մուտքագրեք ձեր ներկայիս PIN-ը" - - Դուք 5 անգամ մուտքագրել եք սխալ PIN կոդ:\nՓորձեք նորից %1$d վայրկյանից: - Դուք 5 անգամ մուտքագրել եք սխալ PIN կոդ:\nՓորձեք նորից %1$d վայրկյանից: - - "PIN-ը սխալ էր: Կրկին փորձեք:" - "Փորձեք կրկին, PIN-ը չի համապատասխանում" - "Կարգավորումներ" - "Հարմարեցնել ալիքների ցանկը" - "Ընտրեք ալիքներ հեռուստահաղորդումների ծրագրի համար" - "Ալիքների աղբյուրներ" - "Առկա են նոր ալիքներ" - "Ծնողական վերահսկողություն" - "Բաց կոդով ծրագրակազմի արտոնագրեր" - "Բաց կոդով ծրագրաշարի լիցենզիաներ" - "Տարբերակ" - "Այս կապուղին դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը" - "Այս ծրագիրը դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը" - "Այս ծրագրի վարկանիշը %1$s է:\nԱյն դիտելու համար սեղմեք Աջ և մուտքագրեք PIN կոդը:" - "Այս ալիքը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:" - "Այս հաղորդումը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:" - "Այս հաղորդումը գնահատվել է որպես %1$s:\nԱյս հաղորդումը դիտելու համար օգտագործեք Live TV կենդանի կանխադրված հավելվածը:" - "Ծրագիրն արգելափակված է" - "Այս ծրագրի վարկանիշը %1$s է" - "Միայն ձայն" - "Թույլ ազդանշան" - "Համացանցի կապակցում չկա" - - This channel can\'t be played until %1$s because other channels are being recorded. \n\nPress Right to adjust recording schedule. - Այս ալիքը հնարավոր չէ ցուցադրել մինչև %1$s-ը, քանի որ ներկայումս տեսագրվում են այլ ալիքներ: \n\nՏեսագրման ժամանակացույցը կարգավորելու համար սեղմեք Աջ կոճակը: - - "Անվերնագիր" - "Ալիքն արգելափակված է" - "Նոր" - "Աղբյուրներ" - - %1$d ալիք - %1$d ալիք - - "Ալիքներ չկան" - "Նոր" - "Կարգավորված չէ" - "Ավելացնել այլ աղբյուրներ" - "Որոնեք ուղիղ եթեր առաջարկող հավելվածներ" - "Հասանելի են ալիքների նոր աղբյուրներ" - "Ալիքների նոր աղբյուրները պարունակում են նոր ալիքներ:\nԿարգավորեք դրանք հիմա կամ ավելի ուշ՝ ալիքների աղբյուրների կարգավորումների միջոցով:" - "Կարգավորել հիմա" - "Լավ, հասկացա" - - - "Հեռուստացույցի ընտրացանկից օգտվելու համար ""սեղմեք ԸՆՏՐԵԼ"":" - "Հեռուստացույցի մուտք չի գտնվել" - "Հեռուստացույցի մուտքը տեղադրված չէ" - "PIP-ը չի աջակցվում" - "PIP-ը ցուցադրելու համար աղբյուր չկա" - "Կարգավորիչի մուտքը համապատասխան չէ: Գործարկեք «Ուղիղ եթեր» հավելվածը, եթե օգտագործում եք հեռուստացույցի մուտք:" - "Կարգավորումը չհաջողվեց" - "Այս գործողությունը կատարելու համար ոչ մի հավելված չի գտնվել" - "Բոլոր ալիքները թաքնված են:\nԸնտրեք առնվազն մեկ ալիք:" - "Անսպասելի սխալ առաջացավ տեսանյութի ցուցադրման ժամանակ" - "«Հետ» կոճակը միացված սարքի համար է: Դուս գալու համար սեղմեք «Գլխավոր» կոճակը:" - "Հեռուստատեսային ծրագրերը կարդալու համար ուղիղ եթերին թույլտվություն է անհրաժեշտ:" - "Կարգավորեք աղբյուրները" - "Ուղիղ եթերը համատեղում է ավանդական հեռուստաալիքները և հավելվածների կողմից հոսքային եղանակով հեռարձակվող ալիքները: \n\nՍկսեք արդեն իսկ տեղադրված ալիքների աղբյուրները կարգավորելուց կամ Google Play Խանութում որոնեք ուղիղ եթեր առաջարկող այլ հավելվածներ:" - "Տեսագրում և ժամանակացույցեր" - "10 րոպե" - "30 րոպե" - "1 ժամ" - "3 ժամ" - "Վերջինները" - "Ծրագրավորված" - "Սերիալներ" - "Այլ" - "Հնարավոր չէ տեսագրել այս հեռուստաալիքը:" - "Հնարավոր չէ տեսագրել այս ծրագիրը:" - "%1$s ծրագրի տեսագրումը ծրագրավորվեց" - "%1$s ծրագրի տեսագրում այս պահից մինչև %2$s" - "Ամբողջական ժամանակացույց" - - Next %1$d days - %1$d օր - - - %1$d minutes - %1$d րոպե - - - %1$d new recordings - %1$d նոր տեսագրություն - - - %1$d recordings - %1$d տեսագրություն - - - %1$d recordings scheduled - Ծրագրավորվել է %1$d տեսագրություն - - "Դիտել" - "Նվագարկել սկզբից" - "Վերսկսել նվագարկումը" - "Ջնջել" - "Ջնջել տեսագրությունները" - "Շարունակել" - "Եթերաշրջան %1$s" - "Ժամանակացույց" - "Կարդալ ավելին" - "Ջնջել տեսագրումները" - "Ընտրեք դրվագները, որոնք ցանկանում եք ջնջել: Ջնջելուց հետո դրանք այլևս հնարավոր չի լինի տեսագրել:" - "Ջնջելու համար տեսագրումներ չկան" - "Նշել դիտված դրվագները" - "Նշել բոլոր դրվագները" - "Ապանշել բոլոր դրվագները" - "%2$d րոպեից դիտվել է %1$d-ը" - "%2$d վայրկյանից դիտվել է %1$d-ը" - "Երբեք չդիտված" - - %1$d of %2$d episodes are deleted - %2$d դրվագներից %1$d-ը ջնջվել է - - "Առաջնահերթություն" - "Ամենաբարձր" - "Ամենացածր" - "Ոչ: %1$d" - "Հեռուստաալիքներ" - "Ցանկացած" - "Ընտրեք առաջնահերթությունը" - "Եթե միաժամանակ ծրագրավորված է չափազանց շատ ծրագրերի տեսագրում, ապա կտեսագրվեն միայն բարձր առաջնահերթություն ունեցող ծրագրերը:" - "Պահել" - "Միանգամյա տեսագրումն ունի ամենաբարձր առաջնահերթությունը" - "Չեղարկել" - "Չեղարկել" - "Մոռանալ" - "Դադար" - "Դիտել տեսագրման ժամանակացույցը" - "Միայն այս ծրագիրը" - "այժմ - %1$s" - "Ամբողջ սերիալը…" - "Ծրագրավորել" - "Փոխարենը տեսագրել այս ծրագիրը" - "Չեղարկել այս տեսագրումը" - "Դիտել հիմա" - "Հնարավոր է տեսագրել" - "Տեսագրումը ծրագրավորված է" - "Տեսագրման հակասություն" - "Տեսագրում" - "Չհաջողվեց տեսագրել" - "Ծրագրերի ընթերցում՝ տեսագրության ժամանակացույցեր ստեղծելու համար" - "Ծրագրերի ընթերցում" - - - "DVR-ին ավելի շատ հիշողություն է անհրաժեշտ" - "Դուք կկարողանաք տեսագրել ծրագրեր DVR-ի օգնությամբ: Սակայն այս պահին ձեր սարքում DVR-ի աշխատանքի համար անհրաժեշտ բավականաչափ հիշողություն չկա: Միացեք առնվազն %1$sԳԲ հիշողություն ունեցող արտաքին սարք և հետևեք ցուցումներին՝ այն որպես սարքի հիշողություն ձևաչափելու համար:" - "Հիշողությունն անհասանելի է" - "DVR-ի կողմից օգտագործվող հիշողության մի մասն անհասանելի է: DVR-ը կրկին ակտիվացնելու համար միացրեք նախկինում օգտագործված արտաքին սարքը: Կարող եք նաև մոռանալ հիշողությունը, եթե այն այևս հասանելի չէ:" - "Մոռանա՞լ հիշողությունը:" - "Ձեր տեսագրած ամբողջ բովանդակությունը և ժամանակացույցները չեն պահվի:" - "Դադարեցնե՞լ տեսագրումը:" - "Տեսագրված բովանդակությունը կպահվի:" - - - "Տեսագրումը ծրագրավորվեց, սակայն այն հակասություններ ունի" - "Տեսագրումը սկսվել է, սակայն որոշ հակասություններ ունի" - "%1$s ծրագիրը կտեսագրվի:" - "%1$s ալիքը տեսագրվում է:" - "%1$s ծրագրի որոշ հատվածներ չեն տեսագրվի:" - "%1$s և %2$s ծրագրերի որոշ հատվածներ չեն տեսագրվի:" - "%1$s, %2$s ու ևս մեկ ծրագրի որոշ հատվածներ չեն տեսագրվի:" - - Some parts of %1$s, %2$s and %3$d more schedules will not be recorded. - %1$s, %2$s ու ևս %3$d ծրագրի որոշ հատվածներ չեն տեսագրվի: - - "Ի՞նչ եք ցանկանում տեսագրել:" - "Նշեք տեսագրելու տևողությունը" - "Արդեն ծրագրավորվել է" - "Միևնույն ծրագրի տեսագրումն արդեն ծրագրավորվել է %1$s-ին:" - "Արդեն տեսագրվել է" - "Այս ծրագիրն արդեն տեսագրվել է: Այն հասանելի է DVR դարանում:" - - - - - - - - - "Տեսագրված ծրագիրը չի գտնվել:" - "Առնչվող տեսագրություններ" - "(Ծրագիրը նկարագրություն չունի)" - - %1$d recordings - %1$d տեսագրություն - - " / " - "%1$s ծրագիրը հեռացվել է տեսագրման ժամանակացույցից" - "Ընդունիչների հակասությունների պատճառով կտեսագրվի մասամբ:" - "Ընդունիչների հակասությունների պատճառով չի տեսագրվի:" - "Ծրագրավորված տեսագրումներ դեռ չկան:\nՏեսագրումը կարող եք ծրագրավորել հեռուստահաղորդումների ցանկից:" - - %1$d recording conflicts - Տեսագրման %1$d հակասություն - - "Սերիալների կարգավորումներ" - "Սկսել սերիալի տեսագրումը" - "Դադարեցնել սերիալի տեսագրումը" - "Դադարեցնե՞լ սերիալի տեսագրումը:" - "Տեսագրված դրվագները հասանելի կմնան միայն DVR դարանում:" - "Դադար" - "Հասանելի դրվագներ չկան:\nԴրանք կտեսագրվեն հասանելի դառնալուց հետո:" - - (%1$d minutes) - (%1$d րոպե) - - "Այսօր" - "Վաղը" - "Երեկ" - "Այսօր, %1$s" - "Վաղը, %1$s" - "Գնահատական" - diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 662cfc89..33cca968 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Kontrol pemutar" - "Saluran terkini" + "Saluran" "Opsi TV" - "Opsi PIP" "Kontrol Play tidak tersedia untuk saluran ini" "Putar atau jeda" "Maju cepat" @@ -35,33 +34,15 @@ "Teks" "Mode tayang" "PIP" - "Aktif" - "Nonaktif" "Multi-audio" "Dapatkan saluran lain" "Setelan" - "Sumber" - "Tukar" - "Aktif" - "Nonaktif" - "Suara" - "Utama" - "Jendela PIP" - "Tata letak" - "Kanan bawah" - "Kanan atas" - "Kiri atas" - "Kiri bawah" - "Berdampingan" - "Ukuran" - "Besar" - "Kecil" - "Sumber masukan" "TV (antena/kabel)" "Tidak ada informasi program" "Tidak ada informasi" "Saluran yang diblokir" - "Bahasa tidak dikenal" + "Bahasa tidak dikenal" + "Subtitel, CC %1$d" "Teks" "Nonaktif" "Sesuaikan format" @@ -83,6 +64,7 @@ "SD" "Kelompokkan menurut" "Program ini diblokir" + "Program ini tidak memiliki nilai" "Program ini diberi rating %1$s" "Masukan tidak mendukung pemindaian otomatis" "Tidak dapat memulai pemindaian otomatis untuk \'%s\'" @@ -92,7 +74,6 @@ %1$d saluran ditambahkan "Tidak ditambahkan saluran" - "Tuner" "Kontrol induk" "Aktif" "Nonaktif" @@ -108,6 +89,8 @@ "Negara lain" "Tidak ada" "Tidak ada" + "Tanpa nilai" + "Blokir program tanpa nilai" "Tidak ada" "Pembatasan tinggi" "Pembatasan menengah" @@ -124,6 +107,7 @@ "Masukkan PIN untuk menonton saluran ini" "Masukkan PIN untuk menonton program ini" "Program ini memiliki rating %1$s. Masukkan PIN untuk menonton program ini" + "Program ini tidak memiliki nilai. Masukkan PIN untuk menonton program ini" "Masukkan PIN" "Untuk menyetel kontrol induk, buat PIN" "Masukkan PIN baru" @@ -135,22 +119,31 @@ "PIN salah. Coba lagi." "Pin tidak cocok, coba lagi" + "Memasukkan Kode Pos" + "Aplikasi Live TV akan menggunakan Kode Pos untuk memberikan panduan program lengkap bagi saluran TV." + "Masukkan Kode Pos" + "Kode ZIP Tidak Valid" "Setelan" "Sesuaikan daftar saluran" "Pilih saluran untuk panduan program" "Sumber saluran" "Tersedia saluran baru" "Kontrol induk" - "Lisensi sumber terbuka" - "Lisensi sumber terbuka" + "Pergantian waktu" + "Rekam sambil menonton sehingga Anda dapat menjeda atau memutar ulang program live.\nPeringatan: Tindakan ini menggunakan penyimpanan secara intensif sehingga dapat mengurangi masa pakai penyimpanan internal." + "Lisensi open source" + "Kirim masukan" "Versi" "Untuk menonton saluran ini, tekan Kanan dan masukkan PIN" "Untuk menonton program ini, tekan Kanan dan masukkan PIN" + "Program ini tidak memiliki nilai.\nUntuk menonton program ini, tekan Kanan lalu masukkan PIN" "Program ini diberi rating %1$s.\nUntuk menonton program ini, tekan Panah Kanan dan masukkan PIN." "Untuk menonton saluran ini, gunakan aplikasi TV Siaran Langsung default." "Untuk menonton program ini, gunakan aplikasi TV Siaran Langsung default." + "Program ini tidak memiliki nilai.\nUntuk menonton program ini, gunakan aplikasi TV Siaran Langsung default." "Program ini memiliki rating %1$s.\nUntuk menonton program ini, gunakan aplikasi TV Siaran Langsung default." "Program diblokir" + "Program ini tidak memiliki nilai" "Program ini diberi rating %1$s" "Audio saja" "Sinyal lemah" @@ -181,8 +174,6 @@ "Tekan PILIH"" untuk mengakses menu TV." "Tidak ditemukan masukan TV" "Tidak dapat menemukan masukan TV" - "PIP tidak didukung" - "Tidak tersedia masukan yang dapat ditampilkan dengan PIP" "Jenis tuner tidak cocok. Luncurkan aplikasi Saluran Siaran Langsung untuk masukan TV yang sesuai dengan jenis tuner" "Penalaan gagal" "Tidak ditemukan aplikasi untuk menangani tindakan ini." @@ -226,6 +217,8 @@ %1$d jadwal rekaman %1$d jadwal rekaman + "Batalkan rekaman" + "Berhenti merekam" "Tonton" "Putar dari awal" "Lanjutkan pemutaran" @@ -258,9 +251,6 @@ "Jika terlalu banyak program yang akan direkam secara bersamaan, hanya program dengan prioritas lebih tinggi yang akan direkam." "Simpan" "Perekaman satu kali memiliki prioritas tertinggi" - "Batal" - "Batal" - "Lupakan" "Berhenti" "Lihat jadwal rekaman" "Program tunggal ini" @@ -270,25 +260,28 @@ "Rekam yang ini saja" "Batalkan rekaman ini" "Tonton sekarang" + "Hapus rekaman…" "Dapat direkam" "Perekaman dijadwalkan" "Perekaman bentrok" "Merekam" "Rekaman gagal" - "Membaca program untuk membuat jadwal rekaman" - "Membaca program" - - + "Membaca program" + "Lihat rekaman baru-baru ini" + "Rekaman %1$s tidak selesai." + "Rekaman %1$s dan %2$s tidak selesai." + "Rekaman %1$s, %2$s, dan %3$s tidak selesai." + "Rekaman %1$s tidak selesai karena penyimpanan tidak mencukupi." + "Rekaman %1$s dan %2$s tidak selesai karena penyimpanan tidak mencukupi." + "Rekaman %1$s, %2$s, dan %3$s tidak selesai karena penyimpanan tidak mencukupi." "DVR memerlukan penyimpanan ekstra" - "Anda akan dapat merekam program dengan DVR. Namun, saat ini penyimpanan pada perangkat tidak cukup untuk menjalankan DVR. Hubungkan drive eksternal berukuran %1$sGB atau lebih besar dan ikuti langkah untuk memformat drive tersebut sebagai perangkat penyimpanan." + "Anda akan dapat merekam program dengan DVR. Namun, saat ini penyimpanan pada perangkat tidak cukup untuk menjalankan DVR. Hubungkan drive eksternal berukuran %1$dGB atau lebih besar dan ikuti langkah untuk memformat drive tersebut sebagai perangkat penyimpanan." + "Penyimpanan tidak cukup" + "Program ini tidak akan direkam karena penyimpanan tidak cukup. Coba menghapus beberapa rekaman yang ada." "Penyimpanan tidak ada" - "Beberapa penyimpanan yang digunakan oleh DVR tidak ada. Hubungkan drive eksternal yang sebelumnya digunakan untuk kembali mengaktifkan DVR. Atau, Anda dapat memilih untuk melupakan penyimpanan jika sudah tidak tersedia." - "Lupakan penyimpanan?" - "Semua konten dan jadwal yang direkam akan hilang." "Berhenti merekam?" "Konten yang direkam akan disimpan." - - + "Perekaman %1$s akan dihentikan karena bentrok dengan program ini. Konten yang direkam akan disimpan." "Rekaman telah dijadwalkan, tapi jadwalnya bentrok" "Rekaman telah dimulai, namun jadwalnya bentrok" "%1$s akan direkam." @@ -306,17 +299,29 @@ "Program yang sama telah dijadwalkan untuk direkam pukul %1$s." "Sudah direkam" "Program ini telah direkam. Rekaman ada di pustaka DVR." - - - - - - - - + "Perekaman serial dijadwalkan" + + %1$d rekaman telah dijadwalkan untuk %2$s. + %1$d rekaman telah dijadwalkan untuk %2$s. + + + %1$d rekaman telah dijadwalkan untuk %2$s. %3$d rekaman tersebut tidak akan direkam karena jadwalnya bentrok. + %1$d rekaman telah dijadwalkan untuk %2$s. Rekaman tersebut tidak akan direkam karena jadwalnya bentrok. + + + %1$d rekaman telah dijadwalkan untuk %2$s. %3$d episode seri ini dan seri lainnya tidak akan direkam karena jadwalnya bentrok. + %1$d rekaman telah dijadwalkan untuk %2$s. %3$d episode seri ini dan seri lainnya tidak akan direkam karena jadwalnya bentrok. + + + %1$d rekaman telah dijadwalkan untuk %2$s. 1 episode seri lainnya tidak akan direkam karena jadwalnya bentrok. + %1$d rekaman telah dijadwalkan untuk %2$s. 1 episode seri lainnya tidak akan direkam karena jadwalnya bentrok. + + + %1$d rekaman telah dijadwalkan untuk %2$s. %3$d episode seri lainnya tidak akan direkam karena jadwalnya bentrok. + %1$d rekaman telah dijadwalkan untuk %2$s. %3$d episode seri lainnya tidak akan direkam karena jadwalnya bentrok. + "Tidak ditemukan program yang direkam." "Rekaman terkait" - "(Tidak ada deskripsi program)" %1$d rekaman %1$d rekaman @@ -336,6 +341,7 @@ "Hentikan rekaman seri?" "Episode yang direkam akan tetap tersedia di pustaka DVR." "Hentikan" + "Tidak ada episode tayang sekarang." "Tidak ada episode yang tersedia.\nEpisode akan direkam setelah tersedia." (%1$d menit) @@ -347,4 +353,5 @@ "%1$s hari ini" "%1$s besok" "Skor" + "Program yang Direkam" diff --git a/res/values-is-rIS-v23/strings.xml b/res/values-is-rIS-v23/strings.xml new file mode 100644 index 00000000..2954d77b --- /dev/null +++ b/res/values-is-rIS-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Rásir" + diff --git a/res/values-is-rIS/arrays.xml b/res/values-is-rIS/arrays.xml new file mode 100644 index 00000000..27cb8655 --- /dev/null +++ b/res/values-is-rIS/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Venjulegt" + "Allur skjárinn" + "Aðdráttur" + + + "Allar rásir" + "Fjölskylduefni/barnaefni" + "Íþróttir" + "Verslun" + "Kvikmyndir" + "Grín" + "Ferðalög" + "Drama" + "Fræðsla" + "Dýr/dýralíf" + "Fréttir" + "Leikir" + "List" + "Afþreying" + "Lífsstíll" + "Tónlist" + "Úrvalsefni" + "Tækni og vísindi" + + + "Beinar útsendingar" + "Einföld leið til að uppgötva nýtt efni" + "Sæktu forrit, fáðu fleiri rásir" + "Breyttu uppröðun rása eftir þínu höfði" + + + "Horfðu á efni úr forritunum þínum eins og þú sért að horfa á sjónvarpið." + "Flettu í gegnum efni úr forritunum þínum með þægilegri leiðsögn og vinalegu notandaviðmóti, \nalveg eins og þú sért að skipta milli sjónvarpsrása." + "Bættu við fleiri rásum með því að setja upp forrit sem bjóða upp á beinar útsendingar. \nFinndu samhæfð forrit í Google Play Store með því að nota tengilinn í sjónvarpsvalmyndinni." + "Settu upp nýlega sótt inntök rása til að raða rásalistanum eftir eigin höfði. \nVeldu Inntak rása í stillingavalmyndinni til að hefjast handa." + + diff --git a/res/values-is-rIS/rating_system_strings.xml b/res/values-is-rIS/rating_system_strings.xml new file mode 100644 index 00000000..1725d040 --- /dev/null +++ b/res/values-is-rIS/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Efnið getur innihaldið atriði sem eru ekki við hæfi barna undir 15 ára aldri og foreldri skal því horfa með barninu." + "Efnið getur innihaldið atriði sem eru ekki við hæfi einstaklinga undir 19 ára aldri." + "Tvíræðar samræður" + "Óheflað málfar" + "Kynferðislegt efni" + "Ofbeldi" + "Ofbeldi í ímynduðum heimi" + "Þessi dagskrárliður er ætlaður öllum börnum." + "Þessi dagskrárliður er ætlaður börnum 7 ára og eldri." + "Flestir foreldrar myndu telja þennan dagskrárlið henta öllum aldurshópum." + "Þessi dagskrárliður inniheldur efni sem foreldrar kunna að telja óviðeigandi fyrir yngri börn. Foreldrar gætu viljað horfa á hann með yngri börnum sínum." + "Þessi dagskrárliður inniheldur efni sem margir foreldrar myndu telja óviðeigandi fyrir börn yngri en 14 ára." + "Þessi dagskrárliður er sérstaklega ætlaður fullorðnum og kann því að vera óviðeigandi fyrir börn yngri en 17 ára." + "Kvikmyndaflokkun" + "Allir aldurshópar. Ekkert sem myndi fara fyrir brjóstið á foreldrum þegar börn horfa." + "Áhorf með foreldrum æskilegt. Kann að innihalda efni sem foreldrum finnst ekki hæfa ungum börnum sínum." + "Foreldrar eindregið varaðir við. Visst efni kann að vera óviðeigandi fyrir börn undir táningsskeiði." + "Takmarkað. Inniheldur eitthvað efni ætlað fullorðnum. Foreldrar eru hvattir til að kynna sér kvikmyndina betur áður en þeir horfa á hana með ungum börnum sínum." + "Bannað áhorfendum 17 ára og yngri. Klárlega fullorðinsefni. Börnum bannaður aðgangur." + diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml new file mode 100644 index 00000000..99e84596 --- /dev/null +++ b/res/values-is-rIS/strings.xml @@ -0,0 +1,357 @@ + + + + + "einóma" + "víðóma" + "Spilunarstýringar" + "Rásir" + "Sjónvarpskostir" + "Spilunarstýringar eru ekki í boði fyrir þessa rás" + "Spila eða gera hlé" + "Spóla áfram" + "Spóla til baka" + "Áfram" + "Til baka" + "Dagskrárvísir" + "Nýjar rásir í boði" + "Opna %1$s" + "Skjátextar" + "Birtingarstill." + "Innfelld mynd" + "Multi-Audio" + "Fá fleiri rásir" + "Stillingar" + "Sjónvarp (loftnet/kapall)" + "Engar dagskrárupplýsingar" + "Engar upplýsingar" + "Læst rás" + "Óþekkt tungumál" + "Skjátexti %1$d" + "Skjátextar" + "Slökkt" + "Sérstilla snið" + "Stilla skjátextaval fyrir allt kerfið" + "Birtingarstilling" + "Multi-Audio" + "einóma" + "víðóma" + "5.1 surround" + "7.1 surround" + "%d rásir" + "Sérsníða rásalista" + "Velja hóp" + "Taka val af hóp" + "Flokka eftir" + "Inntak rása" + "Háskerpa/staðalgæði" + "Háskerpa" + "Staðalgæði" + "Flokka eftir" + "Þessi dagskrárliður er læstur" + "Þessi þáttur er óflokkaður" + "Þessi dagskrárliður er flokkaður sem %1$s" + "Inntakið styður ekki sjálfvirka skönnun" + "Ekki tókst að hefja sjálfvirka skönnun fyrir „%s“" + "Ekki er hægt að opna skjátextastillingar fyrir kerfið allt." + + %1$d rás bætt við + %1$d rásum bætt við + + "Engum rásum bætt við" + "Foreldraeftirlit" + "Kveikt" + "Slökkt" + "Útilokaðar rásir" + "Útiloka allar" + "Opna allar" + "Faldar rásir" + "Aldurstakmark" + "Breyta PIN-númeri" + "Flokkunarkerfi" + "Flokkun" + "Sjá flokkunarkerfi" + "Önnur lönd" + "Engin" + "Ekkert" + "Óflokkað" + "Útiloka þætti án flokkunar" + "Ekkert" + "Miklar takmarkanir" + "Miðlungs takmarkanir" + "Litlar takmarkanir" + "Sérsniðið" + "Efni sem hentar fyrir börn" + "Efni sem hentar eldri börnum" + "Efni sem hentar fyrir unglinga" + "Handstillt aldurstakmark" + + + "%1$s og undirflokkar" + "Undirflokkar" + "Sláðu inn PIN-númerið til að horfa á þessa rás" + "Sláðu inn PIN-númerið til að horfa á þennan þátt" + "Þessi þáttur er flokkaður sem %1$s. Sláðu inn PIN-númerið til að horfa á þáttinn" + "Þessi þáttur er óflokkaður. Sláðu inn PIN-númerið til að horfa á þáttinn" + "Sláðu inn PIN-númerið" + "Til að stilla foreldraeftirlit skaltu búa til PIN-númer" + "Sláðu inn nýtt PIN-númer" + "Staðfestu PIN-númerið" + "Sláðu inn núverandi PIN-númerið þitt" + + Þú slóst fimm sinnum inn rangt PIN-númer.\nReyndur aftur eftir %1$d sekúndu. + Þú slóst fimm sinnum inn rangt PIN-númer.\nReyndur aftur eftir %1$d sekúndur. + + "Rangt PIN-númer. Reyndu aftur." + "Reyndu aftur; PIN-númerin stemma ekki" + "Sláðu inn póstnúmerið þitt." + "Forritið Beinar útsendingar notar póstnúmerið þitt til að veita þér nákvæma sjónvarpsdagskrá." + "Sláðu inn póstnúmerið þitt" + "Ógilt póstnúmer" + "Stillingar" + "Sérsníða rásalista" + "Veldu rásir fyrir dagskrárvísinn þinn" + "Inntak rása" + "Nýjar rásir í boði" + "Foreldraeftirlit" + "Tímabreyting" + "Taktu upp á meðan þú horfir svo þú getir gert hlé eða spólað til baka á útsendingu í beinni.\nViðvörun: Þetta getur dregið úr líftíma innbyggðrar geymslu vegna mikillar notkunar." + "Leyfi opins kóða" + "Senda ábendingu" + "Útgáfa" + "Til að horfa á þessa rás skaltu ýta til hægri og slá inn PIN-númerið þitt" + "Til að horfa á þennan þátt skaltu ýta til hægri og slá inn PIN-númerið þitt" + "Þessi þáttur er óflokkaður.\nTil að horfa á þennan þátt skaltu ýta til hægri og slá inn PIN-númerið" + "Þessi þáttur er flokkaður sem %1$s.\nTil að horfa á þáttinn skaltu ýta til hægri og slá inn PIN-númerið þitt." + "Til að horfa á þessa rás skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." + "Til að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." + "Þessi þáttur er óflokkaður.\nTil að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." + "Þessi þáttur er flokkaður sem %1$s.\nTil að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." + "Þátturinn er læstur" + "Þessi þáttur er óflokkaður" + "Þessi dagskrárliður er flokkaður sem %1$s" + "Aðeins hljóð" + "Lítill sendistyrkur" + "Engin nettenging" + + Ekki er hægt að spila þessa rás fyrr en %1$s þar sem verið er að taka upp aðrar rásir. \n\nÝttu á hægri takkann til að breyta upptökuáætluninni. + Ekki er hægt að spila þessa rás fyrr en %1$s þar sem verið er að taka upp aðrar rásir. \n\nÝttu á hægri takkann til að breyta upptökuáætluninni. + + "Ekkert heiti" + "Rás læst" + "Ný" + "Veitur" + + %1$d rás + %1$d rásir + + "Engar rásir í boði" + "Nýtt" + "Ekki uppsett" + "Sækja fleiri veitur" + "Skoða forrit sem bjóða upp á beinar útsendingar" + "Nýjar sjónvarpsveitur í boði" + "Nýjum sjónvarpsveitum fylgja nýjar sjónvarpsrásir.\nSettu þær upp núna eða gerðu það síðar í stillingunum fyrir inntak rása." + "Setja upp núna" + "Ég skil" + + + "Ýttu á VELJA"" til að fá aðgang að sjónvarpsvalmyndinni." + "Ekkert sjónvarpsinntak fannst" + "Sjónvarpsinntak finnst ekki" + "Gerð móttakara passar ekki. Ræstu forritið Rásir í beinni til að fá sjónvarpsinntak með móttakaragerð." + "Stilling mistókst" + "Ekkert forrit fannst sem getur framkvæmt þessa aðgerð." + "Allar inntaksrásir eru faldar.\nVeldu minnst eina rás til að horfa á." + "Myndskeiðið er óvænt ekki tiltækt" + "Til baka-lykillinn er fyrir tengd tæki. Ýttu á heimahnappinn til að hætta." + "Rásir í beinni þurfa heimild til að lesa sjónvarpsdagskrána." + "Setja upp inntök rása" + "Beinar útsendingar sameina þá upplifun að horfa á venjulegar sjónvarpsstöðvar og straumspila rásir í gegnum forrit. \n\nByrjaðu á því að setja upp þau inntök rása sem þegar hafa verið sótt eða farðu í Google Play Store til að skoða fleiri forrit sem bjóða upp á beinar útsendingar." + "Upptökur og áætlanir" + "10 mínútur" + "30 mínútur" + "1 klukkustund" + "3 klukkustundir" + "Nýlegt" + "Áætlað" + "Þáttaröð" + "Annað" + "Ekki er hægt að taka upp rásina." + "Ekki er hægt að taka upp dagskrárliðinn." + "%1$s var sett á upptökuáætlun" + "Tekur upp %1$s núna og þangað til %2$s" + "Heildaráætlun" + + Næsti %1$d dagur + Næstu %1$d dagar + + + %1$d mínúta + %1$d mínútur + + + %1$d ný upptaka + %1$d nýjar upptökur + + + %1$d upptaka + %1$d upptökur + + + %1$d upptaka á áætlun + %1$d upptökur á áætlun + + "Hætta við upptöku" + "Stöðva upptöku" + "Horfa" + "Spila frá upphafi" + "Halda spilun áfram" + "Eyða" + "Eyða upptökum" + "Halda áfram" + "Þáttaröð %1$s" + "Skoða dagskrá" + "Lesa meira" + "Eyða upptökum" + "Veldu þætti sem þú vilt eyða. Ekki er hægt að endurheimta þætti eftir að þeim hefur verið eytt." + "Engar upptökur til að eyða" + "Velja þætti sem horft var á" + "Velja alla þætti" + "Hætta við val á öllum þáttum" + "Horft á %1$d af %2$d mínútum" + "Horft á %1$d af %2$d sekúndum" + "Aldrei horft á" + + %1$d af %2$d þætti er eytt + %1$d af %2$d þáttum er eytt + + "Forgangur" + "Mestur" + "Mjög lítill" + "Nei. %1$d" + "Stöðvar" + "Hvað sem er" + "Veldu forgang" + "Þegar taka á upp of marga þætti á sama tíma eru aðeins þættir sem eru í forgangi teknir upp." + "Vista" + "Stakar upptökur hafa forgang" + "Stöðva" + "Skoða upptökuáætlun" + "Bara þennan þátt" + "núna – %1$s" + "Alla þáttaröðina…" + "Setja samt á áætlun" + "Taka þetta upp í staðinn" + "Hætta við þessa upptöku" + "Horfa núna" + "Eyða upptökum…" + "Hægt að taka upp" + "Upptaka sett á áætlun" + "Skarast á við aðra upptöku" + "Upptaka í gangi" + "Upptaka mistókst" + "Skoðar þætti" + "Skoða nýlegar upptökur" + "Upptöku á %1$s er ekki lokið." + "Upptöku á %1$s og %2$s er ekki lokið." + "Upptöku á %1$s, %2$s og %3$s er ekki lokið." + "Skortur á geymslurými kom í veg fyrir að upptöku á %1$s væri lokið." + "Skortur á geymslurými kom í veg fyrir að upptöku á %1$s og %2$s væri lokið." + "Skortur á geymslurými kom í veg fyrir að upptöku á %1$s, %2$s og %3$s væri lokið." + "DVR þarf meira geymslupláss" + "Þú getur tekið upp þætti með stafræna upptökubúnaðinum (DVR). Hins vegar er ekki nóg geymslupláss til staðar á tækinu þínu sem stendur til að DVR virki. Tengdu utanáliggjandi disk við tækið sem er %1$d GB eða stærri og fylgdu skrefunum til að setja það upp sem geymslupláss tækisins." + "Of lítið geymslurými" + "Þátturinn verður ekki tekinn upp þar sem of lítið geymslurými er til staðar. Prófaðu að eyða einhverjum af þeim upptökum sem eru þegar til staðar." + "Geymslu vantar" + "Stöðva upptöku?" + "Upptökur á efni verða vistaðar." + "Upptöku á %1$s verður hætt þar sem það skarast á við þennan þátt. Upptekið efni verður vistað." + "Upptaka á áætlun en skarast við aðra" + "Upptaka er hafin en hún skarast við aðra" + "%1$s verður tekið upp." + "Upptaka í gangi á %1$s." + "Einhverjir hlutar af %1$s verða ekki teknir upp." + "Einhverjir hlutar af %1$s og %2$s verða ekki teknir upp." + "Einhverjir hlutar af %1$s, %2$s og einu dagskráratriði í viðbót verða ekki teknir upp." + + Einhverjir hlutar af %1$s, %2$s og %3$d í viðbót verða ekki teknir upp. + Einhverjir hlutar af %1$s, %2$s og %3$d í viðbót verða ekki teknir upp. + + "Hvað viltu taka upp?" + "Hversu lengi viltu taka upp?" + "Þegar áætlað" + "Sami þáttur er þegar á upptökuáætlun kl. %1$s." + "Þegar tekið upp" + "Þessi þáttur hefur þegar verið tekinn upp. Hann er tiltækur í DVR-safninu." + "Upptaka þáttaseríu sett á áætlun" + + %1$d upptaka var sett á áætlun fyrir %2$s. + %1$d upptökur voru settar á áætlun fyrir %2$s. + + + %1$d upptökur voru settar á áætlun fyrir %2$s. %3$d verður ekki tekið upp því að það skarast á við annað. + %1$d upptökur voru settar á áætlun fyrir %2$s. %3$d verður ekki tekið upp því að það skarast á við annað. + + + %1$d upptaka var sett á áætlun fyrir %2$s. %3$d þættir úr þessari þáttaröð og í öðrum þáttaröðum verða ekki teknir upp því það skarast á við annað. + %1$d upptökur voru settar á áætlun fyrir %2$s. %3$d þættir úr þessari þáttaröð og í öðrum þáttaröðum verða ekki teknir upp því það skarast á við annað. + + + %1$d upptaka var sett á áætlun fyrir %2$s. Einn þáttur í annarri þáttaröð verður ekki tekinn upp því að hann skarast á við annað. + %1$d upptökur voru settar á áætlun fyrir %2$s. Einn þáttur í annarri þáttaröð verður ekki tekinn upp því að hann skarast á við annað. + + + %1$d upptaka var sett á áætlun fyrir %2$s. %3$d þættir úr annarri þáttaröð verða ekki teknir upp því þeir skarast á við annað. + %1$d upptökur voru settar á áætlun fyrir %2$s. %3$d þættir úr annarri þáttaröð verða ekki teknir upp því þeir skarast á við annað. + + "Upptekinn þáttur fannst ekki." + "Tengdar upptökur" + + %1$d upptaka + %1$d upptökur + + " / " + "%1$s fjarlægt úr upptökuáætlun" + "Verður tekið upp að hluta til vegna árekstra við stilli." + "Verður ekki tekið upp vegna árekstra við stilli." + "Engar upptökur eru á áætlun enn sem komið er.\nÞú getur bætt upptöku við áætlun í dagskrárvísinum." + + %1$d árekstur í upptöku + %1$d árekstrar í upptöku + + "Þáttaraðastillingar" + "Hefja upptöku á þáttaröð" + "Hætta upptöku á þáttaröð" + "Hætta upptöku á þáttaröð?" + "Upptökur af þáttum verða áfram tiltækar í DVR-safninu." + "Stöðva" + "Ekki er verið að sýna þætti að svo stöddu." + "Engir þættir eru tiltækir.\nÞeir verða teknir upp þegar þeir eru tiltækir." + + (%1$d mínúta) + (%1$d mínútur) + + "Í dag" + "Á morgun" + "Í gær" + "%1$s í dag" + "%1$s á morgun" + "Einkunn" + "Upptökur af þáttum" + diff --git a/res/values-is-v23/strings.xml b/res/values-is-v23/strings.xml deleted file mode 100644 index 2954d77b..00000000 --- a/res/values-is-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Rásir" - diff --git a/res/values-is/arrays.xml b/res/values-is/arrays.xml deleted file mode 100644 index 27cb8655..00000000 --- a/res/values-is/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Venjulegt" - "Allur skjárinn" - "Aðdráttur" - - - "Allar rásir" - "Fjölskylduefni/barnaefni" - "Íþróttir" - "Verslun" - "Kvikmyndir" - "Grín" - "Ferðalög" - "Drama" - "Fræðsla" - "Dýr/dýralíf" - "Fréttir" - "Leikir" - "List" - "Afþreying" - "Lífsstíll" - "Tónlist" - "Úrvalsefni" - "Tækni og vísindi" - - - "Beinar útsendingar" - "Einföld leið til að uppgötva nýtt efni" - "Sæktu forrit, fáðu fleiri rásir" - "Breyttu uppröðun rása eftir þínu höfði" - - - "Horfðu á efni úr forritunum þínum eins og þú sért að horfa á sjónvarpið." - "Flettu í gegnum efni úr forritunum þínum með þægilegri leiðsögn og vinalegu notandaviðmóti, \nalveg eins og þú sért að skipta milli sjónvarpsrása." - "Bættu við fleiri rásum með því að setja upp forrit sem bjóða upp á beinar útsendingar. \nFinndu samhæfð forrit í Google Play Store með því að nota tengilinn í sjónvarpsvalmyndinni." - "Settu upp nýlega sótt inntök rása til að raða rásalistanum eftir eigin höfði. \nVeldu Inntak rása í stillingavalmyndinni til að hefjast handa." - - diff --git a/res/values-is/rating_system_strings.xml b/res/values-is/rating_system_strings.xml deleted file mode 100644 index 1725d040..00000000 --- a/res/values-is/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Efnið getur innihaldið atriði sem eru ekki við hæfi barna undir 15 ára aldri og foreldri skal því horfa með barninu." - "Efnið getur innihaldið atriði sem eru ekki við hæfi einstaklinga undir 19 ára aldri." - "Tvíræðar samræður" - "Óheflað málfar" - "Kynferðislegt efni" - "Ofbeldi" - "Ofbeldi í ímynduðum heimi" - "Þessi dagskrárliður er ætlaður öllum börnum." - "Þessi dagskrárliður er ætlaður börnum 7 ára og eldri." - "Flestir foreldrar myndu telja þennan dagskrárlið henta öllum aldurshópum." - "Þessi dagskrárliður inniheldur efni sem foreldrar kunna að telja óviðeigandi fyrir yngri börn. Foreldrar gætu viljað horfa á hann með yngri börnum sínum." - "Þessi dagskrárliður inniheldur efni sem margir foreldrar myndu telja óviðeigandi fyrir börn yngri en 14 ára." - "Þessi dagskrárliður er sérstaklega ætlaður fullorðnum og kann því að vera óviðeigandi fyrir börn yngri en 17 ára." - "Kvikmyndaflokkun" - "Allir aldurshópar. Ekkert sem myndi fara fyrir brjóstið á foreldrum þegar börn horfa." - "Áhorf með foreldrum æskilegt. Kann að innihalda efni sem foreldrum finnst ekki hæfa ungum börnum sínum." - "Foreldrar eindregið varaðir við. Visst efni kann að vera óviðeigandi fyrir börn undir táningsskeiði." - "Takmarkað. Inniheldur eitthvað efni ætlað fullorðnum. Foreldrar eru hvattir til að kynna sér kvikmyndina betur áður en þeir horfa á hana með ungum börnum sínum." - "Bannað áhorfendum 17 ára og yngri. Klárlega fullorðinsefni. Börnum bannaður aðgangur." - diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml deleted file mode 100644 index 6111b578..00000000 --- a/res/values-is/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "einóma" - "víðóma" - "Spilunarstýringar" - "Nýlegar rásir" - "Sjónvarpskostir" - "Innfelld mynd" - "Spilunarstýringar eru ekki í boði fyrir þessa rás" - "Spila eða gera hlé" - "Spóla áfram" - "Spóla til baka" - "Áfram" - "Til baka" - "Dagskrárvísir" - "Nýjar rásir í boði" - "Opna %1$s" - "Skjátextar" - "Birtingarstill." - "Innfelld mynd" - "Kveikt" - "Slökkt" - "Multi-Audio" - "Fá fleiri rásir" - "Stillingar" - "Inntak" - "Víxla" - "Kveikt" - "Slökkt" - "Hljóð" - "Aðalgluggi" - "Innfelling" - "Útlit" - "Neðst til hægri" - "Efst til hægri" - "Efst til vinstri" - "Neðst til vinstri" - "Hlið við hlið" - "Stærð" - "Stór" - "Lítil" - "Inntak" - "Sjónvarp (loftnet/kapall)" - "Engar dagskrárupplýsingar" - "Engar upplýsingar" - "Læst rás" - "Óþekkt tungumál" - "Skjátextar" - "Slökkt" - "Sérstilla snið" - "Stilla skjátextaval fyrir allt kerfið" - "Birtingarstilling" - "Multi-Audio" - "einóma" - "víðóma" - "5.1 surround" - "7.1 surround" - "%d rásir" - "Sérsníða rásalista" - "Velja hóp" - "Taka val af hóp" - "Flokka eftir" - "Inntak rása" - "Háskerpa/staðalgæði" - "Háskerpa" - "Staðalgæði" - "Flokka eftir" - "Þessi dagskrárliður er læstur" - "Þessi dagskrárliður er flokkaður sem %1$s" - "Inntakið styður ekki sjálfvirka skönnun" - "Ekki tókst að hefja sjálfvirka skönnun fyrir „%s“" - "Ekki er hægt að opna skjátextastillingar fyrir kerfið allt." - - %1$d rás bætt við - %1$d rásum bætt við - - "Engum rásum bætt við" - "Stillir" - "Foreldraeftirlit" - "Kveikt" - "Slökkt" - "Útilokaðar rásir" - "Útiloka allar" - "Opna allar" - "Faldar rásir" - "Aldurstakmark" - "Breyta PIN-númeri" - "Flokkunarkerfi" - "Flokkun" - "Sjá flokkunarkerfi" - "Önnur lönd" - "Engin" - "Ekkert" - "Ekkert" - "Miklar takmarkanir" - "Miðlungs takmarkanir" - "Litlar takmarkanir" - "Sérsniðið" - "Efni sem hentar fyrir börn" - "Efni sem hentar eldri börnum" - "Efni sem hentar fyrir unglinga" - "Handstillt aldurstakmark" - - - "%1$s og undirflokkar" - "Undirflokkar" - "Sláðu inn PIN-númerið til að horfa á þessa rás" - "Sláðu inn PIN-númerið til að horfa á þennan þátt" - "Þessi þáttur er flokkaður sem %1$s. Sláðu inn PIN-númerið til að horfa á þáttinn" - "Sláðu inn PIN-númerið" - "Til að stilla foreldraeftirlit skaltu búa til PIN-númer" - "Sláðu inn nýtt PIN-númer" - "Staðfestu PIN-númerið" - "Sláðu inn núverandi PIN-númerið þitt" - - Þú slóst fimm sinnum inn rangt PIN-númer.\nReyndur aftur eftir %1$d sekúndu. - Þú slóst fimm sinnum inn rangt PIN-númer.\nReyndur aftur eftir %1$d sekúndur. - - "Rangt PIN-númer. Reyndu aftur." - "Reyndu aftur; PIN-númerin stemma ekki" - "Stillingar" - "Sérsníða rásalista" - "Veldu rásir fyrir dagskrárvísinn þinn" - "Inntak rása" - "Nýjar rásir í boði" - "Foreldraeftirlit" - "Leyfi opins kóða" - "Leyfi opins kóða" - "Útgáfa" - "Til að horfa á þessa rás skaltu ýta til hægri og slá inn PIN-númerið þitt" - "Til að horfa á þennan þátt skaltu ýta til hægri og slá inn PIN-númerið þitt" - "Þessi þáttur er flokkaður sem %1$s.\nTil að horfa á þáttinn skaltu ýta til hægri og slá inn PIN-númerið þitt." - "Til að horfa á þessa rás skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." - "Til að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." - "Þessi þáttur er flokkaður sem %1$s.\nTil að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu." - "Þátturinn er læstur" - "Þessi dagskrárliður er flokkaður sem %1$s" - "Aðeins hljóð" - "Lítill sendistyrkur" - "Engin nettenging" - - Ekki er hægt að spila þessa rás fyrr en %1$s þar sem verið er að taka upp aðrar rásir. \n\nÝttu á hægri takkann til að breyta upptökuáætluninni. - Ekki er hægt að spila þessa rás fyrr en %1$s þar sem verið er að taka upp aðrar rásir. \n\nÝttu á hægri takkann til að breyta upptökuáætluninni. - - "Ekkert heiti" - "Rás læst" - "Ný" - "Veitur" - - %1$d rás - %1$d rásir - - "Engar rásir í boði" - "Nýtt" - "Ekki uppsett" - "Sækja fleiri veitur" - "Skoða forrit sem bjóða upp á beinar útsendingar" - "Nýjar sjónvarpsveitur í boði" - "Nýjum sjónvarpsveitum fylgja nýjar sjónvarpsrásir.\nSettu þær upp núna eða gerðu það síðar í stillingunum fyrir inntak rása." - "Setja upp núna" - "Ég skil" - - - "Ýttu á VELJA"" til að fá aðgang að sjónvarpsvalmyndinni." - "Ekkert sjónvarpsinntak fannst" - "Sjónvarpsinntak finnst ekki" - "Ekki er stuðningur við innfellda mynd" - "Ekkert inntak er fyrir hendi til að nota í innfelldri mynd" - "Gerð móttakara passar ekki. Ræstu forritið Rásir í beinni til að fá sjónvarpsinntak með móttakaragerð." - "Stilling mistókst" - "Ekkert forrit fannst sem getur framkvæmt þessa aðgerð." - "Allar inntaksrásir eru faldar.\nVeldu minnst eina rás til að horfa á." - "Myndskeiðið er óvænt ekki tiltækt" - "Til baka-lykillinn er fyrir tengd tæki. Ýttu á heimahnappinn til að hætta." - "Rásir í beinni þurfa heimild til að lesa sjónvarpsdagskrána." - "Setja upp inntök rása" - "Beinar útsendingar sameina þá upplifun að horfa á venjulegar sjónvarpsstöðvar og straumspila rásir í gegnum forrit. \n\nByrjaðu á því að setja upp þau inntök rása sem þegar hafa verið sótt eða farðu í Google Play Store til að skoða fleiri forrit sem bjóða upp á beinar útsendingar." - "Upptökur og áætlanir" - "10 mínútur" - "30 mínútur" - "1 klukkustund" - "3 klukkustundir" - "Nýlegt" - "Áætlað" - "Þáttaröð" - "Annað" - "Ekki er hægt að taka upp rásina." - "Ekki er hægt að taka upp dagskrárliðinn." - "%1$s var sett á upptökuáætlun" - "Tekur upp %1$s núna og þangað til %2$s" - "Heildaráætlun" - - Næsti %1$d dagur - Næstu %1$d dagar - - - %1$d mínúta - %1$d mínútur - - - %1$d ný upptaka - %1$d nýjar upptökur - - - %1$d upptaka - %1$d upptökur - - - %1$d upptaka á áætlun - %1$d upptökur á áætlun - - "Horfa" - "Spila frá upphafi" - "Halda spilun áfram" - "Eyða" - "Eyða upptökum" - "Halda áfram" - "Þáttaröð %1$s" - "Skoða dagskrá" - "Lesa meira" - "Eyða upptökum" - "Veldu þætti sem þú vilt eyða. Ekki er hægt að endurheimta þætti eftir að þeim hefur verið eytt." - "Engar upptökur til að eyða" - "Velja þætti sem horft var á" - "Velja alla þætti" - "Hætta við val á öllum þáttum" - "Horft á %1$d af %2$d mínútum" - "Horft á %1$d af %2$d sekúndum" - "Aldrei horft á" - - %1$d af %2$d þætti er eytt - %1$d af %2$d þáttum er eytt - - "Forgangur" - "Mestur" - "Mjög lítill" - "Nei. %1$d" - "Stöðvar" - "Hvað sem er" - "Veldu forgang" - "Þegar taka á upp of marga þætti á sama tíma eru aðeins þættir sem eru í forgangi teknir upp." - "Vista" - "Stakar upptökur hafa forgang" - "Hætta við" - "Hætta við" - "Gleyma" - "Stöðva" - "Skoða upptökuáætlun" - "Bara þennan þátt" - "núna – %1$s" - "Alla þáttaröðina…" - "Setja samt á áætlun" - "Taka þetta upp í staðinn" - "Hætta við þessa upptöku" - "Horfa núna" - "Hægt að taka upp" - "Upptaka sett á áætlun" - "Skarast á við aðra upptöku" - "Upptaka í gangi" - "Upptaka mistókst" - "Les dagskrár til að búa til upptökuáætlanir" - "Les dagskrár" - - - "DVR þarf meira geymslupláss" - "Þú getur tekið upp þætti með stafræna upptökubúnaðinum (DVR). Hins vegar er ekki nóg geymslupláss til staðar á tækinu þínu sem stendur til að DVR virki. Tengdu utanáliggjandi disk við tækið sem er %1$s GB eða stærri og fylgdu skrefunum til að setja það upp sem geymslupláss tækisins." - "Geymslu vantar" - "Hluta af geymslurýminu sem stafræni upptökubúnaðurinn notar vantar. Tengdu utanáliggjandi drif sem þú notaðir áður til að gera stafræna upptökubúnaðinn virkan á ný. Einnig geturðu valið að gleyma geymslunni ef hún er ekki lengur tiltæk." - "Viltu gleyma geymslunni?" - "Allt efni sem þú hefur tekið upp og allar dagskrár glatast." - "Stöðva upptöku?" - "Upptökur á efni verða vistaðar." - - - "Upptaka á áætlun en skarast við aðra" - "Upptaka er hafin en hún skarast við aðra" - "%1$s verður tekið upp." - "Upptaka í gangi á %1$s." - "Einhverjir hlutar af %1$s verða ekki teknir upp." - "Einhverjir hlutar af %1$s og %2$s verða ekki teknir upp." - "Einhverjir hlutar af %1$s, %2$s og einu dagskráratriði í viðbót verða ekki teknir upp." - - Einhverjir hlutar af %1$s, %2$s og %3$d í viðbót verða ekki teknir upp. - Einhverjir hlutar af %1$s, %2$s og %3$d í viðbót verða ekki teknir upp. - - "Hvað viltu taka upp?" - "Hversu lengi viltu taka upp?" - "Þegar áætlað" - "Sami þáttur er þegar á upptökuáætlun kl. %1$s." - "Þegar tekið upp" - "Þessi þáttur hefur þegar verið tekinn upp. Hann er tiltækur í DVR-safninu." - - - - - - - - - "Upptekinn þáttur fannst ekki." - "Tengdar upptökur" - "(Engin þáttalýsing)" - - %1$d upptaka - %1$d upptökur - - " / " - "%1$s fjarlægt úr upptökuáætlun" - "Verður tekið upp að hluta til vegna árekstra við stilli." - "Verður ekki tekið upp vegna árekstra við stilli." - "Engar upptökur eru á áætlun enn sem komið er.\nÞú getur bætt upptöku við áætlun í dagskrárvísinum." - - %1$d árekstur í upptöku - %1$d árekstrar í upptöku - - "Þáttaraðastillingar" - "Hefja upptöku á þáttaröð" - "Hætta upptöku á þáttaröð" - "Hætta upptöku á þáttaröð?" - "Upptökur af þáttum verða áfram tiltækar í DVR-safninu." - "Stöðva" - "Engir þættir eru tiltækir.\nÞeir verða teknir upp þegar þeir eru tiltækir." - - (%1$d mínúta) - (%1$d mínútur) - - "Í dag" - "Á morgun" - "Í gær" - "%1$s í dag" - "%1$s á morgun" - "Einkunn" - diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index da0b48a6..3f00e561 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Controlli riproduzione" - "Canali recenti" + "Canali" "Opzioni TV" - "Opzioni PIP" "Controlli di riproduzione non disponibili per questo canale" "Riproduci o metti in pausa" "Avanza velocemente" @@ -35,39 +34,21 @@ "Sottotitoli" "Visualizzazione" "PIP" - "Attiva" - "Non attiva" - "Multi-audio" + "Audio multilingua" "Trova altri canali" "Impostazioni" - "Fonte" - "Scambia" - "Attiva" - "Non attiva" - "Suono" - "Principale" - "Finestra PIP" - "Layout" - "In basso a des." - "In alto a des." - "In alto a sin." - "In basso a sin." - "Affiancata" - "Dimensioni" - "Grande" - "Piccola" - "Origine ingresso" "TV (antenna/cavo)" "Nessuna informazione sul programma" "Nessuna informazione" "Canale bloccato" - "Lingua sconosciuta" + "Lingua sconosciuta" + "Sottotitoli in %1$d" "Sottotitoli" "Off" "Personalizza" - "Imposta pref. di sistema per sottotitoli" + "Imposta preferenze di sistema per i sottotitoli" "Visualizzazione" - "Multi-audio" + "Audio multilingua" "mono" "stereo" "Surround 5.1" @@ -83,6 +64,7 @@ "Bassa definizione" "Raggruppa per" "Questo programma è bloccato" + "Questo programma non è classificato" "Questo programma è classificato come %1$s" "L\'ingresso non supporta la ricerca automatica" "Impossibile avviare la ricerca automatica per \"%s\"" @@ -92,7 +74,6 @@ %1$d canale aggiunto "Nessun canale aggiunto" - "Sintonizzatore" "Controllo genitori" "Attivo" "Non attivo" @@ -108,6 +89,8 @@ "Altri Paesi" "Nessuno" "Nessuna" + "Senza classificazione" + "Blocca i non classificati" "Nessuna" "Limitazioni alte" "Limitazioni medie" @@ -126,6 +109,7 @@ "Inserisci il codice PIN per guardare questo canale" "Inserisci il codice PIN per guardare questo programma" "La valutazione di questo programma è %1$s. Inserisci il tuo PIN per guardare il programma" + "Questo programma non è classificato. Per guardarlo, inserisci il codice PIN." "Inserisci il codice PIN" "Per impostare il filtro famiglia, crea un codice PIN" "Inserisci il nuovo PIN" @@ -137,22 +121,31 @@ "Il PIN è errato. Riprova." "Riprova, il PIN non corrisponde" + "Inserisci il codice postale." + "L\'app Dirette TV userà il codice postale per fornire una guida ai programmi completa per i canali TV." + "Inserisci il codice postale" + "Codice postale non valido" "Impostazioni" "Personalizza canali" "Scegli canali per guida ai programmi" "Fonti canali" "Nuovi canali disponibili" "Controllo genitori" + "Timeshift" + "Registra i programmi in diretta mentre li guardi per poterli mettere in pausa o mandarli indietro.\nAvviso: l\'uso intensivo della memoria interna potrebbe ridurne la durata." "Licenze open source" - "Licenze open source" + "Invia feedback" "Versione" "Per guardare questo canale, premi il pulsante destro e inserisci il PIN" "Per guardare questo programma, premi il pulsante destro e inserisci il PIN" + "Questo programma non è classificato.\nPer guardarlo, premi il pulsante destro e inserisci il codice PIN" "Questo programma è classificato come %1$s.\nPer guardarlo, premi il tasto destro e inserisci il PIN." "Per guardare questo canale, utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale." "Per guardare questo programma utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale." + "Questo programma non è classificato.\nPer guardarlo, utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale." "La valutazione di questo programma è %1$s.\nPer guardare questo programma utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale." "Il programma è bloccato" + "Questo programma non è classificato" "Questo programma è classificato come %1$s" "Solo audio" "Segnale debole" @@ -170,7 +163,7 @@ %1$d canale "Nessun canale disponibile" - "Elementi nuovi" + "Nuovo" "Non configurato" "Trova altre fonti" "Trova app che offrono dirette TV" @@ -183,17 +176,15 @@ "Premi SELEZIONA"" per accedere al menu della TV." "Nessun ingresso TV trovato" "Impossibile trovare l\'ingresso TV" - "Funzione PIP non supportata" - "Nessun ingresso disponibile per visualizzazione PIP" - "Tipo di sintonizzatore non adatto. Avvia l\'app Live TV per l\'ingresso TV di tipo sintonizzatore." + "Tipo di sintonizzatore non adatto. Avvia l\'app Dirette TV per l\'ingresso TV di tipo sintonizzatore." "Sintonizzazione non riuscita" "Nessuna app trovata per gestire questa azione." "Tutti i canali di origine sono nascosti.\nSeleziona almeno un canale da guardare." "Il video è improvvisamente non disponibile" "Il tasto INDIETRO è per il dispositivo connesso. Per uscire premi il pulsante HOME." - "Live TV richiede l\'autorizzazione per leggere gli elenchi TV." + "Dirette TV richiede l\'autorizzazione per leggere gli elenchi TV." "Configura le tue fonti" - "L\'app Dirette TV combina i canali TV tradizionali ai canali in streaming forniti dalle app. \n\nInnanzitutto, configura le fonti di canali già installate. In alternativa, cerca altre app di dirette TV sul Google Play Store." + "L\'app Dirette TV combina i canali TV tradizionali con i canali in streaming forniti dalle app. \n\nInnanzitutto, configura le fonti di canali già installate. In alternativa, cerca altre app di dirette TV sul Google Play Store." "Registrazioni e programmazioni" "10 minuti" "30 minuti" @@ -228,6 +219,8 @@ %1$d registrazioni programmate %1$d registrazione programmata + "Annulla registrazione" + "Interrompi registrazione" "Guarda" "Riproduci dall\'inizio" "Riprendi riproduz." @@ -260,9 +253,6 @@ "Quando ci sono troppi programmi da registrare nello stesso momento, vengono registrati solo quelli con le priorità maggiori." "Salva" "Le registrazioni uniche hanno la massima priorità" - "Annulla" - "Annulla" - "Elimina" "Interrompi" "Pianificazione registrazione" "Solo questo programma" @@ -272,25 +262,28 @@ "Registra questo" "Annulla la registrazione" "Guarda adesso" + "Elimina registrazioni…" "Registrabile" "Registrazione programmata" "Conflitto di registrazione" "Registrazione in corso" "Registrazione non riuscita" - "Lettura dei programmi per la creazione delle pianificazioni di registrazione" - "Lettura dei programmi…" - - + "Lettura dei programmi…" + "Visualizza registrazioni recenti" + "La registrazione di %1$s è incompleta." + "Le registrazioni di %1$s e %2$s sono incomplete." + "Le registrazioni di %1$s, %2$s e %3$s sono incomplete." + "La registrazione di %1$s non è stata completata poiché lo spazio di archiviazione non era sufficiente." + "Le registrazioni di %1$s e %2$s non sono state completate poiché lo spazio di archiviazione non era sufficiente." + "Le registrazioni di %1$s, %2$s e %3$s non sono state completate poiché lo spazio di archiviazione non era sufficiente." "Il dispositivo DVR ha bisogno di più spazio di archiviazione" - "Potrai registrare programmi con un dispositivo DVR. Tuttavia, al momento lo spazio di archiviazione non è sufficiente per consentire il funzionamento del DVR. Collega un\'unità esterna di almeno %1$s GB e segui la procedura per formattarla come memoria dispositivo." + "Potrai registrare programmi con un dispositivo DVR. Tuttavia, al momento lo spazio di archiviazione non è sufficiente per consentire il funzionamento del DVR. Collega un\'unità esterna di almeno %1$d GB e segui la procedura per formattarla come memoria dispositivo." + "Spazio di archiviazione insufficiente" + "Il programma non sarà registrato perché lo spazio di archiviazione non è sufficiente. Prova a eliminare alcune registrazioni esistenti." "Memoria mancante" - "Manca parte della memoria utilizzata dal DVR. Collega l\'unità esterna utilizzata prima di riattivare il DVR. In alternativa, puoi scegliere di eliminare la memoria se non è più disponibile." - "Eliminare la memoria?" - "Tutti i contenuti registrati e le pianificazioni andranno persi." "Interrompere la registrazione?" "I contenuti registrati verranno salvati." - - + "La registrazione di %1$s verrà interrotta perché è in conflitto con questo programma. I contenuti registrati verranno salvati." "Registrazione programmata, ma ci sono conflitti" "La registrazione è iniziata, ma ci sono conflitti" "Verrà registrato il programma %1$s." @@ -308,17 +301,29 @@ "È già stata programmata la registrazione dello stesso programma alle %1$s." "Già registrato" "Questo programma è già stato registrato. È disponibile nella raccolta DVR." - - - - - - - - + "Registrazione della serie programmata" + + %1$d registrazioni sono state pianificate per la serie %2$s. + %1$d registrazione è stata pianificata per la serie %2$s. + + + %1$d registrazioni sono state pianificate per la serie %2$s. %3$d non saranno registrate a causa di conflitti. + %1$d registrazione è stata pianificata per la serie %2$s. Non sarà registrata a causa di conflitti. + + + %1$d registrazioni sono state pianificate per la serie %2$s. %3$d puntate di questa e altre serie non saranno registrate a causa di conflitti. + %1$d registrazione è stata pianificata per la serie %2$s. %3$d puntate di questa e altre serie non saranno registrate a causa di conflitti. + + + %1$d registrazioni sono state pianificate per la serie %2$s. Una puntata di un\'altra serie non sarà registrata a causa di conflitti. + %1$d registrazione è stata pianificata per la serie %2$s. Una puntata di un\'altra serie non sarà registrata a causa di conflitti. + + + %1$d registrazioni sono state pianificate per la serie %2$s. %3$d puntate di altre serie non saranno registrate a causa di conflitti. + %1$d registrazione è stata pianificata per la serie %2$s. %3$d puntate di altre serie non saranno registrate a causa di conflitti. + "Programma registrato non trovato." "Registrazioni correlate" - "(Nessuna descrizione programma)" %1$d registrazioni %1$d registrazione @@ -338,6 +343,7 @@ "Interrompere la registrazione della serie?" "Le puntate registrate resteranno disponibili nella raccolta DVR." "Interrompi" + "Nessuna puntata in onda al momento." "Non ci sono episodi a disposizione.\nVerranno registrati non appena saranno disponibili." (%1$d minuti) @@ -349,4 +355,5 @@ "%1$s di oggi" "%1$s di domani" "Punteggio" + "Programmi registrati" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 743b31fd..6599d98c 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -20,9 +20,8 @@ "מונו" "סטריאו" "פקדי הפעלה" - "ערוצים אחרונים" + "ערוצים" "‏אפשרויות TV" - "‏אפשרויות PIP" "פקדי הפעלה אינם זמינים בשביל הערוץ הזה" "הפעל או השהה" "הרץ קדימה" @@ -35,33 +34,15 @@ "כתוביות" "מצב תצוגה" "PIP" - "מופעל" - "כבוי" "מולטי-אודיו" "קבל ערוצים נוספים" "הגדרות" - "מקור" - "החלף" - "מופעל" - "כבוי" - "צליל" - "ראשי" - "‏חלון PIP" - "פריסה" - "שמאל למטה" - "שמאל למעלה" - "ימין למעלה" - "ימין למטה" - "זה לצד זה" - "גודל" - "גדול" - "קטן" - "מקור קלט" "טלוויזיה (אנטנה/כבלים)" "אין פרטי תכנית" "אין מידע" "ערוץ חסום" - "שפה לא ידועה" + "שפה לא ידועה" + "‏כתוביות ב%1$d" "כתוביות" "כבוי" "התאם אישית את הפורמט" @@ -83,6 +64,7 @@ "איכות רגילה" "קבץ לפי" "התכנית הזו חסומה" + "לתוכנית הזו אין סיווג תוכן" "סיווג התכנית הזו הוא %1$s." "הקלט אינו תומך בסריקה אוטומטית" "לא ניתן להתחיל בסריקה אוטומטית עבור \'%s\'" @@ -94,7 +76,6 @@ נוסף ערוץ אחד "לא נוספו ערוצים" - "טיונר" "בקרות הוריות" "מופעל" "כבוי" @@ -110,6 +91,8 @@ "מדינות אחרות" "ללא" "ללא" + "ללא סיווג תוכן" + "חסימת תוכניות ללא סיווג תוכן" "ללא" "הגבלות קפדניות" "הגבלות בינוניות" @@ -126,6 +109,7 @@ "הזן את קוד האימות שלך כדי לצפות בערוץ זה" "הזן את קוד האימות שלך כדי לצפות בתכנית זו" "‏סיווג התוכנית הזו הוא %1$s. הזן את ה-PIN שלך כדי לצפות בתוכנית זו" + "‏לתוכנית הזו אין סיווג תוכן. עליך להזין את ה-PIN כדי לצפות בה" "הזן את קוד האימות שלך" "‏להגדרת בקרות הוריות, צור מספר PIN" "‏הזן את מספר ה-PIN החדש" @@ -139,22 +123,31 @@ "‏מספר ה-PIN היה שגוי. נסה שוב." "נסה שוב, קוד האימות אינו תואם" + "הזן את המיקוד שלך" + "‏אפליקציית Live TV תשתמש במיקוד כדי לספק לך את לוח השידורים המלא של ערוצי הטלוויזיה." + "הזן את המיקוד שלך" + "מיקוד לא חוקי" "הגדרות" "התאם אישית רשימת ערוצים" "בחר ערוצים ללוח השידורים שלך" "מקורות של ערוצים" "ערוצים חדשים זמינים" "בקרת הורים" + "פקדי וידאו" + "אפשר להקליט את השידור בזמן הצפייה כדי להשהות תוכניות או להריץ אותן אחורה בזמן אמת.\nאזהרה: הפעלה של האפשרות הזו עלולה לקצר את חיי האחסון הפנימי עקב שימוש אינטנסיבי." "רישיונות קוד פתוח" - "רישיונות קוד פתוח" + "שלח משוב" "גרסה" "‏כדי לצפות בערוץ הזה, לחץ על \'ימין\' והזן את מספר ה-PIN" "‏כדי לצפות בתכנית הזו, לחץ על \'ימין\' והזן את מספר ה-PIN" + "‏לתוכנית הזו אין סיווג תוכן.\nכדי לצפות בה, יש ללחוץ על החץ ימינה ולהזין את ה-PIN" "‏סיווג התכנית הזו הוא %1$s.\nכדי לצפות בתכנית, לחץ ימינה והזן את ה-PIN." "כדי לצפות בערוץ הזה, השתמש באפליקציית ברירת המחדל לטלוויזיה בשידור חי." "כדי לצפות בתכנית זו, השתמש באפליקציית ברירת המחדל לטלוויזיה בשידור חי." + "לתוכנית הזו אין סיווג תוכן.\nכדי לצפות בה, יש להשתמש באפליקציית הטלוויזיה המוגדרת כברירת מחדל." "סיווג התכנית הזו הוא %1$s.\nכדי לצפות בתכנית זו, השתמש באפליקציית ברירת המחדל לטלוויזיה בשידור חי." "התכנית חסומה" + "לתוכנית הזו אין סיווג תוכן" "סיווג התכנית הזו הוא %1$s." "אודיו בלבד" "אות חלש" @@ -189,8 +182,6 @@ "לחץ על \'בחר\'"" כדי לפתוח את תפריט הטלוויזיה." "לא נמצא קלט טלוויזיה" "לא ניתן למצוא את קלט הטלוויזיה" - "‏PIP אינו נתמך" - "‏אין קלט זמין הניתן להצגה באמצעות PIP" "סוג הטיונר אינו מתאים. הפעל את האפליקציה \'ערוצים בשידור חי\' בשביל טיונר מסוג קלט טלוויזיה." "הכוונון נכשל" "לא נמצאה אפליקציה שיכולה לטפל בפעולה הזו." @@ -244,6 +235,8 @@ ‏תוזמנו %1$d הקלטות הקלטה אחת תוזמנה + "בטל את ההקלטה" + "הפסק את ההקלטה" "צפה" "הפעל מההתחלה" "המשך את ההפעלה" @@ -278,9 +271,6 @@ "כשיש יותר מדי תוכניות להקליט בו-זמנית, רק תוכניות בעלות עדיפות גבוהה יותר יוקלטו." "שמור" "הקלטות חד-פעמיות הן בעלות העדיפות הגבוהה ביותר" - "בטל" - "בטל" - "שכח" "עצור" "הצג לוח זמנים להקלטה" "את התוכנית הזו בלבד" @@ -290,25 +280,28 @@ "הקלטת תוכנית זו במקום זאת" "ביטול הקלטה זאת" "לצפייה עכשיו" + "מחיקת הקלטות…" "ניתנת להקלטה" "ההקלטה מתוזמנת" "התנגשות בין הקלטות" "מקליט" "ההקלטה נכשלה" - "קורא תוכניות כדי ליצור לוחות זמנים להקלטות" - "קורא תוכניות" - - + "קורא תוכניות" + "הצגת ההקלטות האחרונות" + "ההקלטה של %1$s חלקית." + "ההקלטות של %1$s ו-%2$s חלקיות." + "ההקלטות של %1$s, %2$s ו-%3$s חלקיות." + "ההקלטה של %1$s לא הושלמה מפני ששטח האחסון אינו מספיק." + "ההקלטות של %1$s ו-%2$s לא הושלמו מפני ששטח האחסון אינו מספיק." + "ההקלטות של %1$s, %2$s ו-%3$s לא הושלמו מפני ששטח האחסון אינו מספיק." "‏DVR זקוק לשטח אחסון נוסף" - "‏תוכל להקליט תוכניות עם DVR. אולם, אין מספיק מקום אחסון במכשיר שלך כרגע כדי ש-DVR יעבוד. התחבר לכונן חיצוני בגודל GB%1$s או יותר, ובצע את השלבים לביצוע פורמט כאחסון מכשיר." + "‏תוכל להקליט תוכניות עם DVR. אולם, אין מספיק מקום אחסון במכשיר שלך כרגע כדי ש-DVR יעבוד. התחבר לכונן חיצוני בגודל GB%1$d או יותר, ובצע את השלבים לביצוע פורמט כאחסון מכשיר." + "אין מספיק שטח אחסון" + "התוכנית הזאת לא תוקלט מפני שאין מספיק שטח אחסון. נסה למחוק חלק מההקלטות הקיימות." "אחסון חסר" - "‏חלק מהאחסון שנעשה בו שימוש ב-DVR חסר. חבר את הכונן החיצוני שבו השתמשת בעבר כדי להפעיל מחדש את ה-DVR. לחלופין, תוכל לבחור לשכוח את האחסון אם הוא אינו זמין עוד." - "לשכוח את האחסון?" - "כל התוכן ולוחות הזמנים שתיעדת יאבדו." "האם להפסיק את ההקלטה?" "התוכן המוקלט יישמר." - - + "ההקלטה של %1$s תיפסק מפני שהיא מתנגשת עם התוכנית הזאת. התוכן שהוקלט יישמר." "ההקלטה מתוזמנת, אבל ישנן הקלטות מתנגשות." "ההקלטה החלה, אבל ישנן הקלטות מתנגשות" "התוכנית %1$s תוקלט." @@ -328,17 +321,39 @@ "אותה תוכנית כבר תוזמנה להקלטה ב-%1$s." "כבר הוקלט" "‏תוכנית זו כבר הוקלטה. היא זמינה לצפייה בספריית ה-DVR." - - - - - - - - + "הקלטת הסדרה תוזמנה" + + %1$d הקלטות תוזמנו לסדרה %2$s. + %1$d הקלטות תוזמנו לסדרה %2$s. + %1$d הקלטות תוזמנו לסדרה %2$s. + הקלטה %1$d תוזמנה לסדרה %2$s. + + + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d הקלטות מתוכן לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d הקלטות מתוכן לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d הקלטות מתוכן לא יוקלטו עקב התנגשויות. + הקלטה %1$d תוזמנה לסדרה %2$s. היא לא תוקלט עקב התנגשויות. + + + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה זו וסדרה אחרת לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה זו וסדרה אחרת לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה זו וסדרה אחרת לא יוקלטו עקב התנגשויות. + הקלטה %1$d תוזמנה לסדרה %2$s. %3$d פרקים מסדרה זו וסדרה אחרת לא יוקלטו עקב התנגשויות. + + + %1$d הקלטות תוזמנו לסדרה %2$s. פרק אחד מסדרה אחרת לא יוקלט עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. פרק אחד מסדרה אחרת לא יוקלט עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. פרק אחד מסדרה אחרת לא יוקלט עקב התנגשויות. + הקלטה %1$d תזומנה לסדרה %2$s. פרק אחד מסדרה אחרת לא יוקלט עקב התנגשויות. + + + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה אחרת לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה אחרת לא יוקלטו עקב התנגשויות. + %1$d הקלטות תוזמנו לסדרה %2$s. %3$d פרקים מסדרה אחרת לא יוקלטו עקב התנגשויות. + הקלטה %1$d תוזמנה לסדרה %2$s. %3$d פרקים מסדרה אחרת לא יוקלטו עקב התנגשות. + "התוכנית המוקלטת לא נמצאה." "הקלטות קשורות" - "(אין תיאור לתוכנית)" ‏%1$d הקלטות ‏%1$d הקלטות @@ -362,6 +377,7 @@ "האם להפסיק את הקלטת הסדרה?" "‏פרקים מוקלטים יישארו זמינים בספריית DVR." "הפסק" + "אף פרק אינו משודר כעת." "אין פרקים זמינים.\nהם יוקלטו כשיהיו זמינים." ‏(%1$d דקות) @@ -375,4 +391,5 @@ "%1$s היום" "%1$s מחר" "ניקוד" + "תוכניות שהוקלטו" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 9c526c21..47c2bf47 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -20,9 +20,8 @@ "モノラル" "ステレオ" "再生操作" - "最近のチャンネル" + "チャンネル" "テレビオプション" - "PIPオプション" "このチャンネルでは再生操作を使用できません" "再生または一時停止" "早送り" @@ -30,38 +29,20 @@ "次へ" "前へ" "番組ガイド" - "利用できる新しいチャンネル" + "新しいチャンネル" "%1$sを開く" "字幕" "表示モード" "PIP" - "ON" - "OFF" "マルチオーディオ" - "その他のチャンネル" + "他のチャンネル" "設定" - "ソース" - "切り替え" - "ON" - "OFF" - "サウンド" - "メイン" - "PIPウィンドウ" - "レイアウト" - "右下" - "右上" - "左上" - "左下" - "横並び" - "サイズ" - "大" - "小" - "入力ソース" "テレビ(アンテナ/ケーブル)" "プログラム情報がありません" "情報はありません" "ブロックされているチャンネル" - "不明な言語" + "不明な言語" + "クローズド キャプション %1$d" "字幕" "OFF" "書式設定のカスタマイズ" @@ -83,6 +64,7 @@ "SD" "グループ化" "このプログラムはブロックされています" + "この番組にはレーティングがありません" "このプログラムのレーティングは%1$sです" "入力が自動スキャンをサポートしていません" "「%s」の自動スキャンを開始できません" @@ -92,7 +74,6 @@ %1$d 件のチャンネルが追加されました "追加されたチャンネルなし" - "チューナー" "保護者による使用制限" "ON" "OFF" @@ -108,6 +89,8 @@ "その他の国" "なし" "なし" + "レーティングなし" + "レーティングなしの番組をブロック" "なし" "制限: 高" "制限: 中" @@ -124,9 +107,10 @@ "PINを入力してこのチャンネルを視聴" "PINを入力してこのプログラムを視聴" "この番組は「%1$s」レーティングです。この番組を視聴するには PIN を入力してください" + "この番組にはレーティングがありません。この番組を視聴するには PIN を入力してください" "PINの入力" "保護者による使用制限を設定するには、暗証番号を作成してください" - "新しいPINを入力" + "新しい PIN を入力" "PINの確認" "現在のPINの入力" @@ -135,22 +119,31 @@ "PINが正しくありません。もう一度お試しください。" "もう一度お試しください。PINが一致しません。" + "郵便番号の入力" + "ライブ チャンネル アプリは、テレビ チャンネルの完全な番組ガイドを提供するために郵便番号を使用します。" + "郵便番号を入力してください" + "郵便番号が無効です" "設定" "チャンネル リストをカスタマイズ" "番組ガイド用のチャンネルを選択します" "チャンネル ソース" "新しいチャンネルを利用できます" "保護者による使用制限" + "タイムシフト" + "放送中の番組を視聴しながら録画する機能です。一時停止や巻き戻しができます。\n警告: 内部ストレージの負荷が増えるため、ストレージの寿命が短くなる恐れがあります。" "オープンソース ライセンス" - "オープンソースライセンス" + "フィードバックを送信" "バージョン" "このチャンネルを視聴するには、右のボタンを押してPINを入力してください。" "このプログラムを視聴するには、右のボタンを押してPINを入力してください。" + "この番組にはレーティングがありません。\nこの番組を視聴するには、右のボタンを押して PIN を入力してください" "このプログラムのレーティングは%1$sです。\nこのプログラムを視聴するには、右のボタンを押してPINを入力してください。" "このチャンネルを視聴するには、デフォルトのライブテレビアプリを使用してください。" "この番組を視聴するには、デフォルトのライブテレビアプリを使用してください。" + "この番組にはレーティングがありません。\nこの番組を視聴するには、デフォルトのライブテレビ アプリを使用してください。" "この番組は「%1$s」レーティングです。\nこの番組を視聴するには、デフォルトのライブテレビアプリを使用してください。" "プログラムはブロックされています" + "この番組にはレーティングがありません" "このプログラムのレーティングは%1$sです" "音声のみ" "信号強度が弱くなっています" @@ -181,8 +174,6 @@ "テレビのメニューにアクセスするには""[SELECT]を押してください""。" "テレビ入力が見つかりませんでした" "テレビ入力が見つかりません" - "PIPがサポートされていません" - "PIPで表示できる有効な入力はありません" "無効なチューナータイプ。チューナー入力のライブチャンネルアプリを起動してください。" "同調に失敗しました" "この操作を行うアプリが見つかりませんでした。" @@ -193,10 +184,10 @@ "ソースをセットアップ" "ライブ チャンネルを利用すると、アプリで提供されるストリーミング チャンネルを従来のテレビのチャンネルと同様に視聴できます。\n\n使ってみるには、既にインストールされているチャンネル ソースをセットアップします。また、ライブ チャンネルを提供するアプリを Google Play ストアで探してさらに追加することもできます。" "録画予約" - "10分" - "30分" - "1時間" - "3時間" + "10 分" + "30 分" + "1 時間" + "3 時間" "最近" "予約済み" "シリーズ" @@ -226,6 +217,8 @@ %1$d 件の録画予約 %1$d 件の録画予約 + "録画をキャンセル" + "録音を停止" "再生" "最初から再生" "再生を再開" @@ -258,9 +251,6 @@ "同時に多数の番組を録画する場合は、優先度の高い番組だけが録画されます。" "保存" "1 回のみの録画を最優先" - "キャンセル" - "キャンセル" - "削除" "停止" "録画予約を見る" "この番組のみ" @@ -270,25 +260,28 @@ "この番組を代わりに録画" "この録画をキャンセル" "今すぐ見る" + "録画の削除…" "録画できます" "録画を予約しました" "録画予約が重複しています" "録画" "録画に失敗しました" - "録画予約を作成する番組情報を読み取っています" - "番組情報を読み取っています" - - + "番組情報を読み取っています" + "最近の録画を表示" + "「%1$s」の録画を完了できませんでした" + "「%1$s」と「%2$s」の録画を完了できませんでした" + "「%1$s」、「%2$s」、「%3$s」の録画を完了できませんでした" + "ストレージの容量不足のため、「%1$s」の録画を完了できませんでした。" + "ストレージの容量不足のため、「%1$s」と「%2$s」の録画を完了できませんでした。" + "ストレージの容量不足のため、「%1$s」、「%2$s」、「%3$s」の録画を完了できませんでした。" "DVR のストレージ不足" - "DVR を使用して番組を録画することはできますが、お使いの端末のストレージが十分ではないため、現在 DVR を使用できません。%1$s GB 以上の外部ストレージを接続し、手順に沿って端末のストレージと同じようにフォーマットしてください。" + "DVR を使用して番組を録画することはできますが、お使いの端末のストレージが十分ではないため、現在 DVR を使用できません。%1$d GB 以上の外部ストレージを接続し、手順に沿って端末のストレージと同じようにフォーマットしてください。" + "空き容量の不足" + "空き容量が不足しているため、この番組は録画されません。録画済みの番組をいくつか削除してみてください。" "ストレージにアクセスできません" - "DVR で使用しているストレージの一部にアクセスできません。DVR を再度有効にする前に、使用している外部ドライブを接続してください。アクセスできないストレージは削除することもできます。" - "ストレージの削除" - "録画したコンテンツと録画予約がすべて失われます。" "録音の停止" "録画したコンテンツは保存されます。" - - + "「%1$s」の録画は、この番組と重なっているため停止されます。録画したコンテンツは保存されます。" "録画を予約しましたが、他の予約と重なっています" "録画を開始しましたが、他の予約と重なっています" "「%1$s」が録画されます。" @@ -306,17 +299,29 @@ "同じ番組が既に録画予約されています(%1$s)。" "録画済み" "この番組は既に録画され、DVR ライブラリから利用できます。" - - - - - - - - + "シリーズの録画を予約しました" + + %2$s」の録画は %1$d 件予約されています。 + %2$s」の録画は %1$d 件予約されています。 + + + %2$s」の録画は %1$d 件予約されていますが、予約が競合するため %3$d 件は録画されません。 + %2$s」の録画は %1$d 件予約されていますが、予約が競合するため録画されません。 + + + %2$s」の録画は %1$d 件予約されています。予約が競合するため、このシリーズと他のシリーズのエピソード(%3$d 件)は録画されません。 + %2$s」の録画は %1$d 件予約されています。予約が競合するため、このシリーズと他のシリーズのエピソード(%3$d 件)は録画されません。 + + + %2$s」の録画は %1$d 件予約されています。予約が競合するため、他のシリーズのエピソード(1 件)は録画されません。 + %2$s」の録画は %1$d 件予約されています。予約が競合するため、他のシリーズのエピソード(1 件)は録画されません。 + + + %2$s」の録画は %1$d 件予約されています。予約が競合するため、他のシリーズのエピソード(%3$d 件)は録画されません。 + %2$s」の録画は %1$d 件予約されています。予約が競合するため、他のシリーズのエピソード(%3$d 件)は録画されません。 + "録画された番組は見つかりませんでした。" "関連の録画" - "(番組の説明はありません)" %1$d 件の録画 %1$d 件の録画 @@ -336,6 +341,7 @@ "シリーズの録画を中止しますか?" "録画したエピソードは DVR ライブラリから利用できます。" "停止" + "現在、放送中の番組はありません。" "利用可能なエピソードはありません。\n利用可能になると録画されます。" (%1$d分) @@ -347,4 +353,5 @@ "今日(%1$s)" "明日(%1$s)" "スコア" + "録画した番組" diff --git a/res/values-ka-rGE-v23/strings.xml b/res/values-ka-rGE-v23/strings.xml new file mode 100644 index 00000000..20f03595 --- /dev/null +++ b/res/values-ka-rGE-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "არხები" + diff --git a/res/values-ka-rGE/arrays.xml b/res/values-ka-rGE/arrays.xml new file mode 100644 index 00000000..1d6659a6 --- /dev/null +++ b/res/values-ka-rGE/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "ჩვეულებრივი" + "სრული" + "მასშტაბი" + + + "ყველა არხი" + "ოჯახი/ბავშვები" + "სპორტი" + "საყიდლები" + "ფილმები" + "კომედია" + "მოგზაურობა" + "დრამატული" + "განათლება" + "ცხოველები/ველური ბუნება" + "ახალი ამბები" + "თამაშები" + "ხელოვნება" + "გართობა" + "ცხოვრების სტილი" + "მუსიკა" + "პრემიერა" + "ტექნოლოგიები/მეცნიერება" + + + "პირდაპირი არხები" + "კონტენტის აღმოჩენის მარტივი ხერხი" + "ჩამოტვირთეთ აპები და მიიღეთ მეტი არხი" + "მოირგეთ არხების მიმდევრობა" + + + "უყურეთ კონტენტს თქვენი აპებიდან სატელევიზიო არხების მსგავსად." + "დაათვალიერეთ კონტენტი თქვენი აპებიდან სატელევიზიო არხების მსგავსად, \nჩვეული მეგზურისა და მეგობრული ინტერფეისის მეშვეობით." + "დაამატეთ მეტი არხი — დააინსტალირეთ აპები, რომლებიც პირდაპირ არხებს გთავაზობთ. \nთავსებადი აპების საპოვნელად გამოიყენეთ TV-ს მენიუში მოცემული ბმული, რომელიც Google Play Store-ზე გადაგიყვანთ." + "არხების სიის მოსარგებად, დააყენეთ არხების ახლადდაინსტალირებული წყაროები. \nდასაწყებად, პარამეტრების მენიუში აირჩიეთ არხების წყაროები." + + diff --git a/res/values-ka-rGE/rating_system_strings.xml b/res/values-ka-rGE/rating_system_strings.xml new file mode 100644 index 00000000..498d1d82 --- /dev/null +++ b/res/values-ka-rGE/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "პროგრამების შიგთავსი შეიძლება შეუფერებელი იყოს 15 წლამდე მაყურებლებისთვის. აქედან გამომდინარე, მათი ყურება მშობლის თანხმობით უნდა მოხდეს." + "პროგრამების შიგთავსი შეიძლება შეუფერებელი იყოს 19 წლამდე მაყურებლებისთვის. აქედან გამომდინარე, მათი ყურება ამ აუდიტორიისთვის რეკომენდებული არ არის." + "მაცდუნებელი დიალოგი" + "უხამსი მეტყველება" + "სექსუალური ხასიათის შინაარსი" + "ძალადობა" + "ძალადობის შემცველი ფანტასტიკა" + "პროგრამა შექმნილია, რომ მიზანშეწონილი იყოს ნებისმიერი ასაკის ბავშვებისათვის." + "ეს პროგრამა შექმნილია 7 წლის და უფროსი ბავშვებისათვის." + "მრავალი მშობლის აზრით ეს პროგრამა მიზანშეწონილია ნებისმიერი ასაკის მაყურებლისთვის." + "ეს პროგრამა შეიცავს ისეთ მასალას, რაც, მშობლების აზრით, შეიძლება არ იყოს მიზანშეწონილი მცირეწლოვანი ბავშვებისთვის. მათთვის რეკომენდებულია პროგრამის ნახვა მშობლების თანდასწრებით." + "ეს პროგრამა შეიცავს მასალას, რაც მრავალი მშობლის აზრით არ არის მიზანშეწონილი 14 წლამდე ასაკის ბავშვებისათვის." + "ეს პროგრამა განკუთვნილია მოზრდილი მაყურებლისათვის და შესაბამისად შესაძლოა მიზანშეწონილი არ იყოს 17 წლამდე ასაკის ბავშვებისათვის." + "ფილმის რეიტინგები" + "ზოგადი აუდიტორია. მიზანშეწონილია ბავშვებისათვის." + "სასურველია მშობლის მეთვალყურეობა. ზოგიერთი მასალა შეიძლება არ იყოს მიზანშეწონილი ბავშვებისთვის." + "მშობლების კატეგორიული გაფრთხილება. ზოგიერთი მასალა შეიძლება არასათანადო იყოს 13 წლამდე ასაკის ბავშვებისთვის." + "შეზღუდული. ეს პროგრამა შეიცავს გარკვეულ მასალას ზრდასრული ადამიანებისთვის. მშობლებს მოუწოდებენ, რომ შეიტყონ მეტი ფილმის შესახებ, სანამ ბავშვებს თან წაიყვანენ." + "არ დაიშვება 17 წლის და 17 წლამდე ასაკის მაყურებელი. გათვალისწინებულია მოზრდილებისთვის. ბავშვები არ დაიშვებიან." + diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml new file mode 100644 index 00000000..28c8ce3c --- /dev/null +++ b/res/values-ka-rGE/strings.xml @@ -0,0 +1,357 @@ + + + + + "მონო" + "სტერეო" + "ჩართვის კონტროლი" + "არხები" + "TV პარამეტრები" + "ამ არხისთვის არ არის ხელმისაწვდომი ჩართვის კონტროლი" + "დაკვრა ან პაუზა" + "წინ გადახვევა" + "უკან გადახვევა" + "შემდეგი" + "წინა" + "პროგ. სახელმძღვანელო" + "წვდომაა ახალ არხებზე" + "%1$s-ის გახსნა" + "დახურ.წარწერები" + "ჩვენების რეჟიმი" + "PIP" + "მულტი-აუდიო" + "მეტი არხის მიღება" + "პარამეტრები" + "ტელევიზია (ანტენა/კაბელი)" + "პროგრამის ინფორმაცია არ არის" + "ინფორმაცია არ არის ხელმისაწვდომი" + "დაბლოკილი არხი" + "უცნობი ენა" + "დახურული სუბტიტრები %1$d" + "დახურული სუბტიტრები" + "გამორთულია" + "ფორმატირების მორგება" + "მთელ სისტ. პარ.დაყ. დახ. წარწერებისთვის" + "ჩვენების რეჟიმი" + "მულტი-აუდიო" + "მონო" + "სტერეო" + "5.1 surround" + "7.1 surround" + "%d არხი" + "არხ.სიის მორგება" + "ჯგუფის არჩევა" + "ჯგუფის მონიშვ.გაუქმ." + "დაჯგუფება:" + "არხის წყარო" + "HD/SD" + "HD" + "SD" + "დაჯგუფება:" + "ეს პროგრამა დაბლოკილია" + "ეს პროგრამა შეუფასებელია" + "ამ პროგრამის რეიტინგია %1$s" + "ავტო-სკანირება არ არის მხარდაჭერილი შეყვანით." + "ვერ ხერხდება ავტო-სკანირება „%s“-ისთვის" + "დახურული წარწერებისთვის სისტემის მასშტაბით პარამეტრების გაშვება ვერ ხერხდება." + + დაემატა %1$d არხი + დაემატა %1$d არხი + + "არხები არ დამატებულა" + "მშობელთა კონტროლი" + "ჩართულია" + "გამორთულია" + "დაბლოკილი არხები" + "ყველას დაბლოკვა" + "ყველას განბლოკვა" + "დამალული არხები" + "პროგრ. შეზღუდვები" + "PIN-ის შეცვლა" + "რეიტინგ. სისტემები" + "რეიტინგები" + "ყველ.რეიტ.სის. ნახვა" + "სხვა ქვეყნები" + "არცერთი" + "არცერთი" + "შეუფასებელი" + "შეუფასებელ პროგრამათა დაბლოკვა" + "არცერთი" + "მაღალი შეზღუდვები" + "საშუალო შეზღუდვები" + "დაბალი შეზღუდვები" + "მორგებული" + "კონტენტი მიზანშეწონილია ბავშვებისთვის" + "კონტენ. შეეფერება უფროსი ასაკის ბავშვებს" + "კონტენტი განკუთვნილია მოზარდებისთვის" + "ხელით დაყენების შეზღუდვები" + + + "%1$s და ქვე-რეიტინგ." + "ქვე-რეიტინგები" + "შეიყვანეთ თქვენი PIN ამ არხის საყურებლად" + "შეიყვანეთ თქვენი PIN ამ პროგრამის საყურებლად" + "ამ პროგრამის ასაკობრივი შეზღუდვაა: %1$s. მის საყურებლად, შეიყვანეთ PIN-კოდი" + "ეს პროგრამა შეუფასებელია. ამ პროგრამის სანახავად, შეიყვანეთ თქვენი PIN-კოდი" + "შეიყვანეთ თქვენი PIN" + "მშობელთა კონტროლის დასაყენებლად შექმენით PIN" + "შეიყვანეთ ახალი PIN" + "კვლავ შეიყვანეთ თქვენი PIN" + "შეიყვანეთ თქვენი მიმდინარე PIN" + + თქვენ შეიყვანეთ არასწორი PIN 5-ჯერ.\nცადეთ ისევ %1$d წამში. + თქვენ შეიყვანეთ არასწორი PIN 5-ჯერ.\nცადეთ ისევ %1$d წამში. + + "ეს PIN არასწორი იყო. ისევ სცადეთ." + "სცადეთ ისევ, PIN არ შეესაბამება" + "შეიყვანეთ თქვენი ZIP კოდი." + "სატელევიზიო არხების სრული პროგრამის სახელმძღვანელოს მისაწოდებლად, Live TV აპი ZIP კოდს გამოიყენებს." + "შეიყვანეთ თქვენი ZIP კოდი" + "არასწორი ZIP კოდი" + "პარამეტრები" + "არხების სიის მორგება" + "აირჩიეთ არხები პროგრამის სახელმძღვანელოსთვის" + "არხების წყაროები" + "ხელმისაწვდომია ახალი არხები" + "მშობელთა კონტროლი" + "დროის შეცვლა" + "ჩაიწერეთ ყურებისას, რათა შეძლოთ პირდაპირ ეთერში გაშვებული პროგრამების დაპაუზება და უკან გადახვევა.\nგაფრთხილება: ამ ქმედებამ შეიძლება შეამციროს შიდა მეხსიერების ფუნქციონირების პერიოდი, მისი ინტენსიური გამოყენების გამო." + "ღია კოდის ლიცენზიები" + "გამოხმაურება" + "ვერსია" + "ამ არხის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN" + "ამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN" + "ეს პროგრამა შეუფასებელია.\nამ პროგრამის სანახავად, დააჭირეთ „მარჯვენას“ და შეიყვანეთ თქვენი PIN-კოდი." + "ამ პროგრამის რეიტინგია %1$s.\nამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN." + "ამ არხის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." + "ამ პროგრამის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." + "ეს პროგრამა შეუფასებელია.\nამ პროგრამის სანახავად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." + "ამ პროგრამის რეიტინგია: %1$s.\nამ პროგრამის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." + "პროგრამა დაბლოკილია" + "ეს პროგრამა შეუფასებელია" + "ამ პროგრამის რეიტინგია %1$s" + "მხოლოდ აუდიო" + "სიგნალი სუსტია" + "ინტერნეტთან კავშირი არ არის" + + ამ არხის დაკვრა %1$s-მდე ვერ მოხერხდება, რადგან იწერება სხვა არხები. \n\nჩაწერის განრიგის კორექტირებისთვის, დააჭირეთ „მარჯვნივ“-ს. + ამ არხის დაკვრა %1$s-მდე ვერ მოხერხდება, რადგან იწერება სხვა არხი. \n\nჩაწერის განრიგის კორექტირებისთვის, დააჭირეთ „მარჯვნივ“-ს. + + "უსათაურო" + "არხი დაბლოკილია" + "ახალი" + "წყაროები" + + %1$d არხი + %1$d არხი + + "ხელმისაწვდომი არხები არ არის" + "ახალი" + "არ არის დაყენებული" + "მეტი წყაროს მიღება" + "დაათვალიერეთ აპები, რომლებიც პირდაპირ არხებს გთავაზობთ" + "ხელმისაწვდომია არხების ახალი წყაროები" + "არხების ახალი წყაროები დამატებით არხებს გთავაზობთ.\nდააყენეთ ისინი ახლავე ან მოგვიანებით, არხების წყაროების პარამეტრებიდან." + "ახლავე დაყენება" + "კარგი, გასაგებია" + + + "დააჭირეთ არჩევას"" სატელევიზიო მენიუზე წვდომისთვის." + "TV შეყვანა ვერ მოიძებნა." + "TV შეყვანა ვერ იძებნება." + "ტიუნერის ტიპი არ არის შესაფერისი; გთხოვთ გაუშვით პირდაპირი არხების აპი, სატელევიზიო შეტანის ტიუნერის ტიპისათვის." + "არხების დაჭერა ვერ მოხერხდა." + "ამ მოქმედების შესასრულებლად აპი ვერ მოიძებნა." + "წყაროს ყველა არხი დამალულია.\nსაყურებლად აირჩიეთ მინიმუმ ერთი არხი." + "ვიდეო მიუწვდომელი გახდა მოულოდნელად." + "ღილაკი „უკან“ დაკავშირებული მოწყობილობისთვისაა. გამოსასვლელად დააჭირეთ ღილაკს „მთავარი“." + "პირდაპირი არხები საჭიროებს ნებართვას ტელეპროგრამის წასაკითხად." + "დააყენეთ თქვენი წყაროები" + "პირდაპირი არხები ტრადიციულ სატელევიზიო არხებსა და აპების მიერ მოწოდებულ სტრიმინგის არხებს აერთიანებს. \n\nდასაწყებად, დააყენეთ არხების უკვე დაინსტალირებული წყაროები, ან დაათვალიერეთ Google Play Store და აღმოაჩინეთ მეტი აპი, რომლებიც პირდაპირ არხებს გთავაზობთ." + "ჩანაწერები და განრიგები" + "10 წუთი" + "30 წუთი" + "1 საათი" + "3 საათი" + "ბოლო" + "დაგეგმილი" + "სერიები" + "სხვა" + "არხის ჩაწერა ვერ მოხერხდება." + "პროგრამის ჩაწერა ვერ მოხერხდება." + "%1$s დაიგეგმა ჩასაწერად" + "%1$s იწერება ამ მომენტიდან %2$s-მდე" + "სრული განრიგი" + + შემდეგი %1$d დღე + შემდეგი %1$d დღე + + + %1$d წუთი + %1$d წუთი + + + %1$d ახალი ჩანაწერი + %1$d ახალი ჩანაწერი + + + %1$d ჩანაწერი + %1$d ჩანაწერი + + + %1$d ჩანაწერი დაიგეგმა + %1$d ჩანაწერი დაიგეგმა + + "ჩაწერის გაუქმება" + "ჩაწერის შეწყვეტა" + "ყურება" + "თავიდან დაკვრა" + "დაკვრის განახლება" + "წაშლა" + "ჩანაწერების წაშლა" + "განახლება" + "სეზონი %1$s" + "განრიგის ნახვა" + "მეტის წაკითხვა" + "ჩანაწერების წაშლა" + "აირჩიეთ ეპიზოდები, რომელთა წაშლაც გსურთ. წაშლის შემდეგ, მათი აღდგენა ვეღარ მოხერხდება." + "წასაშლელი ჩანაწერები არ არის." + "ნაყურები ეპიზოდების არჩევა" + "ყველა ეპიზოდის არჩევა" + "ყველა მონიშვნის მოხსნა" + "ნაყურებია %2$d-დან %1$d წუთი" + "ნაყურებია %2$d-დან %1$d წამი" + "არასოდეს ნაყურები" + + %2$d-დან %1$d ეპიზოდი წაიშალა + %2$d-დან %1$d ეპიზოდი წაიშალა + + "პრიორიტეტი" + "ყველაზე მაღალი" + "ყველაზე დაბალი" + "№ %1$d" + "არხები" + "ნებისმიერი" + "აირჩიეთ პრიორიტეტი" + "თუ ჩასაწერად მეტისმეტად ბევრი პროგრამა დაიგეგმება, ჩაიწერება მხოლოდ მაღალი პრიორიტეტის მქონე პროგრამები." + "შენახვა" + "ერთჯერად ჩანაწერებს მიენიჭოს მეტი პრიორიტეტი" + "შეწყვეტა" + "ჩაწერის განრიგის ნახვა" + "მხოლოდ ეს პროგრამა" + "ახლა — %1$s" + "მთლიანი სერიალი…" + "მაინც დაგეგმვა" + "სანაცვლოდ, ჩაიწეროს ეს" + "ამ ჩანაწერის გაუქმება" + "ახლავე ყურება" + "ჩანაწერების წაშლა…" + "შესაძლებელია ჩაწერა" + "ჩაწერა დაგეგმილია" + "ჩაწერის კონფლიქტი" + "მიმდინარეობს ჩაწერა" + "ჩაწერა ვერ მოხერხდა" + "მიმდინარეობს პროგრამების წაკითხვა" + "ბოლო ჩანაწერების ნახვა" + "%1$s არასრულად ჩაიწერა." + "%1$s და %2$s არასრულად ჩაიწერა." + "%1$s, %2$s და %3$s არასრულად ჩაიწერა." + "არასაკმარისი მეხსიერების გამო, %1$s არასრულად ჩაიწერა." + "არასაკმარისი მეხსიერების გამო, %1$s და %2$s არასრულად ჩაიწერა." + "არასაკმარისი მეხსიერების გამო, %1$s, %2$s და %3$s არასრულად ჩაიწერა." + "DVR მეტ მეხსიერებას საჭიროებს" + "DVR-ის მეშვეობით პროგრამების ჩაწერა შესაძლებელია, თუმცა თქვენს მოწყობილობაზე ამჟამად ხელმისაწვდომი მეხსიერება DVR-ის მუშაობისთვის არასაკმარისია. გთხოვთ, დააკავშიროთ %1$d გბაიტი ან მეტი მოცულობის დისკწამყვანი და მიჰყვეთ ინსტრუქციას, რომელიც დაგეხმარებათ, დააფორმატოთ ის მოწყობილობის მეხსიერების სახით." + "მეხსიერება არასაკმარისია" + "ეს პროგრამა ვერ ჩაიწერება არასაკმარისი მეხსიერების გამო. ცადეთ არსებული ჩანაწერების წაშლა." + "აკლია მეხსიერება" + "გსურთ ჩაწერის შეწყვეტა?" + "ჩაწერილი კონტენტი შეინახება." + "„%1$s“-ის ჩაწერა შეწყდება ამ პროგრამასთან კონფლიქტის გამო. ჩაწერილი კონტენტი შეინახება." + "ჩაწერა დაგეგმილია, თუმცა ის კონფლიქტშია" + "ჩაწერა დაიწყო, თუმცა ის კონფლიქტშია" + "%1$s ჩაიწერება." + "%1$s იწერება." + "%1$s ნაწილობრივ არ ჩაიწერება." + "%1$s და %2$s ნაწილობრივ არ ჩაიწერება." + "%1$s, %2$s და კიდევ ერთი განრიგი ნაწილობრივ არ ჩაიწერება." + + %1$s, %2$s და კიდევ %3$d განრიგი ნაწილობრივ არ ჩაიწერება. + %1$s, %2$s და კიდევ %3$d განრიგი ნაწილობრივ არ ჩაიწერება. + + "გსურთ ჩაწერა?" + "რამდენი ხანი გსურთ, გაგრძელდეს ჩაწერა?" + "უკვე დაგეგმილია" + "იგივე პროგრამა უკვე დაიგეგმა ჩასაწერად %1$s-ზე." + "უკვე ჩაწერილია" + "ეს პროგრამა უკვე ჩაწერილია და DVR ბიბლიოთეკაშია ხელმისაწვდომი." + "სერიალის ჩაწერა დაგეგმილია" + + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. + + + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. %3$d მათგანი ვერ ჩაიწერება კონფლიქტების გამო. + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. ის ვერ ჩაიწერება კონფლიქტების გამო. + + + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. ამ სერიალის %3$d ეპიზოდი და სხვა სერიალები ვერ ჩაიწერება კონფლიქტების გამო. + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. ამ სერიალის %3$d ეპიზოდი და სხვა სერიალები ვერ ჩაიწერება კონფლიქტების გამო. + + + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. სხვა სერიალის 1 ეპიზოდი ვერ ჩაიწერება კონფლიქტების გამო. + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. სხვა სერიალის 1 ეპიზოდი ვერ ჩაიწერება კონფლიქტების გამო. + + + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. სხვა სერიალების %3$d ეპიზოდი ვერ ჩაიწერება კონფლიქტების გამო. + %2$s“-სთვის დაიგეგმა %1$d ჩანაწერი. სხვა სერიალების %3$d ეპიზოდი ვერ ჩაიწერება კონფლიქტების გამო. + + "ჩაწერილი პროგრამა ვერ მოიძებნა." + "დაკავშირებული ჩანაწერები" + + %1$d ჩანაწერი + %1$d ჩანაწერი + + " / " + "%1$s ამოიშალა ჩაწერის განრიგიდან" + "ჩაიწერება ნაწილობრივ ტუნერის კონფლიქტების გამო." + "არ ჩაიწერება ტუნერის კონფლიქტების გამო." + "ჩაწერა ჯერ დაგეგმილი არ არის.\nჩაწერის დაგეგმვა პროგრამების მეგზურიდან შეგიძლიათ." + + %1$d ჩაწერის კონფლიქტი + %1$d ჩაწერის კონფლიქტი + + "სერიალის პარამეტრები" + "სერიალის ჩაწერის დაწყება" + "სერიალის ჩაწერის შეწყვეტა" + "გსურთ სერიალის ჩაწერის შეწყვეტა?" + "ჩაწერილი ეპიზოდები DVR ბიბლიოთეკაში კვლავ იქნება ხელმისაწვდომი." + "შეწყვეტა" + "ამჟამად ეთერში არცერთი ეპიზოდი არ გადის." + "ეპიზოდები მიუწვდომელია.\nმათი ჩაწერა მოხდება მაშინ, როცა ისინი ხელმისაწვდომი გახდება." + + (%1$d წუთი) + (%1$d წუთი) + + "დღეს" + "ხვალ" + "გუშინ" + "დღეს, %1$s-ზე" + "ხვალ, %1$s-ზე" + "შეფასება" + "ჩაწერილი პროგრამები" + diff --git a/res/values-ka-v23/strings.xml b/res/values-ka-v23/strings.xml deleted file mode 100644 index 20f03595..00000000 --- a/res/values-ka-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "არხები" - diff --git a/res/values-ka/arrays.xml b/res/values-ka/arrays.xml deleted file mode 100644 index 1d6659a6..00000000 --- a/res/values-ka/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "ჩვეულებრივი" - "სრული" - "მასშტაბი" - - - "ყველა არხი" - "ოჯახი/ბავშვები" - "სპორტი" - "საყიდლები" - "ფილმები" - "კომედია" - "მოგზაურობა" - "დრამატული" - "განათლება" - "ცხოველები/ველური ბუნება" - "ახალი ამბები" - "თამაშები" - "ხელოვნება" - "გართობა" - "ცხოვრების სტილი" - "მუსიკა" - "პრემიერა" - "ტექნოლოგიები/მეცნიერება" - - - "პირდაპირი არხები" - "კონტენტის აღმოჩენის მარტივი ხერხი" - "ჩამოტვირთეთ აპები და მიიღეთ მეტი არხი" - "მოირგეთ არხების მიმდევრობა" - - - "უყურეთ კონტენტს თქვენი აპებიდან სატელევიზიო არხების მსგავსად." - "დაათვალიერეთ კონტენტი თქვენი აპებიდან სატელევიზიო არხების მსგავსად, \nჩვეული მეგზურისა და მეგობრული ინტერფეისის მეშვეობით." - "დაამატეთ მეტი არხი — დააინსტალირეთ აპები, რომლებიც პირდაპირ არხებს გთავაზობთ. \nთავსებადი აპების საპოვნელად გამოიყენეთ TV-ს მენიუში მოცემული ბმული, რომელიც Google Play Store-ზე გადაგიყვანთ." - "არხების სიის მოსარგებად, დააყენეთ არხების ახლადდაინსტალირებული წყაროები. \nდასაწყებად, პარამეტრების მენიუში აირჩიეთ არხების წყაროები." - - diff --git a/res/values-ka/rating_system_strings.xml b/res/values-ka/rating_system_strings.xml deleted file mode 100644 index 498d1d82..00000000 --- a/res/values-ka/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "პროგრამების შიგთავსი შეიძლება შეუფერებელი იყოს 15 წლამდე მაყურებლებისთვის. აქედან გამომდინარე, მათი ყურება მშობლის თანხმობით უნდა მოხდეს." - "პროგრამების შიგთავსი შეიძლება შეუფერებელი იყოს 19 წლამდე მაყურებლებისთვის. აქედან გამომდინარე, მათი ყურება ამ აუდიტორიისთვის რეკომენდებული არ არის." - "მაცდუნებელი დიალოგი" - "უხამსი მეტყველება" - "სექსუალური ხასიათის შინაარსი" - "ძალადობა" - "ძალადობის შემცველი ფანტასტიკა" - "პროგრამა შექმნილია, რომ მიზანშეწონილი იყოს ნებისმიერი ასაკის ბავშვებისათვის." - "ეს პროგრამა შექმნილია 7 წლის და უფროსი ბავშვებისათვის." - "მრავალი მშობლის აზრით ეს პროგრამა მიზანშეწონილია ნებისმიერი ასაკის მაყურებლისთვის." - "ეს პროგრამა შეიცავს ისეთ მასალას, რაც, მშობლების აზრით, შეიძლება არ იყოს მიზანშეწონილი მცირეწლოვანი ბავშვებისთვის. მათთვის რეკომენდებულია პროგრამის ნახვა მშობლების თანდასწრებით." - "ეს პროგრამა შეიცავს მასალას, რაც მრავალი მშობლის აზრით არ არის მიზანშეწონილი 14 წლამდე ასაკის ბავშვებისათვის." - "ეს პროგრამა განკუთვნილია მოზრდილი მაყურებლისათვის და შესაბამისად შესაძლოა მიზანშეწონილი არ იყოს 17 წლამდე ასაკის ბავშვებისათვის." - "ფილმის რეიტინგები" - "ზოგადი აუდიტორია. მიზანშეწონილია ბავშვებისათვის." - "სასურველია მშობლის მეთვალყურეობა. ზოგიერთი მასალა შეიძლება არ იყოს მიზანშეწონილი ბავშვებისთვის." - "მშობლების კატეგორიული გაფრთხილება. ზოგიერთი მასალა შეიძლება არასათანადო იყოს 13 წლამდე ასაკის ბავშვებისთვის." - "შეზღუდული. ეს პროგრამა შეიცავს გარკვეულ მასალას ზრდასრული ადამიანებისთვის. მშობლებს მოუწოდებენ, რომ შეიტყონ მეტი ფილმის შესახებ, სანამ ბავშვებს თან წაიყვანენ." - "არ დაიშვება 17 წლის და 17 წლამდე ასაკის მაყურებელი. გათვალისწინებულია მოზრდილებისთვის. ბავშვები არ დაიშვებიან." - diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml deleted file mode 100644 index 72608bd4..00000000 --- a/res/values-ka/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "მონო" - "სტერეო" - "ჩართვის კონტროლი" - "უახლესი არხები" - "TV პარამეტრები" - "PIP პარამეტრები" - "ამ არხისთვის არ არის ხელმისაწვდომი ჩართვის კონტროლი" - "დაკვრა ან პაუზა" - "წინ გადახვევა" - "უკან გადახვევა" - "შემდეგი" - "წინა" - "პროგ. სახელმძღვანელო" - "წვდომაა ახალ არხებზე" - "%1$s-ის გახსნა" - "დახურ.წარწერები" - "ჩვენების რეჟიმი" - "PIP" - "ჩართული" - "გამორთული" - "მულტი-აუდიო" - "მეტი არხის მიღება" - "პარამეტრები" - "წყარო" - "შენაცვლება" - "ჩართული" - "გამორთული" - "ხმა" - "მთავარი" - "PIP ფანჯარა" - "განლაგება" - "ქვედა მარჯვენა" - "ზედა მარჯვენა" - "ზედა მარცხენა" - "ქვედა მარცხენა" - "გვერდი-გვერდ" - "ზომა" - "დიდი" - "პატარა" - "შემავალი წყარო" - "ტელევიზია (ანტენა/კაბელი)" - "პროგრამის ინფორმაცია არ არის" - "ინფორმაცია არ არის ხელმისაწვდომი" - "დაბლოკილი არხი" - "უცნობი ენა" - "დახურული სუბტიტრები" - "გამორთულია" - "ფორმატირების მორგება" - "მთელ სისტ. პარ.დაყ. დახ. წარწერებისთვის" - "ჩვენების რეჟიმი" - "მულტი-აუდიო" - "მონო" - "სტერეო" - "5.1 surround" - "7.1 surround" - "%d არხი" - "არხ.სიის მორგება" - "ჯგუფის არჩევა" - "ჯგუფის მონიშვ.გაუქმ." - "დაჯგუფება:" - "არხის წყარო" - "HD/SD" - "HD" - "SD" - "დაჯგუფება:" - "ეს პროგრამა დაბლოკილია" - "ამ პროგრამის რეიტინგია %1$s" - "ავტო-სკანირება არ არის მხარდაჭერილი შეყვანით." - "ვერ ხერხდება ავტო-სკანირება „%s“-ისთვის" - "დახურული წარწერებისთვის სისტემის მასშტაბით პარამეტრების გაშვება ვერ ხერხდება." - - დაემატა %1$d არხი - დაემატა %1$d არხი - - "არხები არ დამატებულა" - "ტუნერი" - "მშობელთა კონტროლი" - "ჩართულია" - "გამორთულია" - "დაბლოკილი არხები" - "ყველას დაბლოკვა" - "ყველას განბლოკვა" - "დამალული არხები" - "პროგრ. შეზღუდვები" - "PIN-ის შეცვლა" - "რეიტინგ. სისტემები" - "რეიტინგები" - "ყველ.რეიტ.სის. ნახვა" - "სხვა ქვეყნები" - "არცერთი" - "არცერთი" - "არცერთი" - "მაღალი შეზღუდვები" - "საშუალო შეზღუდვები" - "დაბალი შეზღუდვები" - "მორგებული" - "კონტენტი მიზანშეწონილია ბავშვებისთვის" - "კონტენ. შეეფერება უფროსი ასაკის ბავშვებს" - "კონტენტი განკუთვნილია მოზარდებისთვის" - "ხელით დაყენების შეზღუდვები" - - - "%1$s და ქვე-რეიტინგ." - "ქვე-რეიტინგები" - "შეიყვანეთ თქვენი PIN ამ არხის საყურებლად" - "შეიყვანეთ თქვენი PIN ამ პროგრამის საყურებლად" - "ამ პროგრამის ასაკობრივი შეზღუდვაა: %1$s. მის საყურებლად, შეიყვანეთ PIN-კოდი" - "შეიყვანეთ თქვენი PIN" - "მშობელთა კონტროლის დასაყენებლად შექმენით PIN" - "შეიყვანეთ ახალი PIN" - "კვლავ შეიყვანეთ თქვენი PIN" - "შეიყვანეთ თქვენი მიმდინარე PIN" - - თქვენ შეიყვანეთ არასწორი PIN 5-ჯერ.\nცადეთ ისევ %1$d წამში. - თქვენ შეიყვანეთ არასწორი PIN 5-ჯერ.\nცადეთ ისევ %1$d წამში. - - "ეს PIN არასწორი იყო. ისევ სცადეთ." - "სცადეთ ისევ, PIN არ შეესაბამება" - "პარამეტრები" - "არხების სიის მორგება" - "აირჩიეთ არხები პროგრამის სახელმძღვანელოსთვის" - "არხების წყაროები" - "ხელმისაწვდომია ახალი არხები" - "მშობელთა კონტროლი" - "ღია კოდის ლიცენზიები" - "ღია კოდის ლიცენზიები" - "ვერსია" - "ამ არხის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN" - "ამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN" - "ამ პროგრამის რეიტინგია %1$s.\nამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN." - "ამ არხის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." - "ამ პროგრამის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." - "ამ პროგრამის რეიტინგია: %1$s.\nამ პროგრამის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი." - "პროგრამა დაბლოკილია" - "ამ პროგრამის რეიტინგია %1$s" - "მხოლოდ აუდიო" - "სიგნალი სუსტია" - "ინტერნეტთან კავშირი არ არის" - - ამ არხის დაკვრა %1$s-მდე ვერ მოხერხდება, რადგან იწერება სხვა არხები. \n\nჩაწერის განრიგის კორექტირებისთვის, დააჭირეთ „მარჯვნივ“-ს. - ამ არხის დაკვრა %1$s-მდე ვერ მოხერხდება, რადგან იწერება სხვა არხი. \n\nჩაწერის განრიგის კორექტირებისთვის, დააჭირეთ „მარჯვნივ“-ს. - - "უსათაურო" - "არხი დაბლოკილია" - "ახალი" - "წყაროები" - - %1$d არხი - %1$d არხი - - "ხელმისაწვდომი არხები არ არის" - "ახალი" - "არ არის დაყენებული" - "მეტი წყაროს მიღება" - "დაათვალიერეთ აპები, რომლებიც პირდაპირ არხებს გთავაზობთ" - "ხელმისაწვდომია არხების ახალი წყაროები" - "არხების ახალი წყაროები დამატებით არხებს გთავაზობთ.\nდააყენეთ ისინი ახლავე ან მოგვიანებით, არხების წყაროების პარამეტრებიდან." - "ახლავე დაყენება" - "კარგი, გასაგებია" - - - "დააჭირეთ არჩევას"" სატელევიზიო მენიუზე წვდომისთვის." - "TV შეყვანა ვერ მოიძებნა." - "TV შეყვანა ვერ იძებნება." - "არ არის PIP-ის მხარდაჭერა." - "PIP-ში გამოსაჩენი შემავალი სიგნალი მიუწვდომელია." - "ტიუნერის ტიპი არ არის შესაფერისი; გთხოვთ გაუშვით პირდაპირი არხების აპი, სატელევიზიო შეტანის ტიუნერის ტიპისათვის." - "არხების დაჭერა ვერ მოხერხდა." - "ამ მოქმედების შესასრულებლად აპი ვერ მოიძებნა." - "წყაროს ყველა არხი დამალულია.\nსაყურებლად აირჩიეთ მინიმუმ ერთი არხი." - "ვიდეო მიუწვდომელი გახდა მოულოდნელად." - "ღილაკი „უკან“ დაკავშირებული მოწყობილობისთვისაა. გამოსასვლელად დააჭირეთ ღილაკს „მთავარი“." - "პირდაპირი არხები საჭიროებს ნებართვას ტელეპროგრამის წასაკითხად." - "დააყენეთ თქვენი წყაროები" - "პირდაპირი არხები ტრადიციულ სატელევიზიო არხებსა და აპების მიერ მოწოდებულ სტრიმინგის არხებს აერთიანებს. \n\nდასაწყებად, დააყენეთ არხების უკვე დაინსტალირებული წყაროები, ან დაათვალიერეთ Google Play Store და აღმოაჩინეთ მეტი აპი, რომლებიც პირდაპირ არხებს გთავაზობთ." - "ჩანაწერები და განრიგები" - "10 წუთი" - "30 წუთი" - "1 საათი" - "3 საათი" - "ბოლო" - "დაგეგმილი" - "სერიები" - "სხვა" - "არხის ჩაწერა ვერ მოხერხდება." - "პროგრამის ჩაწერა ვერ მოხერხდება." - "%1$s დაიგეგმა ჩასაწერად" - "%1$s იწერება ამ მომენტიდან %2$s-მდე" - "სრული განრიგი" - - შემდეგი %1$d დღე - შემდეგი %1$d დღე - - - %1$d წუთი - %1$d წუთი - - - %1$d ახალი ჩანაწერი - %1$d ახალი ჩანაწერი - - - %1$d ჩანაწერი - %1$d ჩანაწერი - - - %1$d ჩანაწერი დაიგეგმა - %1$d ჩანაწერი დაიგეგმა - - "ყურება" - "თავიდან დაკვრა" - "დაკვრის განახლება" - "წაშლა" - "ჩანაწერების წაშლა" - "განახლება" - "სეზონი %1$s" - "განრიგის ნახვა" - "მეტის წაკითხვა" - "ჩანაწერების წაშლა" - "აირჩიეთ ეპიზოდები, რომელთა წაშლაც გსურთ. წაშლის შემდეგ, მათი აღდგენა ვეღარ მოხერხდება." - "წასაშლელი ჩანაწერები არ არის." - "ნაყურები ეპიზოდების არჩევა" - "ყველა ეპიზოდის არჩევა" - "ყველა მონიშვნის მოხსნა" - "ნაყურებია %2$d-დან %1$d წუთი" - "ნაყურებია %2$d-დან %1$d წამი" - "არასოდეს ნაყურები" - - %2$d-დან %1$d ეპიზოდი წაიშალა - %2$d-დან %1$d ეპიზოდი წაიშალა - - "პრიორიტეტი" - "ყველაზე მაღალი" - "ყველაზე დაბალი" - "№ %1$d" - "არხები" - "ნებისმიერი" - "აირჩიეთ პრიორიტეტი" - "თუ ჩასაწერად მეტისმეტად ბევრი პროგრამა დაიგეგმება, ჩაიწერება მხოლოდ მაღალი პრიორიტეტის მქონე პროგრამები." - "შენახვა" - "ერთჯერად ჩანაწერებს მიენიჭოს მეტი პრიორიტეტი" - "გაუქმება" - "გაუქმება" - "დავიწყება" - "შეწყვეტა" - "ჩაწერის განრიგის ნახვა" - "მხოლოდ ეს პროგრამა" - "ახლა — %1$s" - "მთლიანი სერიალი…" - "მაინც დაგეგმვა" - "სანაცვლოდ, ჩაიწეროს ეს" - "ამ ჩანაწერის გაუქმება" - "ახლავე ყურება" - "შესაძლებელია ჩაწერა" - "ჩაწერა დაგეგმილია" - "ჩაწერის კონფლიქტი" - "მიმდინარეობს ჩაწერა" - "ჩაწერა ვერ მოხერხდა" - "მიმდინარეობს პროგრამების წაკითხვა ჩაწერის განრიგების შესაქმნელად" - "მიმდინარეობს პროგრამების წაკითხვა" - - - "DVR მეტ მეხსიერებას საჭიროებს" - "DVR-ის მეშვეობით პროგრამების ჩაწერა შესაძლებელია, თუმცა თქვენს მოწყობილობაზე ამჟამად ხელმისაწვდომი მეხსიერება DVR-ის მუშაობისთვის არასაკმარისია. გთხოვთ, დააკავშიროთ %1$s გბაიტი ან მეტი მოცულობის დისკწამყვანი და მიჰყვეთ ინსტრუქციას, რომელიც დაგეხმარებათ, დააფორმატოთ ის მოწყობილობის მეხსიერების სახით." - "აკლია მეხსიერება" - "DVR-ის მიერ გამოყენებული მეხსიერების ნაწილი ვერ მოიძებნა. DVR-ის ხელახლა ჩასართავად, გთხოვთ, დააკავშიროთ გარე დისკი, რომლითაც ადრე სარგებლობდით. თუ მეხსიერება ხელმისაწვდომი აღარ არის, შეგიძლიათ მეხსიერების დავიწყება აირჩიოთ." - "გსურთ მეხსიერების დავიწყება?" - "მთელი თქვენი ჩაწერილი კონტენტი და ყველა განრიგი დაიკარგება." - "გსურთ ჩაწერის შეწყვეტა?" - "ჩაწერილი კონტენტი შეინახება." - - - "ჩაწერა დაგეგმილია, თუმცა ის კონფლიქტშია" - "ჩაწერა დაიწყო, თუმცა ის კონფლიქტშია" - "%1$s ჩაიწერება." - "%1$s იწერება." - "%1$s ნაწილობრივ არ ჩაიწერება." - "%1$s და %2$s ნაწილობრივ არ ჩაიწერება." - "%1$s, %2$s და კიდევ ერთი განრიგი ნაწილობრივ არ ჩაიწერება." - - %1$s, %2$s და კიდევ %3$d განრიგი ნაწილობრივ არ ჩაიწერება. - %1$s, %2$s და კიდევ %3$d განრიგი ნაწილობრივ არ ჩაიწერება. - - "გსურთ ჩაწერა?" - "რამდენი ხანი გსურთ, გაგრძელდეს ჩაწერა?" - "უკვე დაგეგმილია" - "იგივე პროგრამა უკვე დაიგეგმა ჩასაწერად %1$s-ზე." - "უკვე ჩაწერილია" - "ეს პროგრამა უკვე ჩაწერილია და DVR ბიბლიოთეკაშია ხელმისაწვდომი." - - - - - - - - - "ჩაწერილი პროგრამა ვერ მოიძებნა." - "დაკავშირებული ჩანაწერები" - "(პროგრამის აღწერა არ არის)" - - %1$d ჩანაწერი - %1$d ჩანაწერი - - " / " - "%1$s ამოიშალა ჩაწერის განრიგიდან" - "ჩაიწერება ნაწილობრივ ტუნერის კონფლიქტების გამო." - "არ ჩაიწერება ტუნერის კონფლიქტების გამო." - "ჩაწერა ჯერ დაგეგმილი არ არის.\nჩაწერის დაგეგმვა პროგრამების მეგზურიდან შეგიძლიათ." - - %1$d ჩაწერის კონფლიქტი - %1$d ჩაწერის კონფლიქტი - - "სერიალის პარამეტრები" - "სერიალის ჩაწერის დაწყება" - "სერიალის ჩაწერის შეწყვეტა" - "გსურთ სერიალის ჩაწერის შეწყვეტა?" - "ჩაწერილი ეპიზოდები DVR ბიბლიოთეკაში კვლავ იქნება ხელმისაწვდომი." - "შეწყვეტა" - "ეპიზოდები მიუწვდომელია.\nმათი ჩაწერა მოხდება მაშინ, როცა ისინი ხელმისაწვდომი გახდება." - - (%1$d წუთი) - (%1$d წუთი) - - "დღეს" - "ხვალ" - "გუშინ" - "დღეს, %1$s-ზე" - "ხვალ, %1$s-ზე" - "შეფასება" - diff --git a/res/values-kk-rKZ-v23/strings.xml b/res/values-kk-rKZ-v23/strings.xml new file mode 100644 index 00000000..4da42f69 --- /dev/null +++ b/res/values-kk-rKZ-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Арналар" + diff --git a/res/values-kk-rKZ/arrays.xml b/res/values-kk-rKZ/arrays.xml new file mode 100644 index 00000000..bbb0282c --- /dev/null +++ b/res/values-kk-rKZ/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Қалыпты" + "Толық" + "Масштабтау" + + + "Барлық арналар" + "Отбасы/балалар" + "Спорт" + "Сатып алулар" + "Фильмдер" + "Комедия" + "Саяхат" + "Драма" + "Білім" + "Жануарлар/жабайы табиғат" + "Жаңалықтар" + "Ойындар" + "Өнер" + "Ойын-сауық" + "Өмір салты" + "Mузыка" + "Премьера" + "Технология/ғылым" + + + "Live арналары" + "Мазмұнды табудың оңай жолы" + "Қолданбаларды жүктеп, көбірек арналарды алу" + "Арналар қатарын реттеу" + + + "Бейнелерді қолданбадан теледидар арналарын көргендей тамашалаңыз." + "Арналарды теледидардан көргендей, бейнелерді нұсқаулығы таныс әрі \nинтерфейсі ыңғайлы қолданбадан шолыңыз." + "Тікелей эфир арналарын ұсынатын қолданбаларды орнату арқылы арналар санын арттырыңыз. \nТеледидар мәзіріндегі сілтемені пайдаланып Google Play Store дүкенінен үйлесімді қолданбаларды табыңыз." + "Жаңадан орнатылған арна көздерінен тізім құрыңыз. \nОл үшін \"Параметрлер\" мәзірінен \"Арна көздері\" тармағын таңдаңыз." + + diff --git a/res/values-kk-rKZ/rating_system_strings.xml b/res/values-kk-rKZ/rating_system_strings.xml new file mode 100644 index 00000000..efeb4715 --- /dev/null +++ b/res/values-kk-rKZ/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Бағдарламаларда 15 жасқа толмаған пайдаланушылардың көруіне болмайтын материалдар болуы мүмкін, сондықтан ата-ананың рұқсаты керек." + "Бағдарламаларда 19 жасқа толмаған пайдаланушылардың көруіне болмайтын материалдар болуы мүмкін, сондықтан 19-ға дейінгі балаларға рұқсат етілмейді." + "Ұятсыз әңгіме" + "Дөрекі сөздер" + "Жыныстық қатынасқа қатысты мазмұн" + "Қатыгездік" + "Қиял және қатыгездік" + "Бұл бағдарлама барлық балаларға жарайтындай жасалған." + "Бұл бағдарлама 7 және одан жоғары жастағы балаларға арналған." + "Ата-аналардың көпшілігі бұл бағдарламаны барлық жастар үшін жарамды деп табады." + "Бұл бағдарлама ата-аналар жасырақ балалар үшін жарамсыз деп табуы мүмкін материалды қамтуы мүмкін. Көптеген ата-аналар жасырақ балаларымен көргісі келуі мүмкін." + "Бұл бағдарлама көп ата-ана 14 жасқа толмаған балалар үшін жарамсыз деп табатын біраз материалды қамтиды." + "Бұл бағдарлама ересектердің көруіне арналған және 17 жасқа толмаған балалар үшін жарамсыз болуы мүмкін." + "Фильм рейтингтері" + "Барлық көрермендерге. Ата-аналар балаларымен көруге болады." + "Ата-ананың бақылауы болған жөн. Ата-аналардың балаларына көрсеткісі келмейтін кейбір материалдар болуы мүмкін." + "Ата-ананың бақылауы қажет. Кейбір материалдар жасөспірімдерге сәйкес келмеуі мүмкін." + "Тыйым салынады, ересектерге арналған кейбір материалдар бар. Ата-ана балаларымен бірге көруден бұрын фильм туралы көбірек білуі қажет." + "17 және одан төмен жастағыларға тыйым салынады. Тек ересектер үшін. Балаларға тыйым салынады." + diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml new file mode 100644 index 00000000..44441192 --- /dev/null +++ b/res/values-kk-rKZ/strings.xml @@ -0,0 +1,357 @@ + + + + + "моно" + "стерео" + "Ойын тетіктері" + "Арналар" + "ТД опциялары" + "Бұл арна үшін ойнатуды басқару элементтерін қол жетімсіз" + "Ойнату немесе кідірту" + "Жылдам алға айналдыру" + "Кері айналдыру" + "Келесі" + "Алдыңғы" + "Бағдарламалар нұс-ғы" + "Жаңа арналар қол жетім." + "%1$s қолданбасын ашу" + "Жасырын титрлер" + "Дисплей режимі" + "PIP" + "Мультиаудио" + "Басқа арналар" + "Параметрлер" + "ТД (антенна/кабель)" + "Бағдарлама туралы ақпарат жоқ" + "Ақпарат жоқ" + "Бөгелген арна" + "Белгісіз тіл" + "Субтитрлер: %1$d" + "Жасырын титрлер" + "Өшірулі" + "Пішімдеуді теңшеу" + "Жасырын титрлер үшін жүйелік пар-ді орн." + "Дисплей режимі" + "Мультиаудио" + "моно" + "стерео" + "5.1 көлемді" + "7.1 көлемді" + "%d арна" + "Арналар тіз. тең." + "Топты таңдау" + "Топтан таңдауды алу" + "Топтау шарты" + "Арна көзі" + "HD/SD" + "HD" + "SD" + "Топтау шарты" + "Бұл бағдарламаға тыйым салынған" + "Бұл бағдарламаның жас бойынша шектеуі белгіленбеген" + "Бұл бағдарламаға %1$s бағасы қойылған" + "Кіріс автоматты түрде іздеуді қолдамайды" + %s» үшін автоматты түрде іздеуді бастау мүмкін болмады" + "Жасырын титрлер үшін жүйелік параметрлерді іске қосу мүмкін болмады." + + %1$d арна қосылды + %1$d арна қосылды + + "Арналар қосылмаған" + "Ата-аналық бақылау" + "Қосулы" + "Өшірулі" + "Арналар бөгелген" + "Барлығын бөгеу" + "Бәрін бөгеуден шығ." + "Жасырын арналар" + "Бағдарлама шек-рі" + "PIN кодын өзгерту" + "Бағалау жүйелері" + "Рейтингтер" + "Бар. бағ. жүй-н көру" + "Басқа елдер" + "Ешқандай" + "Ешқандай" + "Жас бойынша шектеуі белгіленбеген" + "Жас бойынша шектеуі белгіленбеген бағдарламаларды бөгеу" + "Ешқандай" + "Жоғары шектеулер" + "Орташа шектеулер" + "Төмен шектеулер" + "Теңшелетін" + "Мазмұн балалар үшін жарамды" + "Үлкенірек балаларға жарамды мазмұн" + "Мазмұн жасөспірімдерге жарамды" + "Қолмен реттелетін шектеулер" + + + "%1$s және ішкі бағ-р" + "Ішкі бағалар" + "Бұл арнаны көру үшін PIN кодын енгізіңіз" + "Бұл бағдарламаны көру үшін PIN кодын енгізіңіз" + "Бұл бағдарламаға %1$s бағасы берілген. Оны көру үшін PIN кодын енгізіңіз" + "Бұл бағдарламаның жас бойынша шектеуі белгіленбеген. Бұл бағдарламаны көру үшін PIN кодын енгізіңіз" + "PIN кодын енгізіңіз" + "Ата-аналық бақылауды орнату үшін PIN кодын жасаңыз" + "Жаңа PIN кодын енгізіңіз" + "PIN кодын растау" + "Ағымдағы PIN кодын енгізіңіз" + + Қате PIN кодын 5 рет қате енгіздіңіз.\n%1$d секундтан кейін қайталап көріңіз. + Қате PIN кодын 5 рет қате енгіздіңіз.\n%1$d секундтан кейін қайталап көріңіз. + + "Бұл PIN қате болды. Әрекетті қайталаңыз." + "Әрекетті қайталаңыз, PIN кодтары сәйкес емес" + "Пошта индексін енгізіңіз." + "Тікелей арналар қолданбасы телеарналардың толық бағдарламалар нұсқаулығын ұсыну үшін пошта индексін пайдаланады." + "Индексті енгізіңіз" + "Индекс жарамсыз" + "Параметрлер" + "Арналар тізімін теңшеу" + "Бағдарлама нұсқаулығына арналған арналарды таңдау" + "Арна көздері" + "Жаңа арналар қосылды" + "Ата-аналық бақылау" + "Уақытты жылжыту" + "Тікелей эфирдегі бағдарламаларды көру кезінде тоқтатуға немесе артқа айналдыруға болатын жазу.\nЕскерту: бұл жадты қарқынды пайдалану арқылы ішкі жадтың қызмет көрсету мерзімін азайтуы мүмкін." + "Ашық бастапқы код лицензиялары" + "Пікір жіберу" + "Нұсқа" + "Бұл арнаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз" + "Бұл бағдарламаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз" + "Бұл бағдарламаның жас бойынша шектеуі белгіленбеген.\nБұл бағдарламаны көру үшін оң жаққа бағытталған көрсеткіні басып, PIN кодын енгізіңіз" + "Бұл бағдарламаға %1$s бағасы қойылған.\nБұл бағдарламаны көру үшін \"Оңға\" түймесін басып, PIN кодын енгізіңіз." + "Бұл арнаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." + "Бұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." + "Бұл бағдарламаның жас бойынша шектеуі белгіленбеген.\nБұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." + "Бұл бағдарлама %1$s болып бағаланды.\nБұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." + "Бағдарлама бөгелген" + "Бұл бағдарламаның жас бойынша шектеуі белгіленбеген" + "Бұл бағдарламаға %1$s бағасы қойылған" + "Тек аудио" + "Cигнал әлсіз" + "Интернетпен байланыс жоқ" + + Басқа арналар жазылып жатқандықтан, бұл арна %1$s дейін ойнатылмайды. \n\nЖазып алу кестесін реттеу үшін \"Оң жақ\" түймесін басыңыз. + Басқа арна жазылып жатқандықтан, бұл арна %1$s дейін ойнатылмайды. \n\nЖазып алу кестесін реттеу үшін \"Оң жақ\" түймесін басыңыз. + + "Тақырыпсыз" + "Арна бөгелген" + "Жаңа" + "Дереккөздер" + + %1$d арна + %1$d арна + + "Қолжетімді арна жоқ" + "Жаңа" + "Реттелмеген" + "Қосымша дереккөздерді алу" + "Тікелей эфир арналарын ұсынатын қолданбаларды шолу" + "Жаңа арна көздері қолжетімді" + "Жаңа арна көздерінде ұсынатын арналар бар.\nОларды қазір орнатыңыз немесе арна көздері параметрінде кейінірек орындаңыз." + "Қазір орнату" + "Жарайды, түсіндім" + + + "Теледидар мәзіріне кіру үшін ТАҢДАУ"" түймесін басыңыз." + "ТД кіріс сигналы табылмады" + "ТД кіріс сигналын табу мүмкін емес" + "Тюнер түрі жарамсыз. Тюнер түрінің ТД кіріс сигналы үшін Тікелей эфир арналары қолданбасын іске қосыңыз." + "Арнаға реттелмеді" + "Бұл әрекетті орындайтын қолданба табылмады." + "Барлық көз арналар жасырылған.\nКөру үшін кемінде бір арнаны таңдаңыз." + "Бейне белгісіз себеппен қолжетімсіз болып тұр" + "КЕРІ пернесі қосылған құрылғыға арналған. Шығу үшін НЕГІЗГІ пернесін басыңыз." + "Live TV үшін ТД тізімдерін оқу рұқсаты қажет." + "Дерек көздерін орнату" + "Live арналары қолданбалардағы ағынды арналармен қатар дәстүрлі теледидар арналарын да ұсынады. \n\nІсті бұрын орнатылған арна көздерін реттеуден бастаңыз. Тікелей эфир арналарын ұсынатын басқа қолданбаларды Google Play Store дүкенінен алуға болады." + "Жазбалар және кестелер" + "10 минут" + "30 минут" + "1 сағат" + "3 сағат" + "Жақындағы" + "Жоспарланған" + "Сериялар" + "Басқалар" + "Арнаны жазу мүмкін емес." + "Бағдарламаны жазу мүмкін емес." + "%1$s бағдарламасын жазып алу жоспарланды" + "%1$s бағдарламасын қазірден бастап %2$s дейін жазу" + "Толық кесте" + + Келесі %1$d күн бойы + Келесі %1$d күн бойы + + + %1$d минут + %1$d минут + + + %1$d жаңа жазба + %1$d жаңа жазба + + + %1$d жазба + %1$d жазба + + + %1$d жазба жоспарланды + %1$d жазба жоспарланды + + "Жазудан бас тарту" + "Жазуды тоқтату" + "Сағат" + "Басынан бастап ойнату" + "Ойнатуды жалғастыру" + "Жою" + "Жазбаларды жою" + "Жалғастыру" + "%1$s-маусым" + "Кестені көру" + "Толығырақ оқу" + "Жазбаларды жою" + "Жойғыңыз келетін серияларды таңдаңыз. Олар біржола жойылады және қалпына келтірілмейді." + "Жоюға болатын жазбалар жоқ." + "Көрген серияларды таңдау" + "Барлық серияларды таңдау" + "Барлық серияларды таңдаудан алып тастау" + "%1$d минут көрілді (жалпы ұзақтығы: %2$d)" + "%1$d секунд көрілді (жалпы ұзақтығы: %2$d)" + "Ешқашан көрмеген жазбалар" + + %1$d серия жойылды (жалпы саны: %2$d) + %1$d серия жойылды (жалпы саны: %2$d) + + "Маңыздылық" + "Ең жоғары" + "Ең төмен" + "№ %1$d" + "Арналар" + "Кез келген" + "Маңыздылық деңгейін таңдау" + "Бір мезгілде тым көп бағдарламаны жазып алу қажет болса, тек маңыздылығы жоғарылары ғана жазылады." + "Сақтау" + "Бір жолғы жазбалар ең маңызды болып табылады" + "Тоқтату" + "Жазу кестесін көру" + "Тек осы бағдарлама" + "қазір - %1$s" + "Барлық сериялар..." + "Бәрібір жазып алу" + "Орнына мына бағдарламаны жазу" + "Бұл жазып алу кестесінен бас тарту" + "Қазір көру" + "Жазбалар жойылуда…" + "Жазып алуға болады" + "Жазып алынады" + "Жазу кестесінде қайшылық бар" + "Жазылуда" + "Жазу сәтсіз аяқталды" + "Бағдарлама ақпараты оқылуда" + "Соңғы жазбаларды көру" + "%1$s бағдарламасын жазу аяқталмады." + "%1$s және %2$s бағдарламасын жазу аяқталмады." + "%1$s, %2$s және %3$s бағдарламасын жазу аяқталмады." + "Жад жеткіліксіз болғандықтан, %1$s бағдарламасын жазу аяқталмады." + "Жад жеткіліксіз болғандықтан, %1$s және %2$s бағдарламасын жазу аяқталмады." + "Жад жеткіліксіз болғандықтан, %1$s, %2$s және %3$s бағдарламасын жазу аяқталмады." + "DVR көбірек бос орынды қажет етеді" + "Бағдарламаларды DVR арқылы жазып алуға болады. Алайда DVR жұмысы үшін құрылғыда бос орын жеткіліксіз. %1$d ГБ не одан үлкен сыртқы дискіні жалғап, оны құрылғы жады ретінде форматтау қадамдарын орындаңыз." + "Жадта орын жеткіліксіз" + "Жадта орын жеткіліксіз болғандықтан, бұл бағдарлама жазылмайды. Кейбір бұрыннан бар жазбаларды жойыңыз." + "Жад жоқ" + "Жазу тоқтатылсын ба?" + "Жазылған мазмұн сақталады." + "Осы бағдарламамен қайшылық тудырғандықтан, %1$s жазу тоқтатылды. Жазып алынған мазмұн сақталады." + "Жоспарланған, бірақ қайшылықтары бар" + "Жазу басталды, бірақ қайшылықтары бар" + "%1$s жазылады." + "%1$s жазылуда." + "%1$s бағдарламасының кейбір бөліктері жазылмайды." + "%1$s және %2$s бағдарламасының кейбір бөліктері жазылмайды." + "%1$s, %2$s бағдарламаларының және тағы бір жоспарланған бағдарламаның кейбір бөліктері жазылмайды." + + %1$s, %2$s бағдарламалары мен тағы %3$d бағдарламаның кейбір бөліктері жазылмайды. + %1$s, %2$s бағдарламалары мен жоспарланған тағы %3$d бағдарламаның кейбір бөліктері жазылмайды. + + "Нені жазып алғыңыз келеді?" + "Қаншалықты ұзақ жазасыз?" + "Әлдеқашан жоспарланған" + "Бұл бағдарламаны жазып алу басқа уақытқа (%1$s) әлдеқашан жоспарланған." + "Бұрын жазылған" + "Бұл бағдарлама бұрын жазылған және DVR кітапханасында бар." + "Серияларды жазып алу жоспарланды" + + %2$s үшін %1$d жазба жоспарланды. + %2$s үшін %1$d жазба жоспарланды. + + + %2$s үшін %1$d жазба жоспарланды. Олардың ішінде %3$d серия қайшылықтарға байланысты жазылмайды. + %2$s үшін %1$d жазба жоспарланды. Ол қайшылықтарға байланысты жазылмайды. + + + %2$s үшін %1$d жазба жоспарланды. Бұл сериалдың және басқа сериалдың %3$d сериясы қайшылықтарға байланысты жазылмайды. + %2$s үшін %1$d жазба жоспарланды. Бұл сериалдың және басқа сериалдың %3$d сериясы қайшылықтарға байланысты жазылмайды. + + + %2$s үшін %1$d жазба жоспарланды. Басқа сериалдың 1 сериясы қайшылықтарға байланысты жазылмайды. + %2$s үшін %1$d жазба жоспарланды. Басқа сериалдың 1 сериясы қайшылықтарға байланысты жазылмайды. + + + %2$s үшін %1$d жазба .жоспарланды. Басқа сериалдың %3$d сериясы қайшылықтарға байланысты жазылмайды. + %2$s үшін %1$d жазба .жоспарланды. Басқа сериалдың %3$d сериясы қайшылықтарға байланысты жазылмайды. + + "Жазылған бағдарлама табылмады." + "Қатысты жазбалар" + + %1$d жазба + %1$d жазба + + " /  " + "%1$s бағдарламасы жазып алу кестесінен өшірілді" + "Тюнер қайшылықтарына байланысты толық жазылмайды." + "Тюнер қайшылықтарына байланысты жазылмайды." + "Кестеде ешқандай жазып алу белгіленбеген.\nМұны бағдарлама нұсқаулығынан жоспарлауға болады." + + %1$d жазу қайшылығы + %1$d жазу қайшылығы + + "Сериялар параметрлері" + "Серияларды жазуды бастау" + "Серияларды жазуды тоқтату" + "Серияларды жазу тоқтатылсын ба?" + "Жазылған серияларды DVR кітапханасынан алуға болады." + "Тоқтату" + "Ешқандай серия көрсетіліп жатқан жоқ." + "Ешқандай серия қолжетімді емес.\nОларды қолжетімді болған кезде ғана жазып алуға болады." + + (%1$d минут) + (%1$d минут) + + "Бүгін" + "Ертең" + "Кеше" + "Бүгін %1$s" + "Ертең %1$s" + "Ұпай саны" + "Жазылған бағдарламалар" + diff --git a/res/values-kk-v23/strings.xml b/res/values-kk-v23/strings.xml deleted file mode 100644 index 4da42f69..00000000 --- a/res/values-kk-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Арналар" - diff --git a/res/values-kk/arrays.xml b/res/values-kk/arrays.xml deleted file mode 100644 index bbb0282c..00000000 --- a/res/values-kk/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Қалыпты" - "Толық" - "Масштабтау" - - - "Барлық арналар" - "Отбасы/балалар" - "Спорт" - "Сатып алулар" - "Фильмдер" - "Комедия" - "Саяхат" - "Драма" - "Білім" - "Жануарлар/жабайы табиғат" - "Жаңалықтар" - "Ойындар" - "Өнер" - "Ойын-сауық" - "Өмір салты" - "Mузыка" - "Премьера" - "Технология/ғылым" - - - "Live арналары" - "Мазмұнды табудың оңай жолы" - "Қолданбаларды жүктеп, көбірек арналарды алу" - "Арналар қатарын реттеу" - - - "Бейнелерді қолданбадан теледидар арналарын көргендей тамашалаңыз." - "Арналарды теледидардан көргендей, бейнелерді нұсқаулығы таныс әрі \nинтерфейсі ыңғайлы қолданбадан шолыңыз." - "Тікелей эфир арналарын ұсынатын қолданбаларды орнату арқылы арналар санын арттырыңыз. \nТеледидар мәзіріндегі сілтемені пайдаланып Google Play Store дүкенінен үйлесімді қолданбаларды табыңыз." - "Жаңадан орнатылған арна көздерінен тізім құрыңыз. \nОл үшін \"Параметрлер\" мәзірінен \"Арна көздері\" тармағын таңдаңыз." - - diff --git a/res/values-kk/rating_system_strings.xml b/res/values-kk/rating_system_strings.xml deleted file mode 100644 index efeb4715..00000000 --- a/res/values-kk/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Бағдарламаларда 15 жасқа толмаған пайдаланушылардың көруіне болмайтын материалдар болуы мүмкін, сондықтан ата-ананың рұқсаты керек." - "Бағдарламаларда 19 жасқа толмаған пайдаланушылардың көруіне болмайтын материалдар болуы мүмкін, сондықтан 19-ға дейінгі балаларға рұқсат етілмейді." - "Ұятсыз әңгіме" - "Дөрекі сөздер" - "Жыныстық қатынасқа қатысты мазмұн" - "Қатыгездік" - "Қиял және қатыгездік" - "Бұл бағдарлама барлық балаларға жарайтындай жасалған." - "Бұл бағдарлама 7 және одан жоғары жастағы балаларға арналған." - "Ата-аналардың көпшілігі бұл бағдарламаны барлық жастар үшін жарамды деп табады." - "Бұл бағдарлама ата-аналар жасырақ балалар үшін жарамсыз деп табуы мүмкін материалды қамтуы мүмкін. Көптеген ата-аналар жасырақ балаларымен көргісі келуі мүмкін." - "Бұл бағдарлама көп ата-ана 14 жасқа толмаған балалар үшін жарамсыз деп табатын біраз материалды қамтиды." - "Бұл бағдарлама ересектердің көруіне арналған және 17 жасқа толмаған балалар үшін жарамсыз болуы мүмкін." - "Фильм рейтингтері" - "Барлық көрермендерге. Ата-аналар балаларымен көруге болады." - "Ата-ананың бақылауы болған жөн. Ата-аналардың балаларына көрсеткісі келмейтін кейбір материалдар болуы мүмкін." - "Ата-ананың бақылауы қажет. Кейбір материалдар жасөспірімдерге сәйкес келмеуі мүмкін." - "Тыйым салынады, ересектерге арналған кейбір материалдар бар. Ата-ана балаларымен бірге көруден бұрын фильм туралы көбірек білуі қажет." - "17 және одан төмен жастағыларға тыйым салынады. Тек ересектер үшін. Балаларға тыйым салынады." - diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml deleted file mode 100644 index 2a47972c..00000000 --- a/res/values-kk/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "моно" - "стерео" - "Ойын тетіктері" - "Жақ-ғы арналар" - "ТД опциялары" - "PIP опциялары" - "Бұл арна үшін ойнатуды басқару элементтерін қол жетімсіз" - "Ойнату немесе кідірту" - "Жылдам алға айналдыру" - "Кері айналдыру" - "Келесі" - "Алдыңғы" - "Бағдарламалар нұс-ғы" - "Жаңа арналар қол жетім." - "%1$s қолданбасын ашу" - "Жасырын титрлер" - "Дисплей режимі" - "PIP" - "Қосулы" - "Өшірулі" - "Мультиаудио" - "Басқа арналар" - "Параметрлер" - "Көз" - "Алмастыру" - "Қосулы" - "Өшірулі" - "Дыбыс" - "Негізгі" - "PIP терезесі" - "Орналасу" - "Төменгі оң жақ" - "Жоғарғы оң жақ" - "Жоғарғы сол жақ" - "Төменгі сол жақ" - "Қатар" - "Өлшем" - "Үлкен" - "Кішкентай" - "Енгізу көзі" - "ТД (антенна/кабель)" - "Бағдарлама туралы ақпарат жоқ" - "Ақпарат жоқ" - "Бөгелген арна" - "Белгісіз тіл" - "Жасырын титрлер" - "Өшірулі" - "Пішімдеуді теңшеу" - "Жасырын титрлер үшін жүйелік пар-ді орн." - "Дисплей режимі" - "Мультиаудио" - "моно" - "стерео" - "5.1 көлемді" - "7.1 көлемді" - "%d арна" - "Арналар тіз. тең." - "Топты таңдау" - "Топтан таңдауды алу" - "Топтау шарты" - "Арна көзі" - "HD/SD" - "HD" - "SD" - "Топтау шарты" - "Бұл бағдарламаға тыйым салынған" - "Бұл бағдарламаға %1$s бағасы қойылған" - "Кіріс автоматты түрде іздеуді қолдамайды" - %s» үшін автоматты түрде іздеуді бастау мүмкін болмады" - "Жасырын титрлер үшін жүйелік параметрлерді іске қосу мүмкін болмады." - - %1$d арна қосылды - %1$d арна қосылды - - "Арналар қосылмаған" - "Тюнер" - "Ата-аналық бақылау" - "Қосулы" - "Өшірулі" - "Арналар бөгелген" - "Барлығын бөгеу" - "Бәрін бөгеуден шығ." - "Жасырын арналар" - "Бағдарлама шек-рі" - "PIN кодын өзгерту" - "Бағалау жүйелері" - "Рейтингтер" - "Бар. бағ. жүй-н көру" - "Басқа елдер" - "Ешқандай" - "Ешқандай" - "Ешқандай" - "Жоғары шектеулер" - "Орташа шектеулер" - "Төмен шектеулер" - "Теңшелетін" - "Мазмұн балалар үшін жарамды" - "Үлкенірек балаларға жарамды мазмұн" - "Мазмұн жасөспірімдерге жарамды" - "Қолмен реттелетін шектеулер" - - - "%1$s және ішкі бағ-р" - "Ішкі бағалар" - "Бұл арнаны көру үшін PIN кодын енгізіңіз" - "Бұл бағдарламаны көру үшін PIN кодын енгізіңіз" - "Бұл бағдарламаға %1$s бағасы берілген. Оны көру үшін PIN кодын енгізіңіз" - "PIN кодын енгізіңіз" - "Ата-аналық бақылауды орнату үшін PIN кодын жасаңыз" - "Жаңа PIN кодын енгізіңіз" - "PIN кодын растау" - "Ағымдағы PIN кодын енгізіңіз" - - Қате PIN кодын 5 рет қате енгіздіңіз.\n%1$d секундтан кейін қайталап көріңіз. - Қате PIN кодын 5 рет қате енгіздіңіз.\n%1$d секундтан кейін қайталап көріңіз. - - "Бұл PIN қате болды. Әрекетті қайталаңыз." - "Әрекетті қайталаңыз, PIN кодтары сәйкес емес" - "Параметрлер" - "Арналар тізімін теңшеу" - "Бағдарлама нұсқаулығына арналған арналарды таңдау" - "Арна көздері" - "Жаңа арналар қосылды" - "Ата-аналық бақылау" - "Ашық бастапқы код лицензиялары" - "Ашық бастапқы код лицензиялары" - "Нұсқа" - "Бұл арнаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз" - "Бұл бағдарламаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз" - "Бұл бағдарламаға %1$s бағасы қойылған.\nБұл бағдарламаны көру үшін \"Оңға\" түймесін басып, PIN кодын енгізіңіз." - "Бұл арнаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." - "Бұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." - "Бұл бағдарлама %1$s болып бағаланды.\nБұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз." - "Бағдарлама бөгелген" - "Бұл бағдарламаға %1$s бағасы қойылған" - "Тек аудио" - "Cигнал әлсіз" - "Интернетпен байланыс жоқ" - - Басқа арналар жазылып жатқандықтан, бұл арна %1$s дейін ойнатылмайды. \n\nЖазып алу кестесін реттеу үшін \"Оң жақ\" түймесін басыңыз. - Басқа арна жазылып жатқандықтан, бұл арна %1$s дейін ойнатылмайды. \n\nЖазып алу кестесін реттеу үшін \"Оң жақ\" түймесін басыңыз. - - "Тақырыпсыз" - "Арна бөгелген" - "Жаңа" - "Дереккөздер" - - %1$d арна - %1$d арна - - "Қолжетімді арна жоқ" - "Жаңа" - "Реттелмеген" - "Қосымша дереккөздерді алу" - "Тікелей эфир арналарын ұсынатын қолданбаларды шолу" - "Жаңа арна көздері қолжетімді" - "Жаңа арна көздерінде ұсынатын арналар бар.\nОларды қазір орнатыңыз немесе арна көздері параметрінде кейінірек орындаңыз." - "Қазір орнату" - "Жарайды, түсіндім" - - - "Теледидар мәзіріне кіру үшін ТАҢДАУ"" түймесін басыңыз." - "ТД кіріс сигналы табылмады" - "ТД кіріс сигналын табу мүмкін емес" - "PIP мүмкіндігіне қолдау көрсетілмейді" - "PIP мүмкіндігімен көрсетуге болатын кіріс сигналы жоқ" - "Тюнер түрі жарамсыз. Тюнер түрінің ТД кіріс сигналы үшін Тікелей эфир арналары қолданбасын іске қосыңыз." - "Арнаға реттелмеді" - "Бұл әрекетті орындайтын қолданба табылмады." - "Барлық көз арналар жасырылған.\nКөру үшін кемінде бір арнаны таңдаңыз." - "Бейне белгісіз себеппен қолжетімсіз болып тұр" - "КЕРІ пернесі қосылған құрылғыға арналған. Шығу үшін НЕГІЗГІ пернесін басыңыз." - "Live TV үшін ТД тізімдерін оқу рұқсаты қажет." - "Дерек көздерін орнату" - "Live арналары қолданбалардағы ағынды арналармен қатар дәстүрлі теледидар арналарын да ұсынады. \n\nІсті бұрын орнатылған арна көздерін реттеуден бастаңыз. Тікелей эфир арналарын ұсынатын басқа қолданбаларды Google Play Store дүкенінен алуға болады." - "Жазбалар және кестелер" - "10 минут" - "30 минут" - "1 сағат" - "3 сағат" - "Жақындағы" - "Жоспарланған" - "Сериялар" - "Басқалар" - "Арнаны жазу мүмкін емес." - "Бағдарламаны жазу мүмкін емес." - "%1$s бағдарламасын жазып алу жоспарланды" - "%1$s бағдарламасын қазірден бастап %2$s дейін жазу" - "Толық кесте" - - Келесі %1$d күн бойы - Келесі %1$d күн бойы - - - %1$d минут - %1$d минут - - - %1$d жаңа жазба - %1$d жаңа жазба - - - %1$d жазба - %1$d жазба - - - %1$d жазба жоспарланды - %1$d жазба жоспарланды - - "Сағат" - "Басынан бастап ойнату" - "Ойнатуды жалғастыру" - "Жою" - "Жазбаларды жою" - "Жалғастыру" - "%1$s-маусым" - "Кестені көру" - "Толығырақ оқу" - "Жазбаларды жою" - "Жойғыңыз келетін серияларды таңдаңыз. Олар біржола жойылады және қалпына келтірілмейді." - "Жоюға болатын жазбалар жоқ." - "Көрген серияларды таңдау" - "Барлық серияларды таңдау" - "Барлық серияларды таңдаудан алып тастау" - "%1$d минут көрілді (жалпы ұзақтығы: %2$d)" - "%1$d секунд көрілді (жалпы ұзақтығы: %2$d)" - "Ешқашан көрмеген жазбалар" - - %1$d серия жойылды (жалпы саны: %2$d) - %1$d серия жойылды (жалпы саны: %2$d) - - "Маңыздылық" - "Ең жоғары" - "Ең төмен" - "№ %1$d" - "Арналар" - "Кез келген" - "Маңыздылық деңгейін таңдау" - "Бір мезгілде тым көп бағдарламаны жазып алу қажет болса, тек маңыздылығы жоғарылары ғана жазылады." - "Сақтау" - "Бір жолғы жазбалар ең маңызды болып табылады" - "Бас тарту" - "Бас тарту" - "Жою" - "Тоқтату" - "Жазу кестесін көру" - "Тек осы бағдарлама" - "қазір - %1$s" - "Барлық сериялар..." - "Бәрібір жазып алу" - "Орнына мына бағдарламаны жазу" - "Бұл жазып алу кестесінен бас тарту" - "Қазір көру" - "Жазып алуға болады" - "Жазып алынады" - "Жазу кестесінде қайшылық бар" - "Жазылуда" - "Жазу сәтсіз аяқталды" - "Жазу кестелерін жасауға арналған оқу бағдарламалары" - "Бағдарлама ақпараты оқылуда" - - - "DVR көбірек бос орынды қажет етеді" - "Бағдарламаларды DVR арқылы жазып алуға болады. Алайда DVR жұмысы үшін құрылғыда бос орын жеткіліксіз. %1$s ГБ не одан үлкен сыртқы дискіні жалғап, оны құрылғы жады ретінде форматтау қадамдарын орындаңыз." - "Жад жоқ" - "DVR пайдаланатын жадтың бір бөлігі жоқ. DVR құрылғысын қайта іске қосу үшін бұрын пайдаланған сыртқы дискіні жалғаңыз. Сондай-ақ егер жад бұдан әрі қолжетімді болмаса, оны жоюыңызға болады." - "Жад жойылсын ба?" - "Барлық жазылған мазмұндар мен кестелер жойылады." - "Жазу тоқтатылсын ба?" - "Жазылған мазмұн сақталады." - - - "Жоспарланған, бірақ қайшылықтары бар" - "Жазу басталды, бірақ қайшылықтары бар" - "%1$s жазылады." - "%1$s жазылуда." - "%1$s бағдарламасының кейбір бөліктері жазылмайды." - "%1$s және %2$s бағдарламасының кейбір бөліктері жазылмайды." - "%1$s, %2$s бағдарламаларының және тағы бір жоспарланған бағдарламаның кейбір бөліктері жазылмайды." - - %1$s, %2$s бағдарламалары мен тағы %3$d бағдарламаның кейбір бөліктері жазылмайды. - %1$s, %2$s бағдарламалары мен жоспарланған тағы %3$d бағдарламаның кейбір бөліктері жазылмайды. - - "Нені жазып алғыңыз келеді?" - "Қаншалықты ұзақ жазасыз?" - "Әлдеқашан жоспарланған" - "Бұл бағдарламаны жазып алу басқа уақытқа (%1$s) әлдеқашан жоспарланған." - "Бұрын жазылған" - "Бұл бағдарлама бұрын жазылған және DVR кітапханасында бар." - - - - - - - - - "Жазылған бағдарлама табылмады." - "Қатысты жазбалар" - "(Бағдарлама сипаттамасы жоқ)" - - %1$d жазба - %1$d жазба - - " /  " - "%1$s бағдарламасы жазып алу кестесінен өшірілді" - "Тюнер қайшылықтарына байланысты толық жазылмайды." - "Тюнер қайшылықтарына байланысты жазылмайды." - "Кестеде ешқандай жазып алу белгіленбеген.\nМұны бағдарлама нұсқаулығынан жоспарлауға болады." - - %1$d жазу қайшылығы - %1$d жазу қайшылығы - - "Сериялар параметрлері" - "Серияларды жазуды бастау" - "Серияларды жазуды тоқтату" - "Серияларды жазу тоқтатылсын ба?" - "Жазылған серияларды DVR кітапханасынан алуға болады." - "Тоқтату" - "Ешқандай серия қолжетімді емес.\nОларды қолжетімді болған кезде ғана жазып алуға болады." - - (%1$d минут) - (%1$d минут) - - "Бүгін" - "Ертең" - "Кеше" - "Бүгін %1$s" - "Ертең %1$s" - "Ұпай саны" - diff --git a/res/values-km-rKH-v23/strings.xml b/res/values-km-rKH-v23/strings.xml new file mode 100644 index 00000000..67c48f05 --- /dev/null +++ b/res/values-km-rKH-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "ប៉ុស្តិ៍" + diff --git a/res/values-km-rKH/arrays.xml b/res/values-km-rKH/arrays.xml new file mode 100644 index 00000000..e46312b4 --- /dev/null +++ b/res/values-km-rKH/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "ធម្មតា" + "ពេញ" + "ពង្រីក" + + + "គ្រប់​ឆានែល" + "គ្រួសារ/កុមារ" + "កីឡា" + "ទិញ​ទំនិញ" + "ភាពយន្ត" + "រឿង​កំប្លែង" + "ធ្វើដំណើរ" + "ល្ខោន" + "ការអប់រំ" + "សត្វ​ពាហនៈ/សត្វព្រៃ" + "ព័ត៌មាន" + "លេង​ហ្គេម​" + "សិល្បៈ" + "ការ​កម្សាន្ត" + "របៀបរស់នៅ" + "តន្ត្រី" + "បង្ហាញជូនលើដំបូង" + "បច្ចេក/វិទ្យាសាស្ត្រ" + + + "ប៉ុស្តិ៍ផ្សាយផ្ទាល់" + "មធ្យោបាយសាមញ្ញក្នុងការស្វែងរកមាតិកា" + "ដោនឡូតកម្មវិធីដើម្បីទទួលបានប៉ុស្តិ៍បន្ថែមទៀត" + "កំណត់បណ្តុំប៉ុស្តិ៍របស់អ្នកតាមបំណង" + + + "មើលមាតិកាបានមកពីកម្មវិធីរបស់អ្នក ដូចជាការមើលប៉ុស្តិ៍នៅលើទូរទស្សន៍។" + "រុករកមាតិកាបានមកពីកម្មវិធីរបស់អ្នកដែលមានការណែនាំដែលធ្លាប់ស្គាល់ និងចំណុចប្រទាក់ដែលស្រួលសម្រាប់អ្នកប្រើ \nដូចជាប៉ុស្តិ៍ទូរទស្សន៍ជាដើម។" + "បន្ថែមប៉ុស្តិ៍ច្រើនទៀតដោយដំឡើងកម្មវិធីដែលផ្តល់ប៉ុស្តិ៍ផ្សាយផ្ទាល់។ \nស្វែងរកកម្មវិធីដែលត្រូវគ្នានៅក្នុងហាង Google Play ដោយប្រើតំណនៅក្នងម៉ឺនុយទូរទស្សន៍។" + "កំណត់ប្រភពប៉ុស្តិ៍ដែលអ្នកទើបដំឡើងថ្មីៗដើម្បីកំណត់បញ្ជីប៉ុស្តិ៍របស់អ្នកជាលក្ខណៈផ្ទាល់ខ្លួន។ \nដើម្បីចាប់ផ្តើម សូមជ្រើសប្រភពប៉ុស្តិ៍ដែលមានក្នុងមឺុនុយការកំណត់។" + + diff --git a/res/values-km-rKH/rating_system_strings.xml b/res/values-km-rKH/rating_system_strings.xml new file mode 100644 index 00000000..8f5df6b3 --- /dev/null +++ b/res/values-km-rKH/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "កម្មវិធីអាចមានអ្វីមួយដែលមិនសមរម្យសម្រាប់អ្នកទស្សនាដែលមានអាយុក្រោម 15 ឆ្នាំ ដូច្នេះការឃ្លាំមើលពីមាតាបិតាគួរត្រូវបានប្រើសម្រាប់ពួកគេ។" + "កម្មវិធីអាចមានអ្វីមួយដែលមិនសមរម្យសម្រាប់អ្នកទស្សនាដែលមានអាយុក្រោម 19 ឆ្នាំ ដូច្នេះកម្មវិធីទាំងនោះមិនស័ក្កិសមនឹងយុវវ័យដែលមានអាយុក្រោម 19 ឆ្នាំទេ។" + "ការពិភាក្សាផ្តល់យោបល់" + "ភាសាអសុរោះ" + "មាតិកាផ្លូវភេទ" + "អំពើហិង្សា" + "អំពើហិង្សាបែបស្រមើស្រមៃ" + "កម្មវិធីនេះត្រូវបានបង្កើតឡើងឲ្យសមស្របសម្រាប់កុមារទាំងអស់។" + "កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់កុមារដែលមានអាយុ 7 ឡើងទៅ។" + "មាតាបិតាភាគច្រើននឹងយល់ថាកម្មវិធីនេះសមស្របសម្រាប់មនុស្សគ្រប់វ័យ។" + "កម្មវិធីនេះភ្ជាប់មកជាមួយខ្លឹមសារដែលមាតាបិតាគិតថាមិនសមស្របសម្រាប់កុមារ។ មាតាបិតាជាច្រើនអាចនឹងចង់ទស្សនាកម្មវិធីនេះជាមួយកូនៗរបស់ពួកគេ។" + "កម្មវិធីនេះភ្ជាប់មកជាមួយខ្លឹមស្លារមួយចំនួនដែលមាតាបិតាជាច្រើននឹងយល់ថាមិនសមស្របសម្រាប់កុមារដែលមានអាយុក្រោម 14 ឆ្នាំ។" + "កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់ទស្សនាដោយមនុស្សពេញវ័យ ហេតុដូច្នេះហើយវាអាចនឹងមិនសមស្របសម្រាប់កុមារដែលមានអាយុក្រោម 17 ឆ្នាំទេ។" + "ការវាយតម្លៃភាពយន្ត" + "សម្រាប់ទស្សនិកជនទូទៅ។ មាតាបិតានឹងយល់ថាសមស្របសម្រាប់ទស្សនាដោយកូនៗរបស់ពួកគេ។" + "បានស្នើឲ្យមានការគ្រប់គ្រងពីមាតាបិតា។ វាអាចបង្ហាញខ្លឹមសារមួយចំនួនដែលមាតាបិតាមិនចង់ឲ្យកូនៗរបស់ពួកគេទស្សនា។" + "បានព្រមានឲ្យមាតាបិតាមានការប្រុងប្រយ័ត្នខ្ពស់។ ខ្លឹមសារមួយចំនួនអាចមិនសមរម្យសម្រាប់កុមារមិនទាន់ឈានចូលដល់វ័យជំទង់។" + "បានដាក់កម្រិត៖ ភ្ជាប់មកជាមួយខ្លឹមសារមួយចំនួនសម្រាប់មនុស្សពេញវ័យ។ មាតាបិតាត្រូវបានជម្រុញឲ្យស្វែងយល់បន្ថែមអំពីភាពយន្តនេះ មុនពេលនាំកូនៗរបស់ពួកគេទៅទស្សនាជាមួយ។" + "មិនអនុញ្ញាតចំពោះបុគ្គលដែលមានអាយុ 17 ចុះក្រោម។ សម្រាប់មនុស្សពេញវ័យតែប៉ុណ្ណោះ។" + diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml new file mode 100644 index 00000000..ca2c337c --- /dev/null +++ b/res/values-km-rKH/strings.xml @@ -0,0 +1,357 @@ + + + + + "ម៉ូណូ" + "ស្តេរ៉េអូ" + "បញ្ជាការចាក់" + "ឆានែល" + "ជម្រើសទូរទស្សន៍" + "ការបញ្ជាលើការចាក់មិនអាចប្រើបានទេសម្រាប់ឆានែលនេះ។" + "ចាក់ ឬផ្អាក" + "ទៅមុខរហ័ស" + "ខាថយក្រោយ" + "បន្ទាប់" + "មុន" + "ការ​ណែនាំ​កម្មវិធី" + "មានប៉ុស្តិ៍ថ្មី" + "បើក %1$s" + "ចំណងជើង​បិទ" + "របៀប​បង្ហាញ" + "PIP" + "ពហុ​អូឌីយ៉ូ" + "ទទួលយកប៉ុស្តិ៍ជាច្រើនទៀត" + "ការកំណត់" + "ទូរទស្សន៍ (អង់តែន/ខ្សែកាប)" + "មិន​មាន​ព័ត៌មាន​កម្មវិធី" + "មិនមានព័ត៌មានទេ" + "បណ្តាញដែលបានរារាំង" + "ភាសាមិនស្គាល់" + "អក្សរ​រត់ %1$d" + "បានបិទចំណងជើង" + "បិទ" + "ប្ដូររាងតាមត្រូវការ" + "កំណត់ចំណូលចិត្តប្រព័ន្ធទូលាយសម្រាប់ចំណងជើងបិទ" + "របៀប​បង្ហាញ" + "ពហុ​អូឌីយ៉ូ" + "ម៉ូណូ" + "ស្តេរ៉េអូ" + "5.1 ជុំវិញ" + "7.1 ជុំវិញ" + "ឆានែល %d" + "ប្ដូរបញ្ជីឆានែលតាម​តម្រូវការ" + "ជ្រើស​ក្រុម" + "មិន​ជ្រើស​ក្រុម" + "ដាក់​ជា​ក្រុម​តាម" + "ប្រភព​ឆា​នែ​ល" + "HD/SD" + "HD" + "SD" + "ដាក់​ជា​ក្រុម​តាម" + "កម្មវិធីនេះត្រូវបានរារាំង" + "កម្មវិធីនេះមិនបានវាយតម្លៃទេ" + "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s" + "ការ​បញ្ចូល​មិន​គាំទ្រ​ការ​វិភាគ​រក​ស្វ័យ​ប្រវត្តិ" + "មិន​អាច​ចាប់ផ្ដើម​ការ​វិភាគ​ស្វ័យប្រវត្តិ​សម្រាប់ \'%s\'" + "មិន​អាច​ចាប់ផ្ដើម​ចំណូល​ចិត្ត​ប្រព័ន្ធ​ទូលាយ​សម្រាប់​ចំណងជើង​បិទ​ជិត​។" + + បានបន្ថែមប៉ុស្តិ៍ %1$d + បានបន្ថែមប៉ុស្តិ៍ %1$d + + "គ្មាន​ឆា​នែ​ល​បាន​បន្ថែម" + "ការ​ត្រួតពិនិត្យមេ" + "បើក" + "បិទ" + "បាន​ទប់ស្កាត់ឆានែល" + "ទប់ស្កាត់​​ទាំងអស់" + "មិន​ទប់ស្កាត់ទាំងអស់" + "ឆានែល​​ដែល​បាន​លាក់" + "ការដាក់កម្រិតកម្មវិធី" + "ប្ដូរ​កូដ PIN" + "ប្រព័ន្ធ​វាយ​តម្លៃ" + "វាយតម្លៃ" + "មើលប្រព័ន្ធវាយតម្លៃទាំងអស់" + "ប្រទេសផ្សេងទៀត" + "គ្មាន" + "គ្មាន" + "មិនបានវាយតម្លៃ" + "ទប់ស្កាត់កម្មវិធីដែលមិនបានវាយតម្លៃ" + "គ្មាន" + "ការ​ដាក់កម្រិត​ខ្ពស់" + "ការដាក់កម្រិតមធ្យម" + "ការ​ដាក់​កម្រិត​ទាប" + "តាម​តម្រូវ​ការ" + "មាតិកា​សមស្រប​សម្រាប់​កុមារ" + "មាតិកា​សមស្រប​សម្រាប់​កុមារ​ធំៗ" + "មាតិកា​សមស្រប​សម្រាប់​យុវវ័យ" + "ការ​ដាក់កម្រិត​ដោយ​ដៃ" + + + "%1$s និងការវាតម្លៃរង" + "ការ​វាយ​តម្លៃ​រង" + "បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​​ឆានែល​នេះ" + "បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​កម្មវិធី​នេះ" + "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s។ បញ្ចូលកូដ PIN របស់អ្នកដើម្បីមើលកម្មវិធីនេះ" + "កម្មវិធីនេះមិនបានវាយតម្លៃទេ។ បញ្ចូលកូដ PIN របស់អ្នក ដើម្បីមើលកម្មវិធីនេះ" + "បញ្ចូល​កូដ PIN របស់​អ្នក" + "ដើម្បី​កំណត់​ការ​ពិនិត្យ​របស់​ឪពុកម្ដាយ អ្នក​ត្រូវ​បង្កើត​កូដ PIN" + "បញ្ចូល​កូដ PIN ថ្មី" + "បញ្ជាក់​កូដ PIN របស់​អ្នក" + "បញ្ចូល​កូដ PIN បច្ចុប្បន្ន​របស់​អ្នក" + + អ្នកបានបញ្ចូលកូដ PIN ខុស 5 ដង។\nសូមព្យាយាមម្តងទៀតក្នុងរយៈពេល %1$d វិនាទី។ + អ្នកបានបញ្ចូលកូដ PIN ខុស 5 ដង។\nសូមព្យាយាមម្តងទៀតក្នុងរយៈពេល %1$d វិនាទី។ + + "កូដ PIN មិន​ត្រឹមត្រូវ។ ព្យាយាម​ម្ដងទៀត។" + "ព្យាយាម​ម្ដង​ទៀត កូដ​ PIN មិន​ដូច​គ្នា" + "បញ្ចូល​លេខ​កូដ​តំបន់​របស់អ្នក។" + "កម្មវិធី​ប៉ុស្តិ៍​ផ្សាយ​ផ្ទាល់​នឹង​ប្រើ​លេខ​កូដ​តំបន់ ដើម្បី​ផ្តល់ជូន​នូវ​ការ​ណែនាំ​អំពីកម្មវិធី​ទាំងស្រុង​សម្រាប់​ប៉ុស្តិ៍​ទូរទស្សន៍។" + "បញ្ចូល​លេខកូដ​តំបន់​របស់​អ្នក" + "លេខកូដ​តំបន់​មិនត្រឹមត្រូវ​ទេ" + "ការកំណត់" + "ប្ដូរបញ្ជីប៉ុស្តិ៍តាមបំណង" + "ជ្រើសប៉ុស្តិ៍សម្រាប់ការណែនាំកម្មវិធីរបស់អ្នក" + "ប្រភពប៉ុស្តិ៍" + "មានប៉ុស្តិ៍ថ្មី" + "ការគ្រប់គ្រងដោយមាតាបិតា" + "ការថតទុកមើលពេលក្រោយ" + "ថតខណៈពេលកំពុងទស្សនា ដើម្បីឲ្យអ្នកអាចផ្អាក ឬខាកម្មវិធីផ្សាយ​​ផ្ទាល់ថយក្រោយបាន។\nព្រមាន៖ វាអាចកាត់បន្ថយអាយុកាលប្រើប្រាស់ទំហំផ្ទុកខាងក្នុង ដោយសារការប្រើប្រាស់ទំហំផ្ទុកយ៉ាងច្រើន។" + "អាជ្ញាប័ណ្ណប្រភពកូដបើកចំហ" + "ផ្ញើមតិស្ថាបនា" + "កំណែ" + "ដើម្បី​មើល​ឆានែល​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក" + "ដើម្បី​មើល​កម្មវិធី​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក" + "កម្មវិធីនេះមិនបានវាយតម្លៃទេ។\nដើម្បីមើលកម្មវិធីនេះ សូមចុច ស្ដាំ រួចបញ្ចូលកូដ PIN របស់អ្នក" + "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s។\n ដើម្បីមើលកម្មវិធីនេះ សូមចុច ស្តាំ រួចបញ្ចូលកូដ PIN របស់អ្នក។" + "ដើម្បីមើលប៉ុស្តិ៍នេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" + "ដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" + "កម្មវិធីនេះមិនបានវាយតម្លៃទេ។\nដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម។" + "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s\nដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" + "កម្មវិធី​នេះ​ត្រូវ​បាន​ទប់ស្កាត់" + "កម្មវិធីនេះមិនបានវាយតម្លៃទេ" + "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s" + "តែ​អូឌីយ៉ូ​ប៉ុណ្ណោះ" + "សញ្ញា​ខ្សោយ" + "គ្មានការតភ្ជាប់អ៊ីនធឺណិតទេ" + + ប៉ុស្តិ៍នេះមិនអាចចាក់បានទេរហូតដល់ម៉ោង %1$s ដោយសារតែប៉ុស្តិ៍ផ្សេងទៀតកំពុងត្រូវបានថត។ \n\nចុច ស្តាំ ដើម្បីកែសម្រួលកាលវិភាគថត។ + ប៉ុស្តិ៍នេះមិនអាចចាក់បានទេរហូតដល់ម៉ោង %1$s ដោយសារតែប៉ុស្តិ៍ផ្សេងទៀតកំពុងត្រូវបានថត។ \n\nចុច ស្តាំ ដើម្បីកែសម្រួលកាលវិភាគថត។ + + "គ្មាន​ចំណងជើង" + "បាន​ទប់​ស្កាត់​ឆានែល" + "ថ្មី" + "ប្រភព" + + ប៉ុស្តិ៍ %1$d + ប៉ុស្តិ៍ %1$d + + "មិនមានប៉ុស្តិ៍ទេ" + "ថ្មី" + "មិន​បាន​កំណត់" + "ទទួលយកប្រភពច្រើនទៀត" + "រុករកកម្មវិធីដែលមានផ្តល់ជូនប៉ុស្តិ៍ផ្សាយផ្ទាល់" + "មានផ្តល់ជូនប្រភពប៉ុស្តិ៍ថ្មី" + "ប្រភពប៉ុស្តិ៍ថ្មីមានប៉ុស្តិ៍ដែលត្រូវផ្តល់ជូន។\nដំឡើងប៉ុស្តិ៍ទាំងនោះឥឡូវនេះ ឬដំឡើងពេលក្រោយនៅក្នុងការកំណត់ប្រភពប៉ុស្តិ៍។" + "ដំឡើងឥឡូវនេះ" + "យល់​ហើយ" + + + "ចុច ជ្រើសរើស"" ដើម្បីចូលប្រើម៉ឺនុយទូរទស្សន៍។" + "រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ" + "រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ" + "ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍មិនសមស្រប។ សូមចាប់ផ្តើមដំណើរការកម្មវិធី ប៉ុស្តិ៍ផ្សាយផ្ទាល់ សម្រាប់ធាតុបញ្ជូលទូរទស្សន៍ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍។" + "កាកែសម្រួលប៉ុស្តិ៍បានបរាជ័យ" + "រកមិនឃើញកម្មវិធីដើម្បីគ្រប់គ្រងសកម្មភាពនេះទេ។" + "រាល់​​ឆានែល​ប្រភព​គឺ​​លាក់​។ \n ជ្រើស​ឆា​នែ​ល​យ៉ាង​ហោច​ណាស់​មួយ​​ដើម្បី​​មើល​។" + "មិនបានរំពឹងថាមិនមានវីដេអូនេះទេ" + "ប៊ូតុង​ថយក្រោយ​សម្រាប់​ឧបករណ៍​ដែល​បាន​ភ្ជាប់។ ចុច​ប៊ូតុង​ដើម ដើម្បី​ចាកចេញ។" + "ប៉ុស្តិ៍ផ្សាយផ្ទាល់ត្រូវការសិទ្ធិអនុញ្ញាតដើម្បីអានបញ្ជីទូរទស្សន៍។" + "កំណត់ប្រភពរបស់អ្នក" + "ប៉ុស្តិ៍ផ្សាយផ្ទាល់ផ្សាភ្ជាប់បទពិសោធន៍ប្រើប្រាស់ប៉ុស្តិ៍ទូរទស្សន៍លក្ខណៈបុរាណជាមួយប៉ុស្តិ៍ស្រ្ទីមដែលផ្តល់ដោយកម្មវិធី។ \n\nចាប់ផ្តើមដោយកំណត់ប្រភពប៉ុស្តិ៍ដែលបានដំឡើងរួចហើយ។ ឬរុករកក្នុងហាង Google Play ដើម្បីរកកម្មវិធីច្រើនទៀតដែលផ្តល់ជូនប៉ុស្តិ៍ផ្សាយផ្ទាល់។" + "ការថត និងកាលវិភាក" + "10 នាទី" + "30 នាទី" + "1 ម៉ោង" + "3 ម៉ោង" + "ថ្មីៗ" + "បាន​កំណត់​ពេល" + "ស៊េរី" + "ផ្សេងៗ" + "មិនអាចថតប៉ុស្តិ៍នេះបានទេ" + "មិនអាចថតកម្មវិធីនេះបានទេ" + "%1$s ត្រូវបានកំណត់ពេលដើម្បីថត" + "ការថត %1$s ពីឥឡូវនេះដល់ %2$s" + "កាលវិភាគពេញហើយ" + + %1$d ថ្ងៃបន្ទាប់ + %1$d ថ្ងៃបន្ទាប់ + + + %1$d នាទី + %1$d នាទី + + + ការថតថ្មី %1$d + ការថតថ្មី %1$d + + + ការថត %1$d + ការថត %1$d + + + ការថតដែលបានកំណត់ពេល %1$d + ការថតដែលបានកំណត់ពេល %1$d + + "បោះបង់ការថត" + "បញ្ឈប់ការថត" + "ទស្សនា" + "ចាក់ពីដំបូង" + "ចាក់​បន្ត" + "លុប" + "លុបការថត" + "បន្ត" + "វគ្គ %1$s" + "មើលកាលវិភាគ" + "អាន​បន្ថែម" + "លុបការថត" + "ជ្រើសភាគដែលអ្នកចង់លុប។ ពួកវាមិនអាចស្តារឡើវិញបានទេបន្ទាប់ពីលុបរួចហើយ។" + "មិនមានការថតដែលត្រូវលុបទេ" + "ជ្រើសភាគដែលបានមើល" + "ជ្រើសភាគទាំងអស់" + "ដោះការជ្រើសភាគទាំងអស់" + "បានមើល %1$d នៅនាទីទី %2$d" + "បានមើល %1$d នៅវិនាទីទី %2$d" + "មិនដែលមើល" + + ភាគចំនួន %1$d ក្នុងចំណោម %2$d ត្រូវបានលុប + ភាគចំនួន %1$d ក្នុងចំណោម %2$d ត្រូវបានលុប + + "អាទិភាព" + "ខ្ពស់បំផុត" + "ទាបបំផុត" + "លេខ %1$d" + "ប៉ុស្តិ៍" + "មួយណាក៏បាន" + "ជ្រើសអាទិភាព" + "នៅពេលដែលមានកម្មវិធីដែលត្រូវថតច្រើនពេកក្នុងពេលតែមួយ នោះមានតែកម្មវិធីដែលមានអាទិភាពខ្ពស់ជាងប៉ុណ្ណោះដែលនឹងត្រូវថត។" + "រក្សាទុក" + "ការថតមួយដងមានអាទិភាពខ្ពស់បំផុត" + "បញ្ឈប់" + "មើលកាលវិភាគថត" + "កម្មវិធីមួយនេះ" + "ឥឡូវនេះ - %1$s" + "វគ្គទាំងមូល…" + "កាលវិភាគទោះយ៉ាងណាក៏ដោយ" + "ថតកម្មវិធីនេះជំនួសវិញ" + "បោះបង់ការថតនេះ" + "មើលឥឡូវនេះ" + "លុបការថត…" + "អាចថតបាន" + "បានកំណត់ពេលការថត" + "ការថតជាន់ម៉ោងគ្នា" + "ការថត" + "បានបរាជ័យក្នុងការថត" + "កំពុងអានកម្មវិធី" + "មើលការថតថ្មីៗ" + "ការថត %1$s មិនទាន់បញ្ចប់ទេ។" + "ការថត %1$s និង %2$s មិនទាន់បញ្ចប់ទេ។" + "ការថត %1$s, %2$s និង %3$s មិនទាន់បញ្ចប់ទេ។" + "ការថត %1$s មិនបានបញ្ចប់ទេ ដោយសារមិនមានទំហំផ្ទុកគ្រប់គ្រាន់។" + "ការថត %1$s និង %2$s មិនបានបញ្ចប់ទេ ដោយសារមិនមានទំហំផ្ទុកគ្រប់គ្រាន់។" + "ការថត %1$s, %2$s និង %3$s មិនបានបញ្ចប់ទេ ដោយសារមិនមានទំហំផ្ទុកគ្រប់គ្រាន់។" + "DVR ត្រូវការទំហំផ្ទុកបន្ថែមទៀត" + "អ្នកនឹងអាចថតកម្មវិធីដោយប្រើ DVR។ ទោះបីជាយ៉ាងណាក៏ដោយ ឥឡូវនេះមិនមានទំហំផ្ទុកគ្រប់គ្រាន់នៅលើឧបករណ៍របស់អ្នកដើម្បីអនុញ្ញាតឲ្យ DVR ដំណើរការនោះទេ។ សូមភ្ជាប់ទៅឧបករណ៍ផ្ទុកខាងក្រៅដែលមានទំហំផ្ទុក %1$dGB ឬធំជាងនេះ បន្ទាប់មកអនុវត្តតាមជំហានទាំងនេះដើម្បីសម្អាតវាក្នុងនាមជាឧបករណ៍ផ្ទុក។" + "មិនមានទំហំផ្ទុកគ្រប់គ្រាន់ទេ" + "មិនអាចថតកម្មវិធីនេះបានទេ ដោយសារតែមិនមានទំហំផ្ទុកគ្រប់គ្រាន់។ សូមសាកល្បងលុបការថតមួយចំនួនដែលមានស្រាប់។" + "ឧបករណ៍ផ្ទុកដែលបានបាត់" + "បញ្ឈប់ការថតឬ?" + "មាតិកាដែលបានថតនឹងត្រូវបានរក្សាទុក" + "ការថត %1$s នឹងត្រូវបានបញ្ឈប់ ដោយសារតែវាជាន់ម៉ោងគ្នាជាមួយកម្មវិធីនេះ។ មាតិកាដែលបានថតនេះនឹងត្រូវបានរក្សាទុក។" + "ការថតបានកំណត់ពេលហើយ ប៉ុន្តែមានម៉ោងជាន់គ្នា" + "ការថតបានចាប់ផ្តើមប៉ុន្តែវាជាន់គ្នា" + "%1$s នឹងត្រូវបានថត។" + "%1$s កំពុងត្រូវបានថត។" + "ផ្នែកមួយចំនួននៃ %1$s នឹងមិនត្រូវបានថតទេ។" + "ផ្នែកមួយចំនួននៃ %1$s និង %2$s នឹងមិនត្រូវបានថតទេ។" + "ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគមួយទៀតនឹងមិនត្រូវបានថតទេ។" + + ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគ %3$d ទៀតនឹងមិនត្រូវបានថតទេ។ + ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគ %3$d ទៀតនឹងមិនត្រូវបានថតទេ។ + + "តើអ្នកចង់ថតដែរទេ?" + "តើអ្នកចង់ថតរយៈពេលប៉ុន្មានដែរ?" + "បានកំណត់ពេលរួចហើយ" + "កម្មវិធីដូចគ្នានេះត្រូវបានកំណត់ពេលថតរួចហើយនៅម៉ោង %1$s" + "បានថតរួចហើយ" + "កម្មវិធីនេះត្រូវបានថតរួចហើយ។ វាអាចប្រើបាននៅក្នុងបណ្ណាល័យ DVR ។" + "បានកំណត់ពេលថតវគ្គ" + + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s + + + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ការថតចំនួន %3$d ក្នុងចំណោមនោះនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ វានឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + + + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន %3$d នៃវគ្គនេះ និងវគ្គផ្សេងទៀតនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន %3$d នៃវគ្គនេះ និងវគ្គផ្សេងទៀតនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + + + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន 1 នៃវគ្គផ្សេងនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន 1 នៃវគ្គផ្សេងនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + + + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន %3$d នៃវគ្គផ្សេងនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + ការថតចំនួន %1$d ត្រូវបានកំណត់ពេលសម្រាប់ %2$s ។ ភាគចំនួន %3$d នៃវគ្គផ្សេងនឹងមិនត្រូវបានថតទេ ដោយសារម៉ោងជាន់គ្នា។ + + "រកមិនឃើញកម្មវិធីដែលបានថតទេ" + "ការថតដែលពាក់ព័ន្ធ" + + ការថត %1$d + ការថត %1$d + + " / " + "%1$s ត្រូវបានលុបចេញពីកាលវិភាគថត" + "នឹងមិនត្រូវបានថតពេញលេញនោះទេដោយសារតែអង្គរាវប៉ុស្តិ៍ជាន់គ្នា។" + "នឹងមិនត្រូវបានថតនោះទេដោយសារតែអង្គរាវប៉ុស្តិ៍ជាន់គ្នា។" + "មិនទាន់មានការថតនៅក្នុងកាលវិភាគនៅឡើយទេ។\nអ្នកអាចកំណត់ពេលថតចេញពីការណែនាំកម្មវិធីនេះ។" + + ការជាន់ម៉ោងថត %1$d + ការជាន់ម៉ោងថត %1$d + + "ការកំណត់វគ្គ" + "ចាប់ផ្តើមថតវគ្គ" + "ឈប់ថតវគ្គ" + "បញ្ឈប់ការថតវគ្គឬ?" + "ភាគដែលបានថតនឹងនៅតែមាននៅក្នុងបណ្ណាល័យ DVR ។" + "បញ្ឈប់" + "ឥឡូវនេះមិនមានការផ្សាយភាគថ្មីទេ។" + "មិនមានផ្តល់ជូនភាគណាមួយទេ។\nពួកវានឹងត្រូវបានថតបន្ទាប់ពីមានផ្តល់ជូន។" + + (%1$d នាទី) + (%1$d នាទី) + + "ថ្ងៃនេះ" + "ថ្ងៃស្អែក" + "ម្សិលមិញ" + "%1$s ថ្ងៃនេះ" + "%1$s ថ្ងៃស្អែក" + "ពិន្ទុ" + "កម្មវិធីដែល​បានថត" + diff --git a/res/values-km-v23/strings.xml b/res/values-km-v23/strings.xml deleted file mode 100644 index 67c48f05..00000000 --- a/res/values-km-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "ប៉ុស្តិ៍" - diff --git a/res/values-km/arrays.xml b/res/values-km/arrays.xml deleted file mode 100644 index e46312b4..00000000 --- a/res/values-km/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "ធម្មតា" - "ពេញ" - "ពង្រីក" - - - "គ្រប់​ឆានែល" - "គ្រួសារ/កុមារ" - "កីឡា" - "ទិញ​ទំនិញ" - "ភាពយន្ត" - "រឿង​កំប្លែង" - "ធ្វើដំណើរ" - "ល្ខោន" - "ការអប់រំ" - "សត្វ​ពាហនៈ/សត្វព្រៃ" - "ព័ត៌មាន" - "លេង​ហ្គេម​" - "សិល្បៈ" - "ការ​កម្សាន្ត" - "របៀបរស់នៅ" - "តន្ត្រី" - "បង្ហាញជូនលើដំបូង" - "បច្ចេក/វិទ្យាសាស្ត្រ" - - - "ប៉ុស្តិ៍ផ្សាយផ្ទាល់" - "មធ្យោបាយសាមញ្ញក្នុងការស្វែងរកមាតិកា" - "ដោនឡូតកម្មវិធីដើម្បីទទួលបានប៉ុស្តិ៍បន្ថែមទៀត" - "កំណត់បណ្តុំប៉ុស្តិ៍របស់អ្នកតាមបំណង" - - - "មើលមាតិកាបានមកពីកម្មវិធីរបស់អ្នក ដូចជាការមើលប៉ុស្តិ៍នៅលើទូរទស្សន៍។" - "រុករកមាតិកាបានមកពីកម្មវិធីរបស់អ្នកដែលមានការណែនាំដែលធ្លាប់ស្គាល់ និងចំណុចប្រទាក់ដែលស្រួលសម្រាប់អ្នកប្រើ \nដូចជាប៉ុស្តិ៍ទូរទស្សន៍ជាដើម។" - "បន្ថែមប៉ុស្តិ៍ច្រើនទៀតដោយដំឡើងកម្មវិធីដែលផ្តល់ប៉ុស្តិ៍ផ្សាយផ្ទាល់។ \nស្វែងរកកម្មវិធីដែលត្រូវគ្នានៅក្នុងហាង Google Play ដោយប្រើតំណនៅក្នងម៉ឺនុយទូរទស្សន៍។" - "កំណត់ប្រភពប៉ុស្តិ៍ដែលអ្នកទើបដំឡើងថ្មីៗដើម្បីកំណត់បញ្ជីប៉ុស្តិ៍របស់អ្នកជាលក្ខណៈផ្ទាល់ខ្លួន។ \nដើម្បីចាប់ផ្តើម សូមជ្រើសប្រភពប៉ុស្តិ៍ដែលមានក្នុងមឺុនុយការកំណត់។" - - diff --git a/res/values-km/rating_system_strings.xml b/res/values-km/rating_system_strings.xml deleted file mode 100644 index 8f5df6b3..00000000 --- a/res/values-km/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "កម្មវិធីអាចមានអ្វីមួយដែលមិនសមរម្យសម្រាប់អ្នកទស្សនាដែលមានអាយុក្រោម 15 ឆ្នាំ ដូច្នេះការឃ្លាំមើលពីមាតាបិតាគួរត្រូវបានប្រើសម្រាប់ពួកគេ។" - "កម្មវិធីអាចមានអ្វីមួយដែលមិនសមរម្យសម្រាប់អ្នកទស្សនាដែលមានអាយុក្រោម 19 ឆ្នាំ ដូច្នេះកម្មវិធីទាំងនោះមិនស័ក្កិសមនឹងយុវវ័យដែលមានអាយុក្រោម 19 ឆ្នាំទេ។" - "ការពិភាក្សាផ្តល់យោបល់" - "ភាសាអសុរោះ" - "មាតិកាផ្លូវភេទ" - "អំពើហិង្សា" - "អំពើហិង្សាបែបស្រមើស្រមៃ" - "កម្មវិធីនេះត្រូវបានបង្កើតឡើងឲ្យសមស្របសម្រាប់កុមារទាំងអស់។" - "កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់កុមារដែលមានអាយុ 7 ឡើងទៅ។" - "មាតាបិតាភាគច្រើននឹងយល់ថាកម្មវិធីនេះសមស្របសម្រាប់មនុស្សគ្រប់វ័យ។" - "កម្មវិធីនេះភ្ជាប់មកជាមួយខ្លឹមសារដែលមាតាបិតាគិតថាមិនសមស្របសម្រាប់កុមារ។ មាតាបិតាជាច្រើនអាចនឹងចង់ទស្សនាកម្មវិធីនេះជាមួយកូនៗរបស់ពួកគេ។" - "កម្មវិធីនេះភ្ជាប់មកជាមួយខ្លឹមស្លារមួយចំនួនដែលមាតាបិតាជាច្រើននឹងយល់ថាមិនសមស្របសម្រាប់កុមារដែលមានអាយុក្រោម 14 ឆ្នាំ។" - "កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់ទស្សនាដោយមនុស្សពេញវ័យ ហេតុដូច្នេះហើយវាអាចនឹងមិនសមស្របសម្រាប់កុមារដែលមានអាយុក្រោម 17 ឆ្នាំទេ។" - "ការវាយតម្លៃភាពយន្ត" - "សម្រាប់ទស្សនិកជនទូទៅ។ មាតាបិតានឹងយល់ថាសមស្របសម្រាប់ទស្សនាដោយកូនៗរបស់ពួកគេ។" - "បានស្នើឲ្យមានការគ្រប់គ្រងពីមាតាបិតា។ វាអាចបង្ហាញខ្លឹមសារមួយចំនួនដែលមាតាបិតាមិនចង់ឲ្យកូនៗរបស់ពួកគេទស្សនា។" - "បានព្រមានឲ្យមាតាបិតាមានការប្រុងប្រយ័ត្នខ្ពស់។ ខ្លឹមសារមួយចំនួនអាចមិនសមរម្យសម្រាប់កុមារមិនទាន់ឈានចូលដល់វ័យជំទង់។" - "បានដាក់កម្រិត៖ ភ្ជាប់មកជាមួយខ្លឹមសារមួយចំនួនសម្រាប់មនុស្សពេញវ័យ។ មាតាបិតាត្រូវបានជម្រុញឲ្យស្វែងយល់បន្ថែមអំពីភាពយន្តនេះ មុនពេលនាំកូនៗរបស់ពួកគេទៅទស្សនាជាមួយ។" - "មិនអនុញ្ញាតចំពោះបុគ្គលដែលមានអាយុ 17 ចុះក្រោម។ សម្រាប់មនុស្សពេញវ័យតែប៉ុណ្ណោះ។" - diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml deleted file mode 100644 index 98fa13f5..00000000 --- a/res/values-km/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "ម៉ូណូ" - "ស្តេរ៉េអូ" - "បញ្ជាការចាក់" - "ឆានែល​ថ្មីៗ" - "ជម្រើសទូរទស្សន៍" - "ជម្រើស PIP" - "ការបញ្ជាលើការចាក់មិនអាចប្រើបានទេសម្រាប់ឆានែលនេះ។" - "ចាក់ ឬផ្អាក" - "ទៅមុខរហ័ស" - "ខាថយក្រោយ" - "បន្ទាប់" - "មុន" - "ការ​ណែនាំ​កម្មវិធី" - "មានប៉ុស្តិ៍ថ្មី" - "បើក %1$s" - "ចំណងជើង​បិទ" - "របៀប​បង្ហាញ" - "PIP" - "បើក" - "បិទ" - "ពហុ​អូឌីយ៉ូ" - "ទទួលយកប៉ុស្តិ៍ជាច្រើនទៀត" - "ការកំណត់" - "ប្រភព" - "ប្ដូរ" - "បើក" - "បិទ" - "សំឡេង" - "ចម្បង" - "ផ្ទាំងវិនដូ PIP" - "ប្លង់" - "បាតខាងស្តាំ" - "ចុងខាងស្តាំ" - "ចុងខាងឆ្វេង" - "បាតខាងឆ្វេង" - "នៅក្បែរគ្នា" - "ទំហំ" - "ធំ" - "តូច" - "ប្រភព​ចូល" - "ទូរទស្សន៍ (អង់តែន/ខ្សែកាប)" - "មិន​មាន​ព័ត៌មាន​កម្មវិធី" - "មិនមានព័ត៌មានទេ" - "បណ្តាញដែលបានរារាំង" - "ភាសាមិនស្គាល់" - "បានបិទចំណងជើង" - "បិទ" - "ប្ដូររាងតាមត្រូវការ" - "កំណត់ចំណូលចិត្តប្រព័ន្ធទូលាយសម្រាប់ចំណងជើងបិទ" - "របៀប​បង្ហាញ" - "ពហុ​អូឌីយ៉ូ" - "ម៉ូណូ" - "ស្តេរ៉េអូ" - "5.1 ជុំវិញ" - "7.1 ជុំវិញ" - "ឆានែល %d" - "ប្ដូរបញ្ជីឆានែលតាម​តម្រូវការ" - "ជ្រើស​ក្រុម" - "មិន​ជ្រើស​ក្រុម" - "ដាក់​ជា​ក្រុម​តាម" - "ប្រភព​ឆា​នែ​ល" - "HD/SD" - "HD" - "SD" - "ដាក់​ជា​ក្រុម​តាម" - "កម្មវិធីនេះត្រូវបានរារាំង" - "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s" - "ការ​បញ្ចូល​មិន​គាំទ្រ​ការ​វិភាគ​រក​ស្វ័យ​ប្រវត្តិ" - "មិន​អាច​ចាប់ផ្ដើម​ការ​វិភាគ​ស្វ័យប្រវត្តិ​សម្រាប់ \'%s\'" - "មិន​អាច​ចាប់ផ្ដើម​ចំណូល​ចិត្ត​ប្រព័ន្ធ​ទូលាយ​សម្រាប់​ចំណងជើង​បិទ​ជិត​។" - - បានបន្ថែមប៉ុស្តិ៍ %1$d - បានបន្ថែមប៉ុស្តិ៍ %1$d - - "គ្មាន​ឆា​នែ​ល​បាន​បន្ថែម" - "Tuner" - "ការ​ត្រួតពិនិត្យមេ" - "បើក" - "បិទ" - "បាន​ទប់ស្កាត់ឆានែល" - "ទប់ស្កាត់​​ទាំងអស់" - "មិន​ទប់ស្កាត់ទាំងអស់" - "ឆានែល​​ដែល​បាន​លាក់" - "ការដាក់កម្រិតកម្មវិធី" - "ប្ដូរ​កូដ PIN" - "ប្រព័ន្ធ​វាយ​តម្លៃ" - "វាយតម្លៃ" - "មើលប្រព័ន្ធវាយតម្លៃទាំងអស់" - "ប្រទេសផ្សេងទៀត" - "គ្មាន" - "គ្មាន" - "គ្មាន" - "ការ​ដាក់កម្រិត​ខ្ពស់" - "ការដាក់កម្រិតមធ្យម" - "ការ​ដាក់​កម្រិត​ទាប" - "តាម​តម្រូវ​ការ" - "មាតិកា​សមស្រប​សម្រាប់​កុមារ" - "មាតិកា​សមស្រប​សម្រាប់​កុមារ​ធំៗ" - "មាតិកា​សមស្រប​សម្រាប់​យុវវ័យ" - "ការ​ដាក់កម្រិត​ដោយ​ដៃ" - - - "%1$s និងការវាតម្លៃរង" - "ការ​វាយ​តម្លៃ​រង" - "បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​​ឆានែល​នេះ" - "បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​កម្មវិធី​នេះ" - "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s។ បញ្ចូលកូដ PIN របស់អ្នកដើម្បីមើលកម្មវិធីនេះ" - "បញ្ចូល​កូដ PIN របស់​អ្នក" - "ដើម្បី​កំណត់​ការ​ពិនិត្យ​របស់​ឪពុកម្ដាយ អ្នក​ត្រូវ​បង្កើត​កូដ PIN" - "បញ្ចូល​កូដ PIN ថ្មី" - "បញ្ជាក់​កូដ PIN របស់​អ្នក" - "បញ្ចូល​កូដ PIN បច្ចុប្បន្ន​របស់​អ្នក" - - អ្នកបានបញ្ចូលកូដ PIN ខុស 5 ដង។\nសូមព្យាយាមម្តងទៀតក្នុងរយៈពេល %1$d វិនាទី។ - អ្នកបានបញ្ចូលកូដ PIN ខុស 5 ដង។\nសូមព្យាយាមម្តងទៀតក្នុងរយៈពេល %1$d វិនាទី។ - - "កូដ PIN មិន​ត្រឹមត្រូវ។ ព្យាយាម​ម្ដងទៀត។" - "ព្យាយាម​ម្ដង​ទៀត កូដ​ PIN មិន​ដូច​គ្នា" - "ការកំណត់" - "ប្ដូរបញ្ជីប៉ុស្តិ៍តាមបំណង" - "ជ្រើសប៉ុស្តិ៍សម្រាប់ការណែនាំកម្មវិធីរបស់អ្នក" - "ប្រភពប៉ុស្តិ៍" - "មានប៉ុស្តិ៍ថ្មី" - "ការគ្រប់គ្រងដោយមាតាបិតា" - "អាជ្ញាប័ណ្ណប្រភពកូដបើកចំហ" - "អាជ្ញាប័ណ្ណប្រភពកូដចំហ" - "កំណែ" - "ដើម្បី​មើល​ឆានែល​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក" - "ដើម្បី​មើល​កម្មវិធី​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក" - "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s។\n ដើម្បីមើលកម្មវិធីនេះ សូមចុច ស្តាំ រួចបញ្ចូលកូដ PIN របស់អ្នក។" - "ដើម្បីមើលប៉ុស្តិ៍នេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" - "ដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" - "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s\nដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម" - "កម្មវិធី​នេះ​ត្រូវ​បាន​ទប់ស្កាត់" - "កម្មវិធីនេះត្រូវបានវាយតម្លៃ %1$s" - "តែ​អូឌីយ៉ូ​ប៉ុណ្ណោះ" - "សញ្ញា​ខ្សោយ" - "គ្មានការតភ្ជាប់អ៊ីនធឺណិតទេ" - - ប៉ុស្តិ៍នេះមិនអាចចាក់បានទេរហូតដល់ម៉ោង %1$s ដោយសារតែប៉ុស្តិ៍ផ្សេងទៀតកំពុងត្រូវបានថត។ \n\nចុច ស្តាំ ដើម្បីកែសម្រួលកាលវិភាគថត។ - ប៉ុស្តិ៍នេះមិនអាចចាក់បានទេរហូតដល់ម៉ោង %1$s ដោយសារតែប៉ុស្តិ៍ផ្សេងទៀតកំពុងត្រូវបានថត។ \n\nចុច ស្តាំ ដើម្បីកែសម្រួលកាលវិភាគថត។ - - "គ្មាន​ចំណងជើង" - "បាន​ទប់​ស្កាត់​ឆានែល" - "ថ្មី" - "ប្រភព" - - ប៉ុស្តិ៍ %1$d - ប៉ុស្តិ៍ %1$d - - "មិនមានប៉ុស្តិ៍ទេ" - "ថ្មី" - "មិន​បាន​កំណត់" - "ទទួលយកប្រភពច្រើនទៀត" - "រុករកកម្មវិធីដែលមានផ្តល់ជូនប៉ុស្តិ៍ផ្សាយផ្ទាល់" - "មានផ្តល់ជូនប្រភពប៉ុស្តិ៍ថ្មី" - "ប្រភពប៉ុស្តិ៍ថ្មីមានប៉ុស្តិ៍ដែលត្រូវផ្តល់ជូន។\nដំឡើងប៉ុស្តិ៍ទាំងនោះឥឡូវនេះ ឬដំឡើងពេលក្រោយនៅក្នុងការកំណត់ប្រភពប៉ុស្តិ៍។" - "ដំឡើងឥឡូវនេះ" - "យល់​ហើយ" - - - "ចុច ជ្រើសរើស"" ដើម្បីចូលប្រើម៉ឺនុយទូរទស្សន៍។" - "រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ" - "រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ" - "មិនគាំទ្រ PIP ទេ។" - "មិនមានបញ្ចូលដែលអាចបង្ហាញជាមួយ PIP បានទេ។" - "ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍មិនសមស្រប។ សូមចាប់ផ្តើមដំណើរការកម្មវិធី ប៉ុស្តិ៍ផ្សាយផ្ទាល់ សម្រាប់ធាតុបញ្ជូលទូរទស្សន៍ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍។" - "កាកែសម្រួលប៉ុស្តិ៍បានបរាជ័យ" - "រកមិនឃើញកម្មវិធីដើម្បីគ្រប់គ្រងសកម្មភាពនេះទេ។" - "រាល់​​ឆានែល​ប្រភព​គឺ​​លាក់​។ \n ជ្រើស​ឆា​នែ​ល​យ៉ាង​ហោច​ណាស់​មួយ​​ដើម្បី​​មើល​។" - "មិនបានរំពឹងថាមិនមានវីដេអូនេះទេ" - "ប៊ូតុង​ថយក្រោយ​សម្រាប់​ឧបករណ៍​ដែល​បាន​ភ្ជាប់។ ចុច​ប៊ូតុង​ដើម ដើម្បី​ចាកចេញ។" - "ប៉ុស្តិ៍ផ្សាយផ្ទាល់ត្រូវការសិទ្ធិអនុញ្ញាតដើម្បីអានបញ្ជីទូរទស្សន៍។" - "កំណត់ប្រភពរបស់អ្នក" - "ប៉ុស្តិ៍ផ្សាយផ្ទាល់ផ្សាភ្ជាប់បទពិសោធន៍ប្រើប្រាស់ប៉ុស្តិ៍ទូរទស្សន៍លក្ខណៈបុរាណជាមួយប៉ុស្តិ៍ស្រ្ទីមដែលផ្តល់ដោយកម្មវិធី។ \n\nចាប់ផ្តើមដោយកំណត់ប្រភពប៉ុស្តិ៍ដែលបានដំឡើងរួចហើយ។ ឬរុករកក្នុងហាង Google Play ដើម្បីរកកម្មវិធីច្រើនទៀតដែលផ្តល់ជូនប៉ុស្តិ៍ផ្សាយផ្ទាល់។" - "ការថត និងកាលវិភាក" - "10 នាទី" - "30 នាទី" - "1 ម៉ោង" - "3 ម៉ោង" - "ថ្មីៗ" - "បាន​កំណត់​ពេល" - "ស៊េរី" - "ផ្សេងៗ" - "មិនអាចថតប៉ុស្តិ៍នេះបានទេ" - "មិនអាចថតកម្មវិធីនេះបានទេ" - "%1$s ត្រូវបានកំណត់ពេលដើម្បីថត" - "ការថត %1$s ពីឥឡូវនេះដល់ %2$s" - "កាលវិភាគពេញហើយ" - - %1$d ថ្ងៃបន្ទាប់ - %1$d ថ្ងៃបន្ទាប់ - - - %1$d នាទី - %1$d នាទី - - - ការថតថ្មី %1$d - ការថតថ្មី %1$d - - - ការថត %1$d - ការថត %1$d - - - ការថតដែលបានកំណត់ពេល %1$d - ការថតដែលបានកំណត់ពេល %1$d - - "ទស្សនា" - "ចាក់ពីដំបូង" - "ចាក់​បន្ត" - "លុប" - "លុបការថត" - "បន្ត" - "វគ្គ %1$s" - "មើលកាលវិភាគ" - "អាន​បន្ថែម" - "លុបការថត" - "ជ្រើសភាគដែលអ្នកចង់លុប។ ពួកវាមិនអាចស្តារឡើវិញបានទេបន្ទាប់ពីលុបរួចហើយ។" - "មិនមានការថតដែលត្រូវលុបទេ" - "ជ្រើសភាគដែលបានមើល" - "ជ្រើសភាគទាំងអស់" - "ដោះការជ្រើសភាគទាំងអស់" - "បានមើល %1$d នៅនាទីទី %2$d" - "បានមើល %1$d នៅវិនាទីទី %2$d" - "មិនដែលមើល" - - ភាគចំនួន %1$d ក្នុងចំណោម %2$d ត្រូវបានលុប - ភាគចំនួន %1$d ក្នុងចំណោម %2$d ត្រូវបានលុប - - "អាទិភាព" - "ខ្ពស់បំផុត" - "ទាបបំផុត" - "លេខ %1$d" - "ប៉ុស្តិ៍" - "មួយណាក៏បាន" - "ជ្រើសអាទិភាព" - "នៅពេលដែលមានកម្មវិធីដែលត្រូវថតច្រើនពេកក្នុងពេលតែមួយ នោះមានតែកម្មវិធីដែលមានអាទិភាពខ្ពស់ជាងប៉ុណ្ណោះដែលនឹងត្រូវថត។" - "រក្សាទុក" - "ការថតមួយដងមានអាទិភាពខ្ពស់បំផុត" - "បោះបង់" - "បោះបង់" - "បំភ្លេច" - "បញ្ឈប់" - "មើលកាលវិភាគថត" - "កម្មវិធីមួយនេះ" - "ឥឡូវនេះ - %1$s" - "វគ្គទាំងមូល…" - "កាលវិភាគទោះយ៉ាងណាក៏ដោយ" - "ថតកម្មវិធីនេះជំនួសវិញ" - "បោះបង់ការថតនេះ" - "មើលឥឡូវនេះ" - "អាចថតបាន" - "បានកំណត់ពេលការថត" - "ការថតជាន់ម៉ោងគ្នា" - "ការថត" - "បានបរាជ័យក្នុងការថត" - "កំពុងអានកម្មវិធីដើម្បីបង្កើតកាលវិភាគថត" - "កំពុងអានកម្មវិធី" - - - "DVR ត្រូវការទំហំផ្ទុកបន្ថែមទៀត" - "អ្នកនឹងអាចថតកម្មវិធីដោយប្រើ DVR។ ទោះបីជាយ៉ាងណាក៏ដោយ ឥឡូវនេះមិនមានទំហំផ្ទុកគ្រប់គ្រាន់នៅលើឧបករណ៍របស់អ្នកដើម្បីអនុញ្ញាតឲ្យ DVR ដំណើរការនោះទេ។ សូមភ្ជាប់ទៅឧបករណ៍ផ្ទុកខាងក្រៅដែលមានទំហំផ្ទុក %1$sGB ឬធំជាងនេះ បន្ទាប់មកអនុវត្តតាមជំហានទាំងនេះដើម្បីសម្អាតវាក្នុងនាមជាឧបករណ៍ផ្ទុក។" - "ឧបករណ៍ផ្ទុកដែលបានបាត់" - "ឧបករណ៍ផ្ទុកមួយចំនួនដែលប្រើដោយ DVR បានបាត់បង់។ សូមភ្ជាប់ថាសផ្ទុកផ្នែកខាងក្រៅដែលអ្នកបានប្រើពីមុន ដើម្បីបើកដំណើរការ DVR ឡើងវិញ ឬអ្នកអាចជ្រើសរើសធ្វើការបំភ្លេចឧបករណ៍ផ្ទុកនេះ ប្រសិនបើវាមិនអាចប្រើបានតទៅទៀត។" - "បំភ្លេចឧបករណ៍ផ្ទុកឬ?" - "មាតិកា និងកាលវិភាគដែលបានថតទុករបស់អ្នកទាំងអស់នឹងបាត់បង់។" - "បញ្ឈប់ការថតឬ?" - "មាតិកាដែលបានថតនឹងត្រូវបានរក្សាទុក" - - - "ការថតបានកំណត់ពេលហើយ ប៉ុន្តែមានម៉ោងជាន់គ្នា" - "ការថតបានចាប់ផ្តើមប៉ុន្តែវាជាន់គ្នា" - "%1$s នឹងត្រូវបានថត។" - "%1$s កំពុងត្រូវបានថត។" - "ផ្នែកមួយចំនួននៃ %1$s នឹងមិនត្រូវបានថតទេ។" - "ផ្នែកមួយចំនួននៃ %1$s និង %2$s នឹងមិនត្រូវបានថតទេ។" - "ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគមួយទៀតនឹងមិនត្រូវបានថតទេ។" - - ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគ %3$d ទៀតនឹងមិនត្រូវបានថតទេ។ - ផ្នែកមួយចំនួននៃ %1$s, %2$s និងកាលវិភាគ %3$d ទៀតនឹងមិនត្រូវបានថតទេ។ - - "តើអ្នកចង់ថតដែរទេ?" - "តើអ្នកចង់ថតរយៈពេលប៉ុន្មានដែរ?" - "បានកំណត់ពេលរួចហើយ" - "កម្មវិធីដូចគ្នានេះត្រូវបានកំណត់ពេលថតរួចហើយនៅម៉ោង %1$s" - "បានថតរួចហើយ" - "កម្មវិធីនេះត្រូវបានថតរួចហើយ។ វាអាចប្រើបាននៅក្នុងបណ្ណាល័យ DVR ។" - - - - - - - - - "រកមិនឃើញកម្មវិធីដែលបានថតទេ" - "ការថតដែលពាក់ព័ន្ធ" - "(គ្មានការពិពណ៌នាអំពីកម្មវិធីទេ)" - - ការថត %1$d - ការថត %1$d - - " / " - "%1$s ត្រូវបានលុបចេញពីកាលវិភាគថត" - "នឹងមិនត្រូវបានថតពេញលេញនោះទេដោយសារតែអង្គរាវប៉ុស្តិ៍ជាន់គ្នា។" - "នឹងមិនត្រូវបានថតនោះទេដោយសារតែអង្គរាវប៉ុស្តិ៍ជាន់គ្នា។" - "មិនទាន់មានការថតនៅក្នុងកាលវិភាគនៅឡើយទេ។\nអ្នកអាចកំណត់ពេលថតចេញពីការណែនាំកម្មវិធីនេះ។" - - ការជាន់ម៉ោងថត %1$d - ការជាន់ម៉ោងថត %1$d - - "ការកំណត់វគ្គ" - "ចាប់ផ្តើមថតវគ្គ" - "ឈប់ថតវគ្គ" - "បញ្ឈប់ការថតវគ្គឬ?" - "ភាគដែលបានថតនឹងនៅតែមាននៅក្នុងបណ្ណាល័យ DVR ។" - "បញ្ឈប់" - "មិនមានផ្តល់ជូនភាគណាមួយទេ។\nពួកវានឹងត្រូវបានថតបន្ទាប់ពីមានផ្តល់ជូន។" - - (%1$d នាទី) - (%1$d នាទី) - - "ថ្ងៃនេះ" - "ថ្ងៃស្អែក" - "ម្សិលមិញ" - "%1$s ថ្ងៃនេះ" - "%1$s ថ្ងៃស្អែក" - "ពិន្ទុ" - diff --git a/res/values-kn-rIN-v23/strings.xml b/res/values-kn-rIN-v23/strings.xml new file mode 100644 index 00000000..95f50839 --- /dev/null +++ b/res/values-kn-rIN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "ಚಾನಲ್‌ಗಳು" + diff --git a/res/values-kn-rIN/arrays.xml b/res/values-kn-rIN/arrays.xml new file mode 100644 index 00000000..e6d19c9f --- /dev/null +++ b/res/values-kn-rIN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "ಸಾಮಾನ್ಯ" + "ಸಂಪೂರ್ಣ" + "ಝೂಮ್" + + + "ಎಲ್ಲಾ ಚಾನಲ್‌ಗಳು" + "ಕುಟುಂಬ/ಮಕ್ಕಳು" + "ಕ್ರೀಡೆಗಳು" + "ಶಾಪಿಂಗ್" + "ಚಲನಚಿತ್ರಗಳು" + "ಹಾಸ್ಯ ಪ್ರಧಾನ" + "ಪ್ರಯಾಣ" + "ನಾಟಕ" + "ವಿದ್ಯಾಭ್ಯಾಸ" + "ಪ್ರಾಣಿ/ವನ್ಯಜೀವಿ" + "ಸುದ್ದಿ" + "ಗೇಮಿಂಗ್" + "ಕಲೆ" + "ಮನರಂಜನೆ" + "ಲೈಫ್‌ಸ್ಟೈಲ್‌‌" + "ಸಂಗೀತ" + "ಪ್ರೀಮಿಯರ್" + "ತಂತ್ರಜ್ಞಾನ/ವಿಜ್ಞಾನ" + + + "ಲೈವ್‌ ಚಾನಲ್‌ಗಳು" + "ವಿಷಯವನ್ನು ಶೋಧಿಸಲು ಸರಳ ಮಾರ್ಗ" + "ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಹೆಚ್ಚು ಚಾನಲ್‌ಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ" + "ನಿಮ್ಮ ಚಾನಲ್‌ ಲೈನ್‌-ಅಪ್‌ ಕಸ್ಟಮೈಸ್‌ ಮಾಡಿ" + + + "ಟಿವಿಯಲ್ಲಿ ಚಾನಲ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸುವ ಹಾಗೆ ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಂದ ವಿಷಯವನ್ನು ವೀಕ್ಷಿಸಿ." + "ಪರಿಚಿತ ಮಾರ್ಗದರ್ಶಿ ಮತ್ತು ಸ್ನೇಹಿ ಇಂಟರ್ಫೇಸ್ ಹೊಂದಿರುವ ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಂದ ವಿಷಯವನ್ನು ಬ್ರೌಸ್‌ ಮಾಡಿ, \nಟಿವಿಯಲ್ಲಿನ ಚಾನಲ್‌ಗಳಂತೆ." + "ಲೈವ್‌ ಚಾನಲ್‌ಗಳನ್ನು ಆಫರ್ ಮಾಡುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಸ್ಥಾಪಿಸುವ ಮೂಲಕ ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಿ. \nಟಿವಿ ಮೆನುನಲ್ಲಿರುವ ಲಿಂಕ್‌ ಬಳಸಿಕೊಂಡು Google Play ಸ್ಟೋರ್‌ನಲ್ಲಿ ಹೊಂದಾಣಿಕೆಯಾಗಬಲ್ಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ." + "ನಿಮ್ಮ ಚಾನಲ್‌ ಪಟ್ಟಿಯನ್ನು ಕಸ್ಟಮೈಸ್‌ ಮಾಡಲು ನಿಮ್ಮ ಹೊಸದಾಗಿ ಸ್ಥಾಪಿಸಲಾದ ಚಾನಲ್‌ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸಿ. \nಪ್ರಾರಂಭಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೆನುನಲ್ಲಿರುವ ಚಾನಲ್‌ ಮೂಲಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ." + + diff --git a/res/values-kn-rIN/rating_system_strings.xml b/res/values-kn-rIN/rating_system_strings.xml new file mode 100644 index 00000000..4d878099 --- /dev/null +++ b/res/values-kn-rIN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "ಕಾರ್ಯಕ್ರಮವು 15 ವರ್ಷದೊಳಗಿನ ಪ್ರೇಕ್ಷಕರಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು. ಆದ್ದರಿಂದಾಗಿ ಪೋಷಕರು ಅವರ ಸ್ವಂತ ವಿವೇಚನೆ ಬಳಸಬೇಕಾಗುತ್ತದೆ." + "ಕಾರ್ಯಕ್ರಮವು 19 ವರ್ಷದೊಳಗಿನ ಪ್ರೇಕ್ಷಪರಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು ಮತ್ತು ಇಂತಹವುಗಳು 19 ವರ್ಷದೊಳಗಿನ ಯುವಕರಿಗೆ ಸೂಕ್ತವಾಗಿರುವುದಿಲ್ಲ." + "ಸೂಚಿತ ಸಂಭಾಷಣೆ" + "ಒರಟಾದ ಭಾಷೆ" + "ಲೈಂಗಿಕ ವಿಷಯ" + "ಹಿಂಸೆ" + "ಕಾಲ್ಪನಿಕ ಹಿಂಸೆ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ಎಲ್ಲಾ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗಿರುವಂತೆ ರಚಿಸಲಾಗಿದೆ." + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು 7 ಮತ್ತು ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ವಯಸ್ಸಿನ ಮಕ್ಕಳಿಗಾಗಿ ರಚಿಸಲಾಗಿದೆ." + "ಬಹುತೇಕ ಪೋಷಕರ ಪ್ರಕಾರ ಈ ಕಾರ್ಯಕ್ರಮವು ಎಲ್ಲಾ ವಯಸ್ಸಿನ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗಿದೆ." + "ಈ ಕಾರ್ಯಕ್ರಮವು ಕಿರಿಯ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿದೆ ಎಂದು ಪೋಷಕರು ಭಾವಿಸಬಹುದು. ಹಲವಾರು ಪೋಷಕರು ಇದನ್ನು ತಮ್ಮ ಕಿರಿಯ ಮಕ್ಕಳ ಜೊತೆಗೆ ವೀಕ್ಷಿಸಲು ಬಯಸಬಹುದು." + "ಅನೇಕ ಪೋಷಕರ ಪ್ರಕಾರ ಈ ಕಾರ್ಯಕ್ರಮವು 14 ವರ್ಷದೊಳಗಿನ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಲ್ಲದ ಕೆಲವು ವಿಷಯಗಳನ್ನು ಹೊಂದಿದೆ." + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವಿಶೇಷವಾಗಿ ವಯಸ್ಕರು ವೀಕ್ಷಿಸಲು ರಚಿಸಲಾಗಿದೆ ಮತ್ತು 17 ವಯಸ್ಸಿಗಿಂತ ಕಡಿಮೆ ಇರುವ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗದಿರಬಹುದು." + "ಚಲನಚಿತ್ರ ರೇಟಿಂಗ್‌ಗಳು" + "ಸಾಮಾನ್ಯ ಪ್ರೇಕ್ಷಕರು. ಮಕ್ಕಳು ವೀಕ್ಷಿಸಿದರೆ ಅದರಲ್ಲಿ ಪೋಷಕರಿಗೆ ಮುಜುಗರ ಉಂಟು ಮಾಡುವಂತಹದ್ದು ಯಾವುದೂ ಇಲ್ಲ." + "ಪೋಷಕರ ಮಾರ್ಗದರ್ಶನಕ್ಕೆ ಸೂಚಿಸಲಾಗಿದೆ. ಪೋಷಕರು ತಮ್ಮ ಮಕ್ಕಳು ವೀಕ್ಷಿಸಲು ಇಷ್ಟಪಡದಿರುವಂತಹ ಕೆಲವು ವಿಷಯಗಳನ್ನು ಹೊಂದಿರಬಹುದು." + "ಪೋಷಕರೇ ಜಾಗ್ರತೆ! ಇದರಲ್ಲಿರುವ ಕೆಲವು ಸಂಗತಿಗಳು ಹದಿಹರೆಯಪೂರ್ವ ವಯಸ್ಸಿನವರಿಗೆ ಸೂಕ್ತವಲ್ಲದೇ ಇರಬಹುದು." + "ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ, ಕೆಲವು ವಯಸ್ಕರ ವಿಷಯವನ್ನು ಹೊಂದಿದೆ. ಚಿಕ್ಕ ಮಕ್ಕಳನ್ನು ತಮ್ಮೊಂದಿಗೆ ಚಲನಚಿತ್ರಕ್ಕೆ ಕರೆದೊಯ್ಯುವ ಮೊದಲು ಆ ಚಲನಚಿತ್ರದ ಕುರಿತು ಹೆಚ್ಚಿನ ಮಾಹಿತಿ ಪಡೆದುಕೊಳ್ಳುವಂತೆ ಪೋಷಕರಲ್ಲಿ ಕಳಕಳಿಯ ಮನವಿ." + "17 ಕ್ಕಿಂತಲೂ ಕೆಳಗಿನ ವಯಸ್ಸಿನ ಯಾರನ್ನೂ ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ. ಸ್ಪಷ್ಟವಾಗಿ ಹೇಳುವುದಾದರೆ ಇದು ವಯಸ್ಕರಿಗಾಗಿ ಮಾತ್ರ. ಮಕ್ಕಳನ್ನು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ." + diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml new file mode 100644 index 00000000..28f3395e --- /dev/null +++ b/res/values-kn-rIN/strings.xml @@ -0,0 +1,357 @@ + + + + + "ಮೊನೊ" + "ಸ್ಟೀರಿಯೋ" + "Play ನಿಯಂತ್ರಣಗಳು" + "ಚಾನಲ್‌ಗಳು" + "ಟಿವಿ ಆಯ್ಕೆಗಳು" + "ಈ ಚಾನಲ್‌ಗೆ ಪ್ಲೇ ನಿಯಂತ್ರಣಗಳು ಲಭ್ಯವಿಲ್ಲ" + "ಪ್ಲೇ ಮಾಡಿ ಅಥವಾ ವಿರಾಮಗೊಳಿಸಿ" + "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" + "ರೀವೈಂಡ್" + "ಮುಂದೆ" + "ಹಿಂದೆ" + "ಕಾರ್ಯಕ್ರಮ ಮಾರ್ಗಸೂಚಿ" + "ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ" + "%1$s ತೆರೆಯಿರಿ" + "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು" + "ಪ್ರದರ್ಶನ ಮೋಡ್" + "PIP" + "ಬಹು-ಆಡಿಯೊ" + "ಹೆಚ್ಚು ಚಾನಲ್‌ ಪಡೆ" + "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" + "ಟಿವಿ (ಆಂಟೆನಾ/ಕೇಬಲ್)" + "ಯಾವುದೇ ಕಾರ್ಯಕ್ರಮದ ಮಾಹಿತಿ ಇಲ್ಲ" + "ಯಾವುದೇ ಮಾಹಿತಿ ಇಲ್ಲ" + "ನಿರ್ಬಂಧಿಸಲಾದ ಚಾನಲ್" + "ಅಪರಿಚಿತ ಭಾಷೆ" + "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು %1$d" + "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು" + "ಆಫ್" + "ಫಾರ್ಮ್ಯಾಟ್‌ ಮಾಡುವಿಕೆ ವೈಯಕ್ತೀಕರಿಸಿ" + "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗಾಗಿ ಸಿಸ್ಟಂನಾದ್ಯಂತ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಹೊಂದಿಸಿ" + "ಪ್ರದರ್ಶನ ಮೋಡ್" + "ಬಹು-ಆಡಿಯೊ" + "ಮೊನೊ" + "ಸ್ಟೀರಿಯೋ" + "5.1 ಸರೌಂಡ್" + "7.1 ಸರೌಂಡ್" + "%d ಚಾನಲ್‌ಗಳು" + "ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌" + "ಗುಂಪು ಆಯ್ಕೆಮಾಡಿ" + "ಗುಂಪು ಆಯ್ಕೆರದ್ದು" + "ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ" + "ಚಾನಲ್ ಮೂಲ" + "HD/SD" + "HD" + "SD" + "ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ." + "ಇನ್‌ಪುಟ್ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ" + "\'%s\' ಗಾಗಿ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" + "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗೆ ಸಿಸ್ಟಂ-ವೈಡ್‌ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." + + %1$d ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ + %1$d ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ + + "ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿಲ್ಲ" + "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು" + "ಆನ್" + "ಆಫ್" + "ಚಾನಲ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" + "ಎಲ್ಲಾ ನಿರ್ಬಂಧಿಸಿ" + "ಎಲ್ಲವನ್ನೂ ಅನಿರ್ಬಂಧಿಸಿ" + "ಮರೆಮಾಡಲಾದ ಚಾನಲ್‌ಗಳು" + "ಕಾರ್ಯಕ್ರಮ ನಿರ್ಬಂಧಗಳು" + "PIN ಬದಲಾಯಿಸಿ" + "ಸಿಸ್ಟಂಗಳನ್ನು ರೇಟ್ ಮಾಡುವುದು" + "ರೇಟಿಂಗ್‌ಗಳು" + "ಎಲ್ಲಾ ರೇಟಿಂಗ್ ಸಿಸ್ಟಂಗಳನ್ನು ವೀಕ್ಷಿಸಿ" + "ಇತರ ದೇಶಗಳು" + "ಯಾವುದೂ ಇಲ್ಲ" + "ಯಾವುದೂ ಇಲ್ಲ" + "ಅನ್‌ರೇಟೆಡ್" + "ರೇಟ್ ಮಾಡದೇ ಇರುವ ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ" + "ಯಾವುದೂ ಇಲ್ಲ" + "ಅಧಿಕ ನಿರ್ಬಂಧಗಳು" + "ಮಧ್ಯಮ ರೀತಿಯ ನಿರ್ಬಂಧ" + "ಕಡಿಮೆ ನಿರ್ಬಂಧಗಳು" + "ಕಸ್ಟಮ್" + "ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" + "ಹಿರಿಯ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" + "ಹದಿಹರೆಯದವರಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" + "ಹಸ್ತಚಾಲಿತ ನಿರ್ಬಂಧಗಳು" + + + "%1$s ಮತ್ತು ಉಪ-ರೇಟಿಂಗ್‌ಗಳು" + "ಉಪ-ರೇಟಿಂಗ್‌ಗಳು" + "ಈ ಚಾನಲ್‌ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ. ಈ ಕಾರ್ಯಕ್ರಮ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ ಪಿನ್‌ ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ. ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ" + "ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" + "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳನ್ನು ಹೊಂದಿಸಲು, PIN ರಚಿಸಿ" + "ಹೊಸ PIN ನಮೂದಿಸಿ" + "ನಿಮ್ಮ PIN ಅನ್ನು ದೃಢೀಕರಿಸಿ" + "ನಿಮ್ಮ ಪ್ರಸ್ತುತ PIN ಅನ್ನು ನಮೂದಿಸಿ" + + ನೀವು 5 ಬಾರಿ ತಪ್ಪಾದ ಪಿನ್ ನಮೂದಿಸಿದ್ದೀರಿ.\n%1$d ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + ನೀವು 5 ಬಾರಿ ತಪ್ಪಾದ ಪಿನ್ ನಮೂದಿಸಿದ್ದೀರಿ.\n%1$d ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + + "ಆ PIN ತಪ್ಪಾಗಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." + "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ, PIN ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ" + "ನಿಮ್ಮ ಪಿನ್ ಕೋಡ್ ನಮೂದಿಸಿ." + "ಲೈವ್ ಚಾನಲ್‌ಗಳ ಅಪ್ಲಿಕೇಶನ್ ಟಿವಿ ಚಾನಲ್‌ಗಳಿಗೆ ಸಂಪೂರ್ಣ ಕಾರ್ಯಕ್ರಮ ಮಾರ್ಗದರ್ಶನವನ್ನು ಒದಗಿಸಲು ಪಿನ್ ಕೋಡ್ ಅನ್ನು ಬಳಸುತ್ತದೆ." + "ನಿಮ್ಮ ಪಿನ್ ಕೋಡ್ ನಮೂದಿಸಿ" + "ಅಮಾನ್ಯ ಪಿನ್‌ ಕೋಡ್" + "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" + "ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌" + "ನಿಮ್ಮ ಕಾರ್ಯಕ್ರಮ ಸೂಚಿಗಾಗಿ ಚಾನಲ್‌ಗಳನ್ನು ಆರಿಸಿ" + "ಚಾನಲ್ ಮೂಲಗಳು" + "ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ" + "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು" + "ಟೈಮ್‌‌ಶಿಫ್ಟ್‌" + "ಲೈವ್ ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ನೋಡುವಾಗ ರೆಕಾರ್ಡ್ ಮಾಡುವುದರಿಂದ ನೀವು ವಿರಾಮ ಅಥವಾ ರಿವೈಂಡ್ ಮಾಡಬಹುದು.\nಎಚ್ಚರಿಕೆ: ಸಂಗ್ರಹಣೆಯ ತೀವ್ರ ಬಳಕೆಯ ಮೂಲಕ ಆಂತರಿಕ ಸಂಗ್ರಹಣೆಯು ಕಡಿಮೆಯಾಗಬಹುದು." + "ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು" + "ಪ್ರತಿಕ್ರಿಯೆ ಕಳುಹಿಸಿ" + "ಆವೃತ್ತಿ" + "ಈ ಚಾನಲ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಭಾಗದಲ್ಲಿ ಒತ್ತಿರಿ ಮತ್ತು ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ." + "ಈ ಚಾನಲ್ ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡೀಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." + "ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ" + "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ." + "ಆಡಿಯೊ ಮಾತ್ರ" + "ದುರ್ಬಲ ಸಿಗ್ನಲ್" + "ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವಿಲ್ಲ" + + ಇತರ ಚಾನಲ್‌ಗಳು ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿರುವ ಕಾರಣ %1$s ವರೆಗೂ ಈ ಚಾನಲ್‌ ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. \n\nರೆಕಾರ್ಡ್ ಮಾಡುವ ವೇಳಾಪಟ್ಟಿಯನ್ನು ಸರಿಹೊಂದಿಸಲು ಬಲಕ್ಕೆ ಒತ್ತಿರಿ. + ಇತರ ಚಾನಲ್‌ಗಳು ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿರುವ ಕಾರಣ %1$s ವರೆಗೂ ಈ ಚಾನಲ್‌ ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. \n\nರೆಕಾರ್ಡ್ ಮಾಡುವ ವೇಳಾಪಟ್ಟಿಯನ್ನು ಸರಿಹೊಂದಿಸಲು ಬಲಕ್ಕೆ ಒತ್ತಿರಿ. + + "ಯಾವುದೇ ಶೀರ್ಷಿಕೆಯಿಲ್ಲ" + "ಚಾನಲ್ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" + "ಹೊಸತು" + "ಮೂಲಗಳು" + + %1$d ಚಾನಲ್‌ಗಳು + %1$d ಚಾನಲ್‌ಗಳು + + "ಯಾವುದೇ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ" + "ಹೊಸತು" + "ಇನ್ನೂ ಹೊಂದಿಸಿಲ್ಲ" + "ಹೆಚ್ಚು ಮೂಲಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ" + "ಲೈವ್ ಚಾನಲ್‌ಗಳನ್ನು ಒದಗಿಸುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬ್ರೌಸ್ ಮಾಡಿ" + "ಹೊಸ ಚಾನಲ್ ಮೂಲಗಳು ಲಭ್ಯವಿದೆ" + "ಹೊಸ ಚಾನಲ್ ಮೂಲಗಳು ಆಫರ್ ಮಾಡಲು ಚಾನಲ್‌ಗಳನ್ನು ಹೊಂದಿವೆ.\n ಈಗ ಅವುಗಳನ್ನು ಹೊಂದಿಸಿ, ಅಥವಾ ಇದನ್ನು ಚಾನಲ್ ಮೂಲಗಳ ಸೆಟ್ಟಿಂಗ್‌ನಲ್ಲಿ ನಂತರ ಮಾಡಿ." + "ಇದೀಗ ಹೊಂದಿಸಿ" + "ಸರಿ, ಅರ್ಥವಾಯಿತು" + + + "ಟಿವಿ ಮೆನುವನ್ನು ಪ್ರವೇಶಿಸಲು ""ಆಯ್ಕೆ ಮಾಡು ಒತ್ತಿರಿ""." + "ಯಾವುದೇ ಟಿವಿ ಇನ್‌ಪುಟ್ ಕಂಡುಬಂದಿಲ್ಲ" + "ಟಿವಿ ಇನ್‌ಪುಟ್ ಹುಡುಕಲಾಗಲಿಲ್ಲ" + "ಟ್ಯೂನರ್ ಪ್ರಕಾರವು ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ದಯವಿಟ್ಟು ಟ್ಯೂನರ್ ಪ್ರಕಾರದ ಟಿವಿ ಇನ್‌ಪುಟ್‌ಗೆ ಲೈವ್‌ ಚಾನಲ್‌ಗಳ ಅಪ್ಲಿಕೇಶನ್‌ ಪ್ರಾರಂಭಿಸಿ." + "ಟ್ಯೂನ್ ವಿಫಲವಾಗಿದೆ" + "ಈ ಕ್ರಿಯೆಯನ್ನು ನಿರ್ವಹಿಸಲು ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ ಕಂಡುಬಂದಿಲ್ಲ." + "ಎಲ್ಲ ಮೂಲ ಚಾನಲ್‌ಗಳನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ.\nವೀಕ್ಷಿಸಲು ಕನಿಷ್ಠ ಒಂದು ಚಾನಲ್‌‌ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ." + "ವೀಡಿಯೊ ಅನಿರೀಕ್ಷಿತವಾಗಿ ಲಭ್ಯವಿಲ್ಲ" + "ಸಂಪರ್ಕಪಡಿಸಲಾದ ಸಾಧನಕ್ಕಾಗಿ ಹಿಂದೆ ಕೀ. ನಿರ್ಗಮಿಸಲು ಮುಖಪುಟ ಬಟನ್ ಒತ್ತಿರಿ." + "ಟಿವಿ ಪಟ್ಟಿಗಳನ್ನು ರೀಡ್ ಮಾಡಲು ಲೈವ್ ಚಾನಲ್‌ಗಳಿಗೆ ಅನುಮತಿ ಅಗತ್ಯವಿದೆ." + "ನಿಮ್ಮ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸಿ" + "ಲೈವ್‌ ಚಾನಲ್‌ಗಳು, ಅಪ್ಲಿಕೇಶನ್‌ ಮೂಲಕ ಒದಗಿಸಲಾದ ಸ್ಟ್ರೀಮಿಂಗ್ ಚಾನಲ್‌ಗಳ ಜೊತೆಗೆ ಸಾಂಪ್ರದಾಯಿಕ ಟಿವಿ ಅನುಭವವನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ. \n\nಈಗಾಗಲೇ ಸ್ಥಾಪಿಸಲಾದ ಚಾನಲ್‌‌ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸುವ ಮೂಲಕ ಪ್ರಾರಂಭಿಸಿ. ಅಥವಾ ಲೈವ್‌ ಚಾನಲ್‌ಗಳನ್ನು ಆಫರ್‌ ಮಾಡುವಂತಹ ಇನ್ನಷ್ಟು ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ Google Play ಸ್ಟೋರ್‌ನಲ್ಲಿ ಬ್ರೌಸ್‌ ಮಾಡಿ." + "ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಮತ್ತು ವೇಳಾಪಟ್ಟಿಗಳು" + "10 ನಿಮಿಷಗಳು" + "30 ನಿಮಿಷಗಳು" + "1 ಗಂಟೆ" + "3 ಗಂಟೆಗಳು" + "ಇತ್ತೀಚಿನದು" + "ನಿಗದಿಪಡಿಸಲಾಗಿದ್ದು" + "ಸರಣಿ" + "ಇತರರು" + "ಚಾನಲ್ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." + "ಪ್ರೋಗ್ರಾಂ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." + "%1$s ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" + "ಈಗಿನಿಂದ %2$s ವರೆಗೆ %1$s ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ" + "ಪೂರ್ಣ ವೇಳಾಪಟ್ಟಿ" + + ಮುಂದಿನ %1$d ದಿನಗಳು + ಮುಂದಿನ %1$d ದಿನಗಳು + + + %1$d ನಿಮಿಷಗಳು + %1$d ನಿಮಿಷಗಳು + + + %1$d ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + %1$d ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + + + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + + + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳ ವೇಳಾಪಟ್ಟಿ + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳ ವೇಳಾಪಟ್ಟಿ + + "ರೆಕಾರ್ಡಿಂಗ್ ರದ್ದುಗೊಳಿಸಿ" + "ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ" + "ವಾಚ್" + "ಪ್ರಾರಂಭದಿಂದ ಪ್ಲೇ ಮಾಡು" + "ಪ್ಲೇ ಮುಂದುವರಿಸು" + "ಅಳಿಸಿ" + "ರೆಕಾರ್ಡಿಂಗ್‌‌ಗಳನ್ನು ಅಳಿಸಿ" + "ಮುಂದುವರಿಸು" + "ಸೀಸನ್ %1$s" + "ವೇಳಾಪ. ವೀಕ್ಷಿಸಿ" + "ಇನ್ನಷ್ಟು ಓದಿ" + "ರೆಕಾರ್ಡಿಂಗ್‌ ಅಳಿಸಿ" + "ನೀವು ಅಳಿಸಲು ಬಯಸುವಂತಹ ಸಂಚಿಕೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಅವುಗಳನ್ನು ಒಮ್ಮೆ ಅಳಿಸಿದರೆ ಹಿಂಪಡೆಯಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." + "ಅಳಿಸಲು ಯಾವುದೇ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳಿಲ್ಲ." + "ವೀಕ್ಷಿಸಿದ ಸಂಚಿಕೆ ಆಯ್ಕೆಮಾಡಿ" + "ಎಲ್ಲಾ ಸಂಚಿಕೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" + "ಎಲ್ಲಾ ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಿ" + "%2$d ರಲ್ಲಿ %1$d ನಿಮಿಷಗಳ ಕಾಲ ವೀಕ್ಷಿಸಲಾಗಿದೆ" + "%2$d ರಲ್ಲಿ %1$d ಸೆಕೆಂಡುಗಳ ಕಾಲ ವೀಕ್ಷಿಸಲಾಗಿದೆ" + "ಎಂದಿಗೂ ವೀಕ್ಷಿಸಲಾಗಿಲ್ಲ" + + %2$d ರಲ್ಲಿ %1$d ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ + %2$d ರಲ್ಲಿ %1$d ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ + + "ಆದ್ಯತೆ" + "ಹೆಚ್ಚು" + "ಅತಿ ಕಡಿಮೆ" + "ಸಂ. %1$d" + "ಚಾನಲ್‌ಗಳು" + "ಯಾವುದಾದರೂ" + "ಆದ್ಯತೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ" + "ಏಕ ಕಾಲದಲ್ಲಿ ರೆಕಾರ್ಡ್‌ ಮಾಡಲು ಸಾಕಷ್ಟು ಕಾರ್ಯಕ್ರಮಗಳು ಇದ್ದ ಸಂದರ್ಭದಲ್ಲಿ, ಹೆಚ್ಚಿನ ಆದ್ಯತೆ ಇರುವುದನ್ನು ಮಾತ್ರ ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." + "ಉಳಿಸು" + "ಒಂದು ಬಾರಿ ಮಾಡಿದ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಹೆಚ್ಚಿನ ಆದ್ಯತೆ ಹೊಂದಿರುತ್ತದೆ" + "ನಿಲ್ಲಿಸಿ" + "ರೆಕಾರ್ಡಿಂಗ್ ವೇಳಾಪಟ್ಟಿ ವೀಕ್ಷಿಸಿ" + "ಈ ಏಕೈಕ ಪ್ರೋಗ್ರಾಂ" + "ಈಗ - %1$s" + "ಸಂಪೂರ್ಣ ಸರಣಿ…" + "ಹೇಗಾದರೂ ನಿಗದಿಪಡಿಸಿ" + "ಬದಲಿಗೆ ಇದನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ" + "ಈ ರೆಕಾರ್ಡಿಂಗ್ ರದ್ದುಗೊಳಿಸಿ" + "ಈಗ ವೀಕ್ಷಿಸಿ" + "ರೆಕಾರ್ಡಿಂಗ್‌‌ಗಳನ್ನು ಅಳಿಸಿ..." + "ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದಾದ" + "ರೆಕಾರ್ಡಿಂಗ್ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" + "ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷ" + "ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿದೆ" + "ರೆಕಾರ್ಡಿಂಗ್ ವಿಫಲವಾಗಿದೆ" + "ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ರೀಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ" + "ಇತ್ತೀಚಿನ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ" + "%1$s ನ ರೆಕಾರ್ಡಿಂಗ್ ಅಪೂರ್ಣವಾಗಿದೆ." + "%1$s ಮತ್ತು %2$s ನ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಅಪೂರ್ಣವಾಗಿವೆ." + "%1$s, %2$s ಮತ್ತು %3$s ನ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಅಪೂರ್ಣವಾಗಿವೆ." + "ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆ ಇಲ್ಲದಿರುವ ಕಾರಣ %1$s ನ ರೆಕಾರ್ಡಿಂಗ್ ಪೂರ್ಣಗೊಂಡಿಲ್ಲ." + "ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆ ಇಲ್ಲದಿರುವ ಕಾರಣ %1$s ಮತ್ತು %2$s ನ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಪೂರ್ಣಗೊಂಡಿಲ್ಲ." + "ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆ ಇಲ್ಲದಿರುವ ಕಾರಣ %1$s, %2$s ಮತ್ತು %3$s ನ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಪೂರ್ಣಗೊಂಡಿಲ್ಲ." + "DVR ಗೆ ಹೆಚ್ಚಿನ ಸಂಗ್ರಹಣೆಯ ಅಗತ್ಯವಿದೆ" + "DVR ಮೂಲಕ ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ. ಅದಾಗ್ಯೂ DVR ಗೆ ಕೆಲಸ ಮಾಡಲು ಇದೀಗ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ. ದಯವಿಟ್ಟು %1$dGB ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಬಾಹ್ಯ ಡ್ರೈವ್‌ಗೆ ಸಂಪರ್ಕಿಸಿ ಮತ್ತು ಸಾಧನ ಸಂಗ್ರಹಣೆಯಂತೆ ಫಾರ್ಮ್ಯಾಟ್‌ ಮಾಡಲು ಹಂತಗಳನ್ನು ಅನುಸರಿಸಿ." + "ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆಯಿಲ್ಲ" + "ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆ ಇಲ್ಲದಿರುವ ಕಾರಣ ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಕೆಲವು ರೆಕಾರ್ಡಿಂಗ್ ಅಳಿಸಲು ಪ್ರಯತ್ನಿಸಿ." + "ಸಂಗ್ರಹಣೆ ಕಾಣೆಯಾಗಿದೆ" + "ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸುವುದೇ?" + "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ವಿಷಯವನ್ನು ಉಳಿಸಲಾಗುತ್ತದೆ." + "ಈ ಕಾರ್ಯಕ್ರಮದ ಜೊತೆಗೆ ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷಿಸುವ ಕಾರಣದಿಂದಾಗಿ %1$s ರೆಕಾರ್ಡಿಂಗ್ ಅನ್ನು ನಿಲ್ಲಿಸಲಾಗುವುದು. ರೆಕಾರ್ಡ್ ಮಾಡಲಾದ ವಿಷಯವನ್ನು ಉಳಿಸಲಾಗುವುದು." + "ರೆಕಾರ್ಡಿಂಗ್ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ ಆದರೆ ಸಂಘರ್ಷಣೆಗಳನ್ನು ಹೊಂದಿದೆ" + "ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಲಾಗಿದೆ ಆದರೆ ಸಂಘರ್ಷಣೆಗಳನ್ನು ಹೊಂದಿದೆ" + "%1$s ಅನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." + "%1$s ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ." + "%1$s ನ ಕೆಲವು ಭಾಗಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." + "%1$s ಮತ್ತು %2$s ನ ಕೆಲವು ಭಾಗಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." + "%1$s, %2$s ಮತ್ತು ಇನ್ನೊಂದು ವೇಳಾಪಟ್ಟಿಯನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." + + %1$s, %2$s ಮತ್ತು %3$d ಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಕೆಲವು ಭಾಗಗಳ ವೇಳಾಪಟ್ಟಿಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + %1$s, %2$s ಮತ್ತು %3$d ಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಕೆಲವು ಭಾಗಗಳ ವೇಳಾಪಟ್ಟಿಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + + "ನೀವು ಏನನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ಬಯಸುತ್ತೀರಿ?" + "ನೀವು ಎಷ್ಟು ಸಮಯ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಬಯಸುವಿರಿ?" + "ಈಗಾಗಲೇ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" + "ಅದೇ ಕಾರ್ಯಕ್ರಮವನ್ನು ಈಗಾಗಲೇ %1$s ಸಮಯಕ್ಕೆ ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ." + "ಈಗಾಗಲೇ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ" + "ಈ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಈಗಾಗಲೇ ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗಿದೆ. ಇದು DVR ಲೈಬ್ರರಿಯಲ್ಲಿ ಲಭ್ಯವಿದೆ." + "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" + + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. + + + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಅವುಗಳಲ್ಲಿ %3$d ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಅವುಗಳಲ್ಲಿ %3$d ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + + + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಈ ಸರಣಿಯ ಮತ್ತು ಇತರ ಸರಣಿಯ %3$d ಸಂಚಿಕೆಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಈ ಸರಣಿಯ ಮತ್ತು ಇತರ ಸರಣಿಯ %3$d ಸಂಚಿಕೆಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + + + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಇತರ ಸರಣಿಯ 1 ಸಂಚಿಕೆಯನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಇತರ ಸರಣಿಯ 1 ಸಂಚಿಕೆಯನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + + + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಇತರ ಸರಣಿಯ %3$d ಸಂಚಿಕೆಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + %2$s ಗೆ %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ. ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಇತರ ಸರಣಿಯ %3$d ಸಂಚಿಕೆಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + + "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ಕಾರ್ಯಕ್ರಮ ಕಂಡುಬಂದಿಲ್ಲ." + "ಸಂಬಂಧಿಸಿದ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು" + + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು + + " / " + "ರೆಕಾರ್ಡ್‌ ಮಾಡುವಿಕೆ ವೇಳಾಪಟ್ಟಿಯಿಂದ %1$s ತೆಗೆದುಹಾಕಲಾಗಿದೆ" + "ಟ್ಯೂನರ್ ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಭಾಗಶಃ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ." + "ಟ್ಯೂನರ್ ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ." + "ಯಾವುದೇ ರೆಕಾರ್ಡಿಂಗ್ ಇನ್ನೂ ನಿಗದಿಪಡಿಸಿಲ್ಲ.\n ನೀವು ಪ್ರೋಗ್ರಾಂ ಮಾರ್ಗಸೂಚಿ ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್ ಅನ್ನು ನಿಗದಿಪಡಿಸಬಹುದು." + + %1$d ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷಗಳು + %1$d ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷಗಳು + + "ಸರಣಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" + "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭ" + "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ" + "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸುವುದೇ?" + "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ಭಾಗಗಳು DVR ಲೈಬ್ರರಿಯಲ್ಲಿ ಲಭ್ಯವಿರುತ್ತವೆ." + "ನಿಲ್ಲಿಸಿ" + "ಈಗ ಪ್ರಸಾರ ಮಾಡಲು ಯಾವುದೇ ಸಂಚಿಕೆಗಳಿಲ್ಲ." + "ಯಾವುದೇ ಸಂಚಿಕೆಗಳು ಲಭ್ಯವಿಲ್ಲ.\nಅವುಗಳು ಒಮ್ಮೆ ಲಭ್ಯವಾದಾಗ ಅವುಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." + + (%1$d ನಿಮಿಷಗಳು) + (%1$d ನಿಮಿಷಗಳು) + + "ಇಂದು" + "ನಾಳೆ" + "ನಿನ್ನೆ" + "%1$s ಇಂದು" + "%1$s ನಾಳೆ" + "ಸ್ಕೋರ್" + "ರೆಕಾರ್ಡ್ ಮಾಡಲಾದ ಪ್ರೋಗ್ರಾಂಗಳು" + diff --git a/res/values-kn-v23/strings.xml b/res/values-kn-v23/strings.xml deleted file mode 100644 index 95f50839..00000000 --- a/res/values-kn-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "ಚಾನಲ್‌ಗಳು" - diff --git a/res/values-kn/arrays.xml b/res/values-kn/arrays.xml deleted file mode 100644 index e6d19c9f..00000000 --- a/res/values-kn/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "ಸಾಮಾನ್ಯ" - "ಸಂಪೂರ್ಣ" - "ಝೂಮ್" - - - "ಎಲ್ಲಾ ಚಾನಲ್‌ಗಳು" - "ಕುಟುಂಬ/ಮಕ್ಕಳು" - "ಕ್ರೀಡೆಗಳು" - "ಶಾಪಿಂಗ್" - "ಚಲನಚಿತ್ರಗಳು" - "ಹಾಸ್ಯ ಪ್ರಧಾನ" - "ಪ್ರಯಾಣ" - "ನಾಟಕ" - "ವಿದ್ಯಾಭ್ಯಾಸ" - "ಪ್ರಾಣಿ/ವನ್ಯಜೀವಿ" - "ಸುದ್ದಿ" - "ಗೇಮಿಂಗ್" - "ಕಲೆ" - "ಮನರಂಜನೆ" - "ಲೈಫ್‌ಸ್ಟೈಲ್‌‌" - "ಸಂಗೀತ" - "ಪ್ರೀಮಿಯರ್" - "ತಂತ್ರಜ್ಞಾನ/ವಿಜ್ಞಾನ" - - - "ಲೈವ್‌ ಚಾನಲ್‌ಗಳು" - "ವಿಷಯವನ್ನು ಶೋಧಿಸಲು ಸರಳ ಮಾರ್ಗ" - "ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಹೆಚ್ಚು ಚಾನಲ್‌ಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ" - "ನಿಮ್ಮ ಚಾನಲ್‌ ಲೈನ್‌-ಅಪ್‌ ಕಸ್ಟಮೈಸ್‌ ಮಾಡಿ" - - - "ಟಿವಿಯಲ್ಲಿ ಚಾನಲ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸುವ ಹಾಗೆ ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಂದ ವಿಷಯವನ್ನು ವೀಕ್ಷಿಸಿ." - "ಪರಿಚಿತ ಮಾರ್ಗದರ್ಶಿ ಮತ್ತು ಸ್ನೇಹಿ ಇಂಟರ್ಫೇಸ್ ಹೊಂದಿರುವ ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಂದ ವಿಷಯವನ್ನು ಬ್ರೌಸ್‌ ಮಾಡಿ, \nಟಿವಿಯಲ್ಲಿನ ಚಾನಲ್‌ಗಳಂತೆ." - "ಲೈವ್‌ ಚಾನಲ್‌ಗಳನ್ನು ಆಫರ್ ಮಾಡುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಸ್ಥಾಪಿಸುವ ಮೂಲಕ ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಿ. \nಟಿವಿ ಮೆನುನಲ್ಲಿರುವ ಲಿಂಕ್‌ ಬಳಸಿಕೊಂಡು Google Play ಸ್ಟೋರ್‌ನಲ್ಲಿ ಹೊಂದಾಣಿಕೆಯಾಗಬಲ್ಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ." - "ನಿಮ್ಮ ಚಾನಲ್‌ ಪಟ್ಟಿಯನ್ನು ಕಸ್ಟಮೈಸ್‌ ಮಾಡಲು ನಿಮ್ಮ ಹೊಸದಾಗಿ ಸ್ಥಾಪಿಸಲಾದ ಚಾನಲ್‌ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸಿ. \nಪ್ರಾರಂಭಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೆನುನಲ್ಲಿರುವ ಚಾನಲ್‌ ಮೂಲಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ." - - diff --git a/res/values-kn/rating_system_strings.xml b/res/values-kn/rating_system_strings.xml deleted file mode 100644 index 4d878099..00000000 --- a/res/values-kn/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "ಕಾರ್ಯಕ್ರಮವು 15 ವರ್ಷದೊಳಗಿನ ಪ್ರೇಕ್ಷಕರಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು. ಆದ್ದರಿಂದಾಗಿ ಪೋಷಕರು ಅವರ ಸ್ವಂತ ವಿವೇಚನೆ ಬಳಸಬೇಕಾಗುತ್ತದೆ." - "ಕಾರ್ಯಕ್ರಮವು 19 ವರ್ಷದೊಳಗಿನ ಪ್ರೇಕ್ಷಪರಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು ಮತ್ತು ಇಂತಹವುಗಳು 19 ವರ್ಷದೊಳಗಿನ ಯುವಕರಿಗೆ ಸೂಕ್ತವಾಗಿರುವುದಿಲ್ಲ." - "ಸೂಚಿತ ಸಂಭಾಷಣೆ" - "ಒರಟಾದ ಭಾಷೆ" - "ಲೈಂಗಿಕ ವಿಷಯ" - "ಹಿಂಸೆ" - "ಕಾಲ್ಪನಿಕ ಹಿಂಸೆ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ಎಲ್ಲಾ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗಿರುವಂತೆ ರಚಿಸಲಾಗಿದೆ." - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು 7 ಮತ್ತು ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ವಯಸ್ಸಿನ ಮಕ್ಕಳಿಗಾಗಿ ರಚಿಸಲಾಗಿದೆ." - "ಬಹುತೇಕ ಪೋಷಕರ ಪ್ರಕಾರ ಈ ಕಾರ್ಯಕ್ರಮವು ಎಲ್ಲಾ ವಯಸ್ಸಿನ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗಿದೆ." - "ಈ ಕಾರ್ಯಕ್ರಮವು ಕಿರಿಯ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಲ್ಲದ ವಿಷಯವನ್ನು ಹೊಂದಿದೆ ಎಂದು ಪೋಷಕರು ಭಾವಿಸಬಹುದು. ಹಲವಾರು ಪೋಷಕರು ಇದನ್ನು ತಮ್ಮ ಕಿರಿಯ ಮಕ್ಕಳ ಜೊತೆಗೆ ವೀಕ್ಷಿಸಲು ಬಯಸಬಹುದು." - "ಅನೇಕ ಪೋಷಕರ ಪ್ರಕಾರ ಈ ಕಾರ್ಯಕ್ರಮವು 14 ವರ್ಷದೊಳಗಿನ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಲ್ಲದ ಕೆಲವು ವಿಷಯಗಳನ್ನು ಹೊಂದಿದೆ." - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವಿಶೇಷವಾಗಿ ವಯಸ್ಕರು ವೀಕ್ಷಿಸಲು ರಚಿಸಲಾಗಿದೆ ಮತ್ತು 17 ವಯಸ್ಸಿಗಿಂತ ಕಡಿಮೆ ಇರುವ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾಗದಿರಬಹುದು." - "ಚಲನಚಿತ್ರ ರೇಟಿಂಗ್‌ಗಳು" - "ಸಾಮಾನ್ಯ ಪ್ರೇಕ್ಷಕರು. ಮಕ್ಕಳು ವೀಕ್ಷಿಸಿದರೆ ಅದರಲ್ಲಿ ಪೋಷಕರಿಗೆ ಮುಜುಗರ ಉಂಟು ಮಾಡುವಂತಹದ್ದು ಯಾವುದೂ ಇಲ್ಲ." - "ಪೋಷಕರ ಮಾರ್ಗದರ್ಶನಕ್ಕೆ ಸೂಚಿಸಲಾಗಿದೆ. ಪೋಷಕರು ತಮ್ಮ ಮಕ್ಕಳು ವೀಕ್ಷಿಸಲು ಇಷ್ಟಪಡದಿರುವಂತಹ ಕೆಲವು ವಿಷಯಗಳನ್ನು ಹೊಂದಿರಬಹುದು." - "ಪೋಷಕರೇ ಜಾಗ್ರತೆ! ಇದರಲ್ಲಿರುವ ಕೆಲವು ಸಂಗತಿಗಳು ಹದಿಹರೆಯಪೂರ್ವ ವಯಸ್ಸಿನವರಿಗೆ ಸೂಕ್ತವಲ್ಲದೇ ಇರಬಹುದು." - "ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ, ಕೆಲವು ವಯಸ್ಕರ ವಿಷಯವನ್ನು ಹೊಂದಿದೆ. ಚಿಕ್ಕ ಮಕ್ಕಳನ್ನು ತಮ್ಮೊಂದಿಗೆ ಚಲನಚಿತ್ರಕ್ಕೆ ಕರೆದೊಯ್ಯುವ ಮೊದಲು ಆ ಚಲನಚಿತ್ರದ ಕುರಿತು ಹೆಚ್ಚಿನ ಮಾಹಿತಿ ಪಡೆದುಕೊಳ್ಳುವಂತೆ ಪೋಷಕರಲ್ಲಿ ಕಳಕಳಿಯ ಮನವಿ." - "17 ಕ್ಕಿಂತಲೂ ಕೆಳಗಿನ ವಯಸ್ಸಿನ ಯಾರನ್ನೂ ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ. ಸ್ಪಷ್ಟವಾಗಿ ಹೇಳುವುದಾದರೆ ಇದು ವಯಸ್ಕರಿಗಾಗಿ ಮಾತ್ರ. ಮಕ್ಕಳನ್ನು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ." - diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml deleted file mode 100644 index adc99656..00000000 --- a/res/values-kn/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "ಮೊನೊ" - "ಸ್ಟೀರಿಯೋ" - "Play ನಿಯಂತ್ರಣಗಳು" - "ಇತ್ತೀಚಿನ ಚಾನಲ್‌ಗಳು" - "TV ಆಯ್ಕೆಗಳು" - "PIP ಆಯ್ಕೆಗಳು" - "ಈ ಚಾನಲ್‌ಗೆ ಪ್ಲೇ ನಿಯಂತ್ರಣಗಳು ಲಭ್ಯವಿಲ್ಲ" - "ಪ್ಲೇ ಮಾಡಿ ಅಥವಾ ವಿರಾಮಗೊಳಿಸಿ" - "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" - "ರೀವೈಂಡ್" - "ಮುಂದೆ" - "ಹಿಂದೆ" - "ಕಾರ್ಯಕ್ರಮ ಮಾರ್ಗಸೂಚಿ" - "ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ" - "%1$s ತೆರೆಯಿರಿ" - "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು" - "ಪ್ರದರ್ಶನ ಮೋಡ್" - "PIP" - "ಆನ್" - "ಆಫ್" - "ಬಹು-ಆಡಿಯೊ" - "ಹೆಚ್ಚು ಚಾನಲ್‌ ಪಡೆ" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಮೂಲ" - "ಸ್ವ್ಯಾಪ್‌ ಮಾಡು" - "ಆನ್" - "ಆಫ್" - "ಶಬ್ದ" - "ಪ್ರಮುಖ" - "PIP ವಿಂಡೋ" - "ಲೇಔಟ್" - "ಕೆಳಗಿನ ಬಲಭಾಗ" - "ಮೇಲಿನ ಬಲಭಾಗ" - "ಮೇಲಿನ ಎಡಭಾಗ" - "ಕೆಳಗಿನ ಎಡಭಾಗ" - "ಅಕ್ಕ ಪಕ್ಕ" - "ಗಾತ್ರ" - "ದೊಡ್ಡದು" - "ಸಣ್ಣ" - "ಇನ್‌ಪುಟ್ ಮೂಲ" - "TV (ಆಂಟೆನಾ/ಕೇಬಲ್)" - "ಯಾವುದೇ ಕಾರ್ಯಕ್ರಮದ ಮಾಹಿತಿ ಇಲ್ಲ" - "ಯಾವುದೇ ಮಾಹಿತಿ ಇಲ್ಲ" - "ನಿರ್ಬಂಧಿಸಲಾದ ಚಾನಲ್" - "ಅಪರಿಚಿತ ಭಾಷೆ" - "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು" - "ಆಫ್" - "ಫಾರ್ಮ್ಯಾಟ್‌ ಮಾಡುವಿಕೆ ವೈಯಕ್ತೀಕರಿಸಿ" - "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗಾಗಿ ಸಿಸ್ಟಂನಾದ್ಯಂತ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಹೊಂದಿಸಿ" - "ಪ್ರದರ್ಶನ ಮೋಡ್" - "ಬಹು-ಆಡಿಯೊ" - "ಮೊನೊ" - "ಸ್ಟೀರಿಯೋ" - "5.1 ಸರೌಂಡ್" - "7.1 ಸರೌಂಡ್" - "%d ಚಾನಲ್‌ಗಳು" - "ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌" - "ಗುಂಪು ಆಯ್ಕೆಮಾಡಿ" - "ಗುಂಪು ಆಯ್ಕೆರದ್ದು" - "ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ" - "ಚಾನಲ್ ಮೂಲ" - "HD/SD" - "HD" - "SD" - "ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ." - "ಇನ್‌ಪುಟ್ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ" - "\'%s\' ಗಾಗಿ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗೆ ಸಿಸ್ಟಂ-ವೈಡ್‌ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." - - %1$d ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ - %1$d ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ - - "ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿಲ್ಲ" - "ಟ್ಯೂನರ್" - "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು" - "ಆನ್" - "ಆಫ್" - "ಚಾನಲ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಎಲ್ಲಾ ನಿರ್ಬಂಧಿಸಿ" - "ಎಲ್ಲವನ್ನೂ ಅನಿರ್ಬಂಧಿಸಿ" - "ಮರೆಮಾಡಲಾದ ಚಾನಲ್‌ಗಳು" - "ಕಾರ್ಯಕ್ರಮ ನಿರ್ಬಂಧಗಳು" - "PIN ಬದಲಾಯಿಸಿ" - "ಸಿಸ್ಟಂಗಳನ್ನು ರೇಟ್ ಮಾಡುವುದು" - "ರೇಟಿಂಗ್‌ಗಳು" - "ಎಲ್ಲಾ ರೇಟಿಂಗ್ ಸಿಸ್ಟಂಗಳನ್ನು ವೀಕ್ಷಿಸಿ" - "ಇತರ ದೇಶಗಳು" - "ಯಾವುದೂ ಇಲ್ಲ" - "ಯಾವುದೂ ಇಲ್ಲ" - "ಯಾವುದೂ ಇಲ್ಲ" - "ಅಧಿಕ ನಿರ್ಬಂಧಗಳು" - "ಮಧ್ಯಮ ರೀತಿಯ ನಿರ್ಬಂಧ" - "ಕಡಿಮೆ ನಿರ್ಬಂಧಗಳು" - "ಕಸ್ಟಮ್" - "ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" - "ಹಿರಿಯ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" - "ಹದಿಹರೆಯದವರಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ" - "ಹಸ್ತಚಾಲಿತ ನಿರ್ಬಂಧಗಳು" - - - "%1$s ಮತ್ತು ಉಪ-ರೇಟಿಂಗ್‌ಗಳು" - "ಉಪ-ರೇಟಿಂಗ್‌ಗಳು" - "ಈ ಚಾನಲ್‌ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ" - "ಈ ಕಾರ್ಯಕ್ರಮ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ. ಈ ಕಾರ್ಯಕ್ರಮ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ ಪಿನ್‌ ನಮೂದಿಸಿ" - "ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" - "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳನ್ನು ಹೊಂದಿಸಲು, PIN ರಚಿಸಿ" - "ಹೊಸ PIN ನಮೂದಿಸಿ" - "ನಿಮ್ಮ PIN ಅನ್ನು ದೃಢೀಕರಿಸಿ" - "ನಿಮ್ಮ ಪ್ರಸ್ತುತ PIN ಅನ್ನು ನಮೂದಿಸಿ" - - ನೀವು 5 ಬಾರಿ ತಪ್ಪಾದ ಪಿನ್ ನಮೂದಿಸಿದ್ದೀರಿ.\n%1$d ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. - ನೀವು 5 ಬಾರಿ ತಪ್ಪಾದ ಪಿನ್ ನಮೂದಿಸಿದ್ದೀರಿ.\n%1$d ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. - - "ಆ PIN ತಪ್ಪಾಗಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." - "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ, PIN ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌" - "ನಿಮ್ಮ ಕಾರ್ಯಕ್ರಮ ಸೂಚಿಗಾಗಿ ಚಾನಲ್‌ಗಳನ್ನು ಆರಿಸಿ" - "ಚಾನಲ್ ಮೂಲಗಳು" - "ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ" - "ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು" - "ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು" - "ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು" - "ಆವೃತ್ತಿ" - "ಈ ಚಾನಲ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ." - "ಈ ಚಾನಲ್ ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ರೇಟ್ ಮಾಡಲಾಗಿದೆ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡಿಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ." - "ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು %1$s ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ." - "ಆಡಿಯೊ ಮಾತ್ರ" - "ದುರ್ಬಲ ಸಿಗ್ನಲ್" - "ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವಿಲ್ಲ" - - ಇತರ ಚಾನಲ್‌ಗಳು ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿರುವ ಕಾರಣ %1$s ವರೆಗೂ ಈ ಚಾನಲ್‌ ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. \n\nರೆಕಾರ್ಡ್ ಮಾಡುವ ವೇಳಾಪಟ್ಟಿಯನ್ನು ಸರಿಹೊಂದಿಸಲು ಬಲಕ್ಕೆ ಒತ್ತಿರಿ. - ಇತರ ಚಾನಲ್‌ಗಳು ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿರುವ ಕಾರಣ %1$s ವರೆಗೂ ಈ ಚಾನಲ್‌ ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. \n\nರೆಕಾರ್ಡ್ ಮಾಡುವ ವೇಳಾಪಟ್ಟಿಯನ್ನು ಸರಿಹೊಂದಿಸಲು ಬಲಕ್ಕೆ ಒತ್ತಿರಿ. - - "ಯಾವುದೇ ಶೀರ್ಷಿಕೆಯಿಲ್ಲ" - "ಚಾನಲ್ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ" - "ಹೊಸತು" - "ಮೂಲಗಳು" - - %1$d ಚಾನಲ್‌ಗಳು - %1$d ಚಾನಲ್‌ಗಳು - - "ಯಾವುದೇ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ" - "ಹೊಸತು" - "ಇನ್ನೂ ಹೊಂದಿಸಿಲ್ಲ" - "ಹೆಚ್ಚು ಮೂಲಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ" - "ಲೈವ್ ಚಾನಲ್‌ಗಳನ್ನು ಒದಗಿಸುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬ್ರೌಸ್ ಮಾಡಿ" - "ಹೊಸ ಚಾನಲ್ ಮೂಲಗಳು ಲಭ್ಯವಿದೆ" - "ಹೊಸ ಚಾನಲ್ ಮೂಲಗಳು ಆಫರ್ ಮಾಡಲು ಚಾನಲ್‌ಗಳನ್ನು ಹೊಂದಿವೆ.\n ಈಗ ಅವುಗಳನ್ನು ಹೊಂದಿಸಿ, ಅಥವಾ ಇದನ್ನು ಚಾನಲ್ ಮೂಲಗಳ ಸೆಟ್ಟಿಂಗ್‌ನಲ್ಲಿ ನಂತರ ಮಾಡಿ." - "ಇದೀಗ ಹೊಂದಿಸಿ" - "ಸರಿ, ಅರ್ಥವಾಯಿತು" - - - "TV ಮೆನುವನ್ನು ಪ್ರವೇಶಿಸಲು ""ಆಯ್ಕೆ ಮಾಡು ಒತ್ತಿರಿ""." - "ಯಾವುದೇ TV ಇನ್‌ಪುಟ್ ಕಂಡುಬಂದಿಲ್ಲ" - "TV ಇನ್‌ಪುಟ್ ಹುಡುಕಲಾಗಲಿಲ್ಲ" - "PIP ಬೆಂಬಲಿತವಾಗಿಲ್ಲ" - "PIP ನೊಂದಿಗೆ ತೋರಿಸಬಹುದಾದ ಯಾವುದೇ ಇನ್‌ಪುಟ್ ಲಭ್ಯವಿಲ್ಲ" - "ಟ್ಯೂನರ್ ಪ್ರಕಾರವು ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ದಯವಿಟ್ಟು ಟ್ಯೂನರ್ ಪ್ರಕಾರದ TV ಇನ್‌ಪುಟ್‌ಗೆ ಲೈವ್‌ ಚಾನಲ್‌ಗಳ ಅಪ್ಲಿಕೇಶನ್‌ ಪ್ರಾರಂಭಿಸಿ." - "ಟ್ಯೂನ್ ವಿಫಲವಾಗಿದೆ" - "ಈ ಕ್ರಿಯೆಯನ್ನು ನಿರ್ವಹಿಸಲು ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ ಕಂಡುಬಂದಿಲ್ಲ." - "ಎಲ್ಲ ಮೂಲ ಚಾನಲ್‌ಗಳನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ.\nವೀಕ್ಷಿಸಲು ಕನಿಷ್ಠ ಒಂದು ಚಾನಲ್‌‌ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ." - "ವೀಡಿಯೊ ಅನಿರೀಕ್ಷಿತವಾಗಿ ಲಭ್ಯವಿಲ್ಲ" - "ಸಂಪರ್ಕಪಡಿಸಲಾದ ಸಾಧನಕ್ಕಾಗಿ ಹಿಂದೆ ಕೀ. ನಿರ್ಗಮಿಸಲು ಮುಖಪುಟ ಬಟನ್ ಒತ್ತಿರಿ." - "ಟಿವಿ ಪಟ್ಟಿಗಳನ್ನು ರೀಡ್ ಮಾಡಲು ಲೈವ್ ಚಾನಲ್‌ಗಳಿಗೆ ಅನುಮತಿ ಅಗತ್ಯವಿದೆ." - "ನಿಮ್ಮ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸಿ" - "ಲೈವ್‌ ಚಾನಲ್‌ಗಳು, ಅಪ್ಲಿಕೇಶನ್‌ ಮೂಲಕ ಒದಗಿಸಲಾದ ಸ್ಟ್ರೀಮಿಂಗ್ ಚಾನಲ್‌ಗಳ ಜೊತೆಗೆ ಸಾಂಪ್ರದಾಯಿಕ ಟಿವಿ ಅನುಭವವನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ. \n\nಈಗಾಗಲೇ ಸ್ಥಾಪಿಸಲಾದ ಚಾನಲ್‌‌ ಮೂಲಗಳನ್ನು ಹೊಂದಿಸುವ ಮೂಲಕ ಪ್ರಾರಂಭಿಸಿ. ಅಥವಾ ಲೈವ್‌ ಚಾನಲ್‌ಗಳನ್ನು ಆಫರ್‌ ಮಾಡುವಂತಹ ಇನ್ನಷ್ಟು ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ Google Play ಸ್ಟೋರ್‌ನಲ್ಲಿ ಬ್ರೌಸ್‌ ಮಾಡಿ." - "ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಮತ್ತು ವೇಳಾಪಟ್ಟಿಗಳು" - "10 ನಿಮಿಷಗಳು" - "30 ನಿಮಿಷಗಳು" - "1 ಗಂಟೆ" - "3 ಗಂಟೆಗಳು" - "ಇತ್ತೀಚಿನದು" - "ನಿಗದಿಪಡಿಸಲಾಗಿದ್ದು" - "ಸರಣಿ" - "ಇತರರು" - "ಚಾನಲ್ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." - "ಪ್ರೋಗ್ರಾಂ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." - "%1$s ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" - "ಈಗಿನಿಂದ %2$s ವರೆಗೆ %1$s ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ" - "ಪೂರ್ಣ ವೇಳಾಪಟ್ಟಿ" - - ಮುಂದಿನ %1$d ದಿನಗಳು - ಮುಂದಿನ %1$d ದಿನಗಳು - - - %1$d ನಿಮಿಷಗಳು - %1$d ನಿಮಿಷಗಳು - - - %1$d ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - %1$d ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - - - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - - - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳ ವೇಳಾಪಟ್ಟಿ - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳ ವೇಳಾಪಟ್ಟಿ - - "ವಾಚ್" - "ಪ್ರಾರಂಭದಿಂದ ಪ್ಲೇ ಮಾಡು" - "ಪ್ಲೇ ಮುಂದುವರಿಸು" - "ಅಳಿಸಿ" - "ರೆಕಾರ್ಡಿಂಗ್‌‌ಗಳನ್ನು ಅಳಿಸು" - "ಮುಂದುವರಿಸು" - "ಸೀಸನ್ %1$s" - "ವೇಳಾಪ. ವೀಕ್ಷಿಸಿ" - "ಇನ್ನಷ್ಟು ಓದಿ" - "ರೆಕಾರ್ಡಿಂಗ್‌ ಅಳಿಸಿ" - "ನೀವು ಅಳಿಸಲು ಬಯಸುವಂತಹ ಸಂಚಿಕೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಅವುಗಳನ್ನು ಒಮ್ಮೆ ಅಳಿಸಿದರೆ ಹಿಂಪಡೆಯಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ." - "ಅಳಿಸಲು ಯಾವುದೇ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳಿಲ್ಲ." - "ವೀಕ್ಷಿಸಿದ ಸಂಚಿಕೆ ಆಯ್ಕೆಮಾಡಿ" - "ಎಲ್ಲಾ ಸಂಚಿಕೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಎಲ್ಲಾ ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಿ" - "%2$d ರಲ್ಲಿ %1$d ನಿಮಿಷಗಳ ಕಾಲ ವೀಕ್ಷಿಸಲಾಗಿದೆ" - "%2$d ರಲ್ಲಿ %1$d ಸೆಕೆಂಡುಗಳ ಕಾಲ ವೀಕ್ಷಿಸಲಾಗಿದೆ" - "ಎಂದಿಗೂ ವೀಕ್ಷಿಸಲಾಗಿಲ್ಲ" - - %2$d ರಲ್ಲಿ %1$d ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ - %2$d ರಲ್ಲಿ %1$d ಸಂಚಿಕೆಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ - - "ಆದ್ಯತೆ" - "ಹೆಚ್ಚು" - "ಅತಿ ಕಡಿಮೆ" - "ಸಂ. %1$d" - "ಚಾನಲ್‌ಗಳು" - "ಯಾವುದಾದರೂ" - "ಆದ್ಯತೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಏಕ ಕಾಲದಲ್ಲಿ ರೆಕಾರ್ಡ್‌ ಮಾಡಲು ಸಾಕಷ್ಟು ಕಾರ್ಯಕ್ರಮಗಳು ಇದ್ದ ಸಂದರ್ಭದಲ್ಲಿ, ಹೆಚ್ಚಿನ ಆದ್ಯತೆ ಇರುವುದನ್ನು ಮಾತ್ರ ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." - "ಉಳಿಸು" - "ಒಂದು ಬಾರಿ ಮಾಡಿದ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು ಹೆಚ್ಚಿನ ಆದ್ಯತೆ ಹೊಂದಿರುತ್ತದೆ" - "ರದ್ದುಮಾಡು" - "ರದ್ದುಮಾಡಿ" - "ಮರೆತುಬಿಡು" - "ನಿಲ್ಲಿಸು" - "ರೆಕಾರ್ಡಿಂಗ್ ವೇಳಾಪಟ್ಟಿ ವೀಕ್ಷಿಸಿ" - "ಈ ಏಕೈಕ ಪ್ರೋಗ್ರಾಂ" - "ಈಗ - %1$s" - "ಸಂಪೂರ್ಣ ಸರಣಿ…" - "ಹೇಗಾದರೂ ನಿಗದಿಪಡಿಸಿ" - "ಬದಲಿಗೆ ಇದನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ" - "ಈ ರೆಕಾರ್ಡಿಂಗ್ ರದ್ದುಗೊಳಿಸಿ" - "ಈಗ ವೀಕ್ಷಿಸಿ" - "ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದಾದ" - "ರೆಕಾರ್ಡಿಂಗ್ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" - "ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷ" - "ರೆಕಾರ್ಡ್ ಆಗುತ್ತಿದೆ" - "ರೆಕಾರ್ಡಿಂಗ್ ವಿಫಲವಾಗಿದೆ" - "ರೆಕಾರ್ಡಿಂಗ್ ವೇಳಾಪಟ್ಟಿಗಳನ್ನು ರಚಿಸಲು ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ರೀಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ" - "ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ರೀಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ" - - - "DVR ಗೆ ಹೆಚ್ಚಿನ ಸಂಗ್ರಹಣೆಯ ಅಗತ್ಯವಿದೆ" - "DVR ಮೂಲಕ ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ. ಅದಾಗ್ಯೂ DVR ಗೆ ಕೆಲಸ ಮಾಡಲು ಇದೀಗ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ. ದಯವಿಟ್ಟು %1$sGB ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಬಾಹ್ಯ ಡ್ರೈವ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಿ ಮತ್ತು ಸಾಧನ ಸಂಗ್ರಹಣೆಯಂತೆ ಫಾರ್ಮ್ಯಾಟ್‌ ಮಾಡಲು ಹಂತಗಳನ್ನು ಅನುಸರಿಸಿ." - "ಸಂಗ್ರಹಣೆ ಕಾಣೆಯಾಗಿದೆ" - "DVR ಮೂಲಕ ಬಳಸಲಾದ ಕೆಲವು ಸಂಗ್ರಹಣೆಯು ಕಾಣೆಯಾಗಿದೆ. ನೀವು DVR ಮರು-ಸಕ್ರಿಯಗೊಳಿಸುವ ಮೊದಲು ಬಳಸಲಾದ ಬಾಹ್ಯ ಡ್ರೈವ್ ಅನ್ನು ಸಂಪರ್ಕಪಡಿಸಿ. ಪರ್ಯಾಯವಾಗಿ, ಇನ್ನೂ ಮುಂದೆ ಲಭ್ಯವಿಲ್ಲದಿದ್ದರೆ ಸಂಗ್ರಹಣೆಯನ್ನು ಮರೆಯಲು ನೀವು ಆಯ್ಕೆಮಾಡಬಹುದು." - "ಸಂಗ್ರಹಣೆಯನ್ನು ಮರೆತಿರುವಿರಾ?" - "ನಿಮ್ಮ ಎಲ್ಲಾ ರೆಕಾರ್ಡ್ ಮಾಡಲಾದ ವಿಷಯ ಮತ್ತು ವೇಳಾಪಟ್ಟಿಗಳು ಕಳೆದುಹೋಗುತ್ತವೆ." - "ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸುವುದೇ?" - "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ವಿಷಯವನ್ನು ಉಳಿಸಲಾಗುತ್ತದೆ." - - - "ರೆಕಾರ್ಡಿಂಗ್ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ ಆದರೆ ಸಂಘರ್ಷಣೆಗಳನ್ನು ಹೊಂದಿದೆ" - "ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಲಾಗಿದೆ ಆದರೆ ಸಂಘರ್ಷಣೆಗಳನ್ನು ಹೊಂದಿದೆ" - "%1$s ಅನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." - "%1$s ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ." - "%1$s ನ ಕೆಲವು ಭಾಗಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." - "%1$s ಮತ್ತು %2$s ನ ಕೆಲವು ಭಾಗಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." - "%1$s, %2$s ಮತ್ತು ಇನ್ನೊಂದು ವೇಳಾಪಟ್ಟಿಯನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ." - - %1$s, %2$s ಮತ್ತು %3$d ಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಕೆಲವು ಭಾಗಗಳ ವೇಳಾಪಟ್ಟಿಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ. - %1$s, %2$s ಮತ್ತು %3$d ಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಕೆಲವು ಭಾಗಗಳ ವೇಳಾಪಟ್ಟಿಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುವುದಿಲ್ಲ. - - "ನೀವು ಏನನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ಬಯಸುತ್ತೀರಿ?" - "ನೀವು ಎಷ್ಟು ಸಮಯ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಬಯಸುವಿರಿ?" - "ಈಗಾಗಲೇ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ" - "ಅದೇ ಕಾರ್ಯಕ್ರಮವನ್ನು ಈಗಾಗಲೇ %1$s ಸಮಯಕ್ಕೆ ರೆಕಾರ್ಡ್ ಮಾಡಲು ನಿಗದಿಪಡಿಸಲಾಗಿದೆ." - "ಈಗಾಗಲೇ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ" - "ಈ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಈಗಾಗಲೇ ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗಿದೆ. ಇದು DVR ಲೈಬ್ರರಿಯಲ್ಲಿ ಲಭ್ಯವಿದೆ." - - - - - - - - - "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ಕಾರ್ಯಕ್ರಮ ಕಂಡುಬಂದಿಲ್ಲ." - "ಸಂಬಂಧಿಸಿದ ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು" - "(ಯಾವುದೇ ಪ್ರೋಗ್ರಾಂ ವಿವರಣೆಯಿಲ್ಲ)" - - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - %1$d ರೆಕಾರ್ಡಿಂಗ್‌ಗಳು - - " / " - "ರೆಕಾರ್ಡ್‌ ಮಾಡುವಿಕೆ ವೇಳಾಪಟ್ಟಿಯಿಂದ %1$s ತೆಗೆದುಹಾಕಲಾಗಿದೆ" - "ಟ್ಯೂನರ್ ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ಭಾಗಶಃ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ." - "ಟ್ಯೂನರ್ ಸಂಘರ್ಷಗಳ ಕಾರಣದಿಂದಾಗಿ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ." - "ಯಾವುದೇ ರೆಕಾರ್ಡಿಂಗ್ ಇನ್ನೂ ನಿಗದಿಪಡಿಸಿಲ್ಲ.\n ನೀವು ಪ್ರೋಗ್ರಾಂ ಮಾರ್ಗಸೂಚಿ ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್ ಅನ್ನು ನಿಗದಿಪಡಿಸಬಹುದು." - - %1$d ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷಗಳು - %1$d ರೆಕಾರ್ಡಿಂಗ್ ಸಂಘರ್ಷಗಳು - - "ಸರಣಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭ" - "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸು" - "ಸರಣಿ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸುವುದೇ?" - "ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾದ ಭಾಗಗಳು DVR ಲೈಬ್ರರಿಯಲ್ಲಿ ಲಭ್ಯವಿರುತ್ತವೆ." - "ನಿಲ್ಲಿಸಿ" - "ಯಾವುದೇ ಸಂಚಿಕೆಗಳು ಲಭ್ಯವಿಲ್ಲ.\nಅವುಗಳು ಒಮ್ಮೆ ಲಭ್ಯವಾದಾಗ ಅವುಗಳನ್ನು ರೆಕಾರ್ಡ್‌ ಮಾಡಲಾಗುತ್ತದೆ." - - (%1$d ನಿಮಿಷಗಳು) - (%1$d ನಿಮಿಷಗಳು) - - "ಇಂದು" - "ನಾಳೆ" - "ನಿನ್ನೆ" - "%1$s ಇಂದು" - "%1$s ನಾಳೆ" - "ಸ್ಕೋರ್" - diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 4e9fa6a6..de25fd97 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -20,9 +20,8 @@ "모노" "스테레오" "재생 컨트롤" - "최근 시청한 채널" + "채널" "TV 옵션" - "PIP 옵션" "이 채널에서 재생 컨트롤을 사용할 수 없습니다." "재생 또는 일시중지" "빨리 감기" @@ -35,33 +34,15 @@ "자막" "표시 모드" "PIP" - "사용" - "사용 안함" "멀티 오디오" "채널 더보기" "설정" - "소스" - "전환" - "사용" - "사용 안함" - "소리" - "기본" - "PIP 창" - "레이아웃" - "오른쪽 하단" - "오른쪽 상단" - "왼쪽 상단" - "왼쪽 하단" - "나란히" - "크기" - "크게" - "작게" - "입력 소스" "TV(안테나/케이블)" "프로그램 정보가 없습니다." "정보 없음" "차단된 채널" - "알 수 없는 언어" + "알 수 없는 언어" + "%1$d 자막" "자막" "사용 안함" "포맷 맞춤설정" @@ -83,6 +64,7 @@ "SD" "그룹 기준" "프로그램이 차단되었습니다." + "등급이 없는 프로그램입니다." "이 프로그램의 시청 등급은 %1$s입니다." "입력이 자동 스캔을 지원하지 않습니다." "\'%s\'의 자동 스캔을 시작할 수 없습니다." @@ -92,7 +74,6 @@ 채널 %1$d개 추가됨 "추가된 채널 없음" - "튜너" "자녀 보호 기능" "사용" "사용 안함" @@ -108,6 +89,8 @@ "기타 국가" "없음" "없음" + "등급 없음" + "등급이 없는 프로그램 차단" "없음" "높은 제한" "중간 제한" @@ -126,6 +109,7 @@ "PIN을 입력하여 이 채널 시청" "PIN을 입력하여 이 프로그램 시청" "이 프로그램의 등급은 %1$s입니다. 이 프로그램을 감상하려면 PIN을 입력하세요." + "등급이 없는 프로그램입니다. 프로그램을 시청하려면 PIN을 입력하세요." "PIN 입력" "PIN을 생성하여 자녀 보호 기능 설정" "새 PIN 입력" @@ -137,22 +121,31 @@ "PIN이 잘못되었습니다. 다시 시도해 주세요." "다시 시도해 주세요. PIN이 일치하지 않습니다." + "우편번호 입력" + "실시간 채널 앱에서 TV 채널에 관한 전체 프로그램 가이드를 제공하는 데 우편번호가 사용됩니다." + "우편번호를 입력하세요." + "잘못된 우편번호입니다." "설정" "채널 목록 맞춤설정" "프로그램 가이드용 채널을 선택합니다." "채널 소스" "새 채널 사용 가능" "자녀 보호 기능" + "타임시프트" + "생방송 프로그램을 일시중지하거나 되감을 수 있도록 시청하면서 녹화해 보세요.\n경고: 이 기능을 사용하면 저장소가 많이 사용되므로 내부 저장소 수명이 줄어들 수도 있습니다." "오픈소스 라이선스" - "오픈소스 라이선스" + "의견 보내기" "버전" "이 채널을 보려면 오른쪽을 누르고 PIN을 입력하세요." "이 프로그램을 보려면 오른쪽을 누르고 PIN을 입력하세요." + "등급이 없는 프로그램입니다.\n프로그램을 시청하려면 오른쪽을 누르고 PIN을 입력하세요." "이 프로그램의 시청 등급은 %1$s입니다.\n이 프로그램을 보려면 오른쪽을 누르고 PIN을 입력하세요." "이 채널을 감상하려면 기본 실시간 TV 앱을 사용합니다." "이 프로그램을 감상하려면 기본 실시간 TV 앱을 사용합니다." + "등급이 없는 프로그램입니다.\n프로그램을 시청하려면 기본 실시간 TV 앱을 사용하세요." "이 프로그램의 등급은 %1$s입니다.\n이 프로그램을 감상하려면 기본 실시간 TV 앱을 사용합니다." "차단된 프로그램입니다." + "등급이 없는 프로그램입니다." "이 프로그램의 시청 등급은 %1$s입니다." "오디오 전용" "신호 약함" @@ -183,8 +176,6 @@ "선택을 눌러"" TV 메뉴에 액세스합니다." "TV 입력이 없습니다." "TV 입력을 찾을 수 없습니다." - "PIP가 지원되지 않습니다." - "PIP로 표시할 수 있는 입력이 없습니다." "튜너 유형이 적합하지 않습니다. 튜너 유형 TV 입력에 실시간 채널 앱을 실행하세요." "조정에 실패했습니다." "이 작업을 처리하는 앱을 찾을 수 없습니다." @@ -228,6 +219,8 @@ 예약된 녹화 %1$d개 예약된 녹화 %1$d개 + "녹화 취소" + "녹화 중지" "시계" "처음부터 재생" "이어서 보기" @@ -260,9 +253,6 @@ "동시에 녹화해야 하는 프로그램이 너무 많은 경우 우선순위가 높은 프로그램만 녹화됩니다." "저장" "일회 녹화의 우선순위가 가장 높음" - "취소" - "취소" - "삭제" "중지" "녹화 일정 보기" "이 프로그램만" @@ -272,25 +262,28 @@ "대신 이 항목을 녹화하기" "이 녹화 취소하기" "지금 보기" + "녹화된 프로그램 삭제 중..." "녹화 가능" "녹화 예약됨" "녹화 예약 충돌" "녹화 중" "녹화 실패" - "프로그램을 확인하고 녹화 일정을 만듭니다." - "프로그램 정보 읽는 중" - - + "프로그램 정보 읽는 중" + "최근 녹화 보기" + "%1$s 녹화를 완료하지 못했습니다." + "%1$s, %2$s 녹화를 완료하지 못했습니다." + "%1$s, %2$s, %3$s 녹화를 완료하지 못했습니다." + "저장용량이 부족하여 %1$s 녹화를 완료하지 못했습니다." + "저장용량이 부족하여 %1$s, %2$s 녹화를 완료하지 못했습니다." + "저장용량이 부족하여 %1$s, %2$s, %3$s 녹화를 완료하지 못했습니다." "DVR에 저장용량이 더 필요합니다." - "DVR을 사용하여 프로그램을 녹화할 수 있습니다. 그러나 DVR을 사용하기에는 기기에 남아있는 저장용량이 충분하지 않습니다. %1$sGB 이상의 외부 드라이브를 연결하고 외부 드라이브를 기기 저장소로 포맷하기 위한 단계를 따르세요." + "DVR을 사용하여 프로그램을 녹화할 수 있습니다. 그러나 DVR을 사용하기에는 기기에 남아있는 저장용량이 충분하지 않습니다. %1$dGB 이상의 외부 드라이브를 연결하고 외부 드라이브를 기기 저장소로 포맷하기 위한 단계를 따르세요." + "저장공간 부족" + "저장용량이 부족하여 이 프로그램을 녹화할 수 없습니다. 기존 녹화 프로그램 일부를 삭제해 주세요." "저장소 없음" - "DVR에 사용되던 일부 저장소가 없습니다. 사용하던 외부 드라이브를 연결한 다음 DVR을 다시 사용 설정하시기 바랍니다. 또는 저장소를 더 이상 사용할 수 없는 경우 삭제할 수 있습니다." - "저장소를 삭제하시겠습니까?" - "기록된 모든 콘텐츠 및 일정이 삭제됩니다." "녹화를 중지하시겠습니까?" "녹화된 콘텐츠가 저장됩니다." - - + "%1$s 녹화가 이 프로그램과 충돌하여 중지됩니다. 녹화된 콘텐츠는 저장됩니다." "녹화가 예약되었으나 충돌이 있음" "녹화가 시작되었으나 충돌이 있음" "%1$s이(가) 녹화됩니다." @@ -308,17 +301,29 @@ "동일한 프로그램이 이미 %1$s에 녹화 예약되었습니다." "이미 녹화됨" "이 프로그램이 이미 녹화되었습니다. DVR 라이브러리에서 사용할 수 있습니다." - - - - - - - - + "시리즈 녹화 예약됨" + + %2$s 녹화 %1$d개가 예약되었습니다. + %2$s 녹화 %1$d개가 예약되었습니다. + + + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 이 중 %3$d개는 녹화되지 않습니다. + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 녹화되지 않습니다. + + + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 이 시리즈 및 다른 시리즈의 에피소드 %3$d개는 녹화되지 않습니다. + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 이 시리즈 및 다른 시리즈의 에피소드 %3$d개는 녹화되지 않습니다. + + + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 다른 시리즈의 에피소드 1개는 녹화되지 않습니다. + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 다른 시리즈의 에피소드 1개는 녹화되지 않습니다. + + + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 다른 시리즈의 에피소드 %3$d개는 녹화되지 않습니다. + %2$s 녹화 %1$d개가 예약되었습니다. 충돌이 발생하여 다른 시리즈의 에피소드 %3$d개는 녹화되지 않습니다. + "녹화된 프로그램을 찾을 수 없습니다." "관련 녹화" - "(프로그램 설명 없음)" 녹화 %1$d개 녹화 %1$d개 @@ -338,6 +343,7 @@ "시리즈 녹화를 중지하시겠습니까?" "녹화된 에피소드는 DVR 라이브러리에서 계속 사용할 수 있습니다." "중지" + "현재 방송 중인 에피소드가 없습니다." "사용할 수 있는 에피소드가 없습니다.\n사용 가능한 에피소드가 생기면 녹화됩니다." (%1$d분) @@ -349,4 +355,5 @@ "오늘 %1$s" "내일 %1$s" "점수" + "녹화된 프로그램" diff --git a/res/values-ky-rKG-v23/strings.xml b/res/values-ky-rKG-v23/strings.xml new file mode 100644 index 00000000..ae85b5f8 --- /dev/null +++ b/res/values-ky-rKG-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Каналдар" + diff --git a/res/values-ky-rKG/arrays.xml b/res/values-ky-rKG/arrays.xml new file mode 100644 index 00000000..835274e6 --- /dev/null +++ b/res/values-ky-rKG/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Кадимки" + "Толук" + "Ченөлчөм" + + + "Бардык каналдар" + "Үй-бүлө/Балдар" + "Спорт" + "Соода-сатык" + "Тасмалар" + "Комедия" + "Саякат" + "Драма" + "Билим алуу" + "Жаныбарлар дүйнөсү" + "Жаңылыктар" + "Оюндар" + "Көркөм өнөрчүлүк" + "Көңүл ачуу" + "Жашоо мүнөзү" + "Музыка" + "Бет ачар" + "Тех/Илим" + + + "Түз ободогу каналдар" + "Видеолорду тез табасыз" + "Колдонмолорду жүктөп алып, көбүрөөк каналга ээ болуңуз" + "Каналдарыңыздын жеке тизмеси" + + + "Колдонмолоруңуздан видеону сыналгыдагы каналдардай эле көрүңүз." + "Колдонмолоруңуздагы видеону сыналгыдагы каналдардай которуңуз.\n" + "Түз ободогу каналдарды сунуштаган колдонмолорду орнотуп, жаңы каналдарды кошуңуз. \nМындай колдонмолорду сыналгынын менюсундагы Google Play Store\'дон таба аласыз." + "Каналдарыңыздын жеке тизмесин түзүү үчүн \nЖөндөөлөр менюсунан Каналдар булактарын тандаңыз." + + diff --git a/res/values-ky-rKG/rating_system_strings.xml b/res/values-ky-rKG/rating_system_strings.xml new file mode 100644 index 00000000..1d422a0c --- /dev/null +++ b/res/values-ky-rKG/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Программаларда 15 жашка чыга электерге ылайыксыз материал камтылгандыктан, ата-энелердин уруксаты талап кылынат." + "Программаларда 19 жашка чыга электерге ылайыксыз материал камтылгандыктан, ал куракка чейинки балдарга көрсөтүлбөшү керек." + "Адепсиз диалог" + "Сөгүнгөн сөздөр" + "Сексуалдык мазмун" + "Зомбулук" + "Зомбулук көрүнүштөрү чагылдырылган фантастика" + "Бул программа бардык балдарга ылайыктуу." + "Бул программа 7 жаштан жогору балдарга ылайыктуу." + "Көпчүлүк ата-энелер бул программаны бардык курактагы адамдарга ылайыктуу деп эсептеши мүмкүн." + "Бул программада ата-энелер жаш балдарга ылайыксыз деп эсептеген материалдар камтылган. Көпчүлүк ата-энелер аны жаш балдары менен бирге көргүсү келиши мүмкүн." + "Бул программада ата-энелердин көпчүлүгү 14 жашка чыга элек балдар үчүн ылайыксыз деп эсептеген материалдар камтылган." + "Бул программа жалаң гана чоңдорго арналгандыктан, 17 жашка чыга элек көрүүчүлөргө сунушталбайт." + "Тасмалар рейтинги" + "Жалпы аудиторияга багытталган. Балдарынын көрүшүнө ата-энеси каршы болгудай эч нерсе жок." + "Ата-эненин көзөмөлү астында көрүү керек. Ата-энелер жаш балдарына көрсөткүсү келбеген материалдар камтылышы мүмкүн." + "Ата-эне менен чогуу көрүү керек. Айрым материалдар жаш өспүрүмдөр үчүн орунсуз болушу мүмкүн." + "Чектелген. Чоңдорго гана ылайык материал камтылган. Ата-энелер жаш балдарын ээрчитээрден мурун фильм жөнүндө көбүрөөк билип алуусу сунушталат." + "17 жаштагыларга жана андан кичүүлөргө уруксат жок. Чоңдөр үчүн гана. Балдарга уруксат жок." + diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml new file mode 100644 index 00000000..088bc631 --- /dev/null +++ b/res/values-ky-rKG/strings.xml @@ -0,0 +1,357 @@ + + + + + "моно" + "стерео" + "Ойнотууну башкаруу" + "Каналдар" + "Сынлг прметрлр" + "Бул канал үчүн ойнотуу көзөмөлдөрү жеткиликтүү эмес" + "Ойнотуу же тындыруу" + "Алдыга түрүү" + "Артка түрүү" + "Кийинки" + "Мурунку" + "Программа көрсөтмөсү" + "Жаңы каналдар бар" + "%1$s ачуу" + "Жабык субттрлр" + "Көрсөтүү режими" + "PIP" + "Мульти-аудио" + "Дагы канлдрд алуу" + "Жөндөөлөр" + "Сыналгы (антенна/кабель)" + "Программанын маалыматы жок" + "Маалымат жок" + "Бөгөттөлгөн канал" + "Белгисиз тил" + "Коштомо жазуулар %1$d" + "Коштомо жазуулар" + "Өчүк" + "Формттоону өзгөчөлшт" + "Жабк субтрл үчн жалп верся жөндөөлр коюу" + "Көрсөтүү режими" + "Мульти-аудио" + "моно" + "стерео" + "5.1 айлана" + "7.1 айлана" + "%d канал" + "Канл тизмсн өзгөчл" + "Топ тандоо" + "Топту тандоодн чыгр" + "Топ" + "Канал булагы" + "HD/SD" + "HD" + "SD" + "Топ" + "Бул программа бөгөттөлгөн." + "Бул программанын рейтинги жок" + "Бул программанын рейтинги %1$s" + "Киргизмеде авто-скан колдоого алынбайт" + "\"%s\" үчүн авто издөөнү баштай албайт" + "Жабык субтитрлер үчүн жалпы версия жөндөөлөрүн иштетүү мүмкүн болбой жатат." + + %1$d канал кошулду + %1$d канал кошулду + + "Каналдар кошулган жок" + "Ата-энелик көзөмөл" + "Күйүк" + "Өчүк" + "Каналдар бөгөттлдү" + "Баарын бөгөттөө" + "Баарн бөгөттн чыгруу" + "Жашыруун каналдар" + "Программа чектлрү" + "PIN алмаштыруу" + "Рейтинг тутумдары" + "Баалоолор" + "Бард рйтнг тутмд көр" + "Башка өлкөлөр" + "Эч бири" + "Эч бири" + "Рейтинги жок" + "Рейтинги жок программалар бөгөттөлсүн" + "Эч бири" + "Катаал чектөөлөр" + "Орточо чектөөлөр" + "Жумшак чектөөлөр" + "Өзгөчөлөштүрүлгөн" + "Балдарга ылайыктуу мазмун" + "Чоңураак балдарга ылайыктуу мазмун" + "Өспүрүмдөргө ылайыктуу мазмун" + "Кол менен чектөөлөр" + + + "%1$s жн көмкч рейтнг" + "Көмөкчү рейтинг" + "Бул каналды көрүү үчүн PIN\'иңизди киргизиңиз" + "Бул программаны көрүү үчүн PIN\'иңизди киргизиңиз" + "Бул программанын рейтинги %1$s. Бул программаны көрүү үчүн PIN\'ди киргизиңиз" + "Бул программанын рейтинги жок. Бул программаны көрүү үчүн PIN кодуңузду киргизиңиз" + "PIN\'иңизди киргизиңиз" + "Ата-энелер көзөмөлүн коюу үчүн, PIN код түзүңүз" + "Жаңы PIN киргизиңиз" + "PIN кодуңузду ырастаңыз" + "Учурдагы PIN-иңизди киргизиңиз" + + PIN кодду 5 жолу туура эмес киргиздиңиз.\n%1$d секунддан кийин кайталаңыз. + PIN кодду 5 жолу туура эмес киргиздиңиз.\n%1$d секунддан кийин кайталаңыз. + + "Ал PIN туура эмес. Дагы бир жолу киргизиңиз." + "PIN дал келбей жатат, дагы бир жолу аракет кылыңыз" + "Почтаңыздын индексин киргизиңиз." + "Түз ободогу каналдар колдонмосу сыналгы каналдары боюнча программалардын толук тизмесин түзүү үчүн почта индексин пайдаланат." + "Почтаңыздын индексин киргизиңиз" + "Почта индекси жараксыз" + "Жөндөөлөр" + "Канал тизмесин ыңгайлаштыруу" + "Программа жетегиңиз үчүн каналдарды тандаңыз" + "Канал булактары" + "Жаңы каналдар бар" + "Ата-эненин көзөмөлү" + "Убакытты жылдыруу" + "Түз ободогу программаларды тындыруу жана артка түрүү үчүн, аларды көрүп жатканда, жаздырып алыңыз.\nЭскертүү: Бул функция сактагычты көп пайдалангандыктан, ички сактагычтын иштөө мөөнөтүн азайтышы мүмкүн." + "Ачык программа уруксаттамалары" + "Пикир билдирүү" + "Версиясы" + "Бул каналды көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз" + "Бул программаны көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз" + "Бул программанын рейтинги жок.\nБул программаны көрүү үчүн баскычтоптогу Оң жебени басып туруп, PIN кодуңузду киргизиңиз" + "Бул программанын рейтинги %1$s \n Бул программаны көрүү үчүн, Оң баскычын басып, PIN\'иңизди киргизиңиз" + "Бул каналды көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." + "Бул программаны көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." + "Бул программанын рейтинги жок.\nБул программаны көрүү үчүн демейки Түз обо TV колдонмосун пайдаланыңыз." + "Бул программанын рейтинги %1$s.\nБул программаны көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." + "Бул программа бөгөттөлгөн" + "Бул программанын рейтинги жок" + "Бул программанын рейтинги – %1$s" + "Аудио гана" + "Начар сигнал" + "Интернет туташуусу жок" + + Бул каналды саат %1$s чейин ойнотууга болбойт, себеби башка каналдар жаздырылууда. \n\nЖаздыруунун графигин өзгөртүү үчүн \"Оңго\" деген жебени басыңыз. + Бул каналды саат %1$s чейин ойнотууга болбойт, себеби башка канал жаздырылууда. \n\nЖаздыруунун графигин өзгөртүү үчүн \"Оңго\" деген жебени басыңыз. + + "Аталышы жок" + "Канал бөгөттөлдү" + "Жаңы" + "Булактар" + + %1$d канал + %1$d канал + + "Жеткиликтүү каналдар жок" + "Жаңы" + "Орнотулган жок" + "Дагы булактарды алуу" + "Түз ободогу каналдарды сунуштаган колдонмолорду серептөө" + "Жаңы канал булактары бар" + "Жаңы канал булактарында сунуштай турган каналдар бар.\nАларды азыр жөндөңүз же кийинчерээк канал булактарынын жөндөөсүнөн аткарыңыз." + "Азыр жөндөө" + "Жарайт, түшүндүм" + + + "Сыналгынын менюсун ачуу үчүн ""ТАНДОО"" баскычын басыңыз." + "Сыналгыга киргизме табылган жок" + "Сыналгыга киргизме табылбай жатат" + "Тюнердин түрү ылайыксыз. Тюнердин түрүндөгү сыналгы киргизмеси үчүн Жандуу каналдар колдонмосун ишке киргизиңиз." + "Жөндөлбөй калды" + "Бул ишти аткара турган бир дагы колдонмо табылган жок." + "Бардык булак каналдары жашырылган.\nКөрүү үчүн кеминде бир канал тандаңыз." + "Видео күтүүсүздөн жеткиликсиз болуп калды." + "АРТКА баскычы – туташкан түзмөккө. Чыгуу үчүн БАШКЫ баскычты басыңыз." + "Сыналгы тизмелерин оруу үчүн Түз ободогу каналдарга уруксат керек." + "Булактарыңызды жөндөңүз" + "Түз ободогу каналдарда - кадимки сыналгы каналдары менен орнотулган колдонмолор аркылуу көрсөтүлүүчү видеоканалдар камтылган. \n\nМурунтан эле орнотулган канал булактарын жөндөөдөн баштаңыз же Google Play Store\'дон башка медиа колдонмолорду карап көрүңүз." + "Жаздыруулар жана графиги" + "10 мүнөт" + "30 мүнөт" + "1 саат" + "3 саат" + "Акыркы" + "Ыраатталды" + "Топтомдор" + "Башкалар" + "Каналды жаздыруу мүмкүн эмес." + "Программаны жаздыруу мүмкүн эмес." + "%1$s программасы жаздыруу графигине кошулду" + "%1$s программасын азыркы убактан %2$s чейин жаздыруу" + "Толук графиги" + + Кийинки %1$d күн + Кийинки %1$d күн + + + %1$d мүнөт + %1$d мүнөт + + + жаңы %1$d жаздыруу + жаңы %1$d жаздыруу + + + %1$d жаздыруу + %1$d жаздыруу + + + %1$d жаздыруу ыраатталган + %1$d жаздыруу ыраатталган + + "Жаздырууну жокко чыгаруу" + "Жаздырууну токтотуу" + "Саат" + "Башынан баштап ойнотуу" + "Ойнотууну улантуу" + "Жок кылуу" + "Жаздырылган нерселерди жок кылуу" + "Улантуу" + "%1$s-сезон" + "Графикти көрүү" + "Көбүрөөк окуу" + "Жаздырылган нерселерди жок кылуу" + "Жок кылгыңыз келген серияларды тандаңыз. Алар жок кылынгандан кийин кайра калыбына келтирилбейт." + "Жок кылына турган жаздыруулар жок." + "Көргөн серияларды тандоо" + "Бардык серияларды тандоо" + "Бардык серияларды тандоодон чыгаруу" + "%2$d мүнөттөн %1$d мүнөт көрүлгөн" + "%2$d секундадан %1$d секунда көрүлгөн" + "Эч качан көрүлгөн эмес" + + %2$d сериядан %1$d серия жок кылынды + %2$d сериядан %1$d серия жок кылынды + + "Артыкчылыктуу" + "Эң жогорку" + "Эң төмөн" + "Жок. %1$d" + "Каналдар" + "Баары" + "Артыкчылыкты тандоо" + "Бир эле убакта жаздыра турган программалардын саны өтө көп болгон учурда, эң жогорку артыкчылыгы барлары гана жаздырылат." + "Сактоо" + "Бир жолку жаздыруулар жогорку артыкчылыкка ээ болот" + "Токтотуу" + "Жаздыруу графигин көрүү" + "Жалгыз ушул программа" + "азыр - %1$s" + "Бардык чыгарылыштары..." + "Баары бир графикке киргизилсин" + "Анын ордуна бул жаздырылсын" + "Бул жаздыруу жокко чыгарылсын" + "Азыр көрүңүз" + "Жаздырылган көрсөтүүлөрдү өчүрүү" + "Жаздырууга болот" + "Пландаштырылган жаздыруу" + "Жаздырууда дал келбестик бар" + "Жаздырууда" + "Жаздырылбай калды" + "Программалар окулууда" + "Акыркы жаздырууларды көрүңүз" + "%1$s жаздырылып бүтпөй калды." + "%1$s жана %2$s жаздырылып бүтпөй калды." + "%1$s, %2$s жана %3$s жаздырылып бүтпөй калды." + "Сактагычта орун жетишсиз болгондуктан %1$s жаздырылып бүтпөй калды." + "Сактагычта орун жетишсиз болгондуктан %1$s жана %2$s жаздырылып бүтпөй калды." + "Сактагычта орун жетишсиз болгондуктан %1$s, %2$s жана %3$s жаздырылып бүтпөй калды." + "DVR көбүрөөк орунду талап кылат" + "Сиз DVR менен программаларды жаздыра аласыз. Бирок, DVR\'ды иштетүү үчүн түзмөгүңүздө бош орун калбай калды. Көлөмү %1$dГб же андан ашык болгон тышкы драйверге туташыңыз да, аны түзмөктүн сактагычы катары жөндөп алыңыз." + "Сактагычта орун жетишсиз" + "Сактагычта орун жетишсиз болгондуктан, бул көрсөтүү жаздырылбайт. Мурда жаздырылган көрсөтүүлөрдү өчүрүп көрүңүз." + "Сактагыч жок болуп жатат" + "Жаздыруу токтотулсунбу?" + "Тартылган мазмун сакталып калат." + "Убакыты бул программаныкына дал келип калгандыктан %1$s жаздыруусу токтотулат. Жаздырылган мазмун сакталып калат." + "Пландаштырылган жаздыруу, бирок бир убакытка коюлуп калган" + "Жаздыруу башталды, бирок башка программа менен дал келип калган" + "%1$s жаздырылат." + "%1$s жаздырылууда." + "%1$s программасынын кээ бир бөлүктөрү жаздырылбайт." + "%1$s жана %2$s программаларынын кээ бир бөлүктөрү жаздырылбайт." + "%1$s, %2$s программаларынын кээ бир бөлүктөрү жана графиктеги дагы бирөө жаздырылбайт." + + %1$s%2$s программаларынын кээ бир бөлүктөрү жана графиктеги %3$d жаздырылбайт. + %1$s%2$s программаларынын кээ бир бөлүктөрү жана графиктеги %3$d жаздырылбайт. + + "Эмнени жаздырууну каалайсыз?" + "Канча убакыт жаздырууну каалайсыз?" + "Буга чейин графикке киргизилген" + "Ушул эле программа буга чейин %1$s жаздыруу графигине кошулган." + "Буга чейин жаздырылган" + "Бул программа буга чейин жазылган. Ал DVR китепканасында жеткиликтүү." + "Сериалдын жаздыруусу пландаштырылды" + + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. + + + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, алардын %3$d сериясы жаздырылбайт. + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыты дал келип калгандыктан, ал жаздырылбайт. + + + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, бул сериалдын %3$d сериясы жана башка сериал жаздырылбайт. + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, бул сериалдын %3$d сериясы жана башка сериал жаздырылбайт. + + + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, башка сериалдын 1 сериясы жаздырылбайт. + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, башка сериалдын 1 сериясы жаздырылбайт. + + + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, башка сериалдын %3$d сериясы жаздырылбайт. + %2$s сериалын %1$d жолу жаздыруу пландаштырылган. Убакыттары дал келип калгандыктан, башка сериалдын %3$d сериясы жаздырылбайт. + + "Жаздырылган программа табылган жок." + "Окшош жаздыруулар" + + %1$d жаздыруу + %1$d жаздыруу + + " / " + "%1$s жаздыруу графигинен алынып салынган" + "Күүлөгүчтөгү дал келбестиктерге байланыштуу жарым-жартылай жаздырылат." + "Күүлөгүчтөгү дал келбестиктерге байланыштуу жаздырылбайт." + "Графикке жаздыруу киргизиле элек.\nПрограмма жетегинен жаздыруу графигин түзсөңүз болот." + + Жаздырууда %1$d дал келбестик бар + Жаздырууда %1$d дал келбестик бар + + "Сериал жөндөөлөрү" + "Сериал тартып баштоо" + "Сериал тартууну токтотуу" + "Сериал тартуу токтотулсунбу?" + "Тартылган сериялар DVR китепканасында сакталып калат." + "Токтотуу" + "Азыр эч кандай серия көрсөтүлбөй турат" + "Учурда бир да эпизод жок.\nАлар жеткиликтүү болоору менен жаздырылат." + + (%1$d мүнөт) + (%1$d мүнөт) + + "Бүгүн" + "Эртең" + "Кечээ" + "Бүгүн саат %1$s чейин" + "Эртең саат %1$s чейин" + "Упай" + "Жаздырылган программалар" + diff --git a/res/values-ky-v23/strings.xml b/res/values-ky-v23/strings.xml deleted file mode 100644 index ae85b5f8..00000000 --- a/res/values-ky-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Каналдар" - diff --git a/res/values-ky/arrays.xml b/res/values-ky/arrays.xml deleted file mode 100644 index 835274e6..00000000 --- a/res/values-ky/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Кадимки" - "Толук" - "Ченөлчөм" - - - "Бардык каналдар" - "Үй-бүлө/Балдар" - "Спорт" - "Соода-сатык" - "Тасмалар" - "Комедия" - "Саякат" - "Драма" - "Билим алуу" - "Жаныбарлар дүйнөсү" - "Жаңылыктар" - "Оюндар" - "Көркөм өнөрчүлүк" - "Көңүл ачуу" - "Жашоо мүнөзү" - "Музыка" - "Бет ачар" - "Тех/Илим" - - - "Түз ободогу каналдар" - "Видеолорду тез табасыз" - "Колдонмолорду жүктөп алып, көбүрөөк каналга ээ болуңуз" - "Каналдарыңыздын жеке тизмеси" - - - "Колдонмолоруңуздан видеону сыналгыдагы каналдардай эле көрүңүз." - "Колдонмолоруңуздагы видеону сыналгыдагы каналдардай которуңуз.\n" - "Түз ободогу каналдарды сунуштаган колдонмолорду орнотуп, жаңы каналдарды кошуңуз. \nМындай колдонмолорду сыналгынын менюсундагы Google Play Store\'дон таба аласыз." - "Каналдарыңыздын жеке тизмесин түзүү үчүн \nЖөндөөлөр менюсунан Каналдар булактарын тандаңыз." - - diff --git a/res/values-ky/rating_system_strings.xml b/res/values-ky/rating_system_strings.xml deleted file mode 100644 index 1d422a0c..00000000 --- a/res/values-ky/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Программаларда 15 жашка чыга электерге ылайыксыз материал камтылгандыктан, ата-энелердин уруксаты талап кылынат." - "Программаларда 19 жашка чыга электерге ылайыксыз материал камтылгандыктан, ал куракка чейинки балдарга көрсөтүлбөшү керек." - "Адепсиз диалог" - "Сөгүнгөн сөздөр" - "Сексуалдык мазмун" - "Зомбулук" - "Зомбулук көрүнүштөрү чагылдырылган фантастика" - "Бул программа бардык балдарга ылайыктуу." - "Бул программа 7 жаштан жогору балдарга ылайыктуу." - "Көпчүлүк ата-энелер бул программаны бардык курактагы адамдарга ылайыктуу деп эсептеши мүмкүн." - "Бул программада ата-энелер жаш балдарга ылайыксыз деп эсептеген материалдар камтылган. Көпчүлүк ата-энелер аны жаш балдары менен бирге көргүсү келиши мүмкүн." - "Бул программада ата-энелердин көпчүлүгү 14 жашка чыга элек балдар үчүн ылайыксыз деп эсептеген материалдар камтылган." - "Бул программа жалаң гана чоңдорго арналгандыктан, 17 жашка чыга элек көрүүчүлөргө сунушталбайт." - "Тасмалар рейтинги" - "Жалпы аудиторияга багытталган. Балдарынын көрүшүнө ата-энеси каршы болгудай эч нерсе жок." - "Ата-эненин көзөмөлү астында көрүү керек. Ата-энелер жаш балдарына көрсөткүсү келбеген материалдар камтылышы мүмкүн." - "Ата-эне менен чогуу көрүү керек. Айрым материалдар жаш өспүрүмдөр үчүн орунсуз болушу мүмкүн." - "Чектелген. Чоңдорго гана ылайык материал камтылган. Ата-энелер жаш балдарын ээрчитээрден мурун фильм жөнүндө көбүрөөк билип алуусу сунушталат." - "17 жаштагыларга жана андан кичүүлөргө уруксат жок. Чоңдөр үчүн гана. Балдарга уруксат жок." - diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml deleted file mode 100644 index 2b9d37ab..00000000 --- a/res/values-ky/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "моно" - "стерео" - "Ойнотууну башкаруу" - "Акыркы каналдар" - "Сынлг прметрлр" - "PIP параметрлери" - "Бул канал үчүн ойнотуу көзөмөлдөрү жеткиликтүү эмес" - "Ойнотуу же тындыруу" - "Алдыга түрүү" - "Артка түрүү" - "Кийинки" - "Мурунку" - "Программа көрсөтмөсү" - "Жаңы каналдар бар" - "%1$s ачуу" - "Жабык субттрлр" - "Көрсөтүү режими" - "PIP" - "Күйүк" - "Өчүк" - "Мульти-аудио" - "Дагы канлдрд алуу" - "Жөндөөлөр" - "Булак" - "Алмаштыруу" - "Күйүк" - "Өчүк" - "Үн" - "Негизги" - "PIP терезеси" - "Үлгү" - "Төмөнкү оң" - "Жогорку оң" - "Жогорку сол" - "Төмөнкү сол" - "Тушма-туш" - "Өлчөмү" - "Чоң" - "Кичине" - "Киргизүү булагы" - "Сыналгы (антенна/кабель)" - "Программанын маалыматы жок" - "Маалымат жок" - "Бөгөттөлгөн канал" - "Белгисиз тил" - "Коштомо жазуулар" - "Өчүк" - "Формттоону өзгөчөлшт" - "Жабк субтрл үчн жалп верся жөндөөлр коюу" - "Көрсөтүү режими" - "Мульти-аудио" - "моно" - "стерео" - "5.1 айлана" - "7.1 айлана" - "%d канал" - "Канл тизмсн өзгөчл" - "Топ тандоо" - "Топту тандоодн чыгр" - "Топ" - "Канал булагы" - "HD/SD" - "HD" - "SD" - "Топ" - "Бул программа бөгөттөлгөн." - "Бул программанын рейтинги %1$s" - "Киргизмеде авто-скан колдоого алынбайт" - "\"%s\" үчүн авто издөөнү баштай албайт" - "Жабык субтитрлер үчүн жалпы версия жөндөөлөрүн иштетүү мүмкүн болбой жатат." - - %1$d канал кошулду - %1$d канал кошулду - - "Каналдар кошулган жок" - "Жөндөгүч" - "Ата-энелик көзөмөл" - "Күйүк" - "Өчүк" - "Каналдар бөгөттлдү" - "Баарын бөгөттөө" - "Баарн бөгөттн чыгруу" - "Жашыруун каналдар" - "Программа чектлрү" - "PIN алмаштыруу" - "Рейтинг тутумдары" - "Баалоолор" - "Бард рйтнг тутмд көр" - "Башка өлкөлөр" - "Эч бири" - "Эч бири" - "Эч бири" - "Катаал чектөөлөр" - "Орточо чектөөлөр" - "Жумшак чектөөлөр" - "Өзгөчөлөштүрүлгөн" - "Балдарга ылайыктуу мазмун" - "Чоңураак балдарга ылайыктуу мазмун" - "Өспүрүмдөргө ылайыктуу мазмун" - "Кол менен чектөөлөр" - - - "%1$s жн көмкч рейтнг" - "Көмөкчү рейтинг" - "Бул каналды көрүү үчүн PIN\'иңизди киргизиңиз" - "Бул программаны көрүү үчүн PIN\'иңизди киргизиңиз" - "Бул программанын рейтинги %1$s. Бул программаны көрүү үчүн PIN\'ди киргизиңиз" - "PIN\'иңизди киргизиңиз" - "Ата-энелер көзөмөлүн коюу үчүн, PIN код түзүңүз" - "Жаңы PIN киргизиңиз" - "PIN кодуңузду ырастаңыз" - "Учурдагы PIN-иңизди киргизиңиз" - - PIN кодду 5 жолу туура эмес киргиздиңиз.\n%1$d секунддан кийин кайталаңыз. - PIN кодду 5 жолу туура эмес киргиздиңиз.\n%1$d секунддан кийин кайталаңыз. - - "Ал PIN туура эмес. Дагы бир жолу киргизиңиз." - "PIN дал келбей жатат, дагы бир жолу аракет кылыңыз" - "Жөндөөлөр" - "Канал тизмесин ыңгайлаштыруу" - "Программа жетегиңиз үчүн каналдарды тандаңыз" - "Канал булактары" - "Жаңы каналдар бар" - "Ата-эненин көзөмөлү" - "Ачык программа уруксаттамалары" - "Ачык программа уруксаттамалары" - "Версиясы" - "Бул каналды көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз" - "Бул программаны көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз" - "Бул программанын рейтинги %1$s \n Бул программаны көрүү үчүн, Оң баскычын басып, PIN\'иңизди киргизиңиз" - "Бул каналды көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." - "Бул программаны көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." - "Бул программанын рейтинги %1$s.\nБул программаны көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз." - "Бул программа бөгөттөлгөн" - "Бул программанын рейтинги – %1$s" - "Аудио гана" - "Начар сигнал" - "Интернет туташуусу жок" - - Бул каналды саат %1$s чейин ойнотууга болбойт, себеби башка каналдар жаздырылууда. \n\nЖаздыруунун графигин өзгөртүү үчүн \"Оңго\" деген жебени басыңыз. - Бул каналды саат %1$s чейин ойнотууга болбойт, себеби башка канал жаздырылууда. \n\nЖаздыруунун графигин өзгөртүү үчүн \"Оңго\" деген жебени басыңыз. - - "Аталышы жок" - "Канал бөгөттөлдү" - "Жаңы" - "Булактар" - - %1$d канал - %1$d канал - - "Жеткиликтүү каналдар жок" - "Жаңы" - "Орнотулган жок" - "Дагы булактарды алуу" - "Түз ободогу каналдарды сунуштаган колдонмолорду серептөө" - "Жаңы канал булактары бар" - "Жаңы канал булактарында сунуштай турган каналдар бар.\nАларды азыр жөндөңүз же кийинчерээк канал булактарынын жөндөөсүнөн аткарыңыз." - "Азыр жөндөө" - "Жарайт, түшүндүм" - - - "Сыналгынын менюсун ачуу үчүн ""ТАНДОО"" баскычын басыңыз." - "Сыналгыга киргизме табылган жок" - "Сыналгыга киргизме табылбай жатат" - "PIP колдоого алынбайт" - "PIP аркылуу көрсөтүлө турган эч нерсе киргизилген жок." - "Тюнердин түрү ылайыксыз. Тюнердин түрүндөгү сыналгы киргизмеси үчүн Жандуу каналдар колдонмосун ишке киргизиңиз." - "Жөндөлбөй калды" - "Бул ишти аткара турган бир дагы колдонмо табылган жок." - "Бардык булак каналдары жашырылган.\nКөрүү үчүн кеминде бир канал тандаңыз." - "Видео күтүүсүздөн жеткиликсиз болуп калды." - "АРТКА баскычы – туташкан түзмөккө. Чыгуу үчүн БАШКЫ баскычты басыңыз." - "Сыналгы тизмелерин оруу үчүн Түз ободогу каналдарга уруксат керек." - "Булактарыңызды жөндөңүз" - "Түз ободогу каналдарда - кадимки сыналгы каналдары менен орнотулган колдонмолор аркылуу көрсөтүлүүчү видеоканалдар камтылган. \n\nМурунтан эле орнотулган канал булактарын жөндөөдөн баштаңыз же Google Play Store\'дон башка медиа колдонмолорду карап көрүңүз." - "Жаздыруулар жана графиги" - "10 мүнөт" - "30 мүнөт" - "1 саат" - "3 саат" - "Акыркы" - "Ыраатталды" - "Топтомдор" - "Башкалар" - "Каналды жаздыруу мүмкүн эмес." - "Программаны жаздыруу мүмкүн эмес." - "%1$s программасы жаздыруу графигине кошулду" - "%1$s программасын азыркы убактан %2$s чейин жаздыруу" - "Толук графиги" - - Кийинки %1$d күн - Кийинки %1$d күн - - - %1$d мүнөт - %1$d мүнөт - - - жаңы %1$d жаздыруу - жаңы %1$d жаздыруу - - - %1$d жаздыруу - %1$d жаздыруу - - - %1$d жаздыруу ыраатталган - %1$d жаздыруу ыраатталган - - "Саат" - "Башынан баштап ойнотуу" - "Ойнотууну улантуу" - "Жок кылуу" - "Жаздырылган нерселерди жок кылуу" - "Улантуу" - "%1$s-сезон" - "Графикти көрүү" - "Көбүрөөк окуу" - "Жаздырылган нерселерди жок кылуу" - "Жок кылгыңыз келген серияларды тандаңыз. Алар жок кылынгандан кийин кайра калыбына келтирилбейт." - "Жок кылына турган жаздыруулар жок." - "Көргөн серияларды тандоо" - "Бардык серияларды тандоо" - "Бардык серияларды тандоодон чыгаруу" - "%2$d мүнөттөн %1$d мүнөт көрүлгөн" - "%2$d секундадан %1$d секунда көрүлгөн" - "Эч качан көрүлгөн эмес" - - %2$d сериядан %1$d серия жок кылынды - %2$d сериядан %1$d серия жок кылынды - - "Артыкчылыктуу" - "Эң жогорку" - "Эң төмөн" - "Жок. %1$d" - "Каналдар" - "Баары" - "Артыкчылыкты тандоо" - "Бир эле убакта жаздыра турган программалардын саны өтө көп болгон учурда, эң жогорку артыкчылыгы барлары гана жаздырылат." - "Сактоо" - "Бир жолку жаздыруулар жогорку артыкчылыкка ээ болот" - "Баш тартуу" - "Жокко чыгаруу" - "Унутуу" - "Токтотуу" - "Жаздыруу графигин көрүү" - "Жалгыз ушул программа" - "азыр - %1$s" - "Бардык чыгарылыштары..." - "Баары бир графикке киргизилсин" - "Анын ордуна бул жаздырылсын" - "Бул жаздыруу жокко чыгарылсын" - "Азыр көрүңүз" - "Жаздырууга болот" - "Пландаштырылган жаздыруу" - "Жаздырууда дал келбестик бар" - "Жаздырууда" - "Жаздырылбай калды" - "Ырааттамаларды жаздырууну түзүү үчүн программаларды окуу" - "Программалар окулууда" - - - "DVR көбүрөөк орунду талап кылат" - "Сиз DVR менен программаларды жаздыра аласыз. Бирок, DVR\'ды иштетүү үчүн түзмөгүңүздө бош орун калбай калды. Көлөмү %1$sГб же андан ашык болгон тышкы драйверге туташыңыз да, аны түзмөктүн сактагычы катары жөндөп алыңыз." - "Сактагыч жок болуп жатат" - "DVR колдонгон сактагычтын айрым бөлүмдөрү жок болуп жатат. DVR\'ды кайра иштетүү үчүн колдонулган тышкы драйверди туташтырыңыз. Же болбосо, жеткиликсиз сактагыч унутулсун дегенди тандасаңыз болот." - "Сактагыч унутулсунбу?" - "Бардык жаздырылган мазмун жана графиктериңиз жоголот." - "Жаздыруу токтотулсунбу?" - "Тартылган мазмун сакталып калат." - - - "Пландаштырылган жаздыруу, бирок бир убакытка коюлуп калган" - "Жаздыруу башталды, бирок башка программа менен дал келип калган" - "%1$s жаздырылат." - "%1$s жаздырылууда." - "%1$s программасынын кээ бир бөлүктөрү жаздырылбайт." - "%1$s жана %2$s программаларынын кээ бир бөлүктөрү жаздырылбайт." - "%1$s, %2$s программаларынын кээ бир бөлүктөрү жана графиктеги дагы бирөө жаздырылбайт." - - %1$s%2$s программаларынын кээ бир бөлүктөрү жана графиктеги %3$d жаздырылбайт. - %1$s%2$s программаларынын кээ бир бөлүктөрү жана графиктеги %3$d жаздырылбайт. - - "Эмнени жаздырууну каалайсыз?" - "Канча убакыт жаздырууну каалайсыз?" - "Буга чейин графикке киргизилген" - "Ушул эле программа буга чейин %1$s жаздыруу графигине кошулган." - "Буга чейин жаздырылган" - "Бул программа буга чейин жазылган. Ал DVR китепканасында жеткиликтүү." - - - - - - - - - "Жаздырылган программа табылган жок." - "Окшош жаздыруулар" - "(Программанын сүрөттөлүшү жок)" - - %1$d жаздыруу - %1$d жаздыруу - - " / " - "%1$s жаздыруу графигинен алынып салынган" - "Күүлөгүчтөгү дал келбестиктерге байланыштуу жарым-жартылай жаздырылат." - "Күүлөгүчтөгү дал келбестиктерге байланыштуу жаздырылбайт." - "Графикке жаздыруу киргизиле элек.\nПрограмма жетегинен жаздыруу графигин түзсөңүз болот." - - Жаздырууда %1$d дал келбестик бар - Жаздырууда %1$d дал келбестик бар - - "Сериал жөндөөлөрү" - "Сериал тартып баштоо" - "Сериал тартууну токтотуу" - "Сериал тартуу токтотулсунбу?" - "Тартылган сериялар DVR китепканасында сакталып калат." - "Токтотуу" - "Учурда бир да эпизод жок.\nАлар жеткиликтүү болоору менен жаздырылат." - - (%1$d мүнөт) - (%1$d мүнөт) - - "Бүгүн" - "Эртең" - "Кечээ" - "Бүгүн саат %1$s чейин" - "Эртең саат %1$s чейин" - "Упай" - diff --git a/res/values-ldrtl/dimens.xml b/res/values-ldrtl/dimens.xml index 0b79b144..4b030d4e 100644 --- a/res/values-ldrtl/dimens.xml +++ b/res/values-ldrtl/dimens.xml @@ -28,7 +28,6 @@ 2dp -238dp - 238dp @dimen/program_guide_side_panel_item_width 32dp diff --git a/res/values-lo-rLA-v23/strings.xml b/res/values-lo-rLA-v23/strings.xml new file mode 100644 index 00000000..53804594 --- /dev/null +++ b/res/values-lo-rLA-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "​ຊ່ອງ" + diff --git a/res/values-lo-rLA/arrays.xml b/res/values-lo-rLA/arrays.xml new file mode 100644 index 00000000..5e1c1e0c --- /dev/null +++ b/res/values-lo-rLA/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "​ປົກ​ກະ​ຕິ" + "ເຕັມ" + "ຊູມ" + + + "​ທຸກ​ຊ່ອງ" + "ຄອບ​ຄົວ/ເດັກ​ນ້ອຍ" + "ກິລາ" + "ຊັອບປິງ" + "ຮູບເງົາ" + "ຄອມເມດີ" + "ການເດີນທາງ" + "ດຣາມ່າ" + "ການສຶກສາ" + "ສັດ" + "​ຂ່າວ" + "ເກມ" + "ສິ​ລະ​ປະ" + "ບັນເທີງ" + "ການດຳລົງຊີວິດ" + "ເພງ" + "ການ​ສະ​ແດງ" + "ເຕັກ​ໂນ​ໂລ​ຊີ/ວິ​ທະ​ຍາ​ສາດ" + + + "ຊ່ອງ​ອອກອາກາດສົດ" + "ວິທີງ່າຍດາຍໃນການຄົ້ນພົບເນື້ອຫາ" + "ດາວໂຫຼດແອັບ, ເອົາຊ່ອງເພີ່ມເຕີມ" + "ປັບແຕ່ງການຈັດວາງຊ່ອງຂອງທ່ານ" + + + "ເບິ່ງເນື້ອຫາຈາກແອັບຕ່າງໆ ເຊັ່ນວ່າ ການຮັບຊົມຊ່ອງຕ່າງໆໃນໂທລະພາບ." + "ຊອກເບິ່ງເນື້ອຫາຈາກແອັບຕ່າງໆດ້ວຍຄຳແນະນຳທີ່ຄຸ້ນເຄີຍ ແລະ ສ່ວນຕໍ່ປະສານທີ່ໃຊ້ງ່າຍ, \nຄືກັນກັບຊ່ອງຕ່າງໆໃນໂທລະພາບ." + "ເພີ່ມຊ່ອງເພີ່ມເຕີມ ໂດຍການຕິດຕັ້ງແອັບຕ່າງໆທີ່ໃຫ້ຊ່ອງອອກອາກາດສົດ. \nຊອກຫາແອັບທີ່ເຂົ້າກັນໄດ້ໃນ Google Play Store ໂດຍການໃຊ້ລິ້ງຢູ່ພາຍໃນເມນູໂທລະພາບ." + "ຕັ້ງຄ່າແຫຼ່ງຊ່ອງທີ່ຕິດຕັ້ງໃໝ່ຂອງທ່ານເພື່ອປັບແຕ່ງລາຍການຊ່ອງຂອງທ່ານ. \nເລືອກແຫຼ່ງຊ່ອງຢູ່ພາຍໃນເມນູການຕັ້ງຄ່າເພື່ອເລີ່ມຕົ້ນນຳໃຊ້." + + diff --git a/res/values-lo-rLA/rating_system_strings.xml b/res/values-lo-rLA/rating_system_strings.xml new file mode 100644 index 00000000..534b6a89 --- /dev/null +++ b/res/values-lo-rLA/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "ລາຍການອາດມີເນື້ອຫາທີ່ບໍ່ເໝາະສົມສຳລັບຜູ້ມີອາຍຸຕໍ່າກວ່າ 15 ປີ, ສະນັ້ນ ຄວນໃຫ້ຜູ້ປົກຄອງໃຫ້ຄຳແນະນຳ." + "ລາຍການອາດມີເນື້ອຫາທີ່ບໍ່ເໝາະສົມສຳລັບຜູ້ມີອາຍຸຕໍ່າກວ່າ 19 ປີ ດັ່ງນັ້ນຈຶ່ງບໍ່ເໝາະສົມສຳລັບຜູ້ທີ່ມີອາຍຸຕໍ່າກວ່າ 19 ປີ." + "ການ​ສົນ​ທະ​ນາ​ຊັກ​ຊວນ" + "ພາ​ສາ​ຫຍາບຄາຍ" + "ເນື້ອ​ໃນກ່ຽວ​ກັບ​ເພດ" + "​ຄວາມ​ຮຸນ​ແຮງ" + "ຄວາມ​ຮຸນ​ແຮງ​ເພີ້​ຝັນ" + "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​ໃຫ້​ເໝາະ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທຸກ​​ເພດ​ທຸກໄວ." + "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ອາ​ຍຸ 7 ​ປີ​ຂຶ້ນ​ໄປ." + "​ຜູ້ປົກ​ຄອງ​ສ່ວນ​ໃຫຍ່​​ເຫັນ​ວ່າ​ລາຍ​ການ​ນີ້​ເໝາະ​ສຳ​ລັບ​ທຸກ​ໄວ." + "​ລາຍ​ການ​ນີ້​ມີ​ເນື້ອ​ຫາ​ທີ່​ຜູ້ປົກ​ຄອງ​ອາດ​ເຫັນ​ວ່າບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທີ່​ມີ​ອາ​ຍຸ​ຕ່ຳ. ພໍ່​ແມ່​ບາງ​ຄົນ​ອາດ​ຈະ​ຕ້ອ​ງ​ການ​ເບິ່ງ​ມັນ​ກັບ​ເດັກ​ນ້ອຍ​ຂອງ​ເຂົາ​ເຈົ້າ." + "ລາຍ​ການ​ນີ້​ມີ​ເນື້ອ​ຫາ​ບາງ​ຢ່າງ​ທີ່​ຜູ້ປົກ​ຄອງ​ອາດ​ເຫັນ​ວ່າບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທີ່​ມີ​ອາ​ຍຸ​ຕ່ຳ​ກວ່າ 14 ປີ." + "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​​ໃຫ້​​ຜູ້ໃຫຍ່​ເບິ່ງໂດຍສະເພາະ ແລະ​ອາດບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ອາ​ຍຸ​ຕ່ຳ​ກວ່າ 17 ປີ." + "ການ​ຈັດ​ອັນ​ດັບຮູບ​ເງົາ" + "ຜູ້​ຊົມ​ທົ່ວ​ໄປ. ບໍ່​ມີ​ອັນ​ໃດທີ່ເດັກນ້ອຍເບິ່ງຈະເຮັດໃຫ້ພໍ່ແມ່ບໍ່ພໍໃຈ." + "ແນະ​ນຳ​ໃຫ້​ມີ​ຄຳ​ແນະ​ນຳ​ຂອງ​ຜູ້​ປົກ​ຄອງ. ອາດ​ຈະ​ມີບາງເນື້ອຫາ​ທີ່​ພໍ່​ແມ່ອາດ​ຈະ​ບໍ່​ມັກ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ." + "ໃຫ້​ພໍ່​ແມ່​ລະ​ມັດ​ລະ​ວັງ​ແທ້ໆ. ບາງເນື້ອຫາ​ອາດ​ຈະ​ບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ກ່ອນ​ໄວ​ລຸ້ນ." + "ຈຳ​ກັດ​ສະ​ເພາະ, ມີເນື້ອຫາ​ສຳ​ລັບ​ຜູ້​ໃຫຍ່​ບາງ​ອັນ. ເຕືອນ​ໃຫ້​ພໍ່​ແມ່​ຮຽນ​ຮູ້​ເພີ່ມ​ເຕີມ​ກ່ຽວ​ກັບ​ຮູບ​ເງົາ​ກ່ອນ​ເອົາ​ເດັກ​ນ້ອຍ​ໄປ​ນຳ​ເຂົາ​ເຈົ້າ." + "ບໍ່ອະນຸຍາດສຳລັບຜູ້ທີ່ມີອາຍຸຕ່ຳກວ່າ 17 ປີ. ເປັນ​ຜູ້​ໃຫຍ່​ຈະ​ແຈ້ງ. ບໍ່​ອະ​ນຸ​ຍາດ​​ເດັກ​ນ້ອຍ." + diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml new file mode 100644 index 00000000..06bb63a3 --- /dev/null +++ b/res/values-lo-rLA/strings.xml @@ -0,0 +1,357 @@ + + + + + "​ໂມ​ໂນ" + "ສະ​ເຕ​ຣິ​ໂອ" + "ຄວບ​ຄຸມ​ການ​ຫຼິ້ນ" + "​ຊ່ອງ" + "ໂຕເລືອກ​ໂທລະພາບ" + "ຫຼິ້ນ​ການ​ຄວບ​ຄຸມ​ບໍ່​ມີ​ໃຫ້​ສຳ​ລັບ​ຊ່ອງ​ນີ້" + "ຫຼິ້ນ​ ຫລື​ຢຸດ​ຊົ່ວ​ຄາວ" + "ເລື່ອນ​ໄປ​ໜ້າ" + "​ຣີ​ວາຍກັບ" + "ຕໍ່​ໄປ" + "ກ່ອນໜ້າ" + "ຄຳ​ແນະນຳ​ລາຍການ" + "ມີ​ຊ່ອງ​ໃໝ່​ໃຫ້​ຢູ່" + "ເປີດ %1$s" + "ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" + "ໂໝດ​ການ​ສະແດງຜົນ" + "PIP" + "ຫຼາຍສຽງ" + "ເອົາຊ່ອງເພີ່ມເຕີມ" + "ການ​ຕັ້ງ​ຄ່າ" + "TV (ເສົາ​ສັນ​ຍານ/ສາຍ​ເຄ​ເບິນ)" + "ບໍ່​ມີ​ຂໍ້​ມູນ​ລາຍ​ການ" + "ບໍ່ມີຂໍ້ມູນ" + "ຊ່ອງ​ທີ່​ຖືກບ​ລັອກ" + "ພາ​ສາ​ບໍ່​ຮູ້​ຈັກ" + "ຄຳບັນຍາຍ %1$d" + "ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" + "ປິດ" + "ປັບແຕ່ງການຈັດຮູບແບບ" + "ກຳນົດ​ການຕັ້ງຄ່າ​ລະບົບ​ສຳລັບ​ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" + "ໂໝດ​ສະແດງ​ຜົນ" + "ຫຼາຍ​ສຽງ" + "​ໂມ​ໂນ" + "ສະເຕຣິໂອ" + "ສຽງ​ຮອບ​ທິດ​ທາງ 5.1" + "ສຽງ​ຮອບ​ທິດ​ທາງ 7.1" + "%d ​ຊ່ອງ" + "ປັບແຕ່ງ​ລາຍ​ຊື່​ຊ່ອງ" + "ເລືອກກຸ່ມ" + "ບໍ່​ເລືອກ​ກຸ່ມ" + "ຈັດກຸ່ມຕາມ" + "ທີ່​ມາ​ຂອງ​ຊ່ອງ" + "HD/SD" + "HD" + "SD" + "ຈັດກຸ່ມຕາມ" + "ໂປ​ຣ​ແກ​ຣມ​ນີ້​ຖືກບ​ລັອກ​ແລ້ວ" + "ລາຍການນີ້ບໍ່ໄດ້ຈັດປະເພດເທື່ອ" + "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ %1$s" + "ອິນ​ພຸດ​ນີ້ບໍ່​ຮອງ​ຮັບ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ" + "ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ​ສຳ​ລັບ \'%s\' ໄດ້" + "ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ຕັ້ງ​ຄ່າ​ທົ່ວ​ລະ​ບົບ​ສຳ​ລັບ​ຄຳ​ບັນ​ຍາຍ​ແບບ​ປິດ​ໄດ້." + + ເພີ່ມ %1$d ຊ່ອງແລ້ວ + ເພີ່ມ %1$d ຊ່ອງແລ້ວ + + "ບໍ່​ໄດ້​ເພີ່ມ​ຊ່ອງ" + "ການ​ຄວບຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ" + "ເປີດ" + "ປິດ" + "ຊ່ອງ​ຖືກ​ບລັອກ​ແລ້ວ" + "​ບລັອກ​​ທັງ​​ໝົດ" + "ປົດ​ບລັອກ​ທັງ​ໝົດ" + "​ຊ່ອງ​ທີ່​ຖືກ​ເຊື່ອງ" + "ການ​ຈຳກັດ​ລາຍການ" + "​ປ່ຽນ​ PIN" + "​ລະ​ບົບ​ການ​ຈັດ​ປະ​ເພດ" + "​​​ການ​ຈັດ​ປະ​ເພດ" + "ເບິ່ງ​ລະບົບ​ການ​ຈັດ​ປະເພດ​ທັງ​ໝົດ" + "ປະ​ເທດ​ອື່ນໆ" + "ບໍ່ມີ" + "ບໍ່ມີ" + "ບໍໄດ້ຈັດປະເພດເທື່ອ" + "ບລັອກລາຍການທີ່ຍັງບໍ່ໄດ້ຈັດປະເພດເທື່ອ" + "ບໍ່ມີ" + "ຈຳກັດສູງ" + "ຈຳກັດປານກາງ" + "ຈຳກັດຕ່ຳ" + "ກຳນົດເອງ" + "ເນື້ອຫາ​ເໝາະສົມ​ສຳລັບ​ເດັກນ້ອຍ" + "ເນື້ອຫາ​ເໝາະ​ສຳລັບ​ເດັກ​ທີ່​ບໍ່​ນ້ອຍ​ຫຼາຍ" + "​ເນື້ອ​ຫາ​ເໝາະ​ສົມ​ສຳ​ລັບ​ໄວ​ຮຸ່ນ" + "ຈຳ​ກັດແບບ​​ລະ​ບຸ​ເອງ" + + + "%1$s ແລະປະເພດຍ່ອຍ" + "ປະເພດຍ່ອຍ" + "ປ້ອນ​ລະ​ຫັດ​ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້" + "​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້" + "ລາຍການນີ້ຖືກຈັດປະເພດເປັນ %1$s. ກະລຸນາໃສ່ລະຫັດ PIN ຂອງທ່ານເພື່ອເບິ່ງລາຍການນີ້" + "ລາຍການນີ້ຍັງບໍ່ໄດ້ຈັດປະເພດເທື່ອ. ກະລຸນາລະບຸລະຫັດ PIN ຂອງທ່ານເພື່ອເບິ່ງລາຍການນີ້" + "​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" + "ເພື່ອ​ຕັ້ງ​ຄ່າ​ຄວບ​ຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ, ກະລຸ​ນາຕັ້ງ​ລະ​ຫັດ PIN" + "ໃສ່ລະຫັດ PIN ໂຕໃຫມ່" + "ຢືນຢັນ​ລະຫັດ PIN ຂອງທ່ານ" + "ປ້ອນ​ລະ​ຫັດ PIN ປັດ​ຈຸ​ບັນ​ຂອງ​ທ່ານ" + + ທ່ານປ້ອນ PIN ບໍ່ຖືກຕ້ອງ 5 ເທື່ອແລ້ວ.\nລອງອີກເທື່ອໃນ %1$d ວິນາທີ. + ທ່ານປ້ອນ PIN ບໍ່ຖືກຕ້ອງ 5 ເທື່ອແລ້ວ.\nລອງອີກເທື່ອໃນ %1$d ວິນາທີ. + + "ລະຫັດ PIN ນັ້ນບໍ່​ຖືກ​ຕ້ອງ, ກະ​ລຸ​ນາລອງ​ໃຫມ່​ອີກ​ຄັ້ງ." + "ລະ​ຫັດ PIN ບໍ່​ກົງ​ກັນ, ກະ​ລຸ​ນາ​ລອງ​ໃໝ່." + "ລະບຸລະຫັດ ZIP ຂອງທ່ານ." + "ແອັບ Live TV ຈະໃຊ້ລະຫັດ ZIP ເພື່ອສະໜອງຄຳແນະນຳລາຍການທີ່ສົມບູນໃຫ້ກັບຊ່ອງໂທລະທັດຕ່າງໆ." + "ໃສ່ລະຫັດ ZIP ຂອງທ່ານ" + "ລະຫັດ ZIP ບໍ່ຖືກຕ້ອງ" + "ການ​ຕັ້ງ​ຄ່າ" + "ປັບແຕ່ງ​ລາຍ​ຊື່​ຊ່ອງ" + "ເລືອກ​ຊ່ອງ​ສຳລັບການ​ແນະນຳລາຍການ​ຂອງ​ທ່ານ" + "ແຫຼ່ງທີ່ມາ​ຂອງ​ຊ່ອງ" + "ມີ​ຊ່ອງ​ໃໝ່​" + "ການ​ຄວບຄຸມ​ຂອງພໍ່ແມ່" + "Timeshift" + "ບັນທຶກໃນລະຫວ່າງເບິ່ງເພື່ອໃຫ້ທ່ານສາມາດຢຸດ ຫຼື ເບິ່ງລາຍການສົດຄືນຫຼັງໄດ້.\nຄຳເຕືອນ: ນີ້ອາດຫຼຸດອາຍຸຂອງບ່ອນຈັດເກັບຂໍ້ມູນພາຍໃນເນື່ອງຈາກມີການໃຊ້ບ່ອນຈັດເກັບຂໍ້ມູນຢ່າງຕໍ່ເນື່ອງ." + "​ໃບ​ອະ​ນຸ​ຍາດ​ Open source" + "ສົ່ງຄຳຕິຊົມ" + "ເວີຊັນ" + "​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້, ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" + "​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" + "ລາຍການນີ້ບໍ່ໄດ້ຈັດປະເພດເທື່ອ.\nເພື່ອເບິ່ງລາຍການນີ້, ໃຫ້ກົດປຸ່ມຂວາແລ້ວໃສ່ລະຫັດ PIN ຂອງທ່ານ" + "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ​ %1$s.\n​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" + "ເພື່ອເບິ່ງຊ່ອງນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." + "ເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." + "ລາຍການນີ້ບໍ່ໄດ້ຈັດປະເພດເທື່ອ.\nເພື່ອເບິ່ງລາຍການນີ້, ໃຫ້ໃຊ້ແອັບ Live TV ເລີ່ມຕົ້ນ." + "ໂປຣແກຣມນີ້ຖືກຈັດປະເພດ %1$s.\nເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." + "​ລາຍ​ການ​ຖືກບລັອກ" + "ລາຍການນີ້ບໍ່ໄດ້ຈັດປະເພດເທື່ອ" + "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ %1$s" + "ສຽງເທົ່ານັ້ນ" + "ສັນຍານອ່ອນ" + "ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ" + + ບໍ່ສາມາດຫຼິ້ນຊ່ອງນີ້ໄດ້ຈົນກວ່າຈະຮອດ %1$s ເນື່ອງຈາກກຳລັງບັນທຶກຊ່ອງອື່ນຢູ່. \n\nກົດຂວາເພື່ອປັບແຕ່ງເວລາການບັນທຶກ. + ບໍ່ສາມາດຫຼິ້ນຊ່ອງນີ້ໄດ້ຈົນກວ່າຈະຮອດ %1$s ເນື່ອງຈາກກຳລັງບັນທຶກຊ່ອງອື່ນຢູ່. \n\nກົດຂວາເພື່ອປັບແຕ່ງເວລາການບັນທຶກ. + + "ບໍ່ມີຫົວຂໍ້" + "​ຊ່ອງ​ຖືກບລັອກ" + "ໃໝ່" + "ແຫຼ່ງທີ່ມາຂອງຊ່ອງ" + + %1$d ຊ່ອງ + %1$d ຊ່ອງ + + "ບໍ່​ມີ​ຊ່ອງ​ພ້ອມ​ໃຊ້​ງານ​ໄດ້​ຢູ່" + "ໃໝ່" + "ບໍ່ໄດ້​ຕິດ​ຕັ້ງ" + "ເບິ່ງແຫຼ່ງທີ່ມາຂອງຊ່ອງເພີ່ມເຕີມ" + "ທ່ອງເບິ່ງແອັບທີ່ໃຫ້ຊ່ອງຖ່າຍທອດສົດ" + "ມີແຫຼ່ງ​ຊ່ອງ​ໃໝ່" + "ແຫຼ່ງຊ່ອງໃໝ່ມີຊ່ອງຕ່າງໆໄວ້ໃຫ້ບໍລິການ.\nຕັ້ງພວກມັນດຽວນີ້, ຫຼືເຮັດສິ່ງນີ້ໃນພາຍຫຼັງໃນການຕັ້ງຄ່າແຫຼ່ງຊ່ອງ." + "ຕັ້ງດຽວນີ້" + "ຕົກລົງ, ເຂົ້າ​ໃຈ​ແລ້ວ" + + + "ກົດ SELECT"" ເພື່ອ​ເຂົ້າ​ຫາ​ເມ​ນູ TV." + "ບໍ່​ພົບການ​ປ້ອນ​ເຂົ້າ TV" + "ບໍ່​ສາ​ມາດ​ຊອກ​ຫາການ​ປ້ອນ​ເຂົ້າ TV ​ໄດ້" + "ປະ​ເພດ​ເຄື່ອງ​ຈູນ​ບໍ່​ເໝາະ​ສົມ; ກະ​ລຸ​ນາ​ເປີດ​ໃຊ້​ແອັບ​ຊ່ອງ​ສົດ​ສຳ​ລັບ​ການ​ປ້ອນ​ເຂົ້າ TV ປະ​ເພດ​ເຄື່ອງ​ຈູນ." + "ການ​ປັບ​ຊ່ອງ​ລົ້ມ​ເຫລວ" + "ບໍ່ພົບແອັບຯທີ່ໃຊ້ເພື່ອດຳເນີນການ." + "ຊ່ອງ​ແຫລ່ງ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ຖືກ​ເຊື່ອງ​ໄວ້​ແລ້ວ.\nກະ​ລຸ​ນາ​ເລືອກ​ຢ່າງ​ໜ້ອຍ​ນຶ່ງ​ຊ່ອງ​ເພື່ອ​ເບິ່ງ." + "ວິ​ດີ​ໂອບໍ່​ສາ​ມາດ​ເບິ່ງ​ໄດ້​ໂດຍບໍ່​ຄາດ​ຄິດ" + "ປຸ່ມ Back ແມ່ນ​ສຳ​ລັບ​ອຸ​ປະ​ກອນ​ທີ່​ເຊື່ອມ​ຕໍ່​ແລ້ວ. ໃຫ້​ກົດ​ປຸ່ມ Home ເພື່ອອອກ." + "ຊ່ອງ​ສົດ​ຕ້ອງ​ມີ​ການ​ອະ​ນຸ​ຍາດ​ເພື່ອ​ອ່ານ​ການ​ຈັດ​ລາຍ​ການ TV." + "ຕັ້ງຄ່າແຫຼ່ງຊ່ອງຂອງທ່ານ" + "ຊ່ອງອອກອາກາດສົດລວມເອົາປະສົບການຂອງຊ່ອງໂທລະພາບແບບເດີມເຂົ້າກັບຊ່ອງທີ່ໃຊ້ການສະຕຣີມທີ່ສະໜອງໃຫ້ໂດຍແອັບ. \n\nເລີ່ມຕົ້ນນຳໃຊ້ໂດຍການຕັ້ງຄ່າແຫຼ່ງຊ່ອງທີ່ຕິດຕັ້ງແລ້ວ. ຫຼື ຊອກຫາແອັບເພີ່ມເຕີມທີ່ໃຫ້ຊ່ອງອອກອາກາດສົດໃນ Google Play Store." + "ການບັນທຶກ ແລະ ການຕັ້ງເວລາ" + "10 ນາທີ" + "30 ນາທີ" + "1 ຊົ່ວໂມງ" + "3 ຊົ່ວໂມງ" + "ຫຼ້າສຸດ" + "ຕັ້ງເວລາໄວ້ແລ້ວ" + "ຊີຣີ" + "ອື່ນໆ" + "ບໍ່ສາມາດບັນທຶກຊ່ອງໄດ້." + "ບໍ່ສາມາດບັນທຶກລາຍການໄດ້." + "%1$s ຖືກຕັ້ງເວລາໃຫ້ບັນທຶກໄວ້ແລ້ວ" + "ກຳລັງບັນທຶກ %1$s ຈາກຕອນນີ້ຫາ %2$s" + "ຕາລາງແບບເຕັມ" + + %1$d ມື້ຖັດໄປ + %1$d ມື້ຖັດໄປ + + + %1$d ນາທີ + %1$d ນາທີ + + + %1$d ການບັນທຶກສຽງໃໝ່ + %1$d ການບັນທຶກສຽງໃໝ່ + + + %1$d ການບັນທຶກ + %1$d ການບັນທຶກ + + + %1$d ການບັນທຶກທີ່ຕັ້ງເວລາໄວ້ແລ້ວ + %1$d ການບັນທຶກທີ່ຕັ້ງເວລາໄວ້ແລ້ວ + + "ຍົກເລີກການບັນທຶກ" + "ຢຸດການບັນທຶກ" + "ເບິ່ງ" + "ຫຼິ້ນຕັ້ງແຕ່ຕົ້ນ" + "ສືບຕໍ່ຫຼິ້ນວິດີໂອ" + "​ລຶບ" + "ລຶບການບັນທຶກອອກ" + "ສືບຕໍ່" + "ຊີຊັນ %1$s" + "ເບິ່ງຕາຕາລາງ" + "ອ່ານເພີ່ມເຕີມ" + "ລຶບການບັນທຶກອອກ" + "ເລືອກເອັບພິໂສດທີ່ທ່ານຕ້ອງການລຶບອອກ. ພວກມັນຈະບໍ່ສາມາດກູ້ກັບຄືນມາໄດ້ຫຼັງຈາກທີ່ລຶບອອກໄປແລ້ວ." + "ບໍ່ມີການບັນທຶກໃຫ້ລຶບ." + "ເລືອກເອັບພິໂສດທີ່ເບິ່ງແລ້ວ" + "ເລືອກເອັບພິໂສດທັງໝົດ" + "ບໍ່ເລືອກເອັບພິໂສດທັງໝົດ" + "ເບິ່ງໄປແລ້ວ %1$d ນາທີຈາກທັງໝົດ %2$d ນາທີ" + "ເບິ່ງໄປແລ້ວ %1$d ວິນາທີຈາກທັງໝົດ %2$d ວິນາທີ" + "ຍັງບໍ່ເຄີຍເບິ່ງ" + + ລຶບເອັບພິໂສດ %1$d ຕອນຈາກທັງໝົດ %2$d ຕອນອອກແລ້ວ + ລຶບເອັບພິໂສດ %1$d ຕອນຈາກທັງໝົດ %2$d ຕອນອອກແລ້ວ + + "ຄວາມສຳຄັນ" + "ສູງສຸດ" + "ຕ່ຳສຸດ" + "ບໍ່. %1$d" + "​ຊ່ອງ" + "ອັນ​ໃດ​ກໍ່​ໄດ້" + "ເລືອກຄວາມສຳຄັນ" + "ເມື່ອມີໂປຣແກຣມໃຫ້ບັນທຶກຫຼາຍເກີນໄປໃນເວລາດຽວກັນ, ລະບົບຈະບັນທຶກລາຍການທີ່ມີຄວາມສຳຄັນສູງກວ່າເທົ່ານັ້ນ." + "ບັນທຶກ" + "ການບັນທຶກຕາມເວລາມີຄວາມສຳຄັນສູງສຸດ" + "ຢຸດ" + "ເບິ່ງຕາຕາລາງການບັນທຶກ" + "ນີ້ເປັນລາຍການດ່ຽວ" + "ຕອນນີ້ - %1$s" + "ທັງຊີຣີ…" + "ຕັ້ງເວລາຕໍ່ໄປໂລດ" + "ບັນທຶກອັນນີ້ແທນ" + "ຍົກເລີກການບັນທຶກນີ້" + "ເບິ່ງດຽວນີ້" + "ລຶບການບັນທຶກອອກ…" + "ບັນທຶກໄດ້" + "ຕັ້ງເວລາບັນທຶກແລ້ວ" + "ເກີດຄວາມຂັດແຍ່ງໃນການບັນທຶກ" + "ກຳລັງບັນທຶກ" + "ບັນທຶກບໍ່ສຳເລັດ" + "ກຳລັງອ່ານລາຍການ" + "ເບິ່ງການບັນທຶກຫຼ້າສຸດ" + "ບັນທຶກ %1$s ບໍ່ສົມບູນ." + "ບັນທຶກ %1$s ແລະ %2$s ບໍ່ສົມບູນ." + "ບັນທຶກ %1$s, %2$s ແລະ %3$s ບໍ່ສົມບູນ." + "ບັນທຶກ %1$s ບໍ່ສຳເລັດເນື່ອງຈາກບ່ອນຈັດເກັບຂໍ້ມູນບໍ່ພຽງພໍ." + "ບັນທຶກ %1$s ແລະ %2$s ບໍ່ສຳເລັດເນື່ອງຈາກບ່ອນຈັດເກັບຂໍ້ມູນບໍ່ພຽງພໍ." + "ບັນທຶກ %1$s, %2$s ແລະ %3$s ບໍ່ສຳເລັດເນື່ອງຈາກບ່ອນຈັດເກັບຂໍ້ມູນບໍ່ພຽງພໍ." + "DVR ຕ້ອງໃຊ້ບ່ອນຈັດເກັບຂໍ້ມູນເພີ່ມເຕີມ" + "ທ່ານຈະສາມາດບັນທຶກລາຍການດ້ວຍ DVR ໄດ້. ຢ່າງໃດກໍຕາມ, ອຸປະກອນຂອງທ່ານບໍ່ມີບ່ອນຈັດເກັບຂໍ້ມູນພຽງພໍໃຫ້ DVR ເຮັດວຽກໄດ້. ກະລຸນາເຊື່ອມຕໍ່ຫາໄດຣຟ໌ພາຍນອກທີ່ມີຂະໜາດ %1$dGB ຫຼືໃຫຍ່ກວ່າ ແລ້ວເຮັດຕາມຂັ້ນຕອນໃນການໃຊ້ມັນເປັນອຸປະກອນຈັດເກັບຂໍ້ມູນ." + "ບ່ອນຈັດເກັບຂໍ້ມູນບໍ່ພຽງພໍ" + "ຈະບໍ່ບັນທຶກລາຍການນີ້ເນື່ອງຈາກມີບ່ອນຈັດເກັບຂໍ້ມູນບໍ່ພຽງພໍ. ໃຫ້ລອງລຶບການບັນທຶກບາງອັນອອກກ່ອນ." + "ບໍ່ພົບບ່ອນຈັດເກັບຂໍ້ມູນ" + "ຢຸດການບັນທຶກໄວ້ບໍ?" + "ເນື້ອຫາທີ່ອັດໄວ້ແລ້ວຈະຖືກບັຍທຶກໄວ້." + "ການບັນທຶກ %1$s ຈະຖືກຢຸດໄວ້ເພາະມັນຂັດແຍ່ງກັບລາຍການນີ້. ເນື້ອຫາທີ່ບັນທຶກໄປແລ້ວຈະຖືກຈັດເກັບໄວ້." + "ຕັ້ງເວລາການບັນທຶກແລ້ວແຕ່ມີຂໍ້ຂັດແຍ່ງເກີດຂຶ້ນ" + "ເລີ່ມການບັນທຶກແລ້ວແຕ່ມີຂໍ້ຂັດແຍ່ງ" + "%1$s ຈະຖືກບັນທຶກໄວ້." + "ກຳລັງບັນທຶກ %1$s." + "ບາງສ່ວນຂອງ %1$s ຈະບໍ່ຖືກບັນທຶກ." + "ບາງສ່ວນຂອງ %1$s ແລະ %2$s ຈະບໍ່ຖືກບັນທຶກ." + "ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ອີກໜຶ່ງລາຍການຈະບໍ່ຖືກບັນທຶກ." + + ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ເພີ່ມເຕີມອີກ %3$d ລາຍການຈະບໍ່ມີການບັນທຶກໄວ້. + ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ເພີ່ມເຕີມອີກ %3$d ລາຍການຈະບໍ່ມີການບັນທຶກໄວ້. + + "ທ່ານຕ້ອງການບັນທຶກຫຍັງ?" + "ທ່ານຕ້ອງການບັນທຶກດົນປານໃດ?" + "ມີການຕັ້ງເວລາຢູ່ກ່ອນແລ້ວ" + "ລາຍການດຽວກັນນີ້ໄດ້ຕັ້ງເວລາໃຫ້ບັນທຶກໄວ້ແລ້ວເວລາ %1$s." + "ບັນທຶກໄປກ່ອນແລ້ວ" + "ລາຍການນີ້ຖືກບັນທຶກໄປກ່ອນແລ້ວ. ມັນສາມາດເບິ່ງໄດ້ໃນຫ້ອງສະໝຸດ DVR." + "ຕັ້ງເວລາບັນທຶກຊີຣີແລ້ວ" + + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. + + + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. %3$d ລາຍການຈະບໍ່ຖືກບັນທຶກເນື່ອງຈາກມີຂໍ້ຂັດແຍ່ງ. + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ມັນຈະບໍ່ຖືກບັນທຶກເນື່ອງຈາກມີຂໍ້ຂັດແຍ່ງ. + + + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ %3$d ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ %3$d ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + + + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ 1 ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ 1 ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + + + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ %3$d ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + ຕັ້ງເວລາບັນທຶກ %1$d ລາຍການສຳລັບ %2$s ແລ້ວ. ຈະບໍ່ບັນທຶກເອັບພິໂສດ %3$d ຕອນຂອງຊີຣີອື່ນເນື່ອງຈາກເວລາຂັດແຍ່ງກັນ. + + "ບໍ່ພົບໂປຣແກຣມທີ່ບັນທຶໄວ້." + "ການບັນທຶກທີ່ກ່ຽວຂ້ອງ" + + %1$d ການບັນທຶກ + %1$d ການບັນທຶກ + + " / " + "ລຶບ %1$s ອອກຈາກກຳນົດການບັນທຶກແລ້ວ" + "ຈະຖືກບັນທຶກບາງສ່ວນເນື່ອງຈາກມີຄວາມຂັດແຍ່ງໃນຕົວປັບຫາສັນຍານ." + "ຈະບໍ່ຖືກບັນທຶກເນື່ອງຈາກມີຄວາມຂັດແຍ່ງໃນຕົວປັບຫາສັນຍານ." + "ຍັງບໍ່ມີການຕັ້ງເວລາບັນທຶກເທື່ອ.\nທ່ານສາມາດຕັ້ງເວລາບັນທຶກໄດ້ຈາກຄຳແນະນຳລາຍການ." + + %1$d ການບັນທຶກທີ່ຂັດແຍ່ງກັນ + %1$d ການບັນທຶກທີ່ຂັດແຍ່ງກັນ + + "ການຕັ້ງຄ່າຊີຣີ" + "ເລີ່ມການບັນທຶກຊີຣີ" + "ຢຸດການບັນທຶກຊີຣີ" + "ຢຸດການບັນທຶກຊີຣີບໍ່?" + "ເອັບພິໂສດທີ່ບັນທຶກໄວ້ແລ້ວຈະຍັງຄົງສາມາດເບິ່ງໄດ້ໃນຫ້ອງສະໝຸດ DVR ຢູ່." + "ຢຸດ" + "ບໍ່ມີເອັບພິໂສດໃດທີ່ກຳລັງສາຍຢູ່ໃນຕອນນີ້." + "ຍັງບໍ່ມີເອບພິໂສດທີ່ສາມາດເບິ່ງໄດ້ເທື່ອ.\nພວກມັນຈະຖືກບັນທຶອຶກເມື່ອມີການສາຍ." + + (%1$d ນາທີ) + (%1$d ນາທີ) + + "ມື້ນີ້" + "ມື້ອື່ນ" + "ມື້ວານນີ້" + "%1$s ມື້ນີ້" + "%1$s ມື້ອື່ນ" + "ຄະແນນ" + "ລາຍການທີ່ບັນທຶກໄວ້ແລ້ວ" + diff --git a/res/values-lo-v23/strings.xml b/res/values-lo-v23/strings.xml deleted file mode 100644 index 53804594..00000000 --- a/res/values-lo-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "​ຊ່ອງ" - diff --git a/res/values-lo/arrays.xml b/res/values-lo/arrays.xml deleted file mode 100644 index 5e1c1e0c..00000000 --- a/res/values-lo/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "​ປົກ​ກະ​ຕິ" - "ເຕັມ" - "ຊູມ" - - - "​ທຸກ​ຊ່ອງ" - "ຄອບ​ຄົວ/ເດັກ​ນ້ອຍ" - "ກິລາ" - "ຊັອບປິງ" - "ຮູບເງົາ" - "ຄອມເມດີ" - "ການເດີນທາງ" - "ດຣາມ່າ" - "ການສຶກສາ" - "ສັດ" - "​ຂ່າວ" - "ເກມ" - "ສິ​ລະ​ປະ" - "ບັນເທີງ" - "ການດຳລົງຊີວິດ" - "ເພງ" - "ການ​ສະ​ແດງ" - "ເຕັກ​ໂນ​ໂລ​ຊີ/ວິ​ທະ​ຍາ​ສາດ" - - - "ຊ່ອງ​ອອກອາກາດສົດ" - "ວິທີງ່າຍດາຍໃນການຄົ້ນພົບເນື້ອຫາ" - "ດາວໂຫຼດແອັບ, ເອົາຊ່ອງເພີ່ມເຕີມ" - "ປັບແຕ່ງການຈັດວາງຊ່ອງຂອງທ່ານ" - - - "ເບິ່ງເນື້ອຫາຈາກແອັບຕ່າງໆ ເຊັ່ນວ່າ ການຮັບຊົມຊ່ອງຕ່າງໆໃນໂທລະພາບ." - "ຊອກເບິ່ງເນື້ອຫາຈາກແອັບຕ່າງໆດ້ວຍຄຳແນະນຳທີ່ຄຸ້ນເຄີຍ ແລະ ສ່ວນຕໍ່ປະສານທີ່ໃຊ້ງ່າຍ, \nຄືກັນກັບຊ່ອງຕ່າງໆໃນໂທລະພາບ." - "ເພີ່ມຊ່ອງເພີ່ມເຕີມ ໂດຍການຕິດຕັ້ງແອັບຕ່າງໆທີ່ໃຫ້ຊ່ອງອອກອາກາດສົດ. \nຊອກຫາແອັບທີ່ເຂົ້າກັນໄດ້ໃນ Google Play Store ໂດຍການໃຊ້ລິ້ງຢູ່ພາຍໃນເມນູໂທລະພາບ." - "ຕັ້ງຄ່າແຫຼ່ງຊ່ອງທີ່ຕິດຕັ້ງໃໝ່ຂອງທ່ານເພື່ອປັບແຕ່ງລາຍການຊ່ອງຂອງທ່ານ. \nເລືອກແຫຼ່ງຊ່ອງຢູ່ພາຍໃນເມນູການຕັ້ງຄ່າເພື່ອເລີ່ມຕົ້ນນຳໃຊ້." - - diff --git a/res/values-lo/rating_system_strings.xml b/res/values-lo/rating_system_strings.xml deleted file mode 100644 index 534b6a89..00000000 --- a/res/values-lo/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "ລາຍການອາດມີເນື້ອຫາທີ່ບໍ່ເໝາະສົມສຳລັບຜູ້ມີອາຍຸຕໍ່າກວ່າ 15 ປີ, ສະນັ້ນ ຄວນໃຫ້ຜູ້ປົກຄອງໃຫ້ຄຳແນະນຳ." - "ລາຍການອາດມີເນື້ອຫາທີ່ບໍ່ເໝາະສົມສຳລັບຜູ້ມີອາຍຸຕໍ່າກວ່າ 19 ປີ ດັ່ງນັ້ນຈຶ່ງບໍ່ເໝາະສົມສຳລັບຜູ້ທີ່ມີອາຍຸຕໍ່າກວ່າ 19 ປີ." - "ການ​ສົນ​ທະ​ນາ​ຊັກ​ຊວນ" - "ພາ​ສາ​ຫຍາບຄາຍ" - "ເນື້ອ​ໃນກ່ຽວ​ກັບ​ເພດ" - "​ຄວາມ​ຮຸນ​ແຮງ" - "ຄວາມ​ຮຸນ​ແຮງ​ເພີ້​ຝັນ" - "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​ໃຫ້​ເໝາະ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທຸກ​​ເພດ​ທຸກໄວ." - "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ອາ​ຍຸ 7 ​ປີ​ຂຶ້ນ​ໄປ." - "​ຜູ້ປົກ​ຄອງ​ສ່ວນ​ໃຫຍ່​​ເຫັນ​ວ່າ​ລາຍ​ການ​ນີ້​ເໝາະ​ສຳ​ລັບ​ທຸກ​ໄວ." - "​ລາຍ​ການ​ນີ້​ມີ​ເນື້ອ​ຫາ​ທີ່​ຜູ້ປົກ​ຄອງ​ອາດ​ເຫັນ​ວ່າບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທີ່​ມີ​ອາ​ຍຸ​ຕ່ຳ. ພໍ່​ແມ່​ບາງ​ຄົນ​ອາດ​ຈະ​ຕ້ອ​ງ​ການ​ເບິ່ງ​ມັນ​ກັບ​ເດັກ​ນ້ອຍ​ຂອງ​ເຂົາ​ເຈົ້າ." - "ລາຍ​ການ​ນີ້​ມີ​ເນື້ອ​ຫາ​ບາງ​ຢ່າງ​ທີ່​ຜູ້ປົກ​ຄອງ​ອາດ​ເຫັນ​ວ່າບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ທີ່​ມີ​ອາ​ຍຸ​ຕ່ຳ​ກວ່າ 14 ປີ." - "ລາຍ​ການ​ນີ້​ຖືກ​ອອກ​ແບບ​ມາ​​ໃຫ້​​ຜູ້ໃຫຍ່​ເບິ່ງໂດຍສະເພາະ ແລະ​ອາດບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ອາ​ຍຸ​ຕ່ຳ​ກວ່າ 17 ປີ." - "ການ​ຈັດ​ອັນ​ດັບຮູບ​ເງົາ" - "ຜູ້​ຊົມ​ທົ່ວ​ໄປ. ບໍ່​ມີ​ອັນ​ໃດທີ່ເດັກນ້ອຍເບິ່ງຈະເຮັດໃຫ້ພໍ່ແມ່ບໍ່ພໍໃຈ." - "ແນະ​ນຳ​ໃຫ້​ມີ​ຄຳ​ແນະ​ນຳ​ຂອງ​ຜູ້​ປົກ​ຄອງ. ອາດ​ຈະ​ມີບາງເນື້ອຫາ​ທີ່​ພໍ່​ແມ່ອາດ​ຈະ​ບໍ່​ມັກ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ." - "ໃຫ້​ພໍ່​ແມ່​ລະ​ມັດ​ລະ​ວັງ​ແທ້ໆ. ບາງເນື້ອຫາ​ອາດ​ຈະ​ບໍ່​ເໝາະ​ສົມ​ສຳ​ລັບ​ເດັກ​ນ້ອຍ​ກ່ອນ​ໄວ​ລຸ້ນ." - "ຈຳ​ກັດ​ສະ​ເພາະ, ມີເນື້ອຫາ​ສຳ​ລັບ​ຜູ້​ໃຫຍ່​ບາງ​ອັນ. ເຕືອນ​ໃຫ້​ພໍ່​ແມ່​ຮຽນ​ຮູ້​ເພີ່ມ​ເຕີມ​ກ່ຽວ​ກັບ​ຮູບ​ເງົາ​ກ່ອນ​ເອົາ​ເດັກ​ນ້ອຍ​ໄປ​ນຳ​ເຂົາ​ເຈົ້າ." - "ບໍ່ອະນຸຍາດສຳລັບຜູ້ທີ່ມີອາຍຸຕ່ຳກວ່າ 17 ປີ. ເປັນ​ຜູ້​ໃຫຍ່​ຈະ​ແຈ້ງ. ບໍ່​ອະ​ນຸ​ຍາດ​​ເດັກ​ນ້ອຍ." - diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml deleted file mode 100644 index 05aba1ce..00000000 --- a/res/values-lo/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "​ໂມ​ໂນ" - "ສະ​ເຕ​ຣິ​ໂອ" - "ຄວບ​ຄຸມ​ການ​ຫຼິ້ນ" - "ຊ່ອງ​ບໍ່ດົນ​ມານີ້" - "ໂຕເລືອກ​ໂທລະພາບ" - "​ຕົວ​ເລືອກ PIP" - "ຫຼິ້ນ​ການ​ຄວບ​ຄຸມ​ບໍ່​ມີ​ໃຫ້​ສຳ​ລັບ​ຊ່ອງ​ນີ້" - "ຫຼິ້ນ​ ຫລື​ຢຸດ​ຊົ່ວ​ຄາວ" - "ເລື່ອນ​ໄປ​ໜ້າ" - "​ຣີ​ວາຍກັບ" - "ຕໍ່​ໄປ" - "ກ່ອນໜ້າ" - "ຄຳ​ແນະນຳ​ລາຍການ" - "ມີ​ຊ່ອງ​ໃໝ່​ໃຫ້​ຢູ່" - "ເປີດ %1$s" - "ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" - "ໂໝດ​ການ​ສະແດງຜົນ" - "PIP" - "​ເປີດ" - "ປິດ" - "ຫຼາຍສຽງ" - "ເອົາຊ່ອງເພີ່ມເຕີມ" - "ການ​ຕັ້ງ​ຄ່າ" - "ທີ່​ມາ" - "ສະຫຼັບ" - "​ເປີດ" - "ປິດ" - "ສຽງ" - "ຫຼັກ" - "ໜ້າ​ຕ່າງ PIP" - "ຮູບ​ແບບ" - "ເບື້ອງ​ຂວາ​ທາງ​ລຸ່ມ" - "ເບື້ອງ​ຂວາ​ດ້ານ​ເທິງ" - "ເບື້ອງ​ຊ້າຍ​ດ້ານ​ເທິງ" - "ເບື້ອງ​ຊ້າຍທາງ​ລຸ່ມ" - "ເທື່ອ​ລະ​ດ້ານ" - "ຂະໜາດ" - "ໃຫຍ່" - "ນ້ອຍ" - "​ແຫຼ່ງ​ສັນ​ຍານ" - "TV (ເສົາ​ສັນ​ຍານ/ສາຍ​ເຄ​ເບິນ)" - "ບໍ່​ມີ​ຂໍ້​ມູນ​ລາຍ​ການ" - "ບໍ່ມີຂໍ້ມູນ" - "ຊ່ອງ​ທີ່​ຖືກບ​ລັອກ" - "ພາ​ສາ​ບໍ່​ຮູ້​ຈັກ" - "ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" - "ປິດ" - "ປັບແຕ່ງການຈັດຮູບແບບ" - "ກຳນົດ​ການຕັ້ງຄ່າ​ລະບົບ​ສຳລັບ​ຄຳ​ບັນຍາຍ​ແບບ​ປິດ" - "ໂໝດ​ສະແດງ​ຜົນ" - "ຫຼາຍ​ສຽງ" - "​ໂມ​ໂນ" - "ສະເຕຣິໂອ" - "ສຽງ​ຮອບ​ທິດ​ທາງ 5.1" - "ສຽງ​ຮອບ​ທິດ​ທາງ 7.1" - "%d ​ຊ່ອງ" - "ປັບແຕ່ງ​ລາຍ​ຊື່​ຊ່ອງ" - "ເລືອກກຸ່ມ" - "ບໍ່​ເລືອກ​ກຸ່ມ" - "ຈັດກຸ່ມຕາມ" - "ທີ່​ມາ​ຂອງ​ຊ່ອງ" - "HD/SD" - "HD" - "SD" - "ຈັດກຸ່ມຕາມ" - "ໂປ​ຣ​ແກ​ຣມ​ນີ້​ຖືກບ​ລັອກ​ແລ້ວ" - "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ %1$s" - "ອິນ​ພຸດ​ນີ້ບໍ່​ຮອງ​ຮັບ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ" - "ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ​ສຳ​ລັບ \'%s\' ໄດ້" - "ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ຕັ້ງ​ຄ່າ​ທົ່ວ​ລະ​ບົບ​ສຳ​ລັບ​ຄຳ​ບັນ​ຍາຍ​ແບບ​ປິດ​ໄດ້." - - ເພີ່ມ %1$d ຊ່ອງແລ້ວ - ເພີ່ມ %1$d ຊ່ອງແລ້ວ - - "ບໍ່​ໄດ້​ເພີ່ມ​ຊ່ອງ" - "​ທູນ​ເນີ" - "ການ​ຄວບຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ" - "ເປີດ" - "ປິດ" - "ຊ່ອງ​ຖືກ​ບລັອກ​ແລ້ວ" - "​ບລັອກ​​ທັງ​​ໝົດ" - "ປົດ​ບລັອກ​ທັງ​ໝົດ" - "​ຊ່ອງ​ທີ່​ຖືກ​ເຊື່ອງ" - "ການ​ຈຳກັດ​ລາຍການ" - "​ປ່ຽນ​ PIN" - "​ລະ​ບົບ​ການ​ຈັດ​ປະ​ເພດ" - "​​​ການ​ຈັດ​ປະ​ເພດ" - "ເບິ່ງ​ລະບົບ​ການ​ຈັດ​ປະເພດ​ທັງ​ໝົດ" - "ປະ​ເທດ​ອື່ນໆ" - "ບໍ່ມີ" - "ບໍ່ມີ" - "ບໍ່ມີ" - "ຈຳກັດສູງ" - "ຈຳກັດປານກາງ" - "ຈຳກັດຕ່ຳ" - "ກຳນົດເອງ" - "ເນື້ອຫາ​ເໝາະສົມ​ສຳລັບ​ເດັກນ້ອຍ" - "ເນື້ອຫາ​ເໝາະ​ສຳລັບ​ເດັກ​ທີ່​ບໍ່​ນ້ອຍ​ຫຼາຍ" - "​ເນື້ອ​ຫາ​ເໝາະ​ສົມ​ສຳ​ລັບ​ໄວ​ຮຸ່ນ" - "ຈຳ​ກັດແບບ​​ລະ​ບຸ​ເອງ" - - - "%1$s ແລະປະເພດຍ່ອຍ" - "ປະເພດຍ່ອຍ" - "ປ້ອນ​ລະ​ຫັດ​ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້" - "​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້" - "ລາຍການນີ້ຖືກຈັດປະເພດເປັນ %1$s. ກະລຸນາໃສ່ລະຫັດ PIN ຂອງທ່ານເພື່ອເບິ່ງລາຍການນີ້" - "​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" - "ເພື່ອ​ຕັ້ງ​ຄ່າ​ຄວບ​ຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ, ກະລຸ​ນາຕັ້ງ​ລະ​ຫັດ PIN" - "ໃສ່ລະຫັດ PIN ໂຕໃຫມ່" - "ຢືນຢັນ​ລະຫັດ PIN ຂອງທ່ານ" - "ປ້ອນ​ລະ​ຫັດ PIN ປັດ​ຈຸ​ບັນ​ຂອງ​ທ່ານ" - - ທ່ານປ້ອນ PIN ບໍ່ຖືກຕ້ອງ 5 ເທື່ອແລ້ວ.\nລອງອີກເທື່ອໃນ %1$d ວິນາທີ. - ທ່ານປ້ອນ PIN ບໍ່ຖືກຕ້ອງ 5 ເທື່ອແລ້ວ.\nລອງອີກເທື່ອໃນ %1$d ວິນາທີ. - - "ລະຫັດ PIN ນັ້ນບໍ່​ຖືກ​ຕ້ອງ, ກະ​ລຸ​ນາລອງ​ໃຫມ່​ອີກ​ຄັ້ງ." - "ລະ​ຫັດ PIN ບໍ່​ກົງ​ກັນ, ກະ​ລຸ​ນາ​ລອງ​ໃໝ່." - "ການ​ຕັ້ງ​ຄ່າ" - "ປັບແຕ່ງ​ລາຍ​ຊື່​ຊ່ອງ" - "ເລືອກ​ຊ່ອງ​ສຳລັບການ​ແນະນຳລາຍການ​ຂອງ​ທ່ານ" - "ແຫຼ່ງທີ່ມາ​ຂອງ​ຊ່ອງ" - "ມີ​ຊ່ອງ​ໃໝ່​" - "ການ​ຄວບຄຸມ​ຂອງພໍ່ແມ່" - "​ໃບ​ອະ​ນຸ​ຍາດ​ Open source" - "​ໃບ​ອະ​ນຸ​ຍາດ​ແຫຼ່ງ​ເປີດ" - "ເວີຊັນ" - "​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້, ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" - "​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" - "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ​ %1$s.\n​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ" - "ເພື່ອເບິ່ງຊ່ອງນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." - "ເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." - "ໂປຣແກຣມນີ້ຖືກຈັດປະເພດ %1$s.\nເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ." - "​ລາຍ​ການ​ຖືກບລັອກ" - "​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ %1$s" - "ສຽງເທົ່ານັ້ນ" - "ສັນຍານອ່ອນ" - "ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ" - - ບໍ່ສາມາດຫຼິ້ນຊ່ອງນີ້ໄດ້ຈົນກວ່າຈະຮອດ %1$s ເນື່ອງຈາກກຳລັງບັນທຶກຊ່ອງອື່ນຢູ່. \n\nກົດຂວາເພື່ອປັບແຕ່ງເວລາການບັນທຶກ. - ບໍ່ສາມາດຫຼິ້ນຊ່ອງນີ້ໄດ້ຈົນກວ່າຈະຮອດ %1$s ເນື່ອງຈາກກຳລັງບັນທຶກຊ່ອງອື່ນຢູ່. \n\nກົດຂວາເພື່ອປັບແຕ່ງເວລາການບັນທຶກ. - - "ບໍ່ມີຫົວຂໍ້" - "​ຊ່ອງ​ຖືກບລັອກ" - "ໃໝ່" - "ແຫຼ່ງທີ່ມາຂອງຊ່ອງ" - - %1$d ຊ່ອງ - %1$d ຊ່ອງ - - "ບໍ່​ມີ​ຊ່ອງ​ພ້ອມ​ໃຊ້​ງານ​ໄດ້​ຢູ່" - "ໃໝ່" - "ບໍ່ໄດ້​ຕິດ​ຕັ້ງ" - "ເບິ່ງແຫຼ່ງທີ່ມາຂອງຊ່ອງເພີ່ມເຕີມ" - "ທ່ອງເບິ່ງແອັບທີ່ໃຫ້ຊ່ອງຖ່າຍທອດສົດ" - "ມີແຫຼ່ງ​ຊ່ອງ​ໃໝ່" - "ແຫຼ່ງຊ່ອງໃໝ່ມີຊ່ອງຕ່າງໆໄວ້ໃຫ້ບໍລິການ.\nຕັ້ງພວກມັນດຽວນີ້, ຫຼືເຮັດສິ່ງນີ້ໃນພາຍຫຼັງໃນການຕັ້ງຄ່າແຫຼ່ງຊ່ອງ." - "ຕັ້ງດຽວນີ້" - "ຕົກລົງ, ເຂົ້າ​ໃຈ​ແລ້ວ" - - - "ກົດ SELECT"" ເພື່ອ​ເຂົ້າ​ຫາ​ເມ​ນູ TV." - "ບໍ່​ພົບການ​ປ້ອນ​ເຂົ້າ TV" - "ບໍ່​ສາ​ມາດ​ຊອກ​ຫາການ​ປ້ອນ​ເຂົ້າ TV ​ໄດ້" - "ບໍ່​ຮອງ​ຮັບ PIP" - "ບໍ່ມີການຮັບສັນຍານທີ່ສາມາດສະແດງໄດ້ດ້ວຍຮູບພາບຊ້ອນຮູບພາບ PIP" - "ປະ​ເພດ​ເຄື່ອງ​ຈູນ​ບໍ່​ເໝາະ​ສົມ; ກະ​ລຸ​ນາ​ເປີດ​ໃຊ້​ແອັບ​ຊ່ອງ​ສົດ​ສຳ​ລັບ​ການ​ປ້ອນ​ເຂົ້າ TV ປະ​ເພດ​ເຄື່ອງ​ຈູນ." - "ການ​ປັບ​ຊ່ອງ​ລົ້ມ​ເຫລວ" - "ບໍ່ພົບແອັບຯທີ່ໃຊ້ເພື່ອດຳເນີນການ." - "ຊ່ອງ​ແຫລ່ງ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ຖືກ​ເຊື່ອງ​ໄວ້​ແລ້ວ.\nກະ​ລຸ​ນາ​ເລືອກ​ຢ່າງ​ໜ້ອຍ​ນຶ່ງ​ຊ່ອງ​ເພື່ອ​ເບິ່ງ." - "ວິ​ດີ​ໂອບໍ່​ສາ​ມາດ​ເບິ່ງ​ໄດ້​ໂດຍບໍ່​ຄາດ​ຄິດ" - "ປຸ່ມ Back ແມ່ນ​ສຳ​ລັບ​ອຸ​ປະ​ກອນ​ທີ່​ເຊື່ອມ​ຕໍ່​ແລ້ວ. ໃຫ້​ກົດ​ປຸ່ມ Home ເພື່ອອອກ." - "ຊ່ອງ​ສົດ​ຕ້ອງ​ມີ​ການ​ອະ​ນຸ​ຍາດ​ເພື່ອ​ອ່ານ​ການ​ຈັດ​ລາຍ​ການ TV." - "ຕັ້ງຄ່າແຫຼ່ງຊ່ອງຂອງທ່ານ" - "ຊ່ອງອອກອາກາດສົດລວມເອົາປະສົບການຂອງຊ່ອງໂທລະພາບແບບເດີມເຂົ້າກັບຊ່ອງທີ່ໃຊ້ການສະຕຣີມທີ່ສະໜອງໃຫ້ໂດຍແອັບ. \n\nເລີ່ມຕົ້ນນຳໃຊ້ໂດຍການຕັ້ງຄ່າແຫຼ່ງຊ່ອງທີ່ຕິດຕັ້ງແລ້ວ. ຫຼື ຊອກຫາແອັບເພີ່ມເຕີມທີ່ໃຫ້ຊ່ອງອອກອາກາດສົດໃນ Google Play Store." - "ການບັນທຶກ ແລະ ການຕັ້ງເວລາ" - "10 ນາທີ" - "30 ນາທີ" - "1 ຊົ່ວໂມງ" - "3 ຊົ່ວໂມງ" - "ຫຼ້າສຸດ" - "ຕັ້ງເວລາໄວ້ແລ້ວ" - "ຊີຣີ" - "ອື່ນໆ" - "ບໍ່ສາມາດບັນທຶກຊ່ອງໄດ້." - "ບໍ່ສາມາດບັນທຶກລາຍການໄດ້." - "%1$s ຖືກຕັ້ງເວລາໃຫ້ບັນທຶກໄວ້ແລ້ວ" - "ກຳລັງບັນທຶກ %1$s ຈາກຕອນນີ້ຫາ %2$s" - "ຕາລາງແບບເຕັມ" - - %1$d ມື້ຖັດໄປ - %1$d ມື້ຖັດໄປ - - - %1$d ນາທີ - %1$d ນາທີ - - - %1$d ການບັນທຶກສຽງໃໝ່ - %1$d ການບັນທຶກສຽງໃໝ່ - - - %1$d ການບັນທຶກ - %1$d ການບັນທຶກ - - - %1$d ການບັນທຶກທີ່ຕັ້ງເວລາໄວ້ແລ້ວ - %1$d ການບັນທຶກທີ່ຕັ້ງເວລາໄວ້ແລ້ວ - - "ເບິ່ງ" - "ຫຼິ້ນຕັ້ງແຕ່ຕົ້ນ" - "ສືບຕໍ່ຫຼິ້ນວິດີໂອ" - "​ລຶບ" - "ລຶບການບັນທຶກອອກ" - "ສືບຕໍ່" - "ຊີຊັນ %1$s" - "ເບິ່ງຕາຕາລາງ" - "ອ່ານເພີ່ມເຕີມ" - "ລຶບການບັນທຶກອອກ" - "ເລືອກເອັບພິໂສດທີ່ທ່ານຕ້ອງການລຶບອອກ. ພວກມັນຈະບໍ່ສາມາດກູ້ກັບຄືນມາໄດ້ຫຼັງຈາກທີ່ລຶບອອກໄປແລ້ວ." - "ບໍ່ມີການບັນທຶກໃຫ້ລຶບ." - "ເລືອກເອັບພິໂສດທີ່ເບິ່ງແລ້ວ" - "ເລືອກເອັບພິໂສດທັງໝົດ" - "ບໍ່ເລືອກເອັບພິໂສດທັງໝົດ" - "ເບິ່ງໄປແລ້ວ %1$d ນາທີຈາກທັງໝົດ %2$d ນາທີ" - "ເບິ່ງໄປແລ້ວ %1$d ວິນາທີຈາກທັງໝົດ %2$d ວິນາທີ" - "ຍັງບໍ່ເຄີຍເບິ່ງ" - - ລຶບເອັບພິໂສດ %1$d ຕອນຈາກທັງໝົດ %2$d ຕອນອອກແລ້ວ - ລຶບເອັບພິໂສດ %1$d ຕອນຈາກທັງໝົດ %2$d ຕອນອອກແລ້ວ - - "ຄວາມສຳຄັນ" - "ສູງສຸດ" - "ຕ່ຳສຸດ" - "ບໍ່. %1$d" - "​ຊ່ອງ" - "ອັນ​ໃດ​ກໍ່​ໄດ້" - "ເລືອກຄວາມສຳຄັນ" - "ເມື່ອມີໂປຣແກຣມໃຫ້ບັນທຶກຫຼາຍເກີນໄປໃນເວລາດຽວກັນ, ລະບົບຈະບັນທຶກລາຍການທີ່ມີຄວາມສຳຄັນສູງກວ່າເທົ່ານັ້ນ." - "ບັນທຶກ" - "ການບັນທຶກຕາມເວລາມີຄວາມສຳຄັນສູງສຸດ" - "​ຍົກເລີກ" - "ຍົກເລີກ" - "ລືມ" - "ຢຸດ" - "ເບິ່ງຕາຕາລາງການບັນທຶກ" - "ນີ້ເປັນລາຍການດ່ຽວ" - "ຕອນນີ້ - %1$s" - "ທັງຊີຣີ…" - "ຕັ້ງເວລາຕໍ່ໄປໂລດ" - "ບັນທຶກອັນນີ້ແທນ" - "ຍົກເລີກການບັນທຶກນີ້" - "ເບິ່ງດຽວນີ້" - "ບັນທຶກໄດ້" - "ຕັ້ງເວລາບັນທຶກແລ້ວ" - "ເກີດຄວາມຂັດແຍ່ງໃນການບັນທຶກ" - "ກຳລັງບັນທຶກ" - "ບັນທຶກບໍ່ສຳເລັດ" - "ກຳລັງອ່ານເນື້ອຫາລາຍການເພື່ອຕັ້ງເວລາບັນທຶກ" - "ກຳລັງອ່ານລາຍການ" - - - "DVR ຕ້ອງໃຊ້ບ່ອນຈັດເກັບຂໍ້ມູນເພີ່ມເຕີມ" - "ທ່ານຈະສາມາດບັນທຶກລາຍການດ້ວຍ DVR ໄດ້. ຢ່າງໃດກໍຕາມ, ອຸປະກອນຂອງທ່ານບໍ່ມີບ່ອນຈັດເກັບຂໍ້ມູນພຽງພໍໃຫ້ DVR ເຮັດວຽກໄດ້. ກະລຸນາເຊື່ອມຕໍ່ຫາໄດຣຟ໌ພາຍນອກທີ່ມີຂະໜາດ %1$sGB ຫຼືໃຫຍ່ກວ່າ ແລ້ວເຮັດຕາມຂັ້ນຕອນໃນການໃຊ້ມັນເປັນອຸປະກອນຈັດເກັບຂໍ້ມູນ." - "ບໍ່ພົບບ່ອນຈັດເກັບຂໍ້ມູນ" - "ບໍ່ພົບບ່ອນຈັດເກັບຂໍ້ມູນບາງອັນທີ່ໃຊ້ໂດຍ DVR. ກະລຸນາເຊື່ອມຕໍ່ໄດຣຟ໌ພາຍນອກທີ່ທ່ານໃຊ້ກ່ອນຈະເປີດໃຊ້ DVR ຄືນໃໝ່. ຫຼືອີກວິທີໜຶ່ງ, ທ່ານສາມາດເລືອກໃຫ້ລືມບ່ອນຈັດເກັບຂໍ້ມູນດັ່ງກ່າວໄດ້ຫາກມັນບໍ່ມີໃຫ້ໃຊ້ອີກຕໍ່ໄປແລ້ວ." - "ລືມການຈັດເກັບຂໍ້ມູນບໍ?" - "ທ່ານຈະສູນເສຍເນື້ອຫາ ແລະ ການຕັ້ງເວລາທັງໝົດທີ່ທ່ານບັນທຶກໄວ້ແລ້ວ." - "ຢຸດການບັນທຶກໄວ້ບໍ?" - "ເນື້ອຫາທີ່ອັດໄວ້ແລ້ວຈະຖືກບັຍທຶກໄວ້." - - - "ຕັ້ງເວລາການບັນທຶກແລ້ວແຕ່ມີຂໍ້ຂັດແຍ່ງເກີດຂຶ້ນ" - "ເລີ່ມການບັນທຶກແລ້ວແຕ່ມີຂໍ້ຂັດແຍ່ງ" - "%1$s ຈະຖືກບັນທຶກໄວ້." - "ກຳລັງບັນທຶກ %1$s." - "ບາງສ່ວນຂອງ %1$s ຈະບໍ່ຖືກບັນທຶກ." - "ບາງສ່ວນຂອງ %1$s ແລະ %2$s ຈະບໍ່ຖືກບັນທຶກ." - "ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ອີກໜຶ່ງລາຍການຈະບໍ່ຖືກບັນທຶກ." - - ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ເພີ່ມເຕີມອີກ %3$d ລາຍການຈະບໍ່ມີການບັນທຶກໄວ້. - ບາງສ່ວນຂອງ %1$s, %2$s ແລະ ເພີ່ມເຕີມອີກ %3$d ລາຍການຈະບໍ່ມີການບັນທຶກໄວ້. - - "ທ່ານຕ້ອງການບັນທຶກຫຍັງ?" - "ທ່ານຕ້ອງການບັນທຶກດົນປານໃດ?" - "ມີການຕັ້ງເວລາຢູ່ກ່ອນແລ້ວ" - "ລາຍການດຽວກັນນີ້ໄດ້ຕັ້ງເວລາໃຫ້ບັນທຶກໄວ້ແລ້ວເວລາ %1$s." - "ບັນທຶກໄປກ່ອນແລ້ວ" - "ລາຍການນີ້ຖືກບັນທຶກໄປກ່ອນແລ້ວ. ມັນສາມາດເບິ່ງໄດ້ໃນຫ້ອງສະໝຸດ DVR." - - - - - - - - - "ບໍ່ພົບໂປຣແກຣມທີ່ບັນທຶໄວ້." - "ການບັນທຶກທີ່ກ່ຽວຂ້ອງ" - "(ບໍ່ມີຄຳອະທິບາຍລາຍການ)" - - %1$d ການບັນທຶກ - %1$d ການບັນທຶກ - - " / " - "ລຶບ %1$s ອອກຈາກກຳນົດການບັນທຶກແລ້ວ" - "ຈະຖືກບັນທຶກບາງສ່ວນເນື່ອງຈາກມີຄວາມຂັດແຍ່ງໃນຕົວປັບຫາສັນຍານ." - "ຈະບໍ່ຖືກບັນທຶກເນື່ອງຈາກມີຄວາມຂັດແຍ່ງໃນຕົວປັບຫາສັນຍານ." - "ຍັງບໍ່ມີການຕັ້ງເວລາບັນທຶກເທື່ອ.\nທ່ານສາມາດຕັ້ງເວລາບັນທຶກໄດ້ຈາກຄຳແນະນຳລາຍການ." - - %1$d ການບັນທຶກທີ່ຂັດແຍ່ງກັນ - %1$d ການບັນທຶກທີ່ຂັດແຍ່ງກັນ - - "ການຕັ້ງຄ່າຊີຣີ" - "ເລີ່ມການບັນທຶກຊີຣີ" - "ຢຸດການບັນທຶກຊີຣີ" - "ຢຸດການບັນທຶກຊີຣີບໍ່?" - "ເອັບພິໂສດທີ່ບັນທຶກໄວ້ແລ້ວຈະຍັງຄົງສາມາດເບິ່ງໄດ້ໃນຫ້ອງສະໝຸດ DVR ຢູ່." - "ຢຸດ" - "ຍັງບໍ່ມີເອບພິໂສດທີ່ສາມາດເບິ່ງໄດ້ເທື່ອ.\nພວກມັນຈະຖືກບັນທຶອຶກເມື່ອມີການສາຍ." - - (%1$d ນາທີ) - (%1$d ນາທີ) - - "ມື້ນີ້" - "ມື້ອື່ນ" - "ມື້ວານນີ້" - "%1$s ມື້ນີ້" - "%1$s ມື້ອື່ນ" - "ຄະແນນ" - diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 88afdec0..357b8030 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -20,9 +20,8 @@ "monofon." "stereof." "Leidimo valdikliai" - "Naujausi kanal." + "Kanalai" "TV parinktys" - "PIP parinktys" "Leidimo valdikliai negalimi šiame kanale" "Leisti arba pristabdyti" "Sukti pirmyn" @@ -35,33 +34,15 @@ "Subtitrai" "Rodymo režimas" "PIP" - "Įjungta" - "Išjungta" "Keli garso įr." "Gauti daug. kan." "Nustatymai" - "Šaltinis" - "Sukeisti" - "Įjungta" - "Išjungta" - "Garsas" - "Pagrindinis" - "PIP langas" - "Išdėstymas" - "Apač. dešinėje" - "Virš. dešinėje" - "Viršuje kairėje" - "Apač. kairėje" - "Šalia" - "Dydis" - "Didelis" - "Mažas" - "Įvesties šaltinis" "TV (antena / kabelis)" "Nėra informacijos apie programą" "Informacijos nėra." "Užblokuotas kanalas" - "Nežinoma kalba" + "Nežinoma kalba" + "Subtitrai %1$d" "Subtitrai" "Išjungti" "Tinkinti formatavimą" @@ -83,6 +64,7 @@ "SD" "Grupuoti pagal" "Ši programa yra užblokuota" + "Ši programa neįvertinta" "Ši programa įvertinta kaip %1$s." "Įvestis nepalaiko automatinio nuskaitymo" "Nepavyko paleisti „%s“ automatinio nuskaitymo" @@ -94,7 +76,6 @@ Pridėta %1$d kanalų "Nėra pridėtų kanalų" - "Radijo imtuvas" "Tėvų kontrolė" "Įjungti" "Išjungti" @@ -110,6 +91,8 @@ "Kitos šalys" "Nėra" "Nėra" + "Neįvertinta" + "Blokuoti neįvert. programas" "Nėra" "Dideli apribojimai" "Vid. apribojimai" @@ -126,6 +109,7 @@ "Įveskite PIN kodą, kad galėt. žiūrėti šį kanalą" "Įveskite PIN kodą, kad galėt. žiūrėti šią programą" "Ši programa įvertinta %1$s. Įveskite PIN kodą, kad galėtumėte žiūrėti šią programą" + "Ši programa neįvertinta. Įveskite PIN kodą, kad galėtumėte žiūrėti šią programą" "Įveskite PIN kodą" "Jei norite nustatyti tėvų valdiklius, sukurkite PIN kodą" "Įveskite naują PIN" @@ -139,22 +123,31 @@ "Tas PIN kodas buvo netinkamas. Bandykite dar kartą." "Bandykite dar kartą, PIN kodas neatitinka" + "Įveskite pašto kodą." + "Tiesioginių kanalų programa naudos pašto kodą, kad galėtų pateikti išsamų TV kanalų programų vadovą." + "Įveskite pašto kodą" + "Netinkamas pašto kodas" "Nustatymai" "Tinkinti kanalų sąrašą" "Pasirinkite programų vadovo kanalus" "Kanalų šaltiniai" "Galimi nauji kanalai" "Tėvų kontrolė" + "Laiko poslinkis" + "Įrašykite žiūrėdami, kad galėtumėte pristabdyti ar atgal atsukti tiesiogines programas.\nĮspėjimas: tai darant vidinės atminties veikimo laiką dėl intensyvaus jos naudojimo." "Atvirojo šaltinio licencijos" - "Atvirojo šaltinio licencijos" + "Siųsti atsiliepimą" "Versija" "Jei norite žiūrėti šį kanalą, paspauskite „Tinkamas“ ir įveskite PIN kodą" "Jei norite žiūrėti šią programą, paspauskite „Tinkama“ ir įveskite PIN kodą" + "Ši programa neįvertinta.\nJei norite žiūrėti šią programą, paspauskite „Tinkama“ ir įveskite PIN kodą" "Ši programa įvertinta kaip %1$s.\nJei norite žiūrėti šią programą, paspauskite „Tinkama“ ir įveskite PIN kodą" "Jei norite žiūrėti šį kanalą, naudokite numatytąją tiesioginės TV programą." "Jei norite žiūrėti šią programą, naudokite numatytąją tiesioginės TV programą." + "Ši programa neįvertinta.\nJei norite žiūrėti šią programą, naudokite numatytąją tiesioginės TV programą." "Ši programa įvertinta %1$s.\nJei norite žiūrėti šią programą, naudokite numatytąją tiesioginės TV programą." "Programa užblokuota" + "Ši programa neįvertinta" "Ši programa įvertinta kaip %1$s." "Tik garso įrašas" "Silpnas signalas" @@ -189,8 +182,6 @@ "Paspauskite PASIRINKTI,"" kad pasiektumėte TV meniu." "Nerasta jokių TV įvesčių" "Nepavyksta rasti TV įvesties" - "PIP nepalaikoma" - "Nėra galimos įvesties, kurią galima rodyti kartu su PIP" "Netinkamas derintuvo tipas. Paleiskite derintuvo tipo TV įvestį, skirtą programai „Live TV“." "Derinimas nepavyko" "Nerasta jokių programų šiam veiksmui apdoroti." @@ -244,6 +235,8 @@ %1$d suplanuoto įrašo %1$d suplanuotų įrašų + "Atšaukti įrašymą" + "Sustabdyti įrašymą" "Žiūrėti" "Leisti nuo pradžios" "Tęsti ir leisti" @@ -278,9 +271,6 @@ "Kai vienu metu bus per daug įrašomų programų, bus įrašomos tik aukštesnio prioriteto programos." "Išsaugoti" "Vienkartinis įrašymo veiksmas turi didžiausią prioritetą" - "Atšaukti" - "Atšaukti" - "Pamiršti" "Sustabdyti" "Žr. įrašymo tvarkaraštį" "Ši viena programa" @@ -290,25 +280,29 @@ "Vietoj tos įrašyti šią" "Atšaukti šį įrašymą" "Žiūrėti dabar" + "Ištrinti įrašus…" "Galima įrašyti" "Įrašymas suplanuotas" "Įrašo nesuderinamumas" "Įrašoma" "Įrašyti nepavyko" - "Nuskaitomos programos, kad būtų sukurti įrašymo tvarkaraščiai" - "Nuskaitomos programos" - + "Nuskaitomos programos" + + "„%1$s“ įrašymo procesas nebaigtas." + "„%1$s“ ir „%2$s“ įrašymo procesas nebaigtas." + "„%1$s“, „%2$s“ ir „%3$s“ įrašymo procesas nebaigtas." + "„%1$s“ įrašymo procesas nebaigtas, nes nepakanka saugyklos vietos." + "„%1$s“ ir „%2$s“ įrašymo procesas nebaigtas, nes nepakanka saugyklos vietos." + "„%1$s“, „%2$s“ ir „%3$s“ įrašymo procesas nebaigtas, nes nepakanka saugyklos vietos." "Norint naudoti DVR reikia daugiau saugyklos vietos" - "Naudodami DVR galėsite įrašyti programas. Tačiau dabar įrenginyje nepakanka saugyklos vietos, kad DVR veiktų. Prijunkite išorinį diską, kuris yra %1$s GB arba didesnis, ir atlikite veiksmus, kad formatuotumėte jį kaip įrenginio saugyklą." + "Naudodami DVR galėsite įrašyti programas. Tačiau dabar įrenginyje nepakanka saugyklos vietos, kad DVR veiktų. Prijunkite išorinį diską, kuris yra %1$d GB arba didesnis, ir atlikite veiksmus, kad formatuotumėte jį kaip įrenginio saugyklą." + "Trūksta saugyklos vietos" + "Ši programa nebus įrašyta, nes nepakanka saugyklos vietos. Pabandykite ištrinti kai kuriuos esamus įrašus." "Nėra saugyklos" - "Nėra kai kurių saugyklų, kurias naudoja DVR. Prijunkite anksčiau naudotą išorinį diską, kad iš naujo įgalintumėte DVR. Taip pat galite pasirinkti pamiršti saugyklą, jei ji nebepasiekiama." - "Pamiršti saugyklą?" - "Visas įrašytas turinys ir tvarkaraščiai bus prarasti." "Sustabdyti įrašymą?" "Įrašytas turinys bus išsaugotas." - - + "„%1$s“ įrašas bus sustabdytas dėl prieštaravimų su šia programa. Įrašytas turinys bus išsaugotas." "Įrašymas suplanuotas, bet yra neatitikimų" "Įrašymo procesas pradėtas, bet yra neatitikimų" "Programa „%1$s“ bus įrašyta." @@ -328,17 +322,39 @@ "Ta pati programa jau suplanuota įrašyti %1$s." "Jau įrašyta" "Ši programa jau buvo įrašyta. Ji pasiekiama DVR bibliotekoje." - - - - - - - - + "Suplanuotas serijos įrašas" + + Suplanuotas %1$d%2$s“ įrašas. + Suplanuoti %1$d%2$s“ įrašai. + Suplanuota %1$d%2$s“ įrašo. + Suplanuota %1$d%2$s“ įrašų. + + + Suplanuotas %1$d%2$s“ įrašas. %3$d iš jų nebus įraš. dėl nesuderinamo tvarkaraščio. + Suplanuoti %1$d%2$s“ įrašai. %3$d iš jų nebus įraš. dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašo. %3$d iš jų nebus įraš. dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašų. %3$d iš jų nebus įraš. dėl nesuderinamo tvarkaraščio. + + + Suplanuotas %1$d%2$s“ įrašas. Šio ir kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuoti %1$d%2$s“ įrašai. Šio ir kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašo. Šio ir kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašų. Šio ir kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + + + Suplanuotas %1$d%2$s“ įrašas. 1 kitų serialų serija nebus įrašyta dėl nesuderinamo tvarkaraščio. + Suplanuoti %1$d%2$s“ įrašai. 1 kitų serialų serija nebus įrašyta dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašo. 1 kitų serialų serija nebus įrašyta dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašų. 1 kitų serialų serija nebus įrašyta dėl nesuderinamo tvarkaraščio. + + + Suplanuotas %1$d%2$s“ įrašas. Kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuoti %1$d%2$s“ įrašai. Kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašo. Kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + Suplanuota %1$d%2$s“ įrašų. Kitų serialų serijos (%3$d) nebus įrašytos dėl nesuderinamo tvarkaraščio. + "Įrašyta programa nerasta." "Susiję įrašai" - "(Nėra laidos aprašo)" %1$d įrašas %1$d įrašai @@ -362,6 +378,8 @@ "Sustabdyti serijos įrašymą?" "Įrašytos serijos bus pasiekiamos DVR bibliotekoje." "Sustabdyti" + + "Nėra jokių serijų.\nSerijos bus įrašytos, kai jų bus." (%1$d minutė) @@ -375,4 +393,5 @@ "%1$s šiandien" "%1$s rytoj" "Įvertinimas" + "Įrašytos programos" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 00021bca..3aa06a61 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Atskaņošanas vadīklas" - "Nesenie kanāli" + "Kanāli" "TV iespējas" - "PIP opcijas" "Šim kanālam nav pieejamas atskaņošanas vadīklas." "Atskaņot vai pauzēt" "Pārtīt uz priekšu" @@ -35,33 +34,15 @@ "Slēgtie paraksti" "Attēla režīms" "PIP" - "Ieslēgts" - "Izslēgts" "Multiaudio" "Vairāk kanālu" "Iestatījumi" - "Avots" - "Mainīt" - "Ieslēgts" - "Izslēgts" - "Skaņa" - "Galvenais" - "PIP logs" - "Izkārtojums" - "Apakšā pa labi" - "Augšā pa labi" - "Augšā pa kreisi" - "Apakšā pa kreisi" - "Līdzās" - "Izmēri" - "Liels" - "Mazs" - "Ievades avots" "TV (antena/kabelis)" "Nav informācijas par programmu" "Nav informācijas" "Bloķēts kanāls" - "Nezināma valoda" + "Nezināma valoda" + "Slēgtie paraksti %1$d" "Slēgtie paraksti" "Izslēgti" "Pielāgot formatēšanu" @@ -83,6 +64,7 @@ "SD" "Grupēt pēc:" "Šī programma ir bloķēta." + "Šī programma ir bez vērtējuma." "Šī programma ir novērtēta kā “%1$s”." "Ieeja neatbalsta automātisko meklēšanu." "Nevar sākt automātisko skenēšanu ieejā “%s”." @@ -93,7 +75,6 @@ Tika pievienoti %1$d kanāli. "Kanāli netika pievienoti." - "Kanālu meklētājs" "Vecāku kontrole" "Ieslēgts" "Izslēgts" @@ -109,6 +90,8 @@ "Citas valstis" "Nav" "Nav" + "Bez vērtējuma" + "Bloķēt programmas bez vērtējuma" "Nav" "Stingri ierobežojumi" "Vidēji ierobežojumi" @@ -125,6 +108,7 @@ "PIN ievade, lai skatītos šo kanālu" "PIN ievade, lai skatītos šo programmu" "Šī programma ir novērtēta kā %1$s. Lai skatītos šo programmu, ievadiet PIN kodu." + "Šī programma ir bez vērtējuma. Lai skatītos šo programmu, ievadiet PIN." "PIN ievade" "Lai iestatītu vecāku kontroli, izveidojiet PIN kodu." "Ievadiet jauno PIN" @@ -137,22 +121,31 @@ "PIN kods nav pareizs. Mēģiniet vēlreiz." "Neatbilstošs PIN. Mēģiniet vēlreiz." + "Pasta indeksa ievadīšana" + "Lietotnē Live Channels pasta indekss tiks izmantots, lai nodrošinātu visu TV kanālu programmu ceļvedi." + "Ievadiet pasta indeksu" + "Nederīgs pasta indekss" "Iestatījumi" "Pielāgot kanālu sarakstu" "Izvēlieties kanālus programmu ceļvedim." "Kanālu avoti" "Pieejami jauni kanāli" "Vecāku kontrole" + "Laika nobīde" + "Skatīšanās laikā ierakstiet tiešraides programmas, lai varētu tās apturēt vai attīt atpakaļ.\nBrīdinājums: tas var samazināt iekšējās atmiņas lietojumu, ja atmiņa tiek intensīvi lietota." "Atklātā pirmkoda licences" - "Atklātā pirmkoda licences" + "Sūtīt atsauksmes" "Versija" "Lai skatītos šo kanālu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu." "Lai skatītos šo programmu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu." + "Šī programma ir bez vērtējuma.\nLai skatītos šo programmu, nospiediet pogu labajā pusē un ievadiet PIN." "Šī programma ir novērtēta kā “%1$s”.\nLai skatītos šo programmu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu." "Lai skatītos šo kanālu, izmantojiet noklusējuma lietotni televīzijas skatīšanai tiešraidē." "Lai skatītos šo programmu, izmantojiet noklusējuma lietotni televīzijas skatīšanai tiešraidē." + "Šī programma ir bez vērtējuma.\nLai skatītos šo programmu, izmantojiet noklusējuma lietotni TV skatīšanai tiešraidē." "Šīs programma ir novērtēta kā “%1$s”.\nLai skatītos šo programmu, izmantojiet noklusējuma programmu televīzijas skatīšanai tiešraidē." "Programma ir bloķēta." + "Šī programma ir bez vērtējuma." "Šī programma ir novērtēta kā “%1$s”." "Tikai audio" "Vājš signāls" @@ -185,8 +178,6 @@ "Nospiediet ATLASĪT"", lai piekļūtu TV izvēlnei." "Nav atrasta neviena TV ieeja." "Nevar atrast TV ieeju." - "Funkcija PIP netiek atbalstīta." - "Nav ievades, ko parādīt, izmantojot PIP." "Kanālu meklētāja veids nav piemērots. Lūdzu, palaidiet lietotni “Tiešraides kanāli” kanālu meklētāja veida TV ievadei." "Kanālu meklēšana neizdevās." "Netika atrasta neviena lietotne šīs darbības veikšanai." @@ -235,6 +226,8 @@ %1$d ieplānots ieraksts %1$d ieplānoti ieraksti + "Atcelt ierakstīšanu" + "Apturēt ierakstīšanu" "Skatīties" "Atskaņot no sākuma" "Atsākt atskaņošanu" @@ -268,9 +261,6 @@ "Ja vēlaties ierakstīt pārāk daudz programmu vienlaikus, tiks ierakstītas tikai programmas ar augstāku prioritāti." "Saglabāt" "Vienreizējiem ierakstiem ir visaugstākā prioritāte" - "Atcelt" - "Atcelt" - "Aizmirst" "Apturēt" "Skatīt ierakstīšanas grafiku" "Šī viena programma" @@ -280,25 +270,28 @@ "Tā vietā ierakstīt tālāk norādīto" "Atcelt šo ierakstīšanu" "Skatīties tūlīt" + "Dzēst ierakstus…" "Var ierakstīt" "Ierakstīšana ir ieplānota" "Ierakstīšanas konflikts" "Ierakstīšana" "Neizdevās ierakstīt" - "Tiek lasītas programmas, lai izveidotu ierakstīšanas grafikus." - "Tiek lasītas programmas" - - + "Tiek lasītas programmas" + "Skatīt nesenos ierakstus" + "“%1$s” ierakstīšana netika pabeigta." + "“%1$s” un “%2$s” ierakstīšana netika pabeigta." + "“%1$s”, “%2$s” un “%3$s” ierakstīšana netika pabeigta." + "“%1$s” ierakstīšana netika pabeigta, jo krātuvē nepietiek vietas." + "“%1$s” un “%2$s” ierakstīšana netika pabeigta, jo krātuvē nepietiek vietas." + "“%1$s”, “%2$s” un “%3$s” ierakstīšana netika pabeigta, jo krātuvē nepietiek vietas." "Ciparvideo ierakstītājam nepieciešama lielāka krātuve" - "Jūs varēsiet ierakstīt programmas, izmantojot ciparvideo ierakstītāju. Taču pašlaik jūsu ierīces krātuvē nav pietiekami daudz vietas, lai tas darbotos. Lūdzu, pievienojiet vismaz %1$s GB lielu ārējo disku un izpildiet sniegtos norādījumus, lai formatētu to kā ierīces krātuvi." + "Jūs varēsiet ierakstīt programmas, izmantojot ciparvideo ierakstītāju. Taču pašlaik jūsu ierīces krātuvē nav pietiekami daudz vietas, lai tas darbotos. Lūdzu, pievienojiet vismaz %1$d GB lielu ārējo disku un izpildiet sniegtos norādījumus, lai formatētu to kā ierīces krātuvi." + "Krātuvē nepietiek vietas" + "Šī programma netiks ierakstīta, jo krātuvē nepietiek vietas. Izdzēsiet dažus esošos ierakstus." "Trūkst krātuves" - "Trūkst ciparvideo ierakstītāja izmantotās krātuves. Lūdzu, pievienojiet ārējo disku, ko izmantojāt iepriekš ciparvideo ierakstītāja atkārtotai iespējošanai. Varat arī izvēlēties aizmirst krātuvi, ja tā vairs nav pieejama." - "Vai aizmirst krātuvi?" - "Viss jūsu ierakstītais saturs un grafiki tiks zaudēti." "Vai apturēt ierakstīšanu?" "Ierakstītais saturs tiks saglabāts." - - + "Seriāla “%1$s” ierakstīšana tiks apturēta, jo ir konflikts ar šo programmu. Ierakstītais saturs tiks saglabāts." "Ierakstīšana ir ieplānota, taču ir konflikti" "Ierakstīšana tika sākta, taču ir konflikti" "Programma %1$s tiks ierakstīta." @@ -314,17 +307,34 @@ "Šo pārraidi jau ir plānots ierakstīt plkst. %1$s." "Jau tika ierakstīta" "Šī pārraide jau ir ierakstīta. Tā ir pieejama DVR bibliotēkā." - - - - - - - - + "Ir ieplānota seriāla ierakstīšana" + + Seriālam %2$s ir ieplānoti %1$d ieraksti. + Seriālam %2$s ir ieplānots %1$d ieraksts. + Seriālam %2$s ir ieplānoti %1$d ieraksti. + + + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstīts(-i) %3$d no tiem. + Seriālam %2$s ir ieplānots %1$d ieraksts. Konfliktu dēļ netiks ierakstīts(-i) %3$d no tiem. + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstīts(-i) %3$d no tiem. + + + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstītas %3$d šī un cita(-u) seriāla(-u) sērijas. + Seriālam %2$s ir ieplānots %1$d ieraksts. Konfliktu dēļ netiks ierakstītas %3$d šī un cita(-u) seriāla(-u) sērijas. + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstītas %3$d šī un cita(-u) seriāla(-u) sērijas. + + + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstīta 1 cita seriāla sērija. + Seriālam %2$s ir ieplānots %1$d ieraksts. Konfliktu dēļ netiks ierakstīta 1 cita seriāla sērija. + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstīta 1 cita seriāla sērija. + + + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstītas %3$d cita seriāla sērijas. + Seriālam %2$s ir ieplānots %1$d ieraksts. Konfliktu dēļ netiks ierakstītas %3$d cita seriāla sērijas. + Seriālam %2$s ir ieplānoti %1$d ieraksti. Konfliktu dēļ netiks ierakstītas %3$d cita seriāla sērijas. + "Ierakstītā programma netika atrasta." "Saistītie ieraksti" - "(Nav programmas apraksta)" %1$d ierakstu %1$d ieraksts @@ -346,6 +356,7 @@ "Vai apturēt sērijas ierakstīšanu?" "Ierakstītās sērijas būs pieejamas DVR bibliotēkā." "Apturēt" + "Šobrīd nav iznākusi neviena sērija." "Nav pieejama neviena sērija.\nTās tiks ierakstītas, kad būs pieejamas." (%1$d minūtes) @@ -358,4 +369,5 @@ "Plkst. %1$s šodien" "Plkst. %1$s rīt" "Rezultāts" + "Ierakstītās programmas" diff --git a/res/values-mk-rMK-v23/strings.xml b/res/values-mk-rMK-v23/strings.xml new file mode 100644 index 00000000..4fd7f740 --- /dev/null +++ b/res/values-mk-rMK-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Канали" + diff --git a/res/values-mk-rMK/arrays.xml b/res/values-mk-rMK/arrays.xml new file mode 100644 index 00000000..b1ae7467 --- /dev/null +++ b/res/values-mk-rMK/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Нормален" + "Цел" + "Зум" + + + "Сите канали" + "Семејство/деца" + "Спорт" + "Пазарење" + "Филмови" + "Комедија" + "Патување" + "Драма" + "Образование" + "Животни/див свет" + "Вести" + "Игри" + "Уметности" + "Забава" + "Стил на живеење" + "Музика" + "Главно" + "Техника/наука" + + + "ТВ канали во живо" + "Едноставен начин да се откријат содржини" + "Преземете апликации, добијте повеќе канали" + "Приспособете го редоследот на каналите" + + + "Гледајте ги содржините од апликациите како што ги гледате каналите на телевизорот." + "Прелистувајте содржини од апликациите со познат водич и пријателски интерфејс, \nисто како каналите на телевизорот." + "Додајте повеќе канали со инсталирање апликации што нудат канали во живо. \nНајдете компатибилни апликации во Google Play Store преку врската во менито на телевизорот." + "Поставете ги ново инсталираните извори на канали за да ја приспособите листата со канали. \nИзберете ги изворите на канали во менито Поставки за да започнете." + + diff --git a/res/values-mk-rMK/rating_system_strings.xml b/res/values-mk-rMK/rating_system_strings.xml new file mode 100644 index 00000000..da9a40b3 --- /dev/null +++ b/res/values-mk-rMK/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Програмите може да содржат материјали несоодветни за публика на возраст под 15 години и затоа треба да се користи родителски надзор." + "Програмите може да содржат материјали несоодветни за публика на возраст под 19 години и затоа не се соодветни за таквата публика." + "Сугестивен дијалог" + "Непристоен говор" + "Сексуална содржина" + "Насилство" + "Митолошко насилство" + "Програмава е направена да биде соодветна за сите деца." + "Програмава е наменета за деца од 7 години, па нагоре." + "Според повеќето родители, програмава е погодна за сите возрасти." + "Програмава содржи материјали што според некои родители се можеби несоодветни за мали деца. Многу родители може да сакаат да ја гледаат со своите мали деца." + "Програмава содржи материјали што според повеќето родители се несоодветни за деца под 14-годишна возраст." + "Програмава е специјално наменета за возрасни и поради тоа можеби е несоодветна за деца под 17 години." + "Оценки за филм" + "Општа публика. Нема ништо што ќе ги навреди родителите ако децата го гледаат ова." + "Се предлага надзор од родител. Може да содржи материјали што родителите може не би ги сакале за своите деца." + "Родителите строго се предупредуваат. Некои материјали може да се несоодветни за деца кои се на предтинејџерска возраст." + "Ограничено, содржи материјали за возрасни. Родителите се советуваат да дознаат повеќе за филмот пред да ги поведат своите мали деца со себе." + "Забрането за лица под 17-годишна возраст. Само за возрасни. Забрането за деца." + diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml new file mode 100644 index 00000000..4de54ac4 --- /dev/null +++ b/res/values-mk-rMK/strings.xml @@ -0,0 +1,357 @@ + + + + + "моно" + "стерео" + "Контроли за игри" + "Канали" + "ТВ опции" + "Контролите за репродуцирање се недостапни за овој канал" + "Пуштање или паузирање" + "Брзо премотување напред" + "Премотување наназад" + "Следно" + "Претходно" + "Програмски водич" + "Достапни се нови канали" + "Отвори %1$s" + "Затворени титли" + "Реж. на прикаж." + "ПИП" + "Мултиаудио" + "Добиј уште канали" + "Поставки" + "ТВ (антена/кабел)" + "Нема информации за програмата" + "Нема информации" + "Блокиран канал" + "Непознат јазик" + "Затворени титлови %1$d" + "Затворени титли" + "Исклучено" + "Прилаг. форматирање" + "Пост. парам. за затв, титли во системот" + "Реж. на прикаж." + "Мултиаудио" + "моно" + "стерео" + "5.1 опкружувачки" + "7.1 опкружувачки" + "%d канали" + "Присп.листа канали" + "Избери група" + "Деселектирај група" + "Групирај по" + "Извор на канал" + "HD/SD" + "HD" + "SD" + "Групирај по" + "Оваа програма е блокиранa" + "Програмава е неоценета" + "Оваа програма е оценета %1$s" + "Влезот не поддржува автоматско скенирање" + "Не може да се вклучи автоскенирање на „%s“" + "Не можеше да ги активира претпочитањата на целиот систем за објаснувања." + + %1$d канал е додаден + %1$d канали се додадени + + "Не се додадени канали" + "Родител. контрола" + "Вклучено" + "Исклучено" + "Блокирани канали" + "Блокирај ги сите" + "Одблокирај ги сите" + "Сокриени канали" + "Прог. ограничувања" + "Измени PIN" + "Систем за процена" + "Проценки" + "Види ги сите сист." + "Останати земји" + "Ништо" + "Ништо" + "Неоценето" + "Блокирај неоценети програми" + "Ништо" + "Големи ограничување" + "Средно ограничување" + "Ниско ограничување" + "Приспособено" + "Содржина погодна за деца" + "Содржина погодна за постари деца" + "Содржина погодна за млади" + "Рачни ограничувања" + + + "%1$s и под-рејтинг" + "Под-рејтинг" + "Внесете го вашиот PIN за да го гледате каналот" + "Внесете го вашиот PIN за да ја гледате програмата" + "Програмава е оценета со %1$s. Внесете го PIN-кодот за да ја гледате" + "Програмава е неоценета. Внесете го PIN-кодот за да ја гледате" + "Внесете го вашиот PIN" + "За да поставите родителска контрола, креирајте PIN." + "Внесете го новиот PIN" + "Потврдете го вашиот PIN" + "Внесете го тековниот PIN" + + Внесовте погрешен PIN-код 5 пати.\nОбидете се повторно за %1$d секунда. + Внесовте погрешен PIN-код 5 пати.\nОбидете се повторно за %1$d секунди. + + "PIN-кодот е погрешен. Обидете се повторно." + "Обидете се повторно, PIN-кодот не се совпаѓа" + "Внесете го поштенскиот број." + "Апликацијата Live TV ќе го користи поштенскиот број за да обезбеди целосен програмски водич за ТВ-каналите." + "Внесете го поштенскиот број" + "Неважечки поштенски број" + "Поставки" + "Приспособи го списокот канали" + "Изберете канали за програмскиот водич" + "Извори на канали" + "Достапни се нови канали" + "Родителски надзор" + "Снимање" + "Снимајте додека гледате за да може да ги паузирате или да ги премотувате наназад програмите во живо.\nПредупредување: ова може да го намали траењето на внатрешната меморија поради нејзиното интензивно искористување." + "Лиценци за софтвер со отворен код" + "Испратете повратни информации" + "Верзија" + "За да го гледате овој канал, притиснете Во ред и внесете го вашиот PIN" + "За да ја гледате оваа програма, притиснете Во ред и внесете го вашиот PIN" + "Програмава е неоценета.\nЗа да ја гледате, притиснете Во ред и внесете го PIN-кодот" + "Оваа програма е оценета %1$s.\nЗа да ја гледате оваа програма, притиснете Во ред и внесете го PIN-от." + "За да го гледате овој канал, користете ја стандардната апликација за телевизија во живо." + "За да ја гледате програмава, користете ја стандардната апликација за телевизија во живо." + "Програмава е неоценета.\nЗа да ја гледате, користете ја стандардната апликација за телевизија во живо." + "Оваа програма е оценета %1$s.\nЗа да ја гледате програмава, користете ја стандардната апликација за телевизија во живо." + "Програмата е блокирана" + "Програмава е неоценета" + "Оваа програма е оценета %1$s" + "Само звук" + "Слаб сигнал" + "Нема интернет-врска" + + Каналов не може да се пушти сѐ до %1$s бидејќи се снимаат други канали. \n\nДопрете “Десно“ за приспособување на распоредот на снимање. + Каналов не може да се пушти сѐ до %1$s бидејќи се снимаат други канали. \n\nДопрете “Десно“ за приспособување на распоредот на снимање. + + "Без наслов" + "Каналот е блокиран" + "Нов" + "Извори" + + %1$d канал + %1$d канали + + "Нема достапни канали" + "Нов" + "Не е поставен" + "Преземете повеќе извори" + "Прелистувајте апликации што нудат канали во живо" + "Достапни се нови извори на канали" + "Нови извори на канали имаат понуда на канали.\nПоставете ги сега или направете го тоа подоцна во поставките за извори на канали." + "Постави сега" + "Во ред, разбрав" + + + "Притиснете ИЗБЕРИ"" за да пристапите до ТВ-менито." + "Не е пронајден ТВ-влез" + "ТВ-влезот не може да се најде" + "Типот приемник не е соодветен. Стартувајте ја апликацијата Live TV за ТВ-влез од типот приемник." + "Бирањето не успеа" + "Не е пронајдена апликација што ќе се справи со ова дејство." + "Сите изворни канали се сокриени.\nИзберете барем еден канал за да го гледате." + "Видеото е неочекувано недостапно" + "Копчето НАЗАД е за поврзаниот уред. Притиснете на копчето ПОЧЕТНА СТРАНИЦА за да излезете." + "На каналите во живо им е потребна дозвола за да ги читаат ТВ-листите." + "Поставете ги изворите" + "ТВ каналите во живо го комбинираат искуството на традиционалните ТВ канали со преносот на канали што го овозможуваат апликациите. \n\nЗапочнете со поставување на изворите на канали што се веќе инсталирани. Или прелистајте во Google Play Store за повеќе апликации што нудат ТВ канали во живо." + "Снимки и распореди" + "10 минути" + "30 минути" + "1 час" + "3 часа" + "Неодамнешни" + "Закажани" + "Серии" + "Други" + "Каналот не може да се сними." + "Програмата не може да се сними." + "%1$s е закажана за снимање." + "Се снима %1$s од сега до %2$s" + "Целосен распоред" + + Следниот %1$d ден + Следните %1$d дена + + + %1$d минута + %1$d минути + + + %1$d ново снимање + %1$d нови снимања + + + %1$d снимање + %1$d снимања + + + %1$d закажано снимање + %1$d закажани снимања + + "Откажи го снимањето" + "Сопри со снимање" + "Часовник" + "Репродуцирај отпочеток" + "Прод. репродукција" + "Избриши" + "Избриши ги снимките" + "Продолжи" + "Сезона %1$s" + "Види распоред" + "Прочитајте повеќе" + "Избриши ги снимките" + "Изберете ги епизодите што сакате да ги избришете. Не може да се вратат откако ќе се избришат." + "Нема снимки за бришење." + "Изберете ги гледаните епизоди" + "Изберете ги сите епизоди" + "Откажи избор на сите епизоди" + "Изгледани се %1$d од %2$d минути" + "Изгледани се %1$d од %2$d секунди" + "Никогаш не се гледани" + + %1$d од %2$d епизода се избришани + %1$d од %2$d епизоди се избришани + + "Приоритет" + "Највисок" + "Најнизок" + "Бр. %1$d" + "Канали" + "Кој било" + "Изберете приоритет" + "Кога има повеќе програми што треба да се снимаат во исто време, ќе се снимат само оние со повисок приоритет." + "Зачувај" + "Еднократните снимања имаат највисок приоритет" + "Сопри" + "Прикажи распоред на снимање" + "Само оваа програма" + "сега - %1$s" + "Целата серија…" + "Сепак закажи" + "Наместо неа, снимај ја оваа" + "Откажете го снимањево" + "Гледајте сега" + "Избришете ги снимките…" + "Може да се снима" + "Снимањето е закажано" + "Конфликт при снимање" + "Се снима" + "Неуспешно снимање" + "Се читаат програми" + "Прегледајте ги последните снимки" + "Снимањето на %1$s е незавршено." + "Снимањата на %1$s и %2$s се незавршени." + "Снимањата на %1$s, %2$s и %3$s се незавршени." + "Снимањето на %1$s не заврши поради недоволен простор за складирање." + "Снимањата %1$s и %2$s не завршија поради недоволен простор за складирање." + "Снимањата %1$s, %2$s и %3$s не завршија поради недоволен простор за складирање." + "Потребна е поголема меморија за DVR" + "Ќе може да снимате програми со DVR. Но во моментов нема доволно простор на вашиот уред за да може DVR да функционира. Поврзете надворешна податочна едница од %1$d GB или повеќе и следете ги чекорите за да ја форматирате како меморија на уредот." + "Нема доволно меморија" + "Програмава нема да се сними бидејќи нема доволно меморија. Обидете се да избришете постоечки снимки." + "Недостасува простор" + "Да се сопре со снимање?" + "Снимените содржини ќе се зачуваат." + "Снимањето на %1$s ќе прекине поради конфликти со програмава. Снимената содржина ќе се зачува." + "Снимањето е закажано, но постојат конфликти" + "Снимањето започна, но постојат конфликти" + "%1$s ќе се сними." + "%1$s се снима." + "Некои делови од %1$s нема да се снимат." + "Некои делови од %1$s и %2$s нема да се снимат." + "Некои делови од %1$s, %2$s и уште една закажана нема да се снимат." + + Некои делови од %1$s, %2$s и уште %4$d закажана нема да се снимат. + Некои делови од %1$s, %2$s и уште %4$d закажани нема да се снимат. + + "Што би сакале да снимите?" + "Колку долго би сакале да снимате?" + "Веќе е закажана" + "Истата програма е веќе закажана за снимање во %1$s." + "Веќе е снимена" + "Програмава е веќе снимена. Достапна е во DVR-збирката." + "Закажано е снимање серија" + + %1$d снимање е закажано за %2$s. + %1$d снимања се закажани за %2$s. + + + %1$d снимање е закажано за %2$s. %3$d од нив нема да се снимат поради конфликти. + %1$d снимања се закажани за %2$s. %3$d од нив нема да се снимат поради конфликти. + + + %1$d снимање е закажано за %2$s. %3$d епизоди од оваа и други серии нема да се снимат поради конфликти. + %1$d снимања се закажани за %2$s. %3$d епизоди од оваа и други серии нема да се снимат поради конфликти. + + + %1$d снимање е закажано за %2$s. 1 епизода од други серии нема да се сними поради конфликти. + %1$d снимања се закажани за %2$s. 1 епизода од други серии нема да се сними поради конфликти. + + + %1$d снимање е закажано за %2$s. %3$d епизоди од други серии нема да се снимат поради конфликти. + %1$d снимања се закажани за %2$s. %3$d епизоди од други серии нема да се снимат поради конфликти. + + "Снимената програма не е пронајдена." + "Поврзани снимки" + + %1$d снимка + %1$d снимки + + " / " + "%1$s е отстранета од распоредот на снимање" + "Ќе се сними делумно поради конфликти со приемникот." + "Нема да се сними поради конфликти со приемникот." + "Сѐ уште нема закажани снимања.\nМоже да закажете снимање од програмскиот водич." + + %1$d конфликт на снимање + %1$d конфликти на снимање + + "Поставки за серии" + "Започни со снимање серија" + "Сопри го снимањето серија" + "Да се сопре снимањето на серијата?" + "Снимените епизоди ќе останат достапни во DVR-збирката." + "Сопри" + "Не се емитуваат епизоди во моментов." + "Не се достапни епизоди.\nЌе се снимат штом ќе бидат достапни." + + (%1$d минута) + (%1$d минути) + + "Денес" + "Утре" + "Вчера" + "%1$s денес" + "%1$s утре" + "Оценка" + "Снимени програми" + diff --git a/res/values-mk-v23/strings.xml b/res/values-mk-v23/strings.xml deleted file mode 100644 index 4fd7f740..00000000 --- a/res/values-mk-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Канали" - diff --git a/res/values-mk/arrays.xml b/res/values-mk/arrays.xml deleted file mode 100644 index b1ae7467..00000000 --- a/res/values-mk/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Нормален" - "Цел" - "Зум" - - - "Сите канали" - "Семејство/деца" - "Спорт" - "Пазарење" - "Филмови" - "Комедија" - "Патување" - "Драма" - "Образование" - "Животни/див свет" - "Вести" - "Игри" - "Уметности" - "Забава" - "Стил на живеење" - "Музика" - "Главно" - "Техника/наука" - - - "ТВ канали во живо" - "Едноставен начин да се откријат содржини" - "Преземете апликации, добијте повеќе канали" - "Приспособете го редоследот на каналите" - - - "Гледајте ги содржините од апликациите како што ги гледате каналите на телевизорот." - "Прелистувајте содржини од апликациите со познат водич и пријателски интерфејс, \nисто како каналите на телевизорот." - "Додајте повеќе канали со инсталирање апликации што нудат канали во живо. \nНајдете компатибилни апликации во Google Play Store преку врската во менито на телевизорот." - "Поставете ги ново инсталираните извори на канали за да ја приспособите листата со канали. \nИзберете ги изворите на канали во менито Поставки за да започнете." - - diff --git a/res/values-mk/rating_system_strings.xml b/res/values-mk/rating_system_strings.xml deleted file mode 100644 index da9a40b3..00000000 --- a/res/values-mk/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Програмите може да содржат материјали несоодветни за публика на возраст под 15 години и затоа треба да се користи родителски надзор." - "Програмите може да содржат материјали несоодветни за публика на возраст под 19 години и затоа не се соодветни за таквата публика." - "Сугестивен дијалог" - "Непристоен говор" - "Сексуална содржина" - "Насилство" - "Митолошко насилство" - "Програмава е направена да биде соодветна за сите деца." - "Програмава е наменета за деца од 7 години, па нагоре." - "Според повеќето родители, програмава е погодна за сите возрасти." - "Програмава содржи материјали што според некои родители се можеби несоодветни за мали деца. Многу родители може да сакаат да ја гледаат со своите мали деца." - "Програмава содржи материјали што според повеќето родители се несоодветни за деца под 14-годишна возраст." - "Програмава е специјално наменета за возрасни и поради тоа можеби е несоодветна за деца под 17 години." - "Оценки за филм" - "Општа публика. Нема ништо што ќе ги навреди родителите ако децата го гледаат ова." - "Се предлага надзор од родител. Може да содржи материјали што родителите може не би ги сакале за своите деца." - "Родителите строго се предупредуваат. Некои материјали може да се несоодветни за деца кои се на предтинејџерска возраст." - "Ограничено, содржи материјали за возрасни. Родителите се советуваат да дознаат повеќе за филмот пред да ги поведат своите мали деца со себе." - "Забрането за лица под 17-годишна возраст. Само за возрасни. Забрането за деца." - diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml deleted file mode 100644 index 566377a8..00000000 --- a/res/values-mk/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "моно" - "стерео" - "Контроли за игри" - "Последни канали" - "ТВ опции" - "Опции за ПИП" - "Контролите за репродуцирање се недостапни за овој канал" - "Пуштање или паузирање" - "Брзо премотување напред" - "Премотување наназад" - "Следно" - "Претходно" - "Програмски водич" - "Достапни се нови канали" - "Отвори %1$s" - "Затворени титли" - "Реж. на прикаж." - "ПИП" - "Вклучено" - "Исклучено" - "Мултиаудио" - "Добиј уште канали" - "Поставки" - "Извор" - "Замени" - "Вклучено" - "Исклучено" - "Звук" - "Главен" - "ПИП прозорец" - "Распоред" - "Долу десно" - "Горе десно" - "Горе лево" - "Долу лево" - "Едно до друго" - "Големина" - "Голема" - "Мала" - "Влезен извор" - "ТВ (антена/кабел)" - "Нема информации за програмата" - "Нема информации" - "Блокиран канал" - "Непознат јазик" - "Затворени титли" - "Исклучено" - "Прилаг. форматирање" - "Пост. парам. за затв, титли во системот" - "Реж. на прикаж." - "Мултиаудио" - "моно" - "стерео" - "5.1 опкружувачки" - "7.1 опкружувачки" - "%d канали" - "Присп.листа канали" - "Избери група" - "Деселектирај група" - "Групирај по" - "Извор на канал" - "HD/SD" - "HD" - "SD" - "Групирај по" - "Оваа програма е блокиранa" - "Оваа програма е оценета %1$s" - "Влезот не поддржува автоматско скенирање" - "Не може да се вклучи автоскенирање на „%s“" - "Не можеше да ги активира претпочитањата на целиот систем за објаснувања." - - %1$d канал е додаден - %1$d канали се додадени - - "Не се додадени канали" - "Приемник" - "Родител. контрола" - "Вклучено" - "Исклучено" - "Блокирани канали" - "Блокирај ги сите" - "Одблокирај ги сите" - "Сокриени канали" - "Прог. ограничувања" - "Измени PIN" - "Систем за процена" - "Проценки" - "Види ги сите сист." - "Останати земји" - "Ништо" - "Ништо" - "Ништо" - "Големи ограничување" - "Средно ограничување" - "Ниско ограничување" - "Приспособено" - "Содржина погодна за деца" - "Содржина погодна за постари деца" - "Содржина погодна за млади" - "Рачни ограничувања" - - - "%1$s и под-рејтинг" - "Под-рејтинг" - "Внесете го вашиот PIN за да го гледате каналот" - "Внесете го вашиот PIN за да ја гледате програмата" - "Програмава е оценета со %1$s. Внесете го PIN-кодот за да ја гледате" - "Внесете го вашиот PIN" - "За да поставите родителска контрола, креирајте PIN." - "Внесете го новиот PIN" - "Потврдете го вашиот PIN" - "Внесете го тековниот PIN" - - Внесовте погрешен PIN-код 5 пати.\nОбидете се повторно за %1$d секунда. - Внесовте погрешен PIN-код 5 пати.\nОбидете се повторно за %1$d секунди. - - "PIN-кодот е погрешен. Обидете се повторно." - "Обидете се повторно, PIN-кодот не се совпаѓа" - "Поставки" - "Приспособи го списокот канали" - "Изберете канали за програмскиот водич" - "Извори на канали" - "Достапни се нови канали" - "Родителски надзор" - "Лиценци за софтвер со отворен код" - "Лиценци за отворен код" - "Верзија" - "За да го гледате овој канал, притиснете Во ред и внесете го вашиот PIN" - "За да ја гледате оваа програма, притиснете Во ред и внесете го вашиот PIN" - "Оваа програма е оценета %1$s.\nЗа да ја гледате оваа програма, притиснете Во ред и внесете го PIN-от." - "За да го гледате овој канал, користете ја стандардната апликација за телевизија во живо." - "За да ја гледате програмава, користете ја стандардната апликација за телевизија во живо." - "Оваа програма е оценета %1$s.\nЗа да ја гледате програмава, користете ја стандардната апликација за телевизија во живо." - "Програмата е блокирана" - "Оваа програма е оценета %1$s" - "Само звук" - "Слаб сигнал" - "Нема интернет-врска" - - Каналов не може да се пушти сѐ до %1$s бидејќи се снимаат други канали. \n\nДопрете “Десно“ за приспособување на распоредот на снимање. - Каналов не може да се пушти сѐ до %1$s бидејќи се снимаат други канали. \n\nДопрете “Десно“ за приспособување на распоредот на снимање. - - "Без наслов" - "Каналот е блокиран" - "Нов" - "Извори" - - %1$d канал - %1$d канали - - "Нема достапни канали" - "Нов" - "Не е поставен" - "Преземете повеќе извори" - "Прелистувајте апликации што нудат канали во живо" - "Достапни се нови извори на канали" - "Нови извори на канали имаат понуда на канали.\nПоставете ги сега или направете го тоа подоцна во поставките за извори на канали." - "Постави сега" - "Во ред, разбрав" - - - "Притиснете ИЗБЕРИ"" за да пристапите до ТВ-менито." - "Не е пронајден ТВ-влез" - "ТВ-влезот не може да се најде" - "ПИП не е поддржано" - "Нема достапен влез што може да се прикаже со ПИП" - "Типот приемник не е соодветен. Стартувајте ја апликацијата Live TV за ТВ-влез од типот приемник." - "Бирањето не успеа" - "Не е пронајдена апликација што ќе се справи со ова дејство." - "Сите изворни канали се сокриени.\nИзберете барем еден канал за да го гледате." - "Видеото е неочекувано недостапно" - "Копчето НАЗАД е за поврзаниот уред. Притиснете на копчето ПОЧЕТНА СТРАНИЦА за да излезете." - "На каналите во живо им е потребна дозвола за да ги читаат ТВ-листите." - "Поставете ги изворите" - "ТВ каналите во живо го комбинираат искуството на традиционалните ТВ канали со преносот на канали што го овозможуваат апликациите. \n\nЗапочнете со поставување на изворите на канали што се веќе инсталирани. Или прелистајте во Google Play Store за повеќе апликации што нудат ТВ канали во живо." - "Снимки и распореди" - "10 минути" - "30 минути" - "1 час" - "3 часа" - "Неодамнешни" - "Закажани" - "Серии" - "Други" - "Каналот не може да се сними." - "Програмата не може да се сними." - "%1$s е закажана за снимање." - "Се снима %1$s од сега до %2$s" - "Целосен распоред" - - Следниот %1$d ден - Следните %1$d дена - - - %1$d минута - %1$d минути - - - %1$d ново снимање - %1$d нови снимања - - - %1$d снимање - %1$d снимања - - - %1$d закажано снимање - %1$d закажани снимања - - "Часовник" - "Репродуцирај отпочеток" - "Прод. репродукција" - "Избриши" - "Избриши ги снимките" - "Продолжи" - "Сезона %1$s" - "Види распоред" - "Прочитајте повеќе" - "Избриши ги снимките" - "Изберете ги епизодите што сакате да ги избришете. Не може да се вратат откако ќе се избришат." - "Нема снимки за бришење." - "Изберете ги гледаните епизоди" - "Изберете ги сите епизоди" - "Откажи избор на сите епизоди" - "Изгледани се %1$d од %2$d минути" - "Изгледани се %1$d од %2$d секунди" - "Никогаш не се гледани" - - %1$d од %2$d епизода се избришани - %1$d од %2$d епизоди се избришани - - "Приоритет" - "Највисок" - "Најнизок" - "Бр. %1$d" - "Канали" - "Кој било" - "Изберете приоритет" - "Кога има повеќе програми што треба да се снимаат во исто време, ќе се снимат само оние со повисок приоритет." - "Зачувај" - "Еднократните снимања имаат највисок приоритет" - "Откажи" - "Откажи" - "Заборави" - "Сопри" - "Прикажи распоред на снимање" - "Само оваа програма" - "сега - %1$s" - "Целата серија…" - "Сепак закажи" - "Наместо неа, снимај ја оваа" - "Откажете го снимањево" - "Гледајте сега" - "Може да се снима" - "Снимањето е закажано" - "Конфликт при снимање" - "Се снима" - "Неуспешно снимање" - "Се читаат програми за да се создадат распореди за снимање" - "Се читаат програми" - - - "Потребна е поголема меморија за DVR" - "Ќе може да снимате програми со DVR. Но во моментов нема доволно простор на вашиот уред за да може DVR да функционира. Поврзете надворешна податочна едница од %1$s GB или повеќе и следете ги чекорите за да ја форматирате како меморија на уредот." - "Недостасува простор" - "Недостасува дел од просторот што го користи DVR. Поврзете го надворешниот диск што го користевте претходно за да овозможите DVR повторно. Во спротивно, може да изберете да се заборави просторот ако веќе не е достапен." - "Да се заборави просторот?" - "Сите ваши снимени содржини и распореди ќе се изгубат." - "Да се сопре со снимање?" - "Снимените содржини ќе се зачуваат." - - - "Снимањето е закажано, но постојат конфликти" - "Снимањето започна, но постојат конфликти" - "%1$s ќе се сними." - "%1$s се снима." - "Некои делови од %1$s нема да се снимат." - "Некои делови од %1$s и %2$s нема да се снимат." - "Некои делови од %1$s, %2$s и уште една закажана нема да се снимат." - - Некои делови од %1$s, %2$s и уште %4$d закажана нема да се снимат. - Некои делови од %1$s, %2$s и уште %4$d закажани нема да се снимат. - - "Што би сакале да снимите?" - "Колку долго би сакале да снимате?" - "Веќе е закажана" - "Истата програма е веќе закажана за снимање во %1$s." - "Веќе е снимена" - "Програмава е веќе снимена. Достапна е во DVR-збирката." - - - - - - - - - "Снимената програма не е пронајдена." - "Поврзани снимки" - "(Нема опис на програмата)" - - %1$d снимка - %1$d снимки - - " / " - "%1$s е отстранета од распоредот на снимање" - "Ќе се сними делумно поради конфликти со приемникот." - "Нема да се сними поради конфликти со приемникот." - "Сѐ уште нема закажани снимања.\nМоже да закажете снимање од програмскиот водич." - - %1$d конфликт на снимање - %1$d конфликти на снимање - - "Поставки за серии" - "Започни со снимање серија" - "Сопри го снимањето серија" - "Да се сопре снимањето на серијата?" - "Снимените епизоди ќе останат достапни во DVR-збирката." - "Сопри" - "Не се достапни епизоди.\nЌе се снимат штом ќе бидат достапни." - - (%1$d минута) - (%1$d минути) - - "Денес" - "Утре" - "Вчера" - "%1$s денес" - "%1$s утре" - "Оценка" - diff --git a/res/values-ml-rIN-v23/strings.xml b/res/values-ml-rIN-v23/strings.xml new file mode 100644 index 00000000..2333cc0f --- /dev/null +++ b/res/values-ml-rIN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "ചാനലുകൾ" + diff --git a/res/values-ml-rIN/arrays.xml b/res/values-ml-rIN/arrays.xml new file mode 100644 index 00000000..383837e7 --- /dev/null +++ b/res/values-ml-rIN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "സാധാരണം" + "നിറഞ്ഞു" + "സൂം ചെയ്യുക" + + + "എല്ലാ ചാനലുകളും" + "കുടുംബാംഗങ്ങൾ/കുട്ടികൾ" + "സ്‌പോർട്സ്" + "ഷോപ്പിംഗ്" + "സിനിമകൾ" + "ഹാസ്യം" + "യാത്ര" + "നാടകം" + "വിദ്യാഭ്യാസം" + "മൃഗങ്ങൾ/വൈൽഡ്‌‌ലൈഫ്" + "വാർത്ത" + "ഗെയിമിംഗ്" + "കലകള്‍" + "വിനോദം" + "ജീവിതശൈലി" + "സംഗീതം" + "പ്രീമിയർ" + "സാങ്കേതികവിദ്യ/ശാസ്‌ത്രം" + + + "തത്സമയ ചാനലുകൾ" + "ഉള്ളടക്കം കണ്ടെത്താനുള്ള ലളിതമായ മാർഗ്ഗം" + "ആപ്‌സ് ഡൗൺലോഡ് ചെയ്യുക, കൂടുതൽ ചാനലുകൾ സ്വീകരിക്കുക" + "നിങ്ങളുടെ ചാനൽ ലൈൻ-അപ്പ് ഇഷ്ടാനുസൃതമാക്കുക" + + + "ടിവിയിൽ ചാനലുകൾ കാണുന്നത് പോലെ നിങ്ങളുടെ ആപ്‌സിൽ ഉള്ളടക്കം കാണുക." + "പരിചിത ഗൈഡും ഉപയോഗലാളിത്യമുള്ള ഇന്റർഫേസും ഉപയോഗിച്ച് നിങ്ങളുടെ ആപ്‌സിൽ നിന്ന് ഉള്ളടക്കം ബ്രൗസുചെയ്യുക, \nടിവിയിൽ ചാനലുകൾ കാണുന്നത് പോലെ തന്നെയാണിത്." + "തത്സമയ ചാനലുകൾ നൽകുന്ന ആപ്‌സ് ഇൻസ്റ്റാൾ ചെയ്തുകൊണ്ട് കൂടുതൽ ചാനലുകൾ ചേർക്കുക. \nടിവി മെനുവിനുള്ളിലെ ലിങ്ക് ഉപയോഗിച്ചുകൊണ്ട് Google Play സ്റ്റോറിൽ അനുയോജ്യമായ ആപ്‌സ് കണ്ടെത്തുക." + "നിങ്ങളുടെ ചാനൽ ലിസ്റ്റ് ഇഷ്ടാനുസൃതമാക്കുന്നതിന് നിങ്ങൾ പുതിയതായി ഇൻസ്റ്റാൾ ചെയ്തിട്ടുള്ള ചാനൽ ഉറവിടങ്ങൾ സജ്ജമാക്കുക. \nആരംഭിക്കുന്നതിന്, ക്രമീകരണത്തിനുള്ളിൽ ചാനൽ ഉറവിടങ്ങൾ തിരഞ്ഞെടുക്കുക." + + diff --git a/res/values-ml-rIN/rating_system_strings.xml b/res/values-ml-rIN/rating_system_strings.xml new file mode 100644 index 00000000..e17f91f8 --- /dev/null +++ b/res/values-ml-rIN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "പ്രോഗ്രാമുകളിൽ 15 വയസ്സിൽ താഴെയുള്ള പ്രേക്ഷകർക്ക് ഉചിതമായേക്കാത്ത ഉള്ളടക്കം അടങ്ങിയേക്കാമെന്നതിനാൽ അവർക്കായി രക്ഷാകർതൃ വിവേചനാധികാരം ഉപയോഗിക്കേണ്ടതാണ്." + "പ്രോഗ്രാമുകളിൽ 19 വയസ്സിൽ താഴെയുള്ള പ്രേക്ഷകർക്ക് ഉചിതമായേക്കാത്ത ഉള്ളടക്കം അടങ്ങിയേക്കാമെന്നതിനാൽ 19 വയസ്സിൽ താഴെയുള്ള പ്രായക്കാർക്ക് ഇവ അനുയോജ്യമല്ല." + "ലൈംഗികസ്‌പഷ്‌ടമായ സംഭാഷണം" + "മോശമായ ഭാഷ" + "ലൈംഗിക ഉള്ളടക്കം" + "അക്രമം" + "ഭാവനാരൂപത്തിലുള്ള അക്രമം" + "ഈ പ്രോഗ്രാം എല്ലാ കുട്ടികൾക്കും ഉചിതമായ രീതിയിൽ രൂപകൽപ്പനചെയ്‌തിരിക്കുന്നു." + "7-നും അതിനുമുകളിലും പ്രായമുള്ള കുട്ടികൾക്കായി ഈ പ്രോഗ്രാം രൂപകൽപ്പന ചെയ്‌തിരിക്കുന്നു." + "എല്ലാ പ്രായക്കാർക്കും ഈ പ്രോഗ്രാം അനുയോജ്യമാണെന്ന് മിക്ക മാതാപിതാക്കളും പരിഗണിക്കുന്നു." + "ചെറിയ പ്രായത്തിലുള്ള കുട്ടികൾക്ക് അനുയോജ്യമല്ലെന്ന് പല മാതാപിതാക്കളും പരിഗണിച്ചേക്കാവുന്ന കുറച്ച് ഉള്ളടക്കങ്ങൾ ഈ പ്രോഗ്രാമിലുണ്ട്. കുട്ടികളിത് കാണുമ്പോൾ കൂടെ ഉണ്ടാകണമെന്നാണ് പല മാതാപിതാക്കളും ആഗ്രഹിച്ചേക്കുക." + "14 വയസ്സിന് താഴെയുള്ള കുട്ടികൾക്ക് അനുയോജ്യമല്ലെന്ന് പല മാതാപിതാക്കളും പരിഗണിക്കുന്ന കുറച്ച് ഉള്ളടക്കങ്ങൾ ഈ പ്രോഗ്രാമിലുണ്ട്." + "ഈ പ്രോഗ്രാം മുതിർന്നവർക്ക് കാണുന്നതിനായി പ്രത്യേകമായി തയ്യാറാക്കിയിട്ടുള്ളതായതിനാൽ 17 വയസ്സിന് താഴെയുള്ള കുട്ടികൾക്ക് ഇത് അനുയോജ്യമാകണമെന്നില്ല." + "ഫിലിം റേറ്റിംഗുകൾ" + "സാധാരണ പ്രേക്ഷകർക്കുള്ളത്. കുട്ടികളിത് കാണുന്നതിന് മാതാപിതാക്കൾക്ക് പ്രശ്നമൊന്നും ഉണ്ടാകില്ല." + "രക്ഷാകർതൃ മാർഗ്ഗനിർദ്ദേശം നിർദ്ദേശിച്ചിരിക്കുന്നു. ചെറിയ പ്രായത്തിലുള്ള കുട്ടികൾ കാണുന്നത് മാതാപിതാക്കൾക്ക് രസിക്കാത്ത ചില ഉള്ളടക്കങ്ങൾ ഇതിൽ ഉണ്ടായേക്കാം." + "മാതാപിതാക്കൾക്ക് ശക്തമായി മുന്നറിയിപ്പ് നൽകുന്നു. കൗമാരപ്രായമെത്താത്ത കുട്ടികൾക്ക് അനുചിതമായേക്കാവുന്ന ചില ഉള്ളടക്കങ്ങൾ ഇതിലുണ്ട്." + "നിയന്ത്രിതം, പ്രായപൂർത്തിയായവർക്കുള്ള ചില ഉള്ളടക്കങ്ങൾ ഇതിലുണ്ട്. ഈ സിനിമ കാണുന്നതിന് കുട്ടികളെ കൊണ്ടുപോകുന്നതിന് മുമ്പായി സിനിമയെ കുറിച്ച് കൂടുതലറിയുവാൻ മാതാപിതാക്കളോട് നിർദ്ദേശിക്കുന്നു." + "17 വയസ്സും അതിൽ താഴെയും പ്രായമുള്ളവരെ അനുവദിക്കില്ല. വ്യക്തമായും പ്രായപൂർത്തിയായവർക്ക് മാത്രം. കുട്ടികളെ അനുവദിക്കില്ല." + diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml new file mode 100644 index 00000000..77e46230 --- /dev/null +++ b/res/values-ml-rIN/strings.xml @@ -0,0 +1,355 @@ + + + + + "മോണോ" + "സ്‌റ്റീരിയോ" + "പ്ലേ നിയന്ത്രണങ്ങൾ" + "ചാനലുകൾ" + "ടിവി ഓപ്‌ഷനുകൾ" + "ഈ ചാനലിന് പ്ലേ നിയന്ത്രണങ്ങൾ ലഭ്യമല്ല" + "പ്ലേ ചെയ്യുക അല്ലെങ്കിൽ താല്‍‌ക്കാലികമായി നിര്‍‌ത്തുക" + "വേഗത്തിലുള്ള കൈമാറൽ" + "റിവൈൻഡുചെയ്യുക" + "അടുത്തത്" + "മുമ്പത്തെ" + "പ്രോഗ്രാം സഹായി" + "പുതിയ ചാനലുകൾ ലഭ്യമാണ്" + "%1$s തുറക്കുക" + "അടച്ച അടിക്കുറിപ്പുകൾ" + "ഡിസ്‌പ്ലേ മോഡ്" + "PIP" + "മൾട്ടി ഓഡിയോ" + "കൂടുതൽ ചാനൽ സ്വീകരിക്കൂ" + "ക്രമീകരണം" + "ടിവി (ആന്റിന/കേബിൾ)" + "പ്രോഗ്രാം വിവരമൊന്നുമില്ല" + "വിവരമൊന്നുമില്ല" + "തടഞ്ഞ ചാനൽ" + "അറിയാത്ത ഭാഷ" + "അടച്ച അടിക്കുറിപ്പുകൾ %1$d" + "അടച്ച അടിക്കുറിപ്പുകൾ" + "ഓഫ്" + "ഫോർമാറ്റുചെയ്യൽ ഇഷ്‌ടാനുസൃതമാക്കുക" + "അടച്ച അടിക്കുറിപ്പുകൾക്കുള്ള സിസ്‌റ്റം വിസ്‌തൃത മുൻഗണനകൾ സജ്ജമാക്കുക" + "ഡിസ്‌പ്ലേ മോഡ്" + "മൾട്ടി ഓഡിയോ" + "മോണോ" + "സ്‌റ്റീരിയോ" + "5.1 സറൗണ്ട്" + "7.1 സറൗണ്ട്" + "%d ചാനലുകൾ" + "ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കുക" + "ഗ്രൂപ്പ് തിരഞ്ഞെടുക്കുക" + "ഗ്രൂപ്പ് തിരഞ്ഞെടുത്തത് മാറ്റുക" + "ഗ്രൂപ്പ് അനുസരിച്ച്" + "ചാനൽ ഉറവിടം" + "HD/SD" + "HD" + "SD" + "ഗ്രൂപ്പ് അനുസരിച്ച്" + "ഈ പ്രോഗ്രാം തടഞ്ഞിരിക്കുന്നു" + "ഈ പ്രോഗ്രാം റേറ്റുചെയ്‌തിട്ടില്ല" + "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു" + "സ്വയമേവ സ്‌കാൻ ചെയ്യുന്നതിനെ ഇൻപുട്ട് പിന്തുണയ്‌ക്കുന്നില്ല" + "\'%s\' എന്നതിനായി യാന്ത്രിക സ്‌കാൻ ആരംഭിക്കാനായില്ല" + "അടച്ച അടിക്കുറിപ്പുകൾക്കായി സിസ്‌‌റ്റത്തിലെ മുൻഗണനകൾ ആരംഭിക്കാനാകില്ല." + + + "ചാനലുകളൊന്നും ചേർത്തിട്ടില്ല" + "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ" + "ഓൺ" + "ഓഫ്" + "തടഞ്ഞ ചാനലുകൾ" + "എല്ലാം തടയുക" + "തടഞ്ഞതെല്ലാം മാറ്റുക" + "മറച്ച ചാനലുകൾ" + "പ്രോഗ്രാം നിയന്ത്രണങ്ങൾ" + "പിൻ മാറ്റുക" + "റേറ്റിംഗ് സംവിധാനങ്ങൾ" + "റേറ്റിംഗുകള്‍" + "എല്ലാ റേറ്റിംഗ് സംവിധാനങ്ങളും കാണുക" + "മറ്റു രാജ്യങ്ങൾ" + "ഒന്നുമില്ല" + "ഒന്നുമില്ല" + "റേറ്റുചെയ്‌തിട്ടില്ല" + "റേറ്റുചെയ്യാത്ത പ്രോഗ്രാം ബ്ലോക്കുചെയ്യുക" + "ഒന്നുമില്ല" + "ഉയർന്ന നിയന്ത്രണങ്ങൾ" + "ഇടത്തരം നിയന്ത്രണങ്ങൾ" + "പരിമിതമായ നിയന്ത്രണങ്ങൾ" + "ഇഷ്‌ടാനുസൃതം" + "കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" + "മുതിർന്ന കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" + "കൗമാരക്കാർക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" + "സ്വമേധയായുള്ള നിയന്ത്രണങ്ങൾ" + + + "%1$s എന്നതും ഉപറേറ്റിംഗുകളും" + "ഉപറേറ്റിംഗുകൾ" + "ഈ ചാനൽ കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക" + "ഈ പ്രോഗ്രാം കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക" + "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു. ഈ പ്രോഗ്രാം കാണുന്നതിന് നിങ്ങളുടെ പിൻ നൽകുക." + "ഈ പ്രോഗ്രാം റേറ്റുചെയ്‌തിട്ടില്ല. ഇത് കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക" + "നിങ്ങളുടെ പിൻ നൽകുക" + "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ സജ്ജമാക്കാൻ, ഒരു പിൻ സൃഷ്‌ടിക്കുക" + "പുതിയ പിൻ നൽകുക" + "നിങ്ങളുടെ പിൻ സ്ഥിരീകരിക്കുക" + "നിങ്ങളുടെ നിലവിലെ പിൻ നൽകുക" + + നിങ്ങൾ 5 തവണ തെറ്റായ പിൻ നൽകി.\n%1$d സെക്കൻഡിൽ വീണ്ടും ശ്രമിക്കുക. + നിങ്ങൾ 5 തവണ തെറ്റായ പിൻ നൽകി.\n%1$d സെക്കൻഡിൽ വീണ്ടും ശ്രമിക്കുക. + + "നൽകിയ പിൻ തെറ്റാണ്. വീണ്ടും ശ്രമിക്കുക." + "പിൻ യോജിക്കുന്നില്ല, വീണ്ടും ശ്രമിക്കുക" + "നിങ്ങളുടെ തപാൽ കോഡ് നൽകുക." + "ടിവി ചാനലുകൾക്ക് സമ്പൂർണ്ണ പ്രോഗ്രാം ഗൈഡ് നൽകുന്നതിന് തത്സമയ ചാനലുകൾ ആപ്പ്, തപാൽ കോഡ് ഉപയോഗിക്കും." + "നിങ്ങളുടെ തപാൽ കോഡ് നൽകുക" + "തെറ്റായ തപാൽ കോഡ്" + "ക്രമീകരണം" + "ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കൂ" + "നിങ്ങളുടെ പ്രോഗ്രാം ഗൈഡിനായി ചാനലുകൾ തിരഞ്ഞെടുക്കുക" + "ചാനൽ ഉറവിടങ്ങൾ" + "പുതിയ ചാനലുകൾ ലഭ്യമാണ്" + "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ" + "ടൈംഷിഫ്റ്റ്" + "കാണുന്നതിനിടയിൽ റെക്കോർഡുചെയ്യുക, അതുവഴി നിങ്ങൾക്ക് തത്സമയ പ്രോഗ്രാമുകൾ തൽക്കാലം നിർത്താനോ റീവൈൻഡുചെയ്യാനോ കഴിയും.\nമുന്നറിയിപ്പ്: ഇങ്ങനെ ചെയ്യുമ്പോൾ, ഇന്റേണൽ സ്റ്റോറേജ് അധികമായി ഉപയോഗിക്കപ്പെടുന്നതിനാൽ സ്റ്റോറേജിന്റെ ലൈഫ് കുറഞ്ഞേക്കാം." + "ഓപ്പൺ സോഴ്‌സ് ലൈസൻസ്" + "ഫീഡ്‍ബാക്ക് അയയ്ക്കുക" + "പതിപ്പ്" + "ഈ ചാനൽ കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക" + "ഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക" + "ഈ പ്രോഗ്രാം റേറ്റുചെയ്‌തിട്ടില്ല.\nഇത് കാണാൻ, വലതുവശത്ത് അമർത്തിയശേഷം നിങ്ങളുടെ പിൻ നൽകുക" + "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു.\nഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക." + "ഈ ചാനൽ കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." + "ഈ പ്രോഗ്രാം കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." + "ഈ പ്രോഗ്രാം റേറ്റുചെയ്‌തിട്ടില്ല.\nഇത് കാണാൻ ഡിഫോൾട്ട് തത്സമയ ടിവി ആപ്പ് ഉപയോഗിക്കുക." + "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു.\nഈ പ്രോഗ്രാം കാണുന്നതിന് സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." + "പ്രോഗ്രാം തടഞ്ഞു" + "ഈ പ്രോഗ്രാം റേറ്റുചെയ്‌തിട്ടില്ല" + "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു" + "ഓഡിയോ മാത്രം" + "സിഗ്‌നൽ ദുർബലമാണ്" + "ഇന്റർനെറ്റ് കണക്ഷനില്ല" + + മറ്റ് ചാനലുകൾ റെക്കോർഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നതിനാൽ %1$s വരെ ഈ ചാനൽ പ്ലേ ചെയ്യാൻ കഴിയില്ല. \n\nറെക്കോർഡിംഗ് ഷെഡ്യൂൾ ക്രമപ്പെടുത്തുന്നതിന് വലത് അമർത്തുക. + മറ്റൊരു ചാനൽ റെക്കോർഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നതിനാൽ %1$s വരെ ഈ ചാനൽ പ്ലേ ചെയ്യാൻ കഴിയില്ല. \n\nറെക്കോർഡിംഗ് ഷെഡ്യൂൾ ക്രമപ്പെടുത്തുന്നതിന് വലത് അമർത്തുക. + + "ശീർഷകമൊന്നുമില്ല" + "ചാനൽ തടഞ്ഞിരിക്കുന്നു" + "പുതിയത്" + "ഉറവിടങ്ങൾ" + + %1$d ചാനലുകൾ + %1$d ചാനൽ + + "ചാനലുകളൊന്നും ലഭ്യമല്ല" + "പുതിയത്" + "സജ്ജീകരിച്ചിട്ടില്ല" + "കൂടുതൽ ഉറവിടങ്ങൾ സ്വീകരിക്കുക" + "തത്സമയ ചാനലുകൾ നൽകുന്ന ആപ്‌സ് ബ്രൗസുചെയ്യുക" + "ലഭ്യമായ പുതിയ ചാനൽ ഉറവിടങ്ങൾ" + "പുതിയ ചാനൽ ഉറവിടങ്ങളിൽ ആസ്വദിക്കുന്നതിന് കൂടുതൽ ചാനലുകളുണ്ട്.\nഅവയിപ്പോൾ സജ്ജീകരിക്കുകയോ ചാനൽ ഉറവിട ക്രമീകരണത്തിൽ പിന്നീട് സജ്ജീകരിക്കുകയോ ചെയ്യാം." + "ഇപ്പോൾ സജ്ജീകരിക്കുക" + "ശരി, മനസ്സിലായി" + + + "ടിവി മെനു ആക്‌സസ്സ് ചെയ്യാൻ ""\'തിരഞ്ഞെടുക്കുക\' അമർത്തുക""." + "ടിവി ഇൻപുട്ടൊന്നും കണ്ടെത്തിയില്ല" + "ടിവി ഇൻപുട്ട് കണ്ടെത്താനാകില്ല" + "ട്യൂണർ തരം അനുയോജ്യമല്ല. ട്യൂണർ തരം ടിവി ഇൻപുട്ടിനായി തത്സമയ ചാനലുകളുടെ അപ്ലിക്കേഷൻ സമാരംഭിക്കുക." + "ട്യൂൺ ചെയ്യൽ പരാജയപ്പെട്ടു" + "ഈ പ്രവർത്തനം കൈകാര്യം ചെയ്യാൻ ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല." + "എല്ലാ ഉറവിട ചാനലുകളും മറച്ചിരിക്കുന്നു.\nകാണാനായി ഒരു ചാനലെങ്കിലും തിരഞ്ഞെടുക്കുക." + "അപ്രതീക്ഷിതമായി വീഡിയോ ലഭ്യമല്ല" + "കണക്‌റ്റു‌ചെയ്‌തിരിക്കുന്ന ഉപകരണത്തിനുള്ളതാണ് മടങ്ങുക എന്ന കീ. പുറത്തുകടക്കാൻ ഹോം ബട്ടൺ അമർത്തുക." + "TV ലിസ്റ്റിംഗുകൾ വായിക്കുന്നതിന് തത്സമയ ചാനലുകൾക്ക് അനുമതി ആവശ്യമാണ്." + "നിങ്ങളുടെ ഉറവിടങ്ങൾ സജ്ജമാക്കുക" + "പരമ്പരാഗത ടിവി ചാനലുകളുടെയും ആപ്‌സ് നൽകുന്ന സ്‌ട്രീമിംഗ് ചാനലുകളുടെയും അനുഭവമാണ് തത്സമയ ചാനലുകൾ ഒരുമിപ്പിക്കുന്നത്. \n\nഇതിനകം തന്നെ ഇൻസ്റ്റാൾ ചെയ്തിട്ടുള്ള ചാനൽ ഉറവിടങ്ങൾ സജ്ജമാക്കിക്കൊണ്ട് തുടങ്ങുക. അല്ലെങ്കിൽ Google Play സ്റ്റോറിൽ നിന്ന് തത്സമയ ചാനലുകൾ നൽകുന്ന കൂടുതൽ ആപ്‌സ് ബ്രൗസുചെയ്യുക." + "റെക്കോർഡിംഗുകളും ഷെഡ്യൂളുകളും" + "10 മിനിറ്റ്" + "30 മിനിറ്റ്" + "1 മണിക്കൂർ" + "3 മണിക്കൂർ" + "ഏറ്റവും പുതിയത്" + "ഷെഡ്യൂൾചെയ്‌തു" + "സീരീസ്" + "മറ്റുള്ളവ" + "ചാനൽ റെക്കോർഡുചെയ്യാൻ കഴിയില്ല." + "പ്രോഗ്രാം റെക്കോർഡുചെയ്യാൻ കഴിയില്ല." + "റെക്കോർഡുചെയ്യുന്നതിന് %1$s ഷെഡ്യൂൾ ചെയ്തിരിക്കുന്നു" + "ഇപ്പോൾ മുതൽ %2$s വരെ %1$s റെക്കോർഡുചെയ്യുന്നു" + "പൂർണ്ണമായ ഷെഡ്യൂൾ" + + അടുത്ത %1$d ദിവസം + അടുത്ത %1$d ദിവസം + + + %1$d മിനിറ്റ് + %1$d മിനിറ്റ് + + + %1$d പുതിയ റെക്കോർഡിംഗുകൾ + %1$d പുതിയ റെക്കോർഡിംഗ് + + + %1$d റെക്കോർഡിംഗുകൾ + %1$d റെക്കോർഡിംഗ് + + + %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തു + %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തു + + "റെക്കോർഡിംഗ് റദ്ദാക്കുക" + "റെക്കോർഡിംഗ് നിർത്തുക" + "കാ‍ണുക" + "തുടക്കം മുതൽ പ്ലേ ചെയ്യുക" + "പ്ലേ പുനരാരംഭിക്കുക" + "ഇല്ലാതാക്കുക" + "റെക്കോർഡിംഗ് ഇല്ലാതാക്കൂ" + "പുനരാരംഭിക്കുക" + "സീസൺ %1$s" + "ഷെഡ്യൂൾ കാണുക" + "കൂടുതൽ വായിക്കുക" + "റെക്കോർഡിംഗ് ഇല്ലാതാക്കൂ" + "ഇല്ലാതാക്കാൻ ആഗ്രഹിക്കുന്ന എപ്പിസോഡുകൾ തിരഞ്ഞെടുക്കുക. ഇല്ലാതാക്കിക്കഴിഞ്ഞാൽ അവ വീണ്ടെടുക്കാനാവില്ല." + "ഇല്ലാതാക്കാൻ റെക്കോർഡിംഗുകൾ ഒന്നുമില്ല." + "കണ്ടവ തിരഞ്ഞെടുക്കുക" + "എല്ലാം തിരഞ്ഞെടുക്കുക" + "എപ്പിസോഡ് തിരഞ്ഞെടുത്തത് മാറ്റൂ" + "%1$d / %2$d മിനിറ്റ് കണ്ടു" + "%1$d / %2$d സെക്കൻഡ് കണ്ടു" + "ഒരിക്കലും കണ്ടിട്ടില്ല" + + %1$d / %2$d എപ്പിസോഡുകൾ ഇല്ലാതാക്കി + %1$d / %2$d എപ്പിസോഡ് ഇല്ലാതാക്കി + + "മു‌ൻഗണന" + "ഏറ്റവും ഉയർന്നത്" + "ഏറ്റവും കുറഞ്ഞത്" + "നമ്പർ %1$d" + "ചാനലുകൾ" + "ഏതെങ്കിലും" + "മുൻഗണന തിരഞ്ഞെടുക്കുക" + "ഒരേ സമയം നിരവധി പരിപാടികൾ റെക്കോർഡുചെയ്യേണ്ടതുണ്ടെങ്കിൽ, ഉയർന്ന മുൻഗണനകളുള്ള പ്രോഗ്രാമുകൾ മാത്രം റെക്കോർഡുചെയ്യപ്പെടും." + "സംരക്ഷിക്കുക" + "ഒറ്റത്തവണ റെക്കോർഡിംഗുകൾക്ക് ഏറ്റവും ഉയർന്ന മുൻഗണന" + "നിർത്തുക" + "റെക്കോർഡിംഗ് ഷെഡ്യൂൾ കാണുക" + "ഈ ഒരൊറ്റ പ്രോഗ്രാം" + "ഇപ്പോൾ - %1$s" + "മൊത്തം സീരീസ്…" + "എന്തായാലും ഷെഡ്യൂൾ ചെയ്യുക" + "പകരം, ഇത് റെക്കോർഡ് ചെയ്യുക" + "ഈ റെക്കോർഡിംഗ് റദ്ദാക്കുക" + "ഇപ്പോൾ കാണുക" + "റെക്കോർഡിംഗുകൾ ഇല്ലാതാക്കുക…" + "റെക്കോർഡുചെയ്യാവുന്നത്" + "റെക്കോർഡിംഗ് ഷെഡ്യൂൾചെയ്‌തു" + "റെക്കോർഡിംഗ് പൊരുത്തക്കേട്" + "റെക്കോർഡിംഗ്" + "റെക്കോർഡുചെയ്യൽ പരാജയപ്പെട്ടു" + "വായനാ പ്രോഗ്രാമുകൾ" + "സമീപകാല റെക്കോർഡിംഗുകൾ കാണുക" + "%1$s റെക്കോർഡുചെയ്യൽ പൂർണ്ണമായില്ല." + "%1$s, %2$s എന്നിവയുടെ റെക്കോർഡുചെയ്യൽ പൂർണ്ണമായില്ല." + "%1$s, %2$s, %3$s എന്നിവയുടെ റെക്കോർഡുചെയ്യൽ പൂർണ്ണമായില്ല." + "വേണ്ടത്ര സ്റ്റോറേജ് ഇല്ലാത്തതിനാൽ %1$s റെക്കോർഡുചെയ്യൽ പൂർത്തിയായില്ല." + "വേണ്ടത്ര സ്റ്റോറേജ് ഇല്ലാത്തതിനാൽ %1$s, %2$s എന്നിവയുടെ റെക്കോർഡുചെയ്യൽ പൂർത്തിയായില്ല." + "വേണ്ടത്ര സ്റ്റോറേജ് ഇല്ലാത്തതിനാൽ %1$s, %2$s, %3$s എന്നിവയുടെ റെക്കോർഡുചെയ്യൽ പൂർത്തിയായില്ല." + "DVR-ന് കൂടുതൽ സ്റ്റോറേജ് ആവശ്യമാണ്" + "DVR ഉപയോഗിച്ച് നിങ്ങൾക്ക് പ്രോഗ്രാമുകൾ റെക്കോർഡുചെയ്യാനാകും. എന്നിരുന്നാലും, DVR പ്രവർത്തിക്കുന്നതിന് നിങ്ങളുടെ ഉപകരണത്തിൽ ഇപ്പോൾ വേണ്ടത്ര സ്റ്റോറേജ് ഇല്ല. %1$dGB-യോ കൂടുതലോ ഉള്ള ഒരു എക്സ്റ്റേണൽ ഡ്രൈവ് കണക്റ്റുചെയ്യുകയും ഉപകരണ സ്റ്റോറേജായി അത് ഫോർമാറ്റുചെയ്യുന്നതിന് നിർദ്ദേശങ്ങൾ പിന്തുടരുകയും ചെയ്യുക." + "മതിയായ സ്റ്റോറേജ് ഇല്ല" + "മതിയായ സ്റ്റോറേജ് ഇല്ലാത്തതിനാൽ ഈ പ്രോഗ്രാം റെക്കോർഡ് ചെയ്യപ്പെടില്ല. നിലവിലുള്ള റെക്കോർഡിംഗുകളിൽ ചിലത് ഇല്ലാതാക്കിക്കൊണ്ട് ശ്രമിച്ചുനോക്കുക." + "നഷ്ടമായ സ്റ്റോറേജ്" + "റെക്കോർഡിംഗ് നിർത്തണോ?" + "റെക്കോർഡുചെയ്തിട്ടുള്ള ഉള്ളടക്കം സംരക്ഷിക്കപ്പെടും." + "ഈ പ്രോഗ്രാമുമായി പൊരുത്തക്കേട് ഉള്ളതിനാൽ %1$s എന്ന പ്രോഗ്രാമിന്റെ റെക്കോർഡിംഗ് അവസാനിപ്പിക്കും. റെക്കോർഡുചെയ്ത ഉള്ളടക്കം സംരക്ഷിക്കപ്പെടും." + "റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിരിക്കുന്നു, എന്നാൽ പൊരുത്തക്കേടുകൾ ഉണ്ട്" + "റെക്കോർഡിംഗ് ആരംഭിച്ചിരിക്കുന്നു, എന്നാൽ പൊരുത്തക്കേടുകൾ ഉണ്ട്" + "%1$s റെക്കോർഡുചെയ്യപ്പെടും." + "%1$s റെക്കോർഡുചെയ്യുന്നു." + "%1$s പ്രോഗ്രാമിന്റെ ചില ഭാഗങ്ങൾ റെക്കോർഡുചെയ്യപ്പെടില്ല." + "%1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങൾ റെക്കോർഡുചെയ്യപ്പെടില്ല." + "%1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും ഒരു ഷെഡ്യൂളും റെക്കോർഡുചെയ്യപ്പെടില്ല." + + %1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും %3$d ഷെഡ്യൂളുകളും റെക്കോർഡുചെയ്യപ്പെടില്ല. + %1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും %3$d ഷെഡ്യൂളും റെക്കോർഡുചെയ്യപ്പെടില്ല. + + "എന്താണ് റെക്കോർഡുചെയ്യേണ്ടത്?" + "എത്ര സമയം റെക്കോർഡ് ചെയ്യാനാണ് നിങ്ങൾ ആഗ്രഹിക്കുന്നത്?" + "ഇതിനകം തന്നെ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്" + "ഇതേ പ്രോഗ്രാം %1$s-ന് റെക്കോർഡ് ചെയ്യുന്നതിനായി ഇതിനകം തന്നെ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്." + "ഇതിനകം തന്നെ റെക്കോർഡ് ചെയ്തു" + "ഈ പ്രോഗ്രാം ഇതിനകം തന്നെ റെക്കോർഡ് ചെയ്തിട്ടുണ്ട്. DVR ലൈഒബ്രറിയിൽ ഇത് ലഭ്യമാണ്." + "പരമ്പരയുടെ റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്‌തു" + + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. + + + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ അവയിലെ %3$d എണ്ണം റെക്കോർഡുചെയ്യപ്പെടില്ല. + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ അത് റെക്കോർഡ് ചെയ്യപ്പെടില്ല. + + + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ ഈ പരമ്പരയുടെയും മറ്റ് പരമ്പരയുടെയും %3$d എപ്പിസോഡുകൾ റെക്കോർഡുചെയ്യപ്പെടില്ല. + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ ഈ പരമ്പരയുടെയും മറ്റ് പരമ്പരയുടെയും %3$d എപ്പിസോഡുകൾ റെക്കോർഡുചെയ്യപ്പെടില്ല. + + + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ മറ്റ് പരമ്പരയുടെ ഒരു എപ്പിസോഡ് റെക്കോർഡുചെയ്യപ്പെടില്ല. + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ മറ്റ് പരമ്പരയുടെ ഒരു എപ്പിസോഡ് റെക്കോർഡുചെയ്യപ്പെടില്ല. + + + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ മറ്റ് പരമ്പരകളുടെ %3$d എപ്പിസോഡുകൾ റെക്കോർഡുചെയ്യപ്പെടില്ല. + %2$s എന്ന പരമ്പരയുടെ %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്. പൊരുത്തക്കേടുകൾ ഉള്ളതിനാൽ മറ്റ് പരമ്പരകളുടെ %3$d എപ്പിസോഡുകൾ റെക്കോർഡുചെയ്യപ്പെടില്ല. + + "റെക്കോർഡുചെയ്ത പ്രോഗ്രാം കണ്ടെത്തിയില്ല." + "ബന്ധപ്പെട്ട റെക്കോർഡിംഗുകൾ" + + %1$d റെക്കോർഡിംഗുകൾ + %1$d റെക്കോർഡിംഗ് + + " / " + "റെക്കോർഡിംഗ് ഷെഡ്യൂളിൽ നിന്ന് %1$s നീക്കംചെയ്തു" + "ട്യൂണർ പൊരുത്തക്കേടുള്ളതിനാൽ ഭാഗികമായി റെക്കോർഡ് ചെയ്യും." + "ട്യൂണർ പൊരുത്തക്കേടുള്ളതിനാൽ റെക്കോർഡ് ചെയ്യില്ല." + "ഷെഡ്യൂളിൽ ഇതുവരെയും റെക്കോർഡിംഗുകളൊന്നും ഇല്ല.\nപ്രോഗ്രാം ഗൈഡിൽ നിന്ന് നിങ്ങൾക്ക് റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്യാവുന്നതാണ്." + + %1$d റെക്കോർഡിംഗ് പൊരുത്തക്കേടുകൾ + %1$d റെക്കോർഡിംഗ് പൊരുത്തക്കേട് + + "സീരീസ് ക്രമീകരണം" + "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് ആരംഭിക്കുക" + "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് നിർത്തുക" + "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് നിർത്തണോ?" + "റെക്കോർഡുചെയ്ത എപ്പിസോഡുകൾ DVR ലൈബ്രറിയിൽ ലഭ്യമാകുന്നത് തുടരും." + "നിർത്തുക" + "എപ്പിസോഡുകളൊന്നും ഇപ്പോൾ പ്രക്ഷേപണം ചെയ്യുന്നില്ല." + "എപ്പിസോഡുകളൊന്നും ലഭ്യമല്ല.\nലഭ്യമായിക്കഴിഞ്ഞാൽ അവ റെക്കോർഡ് ചെയ്യപ്പെടും." + + (%1$d മിനിറ്റ്) + (%1$d മിനിറ്റ്) + + "ഇന്ന്" + "നാളെ" + "ഇന്നലെ" + "%1$s ഇന്ന്" + "%1$s നാളെ" + "സ്കോർ" + "റെക്കോർഡുചെയ്ത പ്രോഗ്രാമുകൾ" + diff --git a/res/values-ml-v23/strings.xml b/res/values-ml-v23/strings.xml deleted file mode 100644 index 2333cc0f..00000000 --- a/res/values-ml-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "ചാനലുകൾ" - diff --git a/res/values-ml/arrays.xml b/res/values-ml/arrays.xml deleted file mode 100644 index 383837e7..00000000 --- a/res/values-ml/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "സാധാരണം" - "നിറഞ്ഞു" - "സൂം ചെയ്യുക" - - - "എല്ലാ ചാനലുകളും" - "കുടുംബാംഗങ്ങൾ/കുട്ടികൾ" - "സ്‌പോർട്സ്" - "ഷോപ്പിംഗ്" - "സിനിമകൾ" - "ഹാസ്യം" - "യാത്ര" - "നാടകം" - "വിദ്യാഭ്യാസം" - "മൃഗങ്ങൾ/വൈൽഡ്‌‌ലൈഫ്" - "വാർത്ത" - "ഗെയിമിംഗ്" - "കലകള്‍" - "വിനോദം" - "ജീവിതശൈലി" - "സംഗീതം" - "പ്രീമിയർ" - "സാങ്കേതികവിദ്യ/ശാസ്‌ത്രം" - - - "തത്സമയ ചാനലുകൾ" - "ഉള്ളടക്കം കണ്ടെത്താനുള്ള ലളിതമായ മാർഗ്ഗം" - "ആപ്‌സ് ഡൗൺലോഡ് ചെയ്യുക, കൂടുതൽ ചാനലുകൾ സ്വീകരിക്കുക" - "നിങ്ങളുടെ ചാനൽ ലൈൻ-അപ്പ് ഇഷ്ടാനുസൃതമാക്കുക" - - - "ടിവിയിൽ ചാനലുകൾ കാണുന്നത് പോലെ നിങ്ങളുടെ ആപ്‌സിൽ ഉള്ളടക്കം കാണുക." - "പരിചിത ഗൈഡും ഉപയോഗലാളിത്യമുള്ള ഇന്റർഫേസും ഉപയോഗിച്ച് നിങ്ങളുടെ ആപ്‌സിൽ നിന്ന് ഉള്ളടക്കം ബ്രൗസുചെയ്യുക, \nടിവിയിൽ ചാനലുകൾ കാണുന്നത് പോലെ തന്നെയാണിത്." - "തത്സമയ ചാനലുകൾ നൽകുന്ന ആപ്‌സ് ഇൻസ്റ്റാൾ ചെയ്തുകൊണ്ട് കൂടുതൽ ചാനലുകൾ ചേർക്കുക. \nടിവി മെനുവിനുള്ളിലെ ലിങ്ക് ഉപയോഗിച്ചുകൊണ്ട് Google Play സ്റ്റോറിൽ അനുയോജ്യമായ ആപ്‌സ് കണ്ടെത്തുക." - "നിങ്ങളുടെ ചാനൽ ലിസ്റ്റ് ഇഷ്ടാനുസൃതമാക്കുന്നതിന് നിങ്ങൾ പുതിയതായി ഇൻസ്റ്റാൾ ചെയ്തിട്ടുള്ള ചാനൽ ഉറവിടങ്ങൾ സജ്ജമാക്കുക. \nആരംഭിക്കുന്നതിന്, ക്രമീകരണത്തിനുള്ളിൽ ചാനൽ ഉറവിടങ്ങൾ തിരഞ്ഞെടുക്കുക." - - diff --git a/res/values-ml/rating_system_strings.xml b/res/values-ml/rating_system_strings.xml deleted file mode 100644 index e17f91f8..00000000 --- a/res/values-ml/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "പ്രോഗ്രാമുകളിൽ 15 വയസ്സിൽ താഴെയുള്ള പ്രേക്ഷകർക്ക് ഉചിതമായേക്കാത്ത ഉള്ളടക്കം അടങ്ങിയേക്കാമെന്നതിനാൽ അവർക്കായി രക്ഷാകർതൃ വിവേചനാധികാരം ഉപയോഗിക്കേണ്ടതാണ്." - "പ്രോഗ്രാമുകളിൽ 19 വയസ്സിൽ താഴെയുള്ള പ്രേക്ഷകർക്ക് ഉചിതമായേക്കാത്ത ഉള്ളടക്കം അടങ്ങിയേക്കാമെന്നതിനാൽ 19 വയസ്സിൽ താഴെയുള്ള പ്രായക്കാർക്ക് ഇവ അനുയോജ്യമല്ല." - "ലൈംഗികസ്‌പഷ്‌ടമായ സംഭാഷണം" - "മോശമായ ഭാഷ" - "ലൈംഗിക ഉള്ളടക്കം" - "അക്രമം" - "ഭാവനാരൂപത്തിലുള്ള അക്രമം" - "ഈ പ്രോഗ്രാം എല്ലാ കുട്ടികൾക്കും ഉചിതമായ രീതിയിൽ രൂപകൽപ്പനചെയ്‌തിരിക്കുന്നു." - "7-നും അതിനുമുകളിലും പ്രായമുള്ള കുട്ടികൾക്കായി ഈ പ്രോഗ്രാം രൂപകൽപ്പന ചെയ്‌തിരിക്കുന്നു." - "എല്ലാ പ്രായക്കാർക്കും ഈ പ്രോഗ്രാം അനുയോജ്യമാണെന്ന് മിക്ക മാതാപിതാക്കളും പരിഗണിക്കുന്നു." - "ചെറിയ പ്രായത്തിലുള്ള കുട്ടികൾക്ക് അനുയോജ്യമല്ലെന്ന് പല മാതാപിതാക്കളും പരിഗണിച്ചേക്കാവുന്ന കുറച്ച് ഉള്ളടക്കങ്ങൾ ഈ പ്രോഗ്രാമിലുണ്ട്. കുട്ടികളിത് കാണുമ്പോൾ കൂടെ ഉണ്ടാകണമെന്നാണ് പല മാതാപിതാക്കളും ആഗ്രഹിച്ചേക്കുക." - "14 വയസ്സിന് താഴെയുള്ള കുട്ടികൾക്ക് അനുയോജ്യമല്ലെന്ന് പല മാതാപിതാക്കളും പരിഗണിക്കുന്ന കുറച്ച് ഉള്ളടക്കങ്ങൾ ഈ പ്രോഗ്രാമിലുണ്ട്." - "ഈ പ്രോഗ്രാം മുതിർന്നവർക്ക് കാണുന്നതിനായി പ്രത്യേകമായി തയ്യാറാക്കിയിട്ടുള്ളതായതിനാൽ 17 വയസ്സിന് താഴെയുള്ള കുട്ടികൾക്ക് ഇത് അനുയോജ്യമാകണമെന്നില്ല." - "ഫിലിം റേറ്റിംഗുകൾ" - "സാധാരണ പ്രേക്ഷകർക്കുള്ളത്. കുട്ടികളിത് കാണുന്നതിന് മാതാപിതാക്കൾക്ക് പ്രശ്നമൊന്നും ഉണ്ടാകില്ല." - "രക്ഷാകർതൃ മാർഗ്ഗനിർദ്ദേശം നിർദ്ദേശിച്ചിരിക്കുന്നു. ചെറിയ പ്രായത്തിലുള്ള കുട്ടികൾ കാണുന്നത് മാതാപിതാക്കൾക്ക് രസിക്കാത്ത ചില ഉള്ളടക്കങ്ങൾ ഇതിൽ ഉണ്ടായേക്കാം." - "മാതാപിതാക്കൾക്ക് ശക്തമായി മുന്നറിയിപ്പ് നൽകുന്നു. കൗമാരപ്രായമെത്താത്ത കുട്ടികൾക്ക് അനുചിതമായേക്കാവുന്ന ചില ഉള്ളടക്കങ്ങൾ ഇതിലുണ്ട്." - "നിയന്ത്രിതം, പ്രായപൂർത്തിയായവർക്കുള്ള ചില ഉള്ളടക്കങ്ങൾ ഇതിലുണ്ട്. ഈ സിനിമ കാണുന്നതിന് കുട്ടികളെ കൊണ്ടുപോകുന്നതിന് മുമ്പായി സിനിമയെ കുറിച്ച് കൂടുതലറിയുവാൻ മാതാപിതാക്കളോട് നിർദ്ദേശിക്കുന്നു." - "17 വയസ്സും അതിൽ താഴെയും പ്രായമുള്ളവരെ അനുവദിക്കില്ല. വ്യക്തമായും പ്രായപൂർത്തിയായവർക്ക് മാത്രം. കുട്ടികളെ അനുവദിക്കില്ല." - diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml deleted file mode 100644 index e29a8755..00000000 --- a/res/values-ml/strings.xml +++ /dev/null @@ -1,348 +0,0 @@ - - - - - "മോണോ" - "സ്‌റ്റീരിയോ" - "പ്ലേ നിയന്ത്രണങ്ങൾ" - "ഏറ്റവും പുതിയ ചാനലുകൾ" - "ടിവി ഓപ്‌ഷനുകൾ" - "PIP ഓ‌പ്‌ഷനുകൾ" - "ഈ ചാനലിന് പ്ലേ നിയന്ത്രണങ്ങൾ ലഭ്യമല്ല" - "പ്ലേ ചെയ്യുക അല്ലെങ്കിൽ താല്‍‌ക്കാലികമായി നിര്‍‌ത്തുക" - "വേഗത്തിലുള്ള കൈമാറൽ" - "റിവൈൻഡുചെയ്യുക" - "അടുത്തത്" - "മുമ്പത്തെ" - "പ്രോഗ്രാം സഹായി" - "പുതിയ ചാനലുകൾ ലഭ്യമാണ്" - "%1$s തുറക്കുക" - "അടച്ച അടിക്കുറിപ്പുകൾ" - "ഡിസ്‌പ്ലേ മോഡ്" - "PIP" - "ഓണാണ്" - "ഓഫാണ്" - "മൾട്ടി ഓഡിയോ" - "കൂടുതൽ ചാനൽ സ്വീകരിക്കൂ" - "ക്രമീകരണം" - "ഉറവിടം" - "സ്വാപ്പുചെയ്യുക" - "ഓണാണ്" - "ഓഫാണ്" - "ശബ്ദം" - "പ്രധാന വിൻഡോ" - "PIP വിൻഡോ" - "ലേ‌ഔട്ട്" - "ചുവടെ വലതുഭാഗത്ത്" - "മുകളില്‍‌ വലതുഭാഗത്ത്" - "മുകളില്‍‌ ഇടതുഭാഗത്ത്" - "ചുവടെ ഇടതുഭാഗത്ത്" - "വശങ്ങളിലായി" - "വലുപ്പം" - "വലുത്" - "ചെറുത്" - "ഇൻപുട്ട് ഉറവിടം" - "ടിവി (ആന്റിന/കേബിൾ)" - "പ്രോഗ്രാം വിവരമൊന്നുമില്ല" - "വിവരമൊന്നുമില്ല" - "തടഞ്ഞ ചാനൽ" - "അറിയാത്ത ഭാഷ" - "അടച്ച അടിക്കുറിപ്പുകൾ" - "ഓഫ്" - "ഫോർമാറ്റുചെയ്യൽ ഇഷ്‌ടാനുസൃതമാക്കുക" - "അടച്ച അടിക്കുറിപ്പുകൾക്കുള്ള സിസ്‌റ്റം വിസ്‌തൃത മുൻഗണനകൾ സജ്ജമാക്കുക" - "ഡിസ്‌പ്ലേ മോഡ്" - "മൾട്ടി ഓഡിയോ" - "മോണോ" - "സ്‌റ്റീരിയോ" - "5.1 സറൗണ്ട്" - "7.1 സറൗണ്ട്" - "%d ചാനലുകൾ" - "ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കുക" - "ഗ്രൂപ്പ് തിരഞ്ഞെടുക്കുക" - "ഗ്രൂപ്പ് തിരഞ്ഞെടുത്തത് മാറ്റുക" - "ഗ്രൂപ്പ് അനുസരിച്ച്" - "ചാനൽ ഉറവിടം" - "HD/SD" - "HD" - "SD" - "ഗ്രൂപ്പ് അനുസരിച്ച്" - "ഈ പ്രോഗ്രാം തടഞ്ഞിരിക്കുന്നു" - "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു" - "സ്വയമേവ സ്‌കാൻ ചെയ്യുന്നതിനെ ഇൻപുട്ട് പിന്തുണയ്‌ക്കുന്നില്ല" - "\'%s\' എന്നതിനായി യാന്ത്രിക സ്‌കാൻ ആരംഭിക്കാനായില്ല" - "അടച്ച അടിക്കുറിപ്പുകൾക്കായി സിസ്‌‌റ്റത്തിലെ മുൻഗണനകൾ ആരംഭിക്കാനാകില്ല." - - - "ചാനലുകളൊന്നും ചേർത്തിട്ടില്ല" - "ട്യൂണർ" - "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ" - "ഓൺ" - "ഓഫ്" - "തടഞ്ഞ ചാനലുകൾ" - "എല്ലാം തടയുക" - "തടഞ്ഞതെല്ലാം മാറ്റുക" - "മറച്ച ചാനലുകൾ" - "പ്രോഗ്രാം നിയന്ത്രണങ്ങൾ" - "പിൻ മാറ്റുക" - "റേറ്റിംഗ് സംവിധാനങ്ങൾ" - "റേറ്റിംഗുകള്‍" - "എല്ലാ റേറ്റിംഗ് സംവിധാനങ്ങളും കാണുക" - "മറ്റു രാജ്യങ്ങൾ" - "ഒന്നുമില്ല" - "ഒന്നുമില്ല" - "ഒന്നുമില്ല" - "ഉയർന്ന നിയന്ത്രണങ്ങൾ" - "ഇടത്തരം നിയന്ത്രണങ്ങൾ" - "പരിമിതമായ നിയന്ത്രണങ്ങൾ" - "ഇഷ്‌ടാനുസൃതം" - "കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" - "മുതിർന്ന കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" - "കൗമാരക്കാർക്ക് അനുയോജ്യമായ ഉള്ളടക്കം" - "സ്വമേധയായുള്ള നിയന്ത്രണങ്ങൾ" - - - "%1$s എന്നതും ഉപറേറ്റിംഗുകളും" - "ഉപറേറ്റിംഗുകൾ" - "ഈ ചാനൽ കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക" - "ഈ പ്രോഗ്രാം കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക" - "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു. ഈ പ്രോഗ്രാം കാണുന്നതിന് നിങ്ങളുടെ പിൻ നൽകുക." - "നിങ്ങളുടെ പിൻ നൽകുക" - "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ സജ്ജമാക്കാൻ, ഒരു പിൻ സൃഷ്‌ടിക്കുക" - "പുതിയ പിൻ നൽകുക" - "നിങ്ങളുടെ പിൻ സ്ഥിരീകരിക്കുക" - "നിങ്ങളുടെ നിലവിലെ പിൻ നൽകുക" - - നിങ്ങൾ 5 തവണ തെറ്റായ പിൻ നൽകി.\n%1$d സെക്കൻഡിൽ വീണ്ടും ശ്രമിക്കുക. - നിങ്ങൾ 5 തവണ തെറ്റായ പിൻ നൽകി.\n%1$d സെക്കൻഡിൽ വീണ്ടും ശ്രമിക്കുക. - - "നൽകിയ പിൻ തെറ്റാണ്. വീണ്ടും ശ്രമിക്കുക." - "പിൻ യോജിക്കുന്നില്ല, വീണ്ടും ശ്രമിക്കുക" - "ക്രമീകരണം" - "ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കൂ" - "നിങ്ങളുടെ പ്രോഗ്രാം ഗൈഡിനായി ചാനലുകൾ തിരഞ്ഞെടുക്കുക" - "ചാനൽ ഉറവിടങ്ങൾ" - "പുതിയ ചാനലുകൾ ലഭ്യമാണ്" - "രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ" - "ഓപ്പൺ സോഴ്‌സ് ലൈസൻസ്" - "ഓപ്പൺ സോഴ്‌സ് ലൈസൻസുകൾ" - "പതിപ്പ്" - "ഈ ചാനൽ കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക" - "ഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക" - "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു.\nഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക." - "ഈ ചാനൽ കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." - "ഈ പ്രോഗ്രാം കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." - "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു.\nഈ പ്രോഗ്രാം കാണുന്നതിന് സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക." - "പ്രോഗ്രാം തടഞ്ഞു" - "ഈ പ്രോഗ്രാമിനെ %1$s എന്ന് റേറ്റുചെയ്‌തു" - "ഓഡിയോ മാത്രം" - "സിഗ്‌നൽ ദുർബലമാണ്" - "ഇന്റർനെറ്റ് കണക്ഷനില്ല" - - മറ്റ് ചാനലുകൾ റെക്കോർഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നതിനാൽ %1$s വരെ ഈ ചാനൽ പ്ലേ ചെയ്യാൻ കഴിയില്ല. \n\nറെക്കോർഡിംഗ് ഷെഡ്യൂൾ ക്രമപ്പെടുത്തുന്നതിന് വലത് അമർത്തുക. - മറ്റൊരു ചാനൽ റെക്കോർഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നതിനാൽ %1$s വരെ ഈ ചാനൽ പ്ലേ ചെയ്യാൻ കഴിയില്ല. \n\nറെക്കോർഡിംഗ് ഷെഡ്യൂൾ ക്രമപ്പെടുത്തുന്നതിന് വലത് അമർത്തുക. - - "ശീർഷകമൊന്നുമില്ല" - "ചാനൽ തടഞ്ഞിരിക്കുന്നു" - "പുതിയത്" - "ഉറവിടങ്ങൾ" - - %1$d ചാനലുകൾ - %1$d ചാനൽ - - "ചാനലുകളൊന്നും ലഭ്യമല്ല" - "പുതിയത്" - "സജ്ജീകരിച്ചിട്ടില്ല" - "കൂടുതൽ ഉറവിടങ്ങൾ സ്വീകരിക്കുക" - "തത്സമയ ചാനലുകൾ നൽകുന്ന ആപ്‌സ് ബ്രൗസുചെയ്യുക" - "ലഭ്യമായ പുതിയ ചാനൽ ഉറവിടങ്ങൾ" - "പുതിയ ചാനൽ ഉറവിടങ്ങളിൽ ആസ്വദിക്കുന്നതിന് കൂടുതൽ ചാനലുകളുണ്ട്.\nഅവയിപ്പോൾ സജ്ജീകരിക്കുകയോ ചാനൽ ഉറവിട ക്രമീകരണത്തിൽ പിന്നീട് സജ്ജീകരിക്കുകയോ ചെയ്യാം." - "ഇപ്പോൾ സജ്ജീകരിക്കുക" - "ശരി, മനസ്സിലായി" - - - "ടിവി മെനു ആക്‌സസ്സ് ചെയ്യാൻ ""\'തിരഞ്ഞെടുക്കുക\' അമർത്തുക""." - "ടിവി ഇൻപുട്ടൊന്നും കണ്ടെത്തിയില്ല" - "ടിവി ഇൻപുട്ട് കണ്ടെത്താനാകില്ല" - "PIP പിന്തുണയ്‌ക്കുന്നില്ല" - "PIP-യിൽ കാണിക്കാനാകുന്ന ഇൻപുട്ടൊന്നും ലഭ്യമല്ല" - "ട്യൂണർ തരം അനുയോജ്യമല്ല. ട്യൂണർ തരം ടിവി ഇൻപുട്ടിനായി തത്സമയ ചാനലുകളുടെ അപ്ലിക്കേഷൻ സമാരംഭിക്കുക." - "ട്യൂൺ ചെയ്യൽ പരാജയപ്പെട്ടു" - "ഈ പ്രവർത്തനം കൈകാര്യം ചെയ്യാൻ ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല." - "എല്ലാ ഉറവിട ചാനലുകളും മറച്ചിരിക്കുന്നു.\nകാണാനായി ഒരു ചാനലെങ്കിലും തിരഞ്ഞെടുക്കുക." - "അപ്രതീക്ഷിതമായി വീഡിയോ ലഭ്യമല്ല" - "കണക്‌റ്റു‌ചെയ്‌തിരിക്കുന്ന ഉപകരണത്തിനുള്ളതാണ് മടങ്ങുക എന്ന കീ. പുറത്തുകടക്കാൻ ഹോം ബട്ടൺ അമർത്തുക." - "TV ലിസ്റ്റിംഗുകൾ വായിക്കുന്നതിന് തത്സമയ ചാനലുകൾക്ക് അനുമതി ആവശ്യമാണ്." - "നിങ്ങളുടെ ഉറവിടങ്ങൾ സജ്ജമാക്കുക" - "പരമ്പരാഗത ടിവി ചാനലുകളുടെയും ആപ്‌സ് നൽകുന്ന സ്‌ട്രീമിംഗ് ചാനലുകളുടെയും അനുഭവമാണ് തത്സമയ ചാനലുകൾ ഒരുമിപ്പിക്കുന്നത്. \n\nഇതിനകം തന്നെ ഇൻസ്റ്റാൾ ചെയ്തിട്ടുള്ള ചാനൽ ഉറവിടങ്ങൾ സജ്ജമാക്കിക്കൊണ്ട് തുടങ്ങുക. അല്ലെങ്കിൽ Google Play സ്റ്റോറിൽ നിന്ന് തത്സമയ ചാനലുകൾ നൽകുന്ന കൂടുതൽ ആപ്‌സ് ബ്രൗസുചെയ്യുക." - "റെക്കോർഡിംഗുകളും ഷെഡ്യൂളുകളും" - "10 മിനിറ്റ്" - "30 മിനിറ്റ്" - "1 മണിക്കൂർ" - "3 മണിക്കൂർ" - "ഏറ്റവും പുതിയത്" - "ഷെഡ്യൂൾചെയ്‌തു" - "സീരീസ്" - "മറ്റുള്ളവ" - "ചാനൽ റെക്കോർഡുചെയ്യാൻ കഴിയില്ല." - "പ്രോഗ്രാം റെക്കോർഡുചെയ്യാൻ കഴിയില്ല." - "റെക്കോർഡുചെയ്യുന്നതിന് %1$s ഷെഡ്യൂൾ ചെയ്തിരിക്കുന്നു" - "ഇപ്പോൾ മുതൽ %2$s വരെ %1$s റെക്കോർഡുചെയ്യുന്നു" - "പൂർണ്ണമായ ഷെഡ്യൂൾ" - - അടുത്ത %1$d ദിവസം - അടുത്ത %1$d ദിവസം - - - %1$d മിനിറ്റ് - %1$d മിനിറ്റ് - - - %1$d പുതിയ റെക്കോർഡിംഗുകൾ - %1$d പുതിയ റെക്കോർഡിംഗ് - - - %1$d റെക്കോർഡിംഗുകൾ - %1$d റെക്കോർഡിംഗ് - - - %1$d റെക്കോർഡിംഗുകൾ ഷെഡ്യൂൾ ചെയ്തു - %1$d റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തു - - "കാ‍ണുക" - "തുടക്കം മുതൽ പ്ലേ ചെയ്യുക" - "പ്ലേ പുനരാരംഭിക്കുക" - "ഇല്ലാതാക്കുക" - "റെക്കോർഡിംഗ് ഇല്ലാതാക്കൂ" - "പുനരാരംഭിക്കുക" - "സീസൺ %1$s" - "ഷെഡ്യൂൾ കാണുക" - "കൂടുതൽ വായിക്കുക" - "റെക്കോർഡിംഗ് ഇല്ലാതാക്കൂ" - "ഇല്ലാതാക്കാൻ ആഗ്രഹിക്കുന്ന എപ്പിസോഡുകൾ തിരഞ്ഞെടുക്കുക. ഇല്ലാതാക്കിക്കഴിഞ്ഞാൽ അവ വീണ്ടെടുക്കാനാവില്ല." - "ഇല്ലാതാക്കാൻ റെക്കോർഡിംഗുകൾ ഒന്നുമില്ല." - "കണ്ടവ തിരഞ്ഞെടുക്കുക" - "എല്ലാം തിരഞ്ഞെടുക്കുക" - "എപ്പിസോഡ് തിരഞ്ഞെടുത്തത് മാറ്റൂ" - "%1$d / %2$d മിനിറ്റ് കണ്ടു" - "%1$d / %2$d സെക്കൻഡ് കണ്ടു" - "ഒരിക്കലും കണ്ടിട്ടില്ല" - - %1$d / %2$d എപ്പിസോഡുകൾ ഇല്ലാതാക്കി - %1$d / %2$d എപ്പിസോഡ് ഇല്ലാതാക്കി - - "മു‌ൻഗണന" - "ഏറ്റവും ഉയർന്നത്" - "ഏറ്റവും കുറഞ്ഞത്" - "നമ്പർ %1$d" - "ചാനലുകൾ" - "ഏതെങ്കിലും" - "മുൻഗണന തിരഞ്ഞെടുക്കുക" - "ഒരേ സമയം നിരവധി പരിപാടികൾ റെക്കോർഡുചെയ്യേണ്ടതുണ്ടെങ്കിൽ, ഉയർന്ന മുൻഗണനകളുള്ള പ്രോഗ്രാമുകൾ മാത്രം റെക്കോർഡുചെയ്യപ്പെടും." - "സംരക്ഷിക്കുക" - "ഒറ്റത്തവണ റെക്കോർഡിംഗുകൾക്ക് ഏറ്റവും ഉയർന്ന മുൻഗണന" - "റദ്ദാക്കൂ" - "റദ്ദാക്കുക" - "മറക്കുക" - "നിർത്തുക" - "റെക്കോർഡിംഗ് ഷെഡ്യൂൾ കാണുക" - "ഈ ഒരൊറ്റ പ്രോഗ്രാം" - "ഇപ്പോൾ - %1$s" - "മൊത്തം സീരീസ്…" - "എന്തായാലും ഷെഡ്യൂൾ ചെയ്യുക" - "പകരം, ഇത് റെക്കോർഡ് ചെയ്യുക" - "ഈ റെക്കോർഡിംഗ് റദ്ദാക്കുക" - "ഇപ്പോൾ കാണുക" - "റെക്കോർഡുചെയ്യാവുന്നത്" - "റെക്കോർഡിംഗ് ഷെഡ്യൂൾചെയ്‌തു" - "റെക്കോർഡിംഗ് പൊരുത്തക്കേട്" - "റെക്കോർഡിംഗ്" - "റെക്കോർഡുചെയ്യൽ പരാജയപ്പെട്ടു" - "റെക്കോർഡിംഗ് ഷെഡ്യൂളുകൾ സൃഷ്ടിക്കാൻ പ്രോഗ്രാമുകൾ വായിക്കുന്നു" - "വായനാ പ്രോഗ്രാമുകൾ" - - - "DVR-ന് കൂടുതൽ സ്റ്റോറേജ് ആവശ്യമാണ്" - "DVR ഉപയോഗിച്ച് നിങ്ങൾക്ക് പ്രോഗ്രാമുകൾ റെക്കോർഡുചെയ്യാനാകും. എന്നിരുന്നാലും, DVR പ്രവർത്തിക്കുന്നതിന് നിങ്ങളുടെ ഉപകരണത്തിൽ ഇപ്പോൾ വേണ്ടത്ര സ്റ്റോറേജ് ഇല്ല. %1$sGB-യോ കൂടുതലോ ഉള്ള ഒരു എക്സ്റ്റേണൽ ഡ്രൈവ് കണക്റ്റുചെയ്യുകയും ഉപകരണ സ്റ്റോറേജായി അത് ഫോർമാറ്റുചെയ്യുന്നതിന് നിർദ്ദേശങ്ങൾ പിന്തുടരുകയും ചെയ്യുക." - "നഷ്ടമായ സ്റ്റോറേജ്" - "DVR ഉപയോഗിക്കുന്ന സ്റ്റോറേജിന്റെ ചിലത് നഷ്ടമായിരിക്കുന്നു. വീണ്ടും DVR പ്രവർത്തനക്ഷമമാക്കുന്നതിന്, നിങ്ങൾ മുമ്പ് ഉപയോഗിച്ച എക്സ്റ്റേണൽ ഡ്രൈവ് കണക്റ്റുചെയ്യുക. സ്റ്റോറേജ് തുടർന്നങ്ങോട്ട് ലഭ്യമല്ലെങ്കിൽ, ഇതരമാർഗ്ഗമെന്ന നിലയിൽ, നിങ്ങൾക്ക് മറക്കുന്നതിന് തിരഞ്ഞെടുക്കാവുന്നതാണ്." - "സ്റ്റോറേജ് മറക്കണോ?" - "നിങ്ങളുടെ റെക്കോർഡ് ചെയ്തിട്ടുള്ള എല്ലാ ഉള്ളടക്കവും ഷെഡ്യൂളുകളും നഷ്ടപ്പെടും." - "റെക്കോർഡിംഗ് നിർത്തണോ?" - "റെക്കോർഡുചെയ്തിട്ടുള്ള ഉള്ളടക്കം സംരക്ഷിക്കപ്പെടും." - - - "റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്തിരിക്കുന്നു, എന്നാൽ പൊരുത്തക്കേടുകൾ ഉണ്ട്" - "റെക്കോർഡിംഗ് ആരംഭിച്ചിരിക്കുന്നു, എന്നാൽ പൊരുത്തക്കേടുകൾ ഉണ്ട്" - "%1$s റെക്കോർഡുചെയ്യപ്പെടും." - "%1$s റെക്കോർഡുചെയ്യുന്നു." - "%1$s പ്രോഗ്രാമിന്റെ ചില ഭാഗങ്ങൾ റെക്കോർഡുചെയ്യപ്പെടില്ല." - "%1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങൾ റെക്കോർഡുചെയ്യപ്പെടില്ല." - "%1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും ഒരു ഷെഡ്യൂളും റെക്കോർഡുചെയ്യപ്പെടില്ല." - - %1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും %3$d ഷെഡ്യൂളുകളും റെക്കോർഡുചെയ്യപ്പെടില്ല. - %1$s, %2$s എന്നീ പ്രോഗ്രാമുകളുടെ ചില ഭാഗങ്ങളും %3$d ഷെഡ്യൂളും റെക്കോർഡുചെയ്യപ്പെടില്ല. - - "എന്താണ് റെക്കോർഡുചെയ്യേണ്ടത്?" - "എത്ര സമയം റെക്കോർഡ് ചെയ്യാനാണ് നിങ്ങൾ ആഗ്രഹിക്കുന്നത്?" - "ഇതിനകം തന്നെ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്" - "ഇതേ പ്രോഗ്രാം %1$s-ന് റെക്കോർഡ് ചെയ്യുന്നതിനായി ഇതിനകം തന്നെ ഷെഡ്യൂൾ ചെയ്തിട്ടുണ്ട്." - "ഇതിനകം തന്നെ റെക്കോർഡ് ചെയ്തു" - "ഈ പ്രോഗ്രാം ഇതിനകം തന്നെ റെക്കോർഡ് ചെയ്തിട്ടുണ്ട്. DVR ലൈഒബ്രറിയിൽ ഇത് ലഭ്യമാണ്." - - - - - - - - - "റെക്കോർഡുചെയ്ത പ്രോഗ്രാം കണ്ടെത്തിയില്ല." - "ബന്ധപ്പെട്ട റെക്കോർഡിംഗുകൾ" - "(പ്രോഗ്രാം വിവരണമില്ല)" - - %1$d റെക്കോർഡിംഗുകൾ - %1$d റെക്കോർഡിംഗ് - - " / " - "റെക്കോർഡിംഗ് ഷെഡ്യൂളിൽ നിന്ന് %1$s നീക്കംചെയ്തു" - "ട്യൂണർ പൊരുത്തക്കേടുള്ളതിനാൽ ഭാഗികമായി റെക്കോർഡ് ചെയ്യും." - "ട്യൂണർ പൊരുത്തക്കേടുള്ളതിനാൽ റെക്കോർഡ് ചെയ്യില്ല." - "ഷെഡ്യൂളിൽ ഇതുവരെയും റെക്കോർഡിംഗുകളൊന്നും ഇല്ല.\nപ്രോഗ്രാം ഗൈഡിൽ നിന്ന് നിങ്ങൾക്ക് റെക്കോർഡിംഗ് ഷെഡ്യൂൾ ചെയ്യാവുന്നതാണ്." - - %1$d റെക്കോർഡിംഗ് പൊരുത്തക്കേടുകൾ - %1$d റെക്കോർഡിംഗ് പൊരുത്തക്കേട് - - "സീരീസ് ക്രമീകരണം" - "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് ആരംഭിക്കുക" - "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് നിർത്തുക" - "സീരീസ് റെക്കോർഡുചെയ്യുന്നത് നിർത്തണോ?" - "റെക്കോർഡുചെയ്ത എപ്പിസോഡുകൾ DVR ലൈബ്രറിയിൽ ലഭ്യമാകുന്നത് തുടരും." - "നിർത്തുക" - "എപ്പിസോഡുകളൊന്നും ലഭ്യമല്ല.\nലഭ്യമായിക്കഴിഞ്ഞാൽ അവ റെക്കോർഡ് ചെയ്യപ്പെടും." - - (%1$d മിനിറ്റ്) - (%1$d മിനിറ്റ്) - - "ഇന്ന്" - "നാളെ" - "ഇന്നലെ" - "%1$s ഇന്ന്" - "%1$s നാളെ" - "സ്കോർ" - diff --git a/res/values-mn-rMN-v23/strings.xml b/res/values-mn-rMN-v23/strings.xml new file mode 100644 index 00000000..7da24abf --- /dev/null +++ b/res/values-mn-rMN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Суваг" + diff --git a/res/values-mn-rMN/arrays.xml b/res/values-mn-rMN/arrays.xml new file mode 100644 index 00000000..3d0b16ba --- /dev/null +++ b/res/values-mn-rMN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Энгийн" + "Дүүрэн" + "Томруулах" + + + "Бүх суваг" + "Гэр бүл/Хүүхдүүд" + "Спорт" + "Худалдаа" + "Кино" + "Инээдмийн" + "Аялал" + "Драма" + "Боловсрол" + "Амьтад/Зэрлэг байгаль" + "Мэдээ" + "Тоглоом" + "Урлаг" + "Цэнгээн" + "Амьдралын хэв маяг" + "Дуу хөгжим" + "Эхний" + "Технологи/ШУ" + + + "Шууд суваг" + "Агуулга хайх энгийн арга" + "Апп татаж, илүү олон суваг авах" + "Сувгийнхаа дэс дарааллыг өөрчлөх" + + + "TV-дээ суваг үздэг шиг апп-с агуулга үзээрэй." + "TV суваг шиг \nТанил гарын авлага, боломжит харагдах байдлын тусламжтайгаар апп-аасаа агуулга хайгаарай." + "Шууд суваг үзэж болох апп суулгаж, илүү олон суваг нэмээрэй. \nGoogle Play Store-с тохирох апп олохын тулд TV цэсний холбоосыг ашиглаарай." + "Сувгийн жагсаалтаа өөрчлөхийн тулд шинээр суулгасан сувгийн эх сурвалжаа тохируулаарай. \nЭхлүүлэхийн тулд тохиргооны цэснээс сувгийн эх сурвалжийг сонгоорой." + + diff --git a/res/values-mn-rMN/rating_system_strings.xml b/res/values-mn-rMN/rating_system_strings.xml new file mode 100644 index 00000000..7c883a13 --- /dev/null +++ b/res/values-mn-rMN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Хөтөлбөрт 15-с доош насныханд тохиромжгүй материал агуулсан тул эцэг эхийн хяналт шаардлагатай." + "Хөтөлбөрт 19-с дээш насныханд зориулсан материал агуулсан тул 19-с доош насныхан үзэхэд тохиромжгүй." + "Уруу татсан яриа" + "Бүдүүлэг яриа" + "Секс контент" + "Хүчирхийлэл" + "Уран зөгнөлт хэлбэрээр үзүүлсэн хүчирхийлэл" + "Энэ хөтөлбөрийг бүх насны хүүхдэд зориулан бүтээсэн." + "Энэ хөтөлбөрийг 7 болон түүнээс дээш насны хүүхдэд зориулан бүтээсэн." + "Ихэнх эцэг эхчүүд энэ хөтөлбөрийг бүх насанд тохиромжтой гэж үзнэ." + "Энэ хөтөлбөрт зарим эцэг эхчүүдийн хувьд 14-с доош насны хүүхэд үзэхэд тохиромжгүй гэж болох материал агуулсан. Олон эцэг эх хүүхдүүдтэйгээ хамт үзэхийг хүсэж магадгүй юм." + "Энэ хөтөлбөрт зарим эцэг эхчүүдийн хувьд 14-с доош насны хүүхэд үзэхэд тохиромжгүй гэж болох материал агуулсан." + "Энэ хөтөлбөрийг насанд хүрэгчдэд зориулан бэлтгэсэн тул 17-с доош насны хүүхдэд тохиромжгүй." + "Киноны зэрэглэл" + "Энгийн үзэгчид. Эцэг эхийн санаа зовоох, хүүхдэд тохиромжгүй агуулга байхгүй" + "Эцэг эхийн зөвшөөрөл шаардлагатай. Зарим материал хүүхдэд тохиромжгүй байж болзошгүй." + "Эцэг эхийн хяналт онцгой шаардлагатай. Зарим материал нь өсвөр наснаас өмнөх насныханд тохиромжгүй." + "Хориглосон. Насанд хүрэгчдэд зориулсан агуулгатай. Хүүхдүүдээ авч явахаасаа өмнө киноны тухай судална уу." + "17 ба түүнээс доош настай бол зөвшөөрөхгүй. Зөвхөн насанд хүрэгчдэд. Хүүхдүүдэд зөвшөөрөхгүй." + diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml new file mode 100644 index 00000000..3416ea89 --- /dev/null +++ b/res/values-mn-rMN/strings.xml @@ -0,0 +1,355 @@ + + + + + "моно" + "стерео" + "Play хяналт" + "Сувгууд" + "ТВ-н сонголтууд" + "Энэ сувагт тоглуулах хяналтыг ашиглах боломжгүй байна" + "Тоглуулах эсвэл түр зогсоох" + "Хурдан урагшлуулах" + "Хураах" + "Дараах" + "Өмнөх" + "Хөтөлбөр" + "Шинэ суваг нээлттэй байна" + "%1$s-г нээх" + "Хаалттай капшн" + "Дэлгэцийн горим" + "PIP" + "Мульти-аудио" + "Нэмж суваг авах" + "Тохиргоо" + "TВ (Антен /Кабел)" + "Хөтөлбөрийн мэдээлэл байхгүй" + "Мэдээлэл байхгүй" + "Хаагдсан суваг" + "Үл мэдэгдэх хэл" + "Хаалттай тайлбар %1$d" + "Хаалттай тайлбар" + "Идэвхгүй" + "Форматыг тааруулах" + "Системийн хэмжээнд тохируулга хийх" + "Дэлгэцийн горим" + "Мульти-аудио" + "моно" + "стерео" + "5.1 surround" + "7.1 Surround" + "%d суваг" + "Сувгийг тохируулах" + "Бүлэг сонгох" + "Бүлгийн сонголтыг цуцлах" + "Бүлэглэх" + "Сувгийн эх сурвалж" + "HD/SD" + "HD" + "SD" + "Бүлэглэх" + "Энэ програмыг блоклосон байна." + "Энэ хөтөлбөр үнэлгээгүй байна" + "Энэ хөтөлбөрийг %1$s-ээр үнэлэгдсэн байна" + "Оролт нь автомат хайлтыг дэмждэггүй" + "\'%s\'-н автомат хайлтыг эхлүүлэх боломжгүй" + "Текстийн системийн түвшний тохируулгыг эхлүүлэх боломжгүй." + + %1$d суваг нэмсэн + %1$d суваг нэмсэн + + "Суваг нэмэгдээгүй" + "Эцэг эхийн хяналт" + "Идэвхтэй" + "Идэвхгүй" + "Суваг хориглогдсон" + "Бүгдийг хориглох" + "Бүх хоригийг авах" + "Нууцалсан сувгууд" + "Хөтөлбөрийн хязгаар" + "PIN өөрчлөх" + "Үнэлгээний систем" + "Үнэлгээ" + "Үнэлгээний систем" + "Бусад улс орон" + "Хоосон" + "Хоосон" + "Үнэлгээгүй" + "Үнэлгээгүй хөтөлбөрийг блоклох" + "Хоосон" + "Хатуу хязгаарлалт" + "Дунд хязгаарлалт" + "Сул хязгаарлалт" + "Тааруулсан" + "Хүүхдэд зориулсан тохиромжтой контент" + "Том хүүхдэд тохиромжтой контент" + "Өсвөр насныханд тохиромжтой контент" + "Гар хязгаарлалт" + + + "%1$s ба дэд-үнэлгээ" + "Дэд-үнэлгээ" + "Энэ сувгийг үзэхийн тулд PIN оруулна уу" + "Энэ хөтөлбөрийг үзэхийн тулд PIN оруулна уу" + "Энэ хөтөлбөр нь %1$s үнэлгээтэй байна. Энэ хөтөлбөрийг үзэхийн тулд ПИН-ээ оруулна уу." + "Энэ хөтөлбөр үнэлгээгүй байна. Энэ хөтөлбөрийг үзэхийн тулд ПИН-ээ оруулна уу" + "Өөрийн PIN оруулна уу" + "Эцэг эхийн хяналт тохируулахын тулд PIN үүсгээрэй" + "Шинэ PIN оруулна уу" + "Өөрийн PIN-г баталгаажуулна уу" + "Одоогийн PIN оруулна уу" + + Та PIN-г 5 удаа буруу оруулсан байна.\n %1$d секундын дараа дахин оролдоно уу. + Та PIN-г 5 удаа буруу оруулсан байна.\n %1$d секундын дараа оролдоно уу. + + "Энэ PIN буруу байна. Дахин оролдоно уу." + "Дахин оролдоно уу, PIN таарахгүй байна" + "ЗИП кодоо оруулна уу." + "ТВ сувагт хөтөлбөрийн хуваарийг бүрнээр нь олгохын тулд Шууд сувгийн апп ЗИП код ашиглах болно." + "ЗИП кодоо оруулна уу" + "ЗИП код буруу байна" + "Тохиргоо" + "Сувгийн жагсаалтыг тохируулах" + "ТВ хөтөлбөрт оруулах сувгуудыг сонгоно уу" + "Сувгийн эх сурвалж" + "Шинэ суваг боломжтой байна" + "Эцэг эхийн хяналт" + "Нөхөж үзэх" + "Шууд хөтөлбөрийг үзэж байх үедээ бичсэнээр түр зогсоох эсвэл ухрааж үзэх боломжтой.\nСануулга: Энэ нь санг эрчимтэй ашигладаг тул дотоод сангийн багтаамжийг бууруулна." + "Нээлттэй эхийн лиценз" + "Санал хүсэлт илгээх" + "Хувилбар" + "Энэ сувгийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу" + "Энэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу" + "Энэ хөтөлбөр үнэлгээгүй байна.\nЭнэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад ПИН-ээ оруулна уу." + "Энэ хөтөлбөр %1$s үнэлгээтэй байна.\nэнэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу." + "Энэ сувгийг үзэхийн тулд үндсэн шууд ТВ-н апп-г ашиглаарай," + "Энэ хөтөлбөрийг үзэхийн тулд үндсэн шууд ТВ-н апп ашиглаарай." + "Энэ хөтөлбөр үнэлгээгүй байна.\nЭнэ хөтөлбөрийг үзэхийн тулд өгөгдмөл Шууд ТВ аппыг ашиглана уу." + "Энэ хөтөлбөр нь %1$s үнэлгээтэй.\nЭнэ хөтөлбөрийг үзэхийн тулд үндсэн шууд ТВ-н апп-г ашиглаарай." + "Хөтөлбөр хориглогдсон" + "Энэ хөтөлбөр үнэлгээгүй байна" + "Энэ хөтөлбөрийг %1$s-ээр үнэлэгдсэн байна" + "Зөвхөн аудио" + "Дохио муу" + "Интернэт холболт байхгүй" + + Бусад сувгийг бичиж байгаа тул энэ сувгийг %1$s хүртэл тоглуулах боломжгүй. \n\nБичих хуваарийг тохируулахын тулд Баруун товчлуурыг дарна уу. + Өөр сувгийг бичиж байгаа тул энэ сувгийг %1$s хүртэл тоглуулах боломжгүй. \n\nБичих хуваарийг тохируулахын тулд Баруун товчлуурыг дарна уу. + + "Гарчиггүй" + "Сувгийг хориглосон" + "Шинэ" + "Эх сурвалж" + + %1$d суваг + %1$d суваг + + "Ямар ч суваг байхгүй байна" + "Шинэ" + "Тохируулаагүй" + "Бусад сурвалж авах" + "Шууд сувгийн апп хайх" + "Шинэ сувгийн эх сурвалж боломжтой байна" + "Шинэ сувгийн эх сурвалжууд суваг санал болгож байна.\nТа эдгээрийг эх сурвалжийн тохиргоо хэсэгт одоо эсвэл дараа нь тохируулаарай." + "Одоо тохируулах" + "За, ойлголоо" + + + "ТВ цэс рүү хандахын тулд ""СОНГОХ гэснийг дарна уу" + "ТВ оролт олдсонгүй." + "ТВ оролтыг олж чадахгүй байна." + "Дуу тохируулагчийн төрөл тохирохгүй байна. Зурагтын оролтын дуу тохируулагчийн төрөлд зориулан Шууд Сувгуудын апликейшнийг эхлүүл." + "Дуу тааруулж чадсангүй." + "Энэ үйлдлийг гүйцэтгэх апп олдсонгүй." + "Бүх эх сурвалж сувгуудыг нууцалсан.\nҮзэхийн тулд дор хаяж нэг суваг сонгоно уу." + "Энэ видеог үзэх боломжгүй болсон." + "Буцах товчийг холбогдсон төхөөрөмжүүдэд ашиглана. Гарахын тулд Нүүр товчийг дарна уу." + "Шууд сувагт TВ-н жагсаалтыг унших зөвшөөрөл шаардлагатай." + "Эх сурвалжаа тохируулах" + "Шууд суваг нь апп-с хангадаг TV-н уламжлалт урсгал сувгийн хэрэглээтэй хосолдог. \n\nЭхлүүлэхийн тулд өмнө нь суулгасан сувгийн эх сурвалжийг тохируулаарай. Эсвэл Google Play Store-с шууд суваг дамжуулдаг бусад апп-г хайгаарай." + "Бичлэг, хуваарь" + "10 минут" + "30 минут" + "1 цаг" + "3 цаг" + "Сүүлийн" + "Хуваарилсан" + "Цуврал" + "Бусад" + "Сувгийг бичих боломжгүй." + "Хөтөлбөрийг бичих боломжгүй." + "%1$s-г бичихээр товлосон" + "%1$s-г одооноос %2$s хүртэл бичиж байна" + "Бүтэн цагийн хуваарь" + + Дараагийн %1$d хоног + Дараагийн %1$d хоног + + + %1$d минут + %1$d минут + + + + + %1$d бичлэг + %1$d бичлэг + + + %1$d бичлэгийг товлосон + %1$d бичлэгийг товлосон + + "Бичлэгийг цуцлах" + "Дүрс бичихийг зогсоох" + "Үзэх" + "Эхнээс нь тоглуулах" + "Үргэлжлүүлэн тоглуулах" + "Устгах" + "Бичлэгийг устгах" + "Үргэлжлүүлэх" + "%1$s-р цуврал" + "Хуваарь харах" + "Дэлгэрэнгүй унших" + "Бичлэгийг устгах" + "Устгах ангийг сонгоно уу. Устгасан тохиолдолд дахин сэргээх боломжгүй." + "Устгах ямар ч бичлэг алга." + "Үзсэн ангийг сонгох" + "Бүх ангийг сонгох" + "Бүх ангийн сонголтыг цуцлах" + "%2$d%1$d минут үзсэн" + "%2$d%1$d секунд үзсэн" + "Огт үзээгүй" + + %2$d-ийн %1$d ангийг устгасан + %2$d-ийн %1$d ангийг устгасан + + "Ач холбогдол" + "Хамгийн өндөр" + "Хамгийн бага" + "Үгүй. %1$d" + "Суваг" + "Дурын" + "Ач холбогдол сонгох" + "Олон хөтөлбөрийг зэрэг бичихээр сонгосон тохиолдолд зөвхөн өндөр ач холбогдолтой хөтөлбөрийг бичнэ." + "Хадгалах" + "Нэг удаагийн бичлэг өндөр ач холбогдолтой" + "Зогсоох" + "Бичих хуваарийг харах" + "Зөвхөн энэ хөтөлбөр" + "одоо - %1$s" + "Бүтэн цуврал..." + "Ямартай ч товлох" + "Оронд нь үүнийг бичих" + "Энэ бичлэгийг цуцлах" + "Одоо үзэх" + "Бичлэгийг устгах..." + "Дүрс бичих боломжтой" + "Дүрс бичихээр товлосон" + "Дүрс бичих боломжгүй" + "Бичиж байна" + "Бичиж чадсангүй" + "Хөтөлбөрийг уншиж байна" + "Сүүлийн бичлэгийг харах" + "%1$s-г бичиж чадсангүй." + "%1$s, %2$s-г бичиж чадсангүй." + "%1$s, %2$s, %3$s-г бичиж чадсангүй." + "Багтаамж хангалтгүйн улмаас %1$s-г бичиж чадсангүй." + "Багтаамж хангалтгүйн улмаас %1$s, %2$s-г бичиж чадсангүй." + "Багтаамж хангалтгүйн улмаас %1$s, %2$s, %3$s-г бичиж чадсангүй." + "DVR-д илүү багтаамж шаардлагатай" + "Та DVR-р хөтөлбөр бичих боломжтой болно. Гэсэн хэдий ч таны төхөөрөмжид DVR ажиллуулах хангалттай багтаамж алга. %1$dгигабайт, эсвэл үүнээс дээш багтаамжтай гадаад драйв холбож, үүнийг төхөөрөмжийн сан болгож хэлбэршүүлэхийн тулд зааврыг дагана уу." + "Хангалттай сан алга" + "Сангийн багтаамж хангалтгүй байгаа тул энэ хөтөлбөрийг бичихгүй. Зарим шаардлагагүй бичлэгийг устгана уу." + "Сан алга" + "Бичлэгийг зогсоох уу?" + "Бичсэн агуулгыг хадгална." + "%1$s-н бичлэгийг энэ хөтөлбөртэй зөрчилдөж байгаа тул зогсоох болно. Бичсэн агуулгыг хадгална." + "Бичихээр товлосон ч зөрчилтэй байна" + "Бичлэг эхлүүлсэн хэдий ч зөрчилтэй байна" + "%1$s-г бичих болно." + "%1$s-г бичиж байна." + "%1$s-н зарим хэсгийг бичихгүй." + "%1$s болон %2$s-н зарим хэсгийг бичихгүй." + "%1$s, %2$s -н зарим хэсэг болон өөр нэг хуваарийг бичихгүй." + + %1$s, %2$s-н зарим хэсэг болон %3$d бусад хуваарийг бичихгүй. + %1$s, %2$s-н зарим хэсэг болон %3$d бусад хуваарийг бичихгүй. + + "Та юу бичих вэ?" + "Та хэр удаан бичих вэ?" + "Аль хэдийн товлосон" + "Үүнтэй ижил хөтөлбөрийг %1$s-д бичихээр товлосон байна." + "Аль хэдийн бичсэн" + "Энэ хөтөлбөрийг аль хэдийн бичсэн байна. Энэ нь DVR санд боломжтой." + "Цувралын бичлэгийг товлосон" + + %2$s%1$d бичлэг товлосон. + %2$s%1$d бичлэг товлосон. + + + %2$s%1$d бичлэг товлосон. Тэдгээрийн %3$d-г зөрчлийн улмаас бичихгүй. + %2$s%1$d бичлэг товлосон. Үүнийг зөрчлийн улмаас бичихгүй. + + + %2$s%1$d бичлэг товлосон. Энэ болон бусад цувралын %3$d ангийг зөрчлийн улмаас бичихгүй. + %2$s%1$d бичлэг товлосон. Энэ болон бусад цувралын %3$d ангийг зөрчлийн улмаас бичихгүй. + + + %2$s%1$d бичлэг товлосон. Бусад цувралын 1 ангийг зөрчлийн улмаас бичихгүй. + %2$s%1$d бичлэг товлосон. Бусад цувралын 1 ангийг зөрчлийн улмаас бичихгүй. + + + %2$s%1$d бичлэг товлосон. Бусад цувралын %3$d ангийг зөрчлийн улмаас бичихгүй. + %2$s%1$d бичлэг товлосон. Бусад цувралын %3$d ангийг зөрчлийн улмаас бичихгүй. + + "Бичсэн хөтөлбөр олдсонгүй." + "Холбоотой бичлэг" + + %1$d бичлэг + %1$d бичлэг + + " / " + "%1$s-г бичих хуваариас хассан" + "Тааруулагчийн алдааны улмаас хэсэгчлэн бичих болно." + "Тааруулагч алдаатай тул бичихгүй." + "Одоогоор хуваарьт бичлэг байхгүй байна.\nТа ТВ хөтөлбөрөөс бичлэг товлох боломжтой." + + %1$d бичлэгийн зөрчил + %1$d бичлэгийн зөрчил + + "Цувралын тохиргоо" + "Цувралыг бичиж эхлэх" + "Цувралыг бичихээ зогсоох" + "Цувралыг бичихээ зогсоох уу?" + "Бичсэн цувралыг DVR санд хадгална." + "Зогсоох" + "Одоо ямар ч ангийг бичээгүй байна." + "Ямар ч анги алга.\nТэд боломжтой болсон үедээ бичих болно." + + (%1$d минут) + (%1$d минут) + + "Өнөөдөр" + "Маргааш" + "Өчигдөр" + "өнөөдрийн %1$s" + "Маргаашийн %1$s" + "Оноо" + "Бичигдсэн хөтөлбөр" + diff --git a/res/values-mn-v23/strings.xml b/res/values-mn-v23/strings.xml deleted file mode 100644 index 7da24abf..00000000 --- a/res/values-mn-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Суваг" - diff --git a/res/values-mn/arrays.xml b/res/values-mn/arrays.xml deleted file mode 100644 index 3d0b16ba..00000000 --- a/res/values-mn/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Энгийн" - "Дүүрэн" - "Томруулах" - - - "Бүх суваг" - "Гэр бүл/Хүүхдүүд" - "Спорт" - "Худалдаа" - "Кино" - "Инээдмийн" - "Аялал" - "Драма" - "Боловсрол" - "Амьтад/Зэрлэг байгаль" - "Мэдээ" - "Тоглоом" - "Урлаг" - "Цэнгээн" - "Амьдралын хэв маяг" - "Дуу хөгжим" - "Эхний" - "Технологи/ШУ" - - - "Шууд суваг" - "Агуулга хайх энгийн арга" - "Апп татаж, илүү олон суваг авах" - "Сувгийнхаа дэс дарааллыг өөрчлөх" - - - "TV-дээ суваг үздэг шиг апп-с агуулга үзээрэй." - "TV суваг шиг \nТанил гарын авлага, боломжит харагдах байдлын тусламжтайгаар апп-аасаа агуулга хайгаарай." - "Шууд суваг үзэж болох апп суулгаж, илүү олон суваг нэмээрэй. \nGoogle Play Store-с тохирох апп олохын тулд TV цэсний холбоосыг ашиглаарай." - "Сувгийн жагсаалтаа өөрчлөхийн тулд шинээр суулгасан сувгийн эх сурвалжаа тохируулаарай. \nЭхлүүлэхийн тулд тохиргооны цэснээс сувгийн эх сурвалжийг сонгоорой." - - diff --git a/res/values-mn/rating_system_strings.xml b/res/values-mn/rating_system_strings.xml deleted file mode 100644 index 7c883a13..00000000 --- a/res/values-mn/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Хөтөлбөрт 15-с доош насныханд тохиромжгүй материал агуулсан тул эцэг эхийн хяналт шаардлагатай." - "Хөтөлбөрт 19-с дээш насныханд зориулсан материал агуулсан тул 19-с доош насныхан үзэхэд тохиромжгүй." - "Уруу татсан яриа" - "Бүдүүлэг яриа" - "Секс контент" - "Хүчирхийлэл" - "Уран зөгнөлт хэлбэрээр үзүүлсэн хүчирхийлэл" - "Энэ хөтөлбөрийг бүх насны хүүхдэд зориулан бүтээсэн." - "Энэ хөтөлбөрийг 7 болон түүнээс дээш насны хүүхдэд зориулан бүтээсэн." - "Ихэнх эцэг эхчүүд энэ хөтөлбөрийг бүх насанд тохиромжтой гэж үзнэ." - "Энэ хөтөлбөрт зарим эцэг эхчүүдийн хувьд 14-с доош насны хүүхэд үзэхэд тохиромжгүй гэж болох материал агуулсан. Олон эцэг эх хүүхдүүдтэйгээ хамт үзэхийг хүсэж магадгүй юм." - "Энэ хөтөлбөрт зарим эцэг эхчүүдийн хувьд 14-с доош насны хүүхэд үзэхэд тохиромжгүй гэж болох материал агуулсан." - "Энэ хөтөлбөрийг насанд хүрэгчдэд зориулан бэлтгэсэн тул 17-с доош насны хүүхдэд тохиромжгүй." - "Киноны зэрэглэл" - "Энгийн үзэгчид. Эцэг эхийн санаа зовоох, хүүхдэд тохиромжгүй агуулга байхгүй" - "Эцэг эхийн зөвшөөрөл шаардлагатай. Зарим материал хүүхдэд тохиромжгүй байж болзошгүй." - "Эцэг эхийн хяналт онцгой шаардлагатай. Зарим материал нь өсвөр наснаас өмнөх насныханд тохиромжгүй." - "Хориглосон. Насанд хүрэгчдэд зориулсан агуулгатай. Хүүхдүүдээ авч явахаасаа өмнө киноны тухай судална уу." - "17 ба түүнээс доош настай бол зөвшөөрөхгүй. Зөвхөн насанд хүрэгчдэд. Хүүхдүүдэд зөвшөөрөхгүй." - diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml deleted file mode 100644 index e99f8a0e..00000000 --- a/res/values-mn/strings.xml +++ /dev/null @@ -1,348 +0,0 @@ - - - - - "моно" - "стерео" - "Play хяналт" - "Саяхны сувгууд" - "ТВ-н сонголтууд" - "PIP Сонголтууд" - "Энэ сувагт тоглуулах хяналтыг ашиглах боломжгүй байна" - "Тоглуулах эсвэл түр зогсоох" - "Хурдан урагшлуулах" - "Хураах" - "Дараах" - "Өмнөх" - "Хөтөлбөр" - "Шинэ суваг нээлттэй байна" - "%1$s-г нээх" - "Хаалттай капшн" - "Дэлгэцийн горим" - "PIP" - "Идэвхтэй" - "Идэвхгүй" - "Мульти-аудио" - "Нэмж суваг авах" - "Тохиргоо" - "Эх сурвалж" - "Солих" - "Идэвхтэй" - "Идэвхгүй" - "Дуу" - "Үндсэн" - "PIP цонх" - "Байрлал" - "Баруун доод" - "Баруун дээд" - "Зүүн дээд" - "Зүүн доод" - "Зэрэгцсэн" - "Хэмжээ" - "Том" - "Жижиг" - "Оролтын эх үүсвэр" - "TВ (Антен /Кабел)" - "Хөтөлбөрийн мэдээлэл байхгүй" - "Мэдээлэл байхгүй" - "Хаагдсан суваг" - "Үл мэдэгдэх хэл" - "Хаалттай тайлбар" - "Идэвхгүй" - "Форматыг тааруулах" - "Системийн хэмжээнд тохируулга хийх" - "Дэлгэцийн горим" - "Мульти-аудио" - "моно" - "стерео" - "5.1 surround" - "7.1 Surround" - "%d суваг" - "Сувгийг тохируулах" - "Бүлэг сонгох" - "Бүлгийн сонголтыг цуцлах" - "Бүлэглэх" - "Сувгийн эх сурвалж" - "HD/SD" - "HD" - "SD" - "Бүлэглэх" - "Энэ програмыг блоклосон байна." - "Энэ хөтөлбөрийг %1$s-ээр үнэлэгдсэн байна" - "Оролт нь автомат хайлтыг дэмждэггүй" - "\'%s\'-н автомат хайлтыг эхлүүлэх боломжгүй" - "Текстийн системийн түвшний тохируулгыг эхлүүлэх боломжгүй." - - %1$d суваг нэмсэн - %1$d суваг нэмсэн - - "Суваг нэмэгдээгүй" - "Тааруулагч" - "Эцэг эхийн хяналт" - "Идэвхтэй" - "Идэвхгүй" - "Суваг хориглогдсон" - "Бүгдийг хориглох" - "Бүх хоригийг авах" - "Нууцалсан сувгууд" - "Хөтөлбөрийн хязгаар" - "PIN өөрчлөх" - "Үнэлгээний систем" - "Үнэлгээ" - "Үнэлгээний систем" - "Бусад улс орон" - "Хоосон" - "Хоосон" - "Хоосон" - "Хатуу хязгаарлалт" - "Дунд хязгаарлалт" - "Сул хязгаарлалт" - "Тааруулсан" - "Хүүхдэд зориулсан тохиромжтой контент" - "Том хүүхдэд тохиромжтой контент" - "Өсвөр насныханд тохиромжтой контент" - "Гар хязгаарлалт" - - - "%1$s ба дэд-үнэлгээ" - "Дэд-үнэлгээ" - "Энэ сувгийг үзэхийн тулд PIN оруулна уу" - "Энэ хөтөлбөрийг үзэхийн тулд PIN оруулна уу" - "Энэ хөтөлбөр нь %1$s үнэлгээтэй байна. Энэ хөтөлбөрийг үзэхийн тулд ПИН-ээ оруулна уу." - "Өөрийн PIN оруулна уу" - "Эцэг эхийн хяналт тохируулахын тулд PIN үүсгээрэй" - "Шинэ PIN оруулна уу" - "Өөрийн PIN-г баталгаажуулна уу" - "Одоогийн PIN оруулна уу" - - Та PIN-г 5 удаа буруу оруулсан байна.\n %1$d секундын дараа дахин оролдоно уу. - Та PIN-г 5 удаа буруу оруулсан байна.\n %1$d секундын дараа оролдоно уу. - - "Энэ PIN буруу байна. Дахин оролдоно уу." - "Дахин оролдоно уу, PIN таарахгүй байна" - "Тохиргоо" - "Сувгийн жагсаалтыг тохируулах" - "ТВ хөтөлбөрт оруулах сувгуудыг сонгоно уу" - "Сувгийн эх сурвалж" - "Шинэ суваг боломжтой байна" - "Эцэг эхийн хяналт" - "Нээлттэй эхийн лиценз" - "Нээлттэй эхийн лиценз" - "Хувилбар" - "Энэ сувгийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу" - "Энэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу" - "Энэ хөтөлбөр %1$s үнэлгээтэй байна.\nэнэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу." - "Энэ сувгийг үзэхийн тулд үндсэн шууд ТВ-н апп-г ашиглаарай," - "Энэ хөтөлбөрийг үзэхийн тулд үндсэн шууд ТВ-н апп ашиглаарай." - "Энэ хөтөлбөр нь %1$s үнэлгээтэй.\nЭнэ хөтөлбөрийг үзэхийн тулд үндсэн шууд ТВ-н апп-г ашиглаарай." - "Хөтөлбөр хориглогдсон" - "Энэ хөтөлбөрийг %1$s-ээр үнэлэгдсэн байна" - "Зөвхөн аудио" - "Дохио муу" - "Интернэт холболт байхгүй" - - Бусад сувгийг бичиж байгаа тул энэ сувгийг %1$s хүртэл тоглуулах боломжгүй. \n\nБичих хуваарийг тохируулахын тулд Баруун товчлуурыг дарна уу. - Өөр сувгийг бичиж байгаа тул энэ сувгийг %1$s хүртэл тоглуулах боломжгүй. \n\nБичих хуваарийг тохируулахын тулд Баруун товчлуурыг дарна уу. - - "Гарчиггүй" - "Сувгийг хориглосон" - "Шинэ" - "Эх сурвалж" - - %1$d суваг - %1$d суваг - - "Ямар ч суваг байхгүй байна" - "Шинэ" - "Тохируулаагүй" - "Бусад сурвалж авах" - "Шууд сувгийн апп хайх" - "Шинэ сувгийн эх сурвалж боломжтой байна" - "Шинэ сувгийн эх сурвалжууд суваг санал болгож байна.\nТа эдгээрийг эх сурвалжийн тохиргоо хэсэгт одоо эсвэл дараа нь тохируулаарай." - "Одоо тохируулах" - "За, ойлголоо" - - - "ТВ цэс рүү хандахын тулд ""СОНГОХ гэснийг дарна уу" - "ТВ оролт олдсонгүй." - "ТВ оролтыг олж чадахгүй байна." - "PIP дэмжигдээгүй байна." - "PIP-тай харуулж болох бэлэн оролт байхгүй байна." - "Дуу тохируулагчийн төрөл тохирохгүй байна. Зурагтын оролтын дуу тохируулагчийн төрөлд зориулан Шууд Сувгуудын апликейшнийг эхлүүл." - "Дуу тааруулж чадсангүй." - "Энэ үйлдлийг гүйцэтгэх апп олдсонгүй." - "Бүх эх сурвалж сувгуудыг нууцалсан.\nҮзэхийн тулд дор хаяж нэг суваг сонгоно уу." - "Энэ видеог үзэх боломжгүй болсон." - "Буцах товчийг холбогдсон төхөөрөмжүүдэд ашиглана. Гарахын тулд Нүүр товчийг дарна уу." - "Шууд сувагт TВ-н жагсаалтыг унших зөвшөөрөл шаардлагатай." - "Эх сурвалжаа тохируулах" - "Шууд суваг нь апп-с хангадаг TV-н уламжлалт урсгал сувгийн хэрэглээтэй хосолдог. \n\nЭхлүүлэхийн тулд өмнө нь суулгасан сувгийн эх сурвалжийг тохируулаарай. Эсвэл Google Play Store-с шууд суваг дамжуулдаг бусад апп-г хайгаарай." - "Бичлэг, хуваарь" - "10 минут" - "30 минут" - "1 цаг" - "3 цаг" - "Сүүлийн" - "Хуваарилсан" - "Цуврал" - "Бусад" - "Сувгийг бичих боломжгүй." - "Хөтөлбөрийг бичих боломжгүй." - "%1$s-г бичихээр товлосон" - "%1$s-г одооноос %2$s хүртэл бичиж байна" - "Бүтэн цагийн хуваарь" - - Дараагийн %1$d хоног - Дараагийн %1$d хоног - - - %1$d минут - %1$d минут - - - - - %1$d бичлэг - %1$d бичлэг - - - %1$d бичлэгийг товлосон - %1$d бичлэгийг товлосон - - "Үзэх" - "Эхнээс нь тоглуулах" - "Үргэлжлүүлэн тоглуулах" - "Устгах" - "Бичлэгийг устгах" - "Үргэлжлүүлэх" - "%1$s-р цуврал" - "Хуваарь харах" - "Дэлгэрэнгүй унших" - "Бичлэгийг устгах" - "Устгах ангийг сонгоно уу. Устгасан тохиолдолд дахин сэргээх боломжгүй." - "Устгах ямар ч бичлэг алга." - "Үзсэн ангийг сонгох" - "Бүх ангийг сонгох" - "Бүх ангийн сонголтыг цуцлах" - "%2$d%1$d минут үзсэн" - "%2$d%1$d секунд үзсэн" - "Огт үзээгүй" - - %2$d-ийн %1$d ангийг устгасан - %2$d-ийн %1$d ангийг устгасан - - "Ач холбогдол" - "Хамгийн өндөр" - "Хамгийн бага" - "Үгүй. %1$d" - "Суваг" - "Дурын" - "Ач холбогдол сонгох" - "Олон хөтөлбөрийг зэрэг бичихээр сонгосон тохиолдолд зөвхөн өндөр ач холбогдолтой хөтөлбөрийг бичнэ." - "Хадгалах" - "Нэг удаагийн бичлэг өндөр ач холбогдолтой" - "Цуцлах" - "Цуцлах" - "Мартах" - "Зогсоох" - "Бичих хуваарийг харах" - "Зөвхөн энэ хөтөлбөр" - "одоо - %1$s" - "Бүтэн цуврал..." - "Ямартай ч товлох" - "Оронд нь үүнийг бичих" - "Энэ бичлэгийг цуцлах" - "Одоо үзэх" - "Дүрс бичих боломжтой" - "Дүрс бичихээр товлосон" - "Дүрс бичих боломжгүй" - "Бичиж байна" - "Бичиж чадсангүй" - "Бичлэгийн хуваарь үүсгэхийн тулд хөтөлбөрийг уншиж байна" - "Хөтөлбөрийг уншиж байна" - - - "DVR-д илүү багтаамж шаардлагатай" - "Та DVR-р хөтөлбөр бичих боломжтой болно. Гэсэн хэдий ч таны төхөөрөмжид DVR ажиллуулах хангалттай багтаамж алга. %1$sгигабайт, эсвэл үүнээс дээш багтаамжтай гадаад драйв холбож, үүнийг төхөөрөмжийн сан болгож хэлбэршүүлэхийн тулд зааврыг дагана уу." - "Сан алга" - "DVR-н ашигласан зарим сан алга. DVR-г дахин идэвхжүүлэхээс өмнө ашигласан гадаад драйвыг холбоно уу. Хэрэв сан байхгүй бол үүнийг мартах боломжтой." - "Санг мартах уу?" - "Таны бичсэн агуулга, хуваарь устах болно." - "Бичлэгийг зогсоох уу?" - "Бичсэн агуулгыг хадгална." - - - "Бичихээр товлосон ч зөрчилтэй байна" - "Бичлэг эхлүүлсэн хэдий ч зөрчилтэй байна" - "%1$s-г бичих болно." - "%1$s-г бичиж байна." - "%1$s-н зарим хэсгийг бичихгүй." - "%1$s болон %2$s-н зарим хэсгийг бичихгүй." - "%1$s, %2$s -н зарим хэсэг болон өөр нэг хуваарийг бичихгүй." - - %1$s, %2$s-н зарим хэсэг болон %3$d бусад хуваарийг бичихгүй. - %1$s, %2$s-н зарим хэсэг болон %3$d бусад хуваарийг бичихгүй. - - "Та юу бичих вэ?" - "Та хэр удаан бичих вэ?" - "Аль хэдийн товлосон" - "Үүнтэй ижил хөтөлбөрийг %1$s-д бичихээр товлосон байна." - "Аль хэдийн бичсэн" - "Энэ хөтөлбөрийг аль хэдийн бичсэн байна. Энэ нь DVR санд боломжтой." - - - - - - - - - "Бичсэн хөтөлбөр олдсонгүй." - "Холбоотой бичлэг" - "(Хөтөлбөрийн тодорхойлолт алга)" - - %1$d бичлэг - %1$d бичлэг - - " / " - "%1$s-г бичих хуваариас хассан" - "Тааруулагчийн алдааны улмаас хэсэгчлэн бичих болно." - "Тааруулагч алдаатай тул бичихгүй." - "Одоогоор хуваарьт бичлэг байхгүй байна.\nТа ТВ хөтөлбөрөөс бичлэг товлох боломжтой." - - %1$d бичлэгийн зөрчил - %1$d бичлэгийн зөрчил - - "Цувралын тохиргоо" - "Цувралыг бичиж эхлэх" - "Цувралыг бичихээ зогсоох" - "Цувралыг бичихээ зогсоох уу?" - "Бичсэн цувралыг DVR санд хадгална." - "Зогсоох" - "Ямар ч анги алга.\nТэд боломжтой болсон үедээ бичих болно." - - (%1$d минут) - (%1$d минут) - - "Өнөөдөр" - "Маргааш" - "Өчигдөр" - "өнөөдрийн %1$s" - "Маргаашийн %1$s" - "Оноо" - diff --git a/res/values-mr-rIN-v23/strings.xml b/res/values-mr-rIN-v23/strings.xml new file mode 100644 index 00000000..d7b703e6 --- /dev/null +++ b/res/values-mr-rIN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "चॅनेल" + diff --git a/res/values-mr-rIN/arrays.xml b/res/values-mr-rIN/arrays.xml new file mode 100644 index 00000000..a91434f4 --- /dev/null +++ b/res/values-mr-rIN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "सामान्य" + "पूर्ण" + "झूम करा" + + + "सर्व चॅनेल" + "कुटुंब/मुले" + "क्रीडा" + "खरेदी" + "चित्रपट" + "विनोदी" + "प्रवास" + "नाटक" + "शिक्षण" + "प्राणी/वन्यजीवन" + "बातम्या" + "गेमिंग" + "कला" + "मनोरंजन" + "जीवनशैली" + "संगीत" + "प्रमुख" + "तंत्रज्ञान/विज्ञान" + + + "थेट चॅनेल" + "सामग्री शोधण्याचा एक सोपा मार्ग" + "अॅप्स डाउनलोड करा, आणखी चॅनेल मिळवा" + "आपल्या चॅनेलची रांग सानुकूल करा" + + + "TV वर चॅनेल पाहता त्याप्रमाणे आपल्या अॅप्समधून सामग्री पहा." + "परिचित मार्गदर्शक आणि अनुकूल असलेल्या इंटरफेससह\n TV वरील चॅनेलप्रमाणे, आपल्या अॅप्समधून सामग्री ब्राउझ करा." + "थेट चॅनेल प्रदान करणारे अॅप्स स्थापित करून आणखी चॅनेल जोडा. \n TV मेनू मधील दुव्याचा वापर करून Google Play स्टोअर मध्ये सुसंगत अॅप्स शोधा." + "आपली चॅनेल सूची सानुकूल करण्यासाठी आपले नव्याने स्थापित केलेले चॅनेल स्रोत सेट करा. \nप्रारंभ करण्यासाठी सेटिंग्ज मेनूमधील चॅनेल स्रोत निवडा." + + diff --git a/res/values-mr-rIN/rating_system_strings.xml b/res/values-mr-rIN/rating_system_strings.xml new file mode 100644 index 00000000..d192c799 --- /dev/null +++ b/res/values-mr-rIN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "प्रोग्राममध्ये 15 वर्षांखालील प्रेक्षकांसाठी अयोग्य असलेली सामग्री असू शकते आणि त्यामुळे त्यासाठी पालकांची विवेकबुद्धी वापरली जावी." + "प्रोग्राममध्ये 19 वर्षांखालील प्रेक्षकांसाठी अयोग्य असलेली सामग्री कदाचित असू शकते आणि त्यामुळे 19 वर्षांखालील प्रेक्षकांसाठी पालकांची विवेकबुद्धी वापरली जावी." + "अश्लील संवाद" + "असभ्य भाषा" + "लैंगिक सामग्री" + "हिंसा" + "काल्पनिक हिंसा" + "हा प्रोग्राम सर्व मुलांसाठी योग्य असण्यासाठी डिझाइन केला आहे." + "हा प्रोग्राम 7 वर्षे आणि त्यावरील मुलांसाठी डिझाइन केला आहे." + "अनेक पालकांना सर्व वयांसाठी हा प्रोग्राम उचित वाटेल." + "या प्रोग्राममध्ये पालकांना लहान मुलांसाठी अनुचित वाटू शकते अशी सामग्री आहे. अनेक पालक त्यांच्या किशोरवयीन मुलांसह तो पाहू इच्छि‍त असू शकतात." + "या प्रोग्राममध्ये काही सामग्री आहे जी बर्‍याच पालकांना 14 वर्षे वयाखालील मुलांसाठी अनुचित वाटेल." + "हा प्रोग्राम प्रौढांना पाहण्यासाठी विशेषतः डिझाइन केला आहे आणि यामुळे तो 17 वर्षांखालील मुलांसाठी अनुचित असू शकतो." + "फिल्म रेटिंग" + "सर्वसामान्य प्रेक्षक. मुलांनी पाहिल्यास पालकांना लज्जास्पद वाटेल असे काहीही नाही." + "पालक मार्गदर्शन सुचविले आहे. यात काही सामग्री अशी आहे जी पालकांना त्यांच्या किशोरवयीन मुलांसाठी कदाचित आवडणार नाही." + "पालकांना खंबीरपणे धोक्याचा इशारा. काही सामग्री तेरा वर्षापेक्षा कमी वय असलेल्यांसाठी अयोग्य असू शकतात." + "प्रतिबंधित, काही प्रौढ सामग्री असते. पालकांनी त्यांच्या किशोरवयीन मुलांना त्यांच्यासह घेऊन जाण्यापूर्वी चित्रपटाबद्दल अधिक जाणून घेण्याचा त्यांना आग्रह केला जातो." + "17 आणि त्यापेक्षा कमी वय असलेल्या कोणासही प्रवेश नाही. प्रौढ. लहान मुलांना प्रवेश नाही." + diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml new file mode 100644 index 00000000..83d8dd42 --- /dev/null +++ b/res/values-mr-rIN/strings.xml @@ -0,0 +1,357 @@ + + + + + "एक" + "स्टिरिओ" + "प्ले नियंत्रणे" + "चॅनेल" + "टीव्‍ही पर्याय" + "या चॅनेलसाठी अनुपलब्ध असलेली नियंत्रणे प्ले करा" + "प्ले करा किंवा विराम द्या" + "फास्ट फॉरवर्ड करा" + "रिवाईँड करा" + "पुढील" + "मागील" + "कार्यक्रम मार्गदर्शक" + "उपलब्ध नवीन चॅनेल" + "%1$s उघडा" + "उपशीर्षक" + "प्रदर्शन मोड" + "PIP" + "मल्टी-ऑडिओ" + "अधिक चॅनेल मिळवा" + "सेटिंग्ज" + "टीव्ही (अँटेना/केबल)" + "कोणतीही कार्यक्रम माहिती नाही" + "कोणतीही माहिती नाही" + "अवरोधित चॅनेल" + "अज्ञात भाषा" + "बंद मथळा %1$d" + "बंद मथळा" + "बंद" + "स्वरुपन सानुकूलित करा" + "उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये सेट करा" + "प्रदर्शन मोड" + "मल्टी-ऑडिओ" + "एक" + "स्टिरिओ" + "5.1 सराउंड" + "7.1 सराउंड" + "%d चॅनेल" + "चॅनेल सूची सानुकूल करा" + "गट निवडा" + "गट निवड रद्द करा" + "नुसार गट करा" + "चॅनेल स्त्रोत" + "HD/SD" + "HD" + "SD" + "नुसार गट करा" + "हा कार्यक्रम अवरोधित केला आहे" + "या प्रोग्रामला रेट केलेले नाही" + "हा कार्यक्रम %1$s रेट केला आहे" + "इनपुट स्वयं-स्‍कॅनला समर्थन देत नाही" + "\'%s\' साठी स्वयं-स्कॅन प्रारंभ करण्‍यात अक्षम" + "उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये प्रारंभ करण्‍यात अक्षम." + + %1$d चॅनेल जोडले + %1$d चॅनेल जोडले + + "कोणतेही चॅनेल जोडले नाही" + "पालक नियंत्रणे" + "चालू" + "बंद" + "चॅनेल अवरोधित केले" + "सर्व अवरोधित करा" + "सर्व अनावरोधित करा" + "लपविलेले चॅनेल" + "कार्यक्रम निर्बंध" + "पिन बदला" + "रेटिंग सिस्‍टीम" + "रेटिंग" + "सर्व रेटिंग सिस्‍टीम पहा" + "इतर देश" + "कोणतेही नाही" + "काहीही नाही" + "रेट न केलेले" + "रेट न केलेले प्रोग्राम ब्लॉक करा" + "काहीही नाही" + "उच्च निर्बंंध" + "मध्यम निर्बंध" + "कमी निर्बंध" + "सानुकूल" + "मुलांसाठी योग्य असलेली सामग्री" + "मोठ्या मुलांसाठी योग्य असलेली सामग्री" + "किशोरवयीन मुलांसाठी योग्य असलेली सामग्री" + "व्यक्तीचलित निर्बंध" + + + "%1$s आणि उप रेटिंग" + "उप रेटिंग" + "हे चॅनेल पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा" + "हा कार्यक्रम पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा" + "हा प्रोग्राम %1$s रेट केलेला आहे. हा प्रोग्राम पाहण्यासाठी आपला पिन प्रविष्ट करा" + "या प्रोग्रामला रेट केलेले नाही. हा प्रोग्राम पाहण्यासाठी तुमचा पिन टाका" + "आपला पिन प्रविष्‍ट करा" + "पालक नियंत्रणे सेट करण्यासाठी, एक पिन तयार करा" + "नवीन पिन प्रविष्ट करा" + "आपल्या पिन ची पुष्टी करा" + "आपला वर्तमान पिन प्रविष्ट करा" + + आपण चुकीचा पिन 5 वेळा प्रविष्‍ट केला.\n%1$d सेकंदामध्‍ये पुन्हा प्रयत्न करा. + आपण चुकीचा पिन 5 वेळा प्रविष्‍ट केला.\n%1$d सेकंदांमध्‍ये पुन्हा प्रयत्न करा. + + "तो पिन चुकीचा होता. पुन्हा प्रयत्न करा." + "पुन्हा प्रयत्न करा, पिन जुळत नाही" + "आपला पिन कोड प्रविष्ट करा." + "टीव्ही चॅनेलसाठी संपूर्ण कार्यक्रम मार्गदर्शक प्रदान करण्यासाठी थेट चॅनेल अॅप पिन कोड वापरेल." + "आपला पिन कोड प्रविष्ट करा" + "अवैध पिन कोड" + "सेटिंग्ज" + "चॅनेल सूची सानुकूल करा" + "आपल्या कार्यक्रम मार्गदर्शकासाठी चॅनेल निवडा" + "चॅनेल स्त्रोत" + "नवीन चॅनेल उपलब्ध आहेत" + "पालक नियंत्रणे" + "Timeshift" + "पहात असताना रेकॉर्ड करा ज्यामुळे आपण थेट प्रोग्रामांना विराम देऊ किंवा ते रिवाइंड करू शकता.\nचेतावणी: हे संचयाचा अत्याधिक वापर करून कदाचित अंतर्गत संचयाचे आयुष्य कमी करू शकते." + "मुक्त स्त्रोत परवाने" + "अभिप्राय पाठवा" + "आवृत्ती" + "हे चॅनेल पाहण्यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा" + "हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा" + "या प्रोग्रामला रेट केलेले नाही.\nहा प्रोग्राम पाहण्यासाठी उजवीकडे दाबा आणि तुमचा पिन टाका" + "हा कार्यक्रम %1$s रेट केला आहे.\n हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा." + "हे चॅनेल पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." + "हा प्रोग्राम पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." + "या प्रोग्रामला रेट केलेले नाही.\nहा प्रोग्राम पाहण्यासाठी डीफॉल्ट लाइव्ह टीव्ही अ‍ॅप वापरा." + "हा प्रोग्राम %1$s रेट केलेला आहे.\nहा प्रोग्राम पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." + "कार्यक्रम अवरोधित केला आहे" + "या प्रोग्रामला रेट केलेले नाही" + "हा कार्यक्रम %1$s रेट केला आहे" + "फक्त ऑडिओ" + "खराब सिग्नल" + "कोणतेही इंटरनेट कनेक्शन नाही" + + दुसरे चॅनेल रेकॉर्ड केले जात असल्याने %1$s पर्यंत हे चॅनेल प्ले केले जाऊ शकत नाही. \n\nरेकॉर्डिंग अनुसूची समायोजित करण्यासाठी उजवीकडे दाबा. + दुसरे चॅनेल रेकॉर्ड केले जात असल्याने %1$s पर्यंत हे चॅनेल प्ले केले जाऊ शकत नाही. \n\nरेकॉर्डिंग अनुसूची समायोजित करण्यासाठी उजवीकडे दाबा. + + "शीर्षक नाही" + "चॅनेल अवरोधित केले" + "नवीन" + "स्त्रोत" + + %1$d चॅनेल + %1$d चॅनेल + + "कोणतेही चॅनेल उपलब्ध नाही" + "नवीन" + "सेट केलेले नाही" + "अधिक स्रोत मिळवा" + "थेट चॅनेल प्रदान करणारे अॅप्स ब्राउझ करा" + "नवीन चॅनेल स्रोत उपलब्ध आहेत" + "प्रदान करण्‍यासाठी नवीन चॅनेल स्रोतांमध्‍ये चॅनेल आहेत. \n आता ते सेट करा किंवा चॅनेल स्रोत सेटिंगमध्ये हे नंतर करा." + "आता सेट करा" + "ठीक आहे, समजले" + + + "टीव्ही मेनूवर प्रवेश करण्यासाठी ""निवडा दाबा""." + "कोणतेही टीव्ही इनपुट आढळले नाही" + "टीव्ही इनपुट शोधू शकत नाही" + "ट्यूनर प्रकार अनुकूल नाही. कृपया ट्यूनर प्रकार टीव्ही इनपुटसाठी थेट चॅनेल अॅप लाँच करा." + "ट्यून अयशस्वी झाले" + "ही क्रिया हाताळण्यासाठी कोणताही अ‍ॅप आढळला नाही." + "सर्व स्त्रोत चॅनेल लपविले आहेत.\nपाहण्‍यासाठी कमीतकमी एक चॅनेल निवडा." + "व्‍हिडिओ अनपेक्षितपणे अनुपलब्ध आहे" + "बॅक की कनेक्ट केलेल्या डिव्हाइससाठी आहे. बाहेर पडण्यासाठी मुख्यपृष्ठ बटण दाबा." + "थेट चॅनेलना टीव्ही सूचींचे वाचन करण्यासाठी परवानगीची आवश्यकता आहे." + "आपले स्रोत सेट करा" + "थेट चॅनेल पारंपारिक TV चॅनेलच्या अनुभवास अॅप्सने प्रदान केलेल्या प्रवाहित केलेल्या चॅनेलसह एकत्रित करतात. \n\nआधीपासून इंस्टॉल केलेले चॅनेल स्रोत सेट करून प्रारंभ करा किंवा थेट चॅनेल प्रदान करणार्‍या आणखी अॅप्ससाठी Google Play स्टोअर ब्राउझ करा." + "रेकॉर्डिंग आणि अनुसूची" + "10 मिनिटे" + "30 मिनिटे" + "1 तास" + "3 तास" + "अलीकडील" + "अनुसूचित" + "मालिका" + "इतर" + "चॅनेल रेकॉर्ड केले जाऊ शकत नाही." + "प्रोग्राम रेकॉर्ड केला जाऊ शकतो." + "रेकॉर्ड करण्‍यासाठी %1$s ची अनुसूची केली गेली" + "%1$s चे रेकॉर्डिंग आतापासून %2$s पर्यंत" + "पूर्ण अनुसूची" + + पुढील %1$d दिवस + पुढील %1$d दिवस + + + %1$d मिनिट + %1$d मिनिटे + + + %1$d नवीन रेकॉर्डिंग + %1$d नवीन रेकॉर्डिंग + + + %1$d रेकॉर्डिंग + %1$d रेकॉर्डिंग + + + %1$d रेकॉर्डिंगची अनुसूची केली + %1$d रेकॉर्डिंगची अनुसूची केली + + "रेकॉर्डिंग रद्द करा" + "रेकॉर्डिंग थांबवा" + "पहा" + "सुरूवातीपासून प्ले करा" + "प्ले करणे पुनः सुरु करा" + "हटवा" + "रेकॉर्डिंग हटवा" + "पुनः सुरु करा" + "हंगाम %1$s" + "अनुसूची पहा" + "आणखी वाचा" + "रेकॉर्डिंग हटवा" + "आपण हटवू इच्छिता ते भाग निवडा. एकदा हटविले की ते पुनर्प्राप्त केले जाऊ शकत नाहीत." + "हटविण्यासाठी कोणतेही रेकॉर्डिंग नाहीत." + "पाहिलेले भाग निवडा" + "सर्व भाग निवडा" + "सर्व भागांची निवड रद्द करा" + "%2$d पैकी %1$d मिनिटांची रेकॉर्डिंग पाहिली" + "%2$d पैकी %1$d सेकंदांची रेकॉर्डिंग पाहिली" + "कधीही पाहिली नाहीत" + + %2$d पैकी %1$d भाग हटविला आहे + %2$d पैकी %1$d भाग हटविले आहेत + + "प्राधान्य" + "सर्वोच्च" + "सर्वात निम्न" + "%1$d क्र." + "चॅनेल" + "कोणतेही" + "प्राधान्य निवडा" + "एकाच वेळी रेकॉर्ड करण्यासाठी खूप जास्त प्रोग्राम असतात तेव्हा, केवळ अधिक प्राधान्ये असलेले प्रोग्राम रेकॉर्ड केले जातील." + "जतन करा" + "एक-वेळ रेकॉर्डिंगला सर्वोच्च प्राधान्य आहे" + "थांबा" + "रेकॉर्डिंग अनुसूची पहा" + "हा एक कार्यक्रम" + "आता - %1$s" + "संपूर्ण मालिका…" + "तरीही शेड्यूल करा" + "त्याऐवजी हे रेकॉर्ड करा" + "हे रेकॉर्डिंग रद्द करा" + "आता पहा" + "रेकॉर्डिंग हटवा..." + "रेकॉर्ड करण्यायोग्य" + "रेकॉर्डिंग अनुसूचित केले" + "रेकॉर्डिंग संबंधी विरोध" + "रेकॉर्डिंग" + "रेकॉर्डिंग अयशस्वी झाले" + "वाचन कार्यक्रम" + "अलीकडील रेकॉर्डिंग पहा" + "%1$s चे रेकॉर्डिंग अपूर्ण आहे." + "%1$s आणि %2$s चे रेकॉर्डिंग अपूर्ण आहे." + "%1$s, %2$s आणि %3$s चे रेकॉर्डिंग अपूर्ण आहे." + "अपुर्‍या संचयामुळे %1$s चे रेकॉर्डिंग पूर्ण झाले नाही." + "अपुर्‍या संचयामुळे %1$s आणि %2$s चे रेकॉर्डिंग पूर्ण झाले नाही." + "अपुर्‍या संचयामुळे %1$s, %2$s, %3$s चे रेकॉर्डिंग पूर्ण झाले नाही." + "DVR साठी आणखी संचय आवश्यक आहे" + "आपण DVR ने प्रोग्राम रेकॉर्ड करण्यात सक्षम असाल. तथापि DVR ने कार्य करण्यासाठी आपल्या डीव्हाइसवर आता पुरेसा संचय नाही. कृपया %1$dGB किंवा त्यापेक्षा मोठ्या बाह्य ड्राइव्हशी कनेक्ट करा आणि त्यास डीव्हाइस संचय म्हणून स्वरूपित करण्‍यासाठी चरणांचे अनुसरण करा." + "पुरेसा संचय नाही" + "पुरेसा संचय नसल्याने हा कार्यक्रम रेकॉर्ड केला जाणार नाही. काही विद्यमान रेकॉर्डिंग हटवून पहा." + "संचय गहाळ आहे" + "रेकॉर्डिंग थांबवायचे?" + "रेकॉर्ड केलेली सामग्री जतन केली जाईल." + "%1$s चे रेकॉर्डिंग थांबवले जाईल कारण ते या कार्यक्रमासह संघर्ष करते. रेकॉर्ड केलेली सामग्री जतन केली जाईल." + "रेकॉर्डिंगची अनुसूची केली परंतु त्यासंबंधी विरोध आहेत" + "रेकॉर्डिंग सुरू झाली परंतु त्यासंबंधी विरोध आहेत" + "%1$s रेकॉर्ड केला जाईल." + "%1$s रेकॉर्ड केला जात आहे." + "%1$s चे काही भाग रेकॉर्ड केले जाणार नाहीत." + "%1$s आणि %2$s चे काही भाग रेकॉर्ड केले जाणार नाहीत." + "%1$s, %2$s चे काही भाग आणि आणखी एक अनुसूची रेकॉर्ड केली जाणार नाही." + + %1$s, %2$s आणि आणखी %3$d अनुसूची रेकॉर्ड केली जाणार नाही. + %1$s, %2$s आणि आणखी %3$d अनुसूची रेकॉर्ड केली जाणार नाही. + + "आपल्याला काय रेकॉर्ड करायला आवडेल?" + "आपल्याला किती वेळ रेकॉर्ड करायला आवडेल?" + "आधीच शेड्यूल केला आहे" + "%1$s रोजी रेकॉर्ड करण्यासाठी तोच कार्यक्रम आधीच शेड्यूल केला आहे." + "आधीच रेकॉर्ड केला आहे" + "हा कार्यक्रम आधीच रेकॉर्ड केला गेला आहे. तो DVR लायब्ररीमध्ये उपलब्ध आहे." + "मालिका रेकॉर्डिंग अनुसूचित केले" + + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. + + + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. त्यांंच्यापैकी %3$d विरोधांमुळे रेकॉर्ड केले जाणार नाहीत. + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. त्यांंच्यापैकी %3$d विरोधांमुळे रेकॉर्ड केले जाणार नाहीत. + + + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. या मालिकेचे आणि अन्य मालिकांचे %3$d भाग विरोधांमुळे रेकॉर्ड केले जाणार नाहीत. + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. या मालिकेचे आणि अन्य मालिकांचे %3$d भाग विरोधांमुळे रेकॉर्ड केले जाणार नाहीत. + + + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. अन्य मालिकांचा 1 भाग विरोधांमुळे रेकॉर्ड केला जाणार नाही. + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. अन्य मालिकांचा 1 भाग विरोधांमुळे रेकॉर्ड केला जाणार नाही. + + + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. अन्य मालिकेचे %3$d भाग विरोधांंमुळे रेकॉर्ड केले जाणार नाहीत. + %2$s साठी %1$d रेकॉर्डिंगची अनुसूची केली गेली. अन्य मालिकेचे %3$d भाग विरोधांंमुळे रेकॉर्ड केले जाणार नाहीत. + + "रेकॉर्ड केलेला प्रोग्राम सापडला नाही." + "संबंधित रेकॉर्डिंग" + + %1$d रेकॉर्डिंग + %1$d रेकॉर्डिंग + + " / " + "%1$s रेकॉर्डिंग अनुसूची मधून काढला" + "ट्यूनर विरोधामुळे अंशतः रेकॉर्ड केले जाईल." + "ट्यूनर विरोधामुळे रेकॉर्ड केले जाणार नाही." + "अद्याप कोणतेही रेकॉर्डिंग शेड्यूल केलेले नाही\nआपण कार्यक्रम मार्गदर्शकामधून रेकॉर्डिंग शेड्यूल करू शकता." + + %1$d रेकॉर्डिंग संबंधी विरोध + %1$d रेकॉर्डिंग संबंधी विरोध + + "मालिका सेटिंग्ज" + "मालिका रेकॉर्ड करणे प्रारंभ करा" + "मालिका रेकॉर्ड करणे थांबवा" + "मालिका रेकॉर्ड करणे थांबवायचे?" + "रेकॉर्ड केलेले भाग DVR लायब्ररी मध्ये उपलब्ध राहतील." + "थांबा" + "आता कोणत्याही भागांचे प्रसारण होत नाही." + "कोणतेही भाग उपलब्ध नाहीत.\nते उपलब्ध झाल्यावर रेकॉर्ड केले जातील." + + (%1$d मिनिट) + (%1$d मिनिटे) + + "आज" + "उद्या" + "काल" + "आज %1$s" + "उद्या %1$s" + "गुणसंख्‍या" + "रेकॉर्ड केलेले प्रोग्राम" + diff --git a/res/values-mr-v23/strings.xml b/res/values-mr-v23/strings.xml deleted file mode 100644 index d7b703e6..00000000 --- a/res/values-mr-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "चॅनेल" - diff --git a/res/values-mr/arrays.xml b/res/values-mr/arrays.xml deleted file mode 100644 index a91434f4..00000000 --- a/res/values-mr/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "सामान्य" - "पूर्ण" - "झूम करा" - - - "सर्व चॅनेल" - "कुटुंब/मुले" - "क्रीडा" - "खरेदी" - "चित्रपट" - "विनोदी" - "प्रवास" - "नाटक" - "शिक्षण" - "प्राणी/वन्यजीवन" - "बातम्या" - "गेमिंग" - "कला" - "मनोरंजन" - "जीवनशैली" - "संगीत" - "प्रमुख" - "तंत्रज्ञान/विज्ञान" - - - "थेट चॅनेल" - "सामग्री शोधण्याचा एक सोपा मार्ग" - "अॅप्स डाउनलोड करा, आणखी चॅनेल मिळवा" - "आपल्या चॅनेलची रांग सानुकूल करा" - - - "TV वर चॅनेल पाहता त्याप्रमाणे आपल्या अॅप्समधून सामग्री पहा." - "परिचित मार्गदर्शक आणि अनुकूल असलेल्या इंटरफेससह\n TV वरील चॅनेलप्रमाणे, आपल्या अॅप्समधून सामग्री ब्राउझ करा." - "थेट चॅनेल प्रदान करणारे अॅप्स स्थापित करून आणखी चॅनेल जोडा. \n TV मेनू मधील दुव्याचा वापर करून Google Play स्टोअर मध्ये सुसंगत अॅप्स शोधा." - "आपली चॅनेल सूची सानुकूल करण्यासाठी आपले नव्याने स्थापित केलेले चॅनेल स्रोत सेट करा. \nप्रारंभ करण्यासाठी सेटिंग्ज मेनूमधील चॅनेल स्रोत निवडा." - - diff --git a/res/values-mr/rating_system_strings.xml b/res/values-mr/rating_system_strings.xml deleted file mode 100644 index d192c799..00000000 --- a/res/values-mr/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "प्रोग्राममध्ये 15 वर्षांखालील प्रेक्षकांसाठी अयोग्य असलेली सामग्री असू शकते आणि त्यामुळे त्यासाठी पालकांची विवेकबुद्धी वापरली जावी." - "प्रोग्राममध्ये 19 वर्षांखालील प्रेक्षकांसाठी अयोग्य असलेली सामग्री कदाचित असू शकते आणि त्यामुळे 19 वर्षांखालील प्रेक्षकांसाठी पालकांची विवेकबुद्धी वापरली जावी." - "अश्लील संवाद" - "असभ्य भाषा" - "लैंगिक सामग्री" - "हिंसा" - "काल्पनिक हिंसा" - "हा प्रोग्राम सर्व मुलांसाठी योग्य असण्यासाठी डिझाइन केला आहे." - "हा प्रोग्राम 7 वर्षे आणि त्यावरील मुलांसाठी डिझाइन केला आहे." - "अनेक पालकांना सर्व वयांसाठी हा प्रोग्राम उचित वाटेल." - "या प्रोग्राममध्ये पालकांना लहान मुलांसाठी अनुचित वाटू शकते अशी सामग्री आहे. अनेक पालक त्यांच्या किशोरवयीन मुलांसह तो पाहू इच्छि‍त असू शकतात." - "या प्रोग्राममध्ये काही सामग्री आहे जी बर्‍याच पालकांना 14 वर्षे वयाखालील मुलांसाठी अनुचित वाटेल." - "हा प्रोग्राम प्रौढांना पाहण्यासाठी विशेषतः डिझाइन केला आहे आणि यामुळे तो 17 वर्षांखालील मुलांसाठी अनुचित असू शकतो." - "फिल्म रेटिंग" - "सर्वसामान्य प्रेक्षक. मुलांनी पाहिल्यास पालकांना लज्जास्पद वाटेल असे काहीही नाही." - "पालक मार्गदर्शन सुचविले आहे. यात काही सामग्री अशी आहे जी पालकांना त्यांच्या किशोरवयीन मुलांसाठी कदाचित आवडणार नाही." - "पालकांना खंबीरपणे धोक्याचा इशारा. काही सामग्री तेरा वर्षापेक्षा कमी वय असलेल्यांसाठी अयोग्य असू शकतात." - "प्रतिबंधित, काही प्रौढ सामग्री असते. पालकांनी त्यांच्या किशोरवयीन मुलांना त्यांच्यासह घेऊन जाण्यापूर्वी चित्रपटाबद्दल अधिक जाणून घेण्याचा त्यांना आग्रह केला जातो." - "17 आणि त्यापेक्षा कमी वय असलेल्या कोणासही प्रवेश नाही. प्रौढ. लहान मुलांना प्रवेश नाही." - diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml deleted file mode 100644 index 1888e9de..00000000 --- a/res/values-mr/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "एक" - "स्टिरिओ" - "प्ले नियंत्रणे" - "अलीकडील चॅनेल" - "टीव्‍ही पर्याय" - "PIP पर्याय" - "या चॅनेलसाठी अनुपलब्ध असलेली नियंत्रणे प्ले करा" - "प्ले करा किंवा विराम द्या" - "फास्ट फॉरवर्ड करा" - "रिवाईँड करा" - "पुढील" - "मागील" - "कार्यक्रम मार्गदर्शक" - "उपलब्ध नवीन चॅनेल" - "%1$s उघडा" - "उपशीर्षक" - "प्रदर्शन मोड" - "PIP" - "चालू" - "बंद" - "मल्टी-ऑडिओ" - "अधिक चॅनेल मिळवा" - "सेटिंग्ज" - "स्त्रोत" - "अदलाबदल करा" - "चालू" - "बंद" - "ध्वनी" - "मुख्य" - "PIP विंडो" - "लेआउट" - "तळाशी उजवीकडे" - "शीर्षस्थानी उजवीकडे" - "शीर्षस्थानी डावीकडे" - "तळाशी डावीकडे" - "शेजारी शेजारी" - "आकार" - "मोठा" - "लहान" - "इनपुट स्त्रोत" - "टीव्ही (अँटेना/केबल)" - "कोणतीही कार्यक्रम माहिती नाही" - "कोणतीही माहिती नाही" - "अवरोधित चॅनेल" - "अज्ञात भाषा" - "बंद मथळा" - "बंद" - "स्वरुपन सानुकूलित करा" - "उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये सेट करा" - "प्रदर्शन मोड" - "मल्टी-ऑडिओ" - "एक" - "स्टिरिओ" - "5.1 सराउंड" - "7.1 सराउंड" - "%d चॅनेल" - "चॅनेल सूची सानुकूल करा" - "गट निवडा" - "गट निवड रद्द करा" - "नुसार गट करा" - "चॅनेल स्त्रोत" - "HD/SD" - "HD" - "SD" - "नुसार गट करा" - "हा कार्यक्रम अवरोधित केला आहे" - "हा कार्यक्रम %1$s रेट केला आहे" - "इनपुट स्वयं-स्‍कॅनला समर्थन देत नाही" - "\'%s\' साठी स्वयं-स्कॅन प्रारंभ करण्‍यात अक्षम" - "उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये प्रारंभ करण्‍यात अक्षम." - - %1$d चॅनेल जोडले - %1$d चॅनेल जोडले - - "कोणतेही चॅनेल जोडले नाही" - "ट्यूनर" - "पालक नियंत्रणे" - "चालू" - "बंद" - "चॅनेल अवरोधित केले" - "सर्व अवरोधित करा" - "सर्व अनावरोधित करा" - "लपविलेले चॅनेल" - "कार्यक्रम निर्बंध" - "पिन बदला" - "रेटिंग सिस्‍टीम" - "रेटिंग" - "सर्व रेटिंग सिस्‍टीम पहा" - "इतर देश" - "कोणतेही नाही" - "काहीही नाही" - "काहीही नाही" - "उच्च निर्बंंध" - "मध्यम निर्बंध" - "कमी निर्बंध" - "सानुकूल" - "मुलांसाठी योग्य असलेली सामग्री" - "मोठ्या मुलांसाठी योग्य असलेली सामग्री" - "किशोरवयीन मुलांसाठी योग्य असलेली सामग्री" - "व्यक्तीचलित निर्बंध" - - - "%1$s आणि उप रेटिंग" - "उप रेटिंग" - "हे चॅनेल पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा" - "हा कार्यक्रम पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा" - "हा प्रोग्राम %1$s रेट केलेला आहे. हा प्रोग्राम पाहण्यासाठी आपला PIN प्रविष्ट करा" - "आपला पिन प्रविष्‍ट करा" - "पालक नियंत्रणे सेट करण्यासाठी, एक पिन तयार करा" - "नवीन पिन प्रविष्ट करा" - "आपल्या पिन ची पुष्टी करा" - "आपला वर्तमान पिन प्रविष्ट करा" - - आपण चुकीचा पिन 5 वेळा प्रविष्‍ट केला.\n%1$d सेकंदामध्‍ये पुन्हा प्रयत्न करा. - आपण चुकीचा पिन 5 वेळा प्रविष्‍ट केला.\n%1$d सेकंदांमध्‍ये पुन्हा प्रयत्न करा. - - "तो पिन चुकीचा होता. पुन्हा प्रयत्न करा." - "पुन्हा प्रयत्न करा, पिन जुळत नाही" - "सेटिंग्ज" - "चॅनेल सूची सानुकूल करा" - "आपल्या कार्यक्रम मार्गदर्शकासाठी चॅनेल निवडा" - "चॅनेल स्त्रोत" - "नवीन चॅनेल उपलब्ध आहेत" - "पालक नियंत्रणे" - "मुक्त स्त्रोत परवाने" - "मुक्त स्त्रोत परवाने" - "आवृत्ती" - "हे चॅनेल पाहण्यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा" - "हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा" - "हा कार्यक्रम %1$s रेट केला आहे.\n हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा." - "हे चॅनेल पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." - "हा प्रोग्राम पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." - "हा प्रोग्राम %1$s रेट केलेला आहे.\nहा प्रोग्राम पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा." - "कार्यक्रम अवरोधित केला आहे" - "हा कार्यक्रम %1$s रेट केला आहे" - "फक्त ऑडिओ" - "खराब सिग्नल" - "कोणतेही इंटरनेट कनेक्शन नाही" - - दुसरे चॅनेल रेकॉर्ड केले जात असल्याने %1$s पर्यंत हे चॅनेल प्ले केले जाऊ शकत नाही. \n\nरेकॉर्डिंग अनुसूची समायोजित करण्यासाठी उजवीकडे दाबा. - दुसरे चॅनेल रेकॉर्ड केले जात असल्याने %1$s पर्यंत हे चॅनेल प्ले केले जाऊ शकत नाही. \n\nरेकॉर्डिंग अनुसूची समायोजित करण्यासाठी उजवीकडे दाबा. - - "शीर्षक नाही" - "चॅनेल अवरोधित केले" - "नवीन" - "स्त्रोत" - - %1$d चॅनेल - %1$d चॅनेल - - "कोणतेही चॅनेल उपलब्ध नाही" - "नवीन" - "सेट केलेले नाही" - "अधिक स्रोत मिळवा" - "थेट चॅनेल प्रदान करणारे अॅप्स ब्राउझ करा" - "नवीन चॅनेल स्रोत उपलब्ध आहेत" - "प्रदान करण्‍यासाठी नवीन चॅनेल स्रोतांमध्‍ये चॅनेल आहेत. \n आता ते सेट करा किंवा चॅनेल स्रोत सेटिंगमध्ये हे नंतर करा." - "आता सेट करा" - "ठीक आहे, समजले" - - - "टीव्ही मेनूवर प्रवेश करण्यासाठी ""निवडा दाबा""." - "कोणतेही टीव्ही इनपुट आढळले नाही" - "टीव्ही इनपुट शोधू शकत नाही" - "PIP समर्थित नाही" - "PIP सह दर्शविले जाऊ शकते असे कोणतेही उपलब्ध इनपुट नाही" - "ट्यूनर प्रकार अनुकूल नाही. कृपया ट्यूनर प्रकार टीव्ही इनपुटसाठी थेट चॅनेल अॅप लाँच करा." - "ट्यून अयशस्वी झाले" - "ही क्रिया हाताळण्यासाठी कोणताही अ‍ॅप आढळला नाही." - "सर्व स्त्रोत चॅनेल लपविले आहेत.\nपाहण्‍यासाठी कमीतकमी एक चॅनेल निवडा." - "व्‍हिडिओ अनपेक्षितपणे अनुपलब्ध आहे" - "बॅक की कनेक्ट केलेल्या डिव्हाइससाठी आहे. बाहेर पडण्यासाठी मुख्यपृष्ठ बटण दाबा." - "थेट चॅनेलना टीव्ही सूचींचे वाचन करण्यासाठी परवानगीची आवश्यकता आहे." - "आपले स्रोत सेट करा" - "थेट चॅनेल पारंपारिक TV चॅनेलच्या अनुभवास अॅप्सने प्रदान केलेल्या प्रवाहित केलेल्या चॅनेलसह एकत्रित करतात. \n\nआधीपासून स्थापित केलेले चॅनेल स्रोत सेट करून प्रारंभ करा किंवा थेट चॅनेल प्रदान करणार्‍या आणखी अॅप्ससाठी Google Play स्टोअर ब्राउझ करा." - "रेकॉर्डिंग आणि अनुसूची" - "10 मिनिटे" - "30 मिनिटे" - "1 तास" - "3 तास" - "अलीकडील" - "अनुसूचित" - "मालिका" - "इतर" - "चॅनेल रेकॉर्ड केले जाऊ शकत नाही." - "प्रोग्राम रेकॉर्ड केला जाऊ शकतो." - "रेकॉर्ड करण्‍यासाठी %1$s ची अनुसूची केली गेली" - "%1$s चे रेकॉर्डिंग आतापासून %2$s पर्यंत" - "पूर्ण अनुसूची" - - पुढील %1$d दिवस - पुढील %1$d दिवस - - - %1$d मिनिट - %1$d मिनिटे - - - %1$d नवीन रेकॉर्डिंग - %1$d नवीन रेकॉर्डिंग - - - %1$d रेकॉर्डिंग - %1$d रेकॉर्डिंग - - - %1$d रेकॉर्डिंगची अनुसूची केली - %1$d रेकॉर्डिंगची अनुसूची केली - - "पहा" - "सुरूवातीपासून प्ले करा" - "प्ले करणे पुनः सुरु करा" - "हटवा" - "रेकॉर्डिंग हटवा" - "पुनः सुरु करा" - "हंगाम %1$s" - "अनुसूची पहा" - "आणखी वाचा" - "रेकॉर्डिंग हटवा" - "आपण हटवू इच्छिता ते भाग निवडा. एकदा हटविले की ते पुनर्प्राप्त केले जाऊ शकत नाहीत." - "हटविण्यासाठी कोणतेही रेकॉर्डिंग नाहीत." - "पाहिलेले भाग निवडा" - "सर्व भाग निवडा" - "सर्व भागांची निवड रद्द करा" - "%2$d पैकी %1$d मिनिटांची रेकॉर्डिंग पाहिली" - "%2$d पैकी %1$d सेकंदांची रेकॉर्डिंग पाहिली" - "कधीही पाहिली नाहीत" - - %2$d पैकी %1$d भाग हटविला आहे - %2$d पैकी %1$d भाग हटविले आहेत - - "प्राधान्य" - "सर्वोच्च" - "सर्वात निम्न" - "%1$d क्र." - "चॅनेल" - "कोणतेही" - "प्राधान्य निवडा" - "एकाच वेळी रेकॉर्ड करण्यासाठी खूप जास्त प्रोग्राम असतात तेव्हा, केवळ अधिक प्राधान्ये असलेले प्रोग्राम रेकॉर्ड केले जातील." - "जतन करा" - "एक-वेळ रेकॉर्डिंगला सर्वोच्च प्राधान्य आहे" - "रद्द करा" - "रद्द करा" - "विसरा" - "थांबा" - "रेकॉर्डिंग अनुसूची पहा" - "हा एक कार्यक्रम" - "आता - %1$s" - "संपूर्ण मालिका…" - "तरीही शेड्यूल करा" - "त्याऐवजी हे रेकॉर्ड करा" - "हे रेकॉर्डिंग रद्द करा" - "आता पहा" - "रेकॉर्ड करण्यायोग्य" - "रेकॉर्डिंग अनुसूचित केले" - "रेकॉर्डिंग संबंधी विरोध" - "रेकॉर्डिंग" - "रेकॉर्डिंग अयशस्वी झाले" - "रेकॉर्डिंग अनुसूची तयार करण्यासाठी प्रोग्राम वाचत आहे" - "वाचन कार्यक्रम" - - - "DVR साठी आणखी संचय आवश्यक आहे" - "आपण DVR ने प्रोग्राम रेकॉर्ड करण्यात सक्षम असाल. तथापि DVR ने कार्य करण्यासाठी आपल्या डिव्हाइसवर आता पुरेसा संचय नाही. कृपया %1$sGB किंवा त्यापेक्षा मोठ्या बाह्य ड्राइव्हशी कनेक्ट करा आणि त्यास डिव्हाइस संचय म्हणून स्वरूपित करण्‍यासाठी चरणांचे अनुसरण करा." - "संचय गहाळ आहे" - "DVR ने वापरलेला काही संचय गहाळ आहे. कृपया DVR पुन्हा सक्षम करण्‍यासाठी आपण पूर्वी वापरलेला बाह्य ड्राइव्ह कनेक्ट करा. वैकल्पिकपणे, यापुढे संचय उपलब्ध नसल्यास आपण तो विसरणे निवडू शकता." - "संचय विसरलात?" - "आपली सर्व रेकॉर्ड केलेली सामग्री आणि अनुसूची गमावल्या जातील." - "रेकॉर्डिंग थांबवायचे?" - "रेकॉर्ड केलेली सामग्री जतन केली जाईल." - - - "रेकॉर्डिंगची अनुसूची केली परंतु त्यासंबंधी विरोध आहेत" - "रेकॉर्डिंग सुरू झाली परंतु त्यासंबंधी विरोध आहेत" - "%1$s रेकॉर्ड केला जाईल." - "%1$s रेकॉर्ड केला जात आहे." - "%1$s चे काही भाग रेकॉर्ड केले जाणार नाहीत." - "%1$s आणि %2$s चे काही भाग रेकॉर्ड केले जाणार नाहीत." - "%1$s, %2$s चे काही भाग आणि आणखी एक अनुसूची रेकॉर्ड केली जाणार नाही." - - %1$s, %2$s आणि आणखी %3$d अनुसूची रेकॉर्ड केली जाणार नाही. - %1$s, %2$s आणि आणखी %3$d अनुसूची रेकॉर्ड केली जाणार नाही. - - "आपल्याला काय रेकॉर्ड करायला आवडेल?" - "आपल्याला किती वेळ रेकॉर्ड करायला आवडेल?" - "आधीच शेड्यूल केला आहे" - "%1$s रोजी रेकॉर्ड करण्यासाठी तोच कार्यक्रम आधीच शेड्यूल केला आहे." - "आधीच रेकॉर्ड केला आहे" - "हा कार्यक्रम आधीच रेकॉर्ड केला गेला आहे. तो DVR लायब्ररीमध्ये उपलब्ध आहे." - - - - - - - - - "रेकॉर्ड केलेला प्रोग्राम सापडला नाही." - "संबंधित रेकॉर्डिंग" - "(कोणत्याही प्रोग्रामचे वर्णन नाही)" - - %1$d रेकॉर्डिंग - %1$d रेकॉर्डिंग - - " / " - "%1$s रेकॉर्डिंग अनुसूची मधून काढला" - "ट्यूनर विरोधामुळे अंशतः रेकॉर्ड केले जाईल." - "ट्यूनर विरोधामुळे रेकॉर्ड केले जाणार नाही." - "अद्याप कोणतेही रेकॉर्डिंग शेड्यूल केलेले नाही\nआपण कार्यक्रम मार्गदर्शकामधून रेकॉर्डिंग शेड्यूल करू शकता." - - %1$d रेकॉर्डिंग संबंधी विरोध - %1$d रेकॉर्डिंग संबंधी विरोध - - "मालिका सेटिंग्ज" - "मालिका रेकॉर्ड करणे प्रारंभ करा" - "मालिका रेकॉर्ड करणे थांबवा" - "मालिका रेकॉर्ड करणे थांबवायचे?" - "रेकॉर्ड केलेले भाग DVR लायब्ररी मध्ये उपलब्ध राहतील." - "थांबा" - "कोणतेही भाग उपलब्ध नाहीत.\nते उपलब्ध झाल्यावर रेकॉर्ड केले जातील." - - (%1$d मिनिट) - (%1$d मिनिटे) - - "आज" - "उद्या" - "काल" - "आज %1$s" - "उद्या %1$s" - "गुणसंख्‍या" - diff --git a/res/values-ms-rMY-v23/strings.xml b/res/values-ms-rMY-v23/strings.xml new file mode 100644 index 00000000..ba29d7b7 --- /dev/null +++ b/res/values-ms-rMY-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Saluran" + diff --git a/res/values-ms-rMY/arrays.xml b/res/values-ms-rMY/arrays.xml new file mode 100644 index 00000000..16ccce64 --- /dev/null +++ b/res/values-ms-rMY/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Biasa" + "Penuh" + "Zum" + + + "Semua saluran" + "Keluarga/Kanak-Kanak" + "Sukan" + "Beli-belah" + "Filem" + "Komedi" + "Pelancongan" + "Drama" + "Pendidikan" + "Haiwan/Hidupan Liar" + "Berita" + "Permainan" + "Seni" + "Hiburan" + "Gaya Hidup" + "Muzik" + "Perdana" + "Teknikal/Sains" + + + "Saluran Langsung" + "Cara mudah untuk menemui kandungan" + "Muat turun apl, dapatkan lebih banyak saluran" + "Sesuaikan barisan saluran anda" + + + "Tonton kandungan daripada apl anda seperti menonton saluran pada TV." + "Semak imbas kandungan daripada apl anda dengan menggunakan panduan biasa dan antara muka yang mesra, \n sama seperti saluran pada TV." + "Tambahkan lagi saluran dengan memasang apl yang menawarkan saluran langsung. \nCari apl yang serasi di Gedung Google Play dengan menggunakan pautan dalam menu TV." + "Sediakan sumber saluran yang baru dipasang untuk menyesuaikan senarai saluran anda. \nPilih sumber Saluran dalam menu Tetapan untuk memulakan." + + diff --git a/res/values-ms-rMY/rating_system_strings.xml b/res/values-ms-rMY/rating_system_strings.xml new file mode 100644 index 00000000..a5f781fe --- /dev/null +++ b/res/values-ms-rMY/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Rancangan mungkin mengandungi bahan yang tidak sesuai untuk khalayak di bawah umur 15 tahun, maka budi bicara ibu bapa perlu digunakan." + "Rancangan mungkin mengandungi bahan yang tidak sesuai untuk khalayak di bawah umur 19 tahun, maka rancangan itu tidak sesuai untuk golongan muda ini." + "Dialog tidak senonoh" + "Bahasa kasar" + "Kandungan seksual" + "Keganasan" + "Keganasan fantasi" + "Rancangan ini direka agar sesuai untuk semua kanak-kanak." + "Rancangan ini direka untuk kanak-kanak berumur 7 tahun ke atas." + "Kebanyakan ibu bapa akan mendapati rancangan ini sesuai untuk semua peringkat umur." + "Rancangan ini mengandungi bahan yang mungkin dianggap tidak sesuai untuk kanak-kanak oleh ibu bapa. Ramai ibu bapa yang mungkin mahu menontonnya bersama-sama anak kecil mereka." + "Rancangan ini mengandungi bahan yang mungkin dianggap tidak sesuai untuk kanak-kanak di bawah umur 14 tahun oleh kebanyakan ibu bapa." + "Rancangan ini direka khusus untuk tontonan orang dewasa, oleh sebab itu mungkin tidak sesuai untuk kanak-kanak di bawah umur 17 tahun." + "Penilaian filem" + "Khalayak umum. Tiada sebarang kandungan yang menyinggung perasaan ibu bapa untuk ditonton oleh kanak-kanak." + "Bimbingan ibu bapa disyorkan. Mungkin mengandungi bahan yang dianggap tidak sesuai oleh ibu bapa untuk anak kecil mereka." + "Amaran kepada ibu bapa. Sesetengah bahan mungkin tidak sesuai untuk praremaja." + "Terhad. Mengandungi bahan dewasa. Ibu bapa digesa supaya mengetahui lebih lanjut tentang filem sebelum membawa anak kecil bersama mereka." + "Usia 17 tahun ke bawah tidak dibenarkan. Kandungan dewasa. Kanak-kanak tidak dibenarkan." + diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml new file mode 100644 index 00000000..91716d81 --- /dev/null +++ b/res/values-ms-rMY/strings.xml @@ -0,0 +1,357 @@ + + + + + "mono" + "stereo" + "Kawalan main" + "Saluran" + "Pilihan TV" + "Kawalan main tidak tersedia untuk saluran ini" + "Main atau jeda" + "Mara laju" + "Gulung semula" + "Seterusnya" + "Sebelumnya" + "Panduan program" + "Saluran baharu tersedia" + "Buka %1$s" + "Sari kata" + "Mod paparan" + "PIP" + "Berbilang audio" + "Dptkn lg saluran" + "Tetapan" + "TV (antena/kabel)" + "Maklumat program tiada" + "Tiada maklumat" + "Saluran disekat" + "Bahasa tidak diketahui" + "Sari kata %1$d" + "Sari kata" + "Dimatikan" + "Sesuaikan format" + "Tetapkan pilihan sari kata seluruh sistem" + "Mod paparan" + "Berbilang audio" + "mono" + "stereo" + "5.1 keliling" + "7.1 keliling" + "%d saluran" + "Sesuaikan saluran" + "Pilih kumpulan" + "Nyahpilih kumpulan" + "Kumpulkan mengikut" + "Sumber saluran" + "HD/SD" + "HD" + "SD" + "Kumpulkan mengikut" + "Rancangan ini disekat" + "Rancangan ini tidak dinilai" + "Rancangan ini diberi rating %1$s" + "Input tidak menyokong autoimbas" + "Tidak dapat memulakan autoimbas untuk \'%s\'" + "Tidak dapat memulakan pilihan sari kata untuk seluruh sistem." + + %1$d saluran ditambahkan + %1$d saluran ditambahkan + + "Tiada saluran ditambahkan" + "Kawalan ibu bapa" + "Hidup" + "Dimatikan" + "Saluran disekat" + "Sekat semua" + "Nyahsekat semua" + "Saluran tersembunyi" + "Sekatan program" + "Tukar PIN" + "Sistem penilaian" + "Penilaian" + "Lihat sistem penilaian" + "Negara lain" + "Tiada" + "Tiada" + "Tidak dinilai" + "Sekat rancangan yg tdk dinilai" + "Tiada" + "Sekatan ketat" + "Sekatan sederhana" + "Sekatan longgar" + "Tersuai" + "Kandungan sesuai untuk kanak-kanak" + "Kandungan sesuai utk kanak-kanak berusia" + "Kandungan yang sesuai untuk remaja" + "Sekatan manual" + + + "%1$s & subpenilaian" + "Subpenilaian" + "Masukkan PIN anda untuk menonton saluran ini" + "Masukkan PIN anda untuk menonton rancangan ini" + "Rancangan ini diberi rating %1$s. Masukkan PIN anda untuk menonton rancangan ini" + "Rancangan ini tidak dinilai. Masukkan PIN anda untuk menonton rancangan ini" + "Masukkan PIN anda" + "Untuk menetapkan kawalan ibu bapa, buat PIN" + "Masukkan PIN baharu" + "Sahkan PIN anda" + "Masukkan PIN semasa anda" + + Anda telah memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa %1$d saat. + Anda telah memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa %1$d saat. + + "PIN itu salah. Cuba lagi." + "Cuba lagi, PIN tidak sepadan" + "Masukkan Poskod anda." + "Apl Saluran Langsung akan menggunakan Poskod untuk menyediakan panduan lengkap rancangan saluran TV." + "Masukkan Poskod anda" + "Poskod tidak sah" + "Tetapan" + "Sesuaikan senarai saluran" + "Pilih saluran untuk panduan rancangan anda" + "Sumber saluran" + "Saluran baharu tersedia" + "Kawalan ibu bapa" + "Anjakan masa" + "Rakam semasa menonton supaya anda boleh menjeda atau mandir rancangan langsung.\nAmaran: Tindakan ini boleh mengurangkan hayat storan dalaman melalui penggunaan intensif storan itu." + "Lesen sumber terbuka" + "Hantar maklum balas" + "Versi" + "Untuk menonton saluran ini, tekan Kanan dan masukkan PIN anda" + "Untuk menonton rancangan ini, tekan Kanan dan masukkan PIN anda" + "Rancangan ini tidak dinilai.\nUntuk menonton rancangan ini, tekan Kanan kemudian masukkan PIN anda" + "Rancangan ini diberi rating %1$s.\nUntuk menonton rancangan ini, tekan Kanan, kemudian masukkan PIN anda." + "Untuk menonton saluran ini, gunakan apl lalai TV Langsung." + "Untuk menonton program ini, gunakan apl lalai TV Langsung." + "Rancangan ini tidak dinilai.\nUntuk menonton rancangan ini, gunakan apl lalai TV Langsung." + "Atur cara ini telah dinilai %1$s.\nUntuk menonton program ini, gunakan apl lalai TV Langsung." + "Rancangan disekat" + "Rancangan ini tidak dinilai" + "Rancangan ini diberi rating %1$s" + "Audio sahaja" + "Isyarat lemah" + "Tiada sambungan Internet" + + Saluran ini tidak dapat dimainkan hingga %1$s kerana saluran lain sedang dirakamkan. \n\nTekan Kanan untuk melaraskan jadual rakaman. + Saluran ini tidak dapat dimainkan hingga %1$s kerana saluran lain sedang dirakamkan. \n\nTekan Kanan untuk melaraskan jadual rakaman. + + "Tiada tajuk" + "Saluran disekat" + "Baharu" + "Sumber" + + %1$d saluran + 1$d saluran + + "Tiada saluran yang tersedia" + "Baharu" + "Tidak disediakan" + "Dapatkan lebih banyak sumber" + "Semak imbas apl yang menawarkan saluran langsung" + "Sumber saluran yang baharu tersedia" + "Sumber saluran baharu mempunyai saluran untuk ditawarkan.\nSediakan saluran sekarang atau lakukannya kemudian dalam tetapan sumber saluran." + "Sediakan sekarang" + "OK" + + + "Tekan PILIH"" untuk mengakses menu TV." + "Input TV tidak ditemui" + "Tidak menemui input TV" + "Jenis penala tidak sesuai; Sila lancarkan apl Saluran Langsung untuk input TV jenis penala." + "Penalaan gagal" + "Tiada apl ditemui untuk mengendalikan tindakan ini." + "Semua saluran sumber disembunyikan.\nPilih sekurang-kurangnya satu saluran untuk ditonton." + "Video ini tidak tersedia tanpa dijangka" + "Kekunci BACK adalah untuk peranti yang tersambung. Tekan butang HOME untuk keluar." + "Saluran Langsung memerlukan kebenaran untuk membaca penyenaraian TV." + "Sediakan sumber anda" + "Saluran langsung menggabungkan pengalaman saluran TV tradisional dengan saluran penstriman yang disediakan oleh apl. \n\nMulakan dengan menyediakan sumber saluran yang sudah dipasang. Selain itu, semak imbas Gedung Google Play untuk mendapatkan lebih banyak apl yang menawarkan saluran langsung." + "Rakaman & jadual" + "10 minit" + "30 minit" + "1 jam" + "3 jam" + "Terbaharu" + "Dijadualkan" + "Siri" + "Lain-lain" + "Saluran ini tidak boleh dirakam." + "Rancangan ini tidak boleh dirakam." + "%1$s telah dijadualkan untuk dirakamkan" + "Merakam %1$s mulai sekarang hingga %2$s" + "Jadual penuh" + + %1$d hari seterusnya + %1$d hari seterusnya + + + %1$d minit + %1$d minit + + + %1$d rakaman baharu + %1$d rakaman baharu + + + %1$d rakaman + %1$d rakaman + + + %1$d rakaman dijadualkan + %1$d rakaman dijadualkan + + "Batalkan rakaman" + "Hentikan rakaman" + "Tonton" + "Mainkan dari mula" + "Sbg smula prses main" + "Padam" + "Padam rakaman" + "Sambung semula" + "Musim %1$s" + "Lihat jadual" + "Baca lagi" + "Padam rakaman" + "Pilih episod yang anda ingin padamkan. Episod ini tidak boleh dipulihkan setelah dipadamkan." + "Tiada rakaman untuk dipadamkan." + "Pilih episod yg sudah ditonton" + "Pilih semua episod" + "Nyahpilih semua episod" + "%1$d daripada %2$d minit tontonan" + "%1$d daripada %2$d saat tontonan" + "Belum pernah ditonton" + + %1$d daripada %2$d episod dipadamkan + %1$d daripada %2$d episod dipadamkan + + "Keutamaan" + "Tertinggi" + "Terendah" + "Nombor %1$d" + "Saluran" + "Sebarang" + "Pilih keutamaan" + "Apabila ada terlalu banyak rancangan yang ingin dirakamkan secara serentak, rancangan yang berkeutamaan lebih tinggi sahaja yang akan dirakamkan." + "Simpan" + "Rakaman bersifat sekali sahaja mendapat keutamaan tertinggi" + "Berhenti" + "Lihat jadual rakaman" + "Program ini sahaja" + "sekarang - %1$s" + "Keseluruhan siri..." + "Jadualkan juga" + "Sebaliknya rakamkan yang ini" + "Batalkan rakaman ini" + "Tonton sekarang" + "Padam rakaman..." + "Boleh rakam" + "Rakaman dijadualkan" + "Konflik rakaman" + "Merakam" + "Perakaman gagal" + "Membaca rancangan" + "Lihat rakaman terbaharu" + "Rakaman %1$s tidak lengkap." + "Rakaman %1$s dan %2$s tidak lengkap." + "Rakaman %1$s, %2$s dan %3$s tidak lengkap." + "Rakaman %1$s tidak lengkap kerana storan tidak mencukupi." + "Rakaman %1$s dan %2$s tidak lengkap kerana storan tidak mencukupi." + "Rakaman %1$s, %2$s dan %3$s tidak lengkap kerana storan tidak mencukupi." + "DVR memerlukan storan yang lebih besar" + "Anda boleh merakam rancangan menggunakan DVR. Walau bagaimanapun storan pada peranti anda kini tidak mencukupi untuk DVR berfungsi. Sila sambungkan pemacu luaran %1$dGB atau lebih besar dan ikut langkah untuk memformat pemacu itu sebagai storan peranti." + "Storan tidak mencukupi" + "Rancangan ini tidak akan dirakamkan kerana storan tidak mencukupi. Cuba padamkan beberapa rakaman sedia ada." + "Storan hilang" + "Berhenti merakam?" + "Kandungan yang dirakamkan akan disimpan." + "Rakaman %1$s akan dihentikan kerana wujud konflik dengan rancangan ini. Kandungan yang dirakamkan akan disimpan." + "Rakaman dijadualkan tetapi wujud konflik" + "Rakaman telah bermula tetapi wujud konflik" + "%1$s akan dirakamkan." + "%1$s sedang dirakamkan." + "Sebahagian %1$s tidak akan dirakamkan." + "Sebahagian %1$s dan %2$s tidak akan dirakamkan." + "Sebahagian %1$s, %2$s dan satu lagi jadual tidak akan dirakamkan." + + Sebahagian %1$s, %2$s dan %3$d lagi jadual tidak akan dirakamkan. + Sebahagian %1$s, %2$s dan %3$d lagi jadual tidak akan dirakamkan. + + "Apakah jenis rakaman yg anda ingin buat?" + "Berapa lama anda ingin merakam?" + "Sudah dijadualkan" + "Rancangan yang sama telah dijadualkan akan dirakamkan pada %1$s." + "Sudah dirakamkan" + "Rancangan ini telah dirakamkan dan tersedia di pustaka DVR." + "Rakaman siri telah dijadualkan" + + %1$d rakaman telah dijadualkan untuk %2$s. + %1$d rakaman telah dijadualkan untuk %2$s. + + + %1$d rakaman telah dijadualkan untuk %2$s. %3$d daripadanya tidak akan dirakamkan kerana berlaku konflik. + %1$d rakaman telah dijadualkan untuk %2$s. Rakaman ini tidak akan dijalankan kerana berlaku konflik. + + + %1$d rakaman telah dijadualkan untuk %2$s. %3$d episod siri ini dan siri yang lain tidak akan dirakamkan kerana berlaku konflik. + %1$d rakaman telah dijadualkan untuk %2$s. %3$d episod siri ini dan siri yang lain tidak akan dirakamkan kerana berlaku konflik. + + + %1$d rakaman telah dijadualkan untuk %2$s. 1 episod siri yang lain tidak akan dirakamkan kerana berlaku konflik. + %1$d rakaman telah dijadualkan untuk %2$s. 1 episod siri yang lain tidak akan dirakamkan kerana berlaku konflik. + + + %1$d rakaman telah dijadualkan untuk %2$s. %3$d episod siri yang lain tidak akan dirakamkan kerana berlaku konflik. + %1$d rakaman telah dijadualkan untuk %2$s. %3$d episod siri yang lain tidak akan dirakamkan kerana berlaku konflik. + + "Program yang dirakam tidak ditemui." + "Rakaman yang berkaitan" + + %1$d rakaman + %1$d rakaman + + " / " + "%1$s dialih keluar daripada jadual rakaman" + "Sebahagian sahaja yang akan dirakamkan kerana konflik penala." + "Tidak akan dirakamkan kerana konflik penala." + "Belum ada lagi rakaman yang dijadualkan.\nAnda boleh menjadualkan rakaman daripada panduan rancangan." + + %1$d konflik rakaman + %1$d konflik rakaman + + "Tetapan siri" + "Mulakan rakaman siri" + "Hentikan rakaman siri" + "Berhenti merakam siri?" + "Episod yang dirakamkan akan kekal tersedia dalam pustaka DVR." + "Berhenti" + "Tiada episod sedang dalam siaran." + "Tiada episod yang tersedia.\nEpisod ini akan dirakamkan apabila sudah tersedia." + + (%1$d minit) + (%1$d minit) + + "Hari ini" + "Esok" + "Semalam" + "%1$s hari ini" + "%1$s esok" + "Markah" + "Rancangan yang Dirakamkan" + diff --git a/res/values-ms-v23/strings.xml b/res/values-ms-v23/strings.xml deleted file mode 100644 index ba29d7b7..00000000 --- a/res/values-ms-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Saluran" - diff --git a/res/values-ms/arrays.xml b/res/values-ms/arrays.xml deleted file mode 100644 index 16ccce64..00000000 --- a/res/values-ms/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Biasa" - "Penuh" - "Zum" - - - "Semua saluran" - "Keluarga/Kanak-Kanak" - "Sukan" - "Beli-belah" - "Filem" - "Komedi" - "Pelancongan" - "Drama" - "Pendidikan" - "Haiwan/Hidupan Liar" - "Berita" - "Permainan" - "Seni" - "Hiburan" - "Gaya Hidup" - "Muzik" - "Perdana" - "Teknikal/Sains" - - - "Saluran Langsung" - "Cara mudah untuk menemui kandungan" - "Muat turun apl, dapatkan lebih banyak saluran" - "Sesuaikan barisan saluran anda" - - - "Tonton kandungan daripada apl anda seperti menonton saluran pada TV." - "Semak imbas kandungan daripada apl anda dengan menggunakan panduan biasa dan antara muka yang mesra, \n sama seperti saluran pada TV." - "Tambahkan lagi saluran dengan memasang apl yang menawarkan saluran langsung. \nCari apl yang serasi di Gedung Google Play dengan menggunakan pautan dalam menu TV." - "Sediakan sumber saluran yang baru dipasang untuk menyesuaikan senarai saluran anda. \nPilih sumber Saluran dalam menu Tetapan untuk memulakan." - - diff --git a/res/values-ms/rating_system_strings.xml b/res/values-ms/rating_system_strings.xml deleted file mode 100644 index a5f781fe..00000000 --- a/res/values-ms/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Rancangan mungkin mengandungi bahan yang tidak sesuai untuk khalayak di bawah umur 15 tahun, maka budi bicara ibu bapa perlu digunakan." - "Rancangan mungkin mengandungi bahan yang tidak sesuai untuk khalayak di bawah umur 19 tahun, maka rancangan itu tidak sesuai untuk golongan muda ini." - "Dialog tidak senonoh" - "Bahasa kasar" - "Kandungan seksual" - "Keganasan" - "Keganasan fantasi" - "Rancangan ini direka agar sesuai untuk semua kanak-kanak." - "Rancangan ini direka untuk kanak-kanak berumur 7 tahun ke atas." - "Kebanyakan ibu bapa akan mendapati rancangan ini sesuai untuk semua peringkat umur." - "Rancangan ini mengandungi bahan yang mungkin dianggap tidak sesuai untuk kanak-kanak oleh ibu bapa. Ramai ibu bapa yang mungkin mahu menontonnya bersama-sama anak kecil mereka." - "Rancangan ini mengandungi bahan yang mungkin dianggap tidak sesuai untuk kanak-kanak di bawah umur 14 tahun oleh kebanyakan ibu bapa." - "Rancangan ini direka khusus untuk tontonan orang dewasa, oleh sebab itu mungkin tidak sesuai untuk kanak-kanak di bawah umur 17 tahun." - "Penilaian filem" - "Khalayak umum. Tiada sebarang kandungan yang menyinggung perasaan ibu bapa untuk ditonton oleh kanak-kanak." - "Bimbingan ibu bapa disyorkan. Mungkin mengandungi bahan yang dianggap tidak sesuai oleh ibu bapa untuk anak kecil mereka." - "Amaran kepada ibu bapa. Sesetengah bahan mungkin tidak sesuai untuk praremaja." - "Terhad. Mengandungi bahan dewasa. Ibu bapa digesa supaya mengetahui lebih lanjut tentang filem sebelum membawa anak kecil bersama mereka." - "Usia 17 tahun ke bawah tidak dibenarkan. Kandungan dewasa. Kanak-kanak tidak dibenarkan." - diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml deleted file mode 100644 index 816dee35..00000000 --- a/res/values-ms/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "mono" - "stereo" - "Kawalan main" - "Saluran terbaru" - "Pilihan TV" - "Pilihan PIP" - "Kawalan main tidak tersedia untuk saluran ini" - "Main atau jeda" - "Mara laju" - "Gulung semula" - "Seterusnya" - "Sebelumnya" - "Panduan program" - "Saluran baharu tersedia" - "Buka %1$s" - "Sari kata" - "Mod paparan" - "PIP" - "Hidupkan" - "Matikan" - "Berbilang audio" - "Dptkn lg saluran" - "Tetapan" - "Sumber" - "Silih" - "Hidupkan" - "Matikan" - "Bunyi" - "Utama" - "Tetingkap PIP" - "Reka Letak" - "Kanan bawah" - "Kanan atas" - "Kiri atas" - "Kiri bawah" - "Bersebelahan" - "Saiz" - "Besar" - "Kecil" - "Sumber input" - "TV (antena/kabel)" - "Maklumat program tiada" - "Tiada maklumat" - "Saluran disekat" - "Bahasa tidak diketahui" - "Sari kata" - "Dimatikan" - "Sesuaikan format" - "Tetapkan pilihan sari kata seluruh sistem" - "Mod paparan" - "Berbilang audio" - "mono" - "stereo" - "5.1 keliling" - "7.1 keliling" - "%d saluran" - "Sesuaikan saluran" - "Pilih kumpulan" - "Nyahpilih kumpulan" - "Kumpulkan mengikut" - "Sumber saluran" - "HD/SD" - "HD" - "SD" - "Kumpulkan mengikut" - "Rancangan ini disekat" - "Rancangan ini diberi rating %1$s" - "Input tidak menyokong autoimbas" - "Tidak dapat memulakan autoimbas untuk \'%s\'" - "Tidak dapat memulakan pilihan sari kata untuk seluruh sistem." - - %1$d saluran ditambahkan - %1$d saluran ditambahkan - - "Tiada saluran ditambahkan" - "Penala" - "Kawalan ibu bapa" - "Hidup" - "Dimatikan" - "Saluran disekat" - "Sekat semua" - "Nyahsekat semua" - "Saluran tersembunyi" - "Sekatan program" - "Tukar PIN" - "Sistem penilaian" - "Penilaian" - "Lihat sistem penilaian" - "Negara lain" - "Tiada" - "Tiada" - "Tiada" - "Sekatan ketat" - "Sekatan sederhana" - "Sekatan longgar" - "Tersuai" - "Kandungan sesuai untuk kanak-kanak" - "Kandungan sesuai utk kanak-kanak berusia" - "Kandungan yang sesuai untuk remaja" - "Sekatan manual" - - - "%1$s & subpenilaian" - "Subpenilaian" - "Masukkan PIN anda untuk menonton saluran ini" - "Masukkan PIN anda untuk menonton rancangan ini" - "Rancangan ini diberi rating %1$s. Masukkan PIN anda untuk menonton rancangan ini" - "Masukkan PIN anda" - "Untuk menetapkan kawalan ibu bapa, buat PIN" - "Masukkan PIN baharu" - "Sahkan PIN anda" - "Masukkan PIN semasa anda" - - Anda telah memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa %1$d saat. - Anda telah memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa %1$d saat. - - "PIN itu salah. Cuba lagi." - "Cuba lagi, PIN tidak sepadan" - "Tetapan" - "Sesuaikan senarai saluran" - "Pilih saluran untuk panduan rancangan anda" - "Sumber saluran" - "Saluran baharu tersedia" - "Kawalan ibu bapa" - "Lesen sumber terbuka" - "Lesen sumber terbuka" - "Versi" - "Untuk menonton saluran ini, tekan Kanan dan masukkan PIN anda" - "Untuk menonton rancangan ini, tekan Kanan dan masukkan PIN anda" - "Rancangan ini diberi rating %1$s.\nUntuk menonton rancangan ini, tekan Kanan, kemudian masukkan PIN anda." - "Untuk menonton saluran ini, gunakan apl lalai TV Langsung." - "Untuk menonton program ini, gunakan apl lalai TV Langsung." - "Atur cara ini telah dinilai %1$s.\nUntuk menonton program ini, gunakan apl lalai TV Langsung." - "Rancangan disekat" - "Rancangan ini diberi rating %1$s" - "Audio sahaja" - "Isyarat lemah" - "Tiada sambungan Internet" - - Saluran ini tidak dapat dimainkan hingga %1$s kerana saluran lain sedang dirakamkan. \n\nTekan Kanan untuk melaraskan jadual rakaman. - Saluran ini tidak dapat dimainkan hingga %1$s kerana saluran lain sedang dirakamkan. \n\nTekan Kanan untuk melaraskan jadual rakaman. - - "Tiada tajuk" - "Saluran disekat" - "Baharu" - "Sumber" - - %1$d saluran - 1$d saluran - - "Tiada saluran yang tersedia" - "Baharu" - "Tidak disediakan" - "Dapatkan lebih banyak sumber" - "Semak imbas apl yang menawarkan saluran langsung" - "Sumber saluran yang baharu tersedia" - "Sumber saluran baharu mempunyai saluran untuk ditawarkan.\nSediakan saluran sekarang atau lakukannya kemudian dalam tetapan sumber saluran." - "Sediakan sekarang" - "OK" - - - "Tekan PILIH"" untuk mengakses menu TV." - "Input TV tidak ditemui" - "Tidak menemui input TV" - "PIP tidak disokong" - "Tiada input tersedia yang boleh ditunjukkan dengan PIP" - "Jenis penala tidak sesuai; Sila lancarkan apl Saluran Langsung untuk input TV jenis penala." - "Penalaan gagal" - "Tiada apl ditemui untuk mengendalikan tindakan ini." - "Semua saluran sumber disembunyikan.\nPilih sekurang-kurangnya satu saluran untuk ditonton." - "Video ini tidak tersedia tanpa dijangka" - "Kekunci BACK adalah untuk peranti yang tersambung. Tekan butang HOME untuk keluar." - "Saluran Langsung memerlukan kebenaran untuk membaca penyenaraian TV." - "Sediakan sumber anda" - "Saluran langsung menggabungkan pengalaman saluran TV tradisional dengan saluran penstriman yang disediakan oleh apl. \n\nMulakan dengan menyediakan sumber saluran yang sudah dipasang. Selain itu, semak imbas Gedung Google Play untuk mendapatkan lebih banyak apl yang menawarkan saluran langsung." - "Rakaman & jadual" - "10 minit" - "30 minit" - "1 jam" - "3 jam" - "Terbaharu" - "Dijadualkan" - "Siri" - "Lain-lain" - "Saluran ini tidak boleh dirakam." - "Rancangan ini tidak boleh dirakam." - "%1$s telah dijadualkan untuk dirakamkan" - "Merakam %1$s mulai sekarang hingga %2$s" - "Jadual penuh" - - %1$d hari seterusnya - %1$d hari seterusnya - - - %1$d minit - %1$d minit - - - %1$d rakaman baharu - %1$d rakaman baharu - - - %1$d rakaman - %1$d rakaman - - - %1$d rakaman dijadualkan - %1$d rakaman dijadualkan - - "Tonton" - "Mainkan dari mula" - "Sbg smula prses main" - "Padam" - "Padam rakaman" - "Sambung semula" - "Musim %1$s" - "Lihat jadual" - "Baca lagi" - "Padam rakaman" - "Pilih episod yang anda ingin padamkan. Episod ini tidak boleh dipulihkan setelah dipadamkan." - "Tiada rakaman untuk dipadamkan." - "Pilih episod yg sudah ditonton" - "Pilih semua episod" - "Nyahpilih semua episod" - "%1$d daripada %2$d minit tontonan" - "%1$d daripada %2$d saat tontonan" - "Belum pernah ditonton" - - %1$d daripada %2$d episod dipadamkan - %1$d daripada %2$d episod dipadamkan - - "Keutamaan" - "Tertinggi" - "Terendah" - "Nombor %1$d" - "Saluran" - "Sebarang" - "Pilih keutamaan" - "Apabila ada terlalu banyak rancangan yang ingin dirakamkan secara serentak, rancangan yang berkeutamaan lebih tinggi sahaja yang akan dirakamkan." - "Simpan" - "Rakaman bersifat sekali sahaja mendapat keutamaan tertinggi" - "Batal" - "Batal" - "Lupakan" - "Berhenti" - "Lihat jadual rakaman" - "Program ini sahaja" - "sekarang - %1$s" - "Keseluruhan siri..." - "Jadualkan juga" - "Sebaliknya rakamkan yang ini" - "Batalkan rakaman ini" - "Tonton sekarang" - "Boleh rakam" - "Rakaman dijadualkan" - "Konflik rakaman" - "Merakam" - "Perakaman gagal" - "Membaca rancangan untuk membuat jadual rakaman" - "Membaca rancangan" - - - "DVR memerlukan storan yang lebih besar" - "Anda boleh merakam rancangan menggunakan DVR. Walau bagaimanapun storan pada peranti anda kini tidak mencukupi untuk DVR berfungsi. Sila sambungkan pemacu luaran %1$sGB atau lebih besar dan ikut langkah untuk memformat pemacu itu sebagai storan peranti." - "Storan hilang" - "Beberapa storan yang digunakan oleh DVR telah hilang. Sila sambungkan pemacu luaran yang anda gunakan sebelum ini untuk mendayakan semula DVR. Secara alternatif, anda boleh memilih untuk melupakan storan jika storan itu tidak lagi tersedia." - "Lupakan storan?" - "Semua kandungan dan jadual anda yang dirakamkan akan hilang." - "Berhenti merakam?" - "Kandungan yang dirakamkan akan disimpan." - - - "Rakaman dijadualkan tetapi wujud konflik" - "Rakaman telah bermula tetapi wujud konflik" - "%1$s akan dirakamkan." - "%1$s sedang dirakamkan." - "Sebahagian %1$s tidak akan dirakamkan." - "Sebahagian %1$s dan %2$s tidak akan dirakamkan." - "Sebahagian %1$s, %2$s dan satu lagi jadual tidak akan dirakamkan." - - Sebahagian %1$s, %2$s dan %3$d lagi jadual tidak akan dirakamkan. - Sebahagian %1$s, %2$s dan %3$d lagi jadual tidak akan dirakamkan. - - "Apakah jenis rakaman yg anda ingin buat?" - "Berapa lama anda ingin merakam?" - "Sudah dijadualkan" - "Rancangan yang sama telah dijadualkan akan dirakamkan pada %1$s." - "Sudah dirakamkan" - "Rancangan ini telah dirakamkan dan tersedia di pustaka DVR." - - - - - - - - - "Program yang dirakam tidak ditemui." - "Rakaman yang berkaitan" - "(Tiada perihalan program)" - - %1$d rakaman - %1$d rakaman - - " / " - "%1$s dialih keluar daripada jadual rakaman" - "Sebahagian sahaja yang akan dirakamkan kerana konflik penala." - "Tidak akan dirakamkan kerana konflik penala." - "Belum ada lagi rakaman yang dijadualkan.\nAnda boleh menjadualkan rakaman daripada panduan rancangan." - - %1$d konflik rakaman - %1$d konflik rakaman - - "Tetapan siri" - "Mulakan rakaman siri" - "Hentikan rakaman siri" - "Berhenti merakam siri?" - "Episod yang dirakamkan akan kekal tersedia dalam pustaka DVR." - "Berhenti" - "Tiada episod yang tersedia.\nEpisod ini akan dirakamkan apabila sudah tersedia." - - (%1$d minit) - (%1$d minit) - - "Hari ini" - "Esok" - "Semalam" - "%1$s hari ini" - "%1$s esok" - "Markah" - diff --git a/res/values-my-rMM-v23/strings.xml b/res/values-my-rMM-v23/strings.xml new file mode 100644 index 00000000..015247d8 --- /dev/null +++ b/res/values-my-rMM-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "ချာနယ်များ" + diff --git a/res/values-my-rMM/arrays.xml b/res/values-my-rMM/arrays.xml new file mode 100644 index 00000000..e302ceac --- /dev/null +++ b/res/values-my-rMM/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "ပုံမှန်" + "အပြည့်" + "ဇူးမ်" + + + "ချာနယ်များ အားလုံး" + "မိသားစု/ကလေးများ" + "အားကစား" + "​ဈေးဝယ်ခြင်း" + "ရုပ်ရှင်များ" + "ဟာသ" + "ခရီးလှည့်မှု" + "ပြဇာတ်" + "ပညာရေး" + "တိရိစ္ဆာန်/တောရိုင်း" + "သတင်း" + "ဂိမ်းကစားခြင်း" + "အနုပညာများ" + "ဖြေဖျော်မှု" + "ဘဝပုံစံ" + "ဂီတ" + "ပွဲဦး" + "စက်မှု/သိပ္ပံ" + + + "တိုက်ရိုက်လွှင့် ချန်နယ်များ" + "အကြောင်းအရာများကို စူးစမ်းရှာဖွေရန် ရိုးရှင်းသည့်နည်းလမ်းတစ်ခု" + "အက်ပ်ကိုဒေါင်းလုဒ်လုပ်ပြီး၊ နောက်ထပ်ချန်နယ်များ ရယူလိုက်ပါ" + "သင့်ချန်နယ် ထားသိုမှုကိုစိတ်ကြိုက်ပြုပြင်ပါ" + + + "TV ချန်နယ်များကြည့်သလို သင့်အက်ပ်များအတွင်း အကြောင်းအရာများကို ကြည့်ပါ။" + \n"TV ချန်နယ်များကဲ့သို့ပင်၊ အကျွမ်းတဝင်ရှိသည့်လမ်းညွှန်နှင့် ရင်းနှီးမှုရှိသည့် အင်တာဖေ့စ်ဖြင့် အက်ပ်များမှ အကြောင်းအရာများကိုကြည့်ပါ။" + "တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များပါဝင်သည့် အက်ပ်များကို ထည့်သွင်းခြင်းအားဖြင့် နောက်ထပ်ချန်နယ်များ ထည့်ပါ။ \nTV မန်နယူးရှိလင့်ခ်ကိုအသုံးပြု၍ Google Play Store အတွင်း ကိုက်ညီမှုရှိသည့်အက်ပ်များကို ရှာပါ။" + "သင့်ချန်နယ်စာရင်းကို စိတ်ကြိုက်ပြုပြင်ရန် အသစ်ထည့်သွင်းထားသည့် ချန်နယ်အရင်းမြစ်များကို တပ်ဆင်ပါ။ \nစတင်ရန် ဆက်တင်များ မန်နယူးတွင် ချန်နယ်အရင်းမြစ်များကို ရွေးပါ။" + + diff --git a/res/values-my-rMM/rating_system_strings.xml b/res/values-my-rMM/rating_system_strings.xml new file mode 100644 index 00000000..153ad407 --- /dev/null +++ b/res/values-my-rMM/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "အစီအစဉ်များတွင် အသက် ၁၅ နှစ်အောက် ပရိသတ်များအတွက် မသင့်တော်သည့် အကြောင်းအရာများ ပါဝင်နိုင်သောကြောင့် ၎င်းတို့ကို မိဘအုပ်ထိန်းမှုအောက်တွင် ထားရှိသင့်ပါသည်။" + "အစီအစဉ်များတွင် အသက် ၁၉ နှစ်အောက်ပရိသတ်များအတွက် မသင့်တော်သည့် အကြောင်းအရာများ ပါဝင်နိုင်သောကြောင့် အသက် ၁၉ နှစ်နှင့်အောက် လူငယ်များနှင့် ကိုက်ညီမှုမရှိပါ။" + "အကြံပြုထားသည့် စကားပြောဆိုမှု" + "ကြမ်းတမ်းသော စကားလုံးများ" + "လိင်ဆက်ဆံမှု အကြောင်းအရာ" + "အကြမ်းဖက်မှု" + "ဆန်းပြားသော အကြမ်းဖက်မှု" + "ဒီပရိုဂရမ်ကို ကလေးများ အားလုံး အတွက် သင့်လျော်အောင် စီမံပြုစုခဲ့သည်။" + "ဒီပရိုဂရမ်ကို အသက် ၇ နှစ်နှင့် အထက်ရှိ ကလေးများ အတွက် စီမံပြုစုခဲ့သည်။" + "မိသားစု အများစုက ဒီပရိုဂရမ်မှာ အသက်အရွယ် အားလုံး အတွက် ဆီလျော်သည်ဟု ယူဆကြမည်။" + "ဒီ program ဒီပရိုဂရမ်ထဲမှာ ပါဝင်သည့် အရာများကို မိဘများက အသက်ငယ် ကလေးများ အတွက် မသင့်လျော်ဟု ယူဆနိုင်သည်။ မိဘအများက ၎င်းကို သူတို့ရဲ့ ကလေးငယ်များနှင့် ကြည့်လိုနိုင်ကြတယ်။" + "ဒီပရိုဂရမ်ထဲမှာ ပါဝင်ကြသည့် အချို့ အရာများမှာ အသက် ၁၄ နှစ်အောက် ကလေးများ အတွက် မသင့်လျော်ဟု မိဘ အများက ယူဆဖွယ် ရှိပါသည်။" + "ဒီပရိုဂရမ်ကို သက်ကြီးများ ကြည့်ရှုရန် အထူး စီမံပြုစုခဲ့သည်၊ သို့ဖြစ်၍ ၁၇ အောက် ကလေးများ အတွက် သင့်လျော်နိုင်မည် မဟုတ်ပါ။" + "ရုပ်ရှင် အဆင့်သတ်မှတ်ချက်များ" + "သာမန် ပရိသတ်တွေ။ ကလေးတွေက ကြည့်ကြလို့ မိဘများအား မကျေမနပ် ဖြစ်စရာ ဘာမှ မပါပါ။" + "မိဘတွေရဲ့ လမ်းညွှန်မှု လိုအပ်တယ်။ မိဘတွေက ကလေးတွေ အတွက် မကြိုက်ကြမယ့် အကြောင်းအရာ အချို့ ပါရှိနိုင်တယ်။" + "မိဘတွေ အပြင်းအထန် စိုးရိမ်နေကြတယ်။ အချို့ အကြောင်းအရာမှာ ဆယ်ကျော်သက်ထက် ငယ်သူများအတွက် သင့်လျော်ချင်မှ သင်လျော်မယ်။" + "ကန့်သတ်ချက်ရှိ၊ အရွယ်ရောက်သူများ အတွက် အချို့အရာများ ပါရှိတယ်။ မိဘများအား ကလေးငယ်များကို သူတို့နှင့်အတူ ခေါ်မသွားမီ ရုပ်ရှင် အကြောင်းကို နောက်ထပ် လေ့လာရန် အကြံပြုပါတယ်။" + "အသက် ၁၇ နှစ်နှင့် အောက်ရှိသူ ဘယ်သူကိုမှ လက်မခံပါ။ အရွယ်ရောက်ပြီးသား ဖြစ်ရမှာ ရှင်းနေပါတယ်။ ကလေးများကို လက်မခံပါ။" + diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml new file mode 100644 index 00000000..c6475401 --- /dev/null +++ b/res/values-my-rMM/strings.xml @@ -0,0 +1,357 @@ + + + + + "မိုနို" + "စတီရီယို" + "Play ထိန်းချုပ်မှုများ" + "ချာနယ်များ" + "TV ရွေးစရာများ" + "ဤလိုင်းအတွက် အဖွင့်ထိန်းချုပ်ခြင်းများ မရနိုင်ပါ" + "ဖွင့်ပါ သို့မဟုတ် ခဏရပ်ပါ" + "ရှေ့သို့ အမြန်သွားရန်" + "ပြန်ရစ်ရန်" + "ရှေ့သို့" + "ယခင်" + "အစီအစဉ် လမ်းညွှန်" + "ရရှိနိုင်သည့် ချန်နယ်အသစ်များ" + "%1$s ဖွင့်ရန်" + "စာတမ်းထိုးများ" + "မြင်ကွင်း မုဒ်" + "PIP" + "အသံစုံ" + "နောက်ထပ်ချန်နယ်များ ရယူရန်" + "ဆက်တင်များ" + "တီဗီ (ဧရီယာတိုင်/ကြိုး)" + "အစီအစဉ် အချက်အလက်များ မရှိ" + "သတင်းအချက်အလက် မရှိပါ" + "ပိတ်ဆို့ ချာနယ်" + "အမည်မသိဘာသာစကား" + "စာတန်းထိုး %1$d" + "စာတမ်းထိုးများ" + "ပိတ်ထား" + "စိတ်ကြိုက်ပုံစံချရန်" + "စာတမ်းထိုးအတွက် ရွေးစရာများ သတ်မှတ်ရန်" + "မြင်ကွင်း မုဒ်" + "အသံစုံ" + "မိုနို" + "စတီရီယို" + "၅.၁ ပတ်လည်" + "၇.၁ ပတ်လည်" + "%d ချာနယ်" + "ချာနယ်စာရင်း စိတ်တိုင်းကျ" + "အုပ်စု ရွေးရန်" + "အုပ်စု ရွေးမှု ဖျက်ရန်" + "အုပ်စုဖွဲ့မှု" + "ချာနယ် ရင်းမြစ်များ" + "HD/SD" + "HD" + "SD" + "အုပ်စုဖွဲ့မှု" + "ဤအစီအစဉ်အား ပိတ်ဆို့ထားသည်။" + "ဤအစီအစဉ်ကို အဆင့်မသတ်မှတ်ရသေးပါ" + "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏" + "ထည့်သွင်းမှုက အော်တို-စကင် ပံ့ပိုးမပေး" + "\'%s\' အတွက် အော်တို−စကင် မစနိုင်ပါ" + "ပိတ်ထားသည့် စာတန်းများ အတွက် စနစ်ဆိုင်ရာ ဦးစားပေးမှုများကို စတင်မရပါ။" + + ချန်နယ် %1$d ခုပေါင်းထည့်ပြီးပါပြီ + ချန်နယ် %1$d ခုပေါင်းထည့်ပြီးပါပြီ + + "ချာနယ်များ ထည့်မထား" + "မိဘ ထိန်းချုပ်မှု" + "ဖွင့်ရန်" + "ပိတ်ရန်" + "ချာနယ်များ ပိတ်ရန်" + "အားလုံး ပိတ်ဆို့ရန်" + "အားလုံး ပြန်ဖွင့်ရန်" + "ဝှက်ထားသည့် ချာနယ်များ" + "အစီအစဉ်ကန့်သတ်ချက်" + "PIN ပြောင်းရန်" + "အဆင့်ပေး စနစ်များ" + "အမှတ်ပေးမှုများ" + "အဆင့်ပေးစနစ်များပြပါ" + "အခြားနိုင်ငံများ" + "မရှိ" + "မရှိ" + "အဆင့် သတ်မှတ်မထားပါ" + "မစိစစ်သောအစီအစဉ်များ ပိတ်ရန်" + "မရှိ" + "ကန့်သတ်ချက်မြှင့်ရန်" + "ကန့်သတ်ချက်များ အလတ်" + "ကန့်သတ်ချက်များ နိမ့်" + "စိတ်တိုင်းကျ" + "ကလေးများအတွက် သင့်တော်သည့် အကြောင်းအရာ" + "ကလေးကြီးများ အတွက် သင့်သော အကြောင်းအရာ" + "ဆယ်ကျော်သက်များ အတွက် ဆီလျော်သည့် အကြောင်းအရာ" + "လက်ဖြင့် သတ်မှတ်ခဲ့သည့် ကန့်သတ်ချက်များ" + + + "%1$s နှင့် ထပ်ဆင့်မှတ်" + "ထပ်ဆင့်-အမှတ်ပေးမှု" + "ဒီချာနယ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ" + "ဒီအစီအစဉ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ" + "ဤပရိုဂရမ်ကို %1$s အဆင့်သတ်မှတ်ထားပါသည်။ ဤပရိုဂရမ်ကို ကြည့်ရှုရန် သင့်ပင်နံပါတ်ကို ထည့်ပါ" + "ဤအစီအစဉ်ကို အဆင့်မသတ်မှတ်ရသေးပါ။ ဤအစီအစဉ်ကိုကြည့်ရန် သင်၏ ပင်နံပါတ် ထည့်ပါ။" + "သင့် PIN ရိုက်ထည့်ပါ" + "မိဘ ထိန်းချုပ်မှုကို သတ်မှတ်ရန်၊ PIN ကို ဖန်တီးပါ" + "PIN အသစ်ကို ထည့်သွင်းရန်" + "သင့် PIN ကို အတည်ပြုပါ" + "သင့် လက်ရှိ PIN ရိုက်ထည့်ပါ" + + မှားယွင်းသည့်ပင်နံပါတ် ၅ ကြိမ်သင်ထည့်သွင်းခဲ့သည်။\n%1$d စက္ကန့်အကြာတွင် ထပ်လုပ်ကြည့်ပါ။ + မှားယွင်းသည့်ပင်နံပါတ် ၅ ကြိမ်သင်ထည့်သွင်းခဲ့သည်။\n%1$d စက္ကန့်အကြာတွင် ထပ်လုပ်ကြည့်ပါ။ + + "ထို PIN မှာ မှားနေသည်။ ထပ်ကြိုးစားပါ။" + "PIN မှာ မတိုက်ဆိုင်ပါ၊ ထပ်ပြီး စမ်းပါ။" + "စာတိုက်ကုဒ်ကို ထည့်ပါ။" + "TV ချန်နယ်များအတွက် ပြီးပြည့်စုံသည့် အစီအစဉ်များကို ပံ့ပိုးပေးရန် Live TV က စာတိုက်ကုဒ်ကိုသုံးပါမည်။" + "စာတိုက်ကုဒ်ကို ထည့်ပါ" + "စာတိုက်သင်္က​ေတ မမှန်ပါ" + "ဆက်တင်များ" + "ချန်နယ်စာရင်းကို စိတ်တိုင်းကျပြုပြင်ရန်" + "သင့်ပရိုဂရမ်လမ်းညွှန်အတွက် ချန်နယ်များရွေးချယ်ပါ" + "ချန်နယ် အရင်းအမြစ်များ" + "ချန်နယ်အသစ်များ ရနိုင်ပါသည်" + "မိဘ ထိန်းချုပ်မှု" + "အချိန်ရွှေ့ဆိုင်းခြင်း" + "ကြည့်နေရင်း မှတ်တမ်းတင်ထားပါက တိုက်ရိုက်အစီအစဉ်များကို ခေတ္တရပ်နားနိုင်သလို ပြန်ရစ်ကြည့်နိုင်ပါသည်။\nသတိ− ဤသို့ပြုလုပ်လျှင် သိမ်းဆည်းမှုများစွာ အသုံးပြုခြင်းကြောင့် စက်အတွင်း သိမ်းဆည်းရန်နေရာ၏ သက်တမ်းကို လျော့ကျစေနိုင်ပါသည်။" + "အခမဲ့အရင်းအမြစ်လိုင်စင်များ" + "အကြံပြုချက် ပေးပို့ပါ" + "ဗားရှင်း" + "ဤချာနယ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ" + "ဤအစီအစဉ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ" + "ဤအစီအစဉ်ကို အဆင့်မသတ်မှတ်ရသေးပါ။\nဤအစီအစဉ်ကို ကြည့်ရန် ညာဘက်သို့ ဖိပြီး ပင်နံပါတ် ထည့်ပါ" + "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏။ \nဤအစီအစဉ်အား ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ။" + "ဤချန်နယ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့်တီဗွီအပ်ဖ်ကို အသုံးပြုပါ။" + "အစီအစဉ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့် တီဗွီအပ်ဖ်ကိုအသုံးပြုပါ။" + "ဤအစီအစဉ်ကို အဆင့်မသတ်မှတ်ရသေးပါ။\nဤအစီအစဉ်ကို ကြည့်ရန် မူလ တိုက်ရိုက်လွှင့်နေသော TV အက်ပ်ကို အသုံးပြုပါ။" + "ဤအစီအစဉ်ကို %1$s သတ်မှတ်ထားသည်။ \nဤအစီအစဉ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့် တီဗွီအပ်ဖ်ကိုအသုံးပြုပါ။" + "အစီအစဉ်ကို ပိတ်ဆို့ထား" + "ဤအစီအစဉ်ကို အဆင့်မသတ်မှတ်ရသေးပါ" + "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏" + "အသံ သီးသန့်" + "လှိုင်းဆွဲအားနည်းသည်" + "အင်တာနက်ချိတ်ဆက်မှု မရှိပါ" + + အခြားချန်နယ်လိုင်းများကို ဖမ်းယူနေသောကြောင့် ဤချန်နယ်လိုင်းကို %1$s အထိ ဖွင့်၍ရမည်မဟုတ်ပါ။ \n\nဖမ်းယူခြင်းအချိန်ဇယားကို ချိန်ညှိရန် ညာဘက်ကိုနှိပ်ပါ။ + အခြားချန်နယ်လိုင်းကို ဖမ်းယူနေသောကြောင့် ဤချန်နယ်လိုင်းကို %1$s အထိ ဖွင့်၍ရမည်မဟုတ်ပါ။ \n\nဖမ်းယူခြင်းအချိန်ဇယားကို ချိန်ညှိရန် ညာဘက်ကိုနှိပ်ပါ။ + + "ခေါင်းစဉ် မပါ" + "ချာနယ် ပိတ်ဆို့ထား" + "အသစ်" + "အရင်းအမြစ်များ" + + %1$d ချန်နယ်များ + %1$d ချန်နယ် + + "လိုင်းမရှိပါ" + "အသစ်" + "စဖွင့်မသတ်မှတ်ရသေးပါ" + "နောက်ထပ် အရင်းအမြစ်များ ရယူပါ" + "တိုက်ရိုက်လွှင့်ချန်နယ်များရှိသည့် အက်ပ်များကို ရှာပါ" + "ချန်နယ် အရင်းအမြစ် အသစ်များ ရှိပါသည်" + "ချန်နယ် အရင်းအမြစ် အသစ်များမှ ကမ်းလှမ်းလိုသည့် ချန်နယ်များ ရှိပါသည်။ \n၎င်းတို့ကို ယခု စဖွင့်သတ်မှတ်ပါ၊ သို့မဟုတ် နောက်ပိုင်းတွင် ချန်နယ် အရင်းအမြစ်များ ဆက်တင်ထဲတွင် သတ်မှတ်ပါ။" + "ယခု စဖွင့်သတ်မှတ်ပါ" + "အိုကေ၊ ရပါပြီ" + + + " ရွေးချယ်ပါအားနှိပ်ပြီး"" တီဗီမန်နယူးကိုဝင်ရောက်ကြည့်ရှုပါ။" + "တီဗီ ထည့်သွင်းမှု ရှာမတွေ့ပါ။" + "တီဗီ ထည့်သွင်းမှု ရှာမတွေ့နိုင်ပါ။" + "သင့်တော်သည့် တျူနာ အမျိုးအစား မဟုတ်ပါ။ တီဗွီ အဝင်ပေါက်အတွက် တိုက်ရိုက်လွှင့် ချန်နယ်များ အက်ပ်အား ဖွင့်ပါ။" + "ညှိမှု မအောင်မြင်ပါ" + "ဤလုပ်ဆောင်ချက်ကို ကိုင်တွယ်နိုင်သည့် အက်ပ်မရှိပါ။" + "ရင်းမြစ် ချာနယ် အားလုံးကို ဝှက်ထားသည်။ \n ကြည့်ရန် အနည်းဆုံး ချာနယ် တစ်ခုကို ရွေးပါ။" + "ဗီဒီယို မမျှော်လင့်ဘဲ မရရှိနိုင်ပါ။" + "BACK ကီးသည် ချိတ်ဆက် စက်ကိရိယာ အတွက်ဖြစ်၏။ ထွက်ရန် HOME ခလုတ်ကို နှိပ်ပါ။" + "တီဗီစာရင်းများကို ဖတ်ရှုရန် တိုက်ရိုက်ထုတ်လွှင့်သောချန်နယ်လိုင်းများတွင် ခွင့်ပြူချက်လိုအပ်သည်။" + "သင့်သတင်းအရင်းအမြစ်များကို တပ်ဆင်ပါ" + "တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များသည် သမားရိုးကျ TV ချန်နယ်များနှင့် အက်ပ်မှလွှင့်ပေးသည့် ချန်နယ်များကို ပူးပေါင်းထားသည့်ခံစားမှုပင်ဖြစ်သည်။ \n\nထည့်သွင်းပြီးသား ချန်နယ်အရင်းမြစ်များကို တပ်ဆင်ခြင်းဖြင့် စတင်လိုက်ပါ။ သို့မဟုတ် တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များပါဝင်သည့် နောက်ထပ်အက်ပ်များအတွက် Google Play Store တွင်ရှာပါ။" + "ဖမ်းယူခြင်းနှင့် အချိန်ဇယားမျာ" + "၁၀ မိနစ်" + "၃၀ မိနစ်" + "၁ နာရီ" + "၃ နာရီ" + "မကြာမီက" + "စီစဉ်ထားသော" + "စီးရီး" + "အခြား" + "ချန်နယ်ကို မှတ်တမ်းတင်၍မရပါ။" + "ပရိုဂရမ်ကို မှတ်တမ်းတင်၍ မရပါ။" + "%1$s ကို ဖမ်းယူရန် စီစဉ်ထားပါသည်" + "%1$s ကို ယခုချိန်မှ %2$s အထိ ရိုက်ကူးမည်" + "အချိန်ဇယားအပြည့်အစုံ" + + နောက်ထပ် %1$d ရက် + နောက်ထပ် %1$d ရက် + + + %1$d မိနစ် + %1$d မိနစ် + + + ရိုက်ကူးမှုအသစ် %1$d ခု + ရိုက်ကူးမှုအသစ် %1$d ခု + + + ရိုက်ကူးမှု %1$d ခု + ရိုက်ကူးမှု %1$d ခု + + + ရိုက်ကူးရန် အစီအစဉ်%1$dခု + ရိုက်ကူးရန် အစီအစဉ်%1$dခု + + "ကူးယူမှု ပယ်ဖျက်မည်" + "ကူးယူမှု ရပ်မည်" + "ကြည့်ရန်" + "အစမှနေ၍ ဖွင့်ရန်" + "ဆက်ဖွင့်ရန်" + "ဖျက်ရန်" + "မှတ်တမ်းတင်ထားသည်ကို ဖျက်ရန်" + "ဆက်ဖွင့်ရန်" + "အတွဲ %1$s" + "အချိန်ဇယားကိုကြည့်ရန်" + "နောက်ထပ် ဖတ်ရန်" + "ရုပ်သံကူးယူမှုဖျက်ပါ" + "သင်ဖျက်ပစ်လိုသော အခန်းဆက်များကို ရွေးပါ။ ဖျက်လိုက်သည်နှင့် ၎င်းတို့ကို ပြန်ဆည်ယူ၍ မရတော့ပါ။" + "ဖျက်ရန် ရုပ်သံဖမ်းယူမှုများ မရှိတော့ပါ။" + "ကြည့်ပြီးသောအခန်းဆက်များရွေးပါ" + "အခန်းဆက်များအားလုံးကို ရွေးပါ" + "အခန်းဆက်များအားလုံးကို ဖျက်ပါ" + "%2$d မိနစ်ရှိသည့်အနက် %1$d မိနစ် ကြည့်ပြီးသွားပြီ" + "%2$d စက္ကန့်ရှိသည့်အနက် %1$d စက္ကန့် ကြည့်ပြီးသွားပြီ" + "လုံးဝမကြည့်ရသေးပါ" + + အခန်းဆက် %2$d ခုအနက် %1$d ခုကို ဖျက်လိုက်သည် + အခန်းဆက် %2$d ခုအနက် %1$d ခုကို ဖျက်လိုက်သည် + + "ဦးစားပေးမှု" + "အမြင့်ဆုံး" + "အနိမ့်ဆုံး" + "နံပါတ် %1$d" + "ချန်နယ်များ" + "မည်သည့်အရာမဆို" + "ဦးစားပေးမှုကို ရွေးရန်" + "တစ်ချိန်တည်းတွင် ဖမ်းယူရမည့်အစီအစဉ် များပြားလွန်းသည့်အခါ ဦးစားပေးမှု ပိုမိုမြင့်မားသည့် အစီအစဉ်များကိုသာ ဖမ်းယူသွားပါမည်။" + "သိမ်းရန်" + "တစ်ကြိမ်တစ်ခါတည်း ဖမ်းယူခြင်းသည် ဦးစားပေးမှုအမြင့်ဆုံးဖြစ်သည်" + "ရပ်ရန်" + "ဖမ်းယူခြင်းအချိန်ဇယားကို ကြည့်ရန်" + "ဤအစီအစဉ် တစ်ခုတည်း" + "ယခု - %1$s" + "အခန်းဆက်များ တစ်တွဲလုံး…" + "မည်သို့ပင်ဖြစ်စေ စီစဉ်ရန်" + "၎င်းအစား ဤတစ်ခုကို ဖမ်းယူပါ" + "ဤဖမ်းယူမှုကို ပယ်ဖျက်ရန်" + "ယခုကြည့်ရန်" + "ရုပ်သံရိုက်ကူးမှုများ ဖျက်ပါ..." + "ရိုက်ကူးနိုင်သည်" + "ရိုက်ကူးရေးအတွက် စီစဉ်ထားပါသည်" + "ရိုက်ကူးရေးအစီအစဉ်တိုက်နေပါသည်" + "ဖမ်းယူနေသည်" + "ဖမ်းယူခြင်း မအောင်မြင်ပါ" + "ပရိုဂရမ်များကို ဖတ်နေသည်" + "မကြာသေးမီက ရိုက်ကူးမှုများကို ကြည့်ပါ" + "%1$s ကို ကူးယူမှု မပြီးဆုံးခဲ့ပါ။" + "%1$s နှင့် %2$s တို့ကို ကူးယူမှု မပြီးဆုံးခဲ့ပါ။" + "%1$s%2$s နှင့် %3$s တို့ကို ကူးယူမှု မပြီးဆုံးခဲ့ပါ။" + "သိုလှောင်ခန်း လုံလောက်မှု မရှိသည့်အတွက် %1$s ကို ပြီးဆုံးအောင် မကူးယူနိုင်ခဲ့ပါ။" + "သိုလှောင်ခန်း လုံလောက်မှု မရှိသည့်အတွက် %1$s နှင့် %2$s တို့ကို ပြီးစီးအောင် မကူးယူနိုင်ခဲ့ပါ။" + "သိုလှောင်ခန်း လုံလောက်မှု မရှိသည့်အတွက် %1$s%2$s နှင့် %3$s တို့ကို ပြီးစီးအောင် မကူးယူနိုင်ခဲ့ပါ။" + "DVR သည် နောက်ထပ်သိုလှောင်ရန်နေရာလွတ် လိုအပ်နေသည်" + "အစီအစဉ်များကို DVR နှင့် ဖမ်းယူသိမ်းဆည်းထားနိုင်ပါသည်။ သို့သော် DVR ကို အသုံးပြုနိုင်ရန် သင့်စက်ပစ္စည်းတွင် လုံလောက်သော နေရာလွတ် လောလောဆယ်မရှိပါ။ %1$dဂစ်ဂါဘိုက် သို့မဟုတ် ၎င်းနှင့်အထက်ရှိသော ပြင်ပသိုလှောင်ကိရိယာနှင့် ချိတ်ဆက်ပြီး ၎င်းကို သိုလှောင်ခန်းစက်ပစ္စည်းအဖြစ် ပြင်ဆင်သတ်မှတ်ရန် ညွှန်ကြားချက်များအတိုင်း လိုက်နာပါ။" + "သိုလှောင်ခန်း မလုံလောက်ပါ" + "သိုလှောင်ခန်း လုံလောက်မှုမရှိသည့်အတွက် ဤအစီအစဉ်ကို ရိုက်ကူးမည်မဟုတ်ပါ။ လက်ရှိရိုက်ကူးချက်အချို့ကို ဖျက်ကြည့်ပါ။" + "သိုလှောင်မှုများ ပျောက်ဆုံးနေခြင်း" + "ရိုက်ကူးခြင်းကို ရပ်လိုပါသလား။" + "ဖမ်းယူထားသည့် အကြောင်းအရာကို သိမ်းဆည်းထားပါမည်။" + "%1$s ကို ဖမ်းယူခြင်းသည် ဤပရိုဂရမ်နှင့် ပဋိပက္ခများ ဖြစ်နေသောကြောင့် ၎င်းသည် ရပ်တန့်သွားပါမည်။ ဖမ်းယူထားသည့် အကြောင်းအရာကို သိမ်းဆည်းသွားပါမည်။" + "ဖမ်းယူရန် စီစဉ်ထားသော်လည်း အချိန်ဇယားတိုက်နေပါသည်" + "ဖမ်းယူမှုကို စတင်လိုက်ပါပြီ။ သို့သော် အချိန်ဇယားတိုက်နေပါသည်" + "%1$s ကို ဖမ်းယူသွားပါမည်။" + "%1$s ကို ဖမ်းယူနေပါသည်။" + "%1$s ၏ အချို့အစိတ်အပိုင်းများကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" + "%1$s နှင့် %2$s တို့၏ အချို့အစိတ်အပိုင်းများကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" + "%1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် တစ်ခုကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" + + %1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် %3$d ခုတို့ကို ဖမ်းယူသွားမည် မဟုတ်ပါ။ + %1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် %3$d ခုတို့ကို ဖမ်းယူသွားမည် မဟုတ်ပါ။ + + "သင်ဘယ်ရုပ်/သံကို ဖမ်းယူလိုပါသလဲ။" + "ဘယ်လောက်ကြာကြာ ဖမ်းယူလိုပါသနည်း။" + "စီစဉ်ပြီးပါပြီ" + "တူညီသည့် ပရိုဂရမ်ကို %1$s ၌ ဖမ်းယူရန် စီစဉ်ထားပြီး ဖြစ်ပါသည်။" + "ဖမ်းယူပြီးပါပြီ" + "ဤပရိုဂရမ်ကို ဖမ်းယူပြီးပါပြီ။ ၎င်းကို DVR စာကြည့်တိုက်တွင် ကြည့်ရှုနိုင်ပါသည်။" + "စီးရီးများကို ဖမ်းယူခြင်းကို စီစဉ်ထားပါသည်" + + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ + + + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် ၎င်းတို့အနက်မှ %3$d ခုကို ကူးယူသွားမည် မဟုတ်ပါ။ + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် ၎င်းကို ကူးယူသွားမည် မဟုတ်ပါ။ + + + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် ဤစီးရီးနှင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း %3$d ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် ဤစီးရီးနှင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း %3$d ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + + + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း ၁ ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း ၁ ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + + + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း %3$d ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + ရိုက်ကူးမှု %1$d ခုကို %2$s အတွက် စီစဉ်ထားပါသည်။ ပဋိပက္ခများ ရှိနေသဖြင့် အခြားစီးရီးများ၏ ဇာတ်လမ်းပိုင်း %3$d ပိုင်းကို ကူးယူသွားမည်မဟုတ်ပါ။ + + "ရိုက်ကူးထားသည့်ပရိုဂရမ်ကို မတွေ့ပါ။" + "ဆက်စပ်နေသည့် ရိုက်ကူးမှုများ" + + ရိုက်ပြီးသား %1$d ခု + ရိုက်ပြီးသား %1$d ခု + + " / " + "%1$s ကို ဖမ်းယူခြင်းအစီအစဉ်မှ ဖယ်ရှားလိုက်ပါပြီ" + "ချန်နယ်ချိန်ကိရိယာလိုင်းပူးနေ၍ တစ်ချို့ကိုသာ ဖမ်းယူနိုင်မည်။" + "ချန်နယ်ချိန်ကိရိယာ လိုင်းပူးနေ၍ ဖမ်းယူနိုင်မည်မဟုတ်ပါ။" + "စီစဉ်ထားသည့် ဖမ်းယူမှုများ မရှိသေးပါ။\nဖမ်းယူမှုကို ပရိုဂရမ်လမ်းညွှန်အား ကြည့်ရှု၍ စီစဉ်နိုင်ပါသည်။" + + ရိုက်ကူးမှု %1$d ခု ပဋိပက္ခဖြစ်နေပါသည် + ရိုက်ကူးမှု %1$d ခု ပဋိပက္ခဖြစ်နေပါသည် + + "ဇာတ်လမ်းတွဲဆက်တင်များ" + "ဇာတ်လမ်းတွဲ စတင်ဖမ်းယူရန်" + "ဇာတ်လမ်းတွဲဖမ်းယူခြင်း ရပ်ရန်" + "ဇာတ်လမ်းတွဲဖမ်းယူခြင်းကို ရပ်မလား။" + "ဖမ်းယူထားသည့် အပိုင်းများသည် DVR စာကြည့်တိုက်တွင် ရှိနေဦးမည်ဖြစ်သည်။" + "ရပ်ရန်" + "ယခု မည်သည့် ဇာတ်လမ်းပိုင်းကိုမျှ လွှင့်နေခြင်း မရှိပါ။" + "အပိုင်းငယ်များ မရနိုင်သေးပါ။\nရနိုင်သည်နှင့် ၎င်းတို့ကို ဖမ်းယူသွားပါမည်။" + + (%1$d မိနစ်) + (%1$d မိနစ်) + + "ဒီနေ့" + "မနက်ဖြန်" + "မနေ့က" + "ဒီနေ့ %1$s" + "မနက်ဖြန် %1$s" + "ရမှတ်" + "ကူးယူထားသော အစီအစဉ်များ" + diff --git a/res/values-my-v23/strings.xml b/res/values-my-v23/strings.xml deleted file mode 100644 index 015247d8..00000000 --- a/res/values-my-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "ချာနယ်များ" - diff --git a/res/values-my/arrays.xml b/res/values-my/arrays.xml deleted file mode 100644 index e302ceac..00000000 --- a/res/values-my/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "ပုံမှန်" - "အပြည့်" - "ဇူးမ်" - - - "ချာနယ်များ အားလုံး" - "မိသားစု/ကလေးများ" - "အားကစား" - "​ဈေးဝယ်ခြင်း" - "ရုပ်ရှင်များ" - "ဟာသ" - "ခရီးလှည့်မှု" - "ပြဇာတ်" - "ပညာရေး" - "တိရိစ္ဆာန်/တောရိုင်း" - "သတင်း" - "ဂိမ်းကစားခြင်း" - "အနုပညာများ" - "ဖြေဖျော်မှု" - "ဘဝပုံစံ" - "ဂီတ" - "ပွဲဦး" - "စက်မှု/သိပ္ပံ" - - - "တိုက်ရိုက်လွှင့် ချန်နယ်များ" - "အကြောင်းအရာများကို စူးစမ်းရှာဖွေရန် ရိုးရှင်းသည့်နည်းလမ်းတစ်ခု" - "အက်ပ်ကိုဒေါင်းလုဒ်လုပ်ပြီး၊ နောက်ထပ်ချန်နယ်များ ရယူလိုက်ပါ" - "သင့်ချန်နယ် ထားသိုမှုကိုစိတ်ကြိုက်ပြုပြင်ပါ" - - - "TV ချန်နယ်များကြည့်သလို သင့်အက်ပ်များအတွင်း အကြောင်းအရာများကို ကြည့်ပါ။" - \n"TV ချန်နယ်များကဲ့သို့ပင်၊ အကျွမ်းတဝင်ရှိသည့်လမ်းညွှန်နှင့် ရင်းနှီးမှုရှိသည့် အင်တာဖေ့စ်ဖြင့် အက်ပ်များမှ အကြောင်းအရာများကိုကြည့်ပါ။" - "တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များပါဝင်သည့် အက်ပ်များကို ထည့်သွင်းခြင်းအားဖြင့် နောက်ထပ်ချန်နယ်များ ထည့်ပါ။ \nTV မန်နယူးရှိလင့်ခ်ကိုအသုံးပြု၍ Google Play Store အတွင်း ကိုက်ညီမှုရှိသည့်အက်ပ်များကို ရှာပါ။" - "သင့်ချန်နယ်စာရင်းကို စိတ်ကြိုက်ပြုပြင်ရန် အသစ်ထည့်သွင်းထားသည့် ချန်နယ်အရင်းမြစ်များကို တပ်ဆင်ပါ။ \nစတင်ရန် ဆက်တင်များ မန်နယူးတွင် ချန်နယ်အရင်းမြစ်များကို ရွေးပါ။" - - diff --git a/res/values-my/rating_system_strings.xml b/res/values-my/rating_system_strings.xml deleted file mode 100644 index 153ad407..00000000 --- a/res/values-my/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "အစီအစဉ်များတွင် အသက် ၁၅ နှစ်အောက် ပရိသတ်များအတွက် မသင့်တော်သည့် အကြောင်းအရာများ ပါဝင်နိုင်သောကြောင့် ၎င်းတို့ကို မိဘအုပ်ထိန်းမှုအောက်တွင် ထားရှိသင့်ပါသည်။" - "အစီအစဉ်များတွင် အသက် ၁၉ နှစ်အောက်ပရိသတ်များအတွက် မသင့်တော်သည့် အကြောင်းအရာများ ပါဝင်နိုင်သောကြောင့် အသက် ၁၉ နှစ်နှင့်အောက် လူငယ်များနှင့် ကိုက်ညီမှုမရှိပါ။" - "အကြံပြုထားသည့် စကားပြောဆိုမှု" - "ကြမ်းတမ်းသော စကားလုံးများ" - "လိင်ဆက်ဆံမှု အကြောင်းအရာ" - "အကြမ်းဖက်မှု" - "ဆန်းပြားသော အကြမ်းဖက်မှု" - "ဒီပရိုဂရမ်ကို ကလေးများ အားလုံး အတွက် သင့်လျော်အောင် စီမံပြုစုခဲ့သည်။" - "ဒီပရိုဂရမ်ကို အသက် ၇ နှစ်နှင့် အထက်ရှိ ကလေးများ အတွက် စီမံပြုစုခဲ့သည်။" - "မိသားစု အများစုက ဒီပရိုဂရမ်မှာ အသက်အရွယ် အားလုံး အတွက် ဆီလျော်သည်ဟု ယူဆကြမည်။" - "ဒီ program ဒီပရိုဂရမ်ထဲမှာ ပါဝင်သည့် အရာများကို မိဘများက အသက်ငယ် ကလေးများ အတွက် မသင့်လျော်ဟု ယူဆနိုင်သည်။ မိဘအများက ၎င်းကို သူတို့ရဲ့ ကလေးငယ်များနှင့် ကြည့်လိုနိုင်ကြတယ်။" - "ဒီပရိုဂရမ်ထဲမှာ ပါဝင်ကြသည့် အချို့ အရာများမှာ အသက် ၁၄ နှစ်အောက် ကလေးများ အတွက် မသင့်လျော်ဟု မိဘ အများက ယူဆဖွယ် ရှိပါသည်။" - "ဒီပရိုဂရမ်ကို သက်ကြီးများ ကြည့်ရှုရန် အထူး စီမံပြုစုခဲ့သည်၊ သို့ဖြစ်၍ ၁၇ အောက် ကလေးများ အတွက် သင့်လျော်နိုင်မည် မဟုတ်ပါ။" - "ရုပ်ရှင် အဆင့်သတ်မှတ်ချက်များ" - "သာမန် ပရိသတ်တွေ။ ကလေးတွေက ကြည့်ကြလို့ မိဘများအား မကျေမနပ် ဖြစ်စရာ ဘာမှ မပါပါ။" - "မိဘတွေရဲ့ လမ်းညွှန်မှု လိုအပ်တယ်။ မိဘတွေက ကလေးတွေ အတွက် မကြိုက်ကြမယ့် အကြောင်းအရာ အချို့ ပါရှိနိုင်တယ်။" - "မိဘတွေ အပြင်းအထန် စိုးရိမ်နေကြတယ်။ အချို့ အကြောင်းအရာမှာ ဆယ်ကျော်သက်ထက် ငယ်သူများအတွက် သင့်လျော်ချင်မှ သင်လျော်မယ်။" - "ကန့်သတ်ချက်ရှိ၊ အရွယ်ရောက်သူများ အတွက် အချို့အရာများ ပါရှိတယ်။ မိဘများအား ကလေးငယ်များကို သူတို့နှင့်အတူ ခေါ်မသွားမီ ရုပ်ရှင် အကြောင်းကို နောက်ထပ် လေ့လာရန် အကြံပြုပါတယ်။" - "အသက် ၁၇ နှစ်နှင့် အောက်ရှိသူ ဘယ်သူကိုမှ လက်မခံပါ။ အရွယ်ရောက်ပြီးသား ဖြစ်ရမှာ ရှင်းနေပါတယ်။ ကလေးများကို လက်မခံပါ။" - diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml deleted file mode 100644 index 1ab6baf7..00000000 --- a/res/values-my/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "မိုနို" - "စတီရီယို" - "Play ထိန်းချုပ်မှုများ" - "မကြာမီက ချာနယ်" - "TV ရွေးစရာများ" - "PIP ရွေးချယ်စရာများ" - "ဤလိုင်းအတွက် အဖွင့်ထိန်းချုပ်ခြင်းများ မရနိုင်ပါ" - "ဖွင့်ပါ သို့မဟုတ် ခဏရပ်ပါ" - "ရှေ့သို့ အမြန်သွားရန်" - "ပြန်ရစ်ရန်" - "ရှေ့သို့" - "ယခင်" - "အစီအစဉ် လမ်းညွှန်" - "ရရှိနိုင်သည့် ချန်နယ်အသစ်များ" - "%1$s ဖွင့်ရန်" - "စာတမ်းထိုးများ" - "မြင်ကွင်း မုဒ်" - "PIP" - "ဖွင့်ထား" - "ပိတ်ထား" - "အသံစုံ" - "နောက်ထပ်ချန်နယ်များ ရယူရန်" - "ဆက်တင်များ" - "ရင်းမြစ်" - "လဲပြောင်းသည်" - "ဖွင့်ထား" - "ပိတ်ထား" - "အသံ" - "အဓိက" - "PIP ဝင်းဒိုး" - "အဆင်အပြင်" - "အောက်ညာ" - "အပေါ်ညာ" - "အပေါ်ဘယ်" - "အောက်ဘယ်" - "ကပ်လျက်" - "ဆိုက်" - "ကြီး" - "သေး" - "ထည့်သွင်းမှု ရင်းမြစ်" - "တီဗီ (ဧရီယာတိုင်/ကြိုး)" - "အစီအစဉ် အချက်အလက်များ မရှိ" - "သတင်းအချက်အလက် မရှိပါ" - "ပိတ်ဆို့ ချာနယ်" - "အမည်မသိဘာသာစကား" - "စာတမ်းထိုးများ" - "ပိတ်ထား" - "စိတ်ကြိုက်ပုံစံချရန်" - "စာတမ်းထိုးအတွက် ရွေးစရာများ သတ်မှတ်ရန်" - "မြင်ကွင်း မုဒ်" - "အသံစုံ" - "မိုနို" - "စတီရီယို" - "၅.၁ ပတ်လည်" - "၇.၁ ပတ်လည်" - "%d ချာနယ်" - "ချာနယ်စာရင်း စိတ်တိုင်းကျ" - "အုပ်စု ရွေးရန်" - "အုပ်စု ရွေးမှု ဖျက်ရန်" - "အုပ်စုဖွဲ့မှု" - "ချာနယ် ရင်းမြစ်များ" - "HD/SD" - "HD" - "SD" - "အုပ်စုဖွဲ့မှု" - "ဤအစီအစဉ်အား ပိတ်ဆို့ထားသည်။" - "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏" - "ထည့်သွင်းမှုက အော်တို-စကင် ပံ့ပိုးမပေး" - "\'%s\' အတွက် အော်တို−စကင် မစနိုင်ပါ" - "ပိတ်ထားသည့် စာတန်းများ အတွက် စနစ်ဆိုင်ရာ ဦးစားပေးမှုများကို စတင်မရပါ။" - - ချန်နယ် %1$d ခုပေါင်းထည့်ပြီးပါပြီ - ချန်နယ် %1$d ခုပေါင်းထည့်ပြီးပါပြီ - - "ချာနယ်များ ထည့်မထား" - "တျူနား" - "မိဘ ထိန်းချုပ်မှု" - "ဖွင့်ရန်" - "ပိတ်ရန်" - "ချာနယ်များ ပိတ်ရန်" - "အားလုံး ပိတ်ဆို့ရန်" - "အားလုံး ပြန်ဖွင့်ရန်" - "ဝှက်ထားသည့် ချာနယ်များ" - "အစီအစဉ်ကန့်သတ်ချက်" - "PIN ပြောင်းရန်" - "အဆင့်ပေး စနစ်များ" - "အမှတ်ပေးမှုများ" - "အဆင့်ပေးစနစ်များပြပါ" - "အခြားနိုင်ငံများ" - "မရှိ" - "မရှိ" - "မရှိ" - "ကန့်သတ်ချက်မြှင့်ရန်" - "ကန့်သတ်ချက်များ အလတ်" - "ကန့်သတ်ချက်များ နိမ့်" - "စိတ်တိုင်းကျ" - "ကလေးများအတွက် သင့်တော်သည့် အကြောင်းအရာ" - "ကလေးကြီးများ အတွက် သင့်သော အကြောင်းအရာ" - "ဆယ်ကျော်သက်များ အတွက် ဆီလျော်သည့် အကြောင်းအရာ" - "လက်ဖြင့် သတ်မှတ်ခဲ့သည့် ကန့်သတ်ချက်များ" - - - "%1$s နှင့် ထပ်ဆင့်မှတ်" - "ထပ်ဆင့်-အမှတ်ပေးမှု" - "ဒီချာနယ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ" - "ဒီအစီအစဉ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ" - "ဤပရိုဂရမ်ကို %1$s အဆင့်သတ်မှတ်ထားပါသည်။ ဤပရိုဂရမ်ကို ကြည့်ရှုရန် သင့်ပင်နံပါတ်ကို ထည့်ပါ" - "သင့် PIN ရိုက်ထည့်ပါ" - "မိဘ ထိန်းချုပ်မှုကို သတ်မှတ်ရန်၊ PIN ကို ဖန်တီးပါ" - "PIN အသစ်ကို ထည့်သွင်းရန်" - "သင့် PIN ကို အတည်ပြုပါ" - "သင့် လက်ရှိ PIN ရိုက်ထည့်ပါ" - - မှားယွင်းသည့်ပင်နံပါတ် ၅ ကြိမ်သင်ထည့်သွင်းခဲ့သည်။\n%1$d စက္ကန့်အကြာတွင် ထပ်လုပ်ကြည့်ပါ။ - မှားယွင်းသည့်ပင်နံပါတ် ၅ ကြိမ်သင်ထည့်သွင်းခဲ့သည်။\n%1$d စက္ကန့်အကြာတွင် ထပ်လုပ်ကြည့်ပါ။ - - "ထို PIN မှာ မှားနေသည်။ ထပ်ကြိုးစားပါ။" - "PIN မှာ မတိုက်ဆိုင်ပါ၊ ထပ်ပြီး စမ်းပါ။" - "ဆက်တင်များ" - "ချန်နယ်စာရင်းကို စိတ်တိုင်းကျပြုပြင်ရန်" - "သင့်ပရိုဂရမ်လမ်းညွှန်အတွက် ချန်နယ်များရွေးချယ်ပါ" - "ချန်နယ် အရင်းအမြစ်များ" - "ချန်နယ်အသစ်များ ရနိုင်ပါသည်" - "မိဘ ထိန်းချုပ်မှု" - "အခမဲ့အရင်းအမြစ်လိုင်စင်များ" - "အခမဲ့ ရင်းမြစ် လိုင်စင်များ" - "ဗားရှင်း" - "ဤချာနယ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ" - "ဤအစီအစဉ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ" - "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏။ \nဤအစီအစဉ်အား ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ။" - "ဤချန်နယ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့်တီဗွီအပ်ဖ်ကို အသုံးပြုပါ။" - "အစီအစဉ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့် တီဗွီအပ်ဖ်ကိုအသုံးပြုပါ။" - "ဤအစီအစဉ်ကို %1$s သတ်မှတ်ထားသည်။ \nဤအစီအစဉ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့် တီဗွီအပ်ဖ်ကိုအသုံးပြုပါ။" - "အစီအစဉ်ကို ပိတ်ဆို့ထား" - "ဤအစီအစဉ်သည် အဆင့် %1$s ရှိ၏" - "အသံ သီးသန့်" - "လှိုင်းဆွဲအားနည်းသည်" - "အင်တာနက်ချိတ်ဆက်မှု မရှိပါ" - - အခြားချန်နယ်လိုင်းများကို ဖမ်းယူနေသောကြောင့် ဤချန်နယ်လိုင်းကို %1$s အထိ ဖွင့်၍ရမည်မဟုတ်ပါ။ \n\nဖမ်းယူခြင်းအချိန်ဇယားကို ချိန်ညှိရန် ညာဘက်ကိုနှိပ်ပါ။ - အခြားချန်နယ်လိုင်းကို ဖမ်းယူနေသောကြောင့် ဤချန်နယ်လိုင်းကို %1$s အထိ ဖွင့်၍ရမည်မဟုတ်ပါ။ \n\nဖမ်းယူခြင်းအချိန်ဇယားကို ချိန်ညှိရန် ညာဘက်ကိုနှိပ်ပါ။ - - "ခေါင်းစဉ် မပါ" - "ချာနယ် ပိတ်ဆို့ထား" - "အသစ်" - "အရင်းအမြစ်များ" - - %1$d ချန်နယ်များ - %1$d ချန်နယ် - - "လိုင်းမရှိပါ" - "အသစ်" - "စဖွင့်မသတ်မှတ်ရသေးပါ" - "နောက်ထပ် အရင်းအမြစ်များ ရယူပါ" - "တိုက်ရိုက်လွှင့်ချန်နယ်များရှိသည့် အက်ပ်များကို ရှာပါ" - "ချန်နယ် အရင်းအမြစ် အသစ်များ ရှိပါသည်" - "ချန်နယ် အရင်းအမြစ် အသစ်များမှ ကမ်းလှမ်းလိုသည့် ချန်နယ်များ ရှိပါသည်။ \n၎င်းတို့ကို ယခု စဖွင့်သတ်မှတ်ပါ၊ သို့မဟုတ် နောက်ပိုင်းတွင် ချန်နယ် အရင်းအမြစ်များ ဆက်တင်ထဲတွင် သတ်မှတ်ပါ။" - "ယခု စဖွင့်သတ်မှတ်ပါ" - "အိုကေ၊ ရပါပြီ" - - - " ရွေးချယ်ပါအားနှိပ်ပြီး"" တီဗီမန်နယူးကိုဝင်ရောက်ကြည့်ရှုပါ။" - "တီဗီ ထည့်သွင်းမှု ရှာမတွေ့ပါ။" - "တီဗီ ထည့်သွင်းမှု ရှာမတွေ့နိုင်ပါ။" - "PIP ကို ပံ့ပိုးမထားပါ။" - "PIP နှင့် ပြနိုင်သည့် ထည့်သွင်းစရာ မရှိပါ။" - "သင့်တော်သည့် တျူနာ အမျိုးအစား မဟုတ်ပါ။ တီဗွီ အဝင်ပေါက်အတွက် တိုက်ရိုက်လွှင့် ချန်နယ်များ အက်ပ်အား ဖွင့်ပါ။" - "ညှိမှု မအောင်မြင်ပါ" - "ဤလုပ်ဆောင်ချက်ကို ကိုင်တွယ်နိုင်သည့် အက်ပ်မရှိပါ။" - "ရင်းမြစ် ချာနယ် အားလုံးကို ဝှက်ထားသည်။ \n ကြည့်ရန် အနည်းဆုံး ချာနယ် တစ်ခုကို ရွေးပါ။" - "ဗီဒီယို မမျှော်လင့်ဘဲ မရရှိနိုင်ပါ။" - "BACK ကီးသည် ချိတ်ဆက် စက်ကိရိယာ အတွက်ဖြစ်၏။ ထွက်ရန် HOME ခလုတ်ကို နှိပ်ပါ။" - "တီဗီစာရင်းများကို ဖတ်ရှုရန် တိုက်ရိုက်ထုတ်လွှင့်သောချန်နယ်လိုင်းများတွင် ခွင့်ပြူချက်လိုအပ်သည်။" - "သင့်သတင်းအရင်းအမြစ်များကို တပ်ဆင်ပါ" - "တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များသည် သမားရိုးကျ TV ချန်နယ်များနှင့် အက်ပ်မှလွှင့်ပေးသည့် ချန်နယ်များကို ပူးပေါင်းထားသည့်ခံစားမှုပင်ဖြစ်သည်။ \n\nထည့်သွင်းပြီးသား ချန်နယ်အရင်းမြစ်များကို တပ်ဆင်ခြင်းဖြင့် စတင်လိုက်ပါ။ သို့မဟုတ် တိုက်ရိုက်လွှင့်သည့် ချန်နယ်များပါဝင်သည့် နောက်ထပ်အက်ပ်များအတွက် Google Play Store တွင်ရှာပါ။" - "ဖမ်းယူခြင်းနှင့် အချိန်ဇယားမျာ" - "၁၀ မိနစ်" - "၃၀ မိနစ်" - "၁ နာရီ" - "၃ နာရီ" - "မကြာမီက" - "စီစဉ်ထားသော" - "စီးရီး" - "အခြား" - "ချန်နယ်ကို မှတ်တမ်းတင်၍မရပါ။" - "ပရိုဂရမ်ကို မှတ်တမ်းတင်၍ မရပါ။" - "%1$s ကို ဖမ်းယူရန် စီစဉ်ထားပါသည်" - "%1$s ကို ယခုချိန်မှ %2$s အထိ ရိုက်ကူးမည်" - "အချိန်ဇယားအပြည့်အစုံ" - - နောက်ထပ် %1$d ရက် - နောက်ထပ် %1$d ရက် - - - %1$d မိနစ် - %1$d မိနစ် - - - ရိုက်ကူးမှုအသစ် %1$d ခု - ရိုက်ကူးမှုအသစ် %1$d ခု - - - ရိုက်ကူးမှု %1$d ခု - ရိုက်ကူးမှု %1$d ခု - - - ရိုက်ကူးရန် အစီအစဉ်%1$dခု - ရိုက်ကူးရန် အစီအစဉ်%1$dခု - - "ကြည့်ရန်" - "အစမှနေ၍ ဖွင့်ရန်" - "ဆက်ဖွင့်ရန်" - "ဖျက်ရန်" - "မှတ်တမ်းတင်ထားသည်ကို ဖျက်ရန်" - "ဆက်ဖွင့်ရန်" - "အတွဲ %1$s" - "အချိန်ဇယားကိုကြည့်ရန်" - "နောက်ထပ် ဖတ်ရန်" - "ရုပ်သံကူးယူမှုဖျက်ပါ" - "သင်ဖျက်ပစ်လိုသော အခန်းဆက်များကို ရွေးပါ။ ဖျက်လိုက်သည်နှင့် ၎င်းတို့ကို ပြန်ဆည်ယူ၍ မရတော့ပါ။" - "ဖျက်ရန် ရုပ်သံဖမ်းယူမှုများ မရှိတော့ပါ။" - "ကြည့်ပြီးသောအခန်းဆက်များရွေးပါ" - "အခန်းဆက်များအားလုံးကို ရွေးပါ" - "အခန်းဆက်များအားလုံးကို ဖျက်ပါ" - "%2$d မိနစ်ရှိသည့်အနက် %1$d မိနစ် ကြည့်ပြီးသွားပြီ" - "%2$d စက္ကန့်ရှိသည့်အနက် %1$d စက္ကန့် ကြည့်ပြီးသွားပြီ" - "လုံးဝမကြည့်ရသေးပါ" - - အခန်းဆက် %2$d ခုအနက် %1$d ခုကို ဖျက်လိုက်သည် - အခန်းဆက် %2$d ခုအနက် %1$d ခုကို ဖျက်လိုက်သည် - - "ဦးစားပေးမှု" - "အမြင့်ဆုံး" - "အနိမ့်ဆုံး" - "နံပါတ် %1$d" - "ချန်နယ်များ" - "မည်သည့်အရာမဆို" - "ဦးစားပေးမှုကို ရွေးရန်" - "တစ်ချိန်တည်းတွင် ဖမ်းယူရမည့်အစီအစဉ် များပြားလွန်းသည့်အခါ ဦးစားပေးမှု ပိုမိုမြင့်မားသည့် အစီအစဉ်များကိုသာ ဖမ်းယူသွားပါမည်။" - "သိမ်းရန်" - "တစ်ကြိမ်တစ်ခါတည်း ဖမ်းယူခြင်းသည် ဦးစားပေးမှုအမြင့်ဆုံးဖြစ်သည်" - "မလုပ်တော့" - "မလုပ်တော့" - "မေ့ပစ်ရန်" - "ရပ်ရန်" - "ဖမ်းယူခြင်းအချိန်ဇယားကို ကြည့်ရန်" - "ဤအစီအစဉ် တစ်ခုတည်း" - "ယခု - %1$s" - "အခန်းဆက်များ တစ်တွဲလုံး…" - "မည်သို့ပင်ဖြစ်စေ စီစဉ်ရန်" - "၎င်းအစား ဤတစ်ခုကို ဖမ်းယူပါ" - "ဤဖမ်းယူမှုကို ပယ်ဖျက်ရန်" - "ယခုကြည့်ရန်" - "ရိုက်ကူးနိုင်သည်" - "ရိုက်ကူးရေးအတွက် စီစဉ်ထားပါသည်" - "ရိုက်ကူးရေးအစီအစဉ်တိုက်နေပါသည်" - "ဖမ်းယူနေသည်" - "ဖမ်းယူခြင်း မအောင်မြင်ပါ" - "ရိုက်ကူးရေး အချိန်ဇယားများ သတ်မှတ်ရန် အစီအစဉ်များကို ဖတ်နေသည်" - "ပရိုဂရမ်များကို ဖတ်နေသည်" - - - "DVR သည် နောက်ထပ်သိုလှောင်ရန်နေရာလွတ် လိုအပ်နေသည်" - "အစီအစဉ်များကို DVR နှင့် ဖမ်းယူသိမ်းဆည်းထားနိုင်ပါသည်။ သို့သော် DVR ကို အသုံးပြုနိုင်ရန် သင့်စက်ပစ္စည်းတွင် လုံလောက်သော နေရာလွတ် လောလောဆယ်မရှိပါ။ %1$sဂစ်ဂါဘိုက် သို့မဟုတ် ၎င်းနှင့်အထက်ရှိသော ပြင်ပသိုလှောင်ကိရိယာနှင့် ချိတ်ဆက်ပြီး ၎င်းကို သိုလှောင်ခန်းစက်ပစ္စည်းအဖြစ် ပြင်ဆင်သတ်မှတ်ရန် ညွှန်ကြားချက်များအတိုင်း လိုက်နာပါ။" - "သိုလှောင်မှုများ ပျောက်ဆုံးနေခြင်း" - "DVR က အသုံးပြုသော သိုလှောင်ခန်းအချို့မှာ ပျောက်ဆုံးနေသည်။ DVR ကို ပြန်ဖွင့်ရန်အတွက် ယခင်က အသုံးပြုခဲ့သော ပြင်ပသုံးအခွေဖွင့်စက်နှင့် ချိတ်ဆက်ပါ။ နောက်တစ်နည်းအနေဖြင့် ၎င်းကို အသုံးပြု၍ မရတော့လျှင် မေ့ပစ်ရန် ရွေးချယ်နိုင်ပါသည်။" - "သိုလှောင်ခန်းကို မေ့ပစ်မလား။" - "သင်မှတ်တမ်းတင်ထားသော အကြောင်းအရာနှင့် အချိန်ဇယားများအားလုံး ဆုံးရှုံးသွားလိမ့်မည်။" - "ရိုက်ကူးခြင်းကို ရပ်လိုပါသလား။" - "ဖမ်းယူထားသည့် အကြောင်းအရာကို သိမ်းဆည်းထားပါမည်။" - - - "ဖမ်းယူရန် စီစဉ်ထားသော်လည်း အချိန်ဇယားတိုက်နေပါသည်" - "ဖမ်းယူမှုကို စတင်လိုက်ပါပြီ။ သို့သော် အချိန်ဇယားတိုက်နေပါသည်" - "%1$s ကို ဖမ်းယူသွားပါမည်။" - "%1$s ကို ဖမ်းယူနေပါသည်။" - "%1$s ၏ အချို့အစိတ်အပိုင်းများကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" - "%1$s နှင့် %2$s တို့၏ အချို့အစိတ်အပိုင်းများကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" - "%1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် တစ်ခုကို ဖမ်းယူသွားမည် မဟုတ်ပါ။" - - %1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် %3$d ခုတို့ကို ဖမ်းယူသွားမည် မဟုတ်ပါ။ - %1$s%2$s တို့၏ အချို့အစိတ်အပိုင်းများနှင့် နောက်ထပ်အစီအစဉ် %3$d ခုတို့ကို ဖမ်းယူသွားမည် မဟုတ်ပါ။ - - "သင်ဘယ်ရုပ်/သံကို ဖမ်းယူလိုပါသလဲ။" - "ဘယ်လောက်ကြာကြာ ဖမ်းယူလိုပါသနည်း။" - "စီစဉ်ပြီးပါပြီ" - "တူညီသည့် ပရိုဂရမ်ကို %1$s ၌ ဖမ်းယူရန် စီစဉ်ထားပြီး ဖြစ်ပါသည်။" - "ဖမ်းယူပြီးပါပြီ" - "ဤပရိုဂရမ်ကို ဖမ်းယူပြီးပါပြီ။ ၎င်းကို DVR စာကြည့်တိုက်တွင် ကြည့်ရှုနိုင်ပါသည်။" - - - - - - - - - "ရိုက်ကူးထားသည့်ပရိုဂရမ်ကို မတွေ့ပါ။" - "ဆက်စပ်နေသည့် ရိုက်ကူးမှုများ" - "(ပရိုဂရမ် ဖော်ပြချက်မရှိပါ)" - - ရိုက်ပြီးသား %1$d ခု - ရိုက်ပြီးသား %1$d ခု - - " / " - "%1$s ကို ဖမ်းယူခြင်းအစီအစဉ်မှ ဖယ်ရှားလိုက်ပါပြီ" - "ချန်နယ်ချိန်ကိရိယာလိုင်းပူးနေ၍ တစ်ချို့ကိုသာ ဖမ်းယူနိုင်မည်။" - "ချန်နယ်ချိန်ကိရိယာ လိုင်းပူးနေ၍ ဖမ်းယူနိုင်မည်မဟုတ်ပါ။" - "စီစဉ်ထားသည့် ဖမ်းယူမှုများ မရှိသေးပါ။\nဖမ်းယူမှုကို ပရိုဂရမ်လမ်းညွှန်အား ကြည့်ရှု၍ စီစဉ်နိုင်ပါသည်။" - - ရိုက်ကူးမှု %1$d ခု ပဋိပက္ခဖြစ်နေပါသည် - ရိုက်ကူးမှု %1$d ခု ပဋိပက္ခဖြစ်နေပါသည် - - "ဇာတ်လမ်းတွဲဆက်တင်များ" - "ဇာတ်လမ်းတွဲ စတင်ဖမ်းယူရန်" - "ဇာတ်လမ်းတွဲဖမ်းယူခြင်း ရပ်ရန်" - "ဇာတ်လမ်းတွဲဖမ်းယူခြင်းကို ရပ်မလား။" - "ဖမ်းယူထားသည့် အပိုင်းများသည် DVR စာကြည့်တိုက်တွင် ရှိနေဦးမည်ဖြစ်သည်။" - "ရပ်ရန်" - "အပိုင်းငယ်များ မရနိုင်သေးပါ။\nရနိုင်သည်နှင့် ၎င်းတို့ကို ဖမ်းယူသွားပါမည်။" - - (%1$d မိနစ်) - (%1$d မိနစ်) - - "ဒီနေ့" - "မနက်ဖြန်" - "မနေ့က" - "ဒီနေ့ %1$s" - "မနက်ဖြန် %1$s" - "ရမှတ်" - diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index a2cf46cc..b9c149a7 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Play-kontroller" - "Nylige kanaler" + "Kanaler" "TV-alternativer" - "PIP-alternativer" "Play-kontroller er ikke tilgjengelige for denne kanalen" "Spill av eller sett på pause" "Spol fremover" @@ -35,33 +34,15 @@ "Teksting" "Visningsmodus" "PIP" - "På" - "Av" "Flere lydspor" "Få flere kanaler" "Innstillinger" - "Kilde" - "Bytt" - "På" - "Av" - "Lyd" - "Hovedkontroll" - "PIP-vindu" - "Utforming" - "Nede til høyre" - "Oppe til høyre" - "Oppe til venstre" - "Nede til venstre" - "Side om side" - "Størrelse" - "Stor" - "Liten" - "Inndatakilde" "TV (antenne/kabel)" "Ingen programinformasjon" "Ingen informasjon" "Blokkert kanal" - "Ukjent språk" + "Ukjent språk" + "Teksting %1$d" "Teksting for hørselshemmede" "Av" "Tilpass formatering" @@ -83,6 +64,7 @@ "SD" "Gruppér etter" "Dette programmet er blokkert." + "Dette programmet har ingen vurdering" "Dette programmet er vurdert som %1$s." "Inngang støtter ikke auto-kanalsøk" "Kunne ikke starte automatisk skanning for «%s»" @@ -92,7 +74,6 @@ %1$d kanal er lagt til "Ingen kanaler er lagt til" - "Tuner" "Foreldrekontroll" "På" "Av" @@ -108,6 +89,8 @@ "Andre land" "Ingen" "Ingen" + "Ingen vurdering" + "Blokkér program uten vurdering" "Ingen" "Høye begrensninger" "Medium begrensninger" @@ -124,6 +107,7 @@ "Skriv inn PIN-koden din for å se på denne kanalen" "Skriv inn PIN-koden din for å se på dette programmet" "Dette programmet er vurdert som %1$s. Skriv inn PIN-koden din for å se dette programmet" + "Dette programmet har ingen vurdering. Skriv inn PIN-koden din for å se dette programmet" "Skriv inn PIN-koden din" "For å konfigurere foreldrekontroll, angi en PIN-kode" "Skriv inn en ny PIN-kode" @@ -135,22 +119,31 @@ "Prøv på nytt, PIN-koden er feil." "Prøv på nytt, PIN-koden er feil" + "Skriv inn postnummeret ditt." + "Direkte-TV-appen bruker postnummeret til å oppgi en fullstendig programoversikt for TV-kanalene." + "Skriv inn postnummeret ditt" + "Ugyldig postnummer" "Innstillinger" "Tilpass kanallisten" "Velg kanaler for programoversikten din" "Kanalkilder" "Nye kanaler er tilgjengelig" "Foreldrekontroll" + "Tidsforskyvning" + "Ta opp mens du ser på direktesendte programmer, så du kan sette dem på pause eller spole bakover.\nAdvarsel: Dette kan redusere levetiden til den interne lagringen på grunn av intensiv bruk av lagringen." "Lisenser for åpen kildekode" - "Åpen kildekode-lisenser" + "Send tilbakemelding" "Versjon" "For å se på denne kanalen, trykk til høyre og skriv inn PIN-koden din" "For å se på dette programmet, trykk til høyre og skriv inn PIN-koden din" + "Dette programmet har ingen vurdering.\nFor å se dette programmet, trykk på knappen til høyre og skriv inn PIN-koden din" "Dette programmet har vurderingen %1$s.\nFor å se på dette programmet, trykk til høyre og skriv inn PIN-koden din." "For å se på denne kanalen, bruk Live TV-appen som er angitt som standard." "For å se dette programmet, bruk Live TV-appen som er angitt som standard." + "Dette programmet har ingen vurdering.\nFor å se dette programmet, bruk Live TV-appen som er angitt som standard." "Dette programmet er vurdert som %1$s.\nFor å se dette programmet, bruk Live TV-appen som er angitt som standard." "Programmet er blokkert" + "Dette programmet har ingen vurdering" "Dette programmet er vurdert som %1$s." "Bare lyd" "Svakt signal" @@ -181,8 +174,6 @@ "Trykk på «SELECT» (VELG)"" for å åpne TV-menyen." "Kunne ikke finne noen TV-inngang" "Kunne ikke finne TV-inngangen" - "PIP støttes ikke" - "Det finnes ingen tilgjengelige inndata som kan vises med PIP" "Tuner-typen kan ikke brukes. Kjør Live TV-appen med tuner-typen for TV-inndata." "Justering mislyktes." "Kunne ikke finne noen app som kan håndtere denne handlingen." @@ -226,6 +217,8 @@ %1$d planlagte opptak %1$d planlagt opptak + "Avbryt opptak" + "Stopp opptak" "Se på" "Spill av fra begynnelsen" "Gjenoppta avspilling" @@ -258,9 +251,6 @@ "Når det er for mange programmer som skal tas opp samtidig, blir bare programmene med høyere prioritet tatt opp." "Lagre" "Engangsopptak har høyeste prioritet" - "Avbryt" - "Avbryt" - "Glem" "Stopp" "Se tidsplanen for opptak" "Bare dette programmet" @@ -270,25 +260,28 @@ "Spill inn dette i stedet" "Kanseller dette opptaket" "Se nå" + "Slett opptak …" "Opptaksbar" "Opptak planlagt" "Opptakskonflikt" "Tar opp" "Opptaket mislyktes" - "Leser av programmer for å opprette tidsplaner for opptak" - "Leser av programmer" - - + "Leser av programmer" + "Se nylige opptak" + "Opptaket av %1$s er ufullstendig." + "Opptakene av %1$s og %2$s er ufullstendige." + "Opptakene av %1$s, %2$s og %3$s er ufullstendige." + "Opptaket av %1$s ble ikke fullført på grunn av utilstrekkelig lagringsplass." + "Opptakene av %1$s og %2$s ble ikke fullført på grunn av utilstrekkelig lagringsplass." + "Opptakene av %1$s, %2$s og %3$s ble ikke fullført på grunn av utilstrekkelig lagringsplass." "DVR trenger mer lagringsplass" - "Det kommer til å være mulig til å ta opp programmer med DVR. Det er imidlertid ikke nok lagringsplass på enheten din til at DVR kan fungere. Koble til en ekstern stasjon på %1$s GB eller mer, og følg trinnene for å formatere den som lagringsenhet." + "Det kommer til å være mulig til å ta opp programmer med DVR. Det er imidlertid ikke nok lagringsplass på enheten din til at DVR kan fungere. Koble til en ekstern stasjon på %1$d GB eller mer, og følg trinnene for å formatere den som lagringsenhet." + "Ikke nok lagringsplass" + "Dette programmet blir ikke tatt opp fordi det ikke er nok lagringsplass. Prøv å slette noen eksisterende opptak." "Manglende lagringsplass" - "Noe av lagringsplassen som brukes av DVR, mangler. Koble til den eksterne disken du bruke tidligere, for å slå på DVR igjen. Eventuelt kan du velge å glemme lagringsplassen hvis den ikke er tilgjengelig lenger." - "Vil du glemme lagring?" - "Alt innspilt innhold og alle tidsplanene går tapt." "Vil du stoppe opptaket?" "Det innspilte innholdet blir lagret." - - + "Opptaket av %1$s stoppes fordi det har konflikter med dette programmet. Det innspilte innholdet blir lagret." "Opptak er planlagt, men har konflikter" "Opptak har startet, men har konflikter" "%1$s blir tatt opp." @@ -306,17 +299,29 @@ "Det samme programmet er allerede planlagt for opptak %1$s." "Allerede tatt opp" "Dette programmet er allerede tatt opp. Det er tilgjengelig i DVR-biblioteket." - - - - - - - - + "Opptak av serie er planlagt" + + %1$d opptak er planlagt for %2$s. + %1$d opptak er planlagt for %2$s. + + + %1$d opptak er planlagt for %2$s. %3$d av dem blir ikke tatt opp på grunn av konflikter. + %1$d opptak er planlagt for %2$s. Det blir ikke tatt opp på grunn av konflikter. + + + %1$d opptak er planlagt for %2$s. %3$d episoder av denne serien og andre serier blir ikke tatt opp på grunn av konflikter. + %1$d opptak er planlagt for %2$s. %3$d episoder av denne serien og andre serier blir ikke tatt opp på grunn av konflikter. + + + %1$d opptak er planlagt for %2$s. Én episode av andre serier blir ikke tatt opp på grunn av konflikter. + %1$d opptak er planlagt for %2$s. Én episode av andre serier blir ikke tatt opp på grunn av konflikter. + + + %1$d opptak er planlagt for %2$s. %3$d episoder av andre serier blir ikke tatt opp på grunn av konflikter. + %1$d opptak er planlagt for %2$s. %3$d episoder av andre serier blir ikke tatt opp på grunn av konflikter. + "Finner ikke programopptaket." "Relaterte opptak" - "(Ingen programbeskrivelse)" %1$d opptak %1$d opptak @@ -336,6 +341,7 @@ "Vil du stoppe opptaket av serien?" "Episoder som er tatt opp, er fremdeles tilgjengelige i DVR-biblioteket." "Stopp" + "Ingen episoder er på luften nå." "Ingen episoder er tilgjengelige.\nDe blir tatt opp når de er tilgjengelige." (%1$d minutter) @@ -347,4 +353,5 @@ "%1$s i dag" "%1$s i morgen" "Poengsum" + "Programmer som er tatt opp" diff --git a/res/values-ne-rNP-v23/strings.xml b/res/values-ne-rNP-v23/strings.xml new file mode 100644 index 00000000..67220466 --- /dev/null +++ b/res/values-ne-rNP-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "च्यानलहरू" + diff --git a/res/values-ne-rNP/arrays.xml b/res/values-ne-rNP/arrays.xml new file mode 100644 index 00000000..a6dc0066 --- /dev/null +++ b/res/values-ne-rNP/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "सामान्य" + "पूर्ण" + "जूम" + + + "सबै च्यानलहरू" + "पारिवार/बच्चाहरु" + "खेलकुदहरू" + "किनमेल" + "चलचित्रहरू" + "हास्य" + "यात्रा" + "नाटक" + "शिक्षा" + "जनावर/वन्यजन्तु" + "समाचार" + "खेल" + "कलाहरू" + "मनोरञ्जन" + "जीवन शैली" + "संगीत" + "प्रिमियर" + "प्रविधि/विज्ञान" + + + "लाइभ च्यानलहरू" + "सामग्री पत्ता लगाउने सरल तरिका" + "अनुप्रयोगहरू डाउनलोड गर्नुहोस्, अझ बढी च्यानलहरू प्राप्त गर्नुहोस्" + "तपाईंको च्यानलको लाइन-अपलाई अनुकूलन गर्नुहोस्" + + + "TV.मा च्यानलहरू हेर्ने जस्ता सामग्री तपाईंका अनुप्रयोगहरूबाट हेर्नुहोस्।" + \n"TV का व्यानलहरू जस्तै, परिचित निर्देशिका र साथी अनुकूल इन्टरफेससँगै तपाईंका अनुप्रयोगहरूमा सामग्री ब्राउज गर्नुहोस्।" + "लाइभ च्यानलहरूको प्रस्ताव गर्ने अनुप्रयोगहरूको स्थापना गरी अझ बढी च्यानलहरू थप्नुहोस्। \nTV मेनुका लिंक प्रयोग गरी Google Play स्टोरमा मिल्दाजुल्दा अनुप्रयोगहरू फेला पार्नुहोस्।" + "तपाईंको च्यानल सूचीलाई अनुकूलन गर्न तपाईंका नयाँ स्थापित च्यानल स्रोतहरू सेटअप गर्नुहोस्। \nसुरु गर्नका लागि सेटिङ मेनुभित्रको च्यानल स्रोतहरू रोज्नुहोस्।" + + diff --git a/res/values-ne-rNP/rating_system_strings.xml b/res/values-ne-rNP/rating_system_strings.xml new file mode 100644 index 00000000..776e7175 --- /dev/null +++ b/res/values-ne-rNP/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "कार्यक्रमहरूमा १५ वर्ष भन्दा मुनिका दर्शकहरूका लागि अनुपयुक्त सामग्री समावेश हुन सक्छ र त्यसैले तिनीहरूको लागि अभिभावकको स्वविवेक प्रयोग गरिनुपर्छ।" + "कार्यक्रममा १९ वर्ष भन्दा मुनिका दर्शकहरूका लागि अनुपयुक्त सामग्री समावेश हुन सक्छ र त्यसैले यी १९ वर्ष भन्दा मुनिका युवाहरूका लागि उपयुक्त छैनन्।" + "अश्लील संवाद" + "भद्दा भाषा" + "यौन सामग्री" + "हिंसा" + "काल्पनिक हिंसा" + "यो कार्यक्रम सबै बच्चाहरूका लागि उपयुक्त हुने गरी बनाइएको छ।" + "यो कार्यक्रम ७ बर्ष र माथिका बच्चाहरूका लागि बनाइएको हो।" + "यो कार्यक्रम धेरैजसो अभिभावकहरूले सबै उमेरका लागि उपयुक्त पाउँछन्।" + "यो कार्यक्रमका सामग्री अभिभावकहरूले आफ्ना स-साना नानीहरूलाई अनुपयुक्त पाउन सक्छन्। घेरै अभिभावकहरू सम्भवतः आफ्ना नानीहरू सँगै यो हेर्न चाहन्छन्।" + "यो कार्यक्रमका केही सामग्री अभिभावकहरूले आफ्ना १४ वर्ष उमेर मुनिका बच्चाहरूको लागि अनुपयुक्त पाउन सक्छन्।" + "यो कार्यक्रम विशेषतया वयस्कहरूलाई बनाइएको हो, त्यसैले १७ वर्ष मुनिका बालबालिकाहरू लागि अनुपयुक्त हुन सक्छ।" + "फिल्म मूल्याङ्कनहरू" + "सामान्य दर्शकका लागि। बालबच्चाहरूले हेर्दा अभिभावकलाई कुनै आपत्ति नहुने।" + "अभिभावकको मार्गदर्शक सुझाव गरिएको। अभिभावकले आफ्ना स-साना नानीहरूको लागि नरुचाउने सामग्री समावेश हुनसक्छ।" + "अभिभावकलाई अत्यधिक सचेत रहनुपर्नेछ। केही भाग पूर्व - किशोरकिशोरीहरूको लागि अनुपयुक्त हुन सक्छ।" + "प्रतिबन्धित, केही वयस्क सामग्री समावेश छन्। अभिभावकलाई आफ्नो छोराछोरीहरूलाई लिएर जानु अघि चलचित्र बारे थप जान्‍न आग्रह गरिन्छ।" + "१७ वर्ष र मुनिकालाई निषेध। वयस्कको लागि। बच्चाहरूलाई प्रवेश निषेध।" + diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml new file mode 100644 index 00000000..9bc3dcaa --- /dev/null +++ b/res/values-ne-rNP/strings.xml @@ -0,0 +1,357 @@ + + + + + "मोनो" + "स्टेरियो" + "प्ले नियन्त्रणहरु" + "च्यानलहरू" + "टिभी विकल्पहरू" + "यस च्यानलका लागि प्ले नियन्त्रणहरू अनुपलब्ध" + "प्ले वा पज" + "फास्ट फर्वार्ड" + "रिवाइन्ड" + "अर्को" + "अघिल्लो" + "कार्यक्रम गाइड" + "नयाँ च्यानलहरू उपलब्ध" + "%1$s खोल्नुहोस्" + "बन्द क्याप्सनहरु" + "डिस्प्ले मोड" + "PIP" + "मल्टि-अडियो" + "अझ बढी च्यानलहरू प्राप्त गर्नुहोस्" + "सेटिङहरू" + "टिभी (एन्टेना/केबल)" + "कुनै पनि कार्यक्रम जानकारी छैन" + "केही सूचना छैन" + "अवरुद्ध च्यानल" + "अज्ञात भाषा" + "उपशीर्षकहरू %1$d" + "उपशीर्षक" + "बन्द गर्नुहोस्" + "फर्‍म्याटिङ अनुकूलित" + "बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सेट गर्नुहोस्" + "डिस्प्ले मोड" + "मल्टि-अडियो" + "मोनो" + "स्टेरियो" + "५.१ सराउन्ड" + "७.१ सराउन्ड" + "%d च्यानलहरु" + "च्यानल सूची आफू अनुकूल गर्नुहोस्" + "समूह छान्नुहोस्" + "समूह अचयन नगर्नुहोस्" + "द्वारा समूह" + "च्यानल स्रोत" + "HD/SD" + "HD" + "SD" + "द्वारा समूह" + "यो कार्यक्रम निषेध गरिएको छ।" + "यो कार्यक्रमको मूल्याङ्कन गरिएको छैन" + "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ" + "इनपुटले स्वतः स्क्यान समर्थन गर्दैन" + "\'%s\' का लागि स्वतः स्क्यान सुरु गर्न असमर्थ" + "बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सुरु गर्न असमर्थ।" + + %1$d च्यानलहरू थपिए + %1$d च्यानल थपियो + + "कुनै पनि च्यानल थपिएन" + "मुख्य नियन्त्रणहरू" + "खोल्नुहोस्" + "बन्द गर्नुहोस्" + "च्यानलहरू अवरुद्ध" + "सबै ब्लक गर्नुहोस्" + "सबै अनब्लक गर्नुहोस्" + "लुकेका च्यानलहरू" + "कार्यक्रम प्रतिबन्धहरू" + "PIN फेर्नुहोस्" + "मूल्याङ्कन प्रणालीहरू" + "मूल्याङ्कन" + "सबै मूल्याङ्कन प्रणाली हेर्नुहोस्" + "अन्य देशहरू" + "कुनै पनि होइन" + "कुनै पनि होइन" + "मूल्याङ्कन नगरिएको" + "मूल्याङ्कन नगरिएका कार्यक्रमहरूमाथि रोक लगाउनु" + "कुनै पनि होइन" + "उच्च प्रतिबन्धहरू" + "मध्यम प्रतिबन्धहरू" + "न्यून प्रतिबन्धहरू" + "आफू अनुकूल" + "बालबालिकाका लागि उपयुक्त सामग्री" + "ठूला बच्चाहरुका लागि उपयुक्त सामग्री" + "किशोर किशोरीहरुका लागि उपयुक्त सामग्री" + "मेनुअल प्रतिबन्धहरु" + + + "%1$s र सहायक स्तर निर्धारण" + "सहायक स्तर निर्धारण" + "यो च्यानल हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्" + "यो कार्यक्रम हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्" + "यस कार्यक्रमलाई %1$s भनेर मूल्याङ्कन गरिएको छ। यो कार्यक्रम हेर्न आफ्नो PIN प्रविष्ट गर्नुहोस्" + "यो कार्यक्रमको मूल्याङ्कन गरिएको छैन। यो कार्यक्रम हेर्न आफ्नो PIN प्रविष्ट गर्नुहोस्" + "आफ्नो पिन प्रविष्ट गर्नुहोस्" + "अभिभावकत्वको नियन्त्रण सेट गर्न, एउटा PIN सिर्जना गर्नुहोस्" + "नयाँ PIN प्रविष्टि गर्नुहोस्" + "तपाईँको PIN को पुष्टि गर्नुहोस्" + "तपाईंको हालको पिन प्रविष्ट गर्नुहोस्" + + तपाईंले ५ पटक गलत PIN प्रविष्ट गर्नुभयो।\n%1$d सेकेन्डमा पुन: प्रयास गर्नुहोस्। + तपाईंले ५ पटक गलत PIN प्रविष्ट गर्नुभयो।\n%1$d सेकेन्डमा पुन: प्रयास गर्नुहोस्। + + "त्यो PIN गलत थियो। पुनः प्रयास गर्नुहोस्।" + "पुनः प्रयास गर्नुहोस्, PIN मेल खाँदैन" + "आफ्नो जिप कोड प्रविष्ट गर्नुहोस्।" + "TV च्यानलहरूको कार्यक्रमको बारेमा समग्र मार्गदर्शन प्रदान गर्न लाइभ च्यानल अनुप्रयोगले उक्त जिप कोड प्रयोग गर्नेछ।" + "आफ्नो जिप कोड प्रविष्ट गर्नुहोस्" + "अमान्य ZIP कोड" + "सेटिङहरू" + "च्यानलको सूची आफू अनुकूल गर्नुहोस्" + "आफ्नो कार्यक्रम निर्देशिकाको लागि च्यानलहरू छनौट गर्नुहोस्" + "च्यानलका स्रोतहरू" + "नयाँ च्यानलहरू उपलब्ध छन्" + "अभिभावकीय नियन्त्रणहरू" + "टाइमसिफ्ट" + "तपाईंले लाइभ कार्यक्रमहरूलाई पज वा रिवाइन्ड गर्न सक्नुहोस् भन्नका खातिर हेर्दाहेर्दै रेकर्ड गर्नुहोस्।\nचेतावनी: यसले भण्डारणको अत्यन्त धेरै प्रयोग गरेर आन्तरिक भण्डारणको आयु कम गर्न सक्छ।" + "स्रोतका इजाजतपत्रहरू खोल्नुहोस्" + "प्रतिक्रिया पठाउनुहोस्" + "संस्करण" + "यो च्यानल हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्" + "यो कार्यक्रम हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्" + "यो कार्यक्रमको मूल्याङ्कन गरिएको छैन।\nयो कार्यक्रम हेर्न दायाँ थिचेर आफ्नो PIN प्रविष्ट गर्नुहोस्" + "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ।\nयो कार्यक्रम हेर्न, दायाँ थिच्नुहोस् र आफ्नो PIN प्रविष्ट गर्नुहोस्।" + "यो च्यानल हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।" + "यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोगको प्रयोग गर्नुहोस्।" + "यो कार्यक्रमको मूल्याङ्कन गरिएको छैन।\nयो कार्यक्रम हेर्न पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।" + "यो कार्यक्रम मूल्याङ्कन गरिएको छ %1$s।\n यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।" + "प्रोग्राम ब्लक गरिएको छ" + "यो कार्यक्रमको मूल्याङ्कन गरिएको छैन" + "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ" + "अडियो मात्र" + "सिग्नल कमजोर छ" + "इन्टरनेटमा जडान गर्न सकिएन" + + अन्य च्यानलहरू रेकर्ड भइरहेका हुनाले %1$s सम्म यो च्यानल प्ले गर्न सकिँदैन। \n\nरेकर्डिङको समय तालिकालाई समायोजन गर्न दायाँ बटन थिच्नुहोस्। + अर्को च्यानल रेकर्ड भइरहेको हुनाले %1$s सम्म यो च्यानल प्ले गर्न सकिँदैन। \n\nरेकर्डिङको समय तालिकालाई समायोजन गर्न दायाँ बटन थिच्नुहोस्। + + "शीर्षक छैन" + "च्यानल अवरुद्ध" + "नयाँ" + "स्रोतहरू" + + %1$d च्यानलहरू + %1$d च्यानल + + "कुनै च्यानलहरू उपलब्ध छैनन्" + "नयाँ" + "सेटअप गरिएको छैन" + "थप स्रोतहरू प्राप्त गर्नुहोस्" + "लाइभ च्यानलहरू प्रस्तुत गर्ने अनुप्रयोगहरू ब्राउज गर्नुहोस्" + "उपलब्ध नयाँ च्यानलका स्रोतहरू" + "नयाँ च्यानलका स्रोतहरुसँग प्रस्ताव गर्न च्यानलहरू छन्। \n अहिले तिनीहरूलाई सेट गर्नुहोस् वा च्यानल स्रोतहरुका सेटिङमा पछि गर्नुहोस्।" + "अब सेटअप गर्नुहोस्" + "ठीक छ, बुँझें" + + + "टिभी मेनु खोल्न ""SELECT थिच्नुहोस्""।" + "कुनै पनि टिभी स्रोत भेटिएन।" + "टिभी स्रोत भेटिएन लागेन" + "ट्यूनर प्रकार अनुपयुक्त। कृपया ट्यूनर प्रकार टिभी स्रोतको लागि Live TV को अनुप्रयोग सुरु गर्नुहोस।" + "ट्युन गर्न असफल" + "यो कार्य सम्हाल्न कुनै पनि अनुप्रयोग भेटिएन।" + "सबै स्रोत च्यानलहरू लुकेको छन्। \n हेर्नको लागि कम्तीमा एक च्यानल चयन गर्नुहोस्।" + "भिडियो अप्रत्याशित रूपमा अनुपलब्ध।" + "BACK कुञ्जी जडान भएका उपकरणका लागि हो। बाहिर निस्कनका लागि गृह बटन थिच्नुहोस्।" + "लाइभ च्यानलहरुलाई TV सूचीहरूलाई पढ्न अनुमति आवश्यक पदर्छ।" + "आफ्नो स्रोतहरू सेट अप गर्नुहोस्" + "लाइभ च्यानलहरूले TV च्यानलहरूसँगै उपलब्ध गराइएका प्रवाह भइरहेका च्यानलहरूको परम्परागत अनुभवलाई संयोजन गर्छ। \n\nपहिले नै स्थापित च्यानल स्रोतहरू सेट अप गरेर सुरू गरौं। वा लाइभ च्यानलहरू प्रस्ताव गर्ने अझ बढी अनुप्रयोगहरूका लागि Google Play स्टोर ब्राउज गर्नुहोस्।" + "रेकर्डिङ र समयतालिकाहरू" + "१० मिनेट" + "३० मिनेट" + "१ घन्टा" + "३ घन्टा" + "हालको" + "तालिकाबद्ध गरिएको" + "श्रृंखला" + "अन्य" + "यो च्यानल रेकर्ड गर्न सकिँदैन।" + "यो कार्यक्रम रेकर्ड गर्न सकिँदैन।" + "%1$s लाई रेकर्ड गर्ने कार्यतालिका निर्धारण गरिएको छ" + "अहिले देखि %2$s सम्म %1$s नामक कार्यक्रम रेकर्ड गर्दै" + "समय सहितको पूर्ण कार्यतालिका" + + आगामी %1$d दिन + आगामी %1$d दिन + + + %1$d मिनेट + %1$d मिनेट + + + %1$d नयाँ रेकर्डिङहरू + %1$d नयाँ रेकर्डिङ + + + %1$d रेकर्डिङहरू + %1$d रेकर्डिङ + + + %1$d रेकर्डिङहरूको समय निर्धारण गरियो + %1$d रेकर्डिङको समय निर्धारण गरियो + + "रेकर्डिङलाई रद्द गर्नुहोस्" + "रेकर्डिङलाई रोक्नुहोस्" + "हेर्नुहोस्" + "सुरुबाट चलाउनुहोस्" + "पुनःसुरु गर्नुहोस्" + "मेट्नुहोस्" + "रेकर्डिङहरू मेट्नुहोस्" + "पुनःसुरु गर्नु" + "सत्र %1$s" + "समयतालिका हेर्नु" + "थप पढ्नुहोस्" + "रेकर्डिङ मेट्नुहोस्" + "तपाईँले मेट्न चाहनुहुने एपिसोडहरूलाई चयन गर्नुहोस्। एक पटक मेटाएपछि तिनीहरूलाई पुन:प्राप्त गर्न सकिँदैन।" + "मेट्नका लागि कुनै रेकर्डिङ छैन।" + "हेरिएका एपिसोड चयन गर्नुहोस्" + "सबै एपिसोडहरू चयन गर्नुहोस्" + "सबै एपिसोडहरू चयन नगर्नुहोस्" + "%2$d मिनेट मध्ये %1$d मिनेट हेरियो" + "%2$d सेकेन्ड मध्ये %1$d सेकेन्ड हेरियो" + "कहिल्यै नहेरिएका" + + %2$d मध्ये %1$d एपिसोडहरू मेटाइए + %2$d मध्ये %1$d एपिसोड मेटाइयो + + "प्राथमिकता" + "सबैभन्दा उच्च" + "सबैभन्दा न्यून" + "नम्बर %1$d" + "च्यानलहरू" + "जुनसुकै" + "प्राथमिकता छान्नुहोस्" + "एकै समयमा अत्यन्त धेरै कार्यक्रमहरू रेकर्ड गर्नुपर्ने भएकाले उच्च प्राथमिकता भएका कार्यक्रमहरूलाई मात्र रेकर्ड गरिनेछ।" + "सुरक्षित गर्नुहोस्" + "एक-पटके रेकर्डिङहरूलाई सबैभन्दा उच्च प्राथमिकता दिइन्छ" + "रोक्नुहोस्" + "रेकर्डिङको समयतालिका हेर्नुहोस्" + "यो कार्यक्रम मात्र" + "अहिले - %1$s" + "सम्पूर्ण शृंखला…" + "जे भए पनि समय निर्धारण गर्नुहोस्" + "बरु यो रेकर्ड गर्नुहोस्" + "यस रेकर्डिङलाई रद्द गर्नुहोस्" + "अहिले हेर्नुहोस्" + "रेकर्डिङहरू मेट्नुहोस्…" + "रेकर्ड गर्न मिल्ने" + "रेकर्डिङको कार्यतालिका निर्धारण गरिएको छ" + "रेकर्डिङ सम्बन्धी असहमति" + "रेकर्ड गर्दै" + "रेकर्डिङ गर्न सकिएन" + "कार्यक्रमहरूको जानकारी पढ्दै" + "हालका रेकर्डिङहरू हेर्नुहोस्" + "%1$s को रेकर्डिङ अपूर्ण छ।" + "%1$s%2$s का रेकर्डिङहरू अपूर्ण छन्।" + "%1$s, %2$s%3$s का रेकर्डिङहरू अपूर्ण छन्।" + "भण्डारण स्थान अपर्याप्त भएकाले %1$s को रेकर्डिङ पूरा भएन।" + "भण्डारण स्थान अपर्याप्त भएकाले %1$s%2$s का रेकर्डिङहरू पूरा भएनन्।" + "भण्डारण स्थान अपर्याप्त भएकाले %1$s, %2$s%3$s का रेकर्डिङहरू पूरा भएनन्।" + "DVR लाई थप भण्डारण चाहिन्छ" + "तपाईँले DVR मार्फत कार्यक्रमहरू रेकर्ड गर्न सक्नुहुनेछ। यद्यपि, अहिले तपाईँको यन्त्रमा DVR ले काम गर्न पुग्ने गरी पर्याप्त भण्डारण छैन। कृपया %1$d जि.बि.वा सो भन्दा बढी भण्डारण क्षमता भएको कुनै बाह्य ड्राइभ जडान गर्नुहोस् र त्यसलाई यन्त्रको भण्डारणका रूपमा फर्म्याट गर्न आवश्यक कदमहरू चाल्नुहोस्।" + "पर्याप्त भण्डारण छैन" + "पर्याप्त भण्डारण उपलब्ध नभएको हुनाले यस कार्यक्रमलाई रेकर्ड गरिने छैन। केही विद्यमान रेकर्डिङहरू मेटाई हेर्नुहोस्।" + "भण्डारण उपलब्ध छैन" + "रेकर्डिङ रोक्ने हो?" + "रेकर्ड गरिएको सामग्रीलाई सुरक्षित गरिनेछ।" + "यस कार्यक्रमसँग परस्पर विरोधी भएकाले %1$s को रेकर्डिङ रोकिने छ। रेकर्ड गरिएका सामग्रीहरू सुरक्षित गरिने छन्।" + "रेकर्डिङको कार्यतालिका निर्धारण गरिएको छ तर यसमा असहमतिहरू छन्" + "रेकर्डिङ सुरु भएको छ तर यसमा असहमतिहरू छन्" + "%1$s लाई रेकर्ड गरिनेछ।" + "%1$s लाई रेकर्ड गरिँदै छ।" + "%1$s का केही भागहरूलाई रेकर्ड गरिने छैन।" + "%1$s%2$s का केही भागहरूलाई रेकर्ड गरिने छैन।" + "%1$s, %2$s र थप एक कार्यक्रमका केही भागहरूलाई रेकर्ड गरिने छैन।" + + %1$s, %2$s र थप %3$d कार्यक्रमहरूलाई रेकर्ड गरिने छैन। + %1$s, %2$s र थप %3$d कार्यक्रमलाई रेकर्ड गरिने छैन। + + "तपाईं के रेकर्ड गर्न चाहनुहुन्छ?" + "तपाईं कति बेरसम्म रेकर्ड गर्न चाहनुहुन्छ?" + "पहिले नै समय सहितको कार्यतालिका निर्धारण गरिएको छ" + "यस कार्यक्रमलाई पहिले नै %1$s मा रेकर्ड गर्न समय सहितको कार्यतालिका निर्धारण गरिएको छ।" + "पहिले नै रेकर्ड गरिएको छ" + "यस कार्यक्रमलाई पहिले नै रेकर्ड गरिएको छ। यो DVR को लाइब्रेरीमा उपलब्ध छ।" + "शृङ्खलाहरूको रेकर्डिङको समय तालिका बनाइयो" + + %2$s का %1$d रेकर्डिङहरूको समय तालिका तय गरिएको छ। + %2$s को %1$d रेकर्डिङको समय तालिका तय गरिएको छ। + + + %2$s का %1$d रेकर्डिङहरूको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण ती मध्ये %3$d लाई रेकर्ड गरिने छैन। + %2$s को %1$d रेकर्डिङको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण यसलाई रेकर्ड गरिने छैन। + + + %2$s का %1$d रेकर्डिङहरूको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण यस शृङ्खला र अन्य शृङ्खलाका %3$d एपिसोडहरूलाई रेकर्ड गरिने छैन। + %2$s को %1$d रेकर्डिङको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण यस शृङ्खला र अन्य शृङ्खलाका %3$d एपिसोडहरूलाई रेकर्ड गरिने छैन। + + + %2$s का %1$d रेकर्डिङहरूको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण अन्य शृङ्खलाको १ एपिसोडलाई रेकर्ड गरिने छैन। + %2$s को %1$d रेकर्डिङको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण अन्य शृङ्खलाको १ एपिसोडलाई रेकर्ड गरिने छैन। + + + %2$s का %1$d रेकर्डिङहरूको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण अन्य शृङ्खलाका %3$d एपिसोडहरूलाई रेकर्ड गरिने छैन। + %2$s को %1$d रेकर्डिङको समय तालिका तय गरिएको छ। तालमेल नमिलेका कारण अन्य शृङ्खलाका %3$d एपिसोडहरूलाई रेकर्ड गरिने छैन। + + "रेकर्ड गरिएको कार्यक्रम भेट्टिएन।" + "सम्बन्धित रेकर्डिङहरू" + + %1$d रेकर्डिङ + %1$d रेकर्डिङ + + " / " + "%1$s लाई रेकर्डिङको समय सहितको कार्यतालिकाबाट हटाइयो" + "ट्युनर सम्बन्धी असहमतिका कारण आंशिक रूपमा रेकर्ड गरिनेछ।" + "ट्युनर सम्बन्धी असहमतिका कारण रेकर्ड गरिने छैन।" + "अहिलेसम्म समय सहितको कार्यतालिका निर्धारण गरिएको कुनै रेकर्डिङ छैन।\nतपाईं कार्यक्रम निर्देशिका मार्फत रेकर्डिङको समय सहितको कार्यतालिका निर्धारण गर्न सक्नुहुन्छ।" + + रेकर्डिङ सम्बन्धी %1$d असहमतिहरू + रेकर्डिङ सम्बन्धी %1$d असहमति + + "शृंखला सम्बन्धी सेटिङहरू" + "शृंखलाको रेकर्डिङ सुरु गर्नुहोस्" + "शृंखलाको रेकर्डिङ रोक्नुहोस्" + "शृंखलाको रेकर्डिङ रोक्ने हो?" + "रेकर्ड गरिएका एपिसोडहरू DVR सम्बन्धी लाइब्रेरीमा उपलब्ध रहनेछन्।" + "रोक्नुहोस्" + "अहिले कुनै एपिसोड प्रसारण भइरहेको छैन।" + "कुनै एपिसोड उपलब्ध छैन।\nएपिसोडहरू उपलब्ध भएपछि तिनीहरूलाई रेकर्ड गरिनेछ।" + + (%1$d मिनेट) + (%1$d मिनेट) + + "आज" + "भोलि" + "हिजो" + "आज %1$s" + "भोलि %1$s" + "अङ्क" + "रेकर्ड गरिएका कार्यक्रमहरू" + diff --git a/res/values-ne-v23/strings.xml b/res/values-ne-v23/strings.xml deleted file mode 100644 index 67220466..00000000 --- a/res/values-ne-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "च्यानलहरू" - diff --git a/res/values-ne/arrays.xml b/res/values-ne/arrays.xml deleted file mode 100644 index a6dc0066..00000000 --- a/res/values-ne/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "सामान्य" - "पूर्ण" - "जूम" - - - "सबै च्यानलहरू" - "पारिवार/बच्चाहरु" - "खेलकुदहरू" - "किनमेल" - "चलचित्रहरू" - "हास्य" - "यात्रा" - "नाटक" - "शिक्षा" - "जनावर/वन्यजन्तु" - "समाचार" - "खेल" - "कलाहरू" - "मनोरञ्जन" - "जीवन शैली" - "संगीत" - "प्रिमियर" - "प्रविधि/विज्ञान" - - - "लाइभ च्यानलहरू" - "सामग्री पत्ता लगाउने सरल तरिका" - "अनुप्रयोगहरू डाउनलोड गर्नुहोस्, अझ बढी च्यानलहरू प्राप्त गर्नुहोस्" - "तपाईंको च्यानलको लाइन-अपलाई अनुकूलन गर्नुहोस्" - - - "TV.मा च्यानलहरू हेर्ने जस्ता सामग्री तपाईंका अनुप्रयोगहरूबाट हेर्नुहोस्।" - \n"TV का व्यानलहरू जस्तै, परिचित निर्देशिका र साथी अनुकूल इन्टरफेससँगै तपाईंका अनुप्रयोगहरूमा सामग्री ब्राउज गर्नुहोस्।" - "लाइभ च्यानलहरूको प्रस्ताव गर्ने अनुप्रयोगहरूको स्थापना गरी अझ बढी च्यानलहरू थप्नुहोस्। \nTV मेनुका लिंक प्रयोग गरी Google Play स्टोरमा मिल्दाजुल्दा अनुप्रयोगहरू फेला पार्नुहोस्।" - "तपाईंको च्यानल सूचीलाई अनुकूलन गर्न तपाईंका नयाँ स्थापित च्यानल स्रोतहरू सेटअप गर्नुहोस्। \nसुरु गर्नका लागि सेटिङ मेनुभित्रको च्यानल स्रोतहरू रोज्नुहोस्।" - - diff --git a/res/values-ne/rating_system_strings.xml b/res/values-ne/rating_system_strings.xml deleted file mode 100644 index 776e7175..00000000 --- a/res/values-ne/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "कार्यक्रमहरूमा १५ वर्ष भन्दा मुनिका दर्शकहरूका लागि अनुपयुक्त सामग्री समावेश हुन सक्छ र त्यसैले तिनीहरूको लागि अभिभावकको स्वविवेक प्रयोग गरिनुपर्छ।" - "कार्यक्रममा १९ वर्ष भन्दा मुनिका दर्शकहरूका लागि अनुपयुक्त सामग्री समावेश हुन सक्छ र त्यसैले यी १९ वर्ष भन्दा मुनिका युवाहरूका लागि उपयुक्त छैनन्।" - "अश्लील संवाद" - "भद्दा भाषा" - "यौन सामग्री" - "हिंसा" - "काल्पनिक हिंसा" - "यो कार्यक्रम सबै बच्चाहरूका लागि उपयुक्त हुने गरी बनाइएको छ।" - "यो कार्यक्रम ७ बर्ष र माथिका बच्चाहरूका लागि बनाइएको हो।" - "यो कार्यक्रम धेरैजसो अभिभावकहरूले सबै उमेरका लागि उपयुक्त पाउँछन्।" - "यो कार्यक्रमका सामग्री अभिभावकहरूले आफ्ना स-साना नानीहरूलाई अनुपयुक्त पाउन सक्छन्। घेरै अभिभावकहरू सम्भवतः आफ्ना नानीहरू सँगै यो हेर्न चाहन्छन्।" - "यो कार्यक्रमका केही सामग्री अभिभावकहरूले आफ्ना १४ वर्ष उमेर मुनिका बच्चाहरूको लागि अनुपयुक्त पाउन सक्छन्।" - "यो कार्यक्रम विशेषतया वयस्कहरूलाई बनाइएको हो, त्यसैले १७ वर्ष मुनिका बालबालिकाहरू लागि अनुपयुक्त हुन सक्छ।" - "फिल्म मूल्याङ्कनहरू" - "सामान्य दर्शकका लागि। बालबच्चाहरूले हेर्दा अभिभावकलाई कुनै आपत्ति नहुने।" - "अभिभावकको मार्गदर्शक सुझाव गरिएको। अभिभावकले आफ्ना स-साना नानीहरूको लागि नरुचाउने सामग्री समावेश हुनसक्छ।" - "अभिभावकलाई अत्यधिक सचेत रहनुपर्नेछ। केही भाग पूर्व - किशोरकिशोरीहरूको लागि अनुपयुक्त हुन सक्छ।" - "प्रतिबन्धित, केही वयस्क सामग्री समावेश छन्। अभिभावकलाई आफ्नो छोराछोरीहरूलाई लिएर जानु अघि चलचित्र बारे थप जान्‍न आग्रह गरिन्छ।" - "१७ वर्ष र मुनिकालाई निषेध। वयस्कको लागि। बच्चाहरूलाई प्रवेश निषेध।" - diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml deleted file mode 100644 index 142dfade..00000000 --- a/res/values-ne/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "मोनो" - "स्टेरियो" - "प्ले नियन्त्रणहरु" - "भर्खरैका च्यानलहरू" - "टिभी विकल्पहरू" - "PIP विकल्पहरू" - "यस च्यानलका लागि प्ले नियन्त्रणहरू अनुपलब्ध" - "प्ले वा पज" - "फास्ट फर्वार्ड" - "रिवाइन्ड" - "अर्को" - "अघिल्लो" - "कार्यक्रम गाइड" - "नयाँ च्यानलहरू उपलब्ध" - "%1$s खोल्नुहोस्" - "बन्द क्याप्सनहरु" - "डिस्प्ले मोड" - "PIP" - "खुला" - "बन्द" - "मल्टि-अडियो" - "अझ बढी च्यानलहरू प्राप्त गर्नुहोस्" - "सेटिङहरू" - "स्रोत" - "स्वाप" - "खुला" - "बन्द" - "आवाज" - "मुख्य" - "PIP सन्झ्याल" - "लेआउट" - "तल्लो दायाँ" - "माथिल्लो दायाँ" - "माथिल्लो बायाँ" - "तल्लो बायाँ" - "सँगसँगै" - "आकार" - "ठूलो" - "सानो" - "इनपुट स्रोत" - "टिभी (एन्टेना/केबल)" - "कुनै पनि कार्यक्रम जानकारी छैन" - "केही सूचना छैन" - "अवरुद्ध च्यानल" - "अज्ञात भाषा" - "उपशीर्षक" - "बन्द गर्नुहोस्" - "फर्‍म्याटिङ अनुकूलित" - "बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सेट गर्नुहोस्" - "डिस्प्ले मोड" - "मल्टि-अडियो" - "मोनो" - "स्टेरियो" - "५.१ सराउन्ड" - "७.१ सराउन्ड" - "%d च्यानलहरु" - "च्यानल सूची अनुकूलन गर्नुहोस्" - "समूह छान्नुहोस्" - "समूह अचयन नगर्नुहोस्" - "द्वारा समूह" - "च्यानल स्रोत" - "HD/SD" - "HD" - "SD" - "द्वारा समूह" - "यो कार्यक्रम निषेध गरिएको छ।" - "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ" - "इनपुटले स्वतः स्क्यान समर्थन गर्दैन" - "\'%s\' का लागि स्वतः स्क्यान सुरु गर्न असमर्थ" - "बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सुरु गर्न असमर्थ।" - - %1$d च्यानलहरू थपिए - %1$d च्यानल थपियो - - "कुनै पनि च्यानल थपिएन" - "ट्यूनर" - "मुख्य नियन्त्रणहरू" - "खोल्नुहोस्" - "बन्द गर्नुहोस्" - "च्यानलहरू अवरुद्ध" - "सबै ब्लक गर्नुहोस्" - "सबै अनब्लक गर्नुहोस्" - "लुकेका च्यानलहरू" - "कार्यक्रम प्रतिबन्धहरू" - "PIN फेर्नुहोस्" - "मूल्याङ्कन प्रणालीहरू" - "मूल्याङ्कन" - "सबै मूल्याङ्कन प्रणाली हेर्नुहोस्" - "अन्य देशहरू" - "कुनै पनि होइन" - "कुनै पनि होइन" - "कुनै पनि होइन" - "उच्च प्रतिबन्धहरू" - "मध्यम प्रतिबन्धहरू" - "न्यून प्रतिबन्धहरू" - "अनुकूलन" - "बालबालिकाका लागि उपयुक्त सामग्री" - "ठूला बच्चाहरुका लागि उपयुक्त सामग्री" - "किशोर किशोरीहरुका लागि उपयुक्त सामग्री" - "मेनुअल प्रतिबन्धहरु" - - - "%1$s र सहायक स्तर निर्धारण" - "सहायक स्तर निर्धारण" - "यो च्यानल हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्" - "यो कार्यक्रम हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्" - "यस कार्यक्रमलाई %1$s भनेर मूल्याङ्कन गरिएको छ। यो कार्यक्रम हेर्न आफ्नो PIN प्रविष्ट गर्नुहोस्" - "आफ्नो पिन प्रविष्ट गर्नुहोस्" - "अभिभावकत्वको नियन्त्रण सेट गर्न, एउटा PIN सिर्जना गर्नुहोस्" - "नयाँ PIN प्रविष्टि गर्नुहोस्" - "तपाईँको PIN को पुष्टि गर्नुहोस्" - "तपाईंको हालको पिन प्रविष्ट गर्नुहोस्" - - तपाईंले ५ पटक गलत PIN प्रविष्ट गर्नुभयो।\n%1$d सेकेन्डमा पुन: प्रयास गर्नुहोस्। - तपाईंले ५ पटक गलत PIN प्रविष्ट गर्नुभयो।\n%1$d सेकेन्डमा पुन: प्रयास गर्नुहोस्। - - "त्यो PIN गलत थियो। पुनः प्रयास गर्नुहोस्।" - "पुनः प्रयास गर्नुहोस्, PIN मेल खाँदैन" - "सेटिङहरू" - "च्यानलको सूची अनुकूलन गर्नुहोस्" - "आफ्नो कार्यक्रम निर्देशिकाको लागि च्यानलहरू छनौट गर्नुहोस्" - "च्यानलका स्रोतहरू" - "नयाँ च्यानलहरू उपलब्ध छन्" - "अभिभावकीय नियन्त्रणहरू" - "स्रोतका इजाजतपत्रहरू खोल्नुहोस्" - "खुला स्रोत इजाजतपत्रहरू" - "संस्करण" - "यो च्यानल हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्" - "यो कार्यक्रम हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्" - "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ।\nयो कार्यक्रम हेर्न, दायाँ थिच्नुहोस् र आफ्नो PIN प्रविष्ट गर्नुहोस्।" - "यो च्यानल हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।" - "यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोगको प्रयोग गर्नुहोस्।" - "यो कार्यक्रम मूल्याङ्कन गरिएको छ %1$s।\n यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।" - "प्रोग्राम ब्लक गरिएको छ" - "यो कार्यक्रम मूल्याङ्कन %1$s गरिएको छ" - "अडियो मात्र" - "सिग्नल कमजोर छ" - "इन्टरनेटमा जडान गर्न सकिएन" - - अन्य च्यानलहरू रेकर्ड भइरहेका हुनाले %1$s सम्म यो च्यानल प्ले गर्न सकिँदैन। \n\nरेकर्डिङको समय तालिकालाई समायोजन गर्न दायाँ बटन थिच्नुहोस्। - अर्को च्यानल रेकर्ड भइरहेको हुनाले %1$s सम्म यो च्यानल प्ले गर्न सकिँदैन। \n\nरेकर्डिङको समय तालिकालाई समायोजन गर्न दायाँ बटन थिच्नुहोस्। - - "शीर्षक छैन" - "च्यानल अवरुद्ध" - "नयाँ" - "स्रोतहरू" - - %1$d च्यानलहरू - %1$d च्यानल - - "कुनै च्यानलहरू उपलब्ध छैनन्" - "नयाँ" - "सेटअप गरिएको छैन" - "थप स्रोतहरू प्राप्त गर्नुहोस्" - "लाइभ च्यानलहरू प्रस्तुत गर्ने अनुप्रयोगहरू ब्राउज गर्नुहोस्" - "उपलब्ध नयाँ च्यानलका स्रोतहरू" - "नयाँ च्यानलका स्रोतहरुसँग प्रस्ताव गर्न च्यानलहरू छन्। \n अहिले तिनीहरूलाई सेट गर्नुहोस् वा च्यानल स्रोतहरुका सेटिङमा पछि गर्नुहोस्।" - "अब सेटअप गर्नुहोस्" - "ठीक छ, बुँझें" - - - "टिभी मेनु खोल्न ""SELECT थिच्नुहोस्""।" - "कुनै पनि टिभी स्रोत भेटिएन।" - "टिभी स्रोत भेटिएन लागेन" - "PIP समर्थित छैन" - "PIP सँग देखाउन सकिने कुनै स्रोत उपलब्ध छैन।" - "ट्यूनर प्रकार अनुपयुक्त। कृपया ट्यूनर प्रकार टिभी स्रोतको लागि Live TV को अनुप्रयोग सुरु गर्नुहोस।" - "ट्युन गर्न असफल" - "यो कार्य सम्हाल्न कुनै पनि अनुप्रयोग भेटिएन।" - "सबै स्रोत च्यानलहरू लुकेको छन्। \n हेर्नको लागि कम्तीमा एक च्यानल चयन गर्नुहोस्।" - "भिडियो अप्रत्याशित रूपमा अनुपलब्ध।" - "BACK कुञ्जी जडान भएका उपकरणका लागि हो। बाहिर निस्कनका लागि गृह बटन थिच्नुहोस्।" - "लाइभ च्यानलहरुलाई TV सूचीहरूलाई पढ्न अनुमति आवश्यक पदर्छ।" - "आफ्नो स्रोतहरू सेट अप गर्नुहोस्" - "लाइभ च्यानलहरूले TV च्यानलहरूसँगै उपलब्ध गराइएका प्रवाह भइरहेका च्यानलहरूको परम्परागत अनुभवलाई संयोजन गर्छ। \n\nपहिले नै स्थापित च्यानल स्रोतहरू सेट अप गरेर सुरू गरौं। वा लाइभ च्यानलहरू प्रस्ताव गर्ने अझ बढी अनुप्रयोगहरूका लागि Google Play स्टोर ब्राउज गर्नुहोस्।" - "रेकर्डिङ र समयतालिकाहरू" - "१० मिनेट" - "३० मिनेट" - "१ घन्टा" - "३ घन्टा" - "हालको" - "तालिकाबद्ध गरिएको" - "श्रृंखला" - "अन्य" - "यो च्यानल रेकर्ड गर्न सकिँदैन।" - "यो कार्यक्रम रेकर्ड गर्न सकिँदैन।" - "%1$s लाई रेकर्ड गर्ने कार्यतालिका निर्धारण गरिएको छ" - "अहिले देखि %2$s सम्म %1$s नामक कार्यक्रम रेकर्ड गर्दै" - "समय सहितको पूर्ण कार्यतालिका" - - आगामी %1$d दिन - आगामी %1$d दिन - - - %1$d मिनेट - %1$d मिनेट - - - %1$d नयाँ रेकर्डिङहरू - %1$d नयाँ रेकर्डिङ - - - %1$d रेकर्डिङहरू - %1$d रेकर्डिङ - - - %1$d रेकर्डिङहरूको समय निर्धारण गरियो - %1$d रेकर्डिङको समय निर्धारण गरियो - - "हेर्नुहोस्" - "सुरुबाट चलाउनुहोस्" - "पुनःसुरु गर्नुहोस्" - "मेट्नुहोस्" - "रेकर्डिङहरू मेट्नुहोस्" - "पुनःसुरु गर्नु" - "सत्र %1$s" - "समयतालिका हेर्नु" - "थप पढ्नुहोस्" - "रेकर्डिङ मेट्नुहोस्" - "तपाईँले मेट्न चाहनुहुने एपिसोडहरूलाई चयन गर्नुहोस्। एक पटक मेटाएपछि तिनीहरूलाई पुन:प्राप्त गर्न सकिँदैन।" - "मेट्नका लागि कुनै रेकर्डिङ छैन।" - "हेरिएका एपिसोड चयन गर्नुहोस्" - "सबै एपिसोडहरू चयन गर्नुहोस्" - "सबै एपिसोडहरू चयन नगर्नुहोस्" - "%2$d मिनेट मध्ये %1$d मिनेट हेरियो" - "%2$d सेकेन्ड मध्ये %1$d सेकेन्ड हेरियो" - "कहिल्यै नहेरिएका" - - %2$d मध्ये %1$d एपिसोडहरू मेटाइए - %2$d मध्ये %1$d एपिसोड मेटाइयो - - "प्राथमिकता" - "सबैभन्दा उच्च" - "सबैभन्दा न्यून" - "नम्बर %1$d" - "च्यानलहरू" - "जुनसुकै" - "प्राथमिकता छान्नुहोस्" - "एकै समयमा अत्यन्त धेरै कार्यक्रमहरू रेकर्ड गर्नुपर्ने भएकाले उच्च प्राथमिकता भएका कार्यक्रमहरूलाई मात्र रेकर्ड गरिनेछ।" - "सुरक्षित गर्नुहोस्" - "एक-पटके रेकर्डिङहरूलाई सबैभन्दा उच्च प्राथमिकता दिइन्छ" - "रद्द गर्नु" - "रद्द गर्नुहोस्" - "बिर्सनुहोस्" - "रोक्नुहोस्" - "रेकर्डिङको समयतालिका हेर्नुहोस्" - "यो कार्यक्रम मात्र" - "अहिले - %1$s" - "सम्पूर्ण शृंखला…" - "जे भए पनि समय निर्धारण गर्नुहोस्" - "बरु यो रेकर्ड गर्नुहोस्" - "यस रेकर्डिङलाई रद्द गर्नुहोस्" - "अहिले हेर्नुहोस्" - "रेकर्ड गर्न मिल्ने" - "रेकर्डिङको कार्यतालिका निर्धारण गरिएको छ" - "रेकर्डिङ सम्बन्धी असहमति" - "रेकर्ड गर्दै" - "रेकर्डिङ गर्न सकिएन" - "रेकर्डिङका समय तालिकाहरू सिर्जना गर्न कार्यक्रमहरू पढ्दै" - "कार्यक्रमहरूको जानकारी पढ्दै" - - - "DVR लाई थप भण्डारण चाहिन्छ" - "तपाईँले DVR मार्फत कार्यक्रमहरू रेकर्ड गर्न सक्नुहुनेछ। यद्यपि, अहिले तपाईँको यन्त्रमा DVR ले काम गर्न पुग्ने गरी पर्याप्त भण्डारण छैन। कृपया %1$s जि.बि.वा सो भन्दा बढी भण्डारण क्षमता भएको कुनै बाह्य ड्राइभ जडान गर्नुहोस् र त्यसलाई यन्त्रको भण्डारणका रूपमा फर्म्याट गर्न आवश्यक कदमहरू चाल्नुहोस्।" - "भण्डारण उपलब्ध छैन" - "DVR ले प्रयोग गरेको केही भण्डारण उपलब्ध छैन। DVR लाई पुन: सक्षम पार्न कृपया तपाईँले पहिले प्रयोग गर्नुभएको बाह्य ड्राइभलाई जडान गर्नुहोस्। वैकल्पिक रूपमा, यदि अब भण्डारण उपलब्ध छैन भने तपाईँ त्यसलाई बिर्सने विकल्प छान्न सक्नुहुन्छ।" - "भण्डारण बिर्सने हो?" - "तपाईँका रेकर्ड गरिएका सबै सामग्री र समय सहितका कार्यतालिकाहरू हराउने छन्।" - "रेकर्डिङ रोक्ने हो?" - "रेकर्ड गरिएको सामग्रीलाई सुरक्षित गरिनेछ।" - - - "रेकर्डिङको कार्यतालिका निर्धारण गरिएको छ तर यसमा असहमतिहरू छन्" - "रेकर्डिङ सुरु भएको छ तर यसमा असहमतिहरू छन्" - "%1$s लाई रेकर्ड गरिनेछ।" - "%1$s लाई रेकर्ड गरिँदै छ।" - "%1$s का केही भागहरूलाई रेकर्ड गरिने छैन।" - "%1$s%2$s का केही भागहरूलाई रेकर्ड गरिने छैन।" - "%1$s, %2$s र थप एक कार्यक्रमका केही भागहरूलाई रेकर्ड गरिने छैन।" - - %1$s, %2$s र थप %3$d कार्यक्रमहरूलाई रेकर्ड गरिने छैन। - %1$s, %2$s र थप %3$d कार्यक्रमलाई रेकर्ड गरिने छैन। - - "तपाईँ के रेकर्ड गर्न चाहनुहुन्छ?" - "तपाईँ कति बेरसम्म रेकर्ड गर्न चाहनुहुन्छ?" - "पहिले नै समय सहितको कार्यतालिका निर्धारण गरिएको छ" - "यस कार्यक्रमलाई पहिले नै %1$s मा रेकर्ड गर्न समय सहितको कार्यतालिका निर्धारण गरिएको छ।" - "पहिले नै रेकर्ड गरिएको छ" - "यस कार्यक्रमलाई पहिले नै रेकर्ड गरिएको छ। यो DVR को लाइब्रेरीमा उपलब्ध छ।" - - - - - - - - - "रेकर्ड गरिएको कार्यक्रम भेट्टिएन।" - "सम्बन्धित रेकर्डिङहरू" - "(कार्यक्रम सम्बन्धी वर्णन छैन)" - - %1$d रेकर्डिङ - %1$d रेकर्डिङ - - " / " - "%1$s लाई रेकर्डिङको समय सहितको कार्यतालिकाबाट हटाइयो" - "ट्युनर सम्बन्धी असहमतिका कारण आंशिक रूपमा रेकर्ड गरिनेछ।" - "ट्युनर सम्बन्धी असहमतिका कारण रेकर्ड गरिने छैन।" - "अहिलेसम्म समय सहितको कार्यतालिका निर्धारण गरिएको कुनै रेकर्डिङ छैन।\nतपाईँ कार्यक्रम निर्देशिका मार्फत रेकर्डिङको समय सहितको कार्यतालिका निर्धारण गर्न सक्नुहुन्छ।" - - रेकर्डिङ सम्बन्धी %1$d असहमतिहरू - रेकर्डिङ सम्बन्धी %1$d असहमति - - "शृंखला सम्बन्धी सेटिङहरू" - "शृंखलाको रेकर्डिङ सुरु गर्नुहोस्" - "शृंखलाको रेकर्डिङ रोक्नुहोस्" - "शृंखलाको रेकर्डिङ रोक्ने हो?" - "रेकर्ड गरिएका एपिसोडहरू DVR सम्बन्धी लाइब्रेरीमा उपलब्ध रहनेछन्।" - "रोक्नुहोस्" - "कुनै एपिसोड उपलब्ध छैन।\nएपिसोडहरू उपलब्ध भएपछि तिनीहरूलाई रेकर्ड गरिनेछ।" - - (%1$d मिनेट) - (%1$d मिनेट) - - "आज" - "भोलि" - "हिजो" - "आज %1$s" - "भोलि %1$s" - "अङ्क" - diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index f24ed1c3..a3e9dfed 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Afspeelknoppen" - "Recente kanalen" + "Kanalen" "Tv-opties" - "PIP-opties" "Er zijn geen afspeelknoppen beschikbaar voor dit kanaal" "Afspelen of onderbreken" "Vooruitspoelen" @@ -32,36 +31,18 @@ "Programmagids" "Nieuwe kanalen beschikbaar" "%1$s openen" - "Ondertiteling" + "Ondertitels" "Weergavemodus" "PIP" - "Aan" - "Uit" "Multi-audio" "Kanalen ophalen" "Instellingen" - "Bron" - "Wisselen" - "Aan" - "Uit" - "Geluid" - "Hoofd" - "PIP-venster" - "Lay-out" - "Rechtsonder" - "Rechtsboven" - "Linksboven" - "Linksonder" - "Naast elkaar" - "Formaat" - "Groot" - "Klein" - "Invoerbron" "Tv (antenne/kabel)" "Geen programmagegevens" "Geen informatie" "Geblokkeerd kanaal" - "Onbekende taal" + "Onbekende taal" + "Ondertiteling %1$d" "Ondertiteling" "Uit" "Opmaak aanpassen" @@ -83,6 +64,7 @@ "SD" "Groeperen op" "Dit programma is geblokkeerd" + "Dit programma is niet geclassificeerd" "Dit programma is beoordeeld als %1$s" "De invoer biedt geen ondersteuning voor automatisch scannen" "Kan automatisch zoeken naar \'%s\' niet starten" @@ -92,7 +74,6 @@ %1$d kanaal toegevoegd "Geen kanalen toegevoegd" - "Tuner" "Ouderlijk toezicht" "Aan" "Uit" @@ -108,6 +89,8 @@ "Andere landen" "Geen" "Geen" + "Niet geclassificeerd" + "Niet-geclas. prog. blokkeren" "Geen" "Strenge beperkingen" "Gem. beperkingen" @@ -124,6 +107,7 @@ "Je pincode opgeven om dit kanaal te bekijken" "Je pincode opgeven om dit programma te bekijken" "Dit programma heeft de classificatie %1$s. Geef je pincode op om dit programma te bekijken." + "Dit programma is niet geclassificeerd. Geef je pincode op om dit programma te bekijken." "Je pincode opgeven" "Stel een pincode in om ouderlijk toezicht in te stellen" "Nieuwe pincode opgeven" @@ -135,22 +119,31 @@ "Die pincode is onjuist. Probeer het opnieuw." "Probeer het opnieuw; de pincode komt niet overeen" + "Je postcode opgeven" + "De app Live tv gebruikt je postcode om een volledige programmagids voor de tv-kanalen aan te leveren." + "Geef je postcode op" + "Ongeldige postcode" "Instellingen" "Kanaallijst aanpassen" "Kanalen kiezen voor je tv-gids" "Kanaalbronnen" "Nieuwe kanalen beschikbaar" "Ouderlijk toezicht" + "Tijdverschuiving" + "Neem op tijdens het kijken zodat je live programma\'s kunt onderbreken of terugspoelen.\nWaarschuwing: De levensduur van de interne opslag kan afnemen door het intensieve gebruik." "Open-sourcelicenties" - "Open-sourcelicenties" + "Feedback verzenden" "Versie" "Als je dit kanaal wilt bekijken, druk je rechts en geef je je pincode op" "Als je dit programma wilt bekijken, druk je rechts en geef je je pincode op" + "Dit programma is niet geclassificeerd.\nAls je dit programma wilt bekijken, druk je op Rechts en geef je je pincode op." "Dit programma is beoordeeld als %1$s.\nAls je dit programma wilt bekijken, druk je rechts en geef je je pincode op." "Gebruik de standaardapp voor live tv om dit kanaal te bekijken." "Gebruik de standaardapp voor live tv om dit programma te bekijken." + "Dit programma is niet geclassificeerd.\nAls je dit programma wilt bekijken, gebruik je de standaard-app voor live tv." "Dit programma heeft de classificatie %1$s.\nGebruik de standaardapp voor live tv om dit programma te bekijken." "Programma is geblokkeerd" + "Dit programma is niet geclassificeerd" "Dit programma is beoordeeld als %1$s" "Alleen audio" "Zwak signaal" @@ -181,15 +174,13 @@ "Druk op SELECTEREN"" voor toegang tot het tv-menu." "Geen tv-invoer gevonden" "Kan de tv-invoer niet vinden" - "PIP wordt niet ondersteund" - "Geen beschikbare invoer die kan worden weergegeven met PIP" - "Tunertype is geen geschikte optie. Start de app Live kanalen voor tv-invoer van het type tuner." + "Tunertype is geen geschikte optie. Start de Live tv-app voor tv-invoer van het type tuner." "Afstemmen mislukt" "Er is geen app gevonden om deze actie uit te voeren." "Alle bronkanalen zijn verborgen.\nSelecteer ten minste één kanaal om te bekijken." "De video is onverwacht niet beschikbaar" "De toets TERUG is voor verbonden apparaten. Druk op de knop HOME om te sluiten." - "De app \'Live kanalen\' heeft toestemming nodig om tv-vermeldingen te lezen." + "De app \'Live tv\' heeft toestemming nodig om tv-vermeldingen te lezen." "Je bronnen configureren" "Live tv combineert de functionaliteit van traditionele tv-kanalen met streaming kanalen die worden geleverd door apps. \n\nJe kunt aan de slag gaan door de kanaalbronnen te configureren die al zijn geïnstalleerd. Of browse in de Google Play Store voor meer apps die live tv aanbieden." "Opnamen en planningen" @@ -226,6 +217,8 @@ %1$d opnamen gepland %1$d opname gepland + "Opname annuleren" + "Opname stoppen" "Bekijken" "Afspelen vanaf het begin" "Afspelen hervatten" @@ -258,9 +251,6 @@ "Wanneer er te veel programma\'s tegelijk moeten worden opgenomen, worden alleen de programma\'s met de hogere prioriteit opgenomen." "Opslaan" "Eenmalige opnamen krijgen de hoogste prioriteit" - "Annuleren" - "Annuleren" - "Vergeten" "Stoppen" "Opnameschema bekijken" "Dit afzonderlijke programma" @@ -270,25 +260,28 @@ "Dit programma opnemen" "Deze opname annuleren" "Nu bekijken" + "Opnamen verwijderen" "Kan worden opgenomen" "Opname gepland" "Opnameconflict" "Opnemen" "Opname mislukt" - "Programma\'s lezen om opnameplanningen te maken" - "Programma\'s lezen" - - + "Programma\'s lezen" + "Recente opnamen bekijken" + "De opname van %1$s is niet voltooid." + "De opnamen van %1$s en %2$s zijn niet voltooid." + "De opnamen van %1$s, %2$s en %3$s zijn niet voltooid." + "De opname van %1$s is niet voltooid vanwege onvoldoende opslagruimte." + "De opnamen van %1$s en %2$s zijn niet voltooid vanwege onvoldoende opslagruimte." + "De opnamen van %1$s, %2$s en %3$s zijn niet voltooid vanwege onvoldoende opslagruimte." "Voor DVR is meer opslagruimte nodig" - "Je kunt programma\'s opnemen met DVR. Er is echter momenteel onvoldoende opslagruimte beschikbaar op je apparaat om DVR te gebruiken. Sluit een externe schijf aan die %1$s GB of groter is en volg de stappen om deze te formatteren als apparaatopslag." + "Je kunt programma\'s opnemen met DVR. Er is echter momenteel onvoldoende opslagruimte beschikbaar op je apparaat om DVR te gebruiken. Sluit een externe schijf aan die %1$d GB of groter is en volg de stappen om deze te formatteren als apparaatopslag." + "Onvoldoende opslagruimte" + "Dit programma wordt niet opgenomen omdat er onvoldoende opslagruimte is. Verwijder een aantal bestaande opnamen." "Opslag ontbreekt" - "Een deel van de opslagruimte ontbreekt die door de DVR wordt gebruikt. Sluit de externe schijf aan die je eerder hebt gebruikt om DVR opnieuw in te schakelen. Je kunt er ook voor kiezen de opslagruimte te vergeten als deze niet langer beschikbaar is." - "Opslag vergeten?" - "Al je opgenomen content en planningen gaan verloren." "Opname stoppen?" "De opgenomen content wordt opgeslagen." - - + "De opname van %1$s wordt gestopt omdat deze conflicteert met dit programma. De opgenomen content wordt opgeslagen." "Opname ingepland, maar heeft conflicten" "De opname is gestart, maar heeft conflicten" "%1$s wordt opgenomen." @@ -306,17 +299,29 @@ "Hetzelfde programma is al ingepland voor opname om %1$s." "Al opgenomen" "Dit programma is al opgenomen. Het is beschikbaar in de DVR-bibliotheek." - - - - - - - - + "Serie-opname gepland" + + Er zijn %1$d opnamen gepland voor %2$s. + Er is %1$d opname gepland voor %2$s. + + + Er zijn %1$d opnamen gepland voor %2$s. %3$d hiervan worden niet opgenomen vanwege conflicten. + Er is %1$d opname gepland voor %2$s. Dit wordt niet opgenomen vanwege conflicten. + + + Er zijn %1$d opnamen gepland voor %2$s. %3$d afleveringen van deze serie en andere series worden niet opgenomen vanwege conflicten. + Er is %1$d opname gepland voor %2$s. %3$d afleveringen van deze serie en andere series worden niet opgenomen vanwege conflicten. + + + Er zijn %1$d opnamen gepland voor %2$s. 1 aflevering van andere series wordt niet opgenomen vanwege conflicten. + Er is %1$d opname gepland voor %2$s. 1 aflevering van andere series wordt niet opgenomen vanwege conflicten. + + + Er zijn %1$d opnamen gepland voor %2$s. %3$d afleveringen van andere series worden niet opgenomen vanwege conflicten. + Er is %1$d opname gepland voor %2$s. %3$d afleveringen van andere series worden niet opgenomen vanwege conflicten. + "Opgenomen programma niet gevonden." "Gerelateerde opnamen" - "(Geen programmabeschrijving)" %1$d opnamen %1$d opname @@ -336,6 +341,7 @@ "Serie-opname stoppen?" "Opgenomen afleveringen blijven beschikbaar in de DVR-bibliotheek." "Stoppen" + "Er worden nu geen afleveringen uitgezonden." "Er zijn geen afleveringen beschikbaar.\nZe worden opgenomen zodra ze beschikbaar zijn." (%1$d minuten) @@ -347,4 +353,5 @@ "%1$s vandaag" "%1$s morgen" "Score" + "Opgenomen programma\'s" diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml new file mode 100644 index 00000000..5415e3bc --- /dev/null +++ b/res/values-pa-rIN/strings.xml @@ -0,0 +1,356 @@ + + + + + "ਮੋਨੋ" + "ਸਟੀਰੀਓ" + "ਪਲੇਬੈਕ ਕੰਟਰੋਲ" + "ਚੈਨਲ" + "ਟੀਵੀ ਵਿਕਲਪ" + "ਪਲੇਬੈਕ ਕੰਟਰੋਲ ਇਸ ਚੈਨਲ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹਨ" + "ਚਲਾਓ ਜਾਂ ਰੋਕੋ" + "ਤੇਜ਼ੀ ਨਾਲ ਅੱਗੇ ਕਰੋ" + "ਪਿੱਛੇ ਕਰੋ" + "ਅੱਗੇ" + "ਪਿੱਛੇ" + "ਪ੍ਰੋਗਰਾਮ ਗਾਈਡ" + "ਨਵੇਂ ਚੈਨਲ ਉਪਲਬਧ ਹਨ" + "%1$s ਨੂੰ ਖੋਲ੍ਹੋ" + "ਬੰਦ ਸੁਰਖੀਆਂ" + "ਡਿਸਪਲੇ ਮੋਡ" + "PIP" + "ਮਲਟੀ-ਔਡੀਓ" + "ਹੋਰ ਚੈਨਲ ਪ੍ਰਾਪਤ ਕਰੋ" + "ਸੈਟਿੰਗਾਂ" + "ਟੀਵੀ (ਐਂਟੀਨਾ/ਕੇਬਲ)" + "ਕੋਈ ਪ੍ਰੋਗਰਾਮ ਸਬੰਧੀ ਜਾਣਕਾਰੀ ਨਹੀਂ" + "ਕੋਈ ਜਾਣਕਾਰੀ ਨਹੀਂ" + "ਬਲੌਕ ਕੀਤਾ ਚੈਨਲ" + "ਅਗਿਆਤ ਭਾਸ਼ਾ" + "ਬੰਦ ਸੁਰਖੀਆਂ %1$d" + "ਬੰਦ ਸੁਰਖੀਆਂ" + "ਬੰਦ" + "ਵੰਨਗੀਕਰਨ ਨੂੰ ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਰੋ" + "ਬੰਦ ਸੁਰਖੀਆਂ ਦੇ ਲਈ ਸਿਸਟਮ-ਵਿਆਪਕ ਤਰਜੀਹਾਂ ਸੈੱਟ ਕਰੋ" + "ਡਿਸਪਲੇ ਮੋਡ" + "ਮਲਟੀ-ਔਡੀਓ" + "ਮੋਨੋ" + "ਸਟੀਰੀਓ" + "5.1 ਸਰਾਊਂਡ" + "7.1 ਸਰਾਊਂਡ" + "%d ਚੈਨਲ" + "ਚੈਨਲ ਸੂਚੀ ਨੂੰ ਵਿਸ਼ੇਸ਼ ਵਿਉਂਤਬੱਧ ਕਰੋ" + "ਗਰੁੱਪ ਚੁਣੋ" + "ਗਰੁੱਪ ਨੂੰ ਅਣਚੁਣਿਆ ਕਰੋ" + "ਇਸ ਅਨੁਸਾਰ ਗਰੁੱਪ ਬਣਾਓ" + "ਚੈਨਲ ਸਰੋਤ" + "HD/SD" + "HD" + "SD" + "ਇਸ ਅਨੁਸਾਰ ਗਰੁੱਪ ਬਣਾਓ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਬਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ %1$s ਰੇਟ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਇਨਪੁਟ ਸਵੈ-ਸਕੈਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ" + "\'%s\' ਲਈ ਸਵੈ-ਸਕੈਨ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦੇ ਅਯੋਗ" + "ਬੰਦ ਸੁਰਖੀਆਂ ਲਈ ਸਿਸਟਮ-ਵਿਆਪਕ ਤਰਜੀਹਾਂ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦੇ ਅਯੋਗ।" + + %1$d ਚੈਨਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ + %1$d ਚੈਨਲ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ + + "ਕੋਈ ਚੈਨਲ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤੇ ਗਏ" + "ਮਾਪਿਆਂ ਦੇ ਕੰਟਰੋਲ" + "ਚਾਲੂ" + "ਬੰਦ" + "ਚੈਨਲ ਬਲੌਕ ਕੀਤੇ ਗਏ" + "ਸਭ ਬਲੌਕ ਕਰੋ" + "ਸਭ ਅਣਬਲੌਕ ਕਰੋ" + "ਲੁਕੇ ਹੋਏ ਚੈਨਲ" + "ਪ੍ਰੋਗਰਾਮ ਪਾਬੰਦੀਆਂ" + "PIN ਬਦਲੋ" + "ਰੇਟਿੰਗ ਸਿਸਟਮ" + "ਰੇਟਿੰਗਾਂ" + "ਸਭ ਰੇਟਿੰਗ ਸਿਸਟਮ ਵੇਖੋ" + "ਹੋਰ ਦੇਸ਼" + "ਕੋਈ ਨਹੀਂ" + "ਕੋਈ ਨਹੀਂ" + "ਰੇਟਿੰਗ-ਰਹਿਤ" + "ਰੇਟਿੰਗ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ ਬਲੌਕ ਕਰੋ" + "ਕੋਈ ਨਹੀਂ" + "ਉੱਚ ਪਾਬੰਦੀਆਂ" + "ਦਰਮਿਆਨੀਆਂ ਪਾਬੰਦੀਆਂ" + "ਘੱਟ ਪਾਬੰਦੀਆਂ" + "ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ" + "ਬੱਚਿਆਂ ਲਈ ਉਚਿਤ ਸਮੱਗਰੀ" + "ਵੱਡੇ ਬੱਚਿਆਂ ਲਈ ਉਚਿਤ ਸਮੱਗਰੀ" + "ਅੱਲੜ੍ਹਾਂ ਲਈ ਉਚਿਤ ਸਮੱਗਰੀ" + "ਦਸਤੀ ਪਾਬੰਦੀਆਂ" + "ਰੇਟਿੰਗ ਵਰਣਨਾਂ ਲਈ ਸਰੋਤ" + "%1$s ਅਤੇ ਉੱਪ-ਰੇਟਿੰਗਾਂ" + "ਉੱਪ-ਰੇਟਿੰਗਾਂ" + "ਇਹ ਚੈਨਲ ਦੇਖਣ ਲਈ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਦੇਖਣ ਲਈ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ %1$s ਰੇਟ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਵੇਖਣ ਲਈ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਦੇਖਣ ਲਈ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਮਾਪਿਆਂ ਦੇ ਕੰਟਰੋਲਾਂ ਨੂੰ ਸੈੱਟ ਕਰਨ ਲਈ, ਇੱਕ PIN ਬਣਾਓ" + "ਨਵਾਂ PIN ਦਾਖਲ ਕਰੋ" + "ਆਪਣੇ PIN ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ" + "ਆਪਣਾ ਮੌਜੂਦਾ PIN ਦਾਖਲ ਕਰੋ" + + ਤੁਸੀਂ 5 ਵਾਰ ਗਲਤ PIN ਦਾਖਲ ਕੀਤਾ\n%1$d ਸਕਿੰਟ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ + ਤੁਸੀਂ 5 ਵਾਰ ਗਲਤ PIN ਦਾਖਲ ਕੀਤਾ\n%1$d ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ + + "ਉਹ PIN ਗਲਤ ਸੀ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" + "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ, PIN ਮੇਲ ਨਹੀਂ ਖਾਂਦਾ" + "ਆਪਣਾ ਜ਼ਿਪ ਕੋਡ ਦਾਖਲ ਕਰੋ।" + "ਲਾਈਵ ਚੈਨਲ ਐਪ ਟੀਵੀ ਚੈਨਲਾਂ ਲਈ ਮੁਕੰਮਲ ਪ੍ਰੋਗਰਾਮ ਗਾਈਡ ਮੁਹੱਈਆ ਕਰਵਾਉਣ ਵਾਸਤੇ ਜ਼ਿਪ ਕੋਡ ਦੀ ਵਰਤੋਂ ਕਰੇਗੀ।" + "ਆਪਣਾ ਜ਼ਿਪ ਕੋਡ ਦਾਖਲ ਕਰੋ" + "ਅਵੈਧ ਜ਼ਿਪ ਕੋਡ" + "ਸੈਟਿੰਗਾਂ" + "ਚੈਨਲ ਸੂਚੀ ਨੂੰ ਵਿਸ਼ੇਸ਼ ਵਿਉਂਤਬੱਧ ਕਰੋ" + "ਆਪਣੀ ਪ੍ਰੋਗਰਾਮ ਗਾਈਡ ਲਈ ਚੈਨਲ ਚੁਣੋ" + "ਚੈਨਲ ਸਰੋਤ" + "ਨਵੇਂ ਚੈਨਲ ਉਪਲਬਧ ਹਨ" + "ਮਾਪਿਆਂ ਦੇ ਕੰਟਰੋਲ" + "ਟਾਈਮ-ਸ਼ਿਫਟ" + "ਟੀਵੀ ਦੇਖਣ ਦੌਰਾਨ ਰਿਕਾਰਡ ਕਰੋ ਤਾਂ ਕਿ ਤੁਸੀਂ ਲਾਈਵ ਪ੍ਰੋਗਰਾਮਾਂ ਨੂੰ ਰੋਕ ਜਾਂ ਪਿੱਛੇ ਲੈ ਜਾ ਸਕੋਂ।\nਚਿਤਾਵਨੀ: ਇਸ ਨਾਲ ਸਟੋਰੇਜ ਦੀ ਵੱਧ ਵਰਤੋਂ ਹੋਣ ਕਰਕੇ ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਦਾ ਜੀਵਨਕਾਲ ਘੱਟ ਹੋ ਸਕਦਾ ਹੈ।" + "ਖੁੱਲ੍ਹਾ ਸਰੋਤ ਲਾਇਸੰਸ" + "ਪ੍ਰਤੀਕਰਮ ਭੇਜੋ" + "ਰੂਪ" + "ਇਸ ਚੈਨਲ ਨੂੰ ਵੇਖਣ ਲਈ, &apos;ਸੱਜਾ&apos; ਬਟਨ ਦਬਾਓ ਅਤੇ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਸ ਚੈਨਲ ਨੂੰ ਵੇਖਣ ਲਈ, &apos;ਸੱਜਾ&apos; ਬਟਨ ਦਬਾਓ ਅਤੇ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।\nਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਦੇਖਣ ਲਈ, ਸੱਜਾ ਬਟਨ ਦਬਾਓ ਅਤੇ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ %1$s ਰੇਟ ਕੀਤਾ ਗਿਆ ਹੈ।\nਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਵੇਖਣ ਲਈ, &apos;ਸੱਜਾ&apos; ਬਟਨ ਦਬਾਓ ਅਤੇ ਆਪਣਾ PIN ਦਾਖਲ ਕਰੋ।" + "ਇਸ ਚੈਨਲ ਨੂੰ ਵੇਖਣ ਲਈ, ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਵ ਟੀਵੀ ਐਪ ਦੀ ਵਰਤੋਂ ਕਰੋ।" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਵੇਖਣ ਲਈ, ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਵ ਟੀਵੀ ਐਪ ਦੀ ਵਰਤੋਂ ਕਰੋ।" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।\nਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਦੇਖਣ ਲਈ, ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਵ ਟੀਵੀ ਅੈਪ ਦੀ ਵਰਤੋਂ ਕਰੋ।" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ %1$s ਰੇਟ ਕੀਤਾ ਗਿਆ ਹੈ।\nਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਵੇਖਣ ਲਈ, ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਵ ਟੀਵੀ ਐਪ ਦੀ ਵਰਤੋਂ ਕਰੋ।" + "ਪ੍ਰੋਗਰਾਮ ਬਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਇਸ ਪ੍ਰੋਗਰਾਮ ਨੂੰ %1$s ਰੇਟ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਸਿਰਫ ਔਡੀਓ" + "ਕਮਜ਼ੋਰ ਸਿਗਨਲ" + "ਕੋਈ ਇੰਟਰਨੈੱਟ ਕਨੈਕਸ਼ਨ ਨਹੀਂ" + + ਇਸ ਚੈਨਲ ਨੂੰ %1$s ਤੱਕ ਨਹੀਂ ਚਲਾਇਆ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਹੋਰ ਚੈਨਲ ਰਿਕਾਰਡ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ। \n\nਰਿਕਾਰਡਿੰਗ ਦੇ ਨਿਯਤ ਸਮੇਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰਨ ਲਈ &apos;ਸੱਜਾ&apos; ਬਟਨ ਦਬਾਓ। + ਇਸ ਚੈਨਲ ਨੂੰ %1$s ਤੱਕ ਨਹੀਂ ਚਲਾਇਆ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਹੋਰ ਚੈਨਲ ਰਿਕਾਰਡ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ। \n\nਰਿਕਾਰਡਿੰਗ ਦੇ ਨਿਯਤ ਸਮੇਂ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰਨ ਲਈ &apos;ਸੱਜਾ&apos; ਬਟਨ ਦਬਾਓ। + + "ਕੋਈ ਸਿਰਲੇਖ ਨਹੀਂ" + "ਚੈਨਲ ਬਲੌਕ ਹੈ" + "ਨਵਾਂ" + "ਸਰੋਤ" + + %1$d ਚੈਨਲ + %1$d ਚੈਨਲ + + "ਕੋਈ ਚੈਨਲ ਉਪਲਬਧ ਨਹੀਂ ਹਨ" + "ਨਵਾਂ" + "ਸਥਾਪਤ ਨਹੀਂ" + "ਹੋਰ ਸਰੋਤ ਪ੍ਰਾਪਤ ਕਰੋ" + "ਲਾਈਵ ਚੈਨਲਾਂ ਦੀ ਪੇਸ਼ਕਸ਼ ਕਰਦੀਆਂ ਐਪਾਂ ਨੂੰ ਬ੍ਰਾਊਜ਼ ਕਰੋ" + "ਨਵੇਂ ਚੈਨਲ ਸਰੋਤ ਉਪਲਬਧ ਹਨ" + "ਨਵੇਂ ਚੈਨਲ ਸਰੋਤਾਂ ਕੋਲ ਪੇਸ਼ਕਸ਼ ਕਰਨ ਲਈ ਚੈਨਲ ਹਨ।\nਉਹਨਾਂ ਨੂੰ ਹੁਣੇ ਸਥਾਪਤ ਕਰੋ, ਜਾਂ ਇਹ ਬਾਅਦ ਵਿੱਚ ਚੈਨਲ ਸਰੋਤ ਸੈਟਿੰਗ ਵਿੱਚ ਕਰੋ।" + "ਹੁਣੇ ਸਥਾਪਤ ਕਰੋ" + "ਠੀਕ, ਸਮਝ ਲਿਆ" + + + "ਟੀਵੀ ਮੀਨੂ \'ਤੇ ਪਹੁੰਚ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ""&apos;ਚੁਣੋ&apos; ਨੂੰ ਦਬਾਓ""।" + "ਕੋਈ ਟੀਵੀ ਇਨਪੁਟ ਨਹੀਂ ਲੱਭੀ" + "ਟੀਵੀ ਇਨਪੁਟ ਨੂੰ ਲੱਭਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ" + "ਟਿਊਨਰ ਦੀ ਕਿਸਮ ਉਚਿਤ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਟਿਊਨਰ ਕਿਸਮ ਦੀ ਟੀਵੀ ਇਨਪੁਟ ਲਈ ਲਾਈਵ ਚੈਨਲ ਐਪ ਨੂੰ ਲਾਂਚ ਕਰੋ।" + "ਟਿਊਨ ਅਸਫ਼ਲ ਰਿਹਾ" + "ਇਸ ਕਾਰਵਾਈ ਨਾਲ ਨਿਪਟਣ ਲਈ ਕੋਈ ਐਪ ਨਹੀਂ ਲੱਭੀ।" + "ਸਾਰੇ ਸਰੋਤ ਚੈਨਲ ਲੁਕੇ ਹੋਏ ਹਨ।\nਵੇਖਣ ਲਈ ਘੱਟੋ-ਘੱਟ ਇੱਕ ਚੈਨਲ ਨੂੰ ਚੁਣੋ।" + "ਵੀਡੀਓ ਫ਼ਾਈਲ ਅਣਕਿਆਸੇ ਤਰੀਕੇ ਨਾਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "&apos;ਪਿੱਛੇ&apos; ਕੁੰਜੀ ਕਨੈਕਟ ਕੀਤੀ ਡੀਵਾਈਸ ਲਈ ਹੈ। ਬਾਹਰ ਜਾਣ ਲਈ ਹੋਮ ਬਟਨ ਦਬਾਓ।" + "ਲਾਈਵ ਚੈਨਲਾਂ ਨੂੰ ਟੀਵੀ ਸੂਚੀਆਂ ਪੜ੍ਹਨ ਲਈ ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ।" + "ਆਪਣੇ ਸਰੋਤਾਂ ਦੀ ਸਥਾਪਨਾ ਕਰੋ" + "ਲਾਈਵ ਚੈਨਲ ਐਪ ਰਵਾਇਤੀ ਟੀਵੀ ਚੈਨਲਾਂ ਦੇ ਅਨੁਭਵ ਨਾਲ ਐਪਾਂ ਦੁਆਰਾ ਮੁਹੱਈਆ ਕੀਤੇ ਸਟ੍ਰੀਮਿੰਗ ਚੈਨਲਾਂ ਦੇ ਅਨੁਭਵ ਨੂੰ ਮਿਲਾਉਂਦੀ ਹੈ। \n\nਪਹਿਲਾਂ ਤੋਂ ਸਥਾਪਤ ਕੀਤੇ ਚੈਨਲ ਸਰੋਤਾਂ ਦੀ ਸਥਾਪਨਾ ਕਰਕੇ ਸ਼ੁਰੂ ਕਰੋ। ਜਾਂ ਲਾਈਵ ਚੈਨਲਾਂ ਦੀ ਪੇਸ਼ਕਸ਼ ਕਰਦੀਆਂ ਹੋਰ ਐਪਾਂ ਲਈ Google Play ਸਟੋਰ ਨੂੰ ਬ੍ਰਾਊਜ਼ ਕਰੋ।" + "ਰਿਕਾਰਡਿੰਗ ਅਤੇ ਨਿਯਤ ਸਮੇਂ" + "10 ਮਿੰਟ" + "30 ਮਿੰਟ" + "1 ਘੰਟਾ" + "3 ਘੰਟੇ" + "ਹਾਲੀਆ" + "ਨਿਯਤ" + "ਲੜੀ" + "ਹੋਰ" + "ਚੈਨਲ ਨੂੰ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" + "ਪ੍ਰੋਗਰਾਮ ਨੂੰ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" + "%1$s ਨੂੰ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ।" + "ਹੁਣ ਤੋਂ %2$s ਤੱਕ %1$s ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + "ਸੰਪੂਰਨ ਕਾਰਜ-ਕ੍ਰਮ" + + ਅਗਲਾ %1$d ਦਿਨ + ਅਗਲੇ %1$d ਦਿਨ + + + %1$d ਮਿੰਟ + %1$d ਮਿੰਟ + + + %1$d ਨਵੀਂ ਰਿਕਾਰਡਿੰਗ + %1$d ਨਵੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ + + + %1$d ਰਿਕਾਰਡਿੰਗ + %1$d ਰਿਕਾਰਡਿੰਗਾਂ + + + %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ + %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ + + "ਰਿਕਾਰਡਿੰਗ ਰੱਦ ਕਰੋ" + "ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਕਰੋ" + "ਵੇਖੋ" + "ਮੁੱਢ ਤੋਂ ਚਲਾਓ" + "ਜਿੱਥੇ ਛੱਡਿਆ ਸੀ ਓਥੋਂ ਦੁਬਾਰਾ ਚਲਾਓ" + "ਮਿਟਾਓ" + "ਰਿਕਾਰਡਿੰਗਾਂ ਮਿਟਾਓ" + "ਮੁੜ ਸ਼ੁਰੂ ਕਰੋ" + "ਸੀਜ਼ਨ %1$s" + "ਕਾਰਜ-ਕ੍ਰਮ ਵੇਖੋ" + "ਹੋਰ ਪੜ੍ਹੋ" + "ਰਿਕਾਰਡਿੰਗਾਂ ਮਿਟਾਓ" + "ਉਹਨਾਂ ਐਪੀਸੋਡਾਂ ਨੂੰ ਚੁਣੋ ਜਿੰਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ। ਇੱਕ ਵਾਰ ਮਿਟਾਏ ਜਾਣ ਤੋਂ ਬਾਅਦ ਉਹਨਾਂ ਨੂੰ ਮੁੜ-ਹਾਸਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" + "ਮਿਟਾਉਣ ਲਈ ਕੋਈ ਰਿਕਾਰਡਿੰਗਾਂ ਨਹੀਂ ਹਨ।" + "ਵੇਖੇ ਗਏ ਐਪੀਸੋਡ ਚੁਣੋ" + "ਸਾਰੇ ਐਪੀਸੋਡ ਚੁਣੋ" + "ਸਾਰੇ ਐਪੀਸੋਡਾਂ ਨੂੰ ਅਣਚੁਣਿਆ ਕਰੋ" + "%2$d ਵਿੱਚੋਂ %1$d ਮਿੰਟਾਂ ਦੀ ਰਿਕਾਰਡਿੰਗ ਵੇਖੀ ਗਈ" + "%2$d ਵਿੱਚੋਂ %1$d ਸਕਿੰਟਾਂ ਦੀ ਰਿਕਾਰਡਿੰਗ ਵੇਖੀ ਗਈ" + "ਕਦੇ ਵੀ ਨਾ ਦੇਖੀਆਂ" + + %2$d ਵਿੱਚੋਂ %1$d ਐਪੀਸੋਡ ਮਿਟਾਇਆ ਗਿਆ ਹੈ + %2$d ਵਿੱਚੋਂ %1$d ਐਪੀਸੋਡ ਮਿਟਾਏ ਗਏ ਹਨ + + "ਤਰਜੀਹ" + "ਉੱਚਤਮ" + "ਨਿਊਨਤਮ" + "ਨੰਬਰ %1$d" + "ਚੈਨਲ" + "ਕੋਈ ਵੀ" + "ਤਰਜੀਹ ਚੁਣੋ" + "ਜਦੋਂ ਇੱਕੋ ਸਮੇਂ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਬਹੁਤ ਜ਼ਿਆਦਾ ਪ੍ਰੋਗਰਾਮ ਹੋਣ, ਤਾਂ ਸਿਰਫ਼ ਸਭ ਤੋਂ ਵੱਧ ਤਰਜੀਹ ਵਾਲੇ ਪ੍ਰੋਗਰਾਮ ਹੀ ਰਿਕਾਰਡ ਕੀਤੇ ਜਾਣਗੇ।" + "ਰੱਖਿਅਤ ਕਰੋ" + "ਇੱਕ ਵਾਰ ਕੀਤੀਆਂ ਜਾਣ ਵਾਲੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ ਦੀ ਉੱਚਤਮ ਤਰਜੀਹ ਹੁੰਦੀ ਹੈ" + "ਬੰਦ ਕਰੋ" + "ਰਿਕਾਰਡਿੰਗ ਦਾ ਨਿਯਤ ਸਮਾਂ ਵੇਖੋ" + "ਇਹ ਸਿੰਗਲ ਪ੍ਰੋਗਰਾਮ" + "ਹੁਣ - %1$s" + "ਸਮੁੱਚੀ ਲੜੀਆਂ …" + "ਫਿਰ ਵੀ ਨਿਯਤ ਕਰੋ" + "ਇਸ ਦੀ ਬਜਾਏ ਇਸ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ" + "ਇਸ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਦ ਕਰੋ" + "ਹੁਣ ਵੇਖੋ" + "ਰਿਕਾਰਡਿੰਗਾਂ ਮਿਟਾਓ…" + "ਰਿਕਾਰਡ ਕਰਨ ਯੋਗ" + "ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ" + "ਰਿਕਾਰਡਿੰਗ ਵਿਵਾਦ" + "ਰਿਕਾਰਡਿੰਗ" + "ਰਿਕਾਰਡਿੰਗ ਅਸਫਲ ਰਹੀ" + "ਪ੍ਰੋਗਰਾਮਾਂ ਨੂੰ ਪੜ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ" + "ਹਾਲੀਆ ਰਿਕਾਰਡਿੰਗਾਂ ਵੇਖੋ" + "%1$s ਦੀ ਰਿਕਾਰਡਿੰਗ ਅਧੂਰੀ ਹੈ।" + "%1$s ਅਤੇ %2$s ਦੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ ਅਧੂਰੀਆਂ ਹਨ।" + "%1$s, %2$s ਅਤੇ %3$s ਦੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ ਅਧੂਰੀਆਂ ਹਨ।" + "ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਾ ਹੋਣ ਕਰਕੇ %1$s ਦੀ ਰਿਕਾਰਡਿੰਗ ਪੂਰੀ ਨਹੀਂ ਹੋਈ।" + "ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਾ ਹੋਣ ਕਰਕੇ %1$s ਅਤੇ %2$s ਦੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ ਪੂਰੀਆਂ ਨਹੀਂ ਹੋਈਆਂ।" + "ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਾ ਹੋਣ ਕਰਕੇ %1$s, %2$s ਅਤੇ %3$s ਦੀਆਂ ਰਿਕਾਰਡਿੰਗਾਂ ਪੂਰੀਆਂ ਨਹੀਂ ਹੋਈਆਂ।" + "DVR ਨੂੰ ਹੋਰ ਸਟੋਰੇਜ ਦੀ ਲੋੜ ਹੈ" + "ਤੁਸੀਂ DVR ਨਾਲ ਪ੍ਰੋਗਰਾਮਾਂ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਸਕੋਂਗੇ। ਹਾਲਾਂਕਿ ਹੁਣ DVR ਦੇ ਕੰਮ ਕਰਨ ਲਈ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਵਿਚਲੀ ਸਟੋਰੇਜ ਕਾਫੀ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ %1$dGB ਜਾਂ ਉਸਤੋਂ ਵੀ ਜ਼ਿਆਦਾ ਸਟੋਰੇਜ ਵਾਲੀ ਬਾਹਰੀ ਡੀਵਾਈਸ ਨੂੰ ਕਨੈਕਟ ਕਰੋ ਅਤੇ ਉਸ ਨੂੰ ਡੀਵਾਈਸ ਸਟੋਰੇਜ ਵਜੋਂ ਵੰਨਗੀਕਿਰਤ ਕਰਨ ਲਈ ਪੜਾਵਾਂ ਦਾ ਅਨੁਸਰਣ ਕਰੋ।" + "ਸਟੋਰੇਜ ਕਾਫੀ ਨਹੀਂ ਹੈ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ ਕਿਉਂਕਿ ਸਟੋਰੇਜ ਕਾਫ਼ੀ ਨਹੀਂ ਹੈ। ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਕੁਝ ਰਿਕਾਰਡਿੰਗਾਂ ਨੂੰ ਮਿਟਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" + "ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ" + "ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਕਰਨੀ ਹੈ?" + "ਰਿਕਾਰਡ ਕੀਤੀ ਸਮੱਗਰੀ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾਵੇਗਾ।" + "%1$s ਦੀ ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਹੋ ਜਾਵੇਗੀ ਕਿਉਂਕਿ ਇਹ ਇਸ ਪ੍ਰੋਗਰਾਮ ਨਾਲ ਵਿਵਾਦ ਕਰਦੀ ਹੈ। ਰਿਕਾਰਡ ਕੀਤੀ ਸਮੱਗਰੀ ਰੱਖਿਅਤ ਕੀਤੀ ਜਾਵੇਗੀ।" + "ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਪਰ ਇਸ ਵਿੱਚ ਵਿਵਾਦ ਹਨ" + "ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਹੋ ਗਈ ਹੈ ਪਰ ਇਸ ਵਿੱਚ ਵਿਵਾਦ ਹਨ" + "%1$s ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਵੇਗਾ।" + "%1$s ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ।" + "%1$s ਦੇ ਕੁਝ ਹਿੱਸੇ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤੇ ਜਾਣਗੇ।" + "%1$s ਅਤੇ %2$s ਦੇ ਕੁਝ ਹਿੱਸੇ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤੇ ਜਾਣਗੇ।" + "%1$s, %2$s ਅਤੇ ਇੱਕ ਹੋਰ ਨਿਯਤ ਕੀਤੇ ਸਮੇਂ ਵਾਲੇ ਪ੍ਰੋਗਰਾਮਾਂ ਦੇ ਕੁਝ ਹਿੱਸੇ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤੇ ਜਾਣਗੇ।" + + %1$s, %2$s ਅਤੇ %3$d ਨਿਯਤ ਕੀਤੇ ਸਮੇਂ ਦੇ ਕੁਝ ਹਿੱਸੇ ਨੂੰ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + %1$s, %2$s ਅਤੇ %3$d ਨਿਯਤ ਕੀਤੇ ਸਮਿਆਂ ਦੇ ਕੁਝ ਹਿੱਸੇ ਨੂੰ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + + "ਤੁਸੀਂ ਕੀ ਰਿਕਾਰਡ ਕਰਨਾ ਚਾਹੋਂਗੇ?" + "ਤੁਸੀਂ ਕਿੰਨਾ ਚਿਰ ਰਿਕਾਰਡ ਕਰਨਾ ਚਾਹੋਂਗੇ?" + "ਪਹਿਲਾਂ ਹੀ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ" + "ਉਸੇ ਪ੍ਰੋਗਰਾਮ ਦਾ %1$s \'ਤੇ ਰਿਕਾਰਡ ਕੀਤੇ ਜਾਣ ਲਈ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ।" + "ਪਹਿਲਾਂ ਹੀ ਰਿਕਾਰਡ ਕੀਤਾ ਹੋਇਆ ਹੈ" + "ਇਹ ਪ੍ਰੋਗਰਾਮ ਪਹਿਲਾਂ ਹੀ ਰਿਕਾਰਡ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਇਹ DVR ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਉਪਲਬਧ ਹੈ।" + "ਲੜੀ ਦੀ ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ" + + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। + + + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਉਹਨਾਂ ਵਿੱਚੋਂ %3$d ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਉਹਨਾਂ ਵਿੱਚੋਂ %3$d ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + + + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਲੜੀ ਅਤੇ ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ %3$d ਐਪੀਸੋਡ ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਲੜੀ ਅਤੇ ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ %3$d ਐਪੀਸੋਡਾਂ ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + + + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ 1 ਐਪੀਸੋਡ ਦੀ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡਿੰਗ ਨਹੀਂ ਕੀਤੀ ਜਾਵੇਗੀ। + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ 1 ਐਪੀਸੋਡ ਦੀ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡਿੰਗ ਨਹੀਂ ਕੀਤੀ ਜਾਵੇਗੀ। + + + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ %3$d ਐਪੀਸੋਡ ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + %2$s ਲਈ %1$d ਰਿਕਾਰਡਿੰਗਾਂ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕੀਤਾ ਗਿਆ ਹੈ। ਹੋਰਨਾਂ ਲੜੀਆਂ ਦੇ %3$d ਐਪੀਸੋਡਾਂ ਨੂੰ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। + + "ਰਿਕਾਰਡ ਕੀਤਾ ਪ੍ਰੋਗਰਾਮ ਨਹੀਂ ਲੱਭਿਆ।" + "ਸਬੰਧਿਤ ਰਿਕਾਰਡਿੰਗਾਂ" + + %1$d ਰਿਕਾਰਡਿੰਗ + %1$d ਰਿਕਾਰਡਿੰਗਾਂ + + " / " + "%1$s ਨੂੰ ਰਿਕਾਰਡਿੰਗ ਦੇ ਨਿਯਤ ਸਮੇਂ ਤੋਂ ਹਟਾਇਆ ਗਿਆ।" + "ਟਿਊਨਰ ਦੇ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਅਧੂਰਾ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਵੇਗਾ।" + "ਟਿਊਨਰ ਦੇ ਵਿਵਾਦਾਂ ਦੇ ਕਾਰਨ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ।" + "ਹਾਲੇ ਕਾਰਜ-ਕ੍ਰਮ \'ਤੇ ਕੋਈ ਰਿਕਾਰਡਿੰਗਾਂ ਨਹੀਂ ਹਨ।\nਤੁਸੀਂ ਪ੍ਰੋਗਰਾਮ ਗਾਈਡ ਵਿੱਚੋਂ ਰਿਕਾਰਡਿੰਗ ਦਾ ਸਮਾਂ ਨਿਯਤ ਕਰ ਸਕਦੇ ਹੋ।" + + %1$d ਰਿਕਾਰਡਿੰਗ ਵਿਵਾਦ + %1$d ਰਿਕਾਰਡਿੰਗ ਵਿਵਾਦ + + "ਲੜੀ ਦੀਆਂ ਸੈਟਿੰਗਾਂ" + "ਲੜੀ ਦੀ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ" + "ਲੜੀ ਦੀ ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਕਰੋ" + "ਕੀ ਲੜੀ ਦੀ ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਕਰਨੀ ਹੈ?" + "ਰਿਕਾਰਡ ਕੀਤੇ ਐਪੀਸੋਡ DVR ਲਾਇਬਰੇਰੀ ਵਿੱਚ ਉਪਲਬਧ ਰਹਿਣਗੇ।" + "ਬੰਦ ਕਰੋ" + "ਕੋਈ ਵੀ ਐਪੀਸੋਡਾਂ ਦਾ ਹੁਣ ਪ੍ਰਸਾਰਨ ਨਹੀਂ ਹੋ ਰਿਹਾ ਹੈ।" + "ਕੋਈ ਐਪੀਸੋਡ ਉਪਲਬਧ ਨਹੀਂ ਹਨ।\nਉਪਲਬਧ ਹੋਣ \'ਤੇ ਉਹਨਾਂ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਵੇਗਾ।" + + (%1$d ਮਿੰਟ) + (%1$d ਮਿੰਟ) + + "ਅੱਜ" + "ਭਲਕੇ" + "ਕੱਲ੍ਹ" + "%1$s ਅੱਜ" + "%1$s ਭਲਕੇ" + "ਸਕੋਰ" + "ਰਿਕਾਰਡ ਕੀਤੇ ਪ੍ਰੋਗਰਾਮ" + diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 1e86cb7a..3db5beba 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Sterowanie odtwarzaniem" - "Ostatnie kanały" + "Kanały" "Opcje TV" - "Opcje PIP" "Elementy sterujące Play są niedostępne dla tego kanału" "Odtwórz lub wstrzymaj" "Przewiń do przodu" @@ -35,33 +34,15 @@ "Napisy" "Tryb wyświetl." "PIP" - "Włączony" - "Wyłączony" "Wiele kan. audio" "Więcej kanałów" "Ustawienia" - "Źródło" - "Przełącz" - "Włączone" - "Wyłączone" - "Dźwięk" - "Główne okno" - "Okno PIP" - "Układ" - "Prawy dolny róg" - "Prawy górny róg" - "Lewy górny róg" - "Lewy dolny róg" - "Obok siebie" - "Rozmiar" - "Duży" - "Mały" - "Źródło sygnału" "TV (antena/kabel)" "Brak informacji o programach" "Brak informacji" "Kanał zablokowany" - "Nieznany język" + "Nieznany język" + "Napisy: %1$d" "Napisy" "Wył." "Dostosuj format" @@ -83,6 +64,7 @@ "SD" "Grupuj według" "Ten program jest zablokowany" + "Ten program nie ma oceny" "Ten program ma ocenę %1$s." "Wejście nie obsługuje automatycznego skanowania" "Nie można rozpocząć automatycznego skanowania dla „%s”" @@ -94,7 +76,6 @@ Dodano %1$d kanał "Nie dodano kanałów" - "Tuner" "Kontr. rodziciel." "Wł." "Wył." @@ -110,6 +91,8 @@ "Inne kraje" "Brak" "Brak" + "Bez oceny" + "Blokuj programy bez oceny" "Brak" "Duże ograniczenia" "Średnie ograniczenia" @@ -126,6 +109,7 @@ "Wpisz kod PIN, by oglądać ten kanał" "Wpisz kod PIN, by oglądać ten program" "Ten program ma ocenę %1$s. Aby go obejrzeć, podaj kod PIN." + "Ten program nie ma oceny. Aby go obejrzeć, podaj kod PIN" "Wpisz kod PIN" "Aby ustawić kontrolę rodzicielską, utwórz PIN" "Wpisz nowy PIN" @@ -139,22 +123,31 @@ "Nieprawidłowy PIN. Spróbuj ponownie" "Spróbuj ponownie. Niezgodny kod PIN" + "Wpisz kod pocztowy." + "Aplikacja Telewizja online będzie używać kodu pocztowego, by udostępniać kompletny przewodnik po programach telewizyjnych." + "Wpisz kod pocztowy" + "Nieprawidłowy kod pocztowy" "Ustawienia" "Dostosuj listę kanałów" "Wybierz kanały do przewodnika TV" "Źródła kanałów" "Dostępne są nowe kanały" "Kontrola rodzicielska" + "Przesunięcie w czasie" + "Nagrywaj podczas oglądania, by móc wstrzymywać i przewijać programy na żywo.\nOstrzeżenie: funkcja ta intensywnie korzysta z pamięci wewnętrznej, co może skrócić jej żywotność." "Licencje open source" - "Licencje open source" + "Prześlij opinię" "Wersja" "Aby oglądać ten kanał, naciśnij Prawo i wpisz kod PIN" "Aby oglądać ten program, naciśnij Prawo i wpisz kod PIN" + "Ten program nie ma oceny.\nAby go obejrzeć, naciśnij przycisk W prawo i podaj kod PIN" "Ten program ma ocenę %1$s.\nAby go oglądać, naciśnij Prawo i wpisz kod PIN" "Aby oglądać ten kanał, użyj standardowej aplikacji Telewizja na żywo." "Aby obejrzeć ten program, użyj standardowej aplikacji Telewizja na żywo." + "Ten program nie ma oceny.\nAby go obejrzeć, użyj domyślnej aplikacji do obsługi telewizji na żywo." "Ten program ma ocenę %1$s.\nAby go obejrzeć, użyj standardowej aplikacji Telewizja na żywo." "Program jest zablokowany" + "Ten program nie ma oceny" "Ten program ma ocenę %1$s." "Tylko dźwięk" "Słaby sygnał" @@ -189,8 +182,6 @@ "Naciśnij SELECT"", by otworzyć menu TV." "Nie znaleziono wejścia TV" "Nie można znaleźć wejścia TV" - "Tryb PIP nie jest obsługiwany" - "Brak sygnału wejściowego do wyświetlenia w trybie PIP" "Nieodpowiedni typ tunera. Uruchom aplikację Telewizja online na jego wejściu TV." "Strojenie nie powiodło się" "Nie znaleziono aplikacji do obsługi tego działania." @@ -199,7 +190,7 @@ "Klawisz BACK steruje podłączonym urządzeniem. Naciśnij przycisk HOME, by wyjść." "Aby odczytywać programy telewizyjne, Telewizja online potrzebuje uprawnień." "Skonfiguruj źródła" - "Telewizja online to połączenie tradycyjnych kanałów TV i przesyłanych strumieniowo przez aplikacje.\n\nAby rozpocząć, skonfiguruj zainstalowane źródła kanałów. Możesz też przejrzeć Sklep Google Play, by znaleźć więcej aplikacji, które oferują dostęp do telewizji online." + "Telewizja online to połączenie tradycyjnych kanałów TV i przesyłanych strumieniowo przez aplikacje.\n\nAby rozpocząć, skonfiguruj zainstalowane źródła kanałów. Możesz też przejrzeć Sklep Google Play, by znaleźć więcej aplikacji, które oferują dostęp do telewizji online." "Nagrywanie i harmonogramy" "10 minut" "30 minut" @@ -244,6 +235,8 @@ %1$d zaplanowanego nagrania %1$d zaplanowane nagranie + "Anuluj nagrywanie" + "Zatrzymaj nagrywanie" "Obejrzyj" "Odtwórz od początku" "Wznów odtwarzanie" @@ -278,9 +271,6 @@ "Jeśli wybierzesz za dużo programów do nagrania w tym samym czasie, nagrane zostaną tylko te o wyższych priorytetach." "Zapisz" "Nagrania jednorazowe mają najwyższy priorytet" - "Anuluj" - "Anuluj" - "Zapomnij" "Zatrzymaj" "Pokaż harmonogram nagrywania" "Tylko ten program" @@ -290,25 +280,28 @@ "Nagraj to w zamian" "Anuluj to nagrywanie" "Obejrzyj teraz" + "Usuń nagrania…" "Można nagrać" "Zaplanowano nagrywanie" "Konflikt nagrywania" "Nagrywam" "Nie udało się nagrać" - "Odczytuję programy, by utworzyć harmonogram nagrywania" - "Odczytuję programy" - - + "Odczytuję programy" + "Zobacz ostatnie nagrania" + "Nagranie %1$s jest niepełne." + "Nagrania %1$s i %2$s są niepełne." + "Nagrania %1$s, %2$s i %3$s są niepełne." + "Nie udało się dokończyć nagrania %1$s z powodu braku miejsca." + "Nie udało się dokończyć nagrań %1$s i %2$s z powodu braku miejsca." + "Nie udało się dokończyć nagrań %1$s, %2$s i %3$s z powodu braku miejsca." "Nagrywarka DVR potrzebuje więcej miejsca" - "Dzięki funkcji nagrywarki DVR możesz nagrywać programy, ale obecnie na urządzeniu jest za mało miejsca, by można było z niej korzystać. Podłącz dysk zewnętrzny o pojemności co najmniej %1$s GB i postępuj zgodnie z instrukcjami, by sformatować go jako pamięć urządzenia." + "Dzięki funkcji nagrywarki DVR możesz nagrywać programy, ale obecnie na urządzeniu jest za mało miejsca, by można było z niej korzystać. Podłącz dysk zewnętrzny o pojemności co najmniej %1$d GB i postępuj zgodnie z instrukcjami, by sformatować go jako pamięć urządzenia." + "Za mało miejsca" + "Nie można nagrać tego programu, bo brakuje miejsca. Usuń któreś z wcześniejszych nagrań." "Brak dostępu do pamięci" - "Brak dostępu do części pamięci wykorzystywanej przez DVR. Podłącz dysk zewnętrzny, którego używasz, zanim włączysz DVR ponownie. Jeśli nie masz już tego dysku zewnętrznego, możesz go zapomnieć." - "Zapomnieć pamięć nagrywarki?" - "Wszystkie zapisane treści i zaplanowane nagrania zostaną utracone." "Zatrzymać nagrywanie?" "Nagrana treść zostanie zapisana." - - + "Nagrywanie programu „%1$s” zostanie zatrzymane, bo jest w konflikcie z tym programem. Nagrane treści zostaną zachowane." "Nagrywanie zostało zaplanowane, ale wystąpiły konflikty" "Zaczęło się nagrywanie, ale występują konflikty" "Program %1$s zostanie nagrany." @@ -328,17 +321,39 @@ "Nagrywanie tego samego programu zostało już zaplanowane na %1$s." "Już nagrany" "Ten program został już nagrany. Jest dostępny w bibliotece nagrywarki DVR." - - - - - - - - + "Zaplanowano nagrywanie serialu" + + Zaplanowano %1$d nagrania serialu %2$s. + Zaplanowano %1$d nagrań serialu %2$s. + Zaplanowano %1$d nagrania serialu %2$s. + Zaplanowano %1$d nagranie serialu %2$s. + + + Zaplanowano %1$d nagrania serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków. + Zaplanowano %1$d nagrań serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków. + Zaplanowano %1$d nagrania serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków. + Zaplanowano %1$d nagranie serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać tego odcinka. + + + Zaplanowano %1$d nagrania serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków tego i innych seriali. + Zaplanowano %1$d nagrań serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków tego i innych seriali. + Zaplanowano %1$d nagrania serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków tego i innych seriali. + Zaplanowano %1$d nagranie serialu %2$s. Z powodu pokrywających się harmonogramów nie uda się nagrać %3$d odcinków tego i innych seriali. + + + Zaplanowano %1$d nagrania serialu %2$s. Przez to nie uda się nagrać 1 odcinka innego serialu. + Zaplanowano %1$d nagrań serialu %2$s. Przez to nie uda się nagrać 1 odcinka innego serialu. + Zaplanowano %1$d nagrania serialu %2$s. Przez to nie uda się nagrać 1 odcinka innego serialu. + Zaplanowano %1$d nagranie serialu %2$s. Przez to nie uda się nagrać 1 odcinka innego serialu. + + + Zaplanowano %1$d nagrania serialu %2$s. Przez to nie uda się nagrać %3$d odcinków innych seriali. + Zaplanowano %1$d nagrań serialu %2$s. Przez to nie uda się nagrać %3$d odcinków innych seriali. + Zaplanowano %1$d nagrania serialu %2$s. Przez to nie uda się nagrać %3$d odcinków innych seriali. + Zaplanowano %1$d nagranie serialu %2$s. Przez to nie uda się nagrać %3$d odcinków innych seriali. + "Nie znaleziono nagranego programu." "Powiązane nagrania" - "(Brak opisu programu)" %1$d nagrania %1$d nagrań @@ -362,6 +377,7 @@ "Zatrzymać nagrywanie cykliczne?" "Nagrane odcinki będą dostępne w bibliotece nagrywarki DVR." "Zatrzymaj" + "W tej chwili nie są nadawane żadne odcinki." "Brak dostępnych odcinków.\nZostaną one nagrane, gdy będą dostępne." (%1$d minuty) @@ -375,4 +391,5 @@ "Dzisiaj, %1$s" "Jutro, %1$s" "Ocena" + "Nagrane programy" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 45fa4e20..d1d3d09f 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -20,9 +20,8 @@ "mono" "estéreo" "Controlos de reprodução" - "Canais recentes" + "Canais" "Opções de TV" - "Opções de PIP" "Controlos de reprodução indisponíveis para este canal" "Reproduzir ou colocar em pausa" "Avançar" @@ -35,33 +34,15 @@ "Legendas" "Modo de apres." "PIP" - "Ativado" - "Desativado" "Multiáudio" "Obter mais canais" "Definições" - "Fonte" - "Alternar" - "Ativado" - "Desativado" - "Som" - "Principal" - "Janela de PIP" - "Esquema" - "Canto inf. dir." - "Canto sup. dir." - "Canto sup. esq." - "Canto inf. esq." - "Lado a lado" - "Tamanho" - "Grande" - "Pequeno" - "Fonte de entrada" "TV (antena/cabo)" "Sem informação de programação" "Sem informações" "Canal bloqueado" - "Idioma desconhecido" + "Idioma desconhecido" + "Legendas %1$d" "Legendas" "Desativado" "Person. a formatação" @@ -83,16 +64,16 @@ "SD" "Agrupar por" "Este programa está bloqueado" + "Este programa não tem classificação." "Este programa tem a classificação %1$s" "A entrada não suporta procura automática" "Não é possível iniciar a verificação automática para \"%s\"" "Não é possível iniciar as preferências do sistema para as legendas." - %1$d canais adicionados %1$d canal adicionado + %1$d canais adicionados "Nenhum canal adicionado" - "Sintonizador" "Controlo parental" "Ativado" "Desativado" @@ -108,6 +89,8 @@ "Outros países" "Nenhum" "Nenhum" + "Sem classificação" + "Bloquear programas s/ classif." "Nenhum" "Restrições elevadas" "Restrições médias" @@ -124,48 +107,58 @@ "Introduzir o PIN para ver este canal" "Introduzir o PIN para ver este programa" "Este programa tem a classificação %1$s. Introduza o PIN para ver este programa" + "Este programa não tem classificação. Introduza o PIN para ver este programa." "Introduzir o PIN" "Para definir os controlos parentais, crie um PIN" "Introduzir o novo PIN" "Confirmar o PIN" "Introduzir o PIN atual" - Introduziu 5 vezes o PIN incorreto.\nTente novamente dentro de %1$d segundos. Introduziu 5 vezes o PIN incorreto.\nTente novamente dentro de %1$d segundo. + Introduziu 5 vezes o PIN incorreto.\nTente novamente dentro de %1$d segundos. "Esse PIN estava errado. Tente novamente." "Tente novamente, o PIN não corresponde" + "Introduza o seu código postal." + "A aplicação Canais em Direto utiliza o código postal para disponibilizar um guia de programação completo para os canais de TV." + "Introduza o seu código postal" + "Código postal inválido" "Definições" "Personalizar lista de canais" "Escolher canais para o guia de programação" "Fontes dos canais" "Novos canais disponíveis" "Controlo parental" + "Controlo da reprodução" + "Grave enquanto está a ver para poder colocar em pausa ou recuar programas em direto.\nAviso: isto pode reduzir a vida útil da memória de armazenamento interno devido à utilização intensiva da mesma." "Licenças de código aberto" - "Licenças de código aberto" + "Enviar comentários" "Versão" "Para ver este canal, prima Direito e introduza o PIN" "Para ver este programa, prima Direito e introduza o PIN" + "Este programa não tem classificação.\nPara ver este programa, prima Para a direita e introduza o PIN." "Este programa tem a classificação %1$s.\nPara ver este programa, prima Direito e introduza o PIN." "Para ver este canal, utilize a aplicação TV em direto predefinida." "Para ver este programa, utilize a aplicação TV em direto predefinida." + "Este programa não tem classificação.\nPara ver este programa, utilize a aplicação de TV em direto predefinida." "Este programa tem a classificação %1$s.\nPara o ver, utilize a aplicação TV em direto predefinida." "O programa está bloqueado" + "Este programa não tem classificação." "Este programa tem a classificação %1$s" "Apenas áudio" "Sinal fraco" "Sem ligação à Internet" - Não é possível reproduzir este canal até à(s) %1$s porque estão a ser gravados outros canais. \n\nPrima para a direita para ajustar o horário de gravação. Não é possível reproduzir este canal até à(s) %1$s porque está a ser gravado outro canal. \n\nPrima para a direita para ajustar o horário de gravação. + Não é possível reproduzir este canal até à(s) %1$s porque estão a ser gravados outros canais. \n\nPrima para a direita para ajustar o horário de gravação. "Sem título" "Canal bloqueado" "Nova" "Fontes" - %1$d canais %1$d canal + %1$d canais "Nenhum canal disponível" "Nova" @@ -181,8 +174,6 @@ "Prima SELECT"" para aceder ao menu da TV." "Nenhuma entrada de TV encontrada" "Não é possível localizar a entrada de TV" - "Não é suportada a opção PIP" - "Nenhuma entrada disponível para apresentar com PIP" "Tipo de sintonizador inadequado. Inicie a aplicação Canais em direito para a entrada de TV do tipo de sintonizador." "Falha ao sintonizar" "Não foram encontradas aplicações para executar esta ação." @@ -207,25 +198,27 @@ "A gravar %1$s desde agora até às %2$s…" "Agenda completa" - Próximos %1$d dias Próximo dia + Próximos %1$d dias - %1$d minutos %1$d minuto + %1$d minutos - %1$d novas gravações %1$d nova gravação + %1$d novas gravações - %1$d gravações %1$d gravação + %1$d gravações - %1$d gravações agendadas %1$d gravação agendada + %1$d gravações agendadas + "Cancelar gravação" + "Parar gravação" "Ver" "Reproduzir do início" "Retomar a reprodução" @@ -245,8 +238,8 @@ "%1$d de %2$d segundos vistos" "Nunca vistas" - %1$d de %2$d episódios eliminados %1$d de %2$d episódio eliminado + %1$d de %2$d episódios eliminados "Prioridade" "Mais alta" @@ -258,9 +251,6 @@ "Quando existem demasiados programas para serem gravados em simultâneo, apenas são gravados os programas com as prioridades mais altas." "Guardar" "As gravações únicas têm a prioridade mais alta" - "Cancelar" - "Cancelar" - "Esquecer" "Parar" "Ver horários de gravação" "Este programa único" @@ -270,25 +260,28 @@ "Gravar antes este" "Cancelar esta gravação" "Ver agora" + "Eliminar gravações" "Gravável" "Gravação agendada" "Conflito de gravação" "A gravar" "Falha na gravação" - "A ler os programas para criar horários de gravação…" - "A ler os programas…" - - + "A ler os programas…" + "Ver gravações recentes" + "A gravação de %1$s está incompleta." + "As gravações de %1$s e %2$s estão incompletas." + "As gravações de %1$s, %2$s e %3$s estão incompletas." + "A gravação de %1$s não foi concluída devido a armazenamento insuficiente." + "As gravações de %1$s e %2$s não foram concluídas devido a armazenamento insuficiente." + "As gravações de %1$s, %2$s e %3$s não foram concluídas devido a armazenamento insuficiente." "O DVR necessita de mais armazenamento" - "Pode gravar programas com o DVR. Contudo, não existe neste momento armazenamento suficiente no dispositivo para que o DVR funcione. Ligue uma unidade externa que tenha, pelo menos, %1$s GB e siga os passos para a formatar como armazenamento do dispositivo." + "Pode gravar programas com o DVR. Contudo, não existe neste momento armazenamento suficiente no dispositivo para que o DVR funcione. Ligue uma unidade externa que tenha, pelo menos, %1$d GB e siga os passos para a formatar como armazenamento do dispositivo." + "Sem armazenamento suficiente" + "Este programa não será gravado porque não existe armazenamento suficiente. Experimente eliminar algumas gravações existentes." "Armazenamento em falta" - "Algum do armazenamento utilizado pelo DVR está em falta. Ligue a unidade externa que utilizou anteriormente para reativar o DVR. Em alternativa, pode optar por esquecer o armazenamento se este já não estiver disponível." - "Pretende esquecer o armazenamento?" - "Todos os seus conteúdos e agendamentos gravados serão perdidos." "Pretende parar a gravação?" "O conteúdo gravado será guardado." - - + "A gravação de %1$s será interrompida devido a conflitos com este programa. O conteúdo gravado será guardado." "Gravação agendada, mas com conflitos" "A gravação foi iniciada, mas tem conflitos" "O programa %1$s será gravado." @@ -297,8 +290,8 @@ "Algumas partes de %1$s e de %2$s não serão gravadas." "Algumas partes de %1$s, de %2$s e de mais um horário não serão gravadas." - Algumas partes de %1$s, de %2$s e de mais %3$d horários não serão gravadas. Algumas partes de %1$s, de %2$s e de mais %3$d horário não serão gravadas. + Algumas partes de %1$s, de %2$s e de mais %3$d horários não serão gravadas. "O que gostaria de gravar?" "Durante quanto tempo pretende gravar?" @@ -306,20 +299,32 @@ "O mesmo programa já foi agendado para ser gravado às %1$s." "Já gravado" "Este programa já foi gravado. Está disponível na biblioteca do DVR." - - - - - - - - + "Gravação da série agendada" + + Foi agendada %1$d gravação para %2$s. + %1$d gravações para %2$s. + + + Foi agendada %1$d gravação para %2$s. Esta não será gravada devido a conflitos. + %1$d gravações para %2$s. %3$d destas não serão gravadas devido a conflitos. + + + Foi agendada %1$d gravação para %2$s. %3$d episódios desta e de outras séries não serão gravados devido a conflitos. + %1$d gravações para %2$s. %3$d episódios desta e de outras séries não serão gravados devido a conflitos. + + + Foi agendada %1$d gravação para %2$s. Não será gravado 1 episódio de outra série devido a conflitos. + Foram agendadas %1$d gravações para %2$s. Não será gravado 1 episódio de outra série devido a conflitos. + + + Foi agendada %1$d gravação para %2$s. Não serão gravados %3$d episódios de outras séries devido a conflitos. + %1$d gravações para %2$s. Não serão gravados %3$d episódios de outras séries devido a conflitos. + "Programa gravado não encontrado." "Gravações relacionadas" - "(Sem descrição do programa)" - %1$d gravações %1$d gravação + %1$d gravações " / " "%1$s removido da agenda de gravação" @@ -327,8 +332,8 @@ "Não será gravado devido a conflitos de sintonizador." "Ainda não existem gravações agendadas.\nPode agendar a gravação a partir do guia de programação." - %1$d conflitos de gravação %1$d conflito de gravação + %1$d conflitos de gravação "Definições de séries" "Iniciar gravação da série" @@ -336,10 +341,11 @@ "Pretende parar a gravação da série?" "Os episódios gravados ficam disponíveis na biblioteca do DVR." "Parar" + "Não estão a ser transmitidos episódios em direto." "Não existem episódios disponíveis.\nVão ser gravados assim que estiverem disponíveis." - (%1$d minutos) (%1$d minuto) + (%1$d minutos) "Hoje" "Amanhã" @@ -347,4 +353,5 @@ "%1$s de hoje" "%1$s de amanhã" "Pontuação" + "Programas gravados" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 9c22a58a..9a8abe2e 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -20,9 +20,8 @@ "mono" "estéreo" "Controles de reprodução" - "Canais recentes" + "Canais" "Opções da TV" - "Opções de PIP" "Controles de reprodução indisponíveis para este canal" "Reproduzir ou pausar" "Avançar" @@ -33,47 +32,29 @@ "Novos canais disponíveis" "Abrir %1$s" "Closed captions" - "Modo de exibiç." + "Modo de exibição" "PIP" - "Ativado" - "Desativado" - "Múltip. áudios" - "Adquirir mais canais" - "Config." - "Origem" - "Trocar" - "Ativadas" - "Desativadas" - "Som" - "Principal" - "Janela de PIP" - "Layout" - "Inferior direito" - "Superior direito" - "Superior esquerdo" - "Inferior esquerdo" - "Lado a lado" - "Tamanho" - "Grande" - "Pequeno" - "Origem da entrada" + "Áudios múltiplos" + "Mais canais" + "Configurações" "TV (antena/a cabo)" "Nenhuma informação sobre o programa" "Nenhuma informação" "Canal bloqueado" - "Idioma desconhecido" + "Idioma desconhecido" + "Closed captions %1$d" "Closed captions" "Desativado" - "Person. formatação" - "Definir preferências gerais do sistema para closed captions" + "Personalizar formatação" + "Defina as preferências gerais do sistema para closed captions" "Modo de exibição" - "Múltiplos áudios" + "Áudios múltiplos" "mono" "estéreo" "Surround 5.1" "Surround 7.1" "%d canais" - "Pers. lista canais" + "Personalizar lista de canais" "Selecionar grupo" "Desmarcar grupo" "Agrupar por" @@ -83,6 +64,7 @@ "Padrão" "Agrupar por" "Este programa está bloqueado" + "Este programa não tem classificação" "Este programa foi classificado como %1$s" "A entrada não suporta verificação automática" "Não foi possível iniciar a verificação automática de \"%s\"" @@ -92,7 +74,6 @@ %1$d canais adicionados "Nenhum canal adicionado" - "Sintonizador" "Contr. p/ os pais" "Ativado" "Desativado" @@ -108,6 +89,8 @@ "Outros países" "Nenhum" "Nenhum" + "Sem classificação" + "Bloquear programas sem classificação" "Nenhum" "Restrições altas" "Restrições médias" @@ -124,6 +107,7 @@ "Digite seu PIN para assistir a este canal" "Digite seu PIN para assistir a este programa" "Este programa foi classificado como %1$s. Digite seu PIN para assisti-lo" + "Este programa não tem classificação. Digite seu PIN para assisti-lo" "Informe seu PIN" "Para definir os controles para os pais, crie um PIN." "Inserir novo PIN" @@ -135,22 +119,31 @@ "O PIN estava errado. Tente novamente." "Tente novamente, o PIN não corresponde" - "Config." + "Digite seu CEP." + "O app Canais ao vivo usará o CEP para fornecer um guia completo da programação para os canais de TV." + "Digite seu CEP" + "CEP inválido" + "Configurações" "Personalizar lista de canais" - "Escolher canais para guia de programação" - "Fontes do canal" + "Escolha canais para o guia de programação" + "Fontes de canais" "Novos canais disponíveis" "Controle dos pais" + "Diferença de tempo" + "Grave enquanto assiste para poder pausar ou retroceder programas ao vivo.\nAviso: isso pode diminuir a vida útil do armazenamento interno devido ao uso intenso do armazenamento." "Licenças de código aberto" - "Licenças de código aberto" + "Enviar feedback" "Versão" "Para assistir a este canal, pressione para a direita e digite o PIN" "Para assistir a este programa, pressione para a direita e digite o PIN" + "Este programa não tem classificação.\nPressione o botão direito e digite seu PIN para assisti-lo" "Este programa foi classificado como %1$s.\nPara assistir a este programa, pressione para a direita e digite o PIN." "Para assistir este canal, use o app padrão de TV ao vivo." "Para assistir este programa, use o app padrão de TV ao vivo." + "Este programa não tem classificação.\nUse o app padrão de TV ao vivo para assisti-lo." "Este programa foi classificado como %1$s.\nPara assistir este programa, use o app padrão de TV ao vivo." "Programa bloqueado" + "Este programa não tem classificação" "Este programa foi classificado como %1$s" "Somente áudio" "Sinal fraco" @@ -170,8 +163,8 @@ "Nenhum canal disponível" "Novas" "Não configurada" - "Adquirir mais fontes" - "Procurar apps que ofereçam canais ao vivo" + "Adquira mais fontes" + "Procure apps que ofereçam canais ao vivo" "Novas fontes de canais disponíveis" "Novas fontes de canais têm canais para oferecer.\nConfigure-as agora ou faça isso mais tarde nas configurações de fontes de canais." "Configurar agora" @@ -181,8 +174,6 @@ "Pressione \"SELECIONAR\""" para acessar o menu da TV." "Nenhuma entrada de TV encontrada" "Não foi possível encontrar a entrada de TV" - "PIP não é suportado" - "Não há entrada disponível que possa ser mostrada com PIP" "Tipo de sintonizador não adequado. Inicie o app \"Canais ao vivo\" para abrir o tipo de sintonizador para entrada de TV." "Falha na sintonia" "Nenhum app foi encontrado para executar esta ação." @@ -190,7 +181,7 @@ "O vídeo está inesperadamente indisponível" "A tecla VOLTAR é para dispositivos conectados. Pressione o botão INÍCIO para sair." "O app \"Canais ao vivo\" precisa de permissão para ler a programação de TV." - "Configurar suas fontes" + "Configure suas fontes" "Os canais ao vivo combinam a experiência dos canais de TV tradicionais com canais de streaming fornecidos por apps. \n\nComece configurando as fontes de canais já instaladas ou procure na Google Play Store mais apps que ofereçam canais ao vivo." "Gravações e programações" "10 minutos" @@ -226,6 +217,8 @@ %1$d gravação programada %1$d gravações programadas + "Cancelar gravação" + "Interromper gravação" "Assistir" "Reproduzir do início" "Retomar reprodução" @@ -258,9 +251,6 @@ "Quando houver muitos programas a serem gravados ao mesmo tempo, apenas os com maior prioridade serão gravados." "Salvar" "Gravações únicas têm a maior prioridade" - "Cancelar" - "Cancelar" - "Ignorar" "Parar" "Ver programação de gravação" "Este único programa" @@ -270,25 +260,28 @@ "Gravar este, e não o outro" "Cancelar esta gravação" "Assistir agora" + "Excluir gravações…" "Pode ser gravado" "Gravação programada" "Conflito de gravação" "Gravação" "Falha na gravação" - "Lendo programas para criar programações de gravação" - "Lendo programas" - - + "Lendo programas" + "Ver gravações recentes" + "A gravação de %1$s está incompleta." + "As gravações de %1$s e %2$s estão incompletas." + "As gravações de %1$s, %2$s e %3$s estão incompletas." + "A gravação de %1$s não foi concluída devido à falta de espaço de armazenamento." + "As gravações de %1$s e %2$s não foram concluídas devido à falta de espaço de armazenamento." + "As gravações de %1$s, %2$s e %3$s não foram concluídas devido à falta de espaço de armazenamento." "O DVR precisa de mais armazenamento" - "Você poderá gravar programas com DVR. No entanto, não há espaço de armazenamento suficiente no seu dispositivo no momento para que o DVR funcione. Conecte um drive externo que tenha %1$s GB ou mais e siga as etapas para formatá-lo como um armazenamento do dispositivo." + "Você poderá gravar programas com DVR. No entanto, não há espaço de armazenamento suficiente no seu dispositivo no momento para que o DVR funcione. Conecte um drive externo que tenha %1$d GB ou mais e siga as etapas para formatá-lo como um armazenamento do dispositivo." + "Armazenamento insuficiente" + "Este programa não será gravado porque não há espaço de armazenamento suficiente. Tente excluir algumas gravações já existentes." "Armazenamento ausente" - "Alguns dos armazenamentos usados por DVR estão ausentes. Conecte o drive externo usado antes de reativar o DVR. Também é possível optar por esquecer o armazenamento se ele não estiver mais disponível." - "Esquecer armazenamento?" - "Todo o conteúdo gravado e programações serão perdidos." "Interromper gravação?" "O conteúdo gravado será salvo." - - + "A gravação de %1$s será interrompida porque ela tem um conflito com esse programa. O conteúdo gravado será salvo." "Gravação programada, mas há conflitos" "A gravação começou, mas há conflitos" "O programa %1$s será gravado." @@ -306,17 +299,29 @@ "A gravação do mesmo programa já foi programada para %1$s." "Já gravado" "Este programa já foi gravado. Ele está disponível na biblioteca de DVR." - - - - - - - - + "Gravação de série programada" + + Foi programada %1$d gravação de %2$s. + Foram programadas %1$d gravações de %2$s. + + + Foi programada %1$d gravação de %2$s. Ao todo, %3$d delas não serão gravadas devido a conflitos. + Foram programadas %1$d gravações de %2$s. Ao todo, %3$d delas não serão gravadas devido a conflitos. + + + Foi programada %1$d gravação de %2$s. Ao todo, %3$d episódios dessa e de outras séries não serão gravados devido a conflitos. + Foram programadas %1$d gravações de %2$s. Ao todo, %3$d episódios dessa e de outras séries não serão gravados devido a conflitos. + + + Foi programada %1$d gravação de %2$s. Ao todo, 1 episódio de outras séries não será gravado devido a conflitos. + Foram programadas %1$d gravações de %2$s. Ao todo, 1 episódio de outras séries não será gravado devido a conflitos. + + + Foi programada %1$d gravação de %2$s. Ao todo, %3$d episódios de outras séries não serão gravados devido a conflitos. + Foram programadas %1$d gravações de %2$s. Ao todo, %3$d episódios de outras séries não serão gravados devido a conflitos. + "Programa gravado não encontrado." "Gravações relacionadas" - "Nenhuma descrição do programa" %1$d gravação %1$d gravações @@ -336,6 +341,7 @@ "Parar gravação da série?" "Os episódios gravados permanecerão disponíveis na biblioteca de DVR." "Parar" + "Nenhum episódio está sendo transmitido no momento." "Nenhum episódio disponível.\nOs episódios serão gravados quando estiverem disponíveis." (%1$d minuto) @@ -347,4 +353,5 @@ "%1$s hoje" "%1$s amanhã" "Pontuação" + "Programas gravados" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index adc3b359..1fe9e1e9 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Comenzi de redare" - "Canale recente" + "Canale" "Opțiuni TV" - "Opțiuni PIP" "Comenzile de redare nu sunt disponibile pentru acest canal" "Redați sau întrerupeți redarea" "Derulați rapid înainte" @@ -35,33 +34,15 @@ "Subtitrări" "Mod de afișare" "PIP" - "Activat" - "Dezactivat" "Multi-audio" "Obțineți mai multe canale" "Setări" - "Sursă" - "Schimbați" - "Activat" - "Dezactivat" - "Sunet" - "Principală" - "Fereastră PIP" - "Aspect" - "Dreapta jos" - "Dreapta sus" - "Stânga sus" - "Stânga jos" - "Alăturat" - "Dimensiuni" - "Mare" - "Mic" - "Sursă de intrare" "TV (antenă/cablu)" "Nu există informații despre program" "Nicio informație" "Canal blocat" - "Limbă necunoscută" + "Limbă necunoscută" + "Subtitrări %1$d" "Subtitrări" "Dezactivat" "Personaliz. format." @@ -83,6 +64,7 @@ "SD" "Grupați după" "Acest program este blocat" + "Acest program nu este evaluat" "Acest program este clasificat ca %1$s" "Intrarea nu acceptă scanarea automată" "Nu poate fi inițiată scanarea automată pentru „%s”" @@ -93,7 +75,6 @@ %1$d canal adăugat "Niciun canal adăugat" - "Tuner" "Control parental" "Activat" "Dezactivat" @@ -109,6 +90,8 @@ "Alte țări" "Niciunul" "Niciunul" + "Neevaluat" + "Blocați programele neevaluate" "Niciuna" "Restricții majore" "Restricții medii" @@ -125,6 +108,7 @@ "Introduceți codul PIN pentru a viziona acest canal" "Introduceți codul PIN pt. a viziona acest program" "Acest program este evaluat cu %1$s. Pentru a viziona programul, introduceți codul PIN" + "Acest program nu este evaluat. Introduceți codul PIN pentru a-l viziona." "Introduceți codul PIN" "Pentru a seta controlul parental, creați un cod PIN" "Introduceți un nou cod PIN" @@ -137,22 +121,31 @@ "Codul PIN a fost greșit. Încercați din nou." "Încercați din nou. Codul PIN nu se potrivește." + "Introduceți codul poștal" + "Aplicația Canale live va folosi codul poștal pentru a vă oferi un ghid de programe complet pentru canalele TV." + "Introduceți codul poștal" + "Cod poștal nevalid" "Setări" "Personalizați lista de canale" "Alegeți canale pentru ghidul de programe" "Sursele canalelor" "Noi canale disponibile" "Control parental" + "Decalare temporală" + "Înregistrați în timpul vizionării, ca să puteți întrerupe sau derula înapoi programele live.\nAvertisment: astfel, se utilizează intensiv memoria internă și i se poate reduce durata de viață." "Licențe open source" - "Licențe open source" + "Trimiteți feedback" "Versiune" "Pentru a viziona acest canal, apăsați la dreapta și introduceți codul PIN" "Pentru a viziona acest program, apăsați la dreapta și introduceți codul PIN" + "Acest program nu este evaluat.\nPentru a-l viziona, apăsați pe Drept și introduceți codul PIN." "Acest program este clasificat ca %1$s.\nPentru a viziona acest program, apăsați pe săgeata spre dreapta și introduceți codul PIN." "Pentru a viziona acest canal, folosiți aplicația prestabilită pentru programe TV live." "Pentru a viziona acest program, folosiți aplicația prestabilită pentru programe TV live." + "Acest program nu este evaluat.\nPentru a-l viziona, utilizați aplicația prestabilită TV live." "Acest program este evaluat cu %1$s.\nPentru a viziona acest program, folosiți aplicația prestabilită pentru programe TV live." "Programul este blocat" + "Acest program nu este evaluat" "Acest program este clasificat ca %1$s" "Numai conținut audio" "Semnal slab" @@ -185,8 +178,6 @@ "Apăsați pe SELECTAȚI"" pentru a accesa meniul TV." "Nu s-a găsit nicio intrare TV" "Nu se poate găsi intrarea TV" - "Funcția PIP nu este acceptată" - "Nu există intrări disponibile pentru afișarea cu PIP" "Tipul tuner nu este corespunzător. Lansați aplicația Canale live pentru intrarea TV tip tuner." "Eroare la optimizare" "Nu s-a găsit o aplicație care să îndeplinească această acțiune." @@ -235,6 +226,8 @@ %1$d de înregistrări programate %1$d înregistrare programată + "Anulați înregistrarea" + "Opriți înregistrarea" "Vizionați" "Redați de la început" "Reluați redarea" @@ -268,9 +261,6 @@ "Când există prea multe programe de înregistrat în același timp, vor fi înregistrate numai cele cu priorități mai mari." "Salvați" "Înregistrările unice au cea mai mare prioritate" - "Anulați" - "Anulați" - "Eliminați" "Opriți" "Vedeți programul de înregistrare" "Numai acest program" @@ -280,25 +270,28 @@ "Înregistrați acest program" "Anulați această înregistrare" "Vedeți acum" + "Ștergeți înregistrări…" "Se poate înregistra" "Înregistrare programată" "Conflict privind înregistrarea" "Se înregistrează" "Nu s-a înregistrat" - "Se citesc programele pentru crearea programărilor de înregistrare" - "Se citesc programele" - - + "Se citesc programele" + "Vedeți înregistrările recente" + "Înregistrarea pentru %1$s nu este finalizată." + "Înregistrările pentru %1$s și %2$s nu sunt finalizate." + "Înregistrările pentru %1$s, %2$s și %3$s nu sunt finalizate." + "Înregistrarea pentru %1$s nu s-a finalizat, din cauza spațiului de stocare insuficient." + "Înregistrările pentru %1$s și %2$s nu s-au finalizat, din cauza spațiului de stocare insuficient." + "Înregistrările pentru %1$s, %2$s și %3$s nu s-au finalizat, din cauza spațiului de stocare insuficient." "DVR are nevoie de mai mult spațiu de stocare" - "Veți putea înregistra programe folosind DVR. Cu toate acestea, momentan, pe dispozitiv nu există suficient spațiu de stocare ca să funcționeze DVR-ul. Conectați o unitate externă de cel puțin %1$s GB și urmați pașii pentru a o formata ca stocare pe dispozitiv." + "Veți putea înregistra programe folosind DVR. Cu toate acestea, momentan, pe dispozitiv nu există suficient spațiu de stocare ca să funcționeze DVR-ul. Conectați o unitate externă de cel puțin %1$d GB și urmați pașii pentru a o formata ca stocare pe dispozitiv." + "Nu există suficient spațiu de stocare" + "Programul nu va fi înregistrat, deoarece nu există suficient spațiu de stocare. Ștergeți unele înregistrări existente." "Stocare lipsă" - "O parte din stocarea folosită de DVR lipsește. Pentru a reactiva DVR, conectați unitatea externă folosită anterior. Dacă stocarea externă nu mai este disponibilă, puteți să o eliminați." - "Eliminați stocarea?" - "Tot conținutul înregistrat și toate programările vor fi șterse." "Opriți înregistrarea?" "Conținutul înregistrat va fi salvat." - - + "Înregistrarea pentru %1$s va fi oprită, deoarece există conflicte cu acest program. Conținutul înregistrat va fi salvat." "Înregistrarea a fost programată, dar există conflicte" "Înregistrarea a început, dar există conflicte" "%1$s va fi înregistrat." @@ -317,17 +310,34 @@ "Același program a fost programat deja pentru înregistrare la %1$s." "Înregistrat deja" "Acest program a fost înregistrat deja. Este disponibil în biblioteca DVR." - - - - - - - - + "Înregistrarea serialului a fost programată" + + %1$d înregistrări au fost programate pentru %2$s. + %1$d de înregistrări au fost programate pentru %2$s. + %1$d înregistrare a fost programată pentru %2$s. + + + %1$d înregistrări au fost programate pentru %2$s. %3$d dintre acestea nu vor fi înregistrate din cauza unor conflicte. + %1$d de înregistrări au fost programate pentru %2$s. %3$d dintre acestea nu vor fi înregistrate din cauza unor conflicte. + %1$d înregistrare a fost programată pentru %2$s. Nu va fi înregistrată din cauza unor conflicte. + + + %1$d înregistrări au fost programate pentru %2$s. %3$d episoade din acest serial și din alte seriale nu vor fi înregistrate din cauza unor conflicte. + %1$d de înregistrări au fost programate pentru %2$s. %3$d episoade din acest serial și din alte seriale nu vor fi înregistrate din cauza unor conflicte. + %1$d înregistrare a fost programată pentru %2$s. %3$d episoade din acest serial și din alte seriale nu vor fi înregistrate din cauza unor conflicte. + + + %1$d înregistrări au fost programate pentru %2$s. Un episod din alte seriale nu va fi înregistrat din cauza unor conflicte. + %1$d de înregistrări au fost programate pentru %2$s. Un episod din alte seriale nu va fi înregistrat din cauza unor conflicte. + %1$d înregistrare a fost programată pentru %2$s. Un episod din alte seriale nu va fi înregistrat din cauza unor conflicte. + + + %1$d înregistrări au fost programate pentru %2$s. %3$d episoade din alte seriale nu vor fi înregistrate din cauza unor conflicte. + %1$d de înregistrări au fost programate pentru %2$s. %3$d episoade din alte seriale nu vor fi înregistrate din cauza unor conflicte. + %1$d înregistrare a fost programată pentru %2$s. %3$d episoade din alte seriale nu vor fi înregistrate din cauza unor conflicte. + "Programul înregistrat nu a fost găsit." "Înregistrări conexe" - "(Nicio descriere de program)" %1$d înregistrări %1$d de înregistrări @@ -349,6 +359,7 @@ "Opriți înregistrarea seriei?" "Episoadele înregistrate vor rămâne disponibile în biblioteca DVR." "Opriți" + "Acum nu este difuzat niciun episod." "Nu există episoade disponibile.\nAcestea vor fi înregistrate de îndată ce vor fi disponibile." (%1$d minute) @@ -361,4 +372,5 @@ "%1$s, astăzi" "%1$s, mâine" "Scor" + "Programe înregistrate" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 61bef1f6..3d2e8ced 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -20,9 +20,8 @@ "Моно" "Стерео" "Управление" - "Недавние каналы" + "Каналы" "Настройки ТВ" - "Настройки PIP" "Команды управления недоступны для этого канала" "Воспроизведение/пауза" "Перемотать вперед" @@ -35,33 +34,15 @@ "Субтитры" "Режим" "Кадр в кадре" - "Вкл." - "Выкл." "Многоканальный" "Ещё каналы" "Настройки" - "Источник" - "Поменять" - "Вкл." - "Выкл." - "Звук" - "Главное окно" - "Кадр в кадре" - "Расположение" - "Справа внизу" - "Справа вверху" - "Слева вверху" - "Слева внизу" - "Рядом" - "Размер" - "Крупный" - "Мелкий" - "Источник" "ТВ (антенна/кабель)" "Нет информации о программах" "Неизвестно" "Заблокированный канал" - "Неизвестный язык" + "Неизвестный язык" + "Субтитры (%1$d)" "Субтитры" "Выкл." "Настройка субтитров" @@ -83,6 +64,7 @@ "SD" "Группировать по" "Эта программа заблокирована" + "Для программы не заданы возрастные ограничения" "Возрастное ограничение этой программы: %1$s" "Этот вход не поддерживает автоматическое сканирование" "Не удалось начать автосканирование для входа \"%s\"" @@ -94,7 +76,6 @@ Добавлено %1$d канала "Нет каналов" - "Тюнер" "Родительский контроль" "Вкл." "Выкл." @@ -110,6 +91,8 @@ "Другие страны" "Нет" "Без рейтинга" + "Без классификации" + "Блокировать программы без классификации" "Без рейтинга" "Строгие ограничения" "Средние ограничения" @@ -126,6 +109,7 @@ "Введите PIN-код, чтобы посмотреть этот канал" "Введите PIN-код, чтобы посмотреть эту программу" "Возрастное ограничение для этой программы: %1$s. Чтобы посмотреть ее, введите PIN-код." + "Для этой программы не указаны возрастные ограничения. Чтобы посмотреть ее, введите PIN-код." "Введите PIN-код" "Чтобы установить родительский контроль, создайте PIN-код" "Введите новый PIN-код" @@ -139,22 +123,31 @@ "Неверный PIN-код. Повторите попытку." "PIN-коды не совпадают. Повторите попытку." + "Введите почтовый индекс" + "Приложение \"Прямой эфир\" использует почтовый индекс, чтобы создавать телегид специально для вас." + "Введите почтовый индекс" + "Недопустимый почтовый индекс." "Настройки" "Настроить список" "Выбрать каналы для телегида" "Источники каналов" "Доступны новые каналы" "Родительский контроль" + "Сдвиг времени" + "Записывайте прямые трансляции, чтобы потом пересматривать интересные моменты.\nОбратите внимание! Когда включена эта функция, интенсивно используется внутренняя память устройства. В результате срок ее службы может сократиться." "Лицензии открытого ПО" - "Лицензии открытого ПО" + "Отправить отзыв" "Версия" "Чтобы смотреть этот канал, нажмите стрелку вправо и введите PIN-код." "Чтобы смотреть эту программу, нажмите стрелку вправо и введите PIN-код." + "Для программы не заданы возрастные ограничения.\nЧтобы посмотреть ее, нажмите на стрелку вправо и введите PIN-код." "Возрастное ограничение этой программы: %1$s.\nЧтобы посмотреть ее, нажмите на стрелку вправо и введите PIN-код." "Чтобы смотреть этот канал, используйте приложение для просмотра телепрограмм по умолчанию." "Чтобы посмотреть эту программу, используйте приложение для просмотра телепрограмм по умолчанию." + "Для программы не заданы возрастные ограничения.\nЧтобы посмотреть ее, используйте приложение для просмотра телепрограмм, установленное по умолчанию." "Возрастное ограничение для этой программы: %1$s.\nЧтобы посмотреть ее, используйте приложение для просмотра телепрограмм по умолчанию." "Программа заблокирована" + "Для программы не заданы возрастные ограничения" "Возрастное ограничение этой программы: %1$s" "Только аудио" "Слабый сигнал" @@ -189,8 +182,6 @@ "Нажмите кнопку \"ВЫБРАТЬ\""", чтобы открыть меню телевизора." "ТВ-вход не найден" "Не удается найти ТВ-вход" - "PIP не поддерживается" - "Нет источника для режима PIP" "Для ТВ-входа типа \"тюнер\" используйте приложение \"Прямой эфир\"" "Не удалось выполнить настройку" "Действие не поддерживается ни в одном приложении." @@ -244,6 +235,8 @@ %1$d запланированных записей %1$d запланированной записи + "Отменить запись" + "Остановить запись" "Смотреть" "Смотреть с начала" "Продолжить просмотр" @@ -278,9 +271,6 @@ "Если вы выберете слишком много программ для одновременной записи, будут записываться только те, у которых наивысший приоритет." "Сохранить" "Однократная запись имеет самый высокий приоритет" - "Отмена" - "Отмена" - "Удалить" "Остановить" "Смотреть расписание записи" "Только эту серию" @@ -290,25 +280,28 @@ "Записать эту программу" "Отменить эту запись" "Смотреть" + "Удалить записи…" "Можно записать" "Таймер записи установлен" "Конфликт таймера записи" "Идет запись" "Ошибка записи видео" - "Выполняется чтение программ. Будет создано расписание записи." - "Выполняется чтение программ…" - - + "Выполняется чтение программ…" + "Недавние записи" + "Не удалось завершить запись \"%1$s\"." + "Не удалось завершить записи \"%1$s\" и \"%2$s\"." + "Не удалось завершить записи \"%1$s\", \"%2$s\" и \"%3$s\"." + "Не удалось завершить запись \"%1$s\" из-за нехватки места." + "Не удалось завершить записи \"%1$s\" и \"%2$s\" из-за нехватки места." + "Не удалось завершить записи \"%1$s\", \"%2$s\" и \"%3$s\" из-за нехватки места." "Недостаточно места на устройстве" - "Вы сможете записывать программы на DVR, однако в настоящее время на вашем устройстве недостаточно места. Подключите внешний накопитель объемом не менее %1$s ГБ и отформатируйте его как память устройства." + "Вы сможете записывать программы на DVR, однако в настоящее время на вашем устройстве недостаточно места. Подключите внешний накопитель объемом не менее %1$d ГБ и отформатируйте его как память устройства." + "Недостаточно места" + "Недостаточно места для сохранения данных. Попробуйте удалить несколько ненужных записей." "Хранилище отсутствует" - "Хранилище не найдено. Подсоедините внешний диск, прежде чем снова включить видеомагнитофон, либо удалите хранилище, если оно недоступно." - "Удалить хранилище?" - "Все созданные и запланированные записи будут стерты." "Остановить запись?" "Записанный контент будет сохранен." - - + "Запись сериала \"%1$s\" будет остановлена из-за конфликта в расписании. Записанный контент сохранится." "Возник конфликт в расписании записи" "Возник конфликт в расписании записи" "Программа \"%1$s\" будет записана." @@ -328,17 +321,39 @@ "Запись этой программы уже запланирована на %1$s." "Программа уже записана" "Эта программа сохранена в библиотеке видеорекордера." - - - - - - - - + "Запись запланирована" + + Запланирована %1$d запись сериала \"%2$s\". + Запланировано %1$d записи сериала \"%2$s\". + Запланировано %1$d записей сериала \"%2$s\". + Запланировано %1$d записи сериала \"%2$s\". + + + Запланирована %1$d запись сериала \"%2$s\". Несколько серий (%3$d) этого сериала не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) этого сериала не будут записаны из-за конфликта в расписании. + Запланировано %1$d записей сериала \"%2$s\". Несколько серий (%3$d) этого сериала не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) этого сериала не будут записаны из-за конфликта в расписании. + + + Запланирована %1$d запись сериала \"%2$s\". Несколько серий (%3$d) этого и других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) этого и других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записей сериала \"%2$s\". Несколько серий (%3$d) этого и других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) этого и других сериалов не будут записаны из-за конфликта в расписании. + + + Запланирована %1$d запись сериала \"%2$s\". Одна серия другого сериала не будет записана из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Одна серия другого сериала не будет записана из-за конфликта в расписании. + Запланировано %1$d записей сериала \"%2$s\". Одна серия другого сериала не будет записана из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Одна серия другого сериала не будет записана из-за конфликта в расписании. + + + Запланирована %1$d запись сериала \"%2$s\". Несколько серий (%3$d) других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записей сериала \"%2$s\". Несколько серий (%3$d) других сериалов не будут записаны из-за конфликта в расписании. + Запланировано %1$d записи сериала \"%2$s\". Несколько серий (%3$d) других сериалов не будут записаны из-за конфликта в расписании. + "Записанная программа не найдена." "Похожие записи" - "(без описания)" %1$d запись %1$d записи @@ -362,6 +377,7 @@ "Остановить запись?" "Записанные серии будут сохранены в библиотеке видеорекордера." "Остановить" + "В эфире нет ни одной серии." "Серий пока нет.\nОни будут записаны, как только выйдут в эфир." (%1$d минута) @@ -375,4 +391,5 @@ "Сегодня, %1$s" "Завтра, %1$s" "Оценка" + "Записанные программы" diff --git a/res/values-si-rLK-v23/strings.xml b/res/values-si-rLK-v23/strings.xml new file mode 100644 index 00000000..1209e55b --- /dev/null +++ b/res/values-si-rLK-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "නාලිකා" + diff --git a/res/values-si-rLK/arrays.xml b/res/values-si-rLK/arrays.xml new file mode 100644 index 00000000..657fb91d --- /dev/null +++ b/res/values-si-rLK/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "සාමාන්‍ය" + "පූර්ණ" + "විශාලනය කරන්න" + + + "සියළුම නාලිකා" + "පවුල/ළමයින්" + "ක්‍රීඩා" + "සාප්පුයාම" + "චිත්‍රපට" + "විකට" + "සංචාර" + "නාට්‍ය" + "අධ්‍යාපනය" + "සතුන්/වනසතුන්" + "ප්‍රවෘත්ති" + "වීඩියෝ ක්‍රීඩා" + "කලාව" + "විනෝදාස්වාදය" + "ජීවන රටාව" + "සංගීත" + "ප්‍රමුඛ" + "තාක්ෂණය/විද්‍යාව" + + + "සජීවී නාලිකා" + "අන්තර්ගතය සොයා ගැනීමට සරල ක්‍රමයක්" + "යෙදුම් බාගන්න, තවත් නාලිකා ලබා ගන්න" + "ඔබේ නාලිකා පෙළ අභිමත කරන්න" + + + "TV මත නාලිකා නැරඹීම වැනි ඔබේ යෙදුම් වෙතින් අන්තර්ගතය නැරඹීම." + "TV මත නාලිකා මෙන්ම හුරුපුරුදු මාර්ගෝපදේශයක් සහ මිත්‍රශීලී අතුරු මුහුණතක් සමගින්, \nඔබේ යෙදුම් වෙතින් අන්තර්ගතය බ්‍රවුස් කරන්න." + "සජීවී නාලිකා පිරිනමන යෙදුම් ස්ථාපනය කිරීමෙන් තවත් නාලිකා එක් කරන්න. \nTV මෙනුව තුළ ඇති සබැඳිය භාවිත කිරීමෙන් Google Play Store තුළ ගැළපෙන යෙදුම් සොයා ගන්න." + "ඔබේ නාලිකා ලැයිස්තුව අභිමත කිරීමට ඔබේ අලුතින් ස්ථාපනය කළ නාලිකා මූලාශ්‍ර පිහිටුවන්න. \nආරම්භ කිරීමට සැකසුම් මෙනුව තුළින් නාලිකා මූලාශ්‍ර තෝරන්න." + + diff --git a/res/values-si-rLK/rating_system_strings.xml b/res/values-si-rLK/rating_system_strings.xml new file mode 100644 index 00000000..f22044ba --- /dev/null +++ b/res/values-si-rLK/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "වැඩසටහන්වල වයස අවුරුදු 15ට අඩු ප්‍රේක්ෂකයන්ට නොගැළපෙන ද්‍රව්‍ය අඩංගු විය හැකි අතර, එබැවින් ඒවා සඳහා මාපිය කැමැත්ත භාවිත කළ යුතුය." + "වැඩසටහන්වල වයස අවුරුදු 19ට අඩු ප්‍රේක්ෂකයන්ට නොගැළපෙන ද්‍රව්‍ය අඩංගු විය හැකි අතර, එබැවින් ඒවා වයස අවුරුදු 19ට වඩා අඩු අයට සුදුසු නොවේ." + "යෝජිත සංවාදය" + "රළු භාෂාව" + "ලිංගිකත්වය හඟවන අන්තර්ගතය" + "ප‍්‍රචණ්ඩත්වය" + "කාල්පනික ප්‍රචණ්ඩත්වය" + "සියලුම ළමයි සඳහා සුදුසු ලෙස මෙම වැඩසටහන නිර්මාණය කර තිබේ." + "වයස 7 සහ ඊට වැඩි ළමයින් සඳහා මෙම වැඩසටහන නිර්මාණය කර තිබේ." + "මෙම වැඩසටහන සියලු වයස් කාණ්ඩ සඳහා සුදුසු බව බොහෝ මව්පියන් සිතයි." + "මෙම වැඩසටහනෙහි වයසින් අඩු ළමයින් සඳහා නුසුදුසු බවට මව්පියන් සිතිය හැකි ඇතැම් ද්‍රව්‍ය අඩංගුය. බොහෝ මව්පියන්ට එය ඔවුන්ගේ දරුවන් සමග නැරඹීමට අවශ්‍ය විය හැකිය." + "මෙම වැඩසටහනෙහි වයස අවුරුදු 14ට අඩු ළමයින් සඳහා නුසුදුසු බවට මව්පියන් සිතිය හැකි ඇතැම් ද්‍රව්‍ය අඩංගුය." + "මෙම වැඩසටහන වැඩිහිටියන්ට නැරඹීම සඳහා විශේෂිතවම නිර්මාණය කර ඇති අතර එබැවින් වයස 17ට අඩු ළමයින්ට නුසුදුසු විය හැකිය." + "චිත්‍රපට ඇගයීම්" + "සාමාන්‍ය ප්‍රේක්ෂකයන්. ළමයින්ට නැරඹීමට දීම සඳහා දෙමව්පියන් අකමැති කිසිවක් නැත." + "මාපිය මග පෙන්වීම යෝජනා කෙරේ. දෙමව්පියන් ඔවුන්ගේ ළමයින් සඳහා පෙන්වීමට කැමති නොවන ඇතැම් අන්තර්ගතය තිබිය හැකිය." + "දෙමව්පියන්ට දැඩිව අනතුරු අඟවනු ලැබේ. ඇතැම් ද්‍රව්‍ය පූර්ව-නවයෞවනයන්ට නුසුදුසු විය හැකිය." + "සීමා කරන ලදී, යම් වැඩිහිටි අන්තර්ගතය අඩංගුය. දෙමව්පියන්ට සිය ළමුන් කැටුව චිත්‍රපටය නැරඹීමට යාමට පෙර එය පිළිබඳව තව දැන ගන්නා ලෙස බල කෙරේ." + "වයස 17 සහ ඊට අඩු කිසිවෙකු ඇතුළත් කර නොගැනේ. පැහැදිලිවම වැඩිහිටියන්ය. ළමයින් ඇතුළත් කර නොගැනේ." + diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml new file mode 100644 index 00000000..2cd7fbf0 --- /dev/null +++ b/res/values-si-rLK/strings.xml @@ -0,0 +1,357 @@ + + + + + "මොනෝ" + "ස්ටීරියෝ" + "ධාවක පාලන" + "නාලිකා" + "රූපවාහිනී විකල්ප" + "මෙම නාලිකාව සඳහා Play පාලන ලද නොහැකිය" + "ධාවනය හෝ විරාමය කරන්න" + "වේගයෙන් ඉදිරියට" + "නැවත ඔතන්න" + "ඊළඟ" + "පෙර" + "වැඩසටහන් නියාමකය" + "නව නාලිකා දැන් තිබේ" + "%1$s විවෘත කරන්න" + "වසන ලද ශිර්ෂ" + "දර්ශන ආකාරය" + "PIP" + "බහු-ශ්‍රව්‍ය" + "තවත් නාලිකා ගන්න" + "සැකසීම්" + "TV (ඇන්ටනාව/කේබලය)" + "වැඩසටහන් තොරතුරු නැත" + "තොරතුරු නැත." + "නාලිකාව අවහිර කරන ලදි" + "නොදන්නා භාෂාව" + "වැසූ සිරස්තල %1$d" + "වැසූ සිරස්තල" + "අක්‍රිය කරන්න" + "ආකෘතිකරණය අභිරුචි කරන්න" + "වසන ලද ශීර්ෂ සඳහා පද්ධතිය-පුරා මනාපයන් සකසන්න" + "දර්ශන ආකාරය" + "බහු-ශ්‍රව්‍ය" + "මොනෝ" + "ස්ටීරියෝ" + "5.1 සරවුන්ඩ්" + "7.1 සරවුන්ඩ්" + "නාලිකා %1$d" + "අභිරුචිකරණය කළ නාලිකා ලැයිස්තුව" + "වර්ගය තෝරන්න" + "වර්ග තේරීම ඉවත් කරන්න" + "වර්ග මගින්" + "නාලිකාවේ මූලය" + "HD/SD" + "HD" + "SD" + "වර්ග මගින්" + "මෙම වැඩසටහන අවහිර කර තිබේ" + "මෙම වැඩසටහන අගයා නැත" + "මෙම වැඩසටහන %1$s ලෙස අගයා ඇත" + "ආදානය ස්වයංක්‍රිය-සම්මුර්ත කිරීමටසහාය නොදේ" + "\'%s\' සඳහා ස්වයංක්‍රිය-පරිලෝකනය ආරම්භ කිරීමට නොහැකි විය" + "උපසිරැසි සඳහා පද්ධතිය-පුරා මනාපයන් ආරම්භ කළ නොහැක." + + නාලිකා %1$dක් එක් කරන ලදී + නාලිකා %1$dක් එක් කරන ලදී + + "නාලිකා එකතු කළේ නැත" + "මව්පිය පාලනය" + "සක්‍රිය කරන්න" + "අක්‍රිය කරන්න" + "නාලිකා අවහිර කරන ලදි" + "සියල්ල අවහිර කරන්න" + "සියල්ල අවහිර නොකරන්න" + "සැඟවුණු නාලිකා" + "වැඩ සටහන සීමා කිරීම්" + "PIN වෙනස් කරන්න" + "ඇගයීම පද්ධති" + "ශ්‍රේණිගත කිරීම්" + "සියළුම ඇගයීම පද්ධති බලන්න" + "වෙනත් රටවල්" + "කිසිවක් නැත" + "කිසිවක් නැත" + "අගයා නොමැති" + "අගයා නැති වැඩසටහන් අවහිර කරන්න" + "කිසිවක් නැත" + "ඉහළ සීමා කිරීම්" + "මධ්‍යම සීමා කිරීම්" + "අඩු සීමා කිරීම්" + "අභිරුචි" + "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" + "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" + "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" + "අයාන්ත්‍රික සීමා කිරීම්" + + + "%1$s සහ උප-ඇගයීම්" + "උප-ඇගයීම්" + "මෙම නාලිකාව නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන %1$s ඇගයුම ලබා ඇත. මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන අගයා නැත. මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" + "ඔබගේ PIN එක ඇතුළු කරන්න" + "මව්පිය පාලකයක් සකසන්න, PIN එකක් සාදන්න" + "අලුත් PIN එක ඇතුළු කරන්න" + "ඔබගේ PIN එක තහවුරු කරන්න" + "ඔබගේ දැනට තිබෙන PIN එක ඇතුළු කරන්න" + + ඔබ විසින් වැරදි PIN අංකය 5 වතාවක් ඇතුළු කරන ලදී.\nතත්පර %1$dකින් නැවත උත්සාහ කරන්න. + ඔබ විසින් වැරදි PIN අංකය 5 වතාවක් ඇතුළු කරන ලදී.\nතත්පර %1$dකින් නැවත උත්සාහ කරන්න. + + "එම PIN එක වැරදිය. නැවත උත්සාහ කරන්න." + "PIN එක ගැලපී නැත" + "ඔබේ ZIP කේතය ඇතුළු කරන්න." + "සජීව නාලිකා යෙදුම TV නාලිකා සඳහා සම්පූර්ණ වැඩසටහන් මාර්ගෝපදේශයක් සැපයීමට ZIP කේතය භාවිත කරනු ඇත." + "ඔබේ ZIP කේතය ඇතුළු කරන්න" + "වලංගු නොවන ZIP කේතයකි" + "සැකසීම්" + "නාලිකා ලැයිස්තුව අභිමත කරන්න" + "ඔබගේ වැඩසටහන් මාර්ගෝපදේශය සඳහා නාලිකා තෝරන්න" + "නාලිකා මූලාශ්‍ර" + "නව නාලිකා තිබේ" + "මාපිය පාලන" + "Timeshift" + "ඔබට වැඩසටහන් විරාම කිරීමට හෝ ප්‍රතිවාදනය කිරීමට හැකි වන ලෙස නරඹන අතරතුර පටිගත කරන්න.\nඅවවාදයයි: මෙය අභ්‍යන්තර ගබඩාවෙහි ආයු කාලය ගබඩාව දැඩි ලෙස භාවිත කිරීමෙන් අඩු කළ හැකිය." + "විවෘත මූලාශ්‍ර බලපත්‍ර" + "ප්‍රතිපෝෂණය යවන්න" + "අනුවාදය" + "මෙම නාලිකාව නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන අගයා නැත.\nමෙම වැඩසටහන නැරඹීමට දකුණ ඔබා ඔබගේ PIN එක ඇතුළු කරන්න" + "මෙම වැඩසටහන %1$s අගයන ලදි.\nමෙම වැඩසටහන නැරඹීමට දකුණ ඔබා ඔබගේ PIN එක ඇතුළු කරන්න." + "මෙම නාලිකාව නැරඹීම සඳහා, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." + "මෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." + "මෙම වැඩසටහන අගයා නැත.\nමෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිත කරන්න." + "මෙම වැඩසටහන %1$s ලෙස වර්ගීකරණය කර ඇත.\nමෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." + "වැඩසටහන අවහිර කරන ලදි" + "මෙම වැඩසටහන අගයා නැත" + "මෙම වැඩසටහන %1$s ලෙස අගයා ඇත" + "ශ්‍රව්‍ය පමණයි" + "දුර්වල සංඥාව" + "අන්තර්ජාල සබැඳුමක් නැත" + + වෙනත් නාලිකා පටිගත වෙමින් ඇති නිසා මෙම නාලිකාව %1$s වනතෙක් ධාවනය කළ නොහැකිය. \n\nපටිගත කිරීමේ කාල සටහන සීරුමාරු කිරීමට දකුණ ඔබන්න. + වෙනත් නාලිකා පටිගත වෙමින් ඇති නිසා මෙම නාලිකාව %1$s වනතෙක් ධාවනය කළ නොහැකිය. \n\nපටිගත කිරීමේ කාල සටහන සීරුමාරු කිරීමට දකුණ ඔබන්න. + + "මාතෘකාවක් නොමැත" + "නාලිකාව අවහිර කරන ලදි" + "නව" + "මූලාශ්‍ර" + + නාලිකා %1$d + නාලිකා %1$d + + "ලබා ගත හැකි නාලිකා නැත" + "නව" + "සකසා නැත" + "තවත් මූලාශ්‍ර ලබා ගන්න" + "සජීව නාලිකා පිරිනමන යෙදුම් බ්‍රවුස් කරන්න" + "නව නාලිකා මූලාශ්‍ර ලබා ගත හැකිය" + "නව නාලිකා මූලාශ්‍රවලට පිරිනැමීමට නාලිකා ඇත.\nඒවා දැන් පිහිටුවන්න, නැතහොත් මෙය පසුව නාලිකා මූලාශ්‍ර සැකසීම තුළ සිදු කරන්න." + "දැන් පිහිටුවන්න" + "හරි, තේරුණා" + + + "TV මෙනුවට පිවිසීමට ""SELECT ඔබන්න""." + "TV ආදානය සොයාගැනීමට නොහැකි විය" + "TV ආදානය සොයාගත නොහැක" + "සුසරක වර්ගය ගැලපෙන්නේ නැත; කරුණාකර සුසර කරන වර්ගයේ TV අදානය සඳහා සජීවී නාලිකා යෙදුම දමන්න." + "සුසර කිරීම අසාර්ථක වුණි" + "මෙම ක්‍රියාව හැසිරවීමට යෙදුමක් සොයාගත්තේ නැත" + "සියලුම මූල නාලිකා සඟවන ලදි.\nනැරඹීමට අඩුම තරමේ එක නාලිකාවක් වත් තෝරන්න." + "වීඩියෝව බලාපොරොත්තු නොවූ ලෙස නොතිබේ" + "සම්බන්ධිත උපාංගය සඳහා BACK යතුර. පිටවීමට Home බොත්තම ඔබන්න." + "සජීවී නාලිකාවලට TV ලැයිස්තුගත කිරීම් කියවීමට අවසරය අවශ්‍යයි." + "ඔබගේ මූලාශ්‍ර පිහිටුවන්න" + "සජීවී නාලිකා සම්ප්‍රදායික TV නාලිකාවල අත්දැකීම යෙදුම්වලින් සපයන ප්‍රවාහ කිරීමේ නාලිකා සමග ඒකාබද්ධ කරයි. \n\nදැනටමත් ස්ථාපනය කර ඇති නාලිකා මූලාශ්‍ර පිහිටුවීමෙන් ආරම්භ කරන්න. නැතහොත් සජීවී නාලිකා පිරිනමන තවත් යෙදුම් සඳහා Google Play Store බ්‍රවුස් කරන්න." + "පටිගත කිරීම් සහ කාලසටහන්" + "මිනිත්තු 10" + "මිනිත්තු 30" + "පැය 1" + "පැය 3" + "මෑත" + "නියමිත" + "මාලා" + "වෙනත්" + "නාලිකාව පටිගත කළ නොහැකිය." + "වැඩසටහන පටිගත කළ නොහැකිය." + "%1$s පටිගත කිරීමට කාල සටහන්ගත කර ඇත" + "%1$s දැන් සිට %2$s දක්වා පටිගත කරමින්" + "සම්පූර්ණ කාල සටහන" + + ඊළඟ දින %1$d + ඊළඟ දින %1$d + + + මිනිත්තු %1$d + මිනිත්තු %1$d + + + නව පටිගත කිරීම් %1$d + නව පටිගත කිරීම් %1$d + + + පටිගත කිරීම් %1$d + පටිගත කිරීම් %1$d + + + පටිගත කිරීම් %1$dක් කාලසටහන්ගත කර ඇත + පටිගත කිරීම් %1$dක් කාලසටහන්ගත කර ඇත + + "පටිගත කිරීම අවලංගු කරන්න" + "පටිගත කිරීම නවත්වන්න" + "ඔරලෝසුව" + "මුල සිට ධාවනය කරන්න" + "ධාවනය නැවත පටන් ගැනීම" + "මකන්න" + "පටිගත කිරීම් මකන්න" + "නැවත පටන්ගන්න" + "වෙළුම %1$s" + "කාල සටහන බලන්න" + "තව කියවන්න" + "පටිගත කිරීම් මකන්න" + "ඔබ මැකීමට කැමති කථාංග තෝරන්න. වරක් මැකූ පසු ඒවා ප්‍රතිසාධනය කළ නොහැකිය." + "මැකීමට පටිගත කිරීම් නැත." + "නැරඹූ කථාංග තෝරන්න" + "සියලු කථාංග තෝරන්න" + "සියලු කථාංග තේරීම ඉවත් කරන්න" + "මිනිත්තු %2$dකින් %1$dක් නරඹන ලදී" + "තත්පර %2$dකින් %1$dක් නරඹන ලදී" + "කිසිදා නරඹා නැත" + + කථාංග %2$dකින් %1$dක් මකා ඇත + කථාංග %2$dකින් %1$dක් මකා ඇත + + "ප්‍රමුඛතාව" + "වැඩිම" + "අඩුම" + "නැත. %1$d" + "නාලිකා" + "ඕනෑම" + "ප්‍රමුඛතාව තේරීම" + "එකම අවස්ථාවේදී පටිගත කිරීමට ප්‍රමාණයට වඩා වැඩි වැඩසටහන් ගණන් ඇති විට, වඩා ඉහළ ප්‍රමුඛතා සහිත ඒවා පමණක් පටිගත කරනු ඇත." + "සුරකින්න" + "එක්-වරක පටිගත කිරීම්වලට වැඩිම ප්‍රමුඛතාව ඇත" + "නතර කරන්න" + "පටිගත කිරීමේ කාල සටහන බලන්න" + "මෙම තනි වැඩසටහන" + "දැන් - %1$s" + "මුළු මාලාව…" + "කෙසේ වෙතත් කාල සටහන්ගත කරන්න" + "ඒ වෙනුවට මෙය පටිගත කරන්න" + "මෙම පටිගත කිරීම අවලංගු කරන්න" + "දැන් නරඹන්න" + "පටිගත කිරීම් මකන්න..." + "පටිගත කළ හැකි" + "පටිගත කිරීම කාල සටහන්ගතයි" + "පටිගත කිරීමේ ගැටුම" + "පටිගත කරමින්" + "පටිගත කිරීම අසාර්ථක විය" + "කියවීමේ වැඩසටහන්" + "මෑත පටිගත කිරීම් බලන්න" + "%1$s හි පටිගත කිරීම අසම්පූර්ණයි." + "%1$s සහ %2$s හි පටිගත කිරීම් අසම්පූර්ණයි." + "%1$s, %2$s සහ %3$s හි පටිගත කිරීම් අසම්පූර්ණයි." + "ප්‍රමාණවත් නොවන ගබඩාව නිසා %1$s හි පටිගත කිරීම සම්පූර්ණ නොකරන ලදී." + "ප්‍රමාණවත් නොවන ගබඩාව නිසා %1$s සහ %2$s හි පටිගත කිරීම් සම්පූර්ණ නොකරන ලදී." + "ප්‍රමාණවත් නොවන ගබඩාව නිසා %1$s, %2$s සහ %3$s හි පටිගත කිරීම් සම්පූර්ණ නොකරන ලදී." + "DVR සඳහා වැඩිපුර ගබඩාව අවශ්‍යයි" + "ඔබට DVR සමගින් වැඩසටහන් පටිගත කිරීමට හැකි වනු ඇත. කෙසේ වෙතත් දැන් DVR ක්‍රියා කිරීම සඳහා ඔබේ උපාංගයේ ප්‍රමාණවත් තරම් ගබඩාව නැත. කරුණාකර %1$dGB හෝ ඊට වඩා විශාල බාහිර ධාවකයක් සම්බන්ධ කර එය උපාංග ගබඩාව ලෙස ෆෝමැට් කිරීමට පහත පියවර අනුගමනය කරන්න." + "ප්‍රමාණවත් තරම් ගබඩා ඉඩ නැත" + "ප්‍රමාණවත් තරම් ගබඩා ඉඩ නොමැති නිසා මෙම වැඩසටහන පටිගත කළ නොහැකි වනු ඇත. පවතින පටිගත කිරීම් සමහරක් මැකීම උත්සාහ කරන්න." + "අස්ථානගත ගබඩාව" + "පටිගත කිරීම නවත්වන්නද?" + "පටිගත කළ අන්තර්ගතය සුරැකෙනු ඇත." + "මෙම වැඩසටහන සමගින් වන ගැටුම් නිසා %1$s හි පටිගත කිරීම නවත්වන ලදී. පටිගත කරන ලද අන්තර්ගතය සුරකිනු ඇත." + "පටිගත කිරීම කාල සටහන්ගත කර ඇති නමුත් ගැටුම් ඇත" + "පටිගත කිරීම ආරම්භ කර ඇති නමුත් ගැටුම් ඇත" + "%1$s පටිගත කරනු ඇත." + "%1$s වාර්තා කරමින් පවතී." + "%1$s හි සමහර කොටස් පටිගත නොකරනු ඇත." + "%1$s සහ %2$s හි සමහර කොටස් පටිගත නොකරනු ඇත." + "%1$s, %2$s හි සමහර කොටස් සහ තවත් එක් කාලසටහනක් පටිගත නොකරනු ඇත." + + %1$s, %2$s හි සමහර කොටස් සහ තව කාල සටහන් %3$dක් පටිගත නොකරනු ඇත. + %1$s, %2$s හි සමහර කොටස් සහ තව කාල සටහන් %3$dක් පටිගත නොකරනු ඇත. + + "ඔබ පටිගත කිරීමට කැමති කුමක්ද?" + "ඔබ කොපමණ කාලයක් පටිගත කිරීමට කැමතිද?" + "දැනටමත් කාලසටහන්ගත කර ඇත" + "එම වැඩසටහනම %1$sට පටිගත කිරීමට කාල සටහන්ගත කර ඇත." + "දැනටමත් පටිගත කර ඇත" + "මෙම වැඩසටහන දැනටමත් පටිගත කර ඇත. එය DVR පුස්තකාලය තුළදී ලබා ගත හැකිය." + "මාලා පටිගත කිරීම කාලසටහන්ගත කරන ලදී" + + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. + + + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. ඒවායින් %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. ඒවායින් %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + + + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. මෙම මාලාවෙහි සහ වෙනත් මාලාවල කථාංග %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. මෙම මාලාවෙහි සහ වෙනත් මාලාවල කථාංග %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + + + පටිගත කිරීම් %1$dක් %2$s සඳහා කාලසටහන්ගත කර ඇත. වෙනත් මාලාවල කථාංග 1ක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + පටිගත කිරීම් %1$dක් %2$s සඳහා කාලසටහන්ගත කර ඇත. වෙනත් මාලාවල කථාංග 1ක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + + + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. වෙනත් මාලාවේ කථාංග %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + පටිගත කිරීම් %1$dක් %2$s සඳහා කාල සටහන්ගත කර ඇත. වෙනත් මාලාවේ කථාංග %3$dක් ගැටුම් නිසා පටිගත නොකරනු ඇත. + + "පටිගත කළ වැඩසටහන හමු නොවීය." + "අදාළ පටිගත කිරීම්" + + පටිගත කිරීම් %1$d + පටිගත කිරීම් %1$d + + " / " + "පටිගත කිරීමේ කාල සටහනින් %1$s ඉවත් කරන ලදී" + "සුසරක ගැටුම් නිසා අර්ධ වශයෙන් පටිගත කරනු ඇත." + "සුසරක ගැටුම් නිසා පටිගත නොකරනු ඇත." + "තවම කාල සටහනෙහි පටිගත කිරීම් නැත.\nඔබට වැඩසටහන් මාර්ගෝපදේශය වෙතින් පටිගත කිරීම කාලසටහන්ගත කළ හැකිය." + + පටිගත කිරීමේ ගැටුම් %1$d + පටිගත කිරීමේ ගැටුම් %1$d + + "මාලා සැකසීම්" + "මාලා පටිගත කිරීම අරඹන්න" + "මාලා පටිගත කිරීම නවත්වන්න" + "මාලා පටිගත කිරීම නවත්වන්නද?" + "පටිගත කළ කථාංග DVR පුස්තකාලය තුළ ලබා ගත හැකිව පවතිනු ඇත." + "නවත්වන්න" + "දැන් ගුවනේ කථාංග නැත." + "ලබා ගත හැකි කථාංග නැත.\nඒවා ලබා ගත හැකි වූ විට පටිගත කරනු ඇත." + + (මිනිත්තු %1$d) + (මිනිත්තු %1$d) + + "අද" + "හෙට" + "ඊයේ" + "අද %1$s" + "හෙට %1$s" + "ලකුණු" + "පටිගත කළ වැඩසටහන්" + diff --git a/res/values-si-v23/strings.xml b/res/values-si-v23/strings.xml deleted file mode 100644 index 1209e55b..00000000 --- a/res/values-si-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "නාලිකා" - diff --git a/res/values-si/arrays.xml b/res/values-si/arrays.xml deleted file mode 100644 index 657fb91d..00000000 --- a/res/values-si/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "සාමාන්‍ය" - "පූර්ණ" - "විශාලනය කරන්න" - - - "සියළුම නාලිකා" - "පවුල/ළමයින්" - "ක්‍රීඩා" - "සාප්පුයාම" - "චිත්‍රපට" - "විකට" - "සංචාර" - "නාට්‍ය" - "අධ්‍යාපනය" - "සතුන්/වනසතුන්" - "ප්‍රවෘත්ති" - "වීඩියෝ ක්‍රීඩා" - "කලාව" - "විනෝදාස්වාදය" - "ජීවන රටාව" - "සංගීත" - "ප්‍රමුඛ" - "තාක්ෂණය/විද්‍යාව" - - - "සජීවී නාලිකා" - "අන්තර්ගතය සොයා ගැනීමට සරල ක්‍රමයක්" - "යෙදුම් බාගන්න, තවත් නාලිකා ලබා ගන්න" - "ඔබේ නාලිකා පෙළ අභිමත කරන්න" - - - "TV මත නාලිකා නැරඹීම වැනි ඔබේ යෙදුම් වෙතින් අන්තර්ගතය නැරඹීම." - "TV මත නාලිකා මෙන්ම හුරුපුරුදු මාර්ගෝපදේශයක් සහ මිත්‍රශීලී අතුරු මුහුණතක් සමගින්, \nඔබේ යෙදුම් වෙතින් අන්තර්ගතය බ්‍රවුස් කරන්න." - "සජීවී නාලිකා පිරිනමන යෙදුම් ස්ථාපනය කිරීමෙන් තවත් නාලිකා එක් කරන්න. \nTV මෙනුව තුළ ඇති සබැඳිය භාවිත කිරීමෙන් Google Play Store තුළ ගැළපෙන යෙදුම් සොයා ගන්න." - "ඔබේ නාලිකා ලැයිස්තුව අභිමත කිරීමට ඔබේ අලුතින් ස්ථාපනය කළ නාලිකා මූලාශ්‍ර පිහිටුවන්න. \nආරම්භ කිරීමට සැකසුම් මෙනුව තුළින් නාලිකා මූලාශ්‍ර තෝරන්න." - - diff --git a/res/values-si/rating_system_strings.xml b/res/values-si/rating_system_strings.xml deleted file mode 100644 index f22044ba..00000000 --- a/res/values-si/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "වැඩසටහන්වල වයස අවුරුදු 15ට අඩු ප්‍රේක්ෂකයන්ට නොගැළපෙන ද්‍රව්‍ය අඩංගු විය හැකි අතර, එබැවින් ඒවා සඳහා මාපිය කැමැත්ත භාවිත කළ යුතුය." - "වැඩසටහන්වල වයස අවුරුදු 19ට අඩු ප්‍රේක්ෂකයන්ට නොගැළපෙන ද්‍රව්‍ය අඩංගු විය හැකි අතර, එබැවින් ඒවා වයස අවුරුදු 19ට වඩා අඩු අයට සුදුසු නොවේ." - "යෝජිත සංවාදය" - "රළු භාෂාව" - "ලිංගිකත්වය හඟවන අන්තර්ගතය" - "ප‍්‍රචණ්ඩත්වය" - "කාල්පනික ප්‍රචණ්ඩත්වය" - "සියලුම ළමයි සඳහා සුදුසු ලෙස මෙම වැඩසටහන නිර්මාණය කර තිබේ." - "වයස 7 සහ ඊට වැඩි ළමයින් සඳහා මෙම වැඩසටහන නිර්මාණය කර තිබේ." - "මෙම වැඩසටහන සියලු වයස් කාණ්ඩ සඳහා සුදුසු බව බොහෝ මව්පියන් සිතයි." - "මෙම වැඩසටහනෙහි වයසින් අඩු ළමයින් සඳහා නුසුදුසු බවට මව්පියන් සිතිය හැකි ඇතැම් ද්‍රව්‍ය අඩංගුය. බොහෝ මව්පියන්ට එය ඔවුන්ගේ දරුවන් සමග නැරඹීමට අවශ්‍ය විය හැකිය." - "මෙම වැඩසටහනෙහි වයස අවුරුදු 14ට අඩු ළමයින් සඳහා නුසුදුසු බවට මව්පියන් සිතිය හැකි ඇතැම් ද්‍රව්‍ය අඩංගුය." - "මෙම වැඩසටහන වැඩිහිටියන්ට නැරඹීම සඳහා විශේෂිතවම නිර්මාණය කර ඇති අතර එබැවින් වයස 17ට අඩු ළමයින්ට නුසුදුසු විය හැකිය." - "චිත්‍රපට ඇගයීම්" - "සාමාන්‍ය ප්‍රේක්ෂකයන්. ළමයින්ට නැරඹීමට දීම සඳහා දෙමව්පියන් අකමැති කිසිවක් නැත." - "මාපිය මග පෙන්වීම යෝජනා කෙරේ. දෙමව්පියන් ඔවුන්ගේ ළමයින් සඳහා පෙන්වීමට කැමති නොවන ඇතැම් අන්තර්ගතය තිබිය හැකිය." - "දෙමව්පියන්ට දැඩිව අනතුරු අඟවනු ලැබේ. ඇතැම් ද්‍රව්‍ය පූර්ව-නවයෞවනයන්ට නුසුදුසු විය හැකිය." - "සීමා කරන ලදී, යම් වැඩිහිටි අන්තර්ගතය අඩංගුය. දෙමව්පියන්ට සිය ළමුන් කැටුව චිත්‍රපටය නැරඹීමට යාමට පෙර එය පිළිබඳව තව දැන ගන්නා ලෙස බල කෙරේ." - "වයස 17 සහ ඊට අඩු කිසිවෙකු ඇතුළත් කර නොගැනේ. පැහැදිලිවම වැඩිහිටියන්ය. ළමයින් ඇතුළත් කර නොගැනේ." - diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml deleted file mode 100644 index eab8c336..00000000 --- a/res/values-si/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "මොනෝ" - "ස්ටීරියෝ" - "ධාවක පාලන" - "මෑත නාලිකා" - "රූපවාහිනී විකල්ප" - "PIP විකල්ප" - "මෙම නාලිකාව සඳහා Play පාලන ලද නොහැකිය" - "ධාවනය හෝ විරාමය කරන්න" - "වේගයෙන් ඉදිරියට" - "නැවත ඔතන්න" - "ඊළඟ" - "පෙර" - "වැඩසටහන් නියාමකය" - "නව නාලිකා දැන් තිබේ" - "%1$s විවෘත කරන්න" - "වසන ලද ශිර්ෂ" - "දර්ශන ආකාරය" - "PIP" - "සක්‍රියයි" - "අක්‍රිය කරන්න" - "බහු-ශ්‍රව්‍ය" - "තවත් නාලිකා ගන්න" - "සැකසීම්" - "මුල්‍ය" - "මාරු කරන්න" - "සක්‍රියයි" - "අක්‍රිය කරන්න" - "ශබ්දය" - "මූලික" - "PIP කවුළුව" - "පිරිසැලසුම" - "පහළ දකුණ" - "ඉහළ දකුණ" - "ඉහළ වම" - "පහළ වම" - "පැතෙන් පැත්තට" - "ප්‍රමාණය" - "ලොකු" - "කුඩා" - "මූලය අදානය කරන්න" - "TV (ඇන්ටනාව/කේබලය)" - "වැඩසටහන් තොරතුරු නැත" - "තොරතුරු නැත." - "නාලිකාව අවහිර කරන ලදි" - "නොදන්නා භාෂාව" - "වැසූ සිරස්තල" - "අක්‍රිය කරන්න" - "ආකෘතිකරණය අභිරුචි කරන්න" - "වසන ලද ශීර්ෂ සඳහා පද්ධතිය-පුරා මනාපයන් සකසන්න" - "දර්ශන ආකාරය" - "බහු-ශ්‍රව්‍ය" - "මොනෝ" - "ස්ටීරියෝ" - "5.1 සරවුන්ඩ්" - "7.1 සරවුන්ඩ්" - "නාලිකා %1$d" - "අභිරුචිකරණය කළ නාලිකා ලැයිස්තුව" - "වර්ගය තෝරන්න" - "වර්ග තේරීම ඉවත් කරන්න" - "වර්ග මගින්" - "නාලිකාවේ මූලය" - "HD/SD" - "HD" - "SD" - "වර්ග මගින්" - "මෙම වැඩසටහන අවහිර කර තිබේ" - "මෙම වැඩසටහන %1$s ලෙස අගයා ඇත" - "ආදානය ස්වයංක්‍රිය-සම්මුර්ත කිරීමටසහාය නොදේ" - "\'%s\' සඳහා ස්වයංක්‍රිය-පරිලෝකනය ආරම්භ කිරීමට නොහැකි විය" - "උපසිරැසි සඳහා පද්ධතිය-පුරා මනාපයන් ආරම්භ කළ නොහැක." - - නාලිකා %1$dක් එක් කරන ලදී - නාලිකා %1$dක් එක් කරන ලදී - - "නාලිකා එකතු කළේ නැත" - "සුසරකය" - "මව්පිය පාලනය" - "සක්‍රිය කරන්න" - "අක්‍රිය කරන්න" - "නාලිකා අවහිර කරන ලදි" - "සියල්ල අවහිර කරන්න" - "සියල්ල අවහිර නොකරන්න" - "සැඟවුණු නාලිකා" - "වැඩ සටහන සීමා කිරීම්" - "PIN වෙනස් කරන්න" - "ඇගයීම පද්ධති" - "ශ්‍රේණිගත කිරීම්" - "සියළුම ඇගයීම පද්ධති බලන්න" - "වෙනත් රටවල්" - "කිසිවක් නැත" - "කිසිවක් නැත" - "කිසිවක් නැත" - "ඉහළ සීමා කිරීම්" - "මධ්‍යම සීමා කිරීම්" - "අඩු සීමා කිරීම්" - "අභිරුචි" - "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" - "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" - "යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය" - "අයාන්ත්‍රික සීමා කිරීම්" - - - "%1$s සහ උප-ඇගයීම්" - "උප-ඇගයීම්" - "මෙම නාලිකාව නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" - "මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" - "මෙම වැඩසටහන %1$s ඇගයුම ලබා ඇත. මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න" - "ඔබගේ PIN එක ඇතුළු කරන්න" - "මව්පිය පාලකයක් සකසන්න, PIN එකක් සාදන්න" - "අලුත් PIN එක ඇතුළු කරන්න" - "ඔබගේ PIN එක තහවුරු කරන්න" - "ඔබගේ දැනට තිබෙන PIN එක ඇතුළු කරන්න" - - ඔබ විසින් වැරදි PIN අංකය 5 වතාවක් ඇතුළු කරන ලදී.\nතත්පර %1$dකින් නැවත උත්සාහ කරන්න. - ඔබ විසින් වැරදි PIN අංකය 5 වතාවක් ඇතුළු කරන ලදී.\nතත්පර %1$dකින් නැවත උත්සාහ කරන්න. - - "එම PIN එක වැරදිය. නැවත උත්සාහ කරන්න." - "PIN එක ගැලපී නැත" - "සැකසීම්" - "නාලිකා ලැයිස්තුව අභිමත කරන්න" - "ඔබගේ වැඩසටහන් මාර්ගෝපදේශය සඳහා නාලිකා තෝරන්න" - "නාලිකා මූලාශ්‍ර" - "නව නාලිකා තිබේ" - "මාපිය පාලන" - "විවෘත මූලාශ්‍ර බලපත්‍ර" - "විවෘත මූලාශ්‍ර වරපත්" - "අනුවාදය" - "මෙම නාලිකාව නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න" - "මෙම වැඩසටහන නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න" - "මෙම වැඩසටහන %1$s අගයන ලදි.\nමෙම වැඩසටහන නැරඹීමට දකුණ ඔබා ඔබගේ PIN එක ඇතුළු කරන්න." - "මෙම නාලිකාව නැරඹීම සඳහා, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." - "මෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." - "මෙම වැඩසටහන %1$s ලෙස වර්ගීකරණය කර ඇත.\nමෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න." - "වැඩසටහන අවහිර කරන ලදි" - "මෙම වැඩසටහන %1$s ලෙස අගයා ඇත" - "ශ්‍රව්‍ය පමණයි" - "දුර්වල සංඥාව" - "අන්තර්ජාල සබැඳුමක් නැත" - - වෙනත් නාලිකා පටිගත වෙමින් ඇති නිසා මෙම නාලිකාව %1$s වනතෙක් ධාවනය කළ නොහැකිය. \n\nපටිගත කිරීමේ කාල සටහන සීරුමාරු කිරීමට දකුණ ඔබන්න. - වෙනත් නාලිකා පටිගත වෙමින් ඇති නිසා මෙම නාලිකාව %1$s වනතෙක් ධාවනය කළ නොහැකිය. \n\nපටිගත කිරීමේ කාල සටහන සීරුමාරු කිරීමට දකුණ ඔබන්න. - - "මාතෘකාවක් නොමැත" - "නාලිකාව අවහිර කරන ලදි" - "නව" - "මූලාශ්‍ර" - - නාලිකා %1$d - නාලිකා %1$d - - "ලබා ගත හැකි නාලිකා නැත" - "නව" - "සකසා නැත" - "තවත් මූලාශ්‍ර ලබා ගන්න" - "සජීව නාලිකා පිරිනමන යෙදුම් බ්‍රවුස් කරන්න" - "නව නාලිකා මූලාශ්‍ර ලබා ගත හැකිය" - "නව නාලිකා මූලාශ්‍රවලට පිරිනැමීමට නාලිකා ඇත.\nඒවා දැන් පිහිටුවන්න, නැතහොත් මෙය පසුව නාලිකා මූලාශ්‍ර සැකසීම තුළ සිදු කරන්න." - "දැන් පිහිටුවන්න" - "හරි, තේරුණා" - - - "TV මෙනුවට පිවිසීමට ""SELECT ඔබන්න""." - "TV ආදානය සොයාගැනීමට නොහැකි විය" - "TV ආදානය සොයාගත නොහැක" - "PIP සහාය දක්වන්නේ නැත" - "PIP සමඟ පෙන්වූ විට අදානයක් එහි නොතිබේ" - "සුසරක වර්ගය ගැලපෙන්නේ නැත; කරුණාකර සුසර කරන වර්ගයේ TV අදානය සඳහා සජීවී නාලිකා යෙදුම දමන්න." - "සුසර කිරීම අසාර්ථක වුණි" - "මෙම ක්‍රියාව හැසිරවීමට යෙදුමක් සොයාගත්තේ නැත" - "සියලුම මූල නාලිකා සඟවන ලදි.\nනැරඹීමට අඩුම තරමේ එක නාලිකාවක් වත් තෝරන්න." - "වීඩියෝව බලාපොරොත්තු නොවූ ලෙස නොතිබේ" - "සම්බන්ධිත උපාංගය සඳහා BACK යතුර. පිටවීමට Home බොත්තම ඔබන්න." - "සජීවී නාලිකාවලට TV ලැයිස්තුගත කිරීම් කියවීමට අවසරය අවශ්‍යයි." - "ඔබගේ මූලාශ්‍ර පිහිටුවන්න" - "සජීවී නාලිකා සම්ප්‍රදායික TV නාලිකාවල අත්දැකීම යෙදුම්වලින් සපයන ප්‍රවාහ කිරීමේ නාලිකා සමග ඒකාබද්ධ කරයි. \n\nදැනටමත් ස්ථාපනය කර ඇති නාලිකා මූලාශ්‍ර පිහිටුවීමෙන් ආරම්භ කරන්න. නැතහොත් සජීවී නාලිකා පිරිනමන තවත් යෙදුම් සඳහා Google Play Store බ්‍රවුස් කරන්න." - "පටිගත කිරීම් සහ කාලසටහන්" - "මිනිත්තු 10" - "මිනිත්තු 30" - "පැය 1" - "පැය 3" - "මෑත" - "නියමිත" - "මාලා" - "වෙනත්" - "නාලිකාව පටිගත කළ නොහැකිය." - "වැඩසටහන පටිගත කළ නොහැකිය." - "%1$s පටිගත කිරීමට කාල සටහන්ගත කර ඇත" - "%1$s දැන් සිට %2$s දක්වා පටිගත කරමින්" - "සම්පූර්ණ කාල සටහන" - - ඊළඟ දින %1$d - ඊළඟ දින %1$d - - - මිනිත්තු %1$d - මිනිත්තු %1$d - - - නව පටිගත කිරීම් %1$d - නව පටිගත කිරීම් %1$d - - - පටිගත කිරීම් %1$d - පටිගත කිරීම් %1$d - - - පටිගත කිරීම් %1$dක් කාලසටහන්ගත කර ඇත - පටිගත කිරීම් %1$dක් කාලසටහන්ගත කර ඇත - - "ඔරලෝසුව" - "මුල සිට ධාවනය කරන්න" - "ධාවනය නැවත පටන් ගැනීම" - "මකන්න" - "පටිගත කිරීම් මකන්න" - "නැවත පටන්ගන්න" - "වෙළුම %1$s" - "කාල සටහන බලන්න" - "තව කියවන්න" - "පටිගත කිරීම් මකන්න" - "ඔබ මැකීමට කැමති කථාංග තෝරන්න. වරක් මැකූ පසු ඒවා ප්‍රතිසාධනය කළ නොහැකිය." - "මැකීමට පටිගත කිරීම් නැත." - "නැරඹූ කථාංග තෝරන්න" - "සියලු කථාංග තෝරන්න" - "සියලු කථාංග තේරීම ඉවත් කරන්න" - "මිනිත්තු %2$dකින් %1$dක් නරඹන ලදී" - "තත්පර %2$dකින් %1$dක් නරඹන ලදී" - "කිසිදා නරඹා නැත" - - කථාංග %2$dකින් %1$dක් මකා ඇත - කථාංග %2$dකින් %1$dක් මකා ඇත - - "ප්‍රමුඛතාව" - "වැඩිම" - "අඩුම" - "නැත. %1$d" - "නාලිකා" - "ඕනෑම" - "ප්‍රමුඛතාව තේරීම" - "එකම අවස්ථාවේදී පටිගත කිරීමට ප්‍රමාණයට වඩා වැඩි වැඩසටහන් ගණන් ඇති විට, වඩා ඉහළ ප්‍රමුඛතා සහිත ඒවා පමණක් පටිගත කරනු ඇත." - "සුරකින්න" - "එක්-වරක පටිගත කිරීම්වලට වැඩිම ප්‍රමුඛතාව ඇත" - "අවලංගු කර." - "අවලංගු කරන්න" - "අමතක කරන්න" - "නතර කරන්න" - "පටිගත කිරීමේ කාල සටහන බලන්න" - "මෙම තනි වැඩසටහන" - "දැන් - %1$s" - "මුළු මාලාව…" - "කෙසේ වෙතත් කාල සටහන්ගත කරන්න" - "ඒ වෙනුවට මෙය පටිගත කරන්න" - "මෙම පටිගත කිරීම අවලංගු කරන්න" - "දැන් නරඹන්න" - "පටිගත කළ හැකි" - "පටිගත කිරීම කාල සටහන්ගතයි" - "පටිගත කිරීමේ ගැටුම" - "පටිගත කරමින්" - "පටිගත කිරීම අසාර්ථක විය" - "පටිගත කිරීමේ කාලසටහන් සෑදීමට වැඩසටහන් කියවමින්" - "කියවීමේ වැඩසටහන්" - - - "DVR සඳහා වැඩිපුර ගබඩාව අවශ්‍යයි" - "ඔබට DVR සමගින් වැඩසටහන් පටිගත කිරීමට හැකි වනු ඇත. කෙසේ වෙතත් දැන් DVR ක්‍රියා කිරීම සඳහා ඔබේ උපාංගයේ ප්‍රමාණවත් තරම් ගබඩාව නැත. කරුණාකර %1$sGB හෝ ඊට වඩා විශාල බාහිර ධාවකයක් සම්බන්ධ කර එය උපාංග ගබඩාව ලෙස ෆෝමැට් කිරීමට පහත පියවර අනුගමනය කරන්න." - "අස්ථානගත ගබඩාව" - "DVR මගින් භාවිත කළ ගබඩා සමහරක් අස්ථානගතය. කරුණාකර DVR නැවත-සබල කිරීමට ඔබ පෙරදී භාවිත කළ බාහිර drive සම්බන්ධ කරන්න. විකල්පව, එය තවදුරටත් ලබා ගත නොහැකි නම් ඔබට ගබඩාව අමතක කිරීමට තේරිය හැකිය." - "ගබඩාව අමතකද?" - "ඔබේ පටිගත කළ සියලු අන්තර්ගත සහ කාල සටහන් අහිමි වනු ඇත." - "පටිගත කිරීම නවත්වන්නද?" - "පටිගත කළ අන්තර්ගතය සුරැකෙනු ඇත." - - - "පටිගත කිරීම කාල සටහන්ගත කර ඇති නමුත් ගැටුම් ඇත" - "පටිගත කිරීම ආරම්භ කර ඇති නමුත් ගැටුම් ඇත" - "%1$s පටිගත කරනු ඇත." - "%1$s වාර්තා කරමින් පවතී." - "%1$s හි සමහර කොටස් පටිගත නොකරනු ඇත." - "%1$s සහ %2$s හි සමහර කොටස් පටිගත නොකරනු ඇත." - "%1$s, %2$s හි සමහර කොටස් සහ තවත් එක් කාලසටහනක් පටිගත නොකරනු ඇත." - - %1$s, %2$s හි සමහර කොටස් සහ තව කාල සටහන් %3$dක් පටිගත නොකරනු ඇත. - %1$s, %2$s හි සමහර කොටස් සහ තව කාල සටහන් %3$dක් පටිගත නොකරනු ඇත. - - "ඔබ පටිගත කිරීමට කැමති කුමක්ද?" - "ඔබ කොපමණ කාලයක් පටිගත කිරීමට කැමතිද?" - "දැනටමත් කාලසටහන්ගත කර ඇත" - "එම වැඩසටහනම %1$sට පටිගත කිරීමට කාල සටහන්ගත කර ඇත." - "දැනටමත් පටිගත කර ඇත" - "මෙම වැඩසටහන දැනටමත් පටිගත කර ඇත. එය DVR පුස්තකාලය තුළදී ලබා ගත හැකිය." - - - - - - - - - "පටිගත කළ වැඩසටහන හමු නොවීය." - "අදාළ පටිගත කිරීම්" - "(වැඩසටහන් විස්තරය නැත)" - - පටිගත කිරීම් %1$d - පටිගත කිරීම් %1$d - - " / " - "පටිගත කිරීමේ කාල සටහනින් %1$s ඉවත් කරන ලදී" - "සුසරක ගැටුම් නිසා අර්ධ වශයෙන් පටිගත කරනු ඇත." - "සුසරක ගැටුම් නිසා පටිගත නොකරනු ඇත." - "තවම කාල සටහනෙහි පටිගත කිරීම් නැත.\nඔබට වැඩසටහන් මාර්ගෝපදේශය වෙතින් පටිගත කිරීම කාලසටහන්ගත කළ හැකිය." - - පටිගත කිරීමේ ගැටුම් %1$d - පටිගත කිරීමේ ගැටුම් %1$d - - "මාලා සැකසීම්" - "මාලා පටිගත කිරීම අරඹන්න" - "මාලා පටිගත කිරීම නවත්වන්න" - "මාලා පටිගත කිරීම නවත්වන්නද?" - "පටිගත කළ කථාංග DVR පුස්තකාලය තුළ ලබා ගත හැකිව පවතිනු ඇත." - "නවත්වන්න" - "ලබා ගත හැකි කථාංග නැත.\nඒවා ලබා ගත හැකි වූ විට පටිගත කරනු ඇත." - - (මිනිත්තු %1$d) - (මිනිත්තු %1$d) - - "අද" - "හෙට" - "ඊයේ" - "අද %1$s" - "හෙට %1$s" - "ලකුණු" - diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 78d94c7f..1d747d7a 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -20,54 +20,35 @@ "mono" "stereo" "Ovládanie prehrávania" - "Nedávne kanály" - "Možnosti TV" - "Možnosti PIP" + "Kanály" + "Možnosti televízie" "Pre tento kanál nie sú k dispozícii ovládacie prvky prehrávania" "Prehrať alebo pozastaviť" "Pretočiť dopredu" "Pretočiť späť" "Ďalej" "Naspäť" - "Program. sprievodca" - "Dostupné sú nové kanály" + "Televízny program" + "Dostupné nové kanály" "Spustiť aplikáciu %1$s" "Skryté titulky" "Režim zobrazenia" - "PIP" - "Zapnuté" - "Vypnuté" - "Multi-audio" + "Obraz v obraze" + "Viackanálový zvuk" "Ďalšie kanály" "Nastavenia" - "Zdroj" - "Zameniť" - "Zapnuté" - "Vypnuté" - "Zvuk" - "Hlavné" - "Okno PIP" - "Rozloženie" - "Vpravo dole" - "Vpravo hore" - "Vľavo hore" - "Vľavo dole" - "Vedľa seba" - "Rozmery" - "Veľké" - "Malé" - "Zdroj vstupu" "TV (anténa/kábel)" "Žiadne informácie o programe" "Žiadne informácie" "Zablokovaný kanál" - "Neznámy jazyk" + "Neznámy jazyk" + "Skryté titulky: %1$d" "Skryté titulky" "Vypnuté" "Prispôsobiť formát" "Nastavenie systémových predvolieb pre skryté titulky" "Režim zobrazenia" - "Multi-audio" + "Viackanálový zvuk" "mono" "stereo" "Priestorový zvuk 5.1" @@ -83,6 +64,7 @@ "SD" "Zoskupiť podľa" "Tento program je zablokovaný." + "Tento program nemá hodnotenie" "Hodnotenie tohto programu je %1$s." "Vstup nepodporuje automatické vyhľadávanie" "Nepodarilo sa automaticky prehľadať vstup %s" @@ -94,7 +76,6 @@ %1$d pridaný kanál "Žiadne pridané kanály" - "Tuner" "Rodič. kontrola" "Zapnuté" "Vypnuté" @@ -110,6 +91,8 @@ "Iné krajiny" "Žiadny" "Žiadny" + "Nehodnotené" + "Blokovať nehodnotené programy" "Žiadne" "Silné obmedzenia" "Stredné obmedzenia" @@ -126,6 +109,7 @@ "Zadanie kódu PIN na pozeranie tohto kanála" "Zadanie kódu PIN na pozeranie tohto programu" "Tento program má hodnotenie %1$s. Ak ho chcete pozerať, zadajte kód PIN." + "Tento program nemá hodnotenie. Ak ho chcete pozerať, zadajte kód PIN." "Zadanie kódu PIN" "Ak chcete nastaviť rodičovskú kontrolu, vytvorte kód PIN" "Zadajte nový kód PIN" @@ -139,22 +123,31 @@ "Kód PIN bol zadaný chybne. Skúste to znova." "Kód PIN nesúhlasí. Skúste to znova." + "Zadajte svoje PSČ." + "Aplikácia Televízia online vám na základe poštového smerovacieho čísla poskytne kompletný program televíznych kanálov." + "Zadajte svoje PSČ" + "Neplatné PSČ" "Nastavenia" "Prispôsobiť zoznam kanálov" - "Vybrať kanály pre televízny program" + "Vyberte kanály pre televízny program" "Zdroje kanálov" "K dispozícii sú nové kanály" "Rodičovská kontrola" + "Posunutie času" + "Zaznamenávajte pozerané programy vysielané naživo, aby ste ich mohli pozastaviť alebo pretočiť späť.\nUpozornenie: Intenzívnym využívaním interného úložiska môže dôjsť ku skráteniu jeho životnosti." "Licencie open source" - "Licencie open source" + "Odoslať spätnú väzbu" "Verzia" "Ak chcete sledovať tento kanál, stlačte šípku vpravo a zadajte kód PIN" "Ak chcete sledovať tento program, stlačte šípku doprava a zadajte kód PIN" + "Tento program nemá hodnotenie.\nAk ho chcete pozerať, stlačte šípku doprava a zadajte PIN." "Hodnotenie tohto programu je %1$s.\nAk chcete sledovať tento program, stlačte šípku doprava a zadajte kód PIN." "Ak chcete sledovať tento kanál, použite predvolenú aplikáciu Live TV." "Ak chcete sledovať tento program, použite predvolenú aplikáciu Live TV." + "Tento program nemá hodnotenie.\nAk ho chcete pozerať, použite predvolenú aplikáciu Live TV." "Tento program má hodnotenie %1$s.\nAk ho chcete sledovať, použite predvolenú aplikáciu Live TV." "Program je zablokovaný" + "Tento program nemá hodnotenie" "Hodnotenie tohto programu je %1$s." "Iba zvuk" "Slabý signál" @@ -179,7 +172,7 @@ "Nové" "Nenastavené" "Získať ďalšie zdroje" - "Prehliadajte aplikácie, ktoré ponúkajú televíziu online" + "Aplikácie, ponúkajúce televíziu online" "K dispozícii sú nové zdroje kanálov" "K dispozícii sú nové zdroje s ďalšími kanálmi.\nMôžete ich nastaviť hneď alebo neskôr v nastavení zdrojov kanálov." "Nastaviť" @@ -189,8 +182,6 @@ "Stlačením tlačidla VYBRAŤ"" prejdete do TV ponuky." "Nenašiel sa žiadny TV vstup" "TV vstup sa nenašiel" - "Funkcia Obraz v obraze (PIP) nie je podporovaná" - "Neexistuje vstup, ktorý by mohla funkcia PIP zobraziť" "Typ tunera nie je vhodný. Pre TV vstup typu tunera spustite aplikáciu Aktívne kanály." "Ladenie zlyhalo" "Aplikácia potrebná na spracovanie tejto akcie sa nenašla." @@ -199,7 +190,7 @@ "Kláves Späť je určený pre pripojené zariadenie. Aplikáciu ukončite stlačením tlačidla Domov." "Aktívne kanály potrebujú povolenie na čítanie televíznych programov." "Nastavte si zdroje" - "Televízia online spája zážitok z tradičných televíznych kanálov so streamovaním kanálov z aplikácií. \n\nZačnite nastavením zdrojov kanálov, ktoré už máte nainštalované. Prípadne si prehliadnite Obchod Google Play a získajte ďalšie aplikácie, ktoré poskytujú televíziu online." + "Televízia online spája tradičné televízne kanály so streamovanými kanálmi z aplikácií. \n\nAk chcete začať, nastavte si zdroje kanálov, ktoré už máte nainštalované. Prípadne si môžete v Obchode Google Play vyhľadať ďalšie aplikácie, ktoré ponúkajú televíziu online." "Nahrávanie a plány" "10 minút" "30 minút" @@ -244,6 +235,8 @@ %1$d plánovaných záznamov %1$d plánovaný záznam + "Zrušiť zaznamenávanie" + "Zastaviť zaznamenávanie" "Prehrať" "Prehrať od začiatku" "Obnoviť prehrávanie" @@ -278,9 +271,6 @@ "Keď naplánujete nahrávanie príliš veľa programov súčasne, zaznamenajú sa iba programy s najvyššími prioritami." "Uložiť" "Jednorazové záznamenávania majú najvyššiu prioritu" - "Zrušiť" - "Zrušiť" - "Odstrániť" "Zastaviť" "Zobraziť rozvrh nahrávania" "Iba tento program" @@ -290,25 +280,28 @@ "Zaznamenať radšej tento program" "Zrušiť tento záznam" "Pozrieť" + "Odstrániť nahratý obsah..." "Je možné nahrať" "Nahrávanie je naplánované" "Konflikt nahrávania" "Nahráva sa" "Nahrávanie zlyhalo" - "Čítajú sa programy s cieľom vytvoriť plány zaznamenávania" - "Načítavajú sa programy" - - + "Načítavajú sa programy" + "Zobraziť nedávne nahrávky" + "Záznam programu %1$s nie je úplný." + "Záznamy programov %1$s%2$s nie sú úplné." + "Záznamy programov %1$s, %2$s%3$s nie sú úplné." + "Zaznamenávanie programu %1$s nebolo dokončené z dôvodu nedostatku miesta v úložisku." + "Zaznamenávanie programov %1$s%2$s nebolo dokončené z dôvodu nedostatku miesta v úložisku." + "Zaznamenávanie programov %1$s, %2$s%3$s nebolo dokončené z dôvodu nedostatku miesta v úložisku." "DVR vyžaduje viac miesta v úložisku" - "Budete môcť zaznamenávať programy pomocou zariadenia DVR. Teraz však v úložisku vášho zariadenia nie je dostatok miesta na fungovanie zariadenia DVR. Pripojte externý disk s minimálnou kapacitou %1$s GB a podľa uvedených krokov ho naformátujte ako úložisko zariadenia." + "Budete môcť zaznamenávať programy pomocou zariadenia DVR. Teraz však v úložisku vášho zariadenia nie je dostatok miesta na fungovanie zariadenia DVR. Pripojte externý disk s minimálnou kapacitou %1$d GB a podľa uvedených krokov ho naformátujte ako úložisko zariadenia." + "Nedostatok úložiska" + "Tento program nebude nahratý z dôvodu nedostatku úložiska. Skúste odstrániť časť nahratého obsahu." "Chýba úložisko" - "Určitá časť úložiska využitého zariadením DVR chýba. Pred opätovným povolením zariadenia DVR pripojte externý disk, ktorý ste predtým používali. Prípadne môžete úložisko odstrániť, ak už nie je ďalej k dispozícii." - "Odstrániť úložisko?" - "Všetok váš zaznamenaný obsah a plány budú stratené." "Zastaviť nahrávanie?" "Nahraný obsah sa uloží." - - + "Zaznamenávanie relácie %1$s bude zastavené, pretože koliduje s týmto programom. Zaznamenaný obsah sa uloží." "Nahrávanie je naplánované, ale obsahuje konflikty" "Záznam sa spustil, ale obsahuje konflikty" "Zaznamená sa program %1$s." @@ -328,17 +321,39 @@ "Zaznamenanie rovnakého programu už bolo naplánované na %1$s." "Už je zaznamenané" "Tento program je už zaznamenaný. Nájdete ho v knižnici zariadenia DVR." - - - - - - - - + "Zaznamenávanie relácie bolo naplánované" + + Pre seriál %2$s boli naplánované %1$d záznamy. + Pre seriál %2$s bolo naplánovaného %1$d záznamu. + Pre seriál %2$s bolo naplánovaných %1$d záznamov. + Pre seriál %2$s bol naplánovaný %1$d záznam. + + + Pre seriál %2$s boli naplánované %1$d záznamy. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy (počet: %3$d). + Pre seriál %2$s bolo naplánovaného %1$d záznamu. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy (počet: %3$d) + Pre seriál %2$s bolo naplánovaných %1$d záznamov. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy (počet: %3$d) + Pre seriál %2$s bol naplánovaný %1$d záznam. Z dôvodu konfliktov sa nezaznamená. + + + Pre seriál %2$s boli naplánované %1$d záznamy. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy z tohto seriálu a ďalších seriálov (počet: %3$d). + Pre seriál %2$s bolo naplánovaného %1$d záznamu. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy z tohto seriálu a ďalších seriálov (počet: %3$d). + Pre seriál %2$s bolo naplánovaných %1$d záznamov. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy z tohto seriálu a ďalších seriálov (počet: %3$d). + Pre seriál %2$s bol naplánovaný %1$d záznam. Z dôvodu konfliktov sa nezaznamenajú niektoré epizódy z tohto seriálu a ďalších seriálov (počet: %3$d). + + + Pre seriál %2$s boli naplánované %1$d záznamy.Z dôvodu konfliktov nebude zaznamenaná 1 epizóda inej relácie. + Pre seriál %2$s bolo naplánovaného %1$d záznamu.Z dôvodu konfliktov nebude zaznamenaná 1 epizóda inej relácie. + Pre seriál %2$s bolo naplánovaných %1$d záznamov.Z dôvodu konfliktov nebude zaznamenaná 1 epizóda inej relácie. + Pre seriál %2$s bol naplánovaný %1$d záznam.Z dôvodu konfliktov nebude zaznamenaná 1 epizóda inej relácie. + + + Pre seriál %2$s boli naplánované %1$d záznamy. Z dôvodu konfliktov sa nezaznamenajú epizódy z iného seriálu (počet: %3$d). + Pre seriál %2$s bolo naplánovaného %1$d záznamu. Z dôvodu konfliktov sa nezaznamenajú epizódy z iného seriálu (počet: %3$d). + Pre seriál %2$s bolo naplánovaných %1$d záznamov. Z dôvodu konfliktov sa nezaznamenajú epizódy z iného seriálu (počet: %3$d). + Pre seriál %2$s bol naplánovaný %1$d záznam. Z dôvodu konfliktov sa nezaznamenajú epizódy z iného seriálu (počet: %3$d). + "Zaznamenaný program sa nenašiel." "Súvisiace nahrávky" - "(Žiadny popis programu)" %1$d záznamy %1$d záznamu @@ -349,7 +364,7 @@ "Program %1$s bol odstránený z rozvrhu nahrávania" "Z dôvodu konfliktu tunerov bude nahratá iba časť obsahu." "Z dôvodu konfliktu tunerov obsah nebude nahratý." - "Nemáte naplánované žiadne záznamy.\nZaznamenávanie môžete naplánovať z televízneho programu." + "Nemáte naplánované žiadne záznamy.\nZáznam môžete naplánovať z televízneho programu." %1$d konflikty nahrávania %1$d konfliktu nahrávania @@ -362,6 +377,7 @@ "Zastaviť nahrávanie série?" "Nahrané epizódy zostanú k dispozícii v knižnici DVR." "Zastaviť" + "Momentálne nie sú vysielané žiadne epizódy." "Nie sú k dispozícii žiadne epizódy.\nZaznamenajú sa, keď budú dostupné." (%1$d minúty) @@ -375,4 +391,5 @@ "Dnes: %1$s" "Zajtra: %1$s" "Skóre" + "Zaznamenané programy" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 6e79cacc..7fac034b 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Kontrolniki predvajanja" - "Nedavni kanali" + "Kanali" "Možnosti za TV" - "Možnosti za PIP" "Kontrolniki za predvajanje niso na voljo za ta kanal" "Predvajanje ali zaustavitev" "Previjanje naprej" @@ -35,33 +34,15 @@ "Podnapisi" "Način prikaza" "PIP" - "Vklopljeno" - "Izklopljeno" "Multizvok" "Več kanalov" "Nastavitve" - "Vir" - "Zamenjaj" - "Vklopljeno" - "Izklopljeno" - "Zvok" - "Glavno" - "Okno PIP-a" - "Postavitev" - "Spodaj desno" - "Zgoraj desno" - "Zgoraj levo" - "Spodaj levo" - "Vzporedno" - "Velikost" - "Veliko" - "Majhno" - "Vir vhoda" "TV (antena/kabelska)" "Ni informacij o programu" "Ni informacij" "Blokiran kanal" - "Neznan jezik" + "Neznan jezik" + "Podnapisi %1$d" "Podnapisi" "Izklopljeno" "Oblikovanje po meri" @@ -83,6 +64,7 @@ "SD" "Razvrsti po:" "Ta program je blokiran" + "Ta oddaja nima kategorije vsebine" "Ta program ima kategorijo vsebine %1$s" "Vhod ne podpira samodejnega iskanja" "Samodejnega iskanja za »%s« ni mogoče začeti" @@ -94,7 +76,6 @@ %1$d dodanih kanalov "Ni dodanih kanalov" - "Sprejemnik" "Starševski nadzor" "Vklopljeno" "Izklopljeno" @@ -110,6 +91,8 @@ "Druge države" "Brez" "Brez" + "Brez kategorije vsebine" + "Blokiranje oddaj brez kat. vs." "Brez" "Visoke omejitve" "Srednje omejitve" @@ -126,6 +109,7 @@ "Vnesite kodo PIN, če želite gledati ta kanal" "Vnesite kodo PIN, če želite gledati to oddajo" "Ta oddaja ima kategorijo vsebine %1$s. Vnesite kodo PIN, če si želite ogledati to oddajo." + "Ta oddaja nima kategorije vsebine. Vnesite kodo PIN, če jo želite gledati." "Vnos kode PIN" "Če želite nastaviti starševski nadzor, ustvarite kodo PIN." "Vnesite novo kodo PIN" @@ -139,22 +123,31 @@ "Koda PIN je bila napačna. Poskusite znova." "Poskusite znova. Koda PIN se ne ujema." + "Vnos poštne številke." + "Aplikacija Televizija v živo uporablja poštno številko za posredovanje popolnega programskega vodnika za televizijske kanale." + "Vnesite poštno številko" + "Neveljavna poštna številka" "Nastavitve" "Prilagajanje seznama kanalov" "Izbira kanalov za programski vodnik" "Viri kanalov" "Na voljo so novi kanali" "Starševski nadzor" + "Časovni zamik" + "Snemanje med gledanjem, da lahko oddaje v živo zaustavite ali previjete nazaj.\nOpozorilo: zaradi intenzivne rabe shrambe se lahko s tem skrajša življenjska doba notranje shrambe." "Odprtokodne licence" - "Odprtokodne licence" + "Pošljite povratne informacije" "Različica" "Če želite gledati ta kanal, pritisnite v desno in vnesite kodo PIN" "Če želite gledati ta program, pritisnite v desno in vnesite kodo PIN" + "Ta oddaja nima kategorije vsebine.\nČe jo želite gledati, pritisnite desno smerno tipko in vnesite kodo PIN." "Ta program ima kategorijo vsebine %1$s.\nČe ga želite gledati, pritisnite v desno in vnesite kodo PIN." "Če želite gledati ta kanal, uporabite privzeto aplikacijo za TV v živo." "Če želite gledati to oddajo, uporabite privzeto aplikacijo za TV v živo." + "Ta oddaja nima kategorije vsebine.\nČe jo želite gledati, uporabite aplikacijo za TV v živo." "Ta oddaja ima kategorijo vsebine %1$s.\nČe jo želite gledati, uporabite privzeto aplikacijo za TV v živo." "Oddaja je blokirana" + "Ta oddaja nima kategorije vsebine" "Ta program ima kategorijo vsebine %1$s" "Samo zvok" "Šibek signal" @@ -189,8 +182,6 @@ "Pritisnite »IZBIRA«"", če želite dostopati do menija TV-ja." "Ni TV-vhodov" "Ni mogoče najti TV-vhoda" - "Slika v sliki ni podprta" - "Ni vhoda, ki bi omogočal prikaz s sliko v sliki (PIP)" "Vrsta sprejemnika ni ustrezna. Zaženite aplikacijo Kanali v živo za uporabo TV-vhoda, ki deluje kot sprejemnik." "Nastavljanje kanalov ni uspelo" "Za to dejanje ni bilo mogoče najti nobene aplikacije." @@ -201,10 +192,10 @@ "Nastavitev virov" "Kanali v živo združujejo izkušnjo običajnih TV-kanalov in pretočno predvajanje kanalov, ki ga omogočajo aplikacije. \n\nZačnite tako, da nastavite vire kanalov, ki so že nameščeni. V Trgovini Google Play lahko tudi poiščete druge aplikacije, ki omogočajo kanale v živo." "Posnetki in razporedi" - "10 min" - "30 min" + "10 minut" + "30 minut" "1 h" - "3 h" + "3 ure" "Nedavno" "Načrtovano" "Nanizanka" @@ -244,6 +235,8 @@ %1$d načrtovani posnetki %1$d načrtovan. posnetkov + "Prekliči snemanje" + "Ustavi snemanje" "Gledanje" "Predvajaj od začetka" "Nadaljuj predvajanje" @@ -278,9 +271,6 @@ "Če je nastavljeno snemanje preveč oddaj ob istem času, bodo posnete samo oddaje z višjo prednostjo." "Shrani" "Enkratna snemanja imajo najvišjo prednost" - "Prekliči" - "Prekliči" - "Pozabi" "Ustavi" "Ogled razporeda snemanja" "Samo to oddajo" @@ -290,25 +280,28 @@ "Snemanje tega namesto drugega" "Preklic tega snemanja" "Ogled" + "Izbris posnetkov …" "Omogoča snemanje" "Čas snemanja nastavljen" "Posnetek v sporu" "Snemanje" "Snemanje ni uspelo" - "Branje oddaj za ustvarjanje razporedov snemanja" - "Branje oddaj" - - + "Branje oddaj" + "Ogled nedavnih posnetkov" + "Posnetek vsebine %1$s je nepopoln." + "Posnetka vsebin %1$s in %2$s sta nepopolna." + "Posnetki vsebin %1$s, %2$s in %3$s so nepopolni." + "Snemanje vsebine %1$s se ni dokončalo zaradi pomanjkanja prostora za shranjevanje." + "Snemanje vsebin %1$s in %2$s se ni dokončalo zaradi pomanjkanja prostora za shranjevanje." + "Snemanje vsebin %1$s, %2$s in %3$s se ni dokončalo zaradi pomanjkanja prostora za shranjevanje." "Digitalni videorekorder potrebuje več shrambe" - "Z digitalnim videorekorderjem boste lahko snemali oddaje, vendar v napravi ni dovolj shrambe, potrebne za njegovo delovanje. Priključite zunanji pogon velikosti %1$s GB ali več in upoštevajte navodila, da ga formatirate kot shrambo naprave." + "Z digitalnim videorekorderjem boste lahko snemali oddaje, vendar v napravi ni dovolj shrambe, potrebne za njegovo delovanje. Priključite zunanji pogon velikosti %1$d GB ali več in upoštevajte navodila, da ga formatirate kot shrambo naprave." + "Ni dovolj prostora za shranjevanje" + "Ta program ne bo posnet, ker ni dovolj prostora za shranjevanje. Poskusite izbrisati nekatere obstoječe posnetke." "Manjkajoča shramba" - "Del shrambe, ki jo uporablja digitalni videorekorder, manjka. Povežite zunanji pogon, ki ste ga že uporabljali, če želite znova omogočiti digitalni videorekorder. Če shramba ni več na voljo, jo lahko tudi pozabite." - "Želite pozabiti shrambo?" - "Posneta vsebina in razporedi bodo izgubljeni." "Želite ustaviti snemanje?" "Posneta vsebina bo shranjena." - - + "Snemanje vsebine %1$s bo ustavljeno, ker je v sporu s tem programom. Posneta vsebina bo shranjena." "Snemanje je nastavljeno, ampak obstajajo spori" "Snemanje se je začelo, vendar so spori" "Oddaja %1$s bo posneta." @@ -328,17 +321,39 @@ "Snemanje iste oddaje je že nastavljeno za ob %1$s." "Že posneto" "Ta oddaja je že posneta. Na voljo je v knjižnici digitalnega videorekorderja." - - - - - - - - + "Snemanje serije je načrtovano" + + %1$d posnetek je načrtovan za serijo %2$s. + %1$d posnetka sta načrtovana za serijo %2$s. + %1$d posnetki so načrtovani za serijo %2$s. + %1$d posnetkov je načrtovanih za serijo %2$s. + + + %1$d posnetek je načrtovan za serijo %2$s. Zaradi sporov jih ne bo posnetih toliko: %3$d. + %1$d posnetka sta načrtovana za serijo %2$s. Zaradi sporov jih ne bo posnetih toliko: %3$d. + %1$d posnetki so načrtovani za serijo %2$s. Zaradi sporov jih ne bo posnetih toliko: %3$d. + %1$d posnetkov je načrtovanih za serijo %2$s. Zaradi sporov jih ne bo posnetih toliko: %3$d. + + + %1$d posnetek je načrtovan za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod te serije in drugih serij: %3$d. + %1$d posnetka sta načrtovana za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod te serije in drugih serij: %3$d. + %1$d posnetki so načrtovani za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod te serije in drugih serij: %3$d. + %1$d posnetkov je načrtovanih za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod te serije in drugih serij: %3$d. + + + %1$d posnetek je načrtovan za serijo %2$s. Zaradi sporov ne bo posneta 1 epizoda druge serije. + %1$d posnetka sta načrtovana za serijo %2$s. Zaradi sporov ne bo posneta 1 epizoda druge serije. + %1$d posnetki so načrtovani za serijo %2$s. Zaradi sporov ne bo posneta 1 epizoda druge serije. + %1$d posnetkov je načrtovanih za serijo %2$s. Zaradi sporov ne bo posneta 1 epizoda druge serije. + + + %1$d posnetek je načrtovan za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod drugih serij: %3$d. + %1$d posnetka sta načrtovana za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod drugih serij: %3$d. + %1$d posnetki so načrtovani za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod drugih serij: %3$d. + %1$d posnetkov je načrtovanih za serijo %2$s. Zaradi sporov ne bo posnetih toliko epizod drugih serij: %3$d. + "Posnetega programa ni bilo mogoče najti." "Sorodni posnetki" - "(ni opisa programa)" %1$d posnetek %1$d posnetka @@ -362,6 +377,7 @@ "Ustavitev snemanja serije?" "Posnete epizode bodo na voljo v knjižnici digitalnega videorekorderja." "Ustavi" + "Trenutno se ne predvaja nobena epizoda." "Na voljo ni nobena epizoda.\nPosnete bodo, ko bodo na voljo." (%1$d minuta) @@ -375,4 +391,5 @@ "Danes: %1$s" "Jutri: %1$s" "Ocena" + "Posneti programi" diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml new file mode 100644 index 00000000..17ea800b --- /dev/null +++ b/res/values-sq-rAL/strings.xml @@ -0,0 +1,356 @@ + + + + + "mono" + "stereo" + "Kontrollet e luajtjes" + "Kanalet" + "Opsionet e televizorit" + "Kontrollet e luajtjes nuk disponohen për këtë kanal" + "Luaj ose ndërprit" + "Përparo me shpejtësi" + "Rikthe me shpejtësi" + "Përpara" + "Prapa" + "Udhëzuesi i programeve" + "Kanale të reja të disponueshme" + "Hap %1$s" + "Titrat" + "Modaliteti i ekranit" + "PIP" + "Audioja në shumë gjuhë" + "Merr më shumë kanale" + "Cilësimet" + "Televizori (antena/kablloja)" + "Nuk ka informacione për programin" + "Nuk ka informacione" + "Kanal i bllokuar" + "Gjuhë e panjohur" + "Titrat %1$d" + "Titrat" + "Joaktive" + "Personalizo formatimin" + "Cakto preferencat për të gjithë sistemin për titrat" + "Modaliteti i ekranit" + "Audioja në shumë gjuhë" + "mono" + "stereo" + "5.1 rrethues" + "7.1 rrethues" + "%d kanale" + "Personalizo listën e kanaleve" + "Zgjidh grupin" + "Anulo zgjedhjen e grupit" + "Grupo sipas" + "Burimi i kanalit" + "HD/SD" + "HD" + "SD" + "Grupo sipas" + "Ky program është bllokuar" + "Ky program është i pavlerësuar" + "Ky program është vlerësuar si %1$s" + "Hyrja nuk e mbështet skanimin automatik" + "Skanimi automatik nuk mund të fillojë për \"%s\"" + "Preferencat për të gjithë sistemin nuk mund të fillojnë për titrat." + + %1$d kanale u shtuan + %1$d kanal u shtua + + "Nuk u shtua asnjë kanal" + "Kontrollet prindërore" + "Aktive" + "Joaktive" + "Kanalet u bllokuan" + "Bllokoji të gjitha" + "Zhblloko të gjitha" + "Kanalet e fshehura" + "Kufizimet e programeve" + "Ndrysho kodin PIN" + "Sistemet e vlerësimit" + "Vlerësimet" + "Shiko të gjitha sistemet e vlerësimit" + "Vende te tjera" + "Asnjë" + "Asnjë" + "I pavlerësuar" + "Blloko programet e pavlerësuara" + "Asnjë" + "Kufizimet e larta" + "Kufizimet mesatare" + "Kufizimet e ulëta" + "I personalizuar" + "Përmbajtje e përshtatshme për fëmijët" + "Përmbajtje e përshtatshme për fëmijë më të rritur" + "Përmbajtje e përshtatshme për adoleshentë" + "Kufizimet manuale" + "Burimet për përshkrimet e vlerësimeve" + "%1$s dhe vlerësimet dytësore" + "Vlerësimet dytësore" + "Fut kodin PIN për të parë këtë kanal" + "Fut kodin PIN për të shikuar këtë program" + "Ky program është vlerësuar si %1$s. Fut kodin PIN për të shikuar këtë program" + "Ky program është i pavlerësuar. Fut kodin tënd PIN për të shikuar këtë program" + "Fut kodin PIN" + "Për të caktuar kontrollet prindërore, krijo një kod PIN" + "Fut kodin e ri PIN" + "Konfirmo kodin tënd PIN" + "Fut kodin PIN aktual" + + Ke futur kodin e gabuar PIN 5 herë.\nProvo përsëri pas %1$d sekondash. + Ke futur kodin e gabuar PIN 5 herë.\nProvo përsëri pas %1$d sekonde. + + "Ai kod PIN ishte i gabuar. Provo përsëri." + "Provo përsëri, pasi kodi PIN nuk përputhet" + "Fut kodin ZIP." + "Aplikacioni \"Kanalet drejtpërdrejt\" do të përdorë kodin ZIP për të ofruar një udhëzues të plotë të programeve për kanalet e televizorit." + "Fut kodin ZIP" + "Kod ZIP i pavlefshëm" + "Cilësimet" + "Personalizo listën e kanaleve" + "Zgjidh kanalet për udhëzuesin e programeve" + "Burimet e kanaleve" + "Kanale të reja të disponueshme" + "Kontrollet prindërore" + "Zhvendosja në kohë" + "Regjistro ndërkohë që shikon që të mund t\'i vendosësh në pauzë ose t\'i rikthesh pas programet drejtpërdrejt.\nParalajmërim: Kjo mund të zvogëlojë jetëgjatësinë e hapësirës ruajtëse të brendshme nga përdorimi i lartë i hapësirës ruajtëse." + "Licencat me burim të hapur" + "Dërgo komentet" + "Versioni" + "Për të shikuar këtë kanal, shtyp \"Djathtas\" për të futur kodin tënd PIN" + "Për të shikuar këtë program, shtyp \"Djathtas\" për të futur kodin tënd PIN" + "Ky program është i pavlerësuar.\nPër të shikuar këtë program, shtyp \"Djathtas\" dhe fut kodin tënd PIN" + "Ky program është vlerësuar si %1$s.\nPër të shikuar këtë program, shtyp \"Djathtas\" dhe fut kodin tënd PIN." + "Për të shikuar këtë kanal, përdor aplikacionin e parazgjedhur të televizionit drejtpërdrejt." + "Për të shikuar këtë program, përdor aplikacionin e parazgjedhur të televizionit drejtpërdrejt." + "Ky program është i pavlerësuar.\nPër të shikuar këtë program, përdor aplikacionin e parazgjedhur të televizionit drejtpërdrejt." + "Ky program është vlerësuar si %1$s.\nPër të shikuar këtë program, përdor aplikacionin e parazgjedhur të televizionit drejtpërdrejt." + "Programi është bllokuar" + "Ky program është i pavlerësuar" + "Ky program është vlerësuar si %1$s" + "Vetëm audio" + "Sinjal i dobët" + "Nuk ka lidhje interneti" + + Ky kanal nuk mund të luhet deri në %1$s pasi po regjistrohen kanale të tjera. \n\nShtyp \"Djathtas\" për të rregulluar planifikimin e regjistrimit. + Ky kanal nuk mund të luhet deri në %1$s pasi po regjistrohet një kanal tjetër. \n\nShtyp \"Djathtas\" për të rregulluar planifikimin e regjistrimit. + + "Pa titull" + "Kanali u bllokua" + "Të reja" + "Burimet" + + %1$d kanale + %1$d kanal + + "Nuk ka kanale të disponueshme" + "Të reja" + "Nuk është konfiguruar" + "Merr burime të tjera" + "Shfleto aplikacionet që ofrojnë kanale drejtpërdrejt" + "Burime të reja të kanaleve të disponueshme" + "Burime të reja të kanaleve kanë kanale për të ofruar.\nKonfiguroji ato tani ose bëje këtë më vonë te cilësimi i burimeve të kanaleve." + "Konfiguro tani" + "Në rregull, e kuptova" + + + "Shtyp \"ZGJIDH\""" për të pasur qasje te menyja e televizorit." + "Nuk u gjet asnjë hyrje e televizorit" + "Hyrja e televizorit nuk mund të gjendet" + "Lloji i sintonizuesit nuk është i përshtatshëm. Hap aplikacionin \"Kanalet drejtpërdrejt\" për hyrjen e televizorit për llojin e sintonizuesit." + "Sintonizimi dështoi" + "Nuk u gjet asnjë aplikacion për të menaxhuar këtë veprim" + "Të gjitha kanalet e burimit janë të fshehura.\nZgjidh të paktën një kanal për ta shikuar." + "Videoja papritur nuk është e disponueshme" + "Tasti \"PRAPA\" është për pajisjen e lidhur. Shtyp butonin \"KREU\" për të dalë." + "\"Kanalet drejtpërdrejt\" kanë nevojë për leje për të lexuar listimet e televizorit." + "Konfiguro burimet e tua" + "Kanalet drejtpërdrejt ndërthurin eksperiencën e kanaleve televizive tradicionale me kanalet në transmetim të ofruara nga aplikacionet. \n\nFillo duke konfiguruar burimet e kanaleve të instaluara tashmë. Ose shfleto në \"Dyqanin e Google Play\" për më shumë aplikacione që ofrojnë kanale drejtpërdrejt." + "Regjistrimet dhe planifikimet" + "10 minuta" + "30 minuta" + "1 orë" + "3 orë" + "Të fundit" + "Të planifikuara" + "Seritë" + "Të tjera" + "Kanali nuk mund të regjistrohet." + "Programi nuk mund të regjistrohet." + "%1$s është planifikuar për t\'u regjistruar" + "Po regjistron %1$s nga tani deri në %2$s" + "Planifikimi i plotë" + + %1$d ditë të tjera + %1$d ditë tjetër + + + %1$d minuta + %1$d minutë + + + %1$d regjistrime të reja + %1$d regjistrim i ri + + + %1$d regjistrime + %1$d regjistrim + + + %1$d regjistrime të planifikuara + %1$d regjistrim i planifikuar + + "Anulo regjistrimin" + "Ndalo regjistrimin" + "Shiko" + "Luaj nga fillimi" + "Vazhdo luajtjen" + "Fshi" + "Fshi regjistrimet" + "Rifillo" + "Sezoni %1$s" + "Shiko planifikimin" + "Lexo më shumë" + "Fshi regjistrimet" + "Zgjidh episodet që dëshiron të fshish. Ato nuk mund të rikuperohen pasi të fshihen." + "Nuk ka regjistrime për të fshirë." + "Zgjidh episodet e shikuara" + "Zgjidh të gjitha episodet" + "Anulo zgjedhjen për të gjitha episodet" + "%1$d nga %2$d minuta të shikuara" + "%1$d nga %2$d sekonda të shikuara" + "Asnjëherë të shikuara" + + %1$d nga %2$d episode janë fshirë + %1$d nga %2$d episod është fshirë + + "Me prioritet" + "Maksimal" + "Shumë të ulët" + "Numri %1$d" + "Kanalet" + "Çdo kanal" + "Zgjidh përparësinë" + "Kur ka shumë programe për t\'u regjistruar në të njëjtën kohë, vetëm ata me përparësitë më të larta do të regjistrohen." + "Ruaj" + "Regjistrimet e njëhershme kanë përparësinë më të lartë" + "Ndalo" + "Shiko planifikimin e regjistrimeve" + "Vetëm këtë regjistrim" + "tani - %1$s" + "Të gjithë serinë…" + "Planifiko gjithsesi" + "Regjistro këtë më mirë" + "Anuloje këtë regjistrim" + "Shiko tani" + "Fshi regjistrimet…" + "Mund të regjistrohet" + "Regjistrimi u planifikua" + "Konflikt në regjistrim" + "Regjistrimi" + "Regjistrimi dështoi" + "Po lexon programet" + "Shiko regjistrimet e fundit" + "Regjistrimi i %1$s nuk është i plotë." + "Regjistrimet e %1$s dhe %2$s nuk janë të plota." + "Regjistrimet e %1$s, %2$s dhe %3$s nuk janë të plota." + "Regjistrimi i %1$s nuk përfundoi për shkak të hapësirës ruajtëse të pamjaftueshme." + "Regjistrimet e %1$s dhe %2$s nuk përfunduan për shkak të hapësirës ruajtëse të pamjaftueshme." + "Regjistrimet e %1$s, %2$s dhe %3$s nuk përfunduan për shkak të hapësirës ruajtëse të pamjaftueshme." + "DVR-ja ka nevojë për më shumë hapësirë ruajtëse" + "Do të mund t\'i regjistrosh programet me DVR. Sidoqoftë, nuk ka hapësirë ruajtëse të mjaftueshme në pajisje tani që DVR-ja të funksionojë. Lidh një njësi të jashtme disku që është %1$d GB ose më shumë dhe ndiq hapat për ta formatuar si hapësirë ruajtëse të pajisjes." + "Nuk ka hapësirë ruajtëse të mjaftueshme" + "Ky program nuk do të regjistrohet pasi nuk ka hapësirë ruajtëse të mjaftueshme. Provo të fshish disa regjistrime ekzistuese." + "Hapësira ruajtëse mungon" + "Të ndalohet regjistrimi?" + "Përmbajtja e regjistruar do të ruhet." + "Regjistrimi i %1$s do të ndalohet pasi është në konflikt me këtë program. Përmbajtja e regjistruar do të ruhet." + "Regjistrimi u planifikua, por ka konflikte" + "Regjistrimi ka nisur, por ka konflikte" + "%1$s do të regjistrohet." + "%1$s po regjistrohet." + "Disa pjesë të %1$s nuk do të regjistrohen." + "Disa pjesë të %1$s dhe %2$s nuk do të regjistrohen." + "Disa pjesë të %1$s, %2$s dhe një regjistrimi tjetër nuk do të regjistrohen." + + Disa pjesë të %1$s, %2$s dhe %3$d planifikime të tjera nuk do të regjistrohen. + Disa pjesë të %1$s, %2$s dhe %3$d planifikim tjetër nuk do të regjistrohen. + + "Çfarë dëshiron të regjistrosh?" + "Për sa kohë dëshiron të regjistrosh?" + "Planifikuar tashmë" + "I njëjti program është planifikuar tashmë që të regjistrohet në %1$s." + "Regjistruar tashmë" + "Ky program është regjistruar tashmë. Ai disponohet në bibliotekën DVR." + "Regjistrimi i serisë u planifikua" + + %1$d regjistrime janë planifikuar për %2$s. + %1$d regjistrim është planifikuar për %2$s. + + + %1$d regjistrime janë planifikuar për %2$s. %3$d prej tyre nuk do të regjistrohen për shkak të konflikteve. + %1$d regjistrim është planifikuar për %2$s. Ai nuk do të regjistrohet për shkak të konflikteve. + + + %1$d regjistrime janë planifikuar për %2$s. %3$d episode të kësaj serie dhe serive të tjera nuk do të regjistrohen për shkak të konflikteve. + %1$d regjistrim është planifikuar për %2$s. %3$d episode të kësaj serie dhe serive të tjera nuk do të regjistrohen për shkak të konflikteve. + + + %1$d regjistrime janë planifikuar për %2$s. 1 episod i serive të tjera nuk do të regjistrohet për shkak të konflikteve. + %1$d regjistrim është planifikuar për %2$s. 1 episod i serive të tjera nuk do të regjistrohet për shkak të konflikteve. + + + %1$d regjistrime janë planifikuar për %2$s. %3$d episode të serive të tjera nuk do të regjistrohen për shkak të konflikteve. + %1$d regjistrim është planifikuar për %2$s. %3$d episode të serive të tjera nuk do të regjistrohen për shkak të konflikteve. + + "Programi i regjistruar nuk u gjet." + "Regjistrimet e lidhura" + + %1$d regjistrime + %1$d regjistrim + + " / " + "%1$s u hoq nga planifikimi i regjistrimeve" + "Do të regjistrohet pjesërisht për shkak të konflikteve të sintonizuesit." + "Nuk do të regjistrohet për shkak të konflikteve të sintonizuesit." + "Nuk ka asnjë regjistrim ende të planifikim.\nMund ta planifikosh regjistrimin nga udhëzuesi i programeve." + + %1$d konflikte regjistrimi + %1$d konflikt regjistrimi + + "Cilësimet e serive" + "Nis regjistrimin e serisë" + "Ndalo regjistrimin e serisë" + "Të ndalohet regjistrimi i serisë?" + "Episodet e regjistruara do të jenë të disponueshme në bibliotekën DVR." + "Ndalo" + "Nuk ka episode në transmetim tani." + "Nuk ka episode të disponueshme.\nAto do të regjistrohen pasi të jenë të disponueshme." + + (%1$d minuta) + (%1$d minutë) + + "Sot" + "Nesër" + "Dje" + "%1$s sot" + "%1$s nesër" + "Rezultati" + "Programet e regjistruara" + diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 0b4c2f3c..ce291eda 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -20,9 +20,8 @@ "моно" "стерео" "Play контроле" - "Недавни канали" + "Канали" "ТВ опције" - "Опц. сл. у сл." "Контроле за пуштање нису доступне за овај канал" "Пусти или паузирај" "Премотај унапред" @@ -35,36 +34,18 @@ "Титлови" "Режим приказа" "Слика у слици" - "Укључено" - "Искључено" "Вишеструк аудио" "Набави још канала" "Подешавања" - "Извор" - "Замени" - "Укључено" - "Искључено" - "Звук" - "Главни" - "Прозор слике у слици" - "Распоред" - "Доњи десни угао" - "Горњи десни угао" - "Горњи леви угао" - "Доњи леви угао" - "Упоредо" - "Величина" - "Велика" - "Мала" - "Извор улаза" "ТВ (антенска/кабловска)" "Нема информација о програму" "Нема информација" "Блокирани канал" - "Непознат језик" + "Непознат језик" + "Опционални титл: %1$d" "Опционални титлови" "Искључи" - "Прилагоди формат" + "Прилагођавање формата" "Изаберите подешавања титлова за цео систем" "Режим приказа" "Вишеструки аудио" @@ -83,6 +64,7 @@ "SD" "Групиши према" "Овај програм је блокиран" + "Овај програм није оцењен" "Овај програм има оцену %1$s" "Улаз не подржава аутоматско скенирање" "Није успело покретање аутоматског скенирања за „%s“" @@ -93,7 +75,6 @@ Додато је %1$d канала "Нема додатих канала" - "Тјунер" "Родитељски надзор" "Укључи" "Искључи" @@ -109,6 +90,8 @@ "Друге земље" "Ништа" "Ништа" + "Неоцењено" + "Блокирај неоцењене програме" "Ништа" "Велика ограничења" "Средња ограничења" @@ -125,6 +108,7 @@ "Унесите PIN да бисте гледали овај канал" "Унесите PIN да бисте гледали овај програм" "Овај програм има оцену %1$s. Унесите PIN да бисте гледали овај програм" + "Овај програм није оцењен. Унесите PIN да бисте гледали овај програм" "Унесите PIN" "Да бисте подесили родитељску контролу, направите PIN" "Унесите нови PIN" @@ -137,22 +121,31 @@ "Тај PIN је погрешан. Пробајте поново." "Пробајте поново, PIN се не подудара" + "Унесите поштански број" + "Апликација Канали уживо ће користити овај поштански број да би пружила комплетан водич за програме за ТВ канале." + "Унесите поштански број" + "Неважећи поштански број" "Подешавања" "Прилагоди листу канала" "Изаберите канале за водич за програме" "Извори канала" "Доступни су нови канали" "Родитељски надзор" + "Контрола времена" + "Снимајте док гледате да бисте могли да паузирате или премотате програме уживо.\nУпозорење: Ово може да скрати радни век интерне меморије због њеног интензивног коришћења." "Лиценце отвореног кода" - "Лиценце отвореног кода" + "Пошаљи повратне информације" "Верзија" "Да бисте гледали овај канал, притисните дугме Десно и унесите PIN" "Да бисте гледали овај програм, притисните дугме Десно и унесите PIN" + "Овај програм није оцењен.\nДа бисте гледали овај програм, притисните тастер са стрелицом удесно и унесите PIN" "Овај програм има оцену %1$s.\nДа бисте гледали овај програм, притисните дугме Десно и унесите PIN." "Да бисте гледали овај канал, користите подразумевану апликацију за ТВ уживо." "Да бисте гледали овај програм, користите подразумевану апликацију за ТВ уживо." + "Овај програм није оцењен.\nTДа бисте гледали овај програм, користите подразумевану апликацију за ТВ уживо." "Овај програм има оцену %1$s.\nДа бисте гледали овај програм, користите подразумевану апликацију за ТВ уживо." "Програм је блокиран" + "Овај програм није оцењен" "Овај програм има оцену %1$s" "Само аудио" "Слаб сигнал" @@ -185,8 +178,6 @@ "Притисните ИЗАБЕРИ"" да бисте приступили TV менију." "Нису пронађени ТВ улази" "Не можемо да пронађемо ТВ улаз" - "Слика у слици није подржана" - "Нема улаза који може да се прикаже као слика у слици" "Тип тјунера не одговара. Покрените апликацију Канали уживо за тип ТВ улаза са тјунером." "Подешавање није успело" "Није пронађена ниједна апликација која би могла да обави ову радњу." @@ -235,6 +226,8 @@ %1$d снимања су заказана %1$d снимања је заказано + "Откажи снимање" + "Заустави снимање" "Пусти" "Пусти од почетка" "Настави репродукцију" @@ -268,9 +261,6 @@ "Када истовремено има превише програма за снимање, снимаће се само програми са вишим приоритетом." "Сачувај" "Једнократни снимци имају највиши приоритет" - "Откажи" - "Откажи" - "Заборави" "Заустави" "Прикажи распоред за снимање" "Само ова епизода" @@ -280,25 +270,28 @@ "Сними овај програм уместо њега" "Откажи ово снимање" "Пусти одмах" + "Избришите снимке…" "Подржава снимање" "Снимање је заказано" "Неусаглашеност при снимању" "Снимање" "Снимање није успело" - "Читамо програме да бисмо направили распореде" - "Читамо програме" - - + "Читамо програме" + "Прикажи недавне снимке" + "Снимање програма %1$s није довршено." + "Снимање програма %1$s и %2$s није довршено." + "Снимање програма %1$s, %2$s и %3$s није довршено." + "Нисмо довршили снимање програма %1$s због недовољног меморијског простора." + "Снимање програма %1$s и %2$s није довршено због недовољног меморијског простора." + "Снимање програма %1$s, %2$s и %3$s није довршено због недовољног меморијског простора." "DVR-у треба више меморијског простора" - "Моћи ћете да снимате програме помоћу DVR-а. Међутим, тренутно на уређају нема довољно меморијског простора да би DVR функционисао. Повежите спољни диск који има %1$s GB или више и пратите кораке да бисте га форматирали као меморијски простор уређаја." + "Моћи ћете да снимате програме помоћу DVR-а. Међутим, тренутно на уређају нема довољно меморијског простора да би DVR функционисао. Повежите спољни диск који има %1$d GB или више и пратите кораке да бисте га форматирали као меморијски простор уређаја." + "Нема довољно меморијског простора" + "Овај програм неће бити снимљен јер нема довољно меморијског простора. Пробајте да избришете неколико постојећих снимака." "Меморијски простор недостаје" - "Недостаје део меморијског простора који DVR користи. Повежите спољни диск који сте раније користили да бисте поново омогућили DVR. Уместо тога можете да заборавите меморијски простор ако више није доступан." - "Желите ли да заборавите меморијски простор?" - "Сав снимљени садржај и распореди ће бити изгубљени." "Зауставити снимање?" "Снимљени садржај ће се сачувати." - - + "Снимање програма %1$s ће се зауставити јер је дошло до неусаглашености са овим програмом. Снимљени садржај ће бити сачуван." "Снимање је заказано, али постоје неусаглашености" "Снимање је почело али има неусаглашености" "Програм %1$s ће бити снимљен." @@ -317,17 +310,34 @@ "Снимање истог програма је већ заказано за %1$s." "Већ је снимљено" "Овај програм је већ снимљен. Доступан је у DVR филмотеци." - - - - - - - - + "Снимање серије је заказано" + + %1$d снимање је заказано за серијал %2$s. + %1$d снимања су заказана за серијал %2$s. + %1$d снимања је заказано за серијал %2$s. + + + %1$d снимање је заказано за серијал %2$s. Због преклапања неће бити снимљено: %3$d. + %1$d снимања су заказана за серијал %2$s. Због преклапања неће бити снимљено: %3$d. + %1$d снимања је заказано за серијал %2$s. Због преклапања неће бити снимљено: %3$d. + + + %1$d снимање је заказано за серијал %2$s. %3$d епизода(е) овог серијала и других серијала неће бити снимљено због преклапања. + %1$d снимања су заказана за серијал %2$s. %3$d епизода(е) овог серијала и других серијала неће бити снимљено због преклапања. + %1$d снимања је заказано за серијал %2$s. %3$d епизода(е) овог серијала и других серијала неће бити снимљено због преклапања. + + + %1$d снимање је заказано за серијал %2$s. 1 епизода другог серијала неће бити снимљена због преклапања. + %1$d снимања су заказана за серијал %2$s. 1 епизода другог серијала неће бити снимљена због преклапања. + %1$d снимања је заказано за серијал %2$s. 1 епизода другог серијала неће бити снимљена због преклапања. + + + %1$d снимање је заказано за серијал %2$s. %3$d епизоде(а) других серијала неће бити снимљено због преклапања. + %1$d снимања су заказана за серијал %2$s. %3$d епизоде(а) других серијала неће бити снимљено због преклапања. + %1$d снимања је заказано за серијал %2$s. %3$d епизоде(а) других серијала неће бити снимљено због преклапања. + "Снимљени програм није пронађен." "Сродни снимци" - "(Нема описа програма)" %1$d снимак %1$d снимка @@ -349,6 +359,7 @@ "Зауставити снимање серије?" "Снимљене епизоде ће остати доступне у DVR библиотеци." "Заустави" + "Тренутно се не приказује ниједна епизода." "Нема доступних епизода.\nБиће снимљене када постану доступне." (%1$d минут) @@ -361,4 +372,5 @@ "Данас %1$s" "Сутра %1$s" "Оцена" + "Снимљене емисије" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 212fd957..62bbe7fe 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Uppspelningskontroller" - "Senaste kanaler" + "Kanaler" "Tv-alternativ" - "PIP-alternativ" "Uppspelningskontrollerna är inte tillgängliga för den här kanalen" "Spela upp eller pausa" "Snabbspola framåt" @@ -35,33 +34,15 @@ "Textning" "Visningsläge" "PIP" - "På" - "Av" "Flera ljudkäll." "Få fler kanaler" "Inställningar" - "Källa" - "Byt" - "På" - "Av" - "Ljud" - "Primär" - "PIP-fönster" - "Layout" - "Nere till h." - "Uppe till h." - "Uppe till v." - "Nere till v." - "Sida vid sida" - "Storlek" - "Stor" - "Liten" - "Ingångskälla" "Tv (antenn/kabel)" "Ingen programinformation" "Ingen information" "Blockerad kanal" - "Okänt språk" + "Okänt språk" + "Textning %1$d" "Textning" "Av" "Anpassa formatering" @@ -83,6 +64,7 @@ "SD" "Ordna efter" "Programmet är blockerat" + "Det här programmet har ingen åldersgräns" "Det här programmet har kategoriserats som %1$s" "Ingången stöder inte automatisk sökning" "Det går inte att starta automatisk sökning för %s" @@ -92,7 +74,6 @@ %1$d kanal har lagts till "Inga kanaler har lagts till" - "Mottagare" "Barnfilter" "På" "Av" @@ -108,6 +89,8 @@ "Övriga länder" "Inga" "Inga" + "Ingen åldersgräns" + "Blockera prog. utan åldersgr." "Inga" "Höga begränsningar" "Medelbegränsningar" @@ -124,6 +107,7 @@ "Ange pinkoden om du vill titta på den här kanalen" "Ange pinkoden om du vill titta på det här programmet" "Programmet har klassificerats som %1$s. Ange pinkoden om du vill titta på programmet" + "Det här programmet har ingen åldersgräns. Ange pinkoden om du vill titta på programmet" "Ange din pinkod" "Skapa en pinkod om du vill konfigurera barnfiltret" "Ange en ny pinkod" @@ -135,22 +119,31 @@ "Det var fel pinkod. Försök igen." "Försök igen. Pinkoden stämmer inte" + "Ange ditt postnummer." + "Appen Livekanaler använder postnumret till att ta fram en komplett programguide för TV-kanalerna." + "Ange ditt postnummer" + "Ogiltigt postnummer" "Inställningar" "Anpassa kanallista" "Välj kanaler för programguiden" "Kanalkällor" "Det finns nya kanaler" "Innehållsfilter" + "Tidsförskjutning" + "Spela in medan du tittar så att du kan pausa och spola tillbaka program medan de sänds.\nVarning! Detta kan medföra att internminnets livslängd förkortas eftersom lagringsutrymmet används mycket." "Licenser för öppen källkod" - "Öppen källkod" + "Skicka feedback" "Version" "Tryck till höger och ange pinkoden om du vill titta på den här kanalen" "Tryck till höger och ange pinkoden om du vill titta på det här programmet" + "Det här programmet har ingen åldersgräns.\nOm du vill titta på programmet trycker du på Höger och anger pinkoden." "Det här programmet har kategoriserats som %1$s\nTryck till höger och ange pinkoden om du vill titta på det här programmet." "Använd standardappen för live-TV om du vill titta på den här kanalen." "Använd standardappen för live-TV om du vill titta på programmet." + "Det här programmet har ingen åldersgräns.\nAnvänd standardappen för direktsänd TV om du vill titta på programmet." "Programmet har klassificeringen %1$s.\nAnvänd standardappen för live-TV om du vill titta på programmet." "Programmet har blockerats" + "Det här programmet har ingen åldersgräns" "Det här programmet har kategoriserats som %1$s" "Endast ljud" "Svag signal" @@ -181,8 +174,6 @@ "Tryck på VÄLJ"" för att öppna TV-menyn." "Ingen tv-ingång hittades" "Det går inte att hitta tv-ingången" - "PIP stöds inte" - "Det finns ingen tillgänglig ingång som kan visas med PIP" "Olämplig insignal. Starta appen Livekanaler om du vill kunna ta emot insignaler av tv-kortstyp." "Kanaljusteringen misslyckades" "Ingen app som kan hantera åtgärden hittades" @@ -191,11 +182,11 @@ "Knappen BACK (bakåt) gäller en ansluten enhet. Avsluta genom att trycka på knappen HOME (start)." "Livekanaler behöver behörighet att läsa TV-tablåer." "Konfigurera dina källor" - "Livekanaler kombinerar det bästa från traditionella TV-kanaler med strömmande kanaler från appar. \n\nKom igång genom att konfigurera de kanalkällor som redan är installerade eller kolla in Google Play Butik för fler appar som erbjuder livekanaler." + "Livekanaler kombinerar det bästa från traditionella TV-kanaler med streamade kanaler från appar. \n\nKom igång genom att konfigurera de kanalkällor som redan är installerade eller kolla in Google Play Butik för fler appar som erbjuder livekanaler." "Inspelningar och scheman" "10 minuter" "30 minuter" - "En timme" + "1 timme" "3 timmar" "Senaste" "Planerat" @@ -226,6 +217,8 @@ %1$d schemalagda inspelningar %1$d schemalagd inspelning + "Avbryt inspelning" + "Sluta spela in" "Klocka" "Spela upp från början" "Fortsätt spela upp" @@ -258,9 +251,6 @@ "När det finns för många program för att de ska kunna spelas in samtidigt spelas endast de med högst prioritet in." "Spara" "Engångsinspelningar har högsta prioritet" - "Avbryt" - "Avbryt" - "Glöm" "Stoppa" "Visa inspelningsschema" "Bara det här programmet" @@ -270,25 +260,28 @@ "Spela in detta i stället" "Avbryt den här inspelningen" "Titta nu" + "Radera inspelningar …" "Kan spelas in" "Inspelningen har schemalagts" "Inspelningskonflikt" "Inspelning" "Inspelningen misslyckades" - "Läser in program för att inspelningsscheman" - "Läser in program" - - + "Läser in program" + "Visa de senaste inspelningarna" + "Inspelningen av %1$s är ofullständig." + "Inspelningen av %1$s och %2$s är ofullständig." + "Inspelningen av %1$s, %2$s och %3$s är ofullständig." + "Det gick inte att slutföra inspelningen av %1$s eftersom det inte finns tillräckligt med lagringsutrymme." + "Det gick inte att slutföra inspelningen av %1$s och %2$s eftersom det inte finns tillräckligt med lagringsutrymme." + "Det gick inte att slutföra inspelningen av %1$s, %2$s och %3$s eftersom det inte finns tillräckligt med lagringsutrymme." "Mer lagringsutrymme krävs för hårddiskinspelning" - "Du kan använda hårddiskinspelning för att spela in program, men just nu finns det inte tillräckligt mycket ledigt lagringsutrymme på enheten för hårddiskinspelning. Anslut en extern enhet på minst %1$s GB och följ anvisningarna för att formatera den som enhetslagring." + "Du kan använda hårddiskinspelning för att spela in program, men just nu finns det inte tillräckligt mycket ledigt lagringsutrymme på enheten för hårddiskinspelning. Anslut en extern enhet på minst %1$d GB och följ anvisningarna för att formatera den som enhetslagring." + "Otillräckligt lagringsutrymme" + "Det här programmet spelas inte in eftersom lagringsutrymmet inte räcker. Du kan göra plats genom att radera gamla inspelningar." "Lagringsutrymmet är inte tillgängligt" - "En del av lagringsutrymmet som används av DVR saknas. Anslut den externa hårddisken du använde innan DVR återaktiverades. Du kan också att välja att glömma bort lagringsutrymmet om det inte längre är tillgängligt." - "Ska lagringsutrymmet glömmas bort?" - "Allt rekommenderat innehåll och alla inspelningsscheman försvinner." "Vill du sluta spela in?" "Det inspelade innehållet sparas." - - + "Inspelningen av %1$s avbryts eftersom den krockar med det här programmet. Det inspelade innehållet sparas." "Inspelningen har schemalagts men innehåller konflikter" "Inspelningen har startat men innehåller konflikter" "%1$s spelas in." @@ -306,17 +299,29 @@ "Samma program har schemalagts för inspelning kl. %1$s." "Redan inspelat" "Programmet har redan spelats in. Det finns i DVR-biblioteket." - - - - - - - - + "Serieinspelningen har schemalagts" + + %1$d inspelningar har schemalagts för %2$s. + %1$d inspelning har schemalagts för %2$s. + + + %1$d inspelningar har schemalagts för %2$s. %3$d av dem spelas inte in på grund av programkrockar. + %1$d inspelning har schemalagts för %2$s. Det spelas inte in på grund av programkrockar. + + + %1$d inspelningar har schemalagts för %2$s. %3$d avsnitt av den här serien och andra serier spelas inte in på grund av programkrockar. + %1$d inspelning har schemalagts för %2$s. %3$d avsnitt av den här serien och andra serier spelas inte in på grund av programkrockar. + + + %1$d inspelningar har schemalagts för %2$s. 1 avsnitt av andra serier spelas inte in på grund av programkrockar. + %1$d inspelning har schemalagts för %2$s. 1 avsnitt av andra serier spelas inte in på grund av programkrockar. + + + %1$d inspelningar har schemalagts för %2$s. %3$d avsnitt av andra serier spelas inte in på grund av programkrockar. + %1$d inspelning har schemalagts för %2$s. %3$d avsnitt av andra serier spelas inte in på grund av programkrockar. + "Det gick inte att hitta det inspelade programmet." "Relaterade inspelningar" - "(Programbeskrivning saknas)" %1$d inspelningar %1$d inspelning @@ -336,6 +341,7 @@ "Vill du stoppa serieinspelning?" "Inspelade avsnitt finns kvar i DVR-biblioteket" "Stoppa" + "Inga avsnitt sänds just nu." "Det finns inga tillgängliga avsnitt.\nDe spelas in när de är tillgängliga." (%1$d minuter) @@ -347,4 +353,5 @@ "%1$s i dag" "%1$s i morgon" "Betyg" + "Inspelade program" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 00038ff4..ac55f0a0 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Vidhibiti vya kucheza" - "Vituo vya hivi karibuni" + "Vituo" "Chaguo za Runinga" - "Chaguo za PIP" "Vidhibiti vya kucheza havipatikani kwa kituo hiki" "Cheza au usitishe" "Peleka mbele kwa kasi" @@ -35,33 +34,15 @@ "Manukuu" "Hali ya onyesho" "PIP" - "Imewashwa" - "Imezimwa" "Sauti nyingi" "Pata vituo zaidi" "Mipangilio" - "Chanzo" - "Badili" - "Imewashwa" - "Imezimwa" - "Sauti" - "Kuu" - "Dirisha la PIP" - "Muundo" - "Chini kulia" - "Juu kulia" - "Juu kushoto" - "Chini kushoto" - "Upande kwa upande" - "Ukubwa" - "Kubwa" - "Ndogo" - "Chanzo cha data" "Runinga (antena/kebo)" "Hakuna maelezo ya programu" "Hakuna maelezo" "Kituo kilichozuiwa" - "Lugha Isiyojulikana" + "Lugha isiyojulikana" + "Manukuu %1$d" "Manukuu" "Imezimwa" "Geuza muundo ukufae" @@ -83,16 +64,16 @@ "SD" "Panga kikundi kwa" "Kipindi hiki kimezuiwa" + "Kipindi hiki hakijakadiriwa" "Kipindi hiki kimekadiriwa %1$s" - "Kifaa cha kuingiza sauti hakitumii uchanganuzi wa kiotomatiki" - "Haikuweza kuanzisha uchanganuzi kiotomatiki kwa \'%s\'" + "Kifaa cha kuingiza sauti hakitumii ukaguzi wa kiotomatiki" + "Haikuweza kuanzisha ukaguzi wa kiotomatiki wa \'%s\'" "Haikuweza kuanzisha mapendeleo ya mfumo mzima kwa manukuu." Vituo %1$d vimeongezwa Kituo %1$d kimeongezwa "Hakuna vituo vimeongezwa" - "Kirekebisha mawimbi" "Udhibiti wa wazazi" "Imewashwa" "Imezimwa" @@ -108,6 +89,8 @@ "Nchi nyingine" "Hakuna" "Hakuna" + "Hakijakadiriwa" + "Zuia vipindi ambavyo havijakadiriwa" "Hakuna" "Vizuizi kiwango cha juu" "Vikwazo vya kati" @@ -124,6 +107,7 @@ "Weka PIN yako ili uangalie kituo hiki" "Weka PIN yako ili uangalie kipindi hiki" "Kipindi hiki kimepewa ukadiriaji wa %1$s. Weka PIN yako ili ukitazame" + "Kipindi hiki hakijakadiriwa. Andika PIN yako ili ukitazame" "Weka PIN yako" "Ili uweke udhibiti wa wazazi, unda PIN" "Weka PIN mpya" @@ -135,22 +119,31 @@ "PIN uliyoweka si sahihi. Jaribu tena." "Jaribu tena, PIN hailingani" + "Andika Msimbo wa Eneo lako" + "Programu ya Televisheni Mtandaoni itatumia Msimbo wa eneo lako ili kutoa orodha kamili ya vipindi vya vituo vya televisheni." + "Weka Msimbo wa Eneo lako" + "Msimbo wa Eneo si Sahihi" "Mipangilio" "Geuza orodha ya vituo utakavyo" "Chagua vituo kwa ajili ya orodha yako ya vipindi" "Vyanzo vya vituo" "Vituo vipya vinapatikana" "Udhibiti wa wazazi" + "Kipengele cha kubadilisha wakati" + "Rekodi vipindi vinapoendelea kucheza ili uweze kusitisha au kurudisha nyuma vipindi vya moja kwa moja.\nOnyo: Huenda hatua hii ikapunguza muda wa matumizi ya hifadhi ya ndani kutokana na matumizi mengi ya hifadhi." "Leseni za programu huria" - "Leseni za programu huria" + "Tuma maoni" "Toleo" "Ili uangalie kituo hiki, bonyeza Kulia na uweke PIN" "Ili uangalie kipindi hiki, bonyeza Kulia na uweke PIN" + "Kipindi hiki hakijakadiriwa.\nIli ukitazame, bofya \'Sawa\' kisha uandike PIN yako" "Kipindi hiki kimekadiriwa %1$s.\nIli utazame kipindi hiki, bonyeza Kulia na uweke PIN yako" "Ili kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja." "Ili kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja." + "Kipindi hiki hakijakadiriwa.\nIli ukitazame, tumia programu chaguo-msingi ya Televisheni ya Moja kwa Moja." "Programu hii imekadiriwa %1$s.\nIli kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja." "Kipindi kimezuiwa" + "Kipindi hiki hakijakadiriwa" "Kipindi hiki kimekadiriwa %1$s" "Sauti pekee" "Mawimbi dhaifu" @@ -181,8 +174,6 @@ "Bonyeza CHAGUA "" ili ufikie menyu ya televisheni." "Hakuna vifaa vya kuingiza maudhui ya runinga vinavyopatikana" "Haiwezi kupata vifaa vya kuingiza maudhui kwenye runinga" - "PIP haiwezi kutumika" - "Hakuna maudhui yanayoweza kuonyeshwa pamoja na PIP" "Aina ya kirekebishaji haifai. Fungua programu ya Vituo vya Moja kwa Moja ya vifaa vya kuingiza maudhui ya runinga." "Haikuweza kurekebisha" "Hakuna programu iliyopatikana inayoweza kushughulikia kitendo hiki." @@ -226,6 +217,8 @@ Rekodi %1$d zimeratibiwa Rekodi %1$d imeratibiwa + "Ghairi kurekodi" + "Acha kurekodi" "Saa" "Cheza kutoka mwanzo" "Endelea kucheza" @@ -258,9 +251,6 @@ "Wakati kuna vipindi vingi vya kurekodi kwa wakati mmoja, tutarekodi vipindi vilivyopewa kipaumbele pekee." "Hifadhi" "Rekodi za mara moja hupewa kipaumbele" - "Ghairi" - "Ghairi" - "Sahau" "Komesha" "Angalia ratiba ya kurekodi" "Mpango huu mmoja" @@ -270,25 +260,28 @@ "Rekodi hii badala yake" "Ghairi rekodi hii" "Angalia sasa" + "Futa rekodi..." "Inaweza kurekodiwa" "Kurekodi kumeratibiwa" "Hitilafu ya kurekodi" "Inarekodi" "Imeshindwa kurekodi" - "Inasoma maelezo ya programu ili kuunda ratiba" - "Inasoma programu" - - + "Inasoma ratiba" + "Angalia rekodi za hivi majuzi" + "Haikumaliza kurekodi %1$s." + "Haikumaliza kurekodi %1$s na %2$s." + "Haikumaliza kurekodi %1$s, %2$s na %3$s." + "Haikumaliza kurekodi %1$s kwa sababu nafasi ya hifadhi haikutosha." + "Haikumaliza kurekodi %1$s na %2$s kwa sababu nafasi ya hifadhi haikutosha." + "Haikumaliza kurekodi %1$s, %2$s na %3$s kwa sababu nafasi ya hifadhi haikutosha." "DVR inahitaji nafasi zaidi ya hifadhi" - "Utaweza kurekodi vipindi kwa kutumia DVR. Hata hivyo, kwa sasa hakuna hifadhi ya kutosha kwenye kifaa chako ili kuwezesha DVR kufanya kazi. Tafadhali unganisha hifadhi ya nje isiyopungua GB %1$s na ufuate hatua za kuiumbiza kuwa hifadhi ya kifaa." + "Utaweza kurekodi vipindi kwa kutumia DVR. Hata hivyo, kwa sasa hakuna hifadhi ya kutosha kwenye kifaa chako ili kuwezesha DVR kufanya kazi. Tafadhali unganisha hifadhi ya nje isiyopungua GB %1$d na ufuate hatua za kuiumbiza kuwa hifadhi ya kifaa." + "Hifadhi haitoshi" + "Kipindi hiki hakitarekodiwa kwa sababu hakuna hifadhi ya kutosha. Jaribu kufuta baadhi ya vipindi vilivyopo." "Hifadhi haipo" - "Baadhi ya hifadhi inayotumiwa na DVR haipo. Tafadhali unganisha hifadhi ya nje uliyotumia awali ili uwashe upya DVR yako. Vinginevyo, unaweza kuchagua kusahau hifadhi ikiwa haipatikani tena." - "Ungependa kusahau hifadhi?" - "Hatua hii itaondoa maudhui na ratiba zote ulizohifadhi." "Ungependa kuacha kurekodi?" "Itahifadhi maudhui uliyorekodi." - - + "Shughuli ya kurekodi kipindi cha %1$s itasitishwa kwa sababu inakinzana na kipindi hiki. Maudhui yaliyorekodiwa yatahifadhiwa." "Rekodi zimeratibiwa lakini zinakinzana" "Inaendelea kurekodi japo kuna ukinzani" "%1$s itarekodiwa." @@ -306,17 +299,29 @@ "Tayari umeratibu kurekodi kipindi hiki saa %1$s." "Tayari kimerekodiwa" "Tayari umerekodi kipindi hiki. Kinapatikana katika maktaba ya DVR." - - - - - - - - + "Shughuli ya kurekodi mfululizo imeratibiwa" + + Imeratibu kurekodi vipindi %1$d vya %2$s. + Imeratibu kurekodi kipindi %1$d cha %2$s. + + + Imeratibu kurekodi vipindi %1$d vya %2$s. Haitarekodi vipindi %3$d kutokana na ukinzani. + Imeratibu kurekodi kipindi %1$d cha %2$s. Haitarekodi kipindi kutokana na ukinzani. + + + Imeratibu kurekodi vipindi %1$d vya %2$s. Haitarekodi vipindi %3$d vya mfululizo huu na mifululizo mingine kutokana na ukinzani. + Imeratibu kurekodi kipindi %1$d cha %2$s. Haitarekodi vipindi %3$d vya mfululizo huu na mifululizo mingine kutokana na ukinzani. + + + Imeratibu kurekodi vipindi %1$d vya %2$s. Haitarekodi kipindi 1 cha mifululizo mingine kutokana na ukinzani. + Imeratibu kurekodi kipindi %1$d cha %2$s. Haitarekodi kipindi 1 cha mifululizo mingine kutokana na ukinzani. + + + Imeratibu kurekodi vipindi %1$d vya %2$s. Haitarekodi vipindi %3$d vya mifululizo mingine kutokana na ukinzani. + Imeratibu kurekodi kipindi %1$d cha %2$s. Haitarekodi vipindi %3$d vya mifululizo mingine kutokana na ukinzani. + "Haikupata programu iliyorekodiwa." "Rekodi zinazohusiana" - "(Hakuna maelezo ya programu)" Rekodi %1$d Rekodi %1$d @@ -336,6 +341,7 @@ "Ungependa kukomesha kurekodi mfululizo?" "Vipindi vilivyorekodiwa vitaendelea kupatikana katika maktaba ya DVR." "Komesha" + "Hakuna vipindi vinavyopeperushwa kwa sasa." "Hakuna vipindi vinavyopatikana.\nVitarekodiwa pindi vitakapopatikana." (Dakika %1$d) @@ -347,4 +353,5 @@ "Leo %1$s" "Kesho %1$s" "Alama" + "Vipindi Vilivyorekodiwa" diff --git a/res/values-ta-rIN-v23/strings.xml b/res/values-ta-rIN-v23/strings.xml new file mode 100644 index 00000000..1b94e0c4 --- /dev/null +++ b/res/values-ta-rIN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "சேனல்கள்" + diff --git a/res/values-ta-rIN/arrays.xml b/res/values-ta-rIN/arrays.xml new file mode 100644 index 00000000..9d2c1f24 --- /dev/null +++ b/res/values-ta-rIN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "இயல்பு" + "முழு" + "பெரிதாக்கு" + + + "எல்லா சேனல்களும்" + "குடும்பம்/சிறுவர்கள்" + "விளையாட்டு" + "ஷாப்பிங்" + "மூவிகள்" + "நகைச்சுவை" + "பயணம்" + "நாடகம்" + "கல்வி" + "விலங்குகள்" + "செய்திகள்" + "கேமிங்" + "கலைகள்" + "பொழுதுபோக்கு" + "வாழ்க்கை முறை" + "இசை" + "ப்ரீமியர்" + "தொழில்நுட்பம்/அறிவியல்" + + + "நேரலைச் சேனல்கள்" + "உள்ளடக்கத்தைத் தேடுவதற்கான எளிய வழி" + "பயன்பாடுகளை இறக்கி, கூடுதல் சேனல்களைப் பெறலாம்" + "சேனல் வரிசையைத் தனிப்பயனாக்கலாம்" + + + "டிவியில் சேனல்களைப் பார்ப்பதைப் போன்றே, பயன்பாடுகளின் உள்ளடக்கத்தைப் பார்க்கலாம்." + "டிவியில் சேனல்களைப் பார்ப்பதைப் போன்றே, \nபயன்படுத்துவதற்கு எளிதான வழிகாட்டி மற்றும் அறிமுகமான இடைமுகத்தின் மூலம் பயன்பாடுகளின் உள்ளடக்கத்தில் உலாவலாம்." + "நேரலைச் சேனல்களை வழங்கும் பயன்பாடுகளை நிறுவி கூடுதல் சேனல்களைச் சேர்க்கலாம். \nடிவி மெனுவில் உள்ள இணைப்பைப் பயன்படுத்தி Google Play ஸ்டோரில் இணக்கமான பயன்பாடுகளைக் கண்டறியலாம்." + "சேனல் பட்டியலைத் தனிப்பயனாக்க, புதிதாக நிறுவிய சேனல் மூலங்களை அமைக்கலாம். \nதொடங்க, அமைப்புகள் மெனுவில் உள்ள சேனல் மூலங்களைத் தேர்வுசெய்யலாம்." + + diff --git a/res/values-ta-rIN/rating_system_strings.xml b/res/values-ta-rIN/rating_system_strings.xml new file mode 100644 index 00000000..ae80dee6 --- /dev/null +++ b/res/values-ta-rIN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "நிகழ்ச்சிகள் 15 வயதுக்குட்பட்ட பார்வையாளர்களுக்குப் பொருத்தமற்றதாக இருக்கலாம் என்பதால், பெற்றோர் முடிவு எடுக்க வேண்டும்." + "நிகழ்ச்சிகள் 19 வயதுக்குட்பட்ட பார்வையாளர்களுக்குப் பொருத்தமற்றதாக இருக்கலாம் என்பதால், அவை 19 வயதுக்குட்பட்டவர்களுக்கு ஏற்றதல்ல." + "பாலுணர்வைத் தூண்டும் உரையாடல்" + "கொச்சை மொழி" + "பாலியல் உள்ளடக்கம்" + "வன்முறை" + "ஃபேண்டஸி வன்முறை" + "இந்த நிகழ்ச்சி எல்லா வயதினருக்கும் ஏற்றவாறு உருவாக்கப்பட்டது." + "இந்த நிகழ்ச்சி 7 மற்றும் அதற்கு மேற்பட்ட வயதினருக்கு ஏற்றவாறு உருவாக்கப்பட்டது." + "பல பெற்றோர்கள் இந்த நிகழ்ச்சி எல்லா வயதினரும் பார்ப்பதற்கு உகந்தது எனக் கண்டறிந்துள்ளனர்." + "தங்களின் குழந்தைகள் பார்ப்பதற்கு ஏற்றது இல்லை எனப் பெற்றோர்கள் கருதக்கூடிய உள்ளடக்கம் இந்த நிகழ்ச்சியில் உள்ளது. பல பெற்றோர்கள் தங்கள் குழந்தைகளுடன் இதைப் பார்க்க விரும்பலாம்." + "இந்த நிகழ்ச்சியில், 14 வயதிற்குக் கீழ் உள்ள குழந்தைகள் பார்ப்பதற்கு உகந்தது அல்ல எனப் பல பெற்றோர்கள் கண்டறியக்கூடிய உள்ளடக்கம் உள்ளது." + "இந்த நிகழ்ச்சி, பெரியவர்களுக்கானது, எனவே 17 வயதுக்குக் கீழ் உள்ளவர்களுக்கு ஏற்றது அல்ல." + "திரைப்பட மதிப்பீடுகள்" + "பொதுப் பார்வையாளர்களுக்கானது. குழந்தைகள் பார்ப்பதைப் பற்றி பெற்றோர்கள் வருந்தத் தேவையில்லை." + "பெற்றோர் வழிகாட்டல் பரிந்துரைக்கப்படுகிறது. குழந்தைகளுக்கு ஏற்றது அல்ல எனப் பெற்றோர்கள் கருதக்கூடிய சில உள்ளடக்கம் இருக்கலாம்." + "பெற்றோர் மிகுந்த எச்சரிக்கையுடன் இருக்க வேண்டும். சில உள்ளடக்கம் இளம் சிறார்களுக்குப் பொருத்தமற்றதாக இருக்கலாம்." + "பெரியவர்களுக்கான உள்ளடக்கம் இருப்பதால் தடைசெய்யப்பட்டுள்ளது. குழந்தைகளுடன் காணச் செல்லும் முன், திரைப்படத்தைப் பற்றி மேலும் அறிந்துகொள்ளுமாறு பெற்றோர்கள் அறிவுறுத்தப்படுகின்றனர்." + "17 வயது மற்றும் அதற்குக் குறைவான வயதினருக்குப் பொருந்தாது. பெரியவர்களுக்கு மட்டுமே பொருந்தும். குழந்தைகளுக்கு அனுமதியில்லை." + diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml new file mode 100644 index 00000000..43e1a717 --- /dev/null +++ b/res/values-ta-rIN/strings.xml @@ -0,0 +1,357 @@ + + + + + "மோனோ" + "ஸ்டீரியோ" + "இயக்கக் கட்டுப்பாடுகள்" + "சேனல்கள்" + "டிவி விருப்பங்கள்" + "இந்தச் சேனலுக்கு இயக்கக் கட்டுப்பாடுகள் இல்லை" + "இயக்கு அல்லது இடைநிறுத்து" + "வேகமாக முன் நகர்த்து" + "வேகமாக பின் நகர்த்து" + "அடுத்து" + "முந்தையது" + "நிகழ்ச்சிக் கையேடு" + "புதிய சேனல்கள் உள்ளன" + "%1$sஐத் திற" + "விரிவான வசனங்கள்" + "காட்சிப் பயன்முறை" + "PIP" + "மல்டி-ஆடியோ" + "மேலும் சேனல்கள்" + "அமைப்புகள்" + "டிவி (ஆண்டெனா/கேபிள்)" + "நிகழ்ச்சி தகவல் இல்லை" + "தகவல் இல்லை" + "தடுக்கப்பட்ட சேனல்" + "தெரியாத மொழி" + "வசனங்கள் %1$d" + "விரிவான வசனங்கள்" + "முடக்கு" + "வடிவமைப்பைத் தனிப்பயனாக்கவும்" + "விரிவான வசனங்களுக்கான முன்னுரிமைகளை முறைமை முழுவதும் அமைக்கவும்" + "காட்சிப் பயன்முறை" + "மல்டி-ஆடியோ" + "மோனோ" + "ஸ்டீரியோ" + "5.1 சரவுண்ட்" + "7.1 சரவுண்ட்" + "%d சேனல்கள்" + "சேனலை தனிப்படுத்து" + "குழுவைத் தேர்வுசெய்க" + "குழுவின் தேர்வைநீக்கு" + "இதன்படி குழுவாக்கு" + "சேனல் மூலம்" + "HD/SD" + "HD" + "SD" + "இதன்படி குழுவாக்கு" + "இந்த நிகழ்ச்சி தடுக்கப்பட்டுள்ளது" + "இந்த நிகழ்ச்சி மதிப்பிடப்படவில்லை" + "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது" + "தன்னியக்க ஸ்கேனை உள்ளீடு ஆதரிக்காது" + "’%s’க்கான தன்னியக்க ஸ்கேனைத் தொடங்க முடியவில்லை" + "விரிவான வசனங்களுக்கான சாதன விருப்பத்தேர்வுகளைத் தொடங்க முடியவில்லை." + + %1$d சேனல்கள் சேர்க்கப்பட்டன + %1$d சேனல் சேர்க்கப்பட்டது + + "சேனல்கள் எதுவும் சேர்க்கப்படவில்லை" + "பெற்றோர் கட்டுப்பாடுகள்" + "இயக்கு" + "முடக்கு" + "சேனல்கள் தடுக்கப்பட்டன" + "எல்லாவற்றையும் தடு" + "எல்லாவற்றிலும் தடைநீக்கு" + "மறைக்கப்பட்ட சேனல்கள்" + "நிகழ்ச்சிக் கட்டுப்பாடுகள்" + "PIN ஐ மாற்று" + "மதிப்பிடல் அமைப்புகள்" + "மதிப்பீடுகள்" + "அனைத்து மதிப்பீட்டு அமைப்புகளையும் பார்க்கவும்" + "பிற நாடுகள்" + "ஏதுமில்லை" + "ஏதுமில்லை" + "மதிப்பிடாதது" + "மதிப்பிடாத நிகழ்ச்சிகளைத் தடு" + "ஏதுமில்லை" + "அதிகக் கட்டுப்பாடுகள்" + "நடுத்தரக் கட்டுப்பாடுகள்" + "குறைவான கட்டுப்பாடுகள்" + "தனிப்பயன்" + "குழந்தைகளுக்கு ஏற்றது" + "குழந்தைகளுக்கு ஏற்றது" + "பதின்ம வயதினருக்கு ஏற்றது" + "கைமுறைக் கட்டுப்பாடுகள்" + + + "%1$s மற்றும் துணை மதிப்பீடுகள்" + "துணை மதிப்பிடல்கள்" + "இந்தச் சேனலைப் பார்க்க பின்னை உள்ளிடவும்" + "இந்த நிகழ்ச்சியைப் பார்க்க பின்னை உள்ளிடவும்" + "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது. நிகழ்ச்சியைப் பார்க்க, உங்கள் பின்னை உள்ளிடவும்" + "இந்த நிகழ்ச்சி மதிப்பிடப்படவில்லை. இதைப் பார்க்க, உங்கள் பின்னை உள்ளிடவும்" + "பின்னை உள்ளிடவும்" + "பெற்றோர் கட்டுப்பாடுகளை அமைக்க, PINஐ உருவாக்கவும்" + "புதிய PINஐ உள்ளிடவும்" + "PINஐ உறுதிசெய்யவும்" + "உங்கள் தற்போதைய PINஐ உள்ளிடவும்" + + பின்னை 5 முறை தவறாக உள்ளிட்டுள்ளீர்கள்.\n%1$d வினாடிகளில் மீண்டும் முயலவும். + பின்னை 5 முறை தவறாக உள்ளிட்டுள்ளீர்கள்.\n%1$d வினாடியில் மீண்டும் முயலவும். + + "PIN தவறானது. மீண்டும் முயலவும்." + "மீண்டும் முயலவும், PIN பொருந்தவில்லை" + "அஞ்சல் குறியீட்டை உள்ளிடவும்." + "நேரலைச் சேனல்கள் பயன்பாடானது, டிவி சேனல்களின் முழுமையான நிகழ்ச்சி வழிகாட்டியை வழங்குவதற்கு அஞ்சல் குறியீட்டைப் பயன்படுத்தும்." + "அஞ்சல் குறியீட்டை உள்ளிடவும்" + "தவறான அஞ்சல் குறியீடு" + "அமைப்புகள்" + "சேனல் பட்டியலைத் தனிப்பயனாக்கு" + "நிகழ்ச்சி வழிகாட்டியில் சேர்ப்பதற்கான சேனல்களைத் தேர்வுசெய்க" + "சேனல் மூலங்கள்" + "புதிய சேனல்கள் உள்ளன" + "பெற்றோர் கட்டுப்பாடுகள்" + "டைம்ஷிஃப்ட்" + "நேரலை நிகழ்ச்சிகளைப் பார்க்கும் போதே ரெக்கார்டு செய்யலாம் என்பதால், அவற்றை இடைநிறுத்தலாம் அல்லது ரீவைண்ட் செய்து பார்க்கலாம்.\nஎச்சரிக்கை: இது சேமிப்பகத்தை அதிகளவு பயன்படுத்தும் என்பதால், சாதனச் சேமிப்பகத்தின் ஆயுளைக் குறைக்கக்கூடும்." + "ஓப்பன் சோர்ஸ் உரிமங்கள்" + "கருத்து அனுப்பு" + "பதிப்பு" + "இந்தச் சேனலைப் பார்க்க, வலது பக்கம் அழுத்தி, உங்கள் PINஐ உள்ளிடவும்" + "இந்த நிகழ்ச்சியைப் பார்க்க, வலது பக்கம் அழுத்தி PINஐ உள்ளிடவும்" + "இந்த நிகழ்ச்சி மதிப்பிடப்படவில்லை.\nஇதைப் பார்க்க, \"Right\" என்பதை அழுத்தி, உங்கள் பின்னை உள்ளிடவும்" + "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது.\nஅதைப் பார்க்க, வலது பக்கம் அழுத்தி, பின்னை உள்ளிடவும்." + "இந்தச் சேனலைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." + "இந்த நிகழ்ச்சியைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." + "இந்த நிகழ்ச்சி மதிப்பிடப்படவில்லை.\nஇதைப் பார்க்க, இயல்பு லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." + "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது.\nஇந்த நிகழ்ச்சியைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." + "நிகழ்ச்சி தடுக்கப்பட்டுள்ளது" + "இந்த நிகழ்ச்சி மதிப்பிடப்படவில்லை" + "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது" + "ஆடியோ மட்டும்" + "வலுவற்ற சிக்னல்" + "இணைய இணைப்பு இல்லை" + + மற்ற சேனல்கள் ரெக்கார்டு செய்யப்படுவதால், %1$s வரை இந்தச் சேனலை இயக்க முடியாது. \n\nரெக்கார்டு செய்வதற்கான திட்ட அட்டவணையில் மாற்றம் செய்ய, \"Right\" என்பதை அழுத்தவும். + மற்றொரு சேனல் ரெக்கார்டு செய்யப்படுவதால், %1$s வரை இந்தச் சேனலை இயக்க முடியாது. \n\nரெக்கார்டு செய்வதற்கான திட்ட அட்டவணையில் மாற்றம் செய்ய, \"Right\" என்பதை அழுத்தவும். + + "தலைப்பு இல்லை" + "சேனல் தடுக்கப்பட்டது" + "புதியவை" + "மூலங்கள்" + + %1$d சேனல்கள் + %1$d சேனல் + + "சேனல்கள் எதுவுமில்லை" + "புதியவை" + "அமைக்கப்படவில்லை" + "கூடுதல் ஆதாரங்களைப் பெறுக" + "நேரலை சேனல்களை வழங்கும் பயன்பாடுகளைத் தேடலாம்" + "புதிய சேனல் மூலங்கள் உள்ளன" + "புதிய சேனல் மூலங்களில் பல சேனல்களைப் பெறலாம்.\nஅவற்றை இப்போதே அமைக்கவும் அல்லது சேனல் மூலங்கள் அமைப்பில் பிறகு அமைக்கவும்." + "இப்போது அமை" + "சரி" + + + "TV மெனுவை அணுக, ""SELECTஐ அழுத்தவும்""." + "டிவி உள்ளீடு இல்லை" + "டிவி உள்ளீடு இல்லை" + "ட்யூனர் வகை பொருந்தவில்லை. ட்யூனர் வகை டிவி உள்ளீட்டிற்கு நேரலைச் சேனல்கள் பயன்பாட்டைத் துவங்கவும்." + "ட்யூன் செய்ய முடியவில்லை" + "இந்தச் செயலைச் செய்வதற்கான பயன்பாடு எதுவுமில்லை." + "எல்லா சேனல்களும் மறைக்கப்பட்டுள்ளன.\nபார்க்க, ஒரு சேனலையாவது தேர்ந்தெடுக்கவும்." + "வீடியோ கிடைக்கவில்லை" + "Back விசை இணைத்த சாதனத்திற்கானது. வெளியேற, Home பட்டனை அழுத்துக." + "டிவி பட்டியல்களைப் படிக்க, நேரலைச் சேனல்களுக்கு அனுமதி தேவை." + "மூலங்களை அமைக்கவும்" + "நேரலைச் சேனல்களானது பயன்பாடுகள் வழங்கும் ஸ்ட்ரீமிங் சேனல்களுடன் பாரம்பரிய டிவி சேனல்களின் அனுபவத்தை ஒன்றிணைக்கிறது. \n\nஏற்கனவே நிறுவிய சேனல் மூலங்களை அமைப்பதன் மூலம் தொடங்கவும் அல்லது நேரலைச் சேனல்களை வழங்கும் கூடுதல் பயன்பாடுகளைப் பெற, Google Play ஸ்டோரில் தேடவும்." + "ரெக்கார்டிங்குகள் & திட்ட அட்டவணைகள்" + "10 நிமிடங்கள்" + "30 நிமிடங்கள்" + "1 மணிநேரம்" + "3 மணிநேரம்" + "சமீபத்தியவை" + "திட்டமிடப்பட்டது" + "தொடர்" + "மற்றவை" + "இந்தச் சேனலை ரெக்கார்டு செய்ய முடியாது." + "இந்த நிகழ்ச்சியை ரெக்கார்டு செய்ய முடியாது." + "%1$s ரெக்கார்டு செய்வதற்காகத் திட்டமிடப்பட்டது" + "இப்போதிலிருந்து %2$s வரை %1$s ரெக்கார்டு செய்யப்படுகிறது" + "முழுத் திட்ட அட்டவணை" + + அடுத்த %1$d நாட்கள் + அடுத்த %1$d நாள் + + + %1$d நிமிடங்கள் + %1$d நிமிடம் + + + %1$d புதிய ரெக்கார்டிங்குகள் + %1$d புதிய ரெக்கார்டிங் + + + %1$d ரெக்கார்டிங்குகள் + %1$d ரெக்கார்டிங் + + + %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டன + %1$d ரெக்கார்டிங் திட்டமிடப்பட்டது + + "ரெக்கார்டிங்கை ரத்துசெய்" + "ரெக்கார்டிங்கை நிறுத்து" + "இயக்கு" + "முதலிலிருந்து இயக்கு" + "மீண்டும் தொடங்கு" + "நீக்கு" + "ரெக்கார்டிங்குகளை நீக்கு" + "மீண்டும்தொடங்கு" + "சீசன் %1$s" + "ஷெட்யூலை காட்டு" + "மேலும் படிக்க" + "ரெக்கார்டிங்குகளை நீக்கு" + "நீக்க விரும்பும் எபிசோடுகளைத் தேர்ந்தெடுக்கவும். ஒருமுறை நீக்கிவிட்டால், அவற்றை மீட்டெடுக்க முடியாது." + "நீக்குவதற்கான ரெக்கார்டிங்குகள் இல்லை." + "பார்த்த எபிசோடுகளை தேர்ந்தெடு" + "எல்லாம் தேர்ந்தெடு" + "எல்லாம் தேர்வு நீக்கு" + "%2$d இல் %1$d நிமிடங்கள் பார்த்துள்ளீர்கள்" + "%2$d இல் %1$d வினாடிகள் பார்த்துள்ளீர்கள்" + "இதுவரை பார்க்காதவை" + + %2$d இல் %1$d எபிசோடுகள் நீக்கப்பட்டன + %2$d இல் %1$d எபிசோடு நீக்கப்பட்டது + + "முன்னுரிமை" + "மிக அதிக முன்னுரிமை" + "மிகக்குறைந்த முன்னுரிமை" + "முன்னுரிமை: %1$d" + "சேனல்கள்" + "எந்தச் சேனலும்" + "முன்னுரிமையைத் தேர்வுசெய்க" + "ஒரே நேரத்தில் பல நிகழ்ச்சிகளை ரெக்கார்டு செய்ய வேண்டியிருந்தால், அதிக முன்னுரிமைகளைக் கொண்ட நிகழ்ச்சிகள் மட்டுமே ரெக்கார்டு செய்யப்படும்." + "சேமி" + "ஒரே முறை ரெக்கார்டு செய்யக்கூடியவற்றுக்கு மிக அதிக முன்னுரிமை வழங்கு" + "நிறுத்து" + "ரெக்கார்டிங் ஷெட்யூலைக் காட்டு" + "இந்த நிகழ்ச்சியை மட்டும்" + "இப்போதிலிருந்து %1$s வரை" + "தொடர் முழுவதும்…" + "பரவாயில்லை, திட்டமிடு" + "பதிலாக, இதை ரெக்கார்டு செய்" + "இந்த ரெக்கார்டிங்கை ரத்துசெய்" + "இப்போது காட்டு" + "ரெக்கார்டிங்குகளை நீக்கு…" + "ரெக்கார்டு செய்யக்கூடியது" + "ரெக்கார்டிங் திட்டமிடப்பட்டது" + "ரெக்கார்டிங்கில் முரண்பாடு" + "ரெக்கார்ட் செய்யப்படுகிறது" + "ரெக்கார்டு செய்ய முடியவில்லை" + "நிகழ்ச்சிகளைப் படிக்கிறது" + "சமீபத்திய ரெக்கார்டிங்குகளைக் காட்டு" + "%1$sஐ ரெக்கார்டு செய்வது முடிவடையவில்லை." + "%1$s மற்றும் %2$sஐ ரெக்கார்டு செய்வது முடிவடையவில்லை." + "%1$s, %2$s, %3$s ஆகியவற்றை ரெக்கார்டு செய்வது முடிவடையவில்லை." + "போதுமான சேமிப்பிடம் இல்லாததால், %1$sஐ ரெக்கார்டு செய்வது முடிவடையவில்லை." + "போதுமான சேமிப்பிடம் இல்லாததால், %1$s மற்றும் %2$sஐ ரெக்கார்டு செய்வது முடிவடையவில்லை." + "போதுமான சேமிப்பிடம் இல்லாததால், %1$s, %2$s, %3$s ஆகியவற்றை ரெக்கார்டு செய்வது முடிவடையவில்லை." + "DVRக்கு அதிகச் சேமிப்பிடம் தேவை" + "நீங்கள் DVR மூலம் நிகழ்ச்சிகளைப் பதிவுசெய்ய முடியும். எனினும், DVR சரியாக வேலை செய்வதற்குத் தேவைப்படும் போதுமான சேமிப்பகம் இப்போது சாதனத்தில் இல்லை. %1$dஜி.பை. அல்லது அதற்கும் அதிகமான அளவில் உள்ள வெளிப்புற இயக்ககத்தை இணைக்கவும். பின் அதைச் சாதனச் சேமிப்பகமாகப் பயன்படுத்த, இந்தப் படிகளைப் பின்பற்றவும்." + "போதுமான சேமிப்பிடம் இல்லை" + "போதுமான சேமிப்பிடம் இல்லாததால், இந்த நிகழ்ச்சி ரெக்கார்டு செய்யப்படாது. ஏற்கனவே உள்ள சில ரெக்கார்டிங்குகளை நீக்கவும்." + "சேமிப்பகம் இல்லை" + "ரெக்கார்டு செய்வதை நிறுத்தவா?" + "ரெக்கார்டு செய்த உள்ளடக்கம் சேமிக்கப்படும்." + "%1$sஐ ரெக்கார்டு செய்வது இந்த நிகழ்ச்சியுடன் முரண்படுவதால், ரெக்கார்டிங் நிறுத்தப்படும். ரெக்கார்டு செய்த உள்ளடக்கம் சேமிக்கப்படும்." + "ரெக்கார்டிங் திட்டமிடப்பட்டது, ஆனால் முரண்பாடுகள் உள்ளன" + "ரெக்கார்டு செய்வது தொடங்கப்பட்டது, ஆனால் முரண்பாடுகள் உள்ளன" + "%1$s ரெக்கார்டு செய்யப்படும்." + "%1$s ரெக்கார்டு செய்யப்படுகிறது." + "%1$s இன் சில பகுதிகள் ரெக்கார்டு செய்யப்படாது." + "%1$s, %2$s ஆகியவற்றின் சில பகுதிகள் ரெக்கார்டு செய்யப்படாது." + "%1$s, %2$s இன் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் ஒரு நிகழ்ச்சியும் ரெக்கார்டு செய்யப்படாது." + + %1$s, %2$s ஆகியவற்றின் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் %3$d நிகழ்ச்சிகளும் ரெக்கார்டு செய்யப்படாது. + %1$s, %2$s ஆகியவற்றின் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் %3$d நிகழ்ச்சியும் ரெக்கார்டு செய்யப்படாது. + + "எதை ரெக்கார்டு செய்ய விரும்புகிறீர்கள்?" + "எவ்வளவு நேரம் ரெக்கார்டு செய்ய விரும்புகிறீர்கள்?" + "ஏற்கனவே திட்டமிடப்பட்டுள்ளது" + "%1$sக்கு ரெக்கார்டு செய்வதற்காக இந்த நிகழ்ச்சி ஏற்கனவே திட்டமிடப்பட்டுள்ளது." + "ஏற்கனவே ரெக்கார்டு செய்யப்பட்டது" + "இந்த நிகழ்ச்சி ஏற்கனவே ரெக்கார்டு செய்யப்பட்டது. மேலும் DVR நூலகத்தில் கிடைக்கும்." + "தொடர் ரெக்கார்டிங் திட்டமிடப்பட்டது" + + %2$s தொடருக்கு %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டுள்ளன. + %2$s தொடருக்கு %1$d ரெக்கார்டிங் திட்டமிடப்பட்டுள்ளது. + + + %2$s தொடருக்கு %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டுள்ளன. முரண்பாடுகளின் காரணமாக, இந்தத் தொடரின் %3$d எபிசோடுகள் ரெக்கார்டு செய்யப்படாது. + %2$s தொடருக்கு %1$d ரெக்கார்டிங் திட்டமிடப்பட்டுள்ளது. முரண்பாடுகளின் காரணமாக, இது ரெக்கார்டு செய்யப்படாது. + + + %2$s தொடருக்கு %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டுள்ளன. முரண்பாடுகளின் காரணமாக, இந்தத் தொடர் மற்றும் பிற தொடரின் %3$d எபிசோடுகள் ரெக்கார்டு செய்யப்படாது. + %2$s தொடருக்கு %1$d ரெக்கார்டிங் திட்டமிடப்பட்டுள்ளது. முரண்பாடுகளின் காரணமாக, இந்தத் தொடர் மற்றும் பிற தொடரின் %3$d எபிசோடுகள் ரெக்கார்டு செய்யப்படாது. + + + %2$s தொடருக்கு %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டுள்ளன. முரண்பாடுகளின் காரணமாக, பிற தொடரின் ஒரு எபிசோடு ரெக்கார்டு செய்யப்படாது. + %2$s தொடருக்கு %1$d ரெக்கார்டிங் திட்டமிடப்பட்டுள்ளது. முரண்பாடுகளின் காரணமாக, பிற தொடரின் ஒரு எபிசோடு ரெக்கார்டு செய்யப்படாது. + + + %2$s தொடருக்கு %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டுள்ளன. முரண்பாடுகளின் காரணமாக, பிற தொடரின் %3$d எபிசோடுகள் ரெக்கார்டு செய்யப்படாது. + %2$s தொடருக்கு %1$d ரெக்கார்டிங் திட்டமிடப்பட்டுள்ளது. முரண்பாடுகளின் காரணமாக, பிற தொடரின் %3$d எபிசோடுகள் ரெக்கார்டு செய்யப்படாது. + + "ரெக்கார்டு செய்த நிகழ்ச்சி இல்லை." + "தொடர்புடைய ரெக்கார்டிங்குகள்" + + %1$d ரெக்கார்டிங்குகள் + %1$d ரெக்கார்டிங் + + " / " + "ரெக்கார்டிங் திட்ட அட்டவணையிலிருந்து %1$s அகற்றப்பட்டது" + "ட்யூனர் இல்லாததால், பகுதியளவு ரெக்கார்டு செய்யப்படும்." + "ட்யூனர் இல்லாததால், ரெக்கார்டு செய்யப்படாது." + "ரெக்கார்டு செய்வதற்காக எதுவும் திட்டமிடப்படவில்லை.\nநிகழ்ச்சி வழிகாட்டிக்குச் சென்று, ரெக்கார்டிங்கைத் திட்டமிடலாம்." + + %1$d ரெக்கார்டிங் முரண்பாடுகள் + %1$d ரெக்கார்டிங் முரண்பாடு + + "தொடர் அமைப்புகள்" + "தொடர் ரெக்கார்டிங்கைத் தொடங்கு" + "தொடர் ரெக்கார்டிங்கை நிறுத்து" + "தொடர் ரெக்கார்டிங்கை நிறுத்தவா?" + "ரெக்கார்டு செய்யப்பட எபிசோடுகள் தொடர்ந்து DVR நூலகத்தில் இருக்கும்." + "நிறுத்து" + "இப்போது எந்த எபிசோடுகளும் நேரலையில் இல்லை." + "எபிசோடுகள் இல்லை.\nஅவை கிடைக்கும் போது ரெக்கார்டு செய்யப்படும்." + + (%1$d நிமிடங்கள்) + (%1$d நிமிடம்) + + "இன்று" + "நாளை" + "நேற்று" + "இன்று %1$s" + "நாளை %1$s" + "ஸ்கோர்" + "ரெக்கார்டு செய்த நிகழ்ச்சிகள்" + diff --git a/res/values-ta-v23/strings.xml b/res/values-ta-v23/strings.xml deleted file mode 100644 index 1b94e0c4..00000000 --- a/res/values-ta-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "சேனல்கள்" - diff --git a/res/values-ta/arrays.xml b/res/values-ta/arrays.xml deleted file mode 100644 index 9d2c1f24..00000000 --- a/res/values-ta/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "இயல்பு" - "முழு" - "பெரிதாக்கு" - - - "எல்லா சேனல்களும்" - "குடும்பம்/சிறுவர்கள்" - "விளையாட்டு" - "ஷாப்பிங்" - "மூவிகள்" - "நகைச்சுவை" - "பயணம்" - "நாடகம்" - "கல்வி" - "விலங்குகள்" - "செய்திகள்" - "கேமிங்" - "கலைகள்" - "பொழுதுபோக்கு" - "வாழ்க்கை முறை" - "இசை" - "ப்ரீமியர்" - "தொழில்நுட்பம்/அறிவியல்" - - - "நேரலைச் சேனல்கள்" - "உள்ளடக்கத்தைத் தேடுவதற்கான எளிய வழி" - "பயன்பாடுகளை இறக்கி, கூடுதல் சேனல்களைப் பெறலாம்" - "சேனல் வரிசையைத் தனிப்பயனாக்கலாம்" - - - "டிவியில் சேனல்களைப் பார்ப்பதைப் போன்றே, பயன்பாடுகளின் உள்ளடக்கத்தைப் பார்க்கலாம்." - "டிவியில் சேனல்களைப் பார்ப்பதைப் போன்றே, \nபயன்படுத்துவதற்கு எளிதான வழிகாட்டி மற்றும் அறிமுகமான இடைமுகத்தின் மூலம் பயன்பாடுகளின் உள்ளடக்கத்தில் உலாவலாம்." - "நேரலைச் சேனல்களை வழங்கும் பயன்பாடுகளை நிறுவி கூடுதல் சேனல்களைச் சேர்க்கலாம். \nடிவி மெனுவில் உள்ள இணைப்பைப் பயன்படுத்தி Google Play ஸ்டோரில் இணக்கமான பயன்பாடுகளைக் கண்டறியலாம்." - "சேனல் பட்டியலைத் தனிப்பயனாக்க, புதிதாக நிறுவிய சேனல் மூலங்களை அமைக்கலாம். \nதொடங்க, அமைப்புகள் மெனுவில் உள்ள சேனல் மூலங்களைத் தேர்வுசெய்யலாம்." - - diff --git a/res/values-ta/rating_system_strings.xml b/res/values-ta/rating_system_strings.xml deleted file mode 100644 index ae80dee6..00000000 --- a/res/values-ta/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "நிகழ்ச்சிகள் 15 வயதுக்குட்பட்ட பார்வையாளர்களுக்குப் பொருத்தமற்றதாக இருக்கலாம் என்பதால், பெற்றோர் முடிவு எடுக்க வேண்டும்." - "நிகழ்ச்சிகள் 19 வயதுக்குட்பட்ட பார்வையாளர்களுக்குப் பொருத்தமற்றதாக இருக்கலாம் என்பதால், அவை 19 வயதுக்குட்பட்டவர்களுக்கு ஏற்றதல்ல." - "பாலுணர்வைத் தூண்டும் உரையாடல்" - "கொச்சை மொழி" - "பாலியல் உள்ளடக்கம்" - "வன்முறை" - "ஃபேண்டஸி வன்முறை" - "இந்த நிகழ்ச்சி எல்லா வயதினருக்கும் ஏற்றவாறு உருவாக்கப்பட்டது." - "இந்த நிகழ்ச்சி 7 மற்றும் அதற்கு மேற்பட்ட வயதினருக்கு ஏற்றவாறு உருவாக்கப்பட்டது." - "பல பெற்றோர்கள் இந்த நிகழ்ச்சி எல்லா வயதினரும் பார்ப்பதற்கு உகந்தது எனக் கண்டறிந்துள்ளனர்." - "தங்களின் குழந்தைகள் பார்ப்பதற்கு ஏற்றது இல்லை எனப் பெற்றோர்கள் கருதக்கூடிய உள்ளடக்கம் இந்த நிகழ்ச்சியில் உள்ளது. பல பெற்றோர்கள் தங்கள் குழந்தைகளுடன் இதைப் பார்க்க விரும்பலாம்." - "இந்த நிகழ்ச்சியில், 14 வயதிற்குக் கீழ் உள்ள குழந்தைகள் பார்ப்பதற்கு உகந்தது அல்ல எனப் பல பெற்றோர்கள் கண்டறியக்கூடிய உள்ளடக்கம் உள்ளது." - "இந்த நிகழ்ச்சி, பெரியவர்களுக்கானது, எனவே 17 வயதுக்குக் கீழ் உள்ளவர்களுக்கு ஏற்றது அல்ல." - "திரைப்பட மதிப்பீடுகள்" - "பொதுப் பார்வையாளர்களுக்கானது. குழந்தைகள் பார்ப்பதைப் பற்றி பெற்றோர்கள் வருந்தத் தேவையில்லை." - "பெற்றோர் வழிகாட்டல் பரிந்துரைக்கப்படுகிறது. குழந்தைகளுக்கு ஏற்றது அல்ல எனப் பெற்றோர்கள் கருதக்கூடிய சில உள்ளடக்கம் இருக்கலாம்." - "பெற்றோர் மிகுந்த எச்சரிக்கையுடன் இருக்க வேண்டும். சில உள்ளடக்கம் இளம் சிறார்களுக்குப் பொருத்தமற்றதாக இருக்கலாம்." - "பெரியவர்களுக்கான உள்ளடக்கம் இருப்பதால் தடைசெய்யப்பட்டுள்ளது. குழந்தைகளுடன் காணச் செல்லும் முன், திரைப்படத்தைப் பற்றி மேலும் அறிந்துகொள்ளுமாறு பெற்றோர்கள் அறிவுறுத்தப்படுகின்றனர்." - "17 வயது மற்றும் அதற்குக் குறைவான வயதினருக்குப் பொருந்தாது. பெரியவர்களுக்கு மட்டுமே பொருந்தும். குழந்தைகளுக்கு அனுமதியில்லை." - diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml deleted file mode 100644 index 7d13e3cf..00000000 --- a/res/values-ta/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "மோனோ" - "ஸ்டீரியோ" - "இயக்கக் கட்டுப்பாடுகள்" - "சமீபத்திய சேனல்கள்" - "டிவி விருப்பங்கள்" - "PIP விருப்பங்கள்" - "இந்தச் சேனலுக்கு இயக்கக் கட்டுப்பாடுகள் இல்லை" - "இயக்கு அல்லது இடைநிறுத்து" - "வேகமாக முன் நகர்த்து" - "வேகமாக பின் நகர்த்து" - "அடுத்து" - "முந்தையது" - "நிகழ்ச்சிக் கையேடு" - "புதிய சேனல்கள் உள்ளன" - "%1$sஐத் திற" - "விரிவான வசனங்கள்" - "காட்சிப் பயன்முறை" - "PIP" - "இயக்கத்தில்" - "முடக்கத்தில்" - "மல்டி-ஆடியோ" - "மேலும் சேனல்கள்" - "அமைப்புகள்" - "மூலம்" - "மாற்று" - "இயக்கத்தில்" - "முடக்கத்தில்" - "ஒலி" - "முதன்மை" - "PIP சாளரம்" - "தளவமைப்பு" - "கீழ் வலதுபுறம்" - "மேல் வலதுபுறம்" - "மேல் இடதுபுறம்" - "கீழ் இடதுபுறம்" - "அருகருகே" - "அளவு" - "பெரியது" - "சிறியது" - "உள்ளீட்டு மூலம்" - "டிவி (ஆண்டெனா/கேபிள்)" - "நிகழ்ச்சி தகவல் இல்லை" - "தகவல் இல்லை" - "தடுக்கப்பட்ட சேனல்" - "தெரியாத மொழி" - "விரிவான வசனங்கள்" - "முடக்கு" - "வடிவமைப்பைத் தனிப்பயனாக்கவும்" - "விரிவான வசனங்களுக்கான முன்னுரிமைகளை முறைமை முழுவதும் அமைக்கவும்" - "காட்சிப் பயன்முறை" - "மல்டி-ஆடியோ" - "மோனோ" - "ஸ்டீரியோ" - "5.1 சரவுண்ட்" - "7.1 சரவுண்ட்" - "%d சேனல்கள்" - "சேனலை தனிப்படுத்து" - "குழுவைத் தேர்வுசெய்க" - "குழுவின் தேர்வைநீக்கு" - "இதன்படி குழுவாக்கு" - "சேனல் மூலம்" - "HD/SD" - "HD" - "SD" - "இதன்படி குழுவாக்கு" - "இந்த நிகழ்ச்சி தடுக்கப்பட்டுள்ளது" - "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது" - "தன்னியக்க ஸ்கேனை உள்ளீடு ஆதரிக்காது" - "’%s’க்கான தன்னியக்க ஸ்கேனைத் தொடங்க முடியவில்லை" - "விரிவான வசனங்களுக்கான சாதன விருப்பத்தேர்வுகளைத் தொடங்க முடியவில்லை." - - %1$d சேனல்கள் சேர்க்கப்பட்டன - %1$d சேனல் சேர்க்கப்பட்டது - - "சேனல்கள் எதுவும் சேர்க்கப்படவில்லை" - "ட்யூனர்" - "பெற்றோர் கட்டுப்பாடுகள்" - "இயக்கு" - "முடக்கு" - "சேனல்கள் தடுக்கப்பட்டன" - "எல்லாவற்றையும் தடு" - "எல்லாவற்றிலும் தடைநீக்கு" - "மறைக்கப்பட்ட சேனல்கள்" - "நிகழ்ச்சிக் கட்டுப்பாடுகள்" - "PIN ஐ மாற்று" - "மதிப்பிடல் அமைப்புகள்" - "மதிப்பீடுகள்" - "அனைத்து மதிப்பீட்டு அமைப்புகளையும் பார்க்கவும்" - "பிற நாடுகள்" - "ஏதுமில்லை" - "ஏதுமில்லை" - "ஏதுமில்லை" - "அதிகக் கட்டுப்பாடுகள்" - "நடுத்தரக் கட்டுப்பாடுகள்" - "குறைவான கட்டுப்பாடுகள்" - "தனிப்பயன்" - "குழந்தைகளுக்கு ஏற்றது" - "குழந்தைகளுக்கு ஏற்றது" - "பதின்ம வயதினருக்கு ஏற்றது" - "கைமுறைக் கட்டுப்பாடுகள்" - - - "%1$s மற்றும் துணை மதிப்பீடுகள்" - "துணை மதிப்பிடல்கள்" - "இந்தச் சேனலைப் பார்க்க பின்னை உள்ளிடவும்" - "இந்த நிகழ்ச்சியைப் பார்க்க பின்னை உள்ளிடவும்" - "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது. நிகழ்ச்சியைப் பார்க்க, உங்கள் பின்னை உள்ளிடவும்" - "பின்னை உள்ளிடவும்" - "பெற்றோர் கட்டுப்பாடுகளை அமைக்க, PINஐ உருவாக்கவும்" - "புதிய PINஐ உள்ளிடவும்" - "PINஐ உறுதிசெய்யவும்" - "உங்கள் தற்போதைய PINஐ உள்ளிடவும்" - - பின்னை 5 முறை தவறாக உள்ளிட்டுள்ளீர்கள்.\n%1$d வினாடிகளில் மீண்டும் முயலவும். - பின்னை 5 முறை தவறாக உள்ளிட்டுள்ளீர்கள்.\n%1$d வினாடியில் மீண்டும் முயலவும். - - "PIN தவறானது. மீண்டும் முயலவும்." - "மீண்டும் முயலவும், PIN பொருந்தவில்லை" - "அமைப்புகள்" - "சேனல் பட்டியலைத் தனிப்பயனாக்கு" - "நிகழ்ச்சி வழிகாட்டியில் சேர்ப்பதற்கான சேனல்களைத் தேர்வுசெய்க" - "சேனல் மூலங்கள்" - "புதிய சேனல்கள் உள்ளன" - "பெற்றோர் கட்டுப்பாடுகள்" - "ஓப்பன் சோர்ஸ் உரிமங்கள்" - "ஓப்பன் சோர்ஸ் உரிமங்கள்" - "பதிப்பு" - "இந்தச் சேனலைப் பார்க்க, வலது பக்கம் அழுத்தி, உங்கள் PINஐ உள்ளிடவும்" - "இந்த நிகழ்ச்சியைப் பார்க்க, வலது பக்கம் அழுத்தி PINஐ உள்ளிடவும்" - "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது.\nஅதைப் பார்க்க, வலது பக்கம் அழுத்தி, பின்னை உள்ளிடவும்." - "இந்தச் சேனலைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." - "இந்த நிகழ்ச்சியைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." - "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது.\nஇந்த நிகழ்ச்சியைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்." - "நிகழ்ச்சி தடுக்கப்பட்டுள்ளது" - "இந்த நிகழ்ச்சி %1$s என மதிப்பிடப்பட்டுள்ளது" - "ஆடியோ மட்டும்" - "வலுவற்ற சிக்னல்" - "இணைய இணைப்பு இல்லை" - - மற்ற சேனல்கள் ரெக்கார்டு செய்யப்படுவதால், %1$s வரை இந்தச் சேனலை இயக்க முடியாது. \n\nரெக்கார்டு செய்வதற்கான திட்ட அட்டவணையில் மாற்றம் செய்ய, \"Right\" என்பதை அழுத்தவும். - மற்றொரு சேனல் ரெக்கார்டு செய்யப்படுவதால், %1$s வரை இந்தச் சேனலை இயக்க முடியாது. \n\nரெக்கார்டு செய்வதற்கான திட்ட அட்டவணையில் மாற்றம் செய்ய, \"Right\" என்பதை அழுத்தவும். - - "தலைப்பு இல்லை" - "சேனல் தடுக்கப்பட்டது" - "புதியவை" - "மூலங்கள்" - - %1$d சேனல்கள் - %1$d சேனல் - - "சேனல்கள் எதுவுமில்லை" - "புதியவை" - "அமைக்கப்படவில்லை" - "கூடுதல் ஆதாரங்களைப் பெறுக" - "நேரலை சேனல்களை வழங்கும் பயன்பாடுகளைத் தேடலாம்" - "புதிய சேனல் மூலங்கள் உள்ளன" - "புதிய சேனல் மூலங்களில் பல சேனல்களைப் பெறலாம்.\nஅவற்றை இப்போதே அமைக்கவும் அல்லது சேனல் மூலங்கள் அமைப்பில் பிறகு அமைக்கவும்." - "இப்போது அமை" - "சரி" - - - "TV மெனுவை அணுக, ""SELECTஐ அழுத்தவும்""." - "டிவி உள்ளீடு இல்லை" - "டிவி உள்ளீடு இல்லை" - "PIP ஆதரிக்கப்படவில்லை" - "PIP உடன் காண்பிக்கத்தக்க உள்ளீடு எதுவுமில்லை" - "ட்யூனர் வகை பொருந்தவில்லை. ட்யூனர் வகை டிவி உள்ளீட்டிற்கு நேரலைச் சேனல்கள் பயன்பாட்டைத் துவங்கவும்." - "ட்யூன் செய்ய முடியவில்லை" - "இந்தச் செயலைச் செய்வதற்கான பயன்பாடு எதுவுமில்லை." - "எல்லா சேனல்களும் மறைக்கப்பட்டுள்ளன.\nபார்க்க, ஒரு சேனலையாவது தேர்ந்தெடுக்கவும்." - "வீடியோ கிடைக்கவில்லை" - "Back விசை இணைத்த சாதனத்திற்கானது. வெளியேற, Home பட்டனை அழுத்துக." - "டிவி பட்டியல்களைப் படிக்க, நேரலைச் சேனல்களுக்கு அனுமதி தேவை." - "மூலங்களை அமைக்கவும்" - "நேரலைச் சேனல்களானது பயன்பாடுகள் வழங்கும் ஸ்ட்ரீமிங் சேனல்களுடன் பாரம்பரிய டிவி சேனல்களின் அனுபவத்தை ஒன்றிணைக்கிறது. \n\nஏற்கனவே நிறுவிய சேனல் மூலங்களை அமைப்பதன் மூலம் தொடங்கவும் அல்லது நேரலைச் சேனல்களை வழங்கும் கூடுதல் பயன்பாடுகளைப் பெற, Google Play ஸ்டோரில் தேடவும்." - "ரெக்கார்டிங்குகள் & திட்ட அட்டவணைகள்" - "10 நிமிடங்கள்" - "30 நிமிடங்கள்" - "1 மணிநேரம்" - "3 மணிநேரம்" - "சமீபத்தியவை" - "திட்டமிடப்பட்டது" - "தொடர்" - "மற்றவை" - "இந்தச் சேனலை ரெக்கார்டு செய்ய முடியாது." - "இந்த நிகழ்ச்சியை ரெக்கார்டு செய்ய முடியாது." - "%1$s ரெக்கார்டு செய்வதற்காகத் திட்டமிடப்பட்டது" - "இப்போதிலிருந்து %2$s வரை %1$s ரெக்கார்டு செய்யப்படுகிறது" - "முழுத் திட்ட அட்டவணை" - - அடுத்த %1$d நாட்கள் - அடுத்த %1$d நாள் - - - %1$d நிமிடங்கள் - %1$d நிமிடம் - - - %1$d புதிய ரெக்கார்டிங்குகள் - %1$d புதிய ரெக்கார்டிங் - - - %1$d ரெக்கார்டிங்குகள் - %1$d ரெக்கார்டிங் - - - %1$d ரெக்கார்டிங்குகள் திட்டமிடப்பட்டன - %1$d ரெக்கார்டிங் திட்டமிடப்பட்டது - - "இயக்கு" - "முதலிலிருந்து இயக்கு" - "மீண்டும் தொடங்கு" - "நீக்கு" - "ரெக்கார்டிங்குகளை நீக்கு" - "மீண்டும்தொடங்கு" - "சீசன் %1$s" - "ஷெட்யூலை காட்டு" - "மேலும் படிக்க" - "ரெக்கார்டிங்குகளை நீக்கு" - "நீக்க விரும்பும் எபிசோடுகளைத் தேர்ந்தெடுக்கவும். ஒருமுறை நீக்கிவிட்டால், அவற்றை மீட்டெடுக்க முடியாது." - "நீக்குவதற்கான ரெக்கார்டிங்குகள் இல்லை." - "பார்த்த எபிசோடுகளை தேர்ந்தெடு" - "எல்லாம் தேர்ந்தெடு" - "எல்லாம் தேர்வு நீக்கு" - "%2$d இல் %1$d நிமிடங்கள் பார்த்துள்ளீர்கள்" - "%2$d இல் %1$d வினாடிகள் பார்த்துள்ளீர்கள்" - "இதுவரை பார்க்காதவை" - - %2$d இல் %1$d எபிசோடுகள் நீக்கப்பட்டன - %2$d இல் %1$d எபிசோடு நீக்கப்பட்டது - - "முன்னுரிமை" - "மிக அதிக முன்னுரிமை" - "மிகக்குறைந்த முன்னுரிமை" - "முன்னுரிமை: %1$d" - "சேனல்கள்" - "எந்தச் சேனலும்" - "முன்னுரிமையைத் தேர்வுசெய்க" - "ஒரே நேரத்தில் பல நிகழ்ச்சிகளை ரெக்கார்டு செய்ய வேண்டியிருந்தால், அதிக முன்னுரிமைகளைக் கொண்ட நிகழ்ச்சிகள் மட்டுமே ரெக்கார்டு செய்யப்படும்." - "சேமி" - "ஒரே முறை ரெக்கார்டு செய்யக்கூடியவற்றுக்கு மிக அதிக முன்னுரிமை வழங்கு" - "ரத்துசெய்" - "ரத்துசெய்" - "நீக்கு" - "நிறுத்து" - "ரெக்கார்டிங் ஷெட்யூலைக் காட்டு" - "இந்த நிகழ்ச்சியை மட்டும்" - "இப்போதிலிருந்து %1$s வரை" - "தொடர் முழுவதும்…" - "பரவாயில்லை, திட்டமிடு" - "பதிலாக, இதை ரெக்கார்டு செய்" - "இந்த ரெக்கார்டிங்கை ரத்துசெய்" - "இப்போது காட்டு" - "ரெக்கார்டு செய்யக்கூடியது" - "ரெக்கார்டிங் திட்டமிடப்பட்டது" - "ரெக்கார்டிங்கில் முரண்பாடு" - "ரெக்கார்ட் செய்யப்படுகிறது" - "ரெக்கார்டு செய்ய முடியவில்லை" - "ரெக்கார்டிங் ஷெட்யூல்களை உருவாக்க, நிகழ்ச்சிகளைப் படிக்கிறது" - "நிகழ்ச்சிகளைப் படிக்கிறது" - - - "DVRக்கு அதிகச் சேமிப்பிடம் தேவை" - "நீங்கள் DVR மூலம் நிகழ்ச்சிகளைப் பதிவுசெய்ய முடியும். எனினும், DVR சரியாக வேலை செய்வதற்குத் தேவைப்படும் போதுமான சேமிப்பகம் இப்போது சாதனத்தில் இல்லை. %1$sஜி.பை. அல்லது அதற்கும் அதிகமான அளவில் உள்ள வெளிப்புற இயக்ககத்தை இணைக்கவும். பின் அதைச் சாதனச் சேமிப்பகமாகப் பயன்படுத்த, இந்தப் படிகளைப் பின்பற்றவும்." - "சேமிப்பகம் இல்லை" - "DVR பயன்படுத்தும் சேமிப்பகம் இல்லை. DVRஐ மீண்டும் இயக்கும் முன், நீங்கள் பயன்படுத்தும் வெளிப்புற டிரைவை இணைக்கவும். மாற்றுவழியாக, சேமிப்பகத்தை இனி பயன்படுத்த மாட்டீர்கள் எனில், அதை நீக்கும்படியும் தேர்ந்தெடுக்கலாம்." - "சேமிப்பகத்தை நீக்கவா?" - "பதிவுசெய்த உள்ளடக்கத்தையும் திட்ட அட்டவணைகளையும் இழப்பீர்கள்." - "ரெக்கார்டு செய்வதை நிறுத்தவா?" - "ரெக்கார்டு செய்த உள்ளடக்கம் சேமிக்கப்படும்." - - - "ரெக்கார்டிங் திட்டமிடப்பட்டது, ஆனால் முரண்பாடுகள் உள்ளன" - "ரெக்கார்டு செய்வது தொடங்கப்பட்டது, ஆனால் முரண்பாடுகள் உள்ளன" - "%1$s ரெக்கார்டு செய்யப்படும்." - "%1$s ரெக்கார்டு செய்யப்படுகிறது." - "%1$s இன் சில பகுதிகள் ரெக்கார்டு செய்யப்படாது." - "%1$s, %2$s ஆகியவற்றின் சில பகுதிகள் ரெக்கார்டு செய்யப்படாது." - "%1$s, %2$s இன் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் ஒரு நிகழ்ச்சியும் ரெக்கார்டு செய்யப்படாது." - - %1$s, %2$s ஆகியவற்றின் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் %3$d நிகழ்ச்சிகளும் ரெக்கார்டு செய்யப்படாது. - %1$s, %2$s ஆகியவற்றின் சில பகுதிகளும், ரெக்கார்டு செய்யத் திட்டமிட்டிருந்த மேலும் %3$d நிகழ்ச்சியும் ரெக்கார்டு செய்யப்படாது. - - "எதை ரெக்கார்டு செய்ய விரும்புகிறீர்கள்?" - "எவ்வளவு நேரம் ரெக்கார்டு செய்ய விரும்புகிறீர்கள்?" - "ஏற்கனவே திட்டமிடப்பட்டுள்ளது" - "%1$sக்கு ரெக்கார்டு செய்வதற்காக இந்த நிகழ்ச்சி ஏற்கனவே திட்டமிடப்பட்டுள்ளது." - "ஏற்கனவே ரெக்கார்டு செய்யப்பட்டது" - "இந்த நிகழ்ச்சி ஏற்கனவே ரெக்கார்டு செய்யப்பட்டது. மேலும் DVR நூலகத்தில் கிடைக்கும்." - - - - - - - - - "ரெக்கார்டு செய்த நிகழ்ச்சி இல்லை." - "தொடர்புடைய ரெக்கார்டிங்குகள்" - "(நிகழ்ச்சி விளக்கம் இல்லை)" - - %1$d ரெக்கார்டிங்குகள் - %1$d ரெக்கார்டிங் - - " / " - "ரெக்கார்டிங் திட்ட அட்டவணையிலிருந்து %1$s அகற்றப்பட்டது" - "ட்யூனர் இல்லாததால், பகுதியளவு ரெக்கார்டு செய்யப்படும்." - "ட்யூனர் இல்லாததால், ரெக்கார்டு செய்யப்படாது." - "ரெக்கார்டு செய்வதற்காக எதுவும் திட்டமிடப்படவில்லை.\nநிகழ்ச்சி வழிகாட்டிக்குச் சென்று, ரெக்கார்டிங்கைத் திட்டமிடலாம்." - - %1$d ரெக்கார்டிங் முரண்பாடுகள் - %1$d ரெக்கார்டிங் முரண்பாடு - - "தொடர் அமைப்புகள்" - "தொடர் ரெக்கார்டிங்கைத் தொடங்கு" - "தொடர் ரெக்கார்டிங்கை நிறுத்து" - "தொடர் ரெக்கார்டிங்கை நிறுத்தவா?" - "ரெக்கார்டு செய்யப்பட எபிசோடுகள் தொடர்ந்து DVR நூலகத்தில் இருக்கும்." - "நிறுத்து" - "எபிசோடுகள் இல்லை.\nஅவை கிடைக்கும் போது ரெக்கார்டு செய்யப்படும்." - - (%1$d நிமிடங்கள்) - (%1$d நிமிடம்) - - "இன்று" - "நாளை" - "நேற்று" - "இன்று %1$s" - "நாளை %1$s" - "ஸ்கோர்" - diff --git a/res/values-te-rIN-v23/strings.xml b/res/values-te-rIN-v23/strings.xml new file mode 100644 index 00000000..ac59e7af --- /dev/null +++ b/res/values-te-rIN-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "ఛానెల్‌లు" + diff --git a/res/values-te-rIN/arrays.xml b/res/values-te-rIN/arrays.xml new file mode 100644 index 00000000..de6abf32 --- /dev/null +++ b/res/values-te-rIN/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "సాధారణం" + "పూర్తిగా" + "జూమ్" + + + "అన్ని ఛానెల్‌లు" + "కుటుంబం/పిల్లలు" + "క్రీడలు" + "షాపింగ్" + "చలనచిత్రాలు" + "హాస్యం" + "ప్రయాణం" + "నాటకం" + "విద్య" + "జంతువు/వన్యప్రాణులు" + "వార్తలు" + "గేమింగ్" + "కళలు" + "వినోదం" + "జీవన శైలి" + "సంగీతం" + "ప్రీమియర్" + "సాంకేతికం/శాస్త్రం" + + + "ప్రత్యక్ష ప్రసార ఛానెల్‌లు" + "కంటెంట్‌ను కనుగొనడానికి సులభమైన మార్గం" + "అనువర్తనాలను డౌన్‌లోడ్ చేసుకోండి, మరిన్ని ఛానెల్‌లను పొందండి" + "మీ ఛానెల్ క్రమాన్ని అనుకూలీకరించండి" + + + "టీవీలో ఛానెల్‌లు చూస్తున్నట్లుగా మీ అనువర్తనాల్లో కంటెంట్‌ను చూడండి." + "టీవీలో ఛానెల్‌ల మాదిరిగా \nమీ అనువర్తనాల్లో కంటెంట్‌ను సుపరిచయ మార్గదర్శిని మరియు స్నేహపూర్వక ఇంటర్‌ఫేస్‌తో బ్రౌజ్ చేయండి." + "ప్రత్యక్ష ప్రసార ఛానెల్‌లను ఆఫర్ చేసే అనువర్తనాలను ఇన్‌స్టాల్ చేసుకోవడం ద్వారా మరిన్ని ఛానెల్‌లను జోడించండి. \nటీవీ మెనులోని లింక్‌ను ఉపయోగించడం ద్వారా Google Play స్టోర్‌లో అనుకూల అనువర్తనాలను కనుగొనండి." + "మీ ఛానెల్ జాబితాను అనుకూలీకరించడానికి కొత్తగా ఇన్‌స్టాల్ చేయబడిన మీ ఛానెల్ మూలాధారాలను సెటప్ చేయండి. \nప్రారంభించడానికి సెట్టింగ్‌ల మెనులో ఛానెల్ మూలాధారాలను ఎంచుకోండి." + + diff --git a/res/values-te-rIN/rating_system_strings.xml b/res/values-te-rIN/rating_system_strings.xml new file mode 100644 index 00000000..6179802b --- /dev/null +++ b/res/values-te-rIN/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "కార్యక్రమాల్లో 15 ఏళ్లలోపు ప్రేక్షకులకు అనుచితమైన విషయాలు ఉండవచ్చు, కనుక తల్లిదండ్రులు వారి అభీష్టానుసారంగా నిర్ణయం తీసుకోవాలి." + "కార్యక్రమాల్లో 19 ఏళ్లలోపు ప్రేక్షకులకు అనుచితమైన విషయాలు ఉండవచ్చు, కనుక 19 ఏళ్లలోపు యువతకు తగినవి కావు." + "సరస శృంగార వ్యాఖ్యలు" + "అనాగరిక భాష" + "శృంగార విషయాలు" + "హింస" + "కాల్పనిక హింస" + "ఈ కార్యక్రమం అన్ని వయస్సుల పిల్లలకు తగినట్లుగా రూపొందించబడింది." + "ఈ కార్యక్రమం 7 ఏళ్లు అంతకంటే పైబడిన పిల్లల కోసం రూపొందించబడింది." + "దాదాపు అందరు తల్లిదండ్రులు ఈ కార్యక్రమం అన్ని వయస్సుల వారికి తగినదని భావిస్తారు." + "ఈ కార్యక్రమంలో తల్లిదండ్రులు వారి చిన్న పిల్లలకు అనుచితమని భావించే అంశాలు ఉన్నాయి. చాలా మంది తల్లిదండ్రులు దీన్ని తమ చిన్న పిల్లలతో కలిసి చూడాలనుకోవచ్చు." + "ఈ కార్యక్రమంలో చాలామంది తల్లిదండ్రులు 14 ఏళ్లలోపు పిల్లలకు అనుచితమని భావించే కొన్ని అంశాలు ఉన్నాయి." + "ఈ కార్యక్రమం ప్రత్యేకించి పెద్దలు మాత్రమే వీక్షించడం కోసం రూపొందించబడింది, కనుక ఇది 17 ఏళ్లలోపు పిల్లలు చూడదగినది కాదు." + "చలన చిత్ర రేటింగ్‌లు" + "సాధారణ ప్రేక్షకులు. తల్లిదండ్రులు వారి పిల్లలు చూడకుండా ఆక్షేపించాల్సిన అంశాలు ఏమీ ఉండవు." + "తల్లిదండ్రుల మార్గదర్శకం సూచించడమైనది. తల్లిదండ్రులు వారి చిన్న పిల్లలకు తగదని భావించే కొన్ని అంశాలు ఉండవచ్చు." + "తల్లిదండ్రులకు గట్టి హెచ్చరిక. కొన్ని అంశాలు పూర్వ టీనేజీ దశ వారికి అనుచితంగా ఉండవచ్చు." + "నిషిద్ధం, కొన్ని వయోజన అంశాలు ఉన్నాయి. తల్లిదండ్రులు సినిమాకి వారి చిన్న పిల్లలను తీసుకెళ్లే ముందు సినిమా గురించి మరింత తెలుసుకోవాలని కోరుతున్నాము." + "17 ఏళ్లు అంతకంటే తక్కువ వయస్సు గల వారికి నిషిద్ధం. పూర్తిగా పెద్దలకు మాత్రమే. పిల్లలకు నిషిద్ధం." + diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml new file mode 100644 index 00000000..f6b8e538 --- /dev/null +++ b/res/values-te-rIN/strings.xml @@ -0,0 +1,357 @@ + + + + + "మోనో" + "స్టీరియో" + "ప్లే నియంత్రణలు" + "ఛానెల్‌లు" + "టీవీ ఎంపికలు" + "ఈ ఛానెల్ యొక్క ప్లే నియంత్రణలు అందుబాటులో లేవు" + "ప్లే చేస్తుంది లేదా పాజ్ చేస్తుంది" + "ఫాస్ట్ ఫార్వార్డ్ చేస్తుంది" + "రివైండ్ చేస్తుంది" + "తదుపరి" + "మునుపటి" + "ప్రోగ్రామ్ గైడ్" + "కొత్త ఛానె. ఉన్నప్పుడు" + "%1$sని తెరువు" + "సంవృత శీర్షికలు" + "ప్రదర్శన మోడ్" + "PIP" + "బహుళ-ఆడియో" + "మరిన్ని ఛానెల్‌లను పొందండి" + "సెట్టింగ్‌లు" + "టీవీ (యాంటెన్నా/కేబుల్)" + "కార్యక్రమ సమాచారం లేదు" + "సమాచారం లేదు" + "బ్లాక్ చేసిన ఛానెల్" + "భాష తెలియదు" + "ఉపశీర్షికలు %1$d" + "సంవృత శీర్షికలు" + "ఆఫ్ చేయి" + "ఆకృతీకరణను అనుకూలీకరించు" + "సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను సెట్ చేయి" + "ప్రదర్శన మోడ్" + "బహుళ-ఆడియో" + "మోనో" + "స్టీరియో" + "5.1 సరౌండ్" + "7.1 సరౌండ్" + "%d ఛానెల్‌లు" + "ఛానెల్‌ జాబితా అనుకూలీ." + "సమూహాన్ని ఎంచుకోండి" + "సమూహం ఎంపిక తీసివేయి" + "ఈ ప్రకారం సమూహం చేయి" + "ఛానెల్ మూలం" + "HD/SD" + "HD" + "SD" + "ఈ ప్రకారం సమూహం చేయి" + "ఈ కార్యక్రమం బ్లాక్ చేయబడింది" + "ఈ కార్యక్రమం రేట్ చేయబడలేదు" + "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది" + "స్వయంచాలకంగా స్కాన్ చేయడానికి ఇన్‌పుట్ మద్దతు ఇవ్వలేదు" + "\'%s\' కోసం ఆటో-స్కాన్‌ని ప్రారంభించడం సాధ్యపడలేదు" + "సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను ప్రారంభించడం సాధ్యపడలేదు." + + %1$d ఛానెల్‌లు జోడించబడ్డాయి + %1$d ఛానెల్ జోడించబడింది + + "ఛానెల్‌లు జోడించబడలేదు" + "తల్లిదండ్రుల నియంత్రణలు" + "ఆన్ చేయి" + "ఆఫ్ చేయి" + "ఛానె. బ్లాక్ చేయబ." + "అన్నీ బ్లాక్ చేయి" + "అన్నీ అన్‌బ్లాక్ చే." + "దాచబడిన ఛానెల్‌లు" + "ప్రోగ్రామ్ పరిమితులు" + "పిన్‌ను మార్చు" + "రేటింగ్ సిస్టమ్‌లు" + "రేటింగ్‌లు" + "అన్ని రేటింగ్ సిస్టమ్‌లను చూడండి" + "ఇతర దేశాలు" + "ఏదీ లేదు" + "ఏదీ లేదు" + "రేట్ చేయనిది" + "రేట్ చేయని కార్యక్రమాలను బ్లాక్ చేయండి" + "ఏదీ లేదు" + "అధిక పరిమితులు" + "మధ్యస్థ పరిమితులు" + "తక్కువ పరిమితులు" + "అనుకూలం" + "కంటెంట్ పిల్లలకు తగినది" + "కంటెంట్ యుక్తవయస్సు పిల్లలకు తగినది" + "కంటెంట్ టీనేజ్ వాళ్లకు తగినది" + "మాన్యువల్ పరిమితులు" + + + "%1$s, సబ్-రేటింగ్‌లు" + "సబ్-రేటింగ్‌లు" + "ఈ ఛానెల్‌ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి" + "ఈ కార్యక్రమాన్ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి" + "ఈ కార్యక్రమానికి %1$s అని రేట్ చేయబడింది. ఈ కార్యక్రమాన్ని చూడటానికి మీ PINని నమోదు చేయండి" + "ఈ కార్యక్రమం రేట్ చేయబడలేదు. ఈ కార్యక్రమాన్ని చూడటానికి మీ PINని నమోదు చేయండి" + "మీ పిన్‌ని నమోదు చేయండి" + "తల్లిదండ్రుల నియంత్రణలను సెట్ చేయడానికి, పిన్‌ని సృష్టించండి" + "కొత్త పిన్‌ని నమోదు చేయండి" + "మీ పిన్‌ను నిర్ధారించండి" + "మీ ప్రస్తుత పిన్‌ని నమోదు చేయండి" + + మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n%1$d సెకన్లు ఆగి, ఆ తర్వాత మళ్లీ ప్రయత్నించండి. + మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n%1$d సెకను ఆగి, ఆ తర్వాత మళ్లీ ప్రయత్నించండి. + + "ఆ పిన్ తప్పు. మళ్లీ ప్రయత్నించండి." + "మళ్లీ ప్రయత్నించండి, పిన్ సరిపోలలేదు" + "మీ జిప్ కోడ్‌ను నమోదు చేయండి." + "ప్రత్యక్ష ప్రసార ఛానెల్‌ల అనువర్తనం టీవీ ఛానెల్‌లకి సంబంధించిన పూర్తి ప్రోగ్రామ్ గైడ్‌ను అందించడానికి జిప్ కోడ్‌ను ఉపయోగిస్తుంది." + "మీ జిప్ కోడ్‌ను నమోదు చేయండి" + "జిప్ కోడ్ చెల్లదు" + "సెట్టింగ్‌లు" + "ఛానెల్‌ జాబితా అనుకూలీకరించండి" + "మీ ప్రోగ్రామ్ గైడ్ కోసం ఛానెల్‌లను ఎంచుకోండి" + "ఛానెల్ సోర్స్‌లు" + "కొత్త ఛానెల్‌లు అందుబాటులో ఉన్నాయి" + "తల్లిదండ్రుల నియంత్రణలు" + "టైమ్‌షిఫ్ట్" + "ప్రత్యక్ష ప్రసార కార్యక్రమాలను చూస్తున్నప్పుడు రికార్డ్ కూడా చేయండి, ఇలా చేయడం ద్వారా మీరు కోరుకున్నప్పుడు వాటిని పాజ్ లేదా రివైండ్ చేయవచ్చు.\nహెచ్చరిక: ఇది నిల్వను అధికంగా ఉపయోగించడం ద్వారా అంతర్గత నిల్వ జీవితకాలాన్ని తగ్గించవచ్చు." + "ఓపెన్ సోర్స్ లైసెన్స్‌లు" + "అభిప్రాయాన్ని పంపు" + "సంస్కరణ" + "ఈ ఛానెల్‌ను చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి" + "ఈ ప్రోగ్రామ్‌ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి" + "ఈ కార్యక్రమం రేట్ చేయబడలేదు.\nఈ కార్యక్రమాన్ని చూడటానికి, కుడి వైపున నొక్కి, మీ PINని నమోదు చేయండి" + "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది.\nఈ కార్యక్రమాన్ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ PINని నమోదు చేయండి." + "ఈ ఛానెల్‌ను చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." + "ఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." + "ఈ కార్యక్రమం రేట్ చేయబడలేదు.\nఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ ప్రత్యక్ష TV అప్లికేషన్‌ను ఉపయోగించండి." + "ఈ కార్యక్రమానికి %1$s అని రేట్ చేయబడింది.\nఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." + "కార్యక్రమం బ్లాక్ చేయబడింది" + "ఈ కార్యక్రమం రేట్ చేయబడలేదు" + "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది" + "ఆడియో మాత్రమే" + "సిగ్నల్ బలహీనంగా ఉంది" + "ఇంటర్నెట్ కనెక్షన్ లేదు" + + ఇతర ఛానెల్‌లు రికార్డ్ అవుతున్నందున ఈ ఛానెల్‌ని %1$s వరకు ప్లే చేయలేరు. \n\nరికార్డింగ్ షెడ్యూల్‌ను సర్దుబాటు చేయడానికి కుడివైపు నొక్కండి. + మరొక ఛానెల్ రికార్డ్ అవుతున్నందున ఈ ఛానెల్‌ని %1$s వరకు ప్లే చేయలేరు. \n\nరికార్డింగ్ షెడ్యూల్‌ను సర్దుబాటు చేయడానికి కుడివైపు నొక్కండి. + + "శీర్షిక లేదు" + "ఛానెల్ బ్లాక్ చేయబడింది" + "కొత్తవి" + "మూలాలు" + + %1$d ఛానెల్‌లు + %1$d ఛానెల్ + + "ఛానెల్‌లు ఏవీ అందుబాటులో లేవు" + "కొత్తవి" + "సెటప్ చేయలేదు" + "మరిన్ని మూలాధారాలను పొందండి" + "ప్రత్యక్ష ప్రసార ఛానెల్‌లను అందించే అనువర్తనాలను బ్రౌజ్ చేయండి" + "కొత్త ఛానెల్ మూలాలు అందుబాటులో ఉన్నాయి" + "కొత్త ఛానెల్ మూలాలు అందించడానికి ఛానెల్‌లను కలిగి ఉన్నాయి.\nవాటిని ఇప్పుడే సెటప్ చేయండి లేదా ఛానెల్ మూలాల సెట్టింగ్‌లో దీన్ని తర్వాత చేయండి." + "ఇప్పుడే సెటప్ చేయి" + "సరే, అర్థమైంది" + + + "టీవీ మెనుని ప్రాప్యత చేయడానికి ""ఎంచుకోండి నొక్కండి""." + "టీవీ ఇన్‌పుట్ కనుగొనబడలేదు" + "టీవీ ఇన్‌పుట్‌ను కనుగొనడం సాధ్యపడదు" + "ట్యూనర్ రకం తగినది కాదు. దయచేసి ట్యూనర్ రకం టీవీ ఇన్‌పుట్ కోసం లైవ్ ఛానెల్‌లు అనువర్తనాన్ని ప్రారంభించండి." + "ట్యూన్ విఫలమైంది" + "ఈ చర్యను నిర్వహించడానికి అనువర్తనం ఏదీ కనుగొనబడలేదు." + "అన్ని మూల ఛానెల్‌లు దాచబడ్డాయి.\nచూడటానికి కనీసం ఒక ఛానెల్‌ను ఎంచుకోండి." + "వీడియో ఊహించని విధంగా అందుబాటులో లేకుండా పోయింది" + "BACK కీ కనెక్ట్ చేయబడిన పరికరం కోసం ఉద్దేశించినది. నిష్క్రమించడానికి HOME బటన్‌ను నొక్కండి." + "టీవీ జాబితాలను చదవడానికి ప్రత్యక్ష ప్రసార ఛానెల్‌లకు అనుమతి అవసరం." + "మీ మూలాధారాలను సెటప్ చేయండి" + "ప్రత్యక్ష ప్రసార ఛానెల్‌లు అనువర్తనాల ద్వారా అందించబడే ప్రసార ఛానెల్‌ల్లో సాంప్రదాయక టీవీ ఛానెల్‌ల అనుభూతి కలగలిపి అందించబడతాయి. \n\nఇప్పటికే ఇన్‌స్టాల్ చేసుకున్న ఛానెల్ మూలాధారాలను సెటప్ చేయడం ద్వారా ప్రారంభించండి. లేదా ప్రత్యక్ష ప్రసార ఛానెల్‌లను ఆఫర్ చేసే మరిన్ని అనువర్తనాల కోసం Google Play స్టోర్‌లో బ్రౌజ్ చేయండి." + "రికార్డింగ్‌లు & షెడ్యూల్‌లు" + "10 నిమిషాలు" + "30 నిమిషాలు" + "1 గంట" + "3 గంటలు" + "ఇటీవలివి" + "షెడ్యూల్ చేసినవి" + "సిరీస్" + "ఇతరం" + "ఛానెల్‌ను రికార్డ్ చేయడం సాధ్యపడదు." + "కార్యక్రమాన్ని రికార్డ్ చేయడం సాధ్యపడదు." + "%1$s రికార్డ్ చేయడం కోసం షెడ్యూల్ చేయబడింది" + "ఇప్పటి నుండి %2$s వరకు %1$s రికార్డ్ చేస్తుంది" + "పూర్తి షెడ్యూల్" + + తదుపరి %1$d రోజులు + తదుపరి %1$d రోజు + + + %1$d నిమిషాలు + %1$d నిమిషం + + + %1$d కొత్త రికార్డింగ్‌లు + %1$d కొత్త రికార్డింగ్ + + + %1$d రికార్డింగ్‌లు + %1$d రికార్డింగ్ + + + %1$d రికార్డింగ్‌లు చేయాలి + %1$d రికార్డింగ్ చేయాలి + + "రికార్డింగ్‌ను రద్దు చేయి" + "రికార్డింగ్‌ను ఆపివేయి" + "చూడండి" + "మొదటి నుండి ప్లే చేయి" + "ప్లే చేయడం కొనసాగించు" + "తొలగించు" + "రికార్డింగ్‌లను తొలగించు" + "పునఃప్రారంభించు" + "సీజన్ %1$s" + "షెడ్యూల్ చూడండి" + "మరింత చదవండి" + "రికార్డింగ్‌లు తొలగించు" + "మీరు తొలగించాలనుకునే ఎపిసోడ్‌లను ఎంచుకోండి. వీటిని ఒకసారి తొలగించాక తిరిగి పునరుద్ధరించలేరు." + "తొలగించడానికి రికార్డింగ్‌లు ఏవీ లేవు." + "చూసిన ఎపిసోడ్‌లను ఎంచుకోండి" + "అన్ని ఎపిసోడ్‌లను ఎంచుకోండి" + "అన్ని ఎపిసోడ్‌ల ఎంపిక తీసివేయి" + "%2$d నిమిషాల్లో %1$d సమయం చూసారు" + "%2$d సెకన్లలో %1$d సమయం చూసారు" + "ఎప్పుడూ చూడలేదు" + + %2$dలో %1$d ఎపిసోడ్‌లు తొలగించబడ్డాయి + %2$dలో %1$d ఎపిసోడ్ తొలగించబడింది + + "ప్రాధాన్యత" + "అత్యధికం" + "అతి తక్కువ" + "వద్దు. %1$d" + "ఛానెల్‌లు" + "ఏదైనా" + "ప్రాధాన్యతను ఎంచుకోండి" + "ఒకే సమయంలో చాలా ఎక్కువ కార్యక్రమాలు రికార్డ్ చేయాల్సినప్పుడు, అధిక ప్రాధాన్యతలు గలవి మాత్రమే రికార్డ్ చేయబడతాయి." + "సేవ్ చేయి" + "ఒక పర్యాయ రికార్డింగ్‌లు అత్యధిక ప్రాధాన్యతను కలిగి ఉంటాయి" + "ఆపివేయి" + "రికార్డింగ్ షెడ్యూల్ చూడండి" + "ఈ ఒక్క కార్యక్రమం" + "ఇప్పటి నుండి - %1$s వరకు" + "మొత్తం సిరీస్…" + "ఏదేమైనా షెడ్యూల్ చేయి" + "బదులుగా దీన్ని రికార్డ్ చేయి" + "ఈ రికార్డింగ్‌ను రద్దు చేయి" + "ఇప్పుడే చూడండి" + "రికార్డింగ్‌లను తొలగించు…" + "రికార్డ్ చేయవచ్చు" + "రికార్డింగ్ షెడ్యూల్ చేయబడింది" + "రికార్డింగ్ వైరుధ్యం" + "రికార్డ్ అవుతోంది" + "రికార్డింగ్ విఫలమైంది" + "కార్యక్రమాలను చదువుతోంది" + "ఇటీవలి రికార్డింగ్‌లను వీక్షించండి" + "%1$s రికార్డింగ్ అసంపూర్ణంగా ఉంది." + "%1$s మరియు %2$s రికార్డింగ్‌లు అసంపూర్ణంగా ఉన్నాయి." + "%1$s, %2$s మరియు %3$s రికార్డింగ్‌లు అసంపూర్ణంగా ఉన్నాయి." + "తగినంత నిల్వ లేని కారణంగా, %1$s రికార్డింగ్ పూర్తి కాలేదు." + "తగినంత నిల్వ లేని కారణంగా, %1$s మరియు %2$s రికార్డింగ్‌లు పూర్తి కాలేదు." + "తగినంత నిల్వ లేని కారణంగా, %1$s, %2$s మరియు %3$s రికార్డింగ్‌లు పూర్తి కాలేదు." + "DVRకు మరింత నిల్వ అవసరం" + "మీరు DVRతో కార్యక్రమాలను రికార్డ్ చేయగలుగుతారు. అయితే, ప్రస్తుతం DVR పని చేయడానికి మీ పరికరంలో తగినంత నిల్వ ఖాళీ లేదు. దయచేసి %1$dGB లేదా అంతకంటే ఎక్కువ ఖాళీ స్థలం గల బయటి డ్రైవ్‌ను కనెక్ట్ చేసి, ఆపై దాన్ని పరికర నిల్వగా ఫార్మాట్ చేయడానికి సూచనలను అనుసరించండి." + "తగినంత నిల్వ లేదు" + "తగినంత నిల్వ లేనందున ఈ కార్యక్రమం రికార్డ్ చేయబడదు. ఇప్పటికే ఉన్న కొన్ని రికార్డింగ్‌లను తొలగించడానికి ప్రయత్నించండి." + "నిల్వ కనిపించడం లేదు" + "రికార్డింగ్ ఆపివేయాలా?" + "రికార్డ్ అయిన కంటెంట్ సేవ్ చేయబడుతుంది." + "%1$s యొక్క రికార్డింగ్‌కి ఈ కార్యక్రమంతో వైరుధ్యం తలెత్తినందున అది ఆపివేయబడుతుంది. రికార్డ్ చేసిన కంటెంట్ సేవ్ చేయబడుతుంది." + "రికార్డింగ్ షెడ్యూల్ చేయబడింది కానీ మిగిలిన వాటితో వైరుధ్యాలను కలిగి ఉంది" + "రికార్డింగ్ ప్రారంభమైంది, కానీ వైరుధ్యాలను కలిగి ఉంది" + "%1$s రికార్డ్ చేయబడుతుంది." + "%1$s రికార్డ్ అవుతోంది." + "%1$sలో కొన్ని భాగాలు రికార్డ్ చేయబడవు." + "%1$s మరియు %2$sలో కొన్ని భాగాలు రికార్డ్ చేయబడవు." + "%1$s, %2$sలో కొన్ని భాగాలు మరియు మరొక షెడ్యూల్ రికార్డ్ చేయబడవు." + + %1$s, %2$sలో కొన్ని భాగాలు మరియు మరో %3$d షెడ్యూల్‌లు రికార్డ్ చేయబడవు. + %1$s, %2$sలో కొన్ని భాగాలు మరియు మరో %3$d షెడ్యూల్ రికార్డ్ చేయబడవు. + + "మీరు ఏమి రికార్డ్ చేయాలనుకుంటున్నారు?" + "మీరు ఎంత సమయం రికార్డ్ చేయాలనుకుంటున్నారు?" + "ఇప్పటికే షెడ్యూల్ చేయబడింది" + "ఇదే కార్యక్రమం ఇప్పటికే %1$sకి రికార్డ్ చేయడానికి షెడ్యూల్ చేయబడింది." + "ఇప్పటికే రికార్డ్ అయింది" + "ఈ ప్రోగ్రామ్ ఇప్పటికే రికార్డ్ అయింది. ఇది DVR లైబ్రరీలో అందుబాటులో ఉంది." + "సిరీస్ రికార్డింగ్ షెడ్యూల్ చేయబడింది" + + %2$sకి %1$d రికార్డింగ్‌లు షెడ్యూల్ చేయబడ్డాయి. + %2$sకి %1$d రికార్డింగ్ షెడ్యూల్ చేయబడింది. + + + %2$sకి %1$d రికార్డింగ్‌లు షెడ్యూల్ చేయబడ్డాయి. వైరుధ్యాల కారణంగా వాటిలో %3$d రికార్డ్ చేయబడవు. + %2$sకి %1$d రికార్డింగ్ షెడ్యూల్ చేయబడింది. వైరుధ్యాల కారణంగా ఇది రికార్డ్ చేయబడదు. + + + %2$sకి %1$d రికార్డింగ్‌లు షెడ్యూల్ చేయబడ్డాయి. వైరుధ్యాల కారణంగా ఈ సిరీస్ మరియు మరో సిరీస్‌లోని %3$d ఎపిసోడ్‌లు రికార్డ్ చేయబడవు. + %2$sకి %1$d రికార్డింగ్ షెడ్యూల్ చేయబడింది. వైరుధ్యాల కారణంగా ఈ సిరీస్ మరియు మరో సిరీస్‌లోని %3$d ఎపిసోడ్‌లు రికార్డ్ చేయబడవు. + + + %2$sకి %1$d రికార్డింగ్‌లు షెడ్యూల్ చేయబడ్డాయి. వైరుధ్యాల కారణంగా మరో సిరీస్‌లోని 1 ఎపిసోడ్ రికార్డ్ చేయబడదు. + %2$sకి %1$d రికార్డింగ్ షెడ్యూల్ చేయబడింది. వైరుధ్యాల కారణంగా మరో సిరీస్‌లోని 1 ఎపిసోడ్ రికార్డ్ చేయబడదు. + + + %2$sకి %1$d రికార్డింగ్‌లు షెడ్యూల్ చేయబడ్డాయి. వైరుధ్యాల కారణంగా మరో సిరీస్‌లోని %3$d ఎపిసోడ్‌లు రికార్డ్ చేయబడవు. + %2$sకి %1$d రికార్డింగ్ షెడ్యూల్ చేయబడింది. వైరుధ్యాల కారణంగా మరో సిరీస్‌లోని %3$d ఎపిసోడ్‌లు రికార్డ్ చేయబడవు. + + "రికార్డ్ చేసిన కార్యక్రమం కనుగొనబడలేదు." + "సంబంధిత రికార్డింగ్‌లు" + + %1$d రికార్డింగ్‌లు + %1$d రికార్డింగ్ + + " / " + "%1$s రికార్డింగ్ షెడ్యూల్ నుండి తీసివేయబడింది" + "ట్యూనర్ వైరుధ్యాల కారణంగా పాక్షికంగా రికార్డ్ చేయబడుతుంది." + "ట్యూనర్ వైరుధ్యాల కారణంగా రికార్డ్ చేయబడదు." + "ఇంకా షెడ్యూల్ చేసిన రికార్డింగ్‌లు ఏవీ లేవు.\nమీరు కార్యక్రమాల గైడ్ నుండి రికార్డింగ్‌ను షెడ్యూల్ చేయవచ్చు." + + %1$d రికార్డింగ్ వైరుధ్యాలు ఉన్నాయి + %1$d రికార్డింగ్ వైరుధ్యం ఉంది + + "సిరీస్ సెట్టింగ్‌లు" + "సిరీస్ రికార్డింగ్ ఆరంభించు" + "సిరీస్ రికార్డింగ్ ఆపివేయి" + "సిరీస్ రికార్డింగ్‌ను ఆపివేయాలా?" + "రికార్డ్ అయిన ఎపిసోడ్‌లు DVR లైబ్రరీలో అలాగే అందుబాటులో ఉంటాయి." + "ఆపివేయి" + "ప్రస్తుతం ఎపిసోడ్‌లు ఏవీ ప్రసారంలో లేవు." + "ఎపిసోడ్‌లు ఏవీ అందుబాటులో లేవు.\nఇవి అందుబాటులోకి వచ్చిన వెంటనే రికార్డ్ చేయబడతాయి." + + (%1$d నిమిషాలు) + (%1$d నిమిషం) + + "ఈ రోజు" + "రేపు" + "నిన్న" + "%1$s ఈ రోజు" + "%1$s రేపు" + "స్కోర్" + "రికార్డ్ చేసిన కార్యక్రమాలు" + diff --git a/res/values-te-v23/strings.xml b/res/values-te-v23/strings.xml deleted file mode 100644 index ac59e7af..00000000 --- a/res/values-te-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "ఛానెల్‌లు" - diff --git a/res/values-te/arrays.xml b/res/values-te/arrays.xml deleted file mode 100644 index de6abf32..00000000 --- a/res/values-te/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "సాధారణం" - "పూర్తిగా" - "జూమ్" - - - "అన్ని ఛానెల్‌లు" - "కుటుంబం/పిల్లలు" - "క్రీడలు" - "షాపింగ్" - "చలనచిత్రాలు" - "హాస్యం" - "ప్రయాణం" - "నాటకం" - "విద్య" - "జంతువు/వన్యప్రాణులు" - "వార్తలు" - "గేమింగ్" - "కళలు" - "వినోదం" - "జీవన శైలి" - "సంగీతం" - "ప్రీమియర్" - "సాంకేతికం/శాస్త్రం" - - - "ప్రత్యక్ష ప్రసార ఛానెల్‌లు" - "కంటెంట్‌ను కనుగొనడానికి సులభమైన మార్గం" - "అనువర్తనాలను డౌన్‌లోడ్ చేసుకోండి, మరిన్ని ఛానెల్‌లను పొందండి" - "మీ ఛానెల్ క్రమాన్ని అనుకూలీకరించండి" - - - "టీవీలో ఛానెల్‌లు చూస్తున్నట్లుగా మీ అనువర్తనాల్లో కంటెంట్‌ను చూడండి." - "టీవీలో ఛానెల్‌ల మాదిరిగా \nమీ అనువర్తనాల్లో కంటెంట్‌ను సుపరిచయ మార్గదర్శిని మరియు స్నేహపూర్వక ఇంటర్‌ఫేస్‌తో బ్రౌజ్ చేయండి." - "ప్రత్యక్ష ప్రసార ఛానెల్‌లను ఆఫర్ చేసే అనువర్తనాలను ఇన్‌స్టాల్ చేసుకోవడం ద్వారా మరిన్ని ఛానెల్‌లను జోడించండి. \nటీవీ మెనులోని లింక్‌ను ఉపయోగించడం ద్వారా Google Play స్టోర్‌లో అనుకూల అనువర్తనాలను కనుగొనండి." - "మీ ఛానెల్ జాబితాను అనుకూలీకరించడానికి కొత్తగా ఇన్‌స్టాల్ చేయబడిన మీ ఛానెల్ మూలాధారాలను సెటప్ చేయండి. \nప్రారంభించడానికి సెట్టింగ్‌ల మెనులో ఛానెల్ మూలాధారాలను ఎంచుకోండి." - - diff --git a/res/values-te/rating_system_strings.xml b/res/values-te/rating_system_strings.xml deleted file mode 100644 index 6179802b..00000000 --- a/res/values-te/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "కార్యక్రమాల్లో 15 ఏళ్లలోపు ప్రేక్షకులకు అనుచితమైన విషయాలు ఉండవచ్చు, కనుక తల్లిదండ్రులు వారి అభీష్టానుసారంగా నిర్ణయం తీసుకోవాలి." - "కార్యక్రమాల్లో 19 ఏళ్లలోపు ప్రేక్షకులకు అనుచితమైన విషయాలు ఉండవచ్చు, కనుక 19 ఏళ్లలోపు యువతకు తగినవి కావు." - "సరస శృంగార వ్యాఖ్యలు" - "అనాగరిక భాష" - "శృంగార విషయాలు" - "హింస" - "కాల్పనిక హింస" - "ఈ కార్యక్రమం అన్ని వయస్సుల పిల్లలకు తగినట్లుగా రూపొందించబడింది." - "ఈ కార్యక్రమం 7 ఏళ్లు అంతకంటే పైబడిన పిల్లల కోసం రూపొందించబడింది." - "దాదాపు అందరు తల్లిదండ్రులు ఈ కార్యక్రమం అన్ని వయస్సుల వారికి తగినదని భావిస్తారు." - "ఈ కార్యక్రమంలో తల్లిదండ్రులు వారి చిన్న పిల్లలకు అనుచితమని భావించే అంశాలు ఉన్నాయి. చాలా మంది తల్లిదండ్రులు దీన్ని తమ చిన్న పిల్లలతో కలిసి చూడాలనుకోవచ్చు." - "ఈ కార్యక్రమంలో చాలామంది తల్లిదండ్రులు 14 ఏళ్లలోపు పిల్లలకు అనుచితమని భావించే కొన్ని అంశాలు ఉన్నాయి." - "ఈ కార్యక్రమం ప్రత్యేకించి పెద్దలు మాత్రమే వీక్షించడం కోసం రూపొందించబడింది, కనుక ఇది 17 ఏళ్లలోపు పిల్లలు చూడదగినది కాదు." - "చలన చిత్ర రేటింగ్‌లు" - "సాధారణ ప్రేక్షకులు. తల్లిదండ్రులు వారి పిల్లలు చూడకుండా ఆక్షేపించాల్సిన అంశాలు ఏమీ ఉండవు." - "తల్లిదండ్రుల మార్గదర్శకం సూచించడమైనది. తల్లిదండ్రులు వారి చిన్న పిల్లలకు తగదని భావించే కొన్ని అంశాలు ఉండవచ్చు." - "తల్లిదండ్రులకు గట్టి హెచ్చరిక. కొన్ని అంశాలు పూర్వ టీనేజీ దశ వారికి అనుచితంగా ఉండవచ్చు." - "నిషిద్ధం, కొన్ని వయోజన అంశాలు ఉన్నాయి. తల్లిదండ్రులు సినిమాకి వారి చిన్న పిల్లలను తీసుకెళ్లే ముందు సినిమా గురించి మరింత తెలుసుకోవాలని కోరుతున్నాము." - "17 ఏళ్లు అంతకంటే తక్కువ వయస్సు గల వారికి నిషిద్ధం. పూర్తిగా పెద్దలకు మాత్రమే. పిల్లలకు నిషిద్ధం." - diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml deleted file mode 100644 index 274be2ce..00000000 --- a/res/values-te/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "మోనో" - "స్టీరియో" - "ప్లే నియంత్రణలు" - "ఇటీవలి ఛానెళ్లు" - "టీవీ ఎంపికలు" - "PIP ఎంపికలు" - "ఈ ఛానెల్ యొక్క ప్లే నియంత్రణలు అందుబాటులో లేవు" - "ప్లే చేస్తుంది లేదా పాజ్ చేస్తుంది" - "ఫాస్ట్ ఫార్వార్డ్ చేస్తుంది" - "రివైండ్ చేస్తుంది" - "తదుపరి" - "మునుపటి" - "ప్రోగ్రామ్ గైడ్" - "కొత్త ఛానె. ఉన్నప్పుడు" - "%1$sని తెరువు" - "సంవృత శీర్షికలు" - "ప్రదర్శన మోడ్" - "PIP" - "ఆన్‌లో ఉంది" - "ఆఫ్‌లో ఉంది" - "బహుళ-ఆడియో" - "మరిన్ని ఛానెల్‌లను పొందండి" - "సెట్టింగ్‌లు" - "మూలం" - "మార్చు" - "ఆన్‌లో ఉంది" - "ఆఫ్‌లో ఉంది" - "ధ్వని" - "ప్రధానమైనది" - "PIP విండో" - "లేఅవుట్" - "దిగువ కుడివైపు" - "ఎగువ కుడివైపు" - "ఎగువ ఎడమవైపు" - "దిగువ ఎడమవైపు" - "పక్కపక్కన" - "పరిమాణం" - "పెద్దది" - "చిన్నది" - "ఇన్‌పుట్ మూలం" - "టీవీ (యాంటెన్నా/కేబుల్)" - "కార్యక్రమ సమాచారం లేదు" - "సమాచారం లేదు" - "బ్లాక్ చేసిన ఛానెల్" - "భాష తెలియదు" - "సంవృత శీర్షికలు" - "ఆఫ్ చేయి" - "ఆకృతీకరణను అనుకూలీకరించు" - "సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను సెట్ చేయి" - "ప్రదర్శన మోడ్" - "బహుళ-ఆడియో" - "మోనో" - "స్టీరియో" - "5.1 సరౌండ్" - "7.1 సరౌండ్" - "%d ఛానెల్‌లు" - "ఛానెల్‌ జాబితా అనుకూలీ." - "సమూహాన్ని ఎంచుకోండి" - "సమూహం ఎంపిక తీసివేయి" - "ఈ ప్రకారం సమూహం చేయి" - "ఛానెల్ మూలం" - "HD/SD" - "HD" - "SD" - "ఈ ప్రకారం సమూహం చేయి" - "ఈ కార్యక్రమం బ్లాక్ చేయబడింది" - "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది" - "స్వయంచాలకంగా స్కాన్ చేయడానికి ఇన్‌పుట్ మద్దతు ఇవ్వలేదు" - "\'%s\' కోసం ఆటో-స్కాన్‌ని ప్రారంభించడం సాధ్యపడలేదు" - "సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను ప్రారంభించడం సాధ్యపడలేదు." - - %1$d ఛానెల్‌లు జోడించబడ్డాయి - %1$d ఛానెల్ జోడించబడింది - - "ఛానెల్‌లు జోడించబడలేదు" - "ట్యూనర్" - "తల్లిదండ్రుల నియంత్రణలు" - "ఆన్ చేయి" - "ఆఫ్ చేయి" - "ఛానె. బ్లాక్ చేయబ." - "అన్నీ బ్లాక్ చేయి" - "అన్నీ అన్‌బ్లాక్ చే." - "దాచబడిన ఛానెల్‌లు" - "ప్రోగ్రామ్ పరిమితులు" - "పిన్‌ను మార్చు" - "రేటింగ్ సిస్టమ్‌లు" - "రేటింగ్‌లు" - "అన్ని రేటింగ్ సిస్టమ్‌లను చూడండి" - "ఇతర దేశాలు" - "ఏదీ లేదు" - "ఏదీ లేదు" - "ఏదీ లేదు" - "అధిక పరిమితులు" - "మధ్యస్థ పరిమితులు" - "తక్కువ పరిమితులు" - "అనుకూలం" - "కంటెంట్ పిల్లలకు తగినది" - "కంటెంట్ యుక్తవయస్సు పిల్లలకు తగినది" - "కంటెంట్ టీనేజ్ వాళ్లకు తగినది" - "మాన్యువల్ పరిమితులు" - - - "%1$s, సబ్-రేటింగ్‌లు" - "సబ్-రేటింగ్‌లు" - "ఈ ఛానెల్‌ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి" - "ఈ కార్యక్రమాన్ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి" - "ఈ కార్యక్రమానికి %1$s అని రేట్ చేయబడింది. ఈ కార్యక్రమాన్ని చూడటానికి మీ PINని నమోదు చేయండి" - "మీ పిన్‌ని నమోదు చేయండి" - "తల్లిదండ్రుల నియంత్రణలను సెట్ చేయడానికి, పిన్‌ని సృష్టించండి" - "కొత్త పిన్‌ని నమోదు చేయండి" - "మీ పిన్‌ను నిర్ధారించండి" - "మీ ప్రస్తుత పిన్‌ని నమోదు చేయండి" - - మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n%1$d సెకన్లు ఆగి, ఆ తర్వాత మళ్లీ ప్రయత్నించండి. - మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n%1$d సెకను ఆగి, ఆ తర్వాత మళ్లీ ప్రయత్నించండి. - - "ఆ పిన్ తప్పు. మళ్లీ ప్రయత్నించండి." - "మళ్లీ ప్రయత్నించండి, పిన్ సరిపోలలేదు" - "సెట్టింగ్‌లు" - "ఛానెల్‌ జాబితా అనుకూలీకరించండి" - "మీ ప్రోగ్రామ్ గైడ్ కోసం ఛానెల్‌లను ఎంచుకోండి" - "ఛానెల్ సోర్స్‌లు" - "కొత్త ఛానెల్‌లు అందుబాటులో ఉన్నాయి" - "తల్లిదండ్రుల నియంత్రణలు" - "ఓపెన్ సోర్స్ లైసెన్స్‌లు" - "ఓపెన్ సోర్స్ లైసెన్స్‌లు" - "సంస్కరణ" - "ఈ ఛానెల్‌ను చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి" - "ఈ ప్రోగ్రామ్‌ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి" - "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది.\nఈ కార్యక్రమాన్ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ PINని నమోదు చేయండి." - "ఈ ఛానెల్‌ను చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." - "ఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." - "ఈ కార్యక్రమానికి %1$s అని రేట్ చేయబడింది.\nఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి." - "కార్యక్రమం బ్లాక్ చేయబడింది" - "ఈ కార్యక్రమం %1$s అని రేట్ చేయబడింది" - "ఆడియో మాత్రమే" - "సిగ్నల్ బలహీనంగా ఉంది" - "ఇంటర్నెట్ కనెక్షన్ లేదు" - - ఇతర ఛానెల్‌లు రికార్డ్ అవుతున్నందున ఈ ఛానెల్‌ని %1$s వరకు ప్లే చేయలేరు. \n\nరికార్డింగ్ షెడ్యూల్‌ను సర్దుబాటు చేయడానికి కుడివైపు నొక్కండి. - మరొక ఛానెల్ రికార్డ్ అవుతున్నందున ఈ ఛానెల్‌ని %1$s వరకు ప్లే చేయలేరు. \n\nరికార్డింగ్ షెడ్యూల్‌ను సర్దుబాటు చేయడానికి కుడివైపు నొక్కండి. - - "శీర్షిక లేదు" - "ఛానెల్ బ్లాక్ చేయబడింది" - "కొత్తవి" - "మూలాలు" - - %1$d ఛానెల్‌లు - %1$d ఛానెల్ - - "ఛానెల్‌లు ఏవీ అందుబాటులో లేవు" - "కొత్తవి" - "సెటప్ చేయలేదు" - "మరిన్ని మూలాధారాలను పొందండి" - "ప్రత్యక్ష ప్రసార ఛానెల్‌లను అందించే అనువర్తనాలను బ్రౌజ్ చేయండి" - "కొత్త ఛానెల్ మూలాలు అందుబాటులో ఉన్నాయి" - "కొత్త ఛానెల్ మూలాలు అందించడానికి ఛానెల్‌లను కలిగి ఉన్నాయి.\nవాటిని ఇప్పుడే సెటప్ చేయండి లేదా ఛానెల్ మూలాల సెట్టింగ్‌లో దీన్ని తర్వాత చేయండి." - "ఇప్పుడే సెటప్ చేయి" - "సరే, అర్థమైంది" - - - "టీవీ మెనుని ప్రాప్యత చేయడానికి ""ఎంచుకోండి నొక్కండి""." - "టీవీ ఇన్‌పుట్ కనుగొనబడలేదు" - "టీవీ ఇన్‌పుట్‌ను కనుగొనడం సాధ్యపడదు" - "PIPకి మద్దతు లేదు" - "PIPతో చూపబడే ఇన్‌పుట్ ఏదీ అందుబాటులో లేదు" - "ట్యూనర్ రకం తగినది కాదు. దయచేసి ట్యూనర్ రకం టీవీ ఇన్‌పుట్ కోసం లైవ్ ఛానెల్‌లు అనువర్తనాన్ని ప్రారంభించండి." - "ట్యూన్ విఫలమైంది" - "ఈ చర్యను నిర్వహించడానికి అనువర్తనం ఏదీ కనుగొనబడలేదు." - "అన్ని మూల ఛానెల్‌లు దాచబడ్డాయి.\nచూడటానికి కనీసం ఒక ఛానెల్‌ను ఎంచుకోండి." - "వీడియో ఊహించని విధంగా అందుబాటులో లేకుండా పోయింది" - "BACK కీ కనెక్ట్ చేయబడిన పరికరం కోసం ఉద్దేశించినది. నిష్క్రమించడానికి HOME బటన్‌ను నొక్కండి." - "టీవీ జాబితాలను చదవడానికి ప్రత్యక్ష ప్రసార ఛానెల్‌లకు అనుమతి అవసరం." - "మీ మూలాధారాలను సెటప్ చేయండి" - "ప్రత్యక్ష ప్రసార ఛానెల్‌లు అనువర్తనాల ద్వారా అందించబడే ప్రసార ఛానెల్‌ల్లో సాంప్రదాయక టీవీ ఛానెల్‌ల అనుభూతి కలగలిపి అందించబడతాయి. \n\nఇప్పటికే ఇన్‌స్టాల్ చేసుకున్న ఛానెల్ మూలాధారాలను సెటప్ చేయడం ద్వారా ప్రారంభించండి. లేదా ప్రత్యక్ష ప్రసార ఛానెల్‌లను ఆఫర్ చేసే మరిన్ని అనువర్తనాల కోసం Google Play స్టోర్‌లో బ్రౌజ్ చేయండి." - "రికార్డింగ్‌లు & షెడ్యూల్‌లు" - "10 నిమిషాలు" - "30 నిమిషాలు" - "1 గంట" - "3 గంటలు" - "ఇటీవలివి" - "షెడ్యూల్ చేసినవి" - "సిరీస్" - "ఇతరం" - "ఛానెల్‌ను రికార్డ్ చేయడం సాధ్యపడదు." - "కార్యక్రమాన్ని రికార్డ్ చేయడం సాధ్యపడదు." - "%1$s రికార్డ్ చేయడం కోసం షెడ్యూల్ చేయబడింది" - "ఇప్పటి నుండి %2$s వరకు %1$s రికార్డ్ చేస్తుంది" - "పూర్తి షెడ్యూల్" - - తదుపరి %1$d రోజులు - తదుపరి %1$d రోజు - - - %1$d నిమిషాలు - %1$d నిమిషం - - - %1$d కొత్త రికార్డింగ్‌లు - %1$d కొత్త రికార్డింగ్ - - - %1$d రికార్డింగ్‌లు - %1$d రికార్డింగ్ - - - %1$d రికార్డింగ్‌లు చేయాలి - %1$d రికార్డింగ్ చేయాలి - - "చూడండి" - "మొదటి నుండి ప్లే చేయి" - "ప్లే చేయడం కొనసాగించు" - "తొలగించు" - "రికార్డింగ్‌లను తొలగించు" - "పునఃప్రారంభించు" - "సీజన్ %1$s" - "షెడ్యూల్ చూడండి" - "మరింత చదవండి" - "రికార్డింగ్‌లు తొలగించు" - "మీరు తొలగించాలనుకునే ఎపిసోడ్‌లను ఎంచుకోండి. వీటిని ఒకసారి తొలగించాక తిరిగి పునరుద్ధరించలేరు." - "తొలగించడానికి రికార్డింగ్‌లు ఏవీ లేవు." - "చూసిన ఎపిసోడ్‌లను ఎంచుకోండి" - "అన్ని ఎపిసోడ్‌లను ఎంచుకోండి" - "అన్ని ఎపిసోడ్‌ల ఎంపిక తీసివేయి" - "%2$d నిమిషాల్లో %1$d సమయం చూసారు" - "%2$d సెకన్లలో %1$d సమయం చూసారు" - "ఎప్పుడూ చూడలేదు" - - %2$dలో %1$d ఎపిసోడ్‌లు తొలగించబడ్డాయి - %2$dలో %1$d ఎపిసోడ్ తొలగించబడింది - - "ప్రాధాన్యత" - "అత్యధికం" - "అతి తక్కువ" - "వద్దు. %1$d" - "ఛానెల్‌లు" - "ఏదైనా" - "ప్రాధాన్యతను ఎంచుకోండి" - "ఒకే సమయంలో చాలా ఎక్కువ కార్యక్రమాలు రికార్డ్ చేయాల్సినప్పుడు, అధిక ప్రాధాన్యతలు గలవి మాత్రమే రికార్డ్ చేయబడతాయి." - "సేవ్ చేయి" - "ఒక పర్యాయ రికార్డింగ్‌లు అత్యధిక ప్రాధాన్యతను కలిగి ఉంటాయి" - "రద్దు చేయి" - "రద్దు చేయి" - "విస్మరించు" - "ఆపివేయి" - "రికార్డింగ్ షెడ్యూల్ చూడండి" - "ఈ ఒక్క కార్యక్రమం" - "ఇప్పటి నుండి - %1$s వరకు" - "మొత్తం సిరీస్…" - "ఏదేమైనా షెడ్యూల్ చేయి" - "బదులుగా దీన్ని రికార్డ్ చేయి" - "ఈ రికార్డింగ్‌ను రద్దు చేయి" - "ఇప్పుడే చూడండి" - "రికార్డ్ చేయవచ్చు" - "రికార్డింగ్ షెడ్యూల్ చేయబడింది" - "రికార్డింగ్ వైరుధ్యం" - "రికార్డ్ అవుతోంది" - "రికార్డింగ్ విఫలమైంది" - "రికార్డింగ్ షెడ్యూళ్లను రూపొందించడానికి కార్యక్రమాలను చదువుతోంది" - "కార్యక్రమాలను చదువుతోంది" - - - "DVRకు మరింత నిల్వ అవసరం" - "మీరు DVRతో కార్యక్రమాలను రికార్డ్ చేయగలుగుతారు. అయితే, ప్రస్తుతం DVR పని చేయడానికి మీ పరికరంలో తగినంత నిల్వ ఖాళీ లేదు. దయచేసి %1$sGB లేదా అంతకంటే ఎక్కువ ఖాళీ స్థలం గల బయటి డ్రైవ్‌ను కనెక్ట్ చేసి, ఆపై దాన్ని పరికర నిల్వగా ఫార్మాట్ చేయడానికి సూచనలను అనుసరించండి." - "నిల్వ కనిపించడం లేదు" - "DVR ద్వారా ఉపయోగించబడిన కొంత నిల్వ కనిపించడం లేదు. దయచేసి DVRని పునఃప్రారంభించడానికి మీరు ఇంతకుముందు ఉపయోగించిన బయటి డిస్క్‌ను కనెక్ట్ చేయండి. లేదంటే, అది అందుబాటులో లేని పక్షంలో మీరు ఆ నిల్వను విస్మరించమని ఎంచుకోవచ్చు." - "నిల్వను విస్మరించాలా?" - "మీ మొత్తం రికార్డ్ చేసిన కంటెంట్‌ను మరియు షెడ్యూల్‌లను కోల్పోతారు." - "రికార్డింగ్ ఆపివేయాలా?" - "రికార్డ్ అయిన కంటెంట్ సేవ్ చేయబడుతుంది." - - - "రికార్డింగ్ షెడ్యూల్ చేయబడింది కానీ మిగిలిన వాటితో వైరుధ్యాలను కలిగి ఉంది" - "రికార్డింగ్ ప్రారంభమైంది, కానీ వైరుధ్యాలను కలిగి ఉంది" - "%1$s రికార్డ్ చేయబడుతుంది." - "%1$s రికార్డ్ అవుతోంది." - "%1$sలో కొన్ని భాగాలు రికార్డ్ చేయబడవు." - "%1$s మరియు %2$sలో కొన్ని భాగాలు రికార్డ్ చేయబడవు." - "%1$s, %2$sలో కొన్ని భాగాలు మరియు మరొక షెడ్యూల్ రికార్డ్ చేయబడవు." - - %1$s, %2$sలో కొన్ని భాగాలు మరియు మరో %3$d షెడ్యూల్‌లు రికార్డ్ చేయబడవు. - %1$s, %2$sలో కొన్ని భాగాలు మరియు మరో %3$d షెడ్యూల్ రికార్డ్ చేయబడవు. - - "మీరు ఏమి రికార్డ్ చేయాలనుకుంటున్నారు?" - "మీరు ఎంత సమయం రికార్డ్ చేయాలనుకుంటున్నారు?" - "ఇప్పటికే షెడ్యూల్ చేయబడింది" - "ఇదే కార్యక్రమం ఇప్పటికే %1$sకి రికార్డ్ చేయడానికి షెడ్యూల్ చేయబడింది." - "ఇప్పటికే రికార్డ్ అయింది" - "ఈ ప్రోగ్రామ్ ఇప్పటికే రికార్డ్ అయింది. ఇది DVR లైబ్రరీలో అందుబాటులో ఉంది." - - - - - - - - - "రికార్డ్ చేసిన కార్యక్రమం కనుగొనబడలేదు." - "సంబంధిత రికార్డింగ్‌లు" - "(కార్యక్రమం వివరణ లేదు)" - - %1$d రికార్డింగ్‌లు - %1$d రికార్డింగ్ - - " / " - "%1$s రికార్డింగ్ షెడ్యూల్ నుండి తీసివేయబడింది" - "ట్యూనర్ వైరుధ్యాల కారణంగా పాక్షికంగా రికార్డ్ చేయబడుతుంది." - "ట్యూనర్ వైరుధ్యాల కారణంగా రికార్డ్ చేయబడదు." - "ఇంకా షెడ్యూల్ చేసిన రికార్డింగ్‌లు ఏవీ లేవు.\nమీరు కార్యక్రమాల గైడ్ నుండి రికార్డింగ్‌ను షెడ్యూల్ చేయవచ్చు." - - %1$d రికార్డింగ్ వైరుధ్యాలు ఉన్నాయి - %1$d రికార్డింగ్ వైరుధ్యం ఉంది - - "సిరీస్ సెట్టింగ్‌లు" - "సిరీస్ రికార్డింగ్ ఆరంభించు" - "సిరీస్ రికార్డింగ్ ఆపివేయి" - "సిరీస్ రికార్డింగ్‌ను ఆపివేయాలా?" - "రికార్డ్ అయిన ఎపిసోడ్‌లు DVR లైబ్రరీలో అలాగే అందుబాటులో ఉంటాయి." - "ఆపివేయి" - "ఎపిసోడ్‌లు ఏవీ అందుబాటులో లేవు.\nఇవి అందుబాటులోకి వచ్చిన వెంటనే రికార్డ్ చేయబడతాయి." - - (%1$d నిమిషాలు) - (%1$d నిమిషం) - - "ఈ రోజు" - "రేపు" - "నిన్న" - "%1$s ఈ రోజు" - "%1$s రేపు" - "స్కోర్" - diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 8424e9bb..09bf9bd4 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -20,9 +20,8 @@ "โมโน" "สเตอริโอ" "การควบคุมการเล่น" - "ช่องล่าสุด" + "ช่อง" "ตัวเลือกทีวี" - "ตัวเลือกของ PIP" "ไม่มีการควบคุมการเล่นสำหรับช่องนี้" "เล่นหรือหยุดชั่วคราว" "กรอไปข้างหน้า" @@ -35,33 +34,15 @@ "คำบรรยาย" "โหมดการแสดงผล" "PIP" - "เปิด" - "ปิด" "หลายเสียง" - "รับชมช่องต่างๆ มากขึ้น" + "ดูช่องอื่นๆ" "การตั้งค่า" - "แหล่งที่มา" - "สลับ" - "เปิด" - "ปิด" - "เสียง" - "หลัก" - "หน้าต่าง PIP" - "การจัดวาง" - "ขวาล่าง" - "ขวาบน" - "ซ้ายบน" - "ซ้ายล่าง" - "แสดงคู่กัน" - "ขนาด" - "ใหญ่" - "เล็ก" - "แหล่งที่มาอินพุต" "TV (สายอากาศ/เคเบิล)" "ไม่มีข้อมูลโปรแกรม" "ไม่มีข้อมูล" "ช่องที่ถูกบล็อก" - "ภาษาที่ไม่รู้จัก" + "ภาษาที่ไม่รู้จัก" + "คำอธิบายภาพ %1$d" "คำบรรยาย" "ปิด" "กำหนดค่าการจัดรูปแบบ" @@ -83,6 +64,7 @@ "SD" "จัดกลุ่มตาม" "โปรแกรมนี้ถูกบล็อก" + "รายการนี้ไม่ได้รับการจัดประเภท" "โปรแกรมนี้เรต %1$s" "อินพุตไม่สนับสนุนการสแกนอัตโนมัติ" "ไม่สามารถเริ่มการสแกนอัตโนมัติสำหรับ \"%s\"" @@ -92,7 +74,6 @@ เพิ่มแล้ว %1$d ช่อง "ไม่ได้เพิ่มช่อง" - "ตัวรับสัญญาณ" "ควบคุมโดยผู้ปกครอง" "เปิด" "ปิด" @@ -108,6 +89,8 @@ "ประเทศอื่นๆ" "ไม่มี" "ไม่มี" + "ไม่ได้จัดประเภท" + "บล็อกเกมที่ไม่ได้จัดประเภท" "ไม่มี" "ข้อจำกัดสูง" "ข้อจำกัดปานกลาง" @@ -124,6 +107,7 @@ "ป้อน PIN ของคุณเพื่อดูช่องนี้" "ป้อน PIN ของคุณเพื่อดูโปรแกรมนี้" "รายการนี้จัดอยู่ในประเภท %1$s ป้อน PIN เพื่อดูรายการนี้" + "รายการนี้ไม่ได้รับการจัดประเภท ป้อน PIN เพื่อดูรายการนี้" "ป้อน PIN ของคุณ" "หากต้องการตั้งค่าการควบคุมโดยผู้ปกครอง โปรดสร้าง PIN" "ป้อน PIN ใหม่" @@ -135,22 +119,31 @@ "PIN ไม่ถูกต้อง ลองอีกครั้ง" "ลองอีกครั้ง PIN ไม่ตรงกัน" + "ป้อนรหัสไปรษณีย์ของคุณ" + "แอป Live TV จะใช้รหัสไปรษณีย์สำหรับการจัดส่งคู่มือรายการทีวีช่องต่างๆ ฉบับเต็ม" + "ป้อนรหัสไปรษณีย์ของคุณ" + "รหัสไปรษณีย์ไม่ถูกต้อง" "การตั้งค่า" "กำหนดค่ารายการช่อง" "เลือกช่องสำหรับคู่มือรายการทีวีของคุณ" "แหล่งที่มาของช่อง" "มีช่องใหม่ให้บริการ" "การควบคุมโดยผู้ปกครอง" + "ไทม์ชิฟท์" + "บันทึกขณะดูเพื่อให้สามารถหยุดชั่วคราวหรือกรอกลับรายการสดได้\nคำเตือน: การดำเนินการนี้อาจลดอายุการใช้งานของพื้นที่เก็บข้อมูลภายในเนื่องจากการใช้งานหนัก" "ใบอนุญาตโอเพนซอร์ส" - "ใบอนุญาตโอเพนซอร์ส" + "ส่งความคิดเห็น" "เวอร์ชัน" "หากต้องการดูช่องนี้ ให้กดขวาและป้อน PIN" "หากต้องการดูโปรแกรมนี้ ให้กดขวาและป้อน PIN" + "รายการนี้ไม่ได้รับการจัดประเภท\nหากต้องการดูรายการนี้ ให้กด \"สิทธิ์\" และป้อน PIN" "โปรแกรมนี้เรต %1$s\nในการดูโปรแกรมนี้ โปรดกดขวาและป้อน PIN" "หากต้องการดูช่องนี้ ให้ใช้แอปทีวีออนไลน์ที่เป็นค่าเริ่มต้น" "หากต้องการดูโปรแกรมนี้ ให้ใช้แอปทีวีออนไลน์ที่เป็นค่าเริ่มต้น" + "รายการนี้ไม่ได้รับการจัดประเภท\nหากต้องการดูรายการนี้ ให้ใช้แอป Live TV เริ่มต้น" "โปรแกรมนี้ได้รับคะแนน %1$s\nหากต้องการดูโปรแกรมนี้ ให้ใช้แอปทีวีออนไลน์ที่เป็นค่าเริ่มต้น" "ระบบบล็อกโปรแกรมไว้" + "รายการนี้ไม่ได้รับการจัดประเภท" "โปรแกรมนี้เรต %1$s" "เฉพาะเสียง" "สัญญาณไม่ดี" @@ -181,8 +174,6 @@ "กด \"เลือก\""" เพื่อเข้าถึงเมนู TV" "ไม่พบอินพุต TV" "ไม่พบอินพุต TV" - "ไม่สนับสนุน PIP" - "ไม่มีอินพุตที่พร้อมใช้งานซึ่งสามารถแสดงด้วย PIP" "ประเภทตัวรับสัญญาณไม่เหมาะสม โปรดเปิดแอป Live TV สำหรับอินพุต TV ที่เป็นประเภทตัวรับสัญญาณ" "การรับสัญญาณล้มเหลว" "ไม่พบแอปสำหรับการทำงานนี้" @@ -226,6 +217,8 @@ กำหนดเวลาบันทึกแล้ว %1$d รายการ กำหนดเวลาบันทึกแล้ว %1$d รายการ + "ยกเลิกการบันทึก" + "หยุดบันทึก" "ดู" "เล่นจากจุดเริ่มต้น" "เล่นต่อจากที่ค้างไว้" @@ -258,9 +251,6 @@ "เมื่อต้องบันทึกรายการหลายรายการพร้อมกันมากเกินไป ระบบจะบันทึกเฉพาะรายการที่มีลำดับความสำคัญสูงกว่าเท่านั้น" "บันทึก" "การบันทึกครั้งเดียวมีความสำคัญสูงสุด" - "ยกเลิก" - "ยกเลิก" - "ไม่จำ" "หยุด" "ดูกำหนดการบันทึก" "โปรแกรมนี้เท่านั้น" @@ -270,25 +260,28 @@ "บันทึกรายการนี้แทน" "ยกเลิกการบันทึกนี้" "ดูตอนนี้" + "ลบรายการที่บันทึกไว้…" "สามารถบันทึกได้" "กำหนดเวลาบันทึกแล้ว" "ตารางบันทึกชนกัน" "กำลังบันทึก" "การบันทึกล้มเหลว" - "กำลังอ่านรายการเพื่อสร้างกำหนดเวลาการบันทึก" - "กำลังอ่านรายการ" - - + "กำลังอ่านรายการ" + "ดูการบันทึกล่าสุด" + "การบันทึก %1$s ไม่สมบูรณ์" + "การบันทึก %1$s และ %2$s ไม่สมบูรณ์" + "การบันทึก %1$s, %2$s และ %3$s ไม่สมบูรณ์" + "การบันทึก %1$s ไม่สมบูรณ์ เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ" + "การบันทึก %1$s และ %2$s ไม่สมบูรณ์ เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ" + "การบันทึก %1$s, %2$s และ %3$s ไม่สมบูรณ์ เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ" "DVR ต้องการพื้นที่เก็บข้อมูลเพิ่มเติม" - "คุณจะสามารถบันทึกรายการด้วย DVR ได้ อย่างไรก็ตาม ตอนนี้อุปกรณ์ของคุณมีพื้นที่เก็บข้อมูลไม่เพียงพอสำหรับให้ DVR ทำงาน โปรดเชื่อมต่อไดรฟ์ภายนอกที่มีขนาด %1$s GB ขึ้นไป และทำตามขั้นตอนเพื่อฟอร์แมตไดรฟ์ให้เป็นพื้นที่เก็บข้อมูลของอุปกรณ์" + "คุณจะสามารถบันทึกรายการด้วย DVR ได้ อย่างไรก็ตาม ตอนนี้อุปกรณ์ของคุณมีพื้นที่เก็บข้อมูลไม่เพียงพอสำหรับให้ DVR ทำงาน โปรดเชื่อมต่อไดรฟ์ภายนอกที่มีขนาด %1$d GB ขึ้นไป และทำตามขั้นตอนเพื่อฟอร์แมตไดรฟ์ให้เป็นพื้นที่เก็บข้อมูลของอุปกรณ์" + "พื้นที่เก็บข้อมูลไม่เพียงพอ" + "ไม่สามารถบันทึกรายการนี้ได้เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ โปรดลองลบบางรายการที่บันทึกไว้" "พื้นที่เก็บข้อมูลหายไป" - "พื้นที่เก็บข้อมูลบางส่วนที่ DVR ใช้หายไป โปรดเชื่อมต่อไดรฟ์ภายนอกที่คุณใช้ก่อนเปิดใช้ DVR อีกครั้ง หรือคุณอาจเลือกไม่จำพื้นที่เก็บข้อมูลหากพื้นที่เก็บข้อมูลไม่พร้อมใช้งานอีกต่อไป" - "ไม่จำพื้นที่เก็บข้อมูลใช่ไหม" - "เนื้อหาและกำหนดการทั้งหมดที่คุณบันทึกไว้จะหายไป" "หยุดบันทึกใช่ไหม" "ระบบจะเก็บเนื้อหาที่บันทึกไว้" - - + "การบันทึก %1$s จะหยุดลงเนื่องจากตารางบันทึกชนกับรายการนี้ เนื้อหาที่บันทึกแล้วจะได้รับการเก็บไว้" "กำหนดการบันทึกรายการชนกัน" "เริ่มบันทึกรายการแล้ว แต่กำหนดการชนกัน" "ระบบจะบันทึก %1$s" @@ -306,17 +299,29 @@ "มีกำหนดบันทึกรายการเดียวกันนี้แล้วเวลา %1$s" "บันทึกไว้แล้ว" "บันทึกรายการนี้ไว้แล้ว สามารถดูได้ที่ห้องสมุด DVR" - - - - - - - - + "กำหนดเวลาบันทึกซีรีส์แล้ว" + + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s + + + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s %3$d รายการจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s จะไม่มีการบันทึกเนื่องจากเนื่องจากตารางบันทึกชนกัน + + + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์นี้และซีรีส์อื่น %3$d ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์นี้และซีรีส์อื่น %3$d ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + + + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์อื่น 1 ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์อื่น 1 ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + + + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์อื่น %3$d ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + กำหนดเวลาบันทึกแล้ว %1$d รายการสำหรับ %2$s ซีรีส์อื่น %3$d ตอนจะไม่มีการบันทึกเนื่องจากตารางบันทึกชนกัน + "ไม่พบโปรแกรมที่บันทึกไว้" "การบันทึกที่เกี่ยวข้อง" - "(ไม่มีรายละเอียดรายการ)" การบันทึก %1$d รายการ การบันทึก %1$d รายการ @@ -336,6 +341,7 @@ "หยุดบันทึกซีรีส์ไหม" "ตอนที่บันทึกไว้จะยังคงอยู่ในไลบรารี DVR" "หยุด" + "ไม่มีตอนใดออกอากาศในตอนนี้" "ไม่มีตอนที่พร้อมรับชม\nระบบจะเริ่มบันทึกเมื่อมีตอนที่พร้อมรับชม" (%1$d นาที) @@ -347,4 +353,5 @@ "%1$s วันนี้" "%1$s พรุ่งนี้" "คะแนน" + "รายการที่บันทึกไว้" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index a925aee6..cc19b272 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Mga kontrol sa Play" - "Kamakailang channel" + "Mga Channel" "Opsyon sa TV" - "Mga opsyon sa PIP" "Hindi available para sa channel na ito ang mga kontrol ng laro" "I-play o i-pause" "I-fast forward" @@ -35,37 +34,19 @@ "Closed captions" "Display mode" "PIP" - "Naka-on" - "Naka-off" "Multi-audio" "Higit pa channel" "Mga Setting" - "Pinagmulan" - "Pagpalitin" - "Naka-on" - "Naka-off" - "Tunog" - "Pangunahin" - "PIP window" - "Layout" - "Kanang ibaba" - "Kanang itaas" - "Kaliwang itaas" - "Kaliwang ibaba" - "Magkatabi" - "Laki" - "Malaki" - "Maliit" - "Pinagmulan ng input" "TV (antenna/cable)" "Walang impormasyon ng programa" "Walang impormasyon" "Naka-block na channel" - "Hindi kilalang wika" + "Hindi kilalang wika" + "Mga closed caption %1$d" "Mga closed caption" "Naka-off" - "Customize formatting" - "I-set kagustuhan para sa closed captions" + "I-customize ang pag-format" + "I-set ang kagustuhan sa closed captions para sa buong system" "Display mode" "Multi-audio" "mono" @@ -83,6 +64,7 @@ "SD" "Ipangkat ayon sa" "Naka-block ang programang ito" + "Hindi na-rate ang programang ito" "Ang programang ito ay na-rate na %1$s" "Hindi sinusuportahan ng input ang auto-scan" "Hindi maumpisahan ang auto-scan para sa \'%s\'" @@ -92,7 +74,6 @@ Nagdagdag ng %1$d na channel "Wala naidagdag na channel" - "Tuner" "Parental controls" "Naka-on" "Naka-off" @@ -108,6 +89,8 @@ "Iba pang bansa" "Wala" "Wala" + "Hindi na-rate" + "I-block ang mga programang hindi na-rate" "Wala" "High restrictions" "Bahagyang pinaghihigpitan" @@ -124,6 +107,7 @@ "Ilagay iyong PIN upang mapanood ang channel na ito" "Ilagay iyong PIN upang mapanood ang programang ito" "May rating na %1$s ang programang ito. Ilagay ang iyong PIN upang mapanood ang programang ito" + "Hindi na-rate ang programang ito. Ilagay ang iyong PIN upang mapanood ang programang ito" "Ilagay ang iyong PIN" "Upang itakda ang mga parental control, gumawa ng PIN" "Ilagay ang bagong PIN" @@ -135,22 +119,31 @@ "Mali ang PIN na iyon. Subukang muli." "Subukang muli, hindi tumutugma ang PIN" + "Ilagay ang iyong ZIP Code." + "Gagamitin ng app na Mga Live Channel ang ZIP Code upang magbigay ng kumpletong gabay sa programa para sa mga channel sa TV." + "Ilagay ang iyong ZIP Code" + "Di-wasto ang ZIP Code" "Mga Setting" "I-customize lista ng channel" "Pumili ng mga channel para sa gabay sa programa" "Mga pinagmulan ng channel" "May mga bagong channel" "Mga kontrol ng magulang" + "Timeshift" + "Mag-record habang nanonood upang ma-pause o ma-rewind mo ang mga live na programa.\nBabala: Maaari nitong mabawasan ang itatagal ng internal storage dahil sa labis-labis na paggamit ng storage." "Mga open source na lisensya" - "Mga lisensyang open source" + "Magpadala ng feedback" "Bersyon" "Upang mapanood ang channel na ito, pindutin ang Kanan at ilagay ang iyong PIN" "Upang mapanood ang programang ito, pindutin ang Kanan at ilagay ang iyong PIN" + "Hindi na-rate ang programang ito.\nUpang mapanood ang programang ito, pindutin ang Kanan at ilagay ang iyong PIN" "Ang programang ito ay na-rate na %1$s.\nUpang mapanood ang programang ito, pindutin ang Kanan at ilagay ang iyong PIN." "Upang mapanood ang channel na ito, gamitin ang default na Live TV app." "Upang mapanood ang programang ito, gamitin ang default na Live TV app." + "Hindi na-rate ang programang ito.\nUpang mapanood ang programang ito, gamitin ang default na Live TV app." "Ang programang ito ay binigyan ng rating na %1$s.\nUpang mapanood ang programang ito, gamitin ang default na Live TV app." "Naka-block ang programa" + "Hindi na-rate ang programang ito" "Ang programang ito ay na-rate na %1$s" "Audio lang" "Mahinang signal" @@ -181,8 +174,6 @@ "Pindutin ang SELECT"" upang i-access ang menu ng TV." "Walang nahanap na TV input" "Hindi mahanap ang TV input" - "Hindi sinusuportahan ang PIP" - "Walang available na input na maipapakita sa PIP" "Hindi naaangkop ang uri ng tuner. Pakilunsad ang app na Mga Live na Channel para sa uri ng tuner na input ng TV." "Hindi na-tune" "Walang nakitang app na gagawa sa aksyong ito." @@ -226,6 +217,8 @@ %1$d recording ang nakaiskedyul %1$d na recording ang nakaiskedyul + "Kanselahin ang pag-record" + "Ihinto ang pag-record" "Panoorin" "I-play mula sa simula" "Ituloy ang pag-play" @@ -258,9 +251,6 @@ "Kapag masyadong maraming programa ang ire-record nang sabay-sabay, tanging ang may mas mataas na priyoridad lang ang mare-record." "I-save" "Ang mga isang beses na pagre-record ang may pinakamataas na priyoridad" - "Kanselahin" - "Kanselahin" - "Kalimutan" "Ihinto" "Tingnan, iskedyul ng recording" "Ang isang programang ito" @@ -270,25 +260,28 @@ "Ito na lang ang i-record" "Kanselahin ang pag-record na ito" "Panoorin ngayon" + "I-delete ang mga recording…" "Mare-record" "Nakaiskedyul ang recording" "May conflict sa pagre-record" "Nagre-record" "Hindi na-record" - "Nagbabasa ng mga program upang makagawa ng mga iskedyul ng pagre-record" - "Binabasa ang mga programa" - - + "Binabasa ang mga programa" + "Tingnan ang mga kamakailang recording" + "Hindi natapos ang pag-record sa %1$s." + "Hindi natapos ang mga pag-record sa %1$s at %2$s." + "Hindi natapos ang mga pag-record sa %1$s, %2$s at %3$s." + "Hindi natapos ang pag-record sa %1$s dahil sa hindi sapat na storage." + "Hindi natapos ang mga pag-record sa %1$s at %2$s dahil sa hindi sapat na storage." + "Hindi natapos ang mga pag-record sa %1$s, %2$s at %3$s dahil sa hindi sapat na storage." "Kailangan ng DVR ng higit pang storage" - "Makakapag-record ka ng mga program gamit ang DVR. Gayunpaman, walang sapat na storage sa iyong device ngayon upang gumana ang DVR. Mangyaring magkonekta ng external drive na %1$sGB o mas malaki at sundin ang mga hakbang upang i-format ito bilang storage ng device." + "Makakapag-record ka ng mga program gamit ang DVR. Gayunpaman, walang sapat na storage sa iyong device ngayon upang gumana ang DVR. Mangyaring magkonekta ng external drive na %1$dGB o mas malaki at sundin ang mga hakbang upang i-format ito bilang storage ng device." + "Hindi sapat ang storage" + "Hindi mare-record ang program na ito dahil walang sapat na storage. Subukang mag-delete ng ilang dati nang recording." "Nawawala ang storage" - "May nawawalang bahagi ng storage na ginagamit ng DVR. Pakikonekta ang external na drive na ginamit mo dati upang muling i-enable ang DVR. O kaya, maaari mo ring piliing kalimutan ang storage kung hindi na ito available." - "Kalimutan ang storage?" - "Mawawala ang lahat ng na-record mong content at iskedyul." "Ihinto ang pagre-record?" "Mase-save ang na-record na content." - - + "Ihihinto na ang pag-record ng %1$s dahil kasabay ito ng palabas na ito. Ise-save ang na-record na content." "Naiskedyul na ang pagre-record ngunit may mga hindi pagkakatugma" "Nagsimula na ang pagre-record ngunit may mga hindi pagkakatugma" "Mare-record ang %1$s." @@ -306,17 +299,29 @@ "Naiskedyul na ang parehong programa na ma-record sa %1$s." "Na-record na" "Na-record na ang programang ito. Available ito sa DVR library." - - - - - - - - + "Naiskedyul na ang pag-record ng series" + + %1$d recording ang naiskedyul para sa %2$s. + %1$d na recording ang naiskedyul para sa %2$s. + + + %1$d recording ang naiskedyul para sa %2$s. Hindi mare-record ang %3$d sa mga ito dahil sa may mga nakaiskedyul na. + %1$d na recording ang naiskedyul para sa %2$s. Hindi mare-record ang %3$d sa mga ito dahil sa may mga nakaiskedyul na. + + + %1$d recording ang naiskedyul para sa %2$s. %3$d episode ng series na ito at ng iba pang series ang hindi mare-record dahil may mga nakaiskedyul na. + %1$d na recording ang naiskedyul para sa %2$s. %3$d na episode ng series na ito at ng iba pang series ang hindi mare-record dahil may mga nakaiskedyul na. + + + %1$d recording ang naiskedyul para sa %2$s. 1 episode ng iba pang series ang hindi mare-record dahil sa may mga nakaiskedyul na. + %1$d na recording ang naiskedyul para sa %2$s. 1 episode ng iba pang series ang hindi mare-record dahil sa may mga nakaiskedyul na. + + + %1$d recording ang naiskedyul para sa %2$s. %3$d episode ng iba pang series ang hindi mare-record dahil sa may mga nakaiskedyul na. + %1$d na recording ang naiskedyul para sa %2$s. %3$d na episode ng iba pang series ang hindi mare-record dahil sa may mga nakaiskedyul na. + "Hindi nakita ang na-record na programa." "Mga nauugnay na recording" - "(Walang paglalarawan ng program)" %1$d recording %1$d na recording @@ -336,6 +341,7 @@ "Gusto mo bang ihinto ang pagre-record ng mga serye?" "Mananatiling available sa library ng DVR ang mga na-record na episode." "Ihinto" + "Walang ipinapalabas na episode ngayon." "Walang available na mga episode.\nMare-record ang mga ito kapag available na ang mga ito." (%1$d minuto) @@ -347,4 +353,5 @@ "%1$s ngayong araw" "%1$s bukas" "Marka" + "Mga Na-record na Programa" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 8162fbfa..d8f92210 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -20,9 +20,8 @@ "mono" "stereo" "Oynatma denetimleri" - "Son kanallar" + "Kanallar" "TV seçenekleri" - "PIP seçenekleri" "Bu kanal için oynatma denetimleri kullanılamıyor" "Oynat veya duraklat" "İleri sar" @@ -35,33 +34,15 @@ "Altyazılar" "Görüntü modu" "PIP" - "Açık" - "Kapalı" "Çoklu ses" "Daha çok kanal al" "Ayarlar" - "Kaynak" - "Değiştir" - "Açık" - "Kapalı" - "Ses" - "Ana" - "PIP penceresi" - "Düzen" - "Sağ alt" - "Sağ üst" - "Sol üst" - "Sol alt" - "Yan yana" - "Boyut" - "Büyük" - "Küçük" - "Giriş kaynağı" "TV (anten/kablo)" "Hiçbir program bilgisi yok" "Bilgi yok" "Engellenen kanal" - "Bilinmeyen dil" + "Bilinmeyen dil" + "Altyazılar %1$d" "Altyazılar" "Kapalı" "Biçimi özelleştir" @@ -83,6 +64,7 @@ "SD" "Gruplama ölçütü" "Bu program engellendi" + "Bu program derecelendirilmedi" "Bu program %1$s olarak derecelendirilmiştir" "Giriş, otomatik taramayı desteklemiyor" "\"%s\" için otomatik tarama başlatılamıyor" @@ -92,7 +74,6 @@ %1$d kanal eklendi "Hiçbir kanal eklenmedi" - "Tuner" "Ebeveyn denetimi" "Açık" "Kapalı" @@ -108,6 +89,8 @@ "Diğer ülkeler" "Yok" "Yok" + "Derecelendirilmedi" + "Derecelendirilmemiş programları engelle" "Yok" "Yüksek kısıtlama" "Orta düzey kısıtlama" @@ -124,6 +107,7 @@ "Bu kanalı izlemek için PIN\'inizi girin" "Bu programı izlemek için PIN\'inizi girin" "Bu program %1$s olarak derecelendirildi. Bu programı izlemek için PIN kodunuzu girin." + "Bu program derecelendirilmedi. Programı izlemek için PIN kodunuzu girin." "PIN\'inizi girin" "Ebeveyn denetimlerini ayarlamak için PIN oluşturun" "Yeni PIN\'i girin" @@ -135,22 +119,31 @@ "Girdiğiniz PIN hatalıydı. Tekrar deneyin." "Tekrar deneyin, PIN eşleşmiyor" + "Posta Kodunuzu girin." + "Canlı Kanallar uygulaması, TV kanalları için eksiksiz bir program rehberi sunmak üzere Posta Kodu\'nu kullanacaktır." + "Posta Kodunuzu girin" + "Geçersiz Posta Kodu" "Ayarlar" "Kanal listesini özelleştir" "Program rehberiniz için kanalları seçin" "Kanal kaynakları" "Yeni kanallar mevcut" "Ebeveyn denetimleri" + "Zaman kaydırma" + "İzlerken kaydedin. Böylece canlı programları duraklatabilir veya geri sarabilirsiniz. \nUyarı: Bu özellik depolama alanını yoğun bir şekilde kullandığından dahili depolama alanının kullanım ömrü azalabilir." "Açık kaynak lisansları" - "Açık kaynak lisansları" + "Geri bildirim gönder" "Sürüm" "Bu kanalı izlemek için Sağ tuşuna basın ve PIN\'inizi girin" "Bu programı izlemek için Sağ tuşuna basın ve PIN\'inizi girin" + "Bu program derecelendirilmedi.\nProgramı izlemek için Sağ\'a basın ve PIN\'inizi girin." "Bu program %1$s olarak derecelendirilmiştir.\nBu programı izlemek için Sağ tuşuna basın ve PIN\'inizi girin." "Bu kanalı izlemek için varsayılan Canlı TV uygulamasını kullanın." "Bu programı izlemek için varsayılan Canlı TV uygulamasını kullanın." + "Bu program derecelendirilmedi.\nProgramı izlemek için varsayılan Canlı TV uygulamasını kullanın." "Bu program %1$s olarak derecelendirildi.\nBu programı izlemek için varsayılan Canlı TV uygulamasını kullanın." "Program engellendi" + "Bu program derecelendirilmedi" "Bu program %1$s olarak derecelendirilmiştir" "Yalnızca ses" "Zayıf sinyal" @@ -171,7 +164,7 @@ "Yeni" "Yapılandırılmadı" "Daha fazla kaynak al" - "Canlı kanallar sunan uygulamalara göz atın" + "Canlı kanal uygulamalarına göz atın" "Kullanılabilir yeni kanal kaynakları mevcut" "Yeni kanal kaynaklarında sunulan kanallar var.\nBunları şimdi ayarlayın veya daha sonra kanal kaynakları ayarında yapılandırın." "Şimdi ayarla" @@ -181,8 +174,6 @@ "TV menüsüne erişmek için ""SEÇ\'e basın""." "TV girişi bulunamadı" "TV girişi bulunamadı" - "PIP desteklenmiyor" - "PIP ile göstermeye uygun giriş yok" "Kanal tarayıcı türü uygun değil. TV girişi kanal tarayıcı türü için lütfen Canlı Yayın Kanalları uygulamasını başlatın." "Tarama işlemi başarısız oldu" "Bu işlemi gerçekleştirecek uygulama bulunamadı." @@ -226,6 +217,8 @@ %1$d kayıt planlandı %1$d kayıt planlandı + "Kaydı iptal et" + "Kaydı durdur" "İzle" "Baştan oynat" "Oynatmaya devam et" @@ -258,9 +251,6 @@ "Aynı anda kaydedilecek çok sayıda program olduğundan, yalnızca yüksek öncelikli programlar kaydedilir." "Kaydet" "Bir kerelik kayıtlar en yüksek önceliğe sahip" - "İptal" - "İptal" - "Unut" "Durdur" "Kayıt programını görüntüle" "Yalnızca bu program" @@ -270,25 +260,28 @@ "Onun yerine bunu kaydet" "Bu kaydı iptal et" "Şimdi izle" + "Kayıtları sil…" "Kaydedilebilir" "Kayıt programlandı" "Kayıt çakışması" "Kaydediliyor" "Kaydedilemedi" - "Kayıt planlarını oluşturmak için programlar okunuyor" - "Program bilgisi okunuyor" - - + "Program bilgisi okunuyor" + "Son kayıtları görüntüleyin" + "%1$s kaydı tam değil." + "%1$s ve %2$s kayıtları tam değil." + "%1$s, %2$s ve %3$s kayıtları tam değil." + "Kullanılabilir depolama alanı yeterli olmadığından %1$s kaydı tamamlanamadı." + "Kullanılabilir depolama alanı yeterli olmadığından %1$s ve %2$s kayıtları tamamlanamadı." + "Kullanılabilir depolama alanı yeterli olmadığından %1$s, %2$s ve %3$s kayıtları tamamlanamadı." "DVR için daha fazla depolama alanı gerekiyor" - "Programları DVR ile kaydedebileceksiniz. Ancak şu anda cihazınızda DVR\'nin çalışması için yeterli miktarda boş depolama alanı yok. Lütfen %1$s GB veya daha büyük kapasiteye sahip harici sürücü bağlayın ve bu sürücüyü cihaz depolama alanı olarak biçimlendirmek için adımları uygulayın." + "Programları DVR ile kaydedebileceksiniz. Ancak şu anda cihazınızda DVR\'nin çalışması için yeterli miktarda boş depolama alanı yok. Lütfen %1$d GB veya daha büyük kapasiteye sahip harici sürücü bağlayın ve bu sürücüyü cihaz depolama alanı olarak biçimlendirmek için adımları uygulayın." + "Yeterli depolama alanı yok" + "Yeterli depolama alanı olmadığı için bu program kaydedilmeyecek. Mevcut kayıtlardan bazılarını silmeyi deneyin." "Kayıp depolama birimi" - "DVR tarafından kullanılan bazı depolama alanları kayıp. DVR\'yi yeniden etkinleştirmeden önce lütfen kullandığınız harici sürücüyü bağlayın. Ayrıca bu depolama alanı artık kullanılmıyorsa unutulmasını da seçebilirsiniz." - "Depolama alanı unutulsun mu?" - "Kaydedilen tüm içeriğiniz ve programlarınız kaybolacaktır." "Kayıt durudurulsun mu?" "Kaydedilen içerik saklanacak." - - + "%1$s kaydı, bu programla çakıştığından durdurulacak. Kaydedilen içerik saklanacak." "Kayıt programlandı, ancak çakışmalar var" "Kayıt işlemi başladı, ancak çakışmalar var" "%1$s kaydedilecek." @@ -306,17 +299,29 @@ "Aynı program şu tarihte ve saatte kaydedilecek şekilde zaten programlandı: %1$s." "Zaten kaydedildi" "Bu program zaten kaydedildi. DVR kitaplığında bulabilirsiniz." - - - - - - - - + "Dizi kaydı programlandı" + + %2$s için %1$d kayıt programlandı. + %2$s için %1$d kayıt programlandı. + + + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle bu kayıtların %3$d tanesi yapılmayacak. + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle bu kayıt yapılmayacak. + + + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle bu dizinin ve diğer dizilerin %3$d bölümü kaydedilmeyecek. + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle bu dizinin ve diğer dizilerin %3$d bölümü kaydedilmeyecek. + + + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle diğer dizinin 1 bölümü kaydedilmeyecek. + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle diğer dizinin 1 bölümü kaydedilmeyecek. + + + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle diğer dizinin %3$d bölümü kaydedilmeyecek. + %2$s için %1$d kayıt programlandı. Çakışma nedeniyle diğer dizinin %3$d bölümü kaydedilmeyecek. + "Kaydedilen program bulunamadı." "İlgili kayıtlar" - "(Program açıklaması yok)" %1$d kayıt %1$d kayıt @@ -336,6 +341,7 @@ "Dizinin kaydı durdurulsun mu?" "Kaydedilen bölümler DVR kitaplığında kalmaya devam edecektir." "Durdur" + "Şu anda yayınlanan bir bölüm yok." "Kaydedilebilecek bir bölüm yok.\nBölümler kullanıma sunulduğunda kaydedilecektir." (%1$d dakika) @@ -347,4 +353,5 @@ "Bugün %1$s" "Yarın %1$s" "Puanı" + "Kayıtlı Programlar" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index b9d91a7a..1d673dfc 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -20,9 +20,8 @@ "моно" "стерео" "Керування відтворенням" - "Останні канали" + "Канали" "Опції ТБ" - "Опції PIP" "Елементи керування відтворенням, яких немає в цьому каналі" "Відтворити або призупинити" "Перемотати вперед" @@ -35,33 +34,15 @@ "Субтитри" "Режим показу" "PIP" - "Увімкнено" - "Вимкнено" "Кілька аудіо" "Більше каналів" "Налаштування" - "Джерело" - "Заміна" - "Увімкнено" - "Вимкнено" - "Звук" - "Основне вікно" - "Вікно PIP" - "Схема" - "Унизу праворуч" - "Угорі праворуч" - "Угорі ліворуч" - "Унизу ліворуч" - "Поруч" - "Розмір" - "Великий розмір" - "Малий розмір" - "Джерело сигналу" "ТБ (ефірне або кабельне)" "Немає інформації про програму" "Немає інформації." "Заблокований канал" - "Невідома мова" + "Невідома мова" + "Субтитри (%1$d)" "Субтитри" "Вимкнути" "Налаштувати формат" @@ -83,6 +64,7 @@ "SD" "Параметри групування" "Цю телепередачу заблоковано" + "Ця передача без вікових обмежень" "Ця телепередача належить до категорії %1$s" "Джерело вхідного сигналу не підтримує автоматичне сканування" "Не вдалось автоматично просканувати джерело сигналу \"%s\"" @@ -94,7 +76,6 @@ Додано %1$d каналу "Немає каналів" - "Тюнер" "Контроль батьків" "Увімкнути" "Вимкнути" @@ -110,6 +91,8 @@ "Інші країни" "Немає" "Немає" + "Без вікових обмежень" + "Блокувати передачі без вікових обмежень" "Немає" "Суворі обмеження" "Помірні обмеження" @@ -126,6 +109,7 @@ "Введіть PIN-код, щоб дивитися цей канал" "Введіть PIN-код, щоб дивитися цю телепередачу" "Вікові обмеження для цієї передачі: %1$s. Щоб дивитися її, введіть PIN-код" + "Це передача без вікових обмежень. Щоб дивитися її, введіть PIN-код" "Введіть PIN-код" "Щоб налаштувати батьківський контроль, створіть PIN-код" "Введіть новий PIN-код" @@ -139,22 +123,31 @@ "Цей PIN-код неправильний. Повторіть спробу." "PIN-коди не збігаються. Повторіть спробу" + "Введіть поштовий індекс." + "Додаток Телеканали показує повну програму телепередач на основі поштового індексу." + "Введіть поштовий індекс" + "Недійсний поштовий індекс" "Налаштування" "Налаштувати список каналів" "Вибрати канали для програми телепередач" "Джерела каналів" "Доступні нові канали" "Батьківський контроль" + "Зміщення в часі" + "Записуйте передачі під час перегляду, щоб потім призупиняти або перемотувати їх.\nЗастереження. Це може зменшити обсяг внутрішньої пам’яті через її інтенсивне використання." "Ліцензії відкритого коду" - "Ліцензії ПЗ з відкритим кодом" + "Надіслати відгук" "Версія" "Щоб дивитися цей канал, натисніть стрілку праворуч і введіть PIN-код" "Щоб дивитися цю телепередачу, натисніть стрілку праворуч і введіть PIN-код" + "Це передача без вікових обмежень.\nЩоб дивитися її, натисніть стрілку вправо та введіть PIN-код" "Ця телепередача належить до категорії %1$s.\nЩоб дивитися її, натисніть стрілку праворуч і введіть PIN-код." "Щоб дивитися цей канал, відкрийте додаток для прямих трансляцій за умовчанням." "Щоб дивитися цю телепередачу, відкрийте додаток для прямих трансляцій за умовчанням." + "Це передача без вікових обмежень.\nЩоб дивитися її, відкрийте додаток для прямих трансляцій за умовчанням." "Категорія цієї телепередачі – %1$s.\nЩоб дивитися її, відкрийте додаток для прямих трансляцій за умовчанням." "Телепередачу заблоковано" + "Ця передача без вікових обмежень" "Ця телепередача належить до категорії %1$s" "Лише аудіо" "Слабкий сигнал" @@ -179,7 +172,7 @@ "Нові" "Не налаштовано" "Отримати більше джерел" - "Переглянути додатки, у яких пропонуються канали в прямому ефірі" + "Переглянути додатки, у яких можна дивитися телеканали в прямому ефірі" "Доступні нові джерела каналів" "Нові джерела пропонують канали.\nНалаштуйте їх зараз або зробіть це пізніше в налаштуваннях джерел каналів." "Налаштувати" @@ -189,8 +182,6 @@ "Натисніть \"ВИБРАТИ\""", щоб відкрити меню телевізора." "Не знайдено джерел вхідного телесигналу" "Не вдається знайти джерело вхідного телесигналу" - "PIP не підтримується" - "Немає джерел вхідного сигналу для PIP" "Тюнер не підтримується. Щоб використовувати тюнер, запустіть додаток Live TV." "Не вдалося налаштувати" "Не знайдено додатка для цієї дії." @@ -199,7 +190,7 @@ "Клавіша \"НАЗАД\" діє на підключеному пристрої. Натисніть \"ГОЛОВНИЙ ЕКРАН\", щоб вийти." "Додатку Live TV потрібен дозвіл переглядати програму телепередач." "Налаштуйте джерела" - "У телеканалах поєднуються зручність традиційних каналів і трансляція каналів із додатків. \n\nЩоб почати, налаштуйте вже встановлені джерела каналів. Або пошукайте в магазині Google Play інші додатки з телеканалами." + "Телеканали об\'єднують звичайні канали й потокові трансляції з додатків. \n\nПочніть із налаштування вже наявних джерел каналів або пошукайте в магазині Google Play інші додатки з телеканалами." "Записи та графіки" "10 хвилин" "30 хвилин" @@ -244,6 +235,8 @@ Заплановано %1$d записів Заплановано %1$d запису + "Скасувати запис" + "Припинити запис" "Переглянути" "Відтворити з початку" "Відновити відтворення" @@ -278,9 +271,6 @@ "Якщо є забагато програм для одночасного запису, записуватимуться лише програми з вищим пріоритетом." "Зберегти" "Одноразові записи мають найвищий пріоритет" - "Скасувати" - "Скасувати" - "Забути" "Припинити" "Переглянути розклад запису" "Лише ця передача" @@ -290,25 +280,28 @@ "Натомість записати цю передачу" "Скасувати цей запис" "Дивитися" + "Видалити записи…" "Можна записати" "Запис заплановано" "Конфлікт запису" "Запис" "Не записано" - "Читаються назви передач для створення розкладів" - "Читання даних програм" - - + "Читання інформації про передачі" + "Переглянути нещодавні записи" + "Запис передачі \"%1$s\" не завершено." + "Запис передач \"%1$s\" і \"%2$s\" не завершено." + "Запис передач \"%1$s\", \"%2$s\" і \"%3$s\" не завершено." + "Запис передачі \"%1$s\" не завершено. Замало місця." + "Запис передач \"%1$s\" і \"%2$s\" не завершено. Замало місця." + "Запис передач \"%1$s\", \"%2$s\" і \"%3$s\" не завершено. Замало місця." "Пристрою DVR потрібно більше пам’яті" - "За допомогою пристрою DVR можна записувати програми, однак на ньому недостатньо пам’яті. Підключіть зовнішній диск ємністю %1$s Гб або більше та дотримуйтеся вказівок, щоб відформатувати його як пам’ять пристрою." + "За допомогою пристрою DVR можна записувати програми, однак на ньому недостатньо пам’яті. Підключіть зовнішній диск ємністю %1$d ГБ або більше та дотримуйтеся вказівок, щоб відформатувати його як пам’ять пристрою." + "Замало пам’яті" + "Замало пам’яті. Цю передачу не буде записано. Спробуйте видалити деякі наявні записи." "Немає пам’яті" - "Немає деякої пам’яті, яку використовує DVR. Щоб знову ввімкнути DVR, під’єднайте зовнішній диск, який ви використовували раніше. Можна також забути пам’ять, якщо вона недоступна." - "Забути пам’ять?" - "Увесь записаний вміст і розклади буде втрачено." "Припинити запис?" "Записаний вміст буде збережено." - - + "Запис серіалу \"%1$s\" буде зупинено через конфлікти з ним. Записаний вміст буде збережено." "Запис заплановано, однак є конфлікти" "Запис почався, однак є конфлікти" "Програму \"%1$s\" буде записано." @@ -328,17 +321,39 @@ "Цю передачу вже заплановано записати о %1$s" "Уже записано" "Цю передачу вже записано. Вона доступна в бібліотеці DVR." - - - - - - - - + "Заплановано запис серіалу" + + Заплановано %1$d запис серіалу \"%2$s\". + Заплановано %1$d записи серіалу \"%2$s\". + Заплановано %1$d записів серіалу \"%2$s\". + Заплановано %1$d запису серіалу \"%2$s\". + + + Заплановано %1$d запис серіалу \"%2$s\". Через конфлікти не буде записано %3$d із них. + Заплановано %1$d записи серіалу \"%2$s\". Через конфлікти не буде записано %3$d з них. + Заплановано %1$d записів серіалу \"%2$s\". Через конфлікти не буде записано %3$d із них. + Заплановано %1$d запису серіалу \"%2$s\". Через конфлікти не буде записано %3$d із них. + + + Заплановано %1$d запис серіалу \"%2$s\". Через конфлікти не буде записано стільки серій цього й іншого серіалів: %3$d. + Заплановано %1$d записи серіалу \"%2$s\". Через конфлікти не буде записано стільки серій цього й іншого серіалів: %3$d. + Заплановано %1$d записів серіалу \"%2$s\". Через конфлікти не буде записано стільки серій цього й іншого серіалів: %3$d. + Заплановано %1$d запису серіалу \"%2$s\". Через конфлікти не буде записано стільки серій цього й іншого серіалів: %3$d. + + + Заплановано %1$d запис серіалу \"%2$s\". Через конфлікти не буде записано 1 серію іншого серіалу. + Заплановано %1$d записи серіалу \"%2$s\". Через конфлікти не буде записано 1 серію іншого серіалу. + Заплановано %1$d записів серіалу \"%2$s\". Через конфлікти не буде записано 1 серію іншого серіалу. + Заплановано %1$d запису серіалу \"%2$s\". Через конфлікти не буде записано 1 серію іншого серіалу. + + + Заплановано %1$d запис серіалу \"%2$s\". Через конфлікти не буде записано стільки серій іншого серіалу: %3$d. + Заплановано %1$d записи серіалу \"%2$s\". Через конфлікти не буде записано стільки серій іншого серіалу: %3$d. + Заплановано %1$d записів серіалу \"%2$s\". Через конфлікти не буде записано стільки серій іншого серіалу: %3$d. + Заплановано %1$d запису серіалу \"%2$s\". Через конфлікти не буде записано стільки серій іншого серіалу: %3$d. + "Не вдалося знайти записані програми." "Пов’язані записи" - "(Немає опису програми)" %1$d запис %1$d записи @@ -362,6 +377,7 @@ "Припинити запис серій?" "Записані серії можна переглянути в бібліотеці DVR." "Припинити" + "Зараз серії не транслюються." "Немає серій.\nСерії буде записано, щойно вони з’являться." (%1$d хвилина) @@ -375,4 +391,5 @@ "Сьогодні о %1$s" "Завтра о %1$s" "Оцінка" + "Записані передачі" diff --git a/res/values-ur-rPK-v23/strings.xml b/res/values-ur-rPK-v23/strings.xml new file mode 100644 index 00000000..22ec8b49 --- /dev/null +++ b/res/values-ur-rPK-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "چینلز" + diff --git a/res/values-ur-rPK/arrays.xml b/res/values-ur-rPK/arrays.xml new file mode 100644 index 00000000..f54b4914 --- /dev/null +++ b/res/values-ur-rPK/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "حسب معمول" + "مکمل" + "زوم" + + + "سبھی چینلز" + "خاندان/بچے" + "کھیل" + "خریداری" + "موویز" + "کامیڈی" + "سفر" + "ڈراما" + "تعلیم" + "جانور/وائلڈ لائف" + "خبریں" + "گیم سازی" + "فنون" + "تفریح" + "طرز زندگی" + "موسیقی" + "پریمیئر" + "ٹیک/سائنس" + + + "لائیو چینلز" + "مواد دریافت کرنے کا ایک سادہ طریقہ" + "ایپس ڈاؤن لوڈ کریں، مزید چینلز حاصل کریں" + "اپنا چینل لائن اپ حسب ضرورت بنائیں" + + + "‏TV پر چینلز دیکھنے کی طرح اپنی ایپس سے مواد دیکھیں۔" + "‏ایک مانوس گائیڈ اور دوستانہ انٹرفیس کے ساتھ اپنی ایپس سے مواد براؤز کریں، \nبالکل TV چینلز کی طرح۔" + "‏لائیو چینلز کی پیشکش کرنے والی ایپس انسٹال کر کے مزید چینلز شامل کریں۔\nTVمینو کے اندر دیا گیا لنک استعمال کر کے Google Play اسٹور میں موافق ایپس تلاش کریں۔" + "اپنے چینل کی فہرست کو حسب ضرورت بنانے کیلئے اپنے نئے انسٹال کردہ چینل ذرائع سیٹ اپ کریں۔ \nشروع کرنے کیلئے ترتیبات مینو کے اندر موجود چینل ذرائع سے انتخاب کریں۔" + + diff --git a/res/values-ur-rPK/rating_system_strings.xml b/res/values-ur-rPK/rating_system_strings.xml new file mode 100644 index 00000000..07f99a99 --- /dev/null +++ b/res/values-ur-rPK/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "پروگراموں میں ایسا مواد ہو سکتا ہے جو 15 سال سے کم عمر کے بچوں کیلئے غیر مناسب ہو، اس لئے ان کیلئے والدین کی صوابدید استعمال کرنی چاہئیے۔" + "پروگراموں میں ایسا مواد ہو سکتا ہے جو 19 سال سے کم عمر افراد کیلئے غیر مناسب ہو، اور اس لئے یہ پروگرام 19 سال سے چھوٹے نوجوانوں کیلئے موزوں نہیں ہیں۔" + "ہیجان خیز بات چیت" + "نامناسب زبان" + "جنسی مواد" + "تشدد" + "فینٹیسی تشدد" + "اس پروگرام کو سبھی بچوں کیلئے مناسب ہونے کیلئے ڈیزائن کیا گیا ہے۔" + "یہ پروگرام 7 سال اور اس سے زیادہ عمر کے بچوں کیلئے ڈیزائن کیا گیا ہے۔" + "زیادہ تر والدین اس پروگرام کو سبھی عمر کے افراد کیلئے مناسب پائیں گے۔" + "اس پروگرام میں ایسا مواد شامل ہے جسے والدین شاید چھوٹے بچوں کیلئے نامناسب پائیں۔ بہت سے والدین شاید اسے چھوٹے بچوں کے ساتھ دیکھنا چاہیں۔" + "اس پروگرام میں کچھ ایسا مواد شامل ہے جسے بہت سے والدین 14 سال سے کم عمر کے بچوں کیلئے نامناسب پائیں گے۔" + "یہ پروگرام خاص طور پر بالغوں کے دیکھنے کیلئے ڈیزائن کیا گیا ہے اور اسی لیے 17 سال سے کم عمر کے بچوں کیلئے نامناسب ہو سکتا ہے۔" + "فلم کی درجہ بندیاں" + "عام ناظرین. کوئی ایسی چیز نہیں جسے بچے دیکھ لیں تو والدین کو ناگوار گزرے۔" + "والدین کی رہنمائی مجوزہ ہے۔ کچھ ایسا مواد ہو سکتا ہے جسے والدین اپنے چھوٹے بچوں کیلئے پسند نہ کریں۔" + "والدین کو بہت زیادہ تنبیہ کی جاتی ہے۔ کچھ مواد قبل از لڑکپن بچوں کیلئے نامناسب ہو سکتا ہے۔" + "محدود، کچھ بالغ مواد شامل ہے۔ والدین سے گزارش ہے کہ اپنے چھوٹے بچوں کو ساتھ لے کر جانے سے پہلے فلم کے متعلق مزید جان لیں۔" + "17 سال اور اس سے کم عمر کے کسی فرد کو داخلے کی اجازت نہیں۔ واضح طور پر بالغ۔ بچوں کو داخل نہیں کیا جاتا۔" + diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml new file mode 100644 index 00000000..36fed928 --- /dev/null +++ b/res/values-ur-rPK/strings.xml @@ -0,0 +1,357 @@ + + + + + "مونو" + "اسٹیریو" + "پلے کنٹرولز" + "چینلز" + "‏TV کے اختیارات" + "چلانے کے کنٹرولز اس چینل کیلئے غیر دستیاب ہیں" + "چلائیں یا موقوف کریں" + "تیزی سے فارورڈ کریں" + "ریوائینڈ کریں" + "آگے" + "پچھلا" + "پروگرام گائیڈ" + "نئے چینلز دستیاب ہیں" + "%1$s کھولیں" + "سب ٹائٹلز" + "ڈسپلے وضع" + "PIP" + "کثیر آڈیو" + "مزید چینلز حاصل کریں" + "ترتیبات" + "‏TV (اینٹینا/کیبل)" + "پروگرام کی معلومات نہیں" + "کوئی معلومات نہیں ہے" + "مسدود چینل" + "نامعلوم زبان" + "‏سب ٹائٹلز ‎%1$d" + "سب ٹائٹلز" + "آف" + "فارمیٹنگ کسٹمائز کریں" + "سب ٹائٹل کیلئے سسٹم وار ترجیحات سیٹ کریں" + "ڈسپلے وضع" + "کثیر آڈیو" + "مونو" + "اسٹیریو" + "5.1 گھیر" + "7.1 گھیر" + "‏‎%d چینلز" + "چینل لسٹ کسٹمائز کریں" + "گروپ منتخب کریں" + "گروپ غیر منتخب کریں" + "گروپ بلحاظ" + "چینل ماخذ" + "HD/SD" + "HD" + "SD" + "گروپ بلحاظ" + "یہ پروگرام مسدود ہے" + "یہ پروگرام درجہ بند نہیں ہے" + "اس پروگرام کی درجہ بندی %1$s کی گئی ہے" + "ان پٹ آٹو اسکین کو سپورٹ نہیں کرتا" + "\'%s\' کیلئے آٹو اسکین شروع کرنے سے قاصر ہے" + "سب ٹائٹلز کیلئے سسٹم وار ترجیحات شروع کرنے سے قاصر ہے۔" + + ‏%1$d چینل شامل ہو گئے + ‏%1$d چینل شامل ہو گیا + + "کوئی چینلز شامل نہیں کیے گئے" + "پیرنٹل کنٹرولز" + "آن" + "آف" + "چینلز مسدود کر دیے" + "سبھی کو مسدود کریں" + "سبھی غیر مسدود کریں" + "مخفی چینلز" + "پروگرام پابندیاں" + "‏PIN تبدیل کریں" + "درجہ بندی کے سسٹمز" + "درجہ بندیاں" + "درجہ بندی کے سبھی سسٹمز دیکھیں" + "دیگر ممالک" + "کوئی نہیں" + "کوئی نہیں" + "غیر درجہ بند" + "غیردرجہ بند پروگرام مسدود کریں" + "کوئی نہیں" + "زیادہ پابندیاں" + "متوسط پابندیاں" + "کم پابندیاں" + "حسب ضرورت" + "بچوں کیلئے مناسب مواد" + "بڑی عمر کے بچوں کیلئے مناسب مواد" + "نوعمروں کیلئے مناسب مواد" + "دستی پابندیاں" + + + "‏%1$s اور ذیلی درجہ بندیاں" + "ذیلی درجہ بندیاں" + "‏یہ چینل دیکھنے کیلئے اپنا PIN درج کریں" + "‏یہ پروگرام دیکھنے کیلئے اپنا PIN درج کریں" + "‏اس پروگرام کی درجہ بندی %1$s ہوئی ہے۔ اس پروگرام کو دیکھنے کیلئے اپنی PIN درج کریں" + "‏یہ پروگرام درجہ بند نہیں ہے۔ اس پروگرام کو دیکھنے کے لیے اپنا PIN درج کریں" + "‏اپنا PIN درج کریں" + "‏پیرنٹل کنٹرولز سیٹ کرنے کیلئے، ایک PIN بنائیں" + "‏نیا PIN درج کریں" + "‏اپنے PIN کی توثیق کریں" + "‏اپنا موجودہ PIN درج کریں" + + ‏آپ نے 5 بار غلط PIN درج کی۔\n%1$d سیکنڈز بعد دوبارہ کوشش کریں۔ + ‏آپ نے 5 بار غلط PIN درج کی۔\n%1$d سیکنڈ بعد دوبارہ کوشش کریں۔ + + "‏وہ PIN غلط تھا۔ دوبارہ کوشش کریں۔" + "‏دوبارہ کوشش کریں، PIN مماثل نہیں ہے" + "اپنا زپ کوڈ درج کریں۔" + "‏لائیو چینلز ایپ TV چینلز کیلئے ایک مکمل پروگرام گائیڈ فراہم کرنے کیلئے زپ کوڈ استعمال کرے گی۔" + "اپنا زپ کوڈ درج کریں" + "غلط زپ کوڈ" + "ترتیبات" + "چینل فہرست حسب ضرورت بنائیں" + "اپنی پروگرام گائیڈ کیلئے چینلز منتخب کریں" + "چینل کے مآخذ" + "نئے چینلز دستیاب ہیں" + "پیرنٹل کنٹرولز" + "ٹائم شفٹ" + "دیکھنے کے دوران ریکارڈ کریں تاکہ آپ لائیو پروگرامز کو موقوف یا ریوائنڈ کر سکیں۔\nتنبیہ: اس کے باعث اسٹوریج کے بے تحاشا استعمال کی وجہ سے شاید اندرونی اسٹوریج کی زندگی کم ہو جائے۔" + "اوپن سورس لائسنسز" + "تاثرات بھیجیں" + "ورژن" + "‏یہ چینل دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" + "‏یہ پروگرام دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" + "‏یہ پروگرام درجہ بند نہیں ہے۔\nاس پروگرام کو دیکھنے کے لیے، دائیں جانب دبا کر اپنا PIN درج کریں" + "‏اس پروگرام کی درجہ بندی %1$s کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" + "‏اس چینل کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" + "‏اس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" + "‏یہ پروگرام درجہ بند نہیں ہے۔\nاس پروگرام کو دیکھنے کے لیے، ڈیفالٹ لائیو TV ایپ استعمال کریں۔" + "‏اس پروگرام کی درجہ بندی %1$s کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" + "پروگرام مسدود ہے" + "یہ پروگرام درجہ بند نہیں ہے" + "اس پروگرام کی درجہ بندی %1$s کی گئی ہے" + "صرف آڈیو" + "کمزور سگنل" + "کوئی انٹرنیٹ کنکشن نہیں ہے" + + یہ چینل %1$s تک نہیں چلایا جا سکتا کیونکہ دوسرے چینلز کو ریکارڈ کیا جا رہا ہے۔ \n\nریکارڈنگ کا شیڈول ایڈجسٹ کرنے کیلئے دائیں طرف دبائیں۔ + یہ چینل %1$s تک نہیں چلایا جا سکتا کیونکہ ایک اور چینل کو ریکارڈ کیا جا رہا ہے۔ \n\nریکارڈنگ کا شیڈول ایڈجسٹ کرنے کیلئے دائیں طرف دبائیں۔ + + "کوئی عنوان نہیں ہے" + "چینل مسدود کر دیا گیا" + "نئے" + "مآخذ" + + ‏%1$d چینلز + ‏%1$d چینل + + "کوئی چینلز دستیاب نہیں ہیں" + "نئے" + "ترتیب نہیں دیا گیا" + "مزید مآخذ حاصل کریں" + "لائیو چینلز کی پیشکش کرنے والی ایپس براؤز کریں" + "نئے چینل ماخذین دستیاب ہیں" + "نئے چینل ماخذین کے پاس پیشکش کیلئے چینل ہیں۔\nانہیں ابھی سیٹ اپ کریں یا چینل ماخذ ترتیب میں بعد میں کریں۔" + "ابھی سیٹ اپ کریں" + "ٹھیک ہے، سمجھ آ گئی" + + + "‏TV مینو تک رسائی حاصل کرنے کیلئے ""منتخب کریں کو دبائیں""۔" + "‏کوئی TV ان پٹ نہیں ملا" + "‏TV ان پٹ تلاش نہیں کیا جا سکتا" + "‏ٹیونر کی قسم مناسب نہیں ہے۔ براہ کرم ٹیونر کی قسم والے TV ان پٹ کیلئے لائیو چینلز ایپ شروع کریں۔" + "ٹیون کرنا ناکام ہوگیا" + "اس کارروائی کو نمٹانے کیلئے کوئی ایپ نہیں ملی۔" + "سبھی ماخذ چینلز مخفی ہیں۔ \nدیکھنے کیلئے کم از کم ایک چینل منتخب کریں۔" + "ویڈیو غیر متوقع طور پر دستیاب نہیں ہے" + "‏BACK کلید منسلک آلہ کیلئے ہے۔ باہر نکلنے کیلئے HOME بٹن دبائیں۔" + "‏لائیو چینلز کو TV فہرستیں پڑھنے کیلئے اجازت کی ضرورت ہے۔" + "اپنے ذرائع سیٹ اپ کریں" + "‏لائیو چینلز روایتی TV چینلز کے تجربے کو ایپس کی جانب سے فراہم کردہ اسٹریمنگ چینلز سے ملاتے ہیں۔ \n\nپہلے سے انسٹال شدہ چینل ماخذین کو سیٹ اپ کر کے شروع کریں۔ یا لائیو چینلز کی پیشکش کرنے والی مزید ایپس کیلئے Google Play اسٹور براؤز کریں۔" + "ریکارڈنگز اور شیڈولز" + "10 منٹ" + "30 منٹ" + "1 گھنٹہ" + "3 گھنٹے" + "حالیہ" + "شیڈول کردہ" + "سلسلہ" + "دیگر" + "چینل ریکارڈ نہیں ہو سکتا۔" + "پروگرام ریکارڈ نہیں ہو سکتا۔" + "%1$s کی ریکارڈنگ کیلئے شیڈول بن گیا ہے" + "اب سے لے کر %2$s تک %1$s ریکارڈ ہو رہا ہے" + "پورا شیڈول" + + ‏اگلے ‎%1$d دن + ‏اگلا ‎%1$d دن + + + ‏%1$d منٹس + ‏%1$d منٹ + + + ‏%1$d نئی ریکارڈنگز + ‏%1$d نئی ریکارڈنگ + + + ‏%1$d ریکارڈنگز + ‏%1$d ریکارڈنگ + + + ‏%1$d ریکارڈنگز کا شیڈول بن گیا + ‏%1$d ریکارڈنگ کا شیڈول بن گيا + + "ریکارڈنگ منسوخ کریں" + "ریکارڈنگ روکیں" + "دیکھیں" + "شروع سے چلائیں" + "چلانا دوبارہ شروع کریں" + "حذف کریں" + "ریکارڈنگز حذف کریں" + "دوبارہ شروع کریں" + "سیزن %1$s" + "شیڈول دیکھیں" + "مزید پڑھیں" + "ریکارڈنگز حذف کریں" + "وہ ایپی سوڈز منتخب کریں جو آپ حذف کرنا چاہتے ہیں۔ ایک بار حذف ہونے کے بعد ان کو بازیاب نہیں کیا جا سکتا۔" + "حذف کرنے کیلئے کوئی ریکارڈنگ نہیں ہے۔" + "دیکھی گئی ایپی سوڈز منتخب کریں" + "تمام ایپی سوڈز منتخب کریں" + "تمام ایپی سوڈز غیر منتخب کریں" + "%2$d میں سے %1$d منٹ کی دیکھ لیں" + "%2$d میں سے %1$d سیکنڈ کی دیکھ لیں" + "کبھی نہیں دیکھیں" + + ‏%2$d میں سے ‎%1$d ایپی سوڈز حذف ہو گئیں + ‏%2$d میں سے ‎%1$d ایپی سوڈ حذف ہو گئی + + "ترجیح" + "سب سے اعلی" + "سب سے کم" + "نہیں۔ %1$d" + "چینلز" + "کوئی بھی" + "ترجیح کا انتخاب کریں" + "جب ایک وقت میں بہت سارے پروگرامز ریکارڈ کرنے ہوں تو صرف اعلی ترجیح والے پروگرامز ریکارڈ کیے جائیں گے۔" + "محفوظ کریں" + "یک وقتی ریکارڈنگز کو سب سے اعلی ترجیح حاصل ہوتی ہے" + "روکیں" + "ریکارڈنگ کا شیڈول ملاحظہ کریں" + "یہ واحد پروگرام" + "ابھی - %1$s" + "پوری سیریز…" + "کسی بھی طرح شیڈول بنائیں" + "اس کی بجائے یہ ریکارڈ کریں" + "اس ریکارڈنگ کو منسوخ کریں" + "ابھی دیکھیں" + "ریکارڈنگز حذف کریں…" + "قابل ریکارڈ" + "ریکارڈنگ کا شیڈول" + "ریکارڈنگ شیڈول میں تصادم" + "ریکارڈ ہو رہا ہے" + "ریکارڈنگ ناکام ہو گئی" + "پروگرامز پڑھے جا رہے ہیں" + "حالیہ ریکارڈنگز ملاحظہ کریں" + "%1$s کی ریکارڈنگ نامکمل ہے۔" + "%1$s اور %2$s کی ریکارڈنگز نامکمل ہیں۔" + "%1$s، %2$s اور %3$s کی ریکارڈنگز نامکمل ہیں۔" + "ناکافی اسٹوریج کی وجہ سے %1$s کی ریکارڈنگ مکمل نہیں ہوئی۔" + "ناکافی اسٹوریج کی وجہ سے %1$s اور %2$s کی ریکارڈنگز مکمل نہیں ہوئیں۔" + "ناکافی اسٹوریج کی وجہ سے %1$s، %2$s اور %3$s کی ریکارڈنگز مکمل نہیں ہوئیں۔" + "‏DVR کو مزید اسٹوریج درکار ہے" + "‏آپ DVR کے ساتھ پروگرامز ریکارڈ کر سکیں گے۔ تاہم آپ کے آلہ پر DVR کے کام کرنے کیلئے ابھی کافی اسٹوریج نہیں ہے۔ براہ کرم ایک بیرونی ڈرائیو منسلک کریں جو GB %1$d یا اس سے بڑی ہو اور اسے آلہ کی اسٹوریج کے بطور فارمیٹ کرنے کیلئے مراحل کی پیروی کریں۔" + "اسٹوریج کافی نہیں ہے" + "یہ پروگرام ریکارڈ نہیں ہوگا کیونکہ اسٹوریج کافی نہیں ہے۔ کچھ موجودہ ریکارڈنگز حذف کرنے کی کوشش کریں۔" + "اسٹوریج غائب ہے" + "ریکارڈنگ روکیں؟" + "ریکارڈ شدہ مواد محفوظ ہو جائے گا۔" + "%1$s کی ریکارڈنگ روک دی جائے گی کیونکہ یہ اس پروگرام کے ساتھ متضاد ہے۔ ریکارڈ کردہ مواد محفوظ ہو جائے گا۔" + "ریکارڈنگ کا شیڈول لیکن تضادات" + "ریکارڈنگ شروع ہو گئی ہے لیکن اس میں تضادات ہیں" + "%1$s ریکارڈ ہوجائے گا۔" + "%1$s ریکارڈ ہو رہا ہے۔" + "%1$s کے کچھ حصے ریکارڈ نہیں ہوں گے۔" + "%1$s اور %2$s کے کچھ حصے ریکارڈ نہیں ہوں گے۔" + "%1$s، %2$s کے کچھ حصے اور ایک اور شیڈول ریکارڈ نہیں ہوگا۔" + + %1$s، %2$s کے کچھ حصے اور ‎%3$d مزید شیڈولز ریکارڈ نہیں ہوں گے۔ + %1$s، %2$s کے کچھ حصے اور ‎%3$d مزید شیڈول ریکارڈ نہیں ہوگا۔ + + "آپ کیا ریکارڈ کرنا چاہیں گے؟" + "آپ کتنی دیر تک ریکارڈ کرنا پسند کریں گے؟" + "پہلے سے شیڈول کردہ" + "اسی پروگرام کا پہلے ہی %1$s بجے ریکارڈ ہونے کیلئے شیڈول بنا ہوا ہے۔" + "پہلے سے ریکارڈ شدہ" + "‏یہ پروگرام پہلے سے ہی ریکارڈ شدہ ہے۔ یہ DVR لائبریری میں دستیاب ہے۔" + "سیریز کی ریکارڈنگ کا شیڈول بن گیا ہے" + + %2$s کیلئے %1$d ریکارڈنگز کا شیڈول بن گیا ہے۔ + %2$s کیلئے %1$d ریکارڈنگ کا شیڈول بن گیا ہے۔ + + + %2$s کیلئے %1$d ریکارڈنگز کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے ان میں سے %3$d ریکارڈ نہیں ہوں گی۔ + %2$s کیلئے %1$d ریکارڈنگ کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے اس کی ریکارڈنگ نہیں ہو گی۔ + + + %2$s کیلئے %1$d ریکارڈنگز کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے اس سیریز اور دیگر سیریز کی %3$d اقساط ریکارڈ نہیں ہوں گی۔ + %2$s کیلئے %1$d ریکارڈنگ کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے اس سیریز اور دیگر سیریز کی %3$d اقساط ریکارڈ نہیں ہوں گی۔ + + + %2$s کیلئے %1$d ریکارڈنگز کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے دیگر سیریز کی 1 قسط ریکارڈ نہیں ہو گی۔ + %2$s کیلئے %1$d ریکارڈنگ کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے دیگر سیریز کی 1 قسط ریکارڈ نہیں ہو گی۔ + + + %2$s کیلئے %1$d ریکارڈنگز کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے دیگر سیریز کی %3$d اقساط ریکارڈ نہیں ہوں گی۔ + %2$s کیلئے %1$d ریکارڈنگ کا شیڈول بن گیا ہے۔ تضادات کی وجہ سے دیگر سیریز کی %3$d اقساط ریکارڈ نہیں ہوں گی۔ + + "ریکارڈ شدہ پروگرام نہیں ملا۔" + "متعلقہ ریکارڈنگز" + + ‏%1$d ریکارڈنگز + ‏%1$d ریکارڈنگ + + " / " + "%1$s کو ریکارڈنگ شیڈول سے ہٹا دیا گیا" + "ٹیونر تضادات کی وجہ سے جزوی طور پر ریکارڈ ہوگا۔" + "ٹیونر تضادات کی وجہ سے ریکارڈ نہیں ہوگا۔" + "ابھی شیڈول میں کوئی ریکارڈنگز نہیں ہیں۔\nآپ پروگرام گائیڈ سے ریکارڈنگ کا شیڈول بنا سکتے ہیں۔" + + ‏%1$d ریکارڈنگ تنازعات + ‏%1$d ریکارڈنگ تنازعہ + + "سیریز کی ترتیبات" + "سیریز کی ریکارڈنگ شروع کریں" + "سیریز کی ریکارڈنگ روکیں" + "سیریز ریکارڈنگ روکیں؟" + "‏ریکارڈ شدہ ایپی سوڈز DVR لائبریری میں دستیاب رہیں گے۔" + "روکیں" + "ابھی کوئی ایپی سوڈ آن ائیر نہیں ہے۔" + "کوئی ایپی سوڈز دستیاب نہیں۔\nایک بار دستیاب ہوجائے تو ان کو ریکارڈ کیا جائے گا۔" + + ‏(%1$d منٹ) + ‏(%1$d منٹ) + + "ﺁﺝ" + "آئندہ کل" + "گزشتہ کل" + "%1$s آج" + "%1$s آئندہ کل" + "اسکور" + "ریکارڈ شدہ پروگرامز" + diff --git a/res/values-ur-v23/strings.xml b/res/values-ur-v23/strings.xml deleted file mode 100644 index 22ec8b49..00000000 --- a/res/values-ur-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "چینلز" - diff --git a/res/values-ur/arrays.xml b/res/values-ur/arrays.xml deleted file mode 100644 index f54b4914..00000000 --- a/res/values-ur/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "حسب معمول" - "مکمل" - "زوم" - - - "سبھی چینلز" - "خاندان/بچے" - "کھیل" - "خریداری" - "موویز" - "کامیڈی" - "سفر" - "ڈراما" - "تعلیم" - "جانور/وائلڈ لائف" - "خبریں" - "گیم سازی" - "فنون" - "تفریح" - "طرز زندگی" - "موسیقی" - "پریمیئر" - "ٹیک/سائنس" - - - "لائیو چینلز" - "مواد دریافت کرنے کا ایک سادہ طریقہ" - "ایپس ڈاؤن لوڈ کریں، مزید چینلز حاصل کریں" - "اپنا چینل لائن اپ حسب ضرورت بنائیں" - - - "‏TV پر چینلز دیکھنے کی طرح اپنی ایپس سے مواد دیکھیں۔" - "‏ایک مانوس گائیڈ اور دوستانہ انٹرفیس کے ساتھ اپنی ایپس سے مواد براؤز کریں، \nبالکل TV چینلز کی طرح۔" - "‏لائیو چینلز کی پیشکش کرنے والی ایپس انسٹال کر کے مزید چینلز شامل کریں۔\nTVمینو کے اندر دیا گیا لنک استعمال کر کے Google Play اسٹور میں موافق ایپس تلاش کریں۔" - "اپنے چینل کی فہرست کو حسب ضرورت بنانے کیلئے اپنے نئے انسٹال کردہ چینل ذرائع سیٹ اپ کریں۔ \nشروع کرنے کیلئے ترتیبات مینو کے اندر موجود چینل ذرائع سے انتخاب کریں۔" - - diff --git a/res/values-ur/rating_system_strings.xml b/res/values-ur/rating_system_strings.xml deleted file mode 100644 index 07f99a99..00000000 --- a/res/values-ur/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "پروگراموں میں ایسا مواد ہو سکتا ہے جو 15 سال سے کم عمر کے بچوں کیلئے غیر مناسب ہو، اس لئے ان کیلئے والدین کی صوابدید استعمال کرنی چاہئیے۔" - "پروگراموں میں ایسا مواد ہو سکتا ہے جو 19 سال سے کم عمر افراد کیلئے غیر مناسب ہو، اور اس لئے یہ پروگرام 19 سال سے چھوٹے نوجوانوں کیلئے موزوں نہیں ہیں۔" - "ہیجان خیز بات چیت" - "نامناسب زبان" - "جنسی مواد" - "تشدد" - "فینٹیسی تشدد" - "اس پروگرام کو سبھی بچوں کیلئے مناسب ہونے کیلئے ڈیزائن کیا گیا ہے۔" - "یہ پروگرام 7 سال اور اس سے زیادہ عمر کے بچوں کیلئے ڈیزائن کیا گیا ہے۔" - "زیادہ تر والدین اس پروگرام کو سبھی عمر کے افراد کیلئے مناسب پائیں گے۔" - "اس پروگرام میں ایسا مواد شامل ہے جسے والدین شاید چھوٹے بچوں کیلئے نامناسب پائیں۔ بہت سے والدین شاید اسے چھوٹے بچوں کے ساتھ دیکھنا چاہیں۔" - "اس پروگرام میں کچھ ایسا مواد شامل ہے جسے بہت سے والدین 14 سال سے کم عمر کے بچوں کیلئے نامناسب پائیں گے۔" - "یہ پروگرام خاص طور پر بالغوں کے دیکھنے کیلئے ڈیزائن کیا گیا ہے اور اسی لیے 17 سال سے کم عمر کے بچوں کیلئے نامناسب ہو سکتا ہے۔" - "فلم کی درجہ بندیاں" - "عام ناظرین. کوئی ایسی چیز نہیں جسے بچے دیکھ لیں تو والدین کو ناگوار گزرے۔" - "والدین کی رہنمائی مجوزہ ہے۔ کچھ ایسا مواد ہو سکتا ہے جسے والدین اپنے چھوٹے بچوں کیلئے پسند نہ کریں۔" - "والدین کو بہت زیادہ تنبیہ کی جاتی ہے۔ کچھ مواد قبل از لڑکپن بچوں کیلئے نامناسب ہو سکتا ہے۔" - "محدود، کچھ بالغ مواد شامل ہے۔ والدین سے گزارش ہے کہ اپنے چھوٹے بچوں کو ساتھ لے کر جانے سے پہلے فلم کے متعلق مزید جان لیں۔" - "17 سال اور اس سے کم عمر کے کسی فرد کو داخلے کی اجازت نہیں۔ واضح طور پر بالغ۔ بچوں کو داخل نہیں کیا جاتا۔" - diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml deleted file mode 100644 index 47fcb2ad..00000000 --- a/res/values-ur/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "مونو" - "اسٹیریو" - "پلے کنٹرولز" - "حالیہ چینلز" - "‏TV کے اختیارات" - "‏PIP کے اختیارات" - "چلانے کے کنٹرولز اس چینل کیلئے غیر دستیاب ہیں" - "چلائیں یا موقوف کریں" - "تیزی سے فارورڈ کریں" - "ریوائینڈ کریں" - "آگے" - "پچھلا" - "پروگرام گائیڈ" - "نئے چینلز دستیاب ہیں" - "%1$s کھولیں" - "سب ٹائٹلز" - "ڈسپلے وضع" - "PIP" - "آن" - "آف" - "کثیر آڈیو" - "مزید چینلز حاصل کریں" - "ترتیبات" - "ماخذ" - "تبادلہ کریں" - "آن" - "آف" - "آواز" - "مرکزی" - "‏PIP ونڈو" - "لے آؤٹ" - "نیچے دائیں" - "اوپری دائیں" - "اوپری بائیں" - "نیچے بائیں" - "سمت بہ سمت" - "سائز" - "بڑا" - "چھوٹا" - "ان پٹ ماخذ" - "‏TV (اینٹینا/کیبل)" - "پروگرام کی معلومات نہیں" - "کوئی معلومات نہیں ہے" - "مسدود چینل" - "نامعلوم زبان" - "سب ٹائٹلز" - "آف" - "فارمیٹنگ کسٹمائز کریں" - "سب ٹائٹل کیلئے سسٹم وار ترجیحات سیٹ کریں" - "ڈسپلے وضع" - "کثیر آڈیو" - "مونو" - "اسٹیریو" - "5.1 گھیر" - "7.1 گھیر" - "‏‎%d چینلز" - "چینل لسٹ کسٹمائز کریں" - "گروپ منتخب کریں" - "گروپ غیر منتخب کریں" - "گروپ بلحاظ" - "چینل ماخذ" - "HD/SD" - "HD" - "SD" - "گروپ بلحاظ" - "یہ پروگرام مسدود ہے" - "اس پروگرام کی درجہ بندی %1$s کی گئی ہے" - "ان پٹ آٹو اسکین کو سپورٹ نہیں کرتا" - "\'%s\' کیلئے آٹو اسکین شروع کرنے سے قاصر ہے" - "سب ٹائٹلز کیلئے سسٹم وار ترجیحات شروع کرنے سے قاصر ہے۔" - - ‏%1$d چینل شامل ہو گئے - ‏%1$d چینل شامل ہو گیا - - "کوئی چینلز شامل نہیں کیے گئے" - "ٹیونر" - "پیرنٹل کنٹرولز" - "آن" - "آف" - "چینلز مسدود کر دیے" - "سبھی کو مسدود کریں" - "سبھی غیر مسدود کریں" - "مخفی چینلز" - "پروگرام پابندیاں" - "‏PIN تبدیل کریں" - "درجہ بندی کے سسٹمز" - "درجہ بندیاں" - "درجہ بندی کے سبھی سسٹمز دیکھیں" - "دیگر ممالک" - "کوئی نہیں" - "کوئی نہیں" - "کوئی نہیں" - "زیادہ پابندیاں" - "متوسط پابندیاں" - "کم پابندیاں" - "حسب ضرورت" - "بچوں کیلئے مناسب مواد" - "بڑی عمر کے بچوں کیلئے مناسب مواد" - "نوعمروں کیلئے مناسب مواد" - "دستی پابندیاں" - - - "‏%1$s اور ذیلی درجہ بندیاں" - "ذیلی درجہ بندیاں" - "‏یہ چینل دیکھنے کیلئے اپنا PIN درج کریں" - "‏یہ پروگرام دیکھنے کیلئے اپنا PIN درج کریں" - "‏اس پروگرام کی درجہ بندی %1$s ہوئی ہے۔ اس پروگرام کو دیکھنے کیلئے اپنی PIN درج کریں" - "‏اپنا PIN درج کریں" - "‏پیرنٹل کنٹرولز سیٹ کرنے کیلئے، ایک PIN بنائیں" - "‏نیا PIN درج کریں" - "‏اپنے PIN کی توثیق کریں" - "‏اپنا موجودہ PIN درج کریں" - - ‏آپ نے 5 بار غلط PIN درج کی۔\n%1$d سیکنڈز بعد دوبارہ کوشش کریں۔ - ‏آپ نے 5 بار غلط PIN درج کی۔\n%1$d سیکنڈ بعد دوبارہ کوشش کریں۔ - - "‏وہ PIN غلط تھا۔ دوبارہ کوشش کریں۔" - "‏دوبارہ کوشش کریں، PIN مماثل نہیں ہے" - "ترتیبات" - "چینل فہرست حسب ضرورت بنائیں" - "اپنی پروگرام گائیڈ کیلئے چینلز منتخب کریں" - "چینل کے مآخذ" - "نئے چینلز دستیاب ہیں" - "پیرنٹل کنٹرولز" - "اوپن سورس لائسنسز" - "اوپن سورس لائسنسز" - "ورژن" - "‏یہ چینل دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" - "‏یہ پروگرام دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" - "‏اس پروگرام کی درجہ بندی %1$s کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں" - "‏اس چینل کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" - "‏اس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" - "‏اس پروگرام کی درجہ بندی %1$s کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔" - "پروگرام مسدود ہے" - "اس پروگرام کی درجہ بندی %1$s کی گئی ہے" - "صرف آڈیو" - "کمزور سگنل" - "کوئی انٹرنیٹ کنکشن نہیں ہے" - - یہ چینل %1$s تک نہیں چلایا جا سکتا کیونکہ دوسرے چینلز کو ریکارڈ کیا جا رہا ہے۔ \n\nریکارڈنگ کا شیڈول ایڈجسٹ کرنے کیلئے دائیں طرف دبائیں۔ - یہ چینل %1$s تک نہیں چلایا جا سکتا کیونکہ ایک اور چینل کو ریکارڈ کیا جا رہا ہے۔ \n\nریکارڈنگ کا شیڈول ایڈجسٹ کرنے کیلئے دائیں طرف دبائیں۔ - - "کوئی عنوان نہیں ہے" - "چینل مسدود کر دیا گیا" - "نئے" - "مآخذ" - - ‏%1$d چینلز - ‏%1$d چینل - - "کوئی چینلز دستیاب نہیں ہیں" - "نئے" - "ترتیب نہیں دیا گیا" - "مزید مآخذ حاصل کریں" - "لائیو چینلز کی پیشکش کرنے والی ایپس براؤز کریں" - "نئے چینل ماخذین دستیاب ہیں" - "نئے چینل ماخذین کے پاس پیشکش کیلئے چینل ہیں۔\nانہیں ابھی سیٹ اپ کریں یا چینل ماخذ ترتیب میں بعد میں کریں۔" - "ابھی سیٹ اپ کریں" - "ٹھیک ہے، سمجھ آ گئی" - - - "‏TV مینو تک رسائی حاصل کرنے کیلئے ""منتخب کریں کو دبائیں""۔" - "‏کوئی TV ان پٹ نہیں ملا" - "‏TV ان پٹ تلاش نہیں کیا جا سکتا" - "‏PIP تعاون یافتہ نہیں ہے" - "‏ایسا کوئی ان پٹ دستیاب نہیں ہے جسے PIP کے ساتھ دکھایا جا سکے" - "‏ٹیونر کی قسم مناسب نہیں ہے۔ براہ کرم ٹیونر کی قسم والے TV ان پٹ کیلئے لائیو چینلز ایپ شروع کریں۔" - "ٹیون کرنا ناکام ہوگیا" - "اس کارروائی کو نمٹانے کیلئے کوئی ایپ نہیں ملی۔" - "سبھی ماخذ چینلز مخفی ہیں۔ \nدیکھنے کیلئے کم از کم ایک چینل منتخب کریں۔" - "ویڈیو غیر متوقع طور پر دستیاب نہیں ہے" - "‏BACK کلید منسلک آلہ کیلئے ہے۔ باہر نکلنے کیلئے HOME بٹن دبائیں۔" - "‏لائیو چینلز کو TV فہرستیں پڑھنے کیلئے اجازت کی ضرورت ہے۔" - "اپنے ذرائع سیٹ اپ کریں" - "‏لائیو چینلز روایتی TV چینلز کے تجربے کو ایپس کی جانب سے فراہم کردہ اسٹریمنگ چینلز سے ملاتے ہیں۔ \n\nپہلے سے انسٹال شدہ چینل ماخذین کو سیٹ اپ کر کے شروع کریں۔ یا لائیو چینلز کی پیشکش کرنے والی مزید ایپس کیلئے Google Play اسٹور براؤز کریں۔" - "ریکارڈنگز اور شیڈولز" - "10 منٹ" - "30 منٹ" - "1 گھنٹہ" - "3 گھنٹے" - "حالیہ" - "شیڈول کردہ" - "سلسلہ" - "دیگر" - "چینل ریکارڈ نہیں ہو سکتا۔" - "پروگرام ریکارڈ نہیں ہو سکتا۔" - "%1$s کی ریکارڈنگ کیلئے شیڈول بن گیا ہے" - "اب سے لے کر %2$s تک %1$s ریکارڈ ہو رہا ہے" - "پورا شیڈول" - - ‏اگلے ‎%1$d دن - ‏اگلا ‎%1$d دن - - - ‏%1$d منٹس - ‏%1$d منٹ - - - ‏%1$d نئی ریکارڈنگز - ‏%1$d نئی ریکارڈنگ - - - ‏%1$d ریکارڈنگز - ‏%1$d ریکارڈنگ - - - ‏%1$d ریکارڈنگز کا شیڈول بن گیا - ‏%1$d ریکارڈنگ کا شیڈول بن گيا - - "دیکھیں" - "شروع سے چلائیں" - "چلانا دوبارہ شروع کریں" - "حذف کریں" - "ریکارڈنگز حذف کریں" - "دوبارہ شروع کریں" - "سیزن %1$s" - "شیڈول دیکھیں" - "مزید پڑھیں" - "ریکارڈنگز حذف کریں" - "وہ ایپی سوڈز منتخب کریں جو آپ حذف کرنا چاہتے ہیں۔ ایک بار حذف ہونے کے بعد ان کو بازیاب نہیں کیا جا سکتا۔" - "حذف کرنے کیلئے کوئی ریکارڈنگ نہیں ہے۔" - "دیکھی گئی ایپی سوڈز منتخب کریں" - "تمام ایپی سوڈز منتخب کریں" - "تمام ایپی سوڈز غیر منتخب کریں" - "%2$d میں سے %1$d منٹ کی دیکھ لیں" - "%2$d میں سے %1$d سیکنڈ کی دیکھ لیں" - "کبھی نہیں دیکھیں" - - ‏%2$d میں سے ‎%1$d ایپی سوڈز حذف ہو گئیں - ‏%2$d میں سے ‎%1$d ایپی سوڈ حذف ہو گئی - - "ترجیح" - "سب سے اعلی" - "سب سے کم" - "نہیں۔ %1$d" - "چینلز" - "کوئی بھی" - "ترجیح کا انتخاب کریں" - "جب ایک وقت میں بہت سارے پروگرامز ریکارڈ کرنے ہوں تو صرف اعلی ترجیح والے پروگرامز ریکارڈ کیے جائیں گے۔" - "محفوظ کریں" - "یک وقتی ریکارڈنگز کو سب سے اعلی ترجیح حاصل ہوتی ہے" - "منسوخ کریں" - "منسوخ کریں" - "بھول جائیں" - "روکیں" - "ریکارڈنگ کا شیڈول ملاحظہ کریں" - "یہ واحد پروگرام" - "ابھی - %1$s" - "پوری سیریز…" - "کسی بھی طرح شیڈول بنائیں" - "اس کی بجائے یہ ریکارڈ کریں" - "اس ریکارڈنگ کو منسوخ کریں" - "ابھی دیکھیں" - "قابل ریکارڈ" - "ریکارڈنگ کا شیڈول" - "ریکارڈنگ شیڈول میں تصادم" - "ریکارڈ ہو رہا ہے" - "ریکارڈنگ ناکام ہو گئی" - "ریکارڈنگ کے شیڈولز بنانے کیلئے پروگرام پڑھے جا رہے ہیں" - "پروگرامز پڑھے جا رہے ہیں" - - - "‏DVR کو مزید اسٹوریج درکار ہے" - "‏آپ DVR کے ساتھ پروگرامز ریکارڈ کر سکیں گے۔ تاہم آپ کے آلہ پر DVR کے کام کرنے کیلئے ابھی کافی اسٹوریج نہیں ہے۔ براہ کرم ایک بیرونی ڈرائیو منسلک کریں جو GB %1$s یا اس سے بڑی ہو اور اسے آلہ کی اسٹوریج کے بطور فارمیٹ کرنے کیلئے مراحل کی پیروی کریں۔" - "اسٹوریج غائب ہے" - "‏DVR کی استعمال کردہ کچھ اسٹوریج غائب ہے۔ براہ کرم وہ بیرونی ڈرائیو جو آپ نے پہلے DVR کو دوبارہ فعال کرنے کیلئے استعمال کی تھی، منسلک کریں۔ متبادل طور پر، اگر یہ مزید دستیاب نہ ہو تو آپ اسٹوریج کو بھولنے کا انتخاب کر سکتے ہیں۔" - "اسٹوریج کو بھول جائیں؟" - "آپ کا تمام ریکارڈ کردہ مواد اور شیڈولز ضائع ہو جائیں گے۔" - "ریکارڈنگ روکیں؟" - "ریکارڈ شدہ مواد محفوظ ہو جائے گا۔" - - - "ریکارڈنگ کا شیڈول لیکن تضادات" - "ریکارڈنگ شروع ہو گئی ہے لیکن اس میں تضادات ہیں" - "%1$s ریکارڈ ہوجائے گا۔" - "%1$s ریکارڈ ہو رہا ہے۔" - "%1$s کے کچھ حصے ریکارڈ نہیں ہوں گے۔" - "%1$s اور %2$s کے کچھ حصے ریکارڈ نہیں ہوں گے۔" - "%1$s، %2$s کے کچھ حصے اور ایک اور شیڈول ریکارڈ نہیں ہوگا۔" - - %1$s، %2$s کے کچھ حصے اور ‎%3$d مزید شیڈولز ریکارڈ نہیں ہوں گے۔ - %1$s، %2$s کے کچھ حصے اور ‎%3$d مزید شیڈول ریکارڈ نہیں ہوگا۔ - - "آپ کیا ریکارڈ کرنا چاہیں گے؟" - "آپ کتنی دیر تک ریکارڈ کرنا پسند کریں گے؟" - "پہلے سے شیڈول کردہ" - "اسی پروگرام کا پہلے ہی %1$s بجے ریکارڈ ہونے کیلئے شیڈول بنا ہوا ہے۔" - "پہلے سے ریکارڈ شدہ" - "‏یہ پروگرام پہلے سے ہی ریکارڈ شدہ ہے۔ یہ DVR لائبریری میں دستیاب ہے۔" - - - - - - - - - "ریکارڈ شدہ پروگرام نہیں ملا۔" - "متعلقہ ریکارڈنگز" - "(پروگرام کی کوئی تفصیل نہیں)" - - ‏%1$d ریکارڈنگز - ‏%1$d ریکارڈنگ - - " / " - "%1$s کو ریکارڈنگ شیڈول سے ہٹا دیا گیا" - "ٹیونر تضادات کی وجہ سے جزوی طور پر ریکارڈ ہوگا۔" - "ٹیونر تضادات کی وجہ سے ریکارڈ نہیں ہوگا۔" - "ابھی شیڈول میں کوئی ریکارڈنگز نہیں ہیں۔\nآپ پروگرام گائیڈ سے ریکارڈنگ کا شیڈول بنا سکتے ہیں۔" - - ‏%1$d ریکارڈنگ تنازعات - ‏%1$d ریکارڈنگ تنازعہ - - "سیریز کی ترتیبات" - "سیریز کی ریکارڈنگ شروع کریں" - "سیریز کی ریکارڈنگ روکیں" - "سیریز ریکارڈنگ روکیں؟" - "‏ریکارڈ شدہ ایپی سوڈز DVR لائبریری میں دستیاب رہیں گے۔" - "روکیں" - "کوئی ایپی سوڈز دستیاب نہیں۔\nایک بار دستیاب ہوجائے تو ان کو ریکارڈ کیا جائے گا۔" - - ‏(%1$d منٹ) - ‏(%1$d منٹ) - - "ﺁﺝ" - "آئندہ کل" - "گزشتہ کل" - "%1$s آج" - "%1$s آئندہ کل" - "اسکور" - diff --git a/res/values-uz-rUZ-v23/strings.xml b/res/values-uz-rUZ-v23/strings.xml new file mode 100644 index 00000000..9d1c1de7 --- /dev/null +++ b/res/values-uz-rUZ-v23/strings.xml @@ -0,0 +1,21 @@ + + + + + "Kanallar" + diff --git a/res/values-uz-rUZ/arrays.xml b/res/values-uz-rUZ/arrays.xml new file mode 100644 index 00000000..71619002 --- /dev/null +++ b/res/values-uz-rUZ/arrays.xml @@ -0,0 +1,57 @@ + + + + + + "Normal" + "To‘liq" + "Miqyos" + + + "Barcha kanallar" + "Oila va bolalar" + "Sport" + "Xarid" + "Filmlar" + "Komediya" + "Sayohat" + "Drama" + "Ta’lim" + "Tabiat va jonivorlar" + "Yangiliklar" + "O‘yinlar" + "San’at" + "Ko‘ngilochar" + "Turmush tarzi" + "Musiqa" + "Premyera" + "Fan va texnika" + + + "Jonli efir" + "Kontent bilan tanishishning sodda yo‘li" + "Qo‘chimcha kanallarni ko‘rish uchun ilovalar yuklab olish" + "Kanallar tartibini sozlang" + + + "Televizorda kanallarni ko‘rgandek, ilovalaringizda ham kontentni shunday tomosha qiling." + "Televizordagi kanallardek, ilovalaringizdagi kontentlarni \no‘zinga tanish teledasturlar va qulay interfeys yordamida ko‘ring." + "Jonli efirlarni taklif qiluvchi qo‘shimcha kanallarni qo‘shish uchun ilovalar o‘rnating. \nTV menyusidagi havolalar yordamida Google Play Marketda mos ilovalarni toping." + "Kanallar ro‘yxatini moslashtirish uchun yangi o‘rnatilgan manbalarni sozlang. \nBoshlash uchun “Sozlamalar” menyusida “Kanal manbalari” bandini tanlang." + + diff --git a/res/values-uz-rUZ/rating_system_strings.xml b/res/values-uz-rUZ/rating_system_strings.xml new file mode 100644 index 00000000..91d82748 --- /dev/null +++ b/res/values-uz-rUZ/rating_system_strings.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "15 yoshdan kichik foydalanuvchilarga dasturlarni ko‘rishi uchun ota-onasining ruxsat zarur." + "19 yoshdan kichik foydalanuvchilarga dasturlarni ko‘rishi uchun ota-onasining ruxsat zarur." + "Behayo gaplar" + "Qo‘pol nutq" + "Shahvoniy tasvirlar" + "Zo‘ravonlik" + "Fantastik qahramonlarning zo‘ravonligi" + "Ushbu dastur barcha bolalar uchun mo‘ljallangan." + "Ushbu dastur 7 va unda katta yoshdagi bolalar uchun mo‘ljallangan." + "Ko‘pchilik ota-onalar ushbu datsurni barcha yoshdagilar uchun ma’qul ko‘rishadi." + "Ushbu dasturda ota-onalar yosh bolalari uchun nomunosib deb topishi mumkin bo‘lgan materiallar bor. Yosh bolalarga ota-onasi bilan birga ko‘rish tavsiya etiladi." + "Ushbu dasturda 14 yoshga to‘lmagan bolalar uchun to‘g‘ri kelmaydigan tasvirlar bo‘lishi mumkin." + "Ushbu dastur voyaga yetganlar uchun mo‘ljallangan bo‘lib, 17 yoshga to‘lmaganlar uchun tomosha qilish tavsiya etilmaydi." + "Film reytinglari" + "Keng auditoriya uchun. Istalgan yoshdagi bolalarga tomosha qilish uchun ruxsat beriladi." + "Ota-ona nazorati tavsiya etiladi. Ayrim sahnalar yosh bolalar uchun mos bo‘lmasligi mumkin." + "Ota-onalar qattiq ogohlantiriladi. Ayrim materaillar o‘smir yoshidan kichik bolalarga mos kelmasligi mumkin." + "Ko‘rish cheklangan, ayrim materiallar katta yoshdagilar uchun mo‘ljallangan. Ota-onalarga farzandlarini o‘zi bilan birga tomoshaga olib borishdan oldin film haqida qo‘shimcha ma’lumot olish qat’iy tavsiya etiladi." + "17 yoki undan kichik yoshdagilarga ruxsat berilmagan. Faqat kattalar uchun. Bolalarga ruxsat berilmaydi." + diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml new file mode 100644 index 00000000..ff37421d --- /dev/null +++ b/res/values-uz-rUZ/strings.xml @@ -0,0 +1,357 @@ + + + + + "mono" + "stereo" + "Boshqaruv" + "Kanallar" + "TV sozlamalari" + "Ushbu kanal uchun ijro etish boshqaruvlari mavjud emas" + "Ijro yoki pauza" + "Oldinga tezkor o‘tkazish" + "Orqaga o‘tkazish" + "Keyingisi" + "Avvalgi" + "Tele-yo‘lboshchi" + "Yangi kanallar mavjud" + "%1$s ilovasini ochish" + "Taglavhalar" + "Ekran rejimi" + "Kadr ustida kadr" + "Ko‘p kanalli" + "Boshqa kanallar" + "Sozlamalar" + "TV (antenna/kabel)" + "Dastur haqida ma’l. yo‘q" + "Ma’lumot yo‘q" + "Bloklangan kanal" + "Noma’lum til" + "Taglavhalar %1$d" + "Taglavhalar" + "O‘chiq" + "Formatlarni sozlash" + "Taglavhalar uchun tizim sozlamalari" + "Ekran rejimi" + "Ko‘p kanalli" + "mono" + "stereo" + "5.1 qamrovli" + "7.1 qamrovli" + "%d ta kanal" + "Ro‘yxatni sozlash" + "Guruhni tanlash" + "Guruhni o‘chirish" + "Guruhlash tartibi" + "Kanal manbasi" + "HD/SD" + "HD" + "SD" + "Guruhlash tartibi" + "Bu dastur bloklangan" + "Bu dastur tasniflanmagan" + "Bu dastur uchun yosh cheklovi: %1$s" + "Bu kirish avtomatik qidiruvni qo‘llab-quvvatlamaydi" + "“%s” uchun avtomatik qidiruvni boshlab bo‘lmadi" + "Taglavhalar uchun tizim sozlamalarini ochib bo‘lmadi." + + %1$d ta kanal qo‘shildi + %1$d ta kanal qo‘shildi + + "Kanal qo‘shilmadi" + "Ota-ona nazorati" + "Yoniq" + "O‘chiq" + "Kanallar bloklandi" + "Hammasini bloklash" + "Blokdan chiqarish" + "Berkitilgan kanallar" + "Yosh cheklovlari" + "PIN kodni o‘zgartirish" + "Reyting tizimlari" + "Reytinglar" + "Barcha reyting tizimlari" + "Boshqa mamlakatlar" + "Hech biri" + "Hech biri" + "Tasniflanmagan" + "Tasniflanmagan dasturlarni bloklash" + "Hech biri" + "Qat’iy cheklovlar" + "O‘rtacha cheklovlar" + "Yengil cheklovlar" + "Boshqa" + "Kontent bolalar uchun mos" + "Kontent o‘rta yoshdagi bolalar uchun mos" + "Kontent o‘smirlar uchun mos" + "Foydalanuvchi cheklovlari" + + + "%1$s: sub-reytinglar" + "Sub-reytinglar" + "Bu kanalni ko‘rish uchun PIN kodni kiriting" + "Bu dasturni ko‘rish uchun PIN kodni kiriting" + "Bu dastur uchun yosh cheklovi: %1$s. Bu dasturni ko‘rish uchun PIN kodni kiriting" + "Bu dastur tasniflanmagan. Mazkur dasturni tomosha qilish uchun PIN kodni kiriting" + "PIN kodni kiriting" + "Ota-ona nazoratini o‘rnatish uchun PIN-kod yarating" + "Yangi PIN kodni kiriting" + "PIN kodni kiriting" + "Joriy PIN kodni kiriting" + + Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n%1$d soniyadan keyin qayta urinib ko‘ring. + Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n%1$d soniyadan keyin qayta urinib ko‘ring. + + "PIN-kod noto‘g‘ri, qaytadan urining." + "Qaytadan urinib ko‘ring, PIN-kod noto‘g‘ri" + "Pochta indeksini kiriting." + "Jonli efir ilovasi TV kanallari uchun to‘liq teledasturlarni taqdim etish uchun pochta indeksidan foydalanadi." + "Pochta indeksini kiriting" + "Pochta indeksi xato" + "Sozlamalar" + "Ro‘yxatni sozlash" + "Teledastur uchun kanallarni tanlash" + "Kanal manbalari" + "Yangi kanallar topildi" + "Ota-ona nazorati" + "Timeshift" + "Tomosha qilayotganingizda yozib boring, bunda jonli dasturlarni pauzaga qo‘yishingiz yoki orgaga qaytarishingiz mumkin bo‘ladi.\nDiqqat! Bu funksiyasi yoqilganda, ichki xotira intensiv ishlatiladi. Natijada uning xizmat muddati qisqarishi mumkin." + "Ochiq kodli DT litsenziyalari" + "Fikr-mulohaza yuborish" + "Versiyasi" + "Bu kanalni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting" + "Bu dasturni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting" + "Bu dastur tasniflanmagan.\nMazkur dasturni tomosha qilish uchun o‘ngga strelkani bosing va PIN kodni kiriting" + "Bu dastur uchun yosh cheklovi: %1$s.\nUshbu dasturni ko‘rish uchun o‘ngga qaragan milni bosing va PIN-kodni kiriting." + "Bu kanallarni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." + "Bu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." + "Bu dastur tasniflanmagan.\nMazkur dasturni tomosha qilish uchun birlamchi Jonli efir ilovasidan foydalaning." + "Bu dastur uchun yosh cheklovi: %1$s.\nUshbu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." + "Dastur bloklangan" + "Bu dastur tasniflanmagan" + "Bu dastur uchun yosh cheklovi: %1$s" + "Faqat audio" + "Signal kuchsiz" + "Internet aloqasi yo‘q" + + Bu kanalni %1$s gacha yoqib bo‘lmaydi, chunki boshqa kanallar yozib olinmoqda. \n\nYozib olish jadvalini o‘zgartirish uchun o‘ng strelkani bosing. + Bu kanalni %1$s gacha yoqib bo‘lmaydi, chunki boshqa kanal yozib olinmoqda. \n\nYozib olish jadvalini o‘zgartirish uchun o‘ng strelkani bosing. + + "Nomsiz" + "Kanal bloklangan" + "Yangi" + "Manbalar" + + %1$d ta kanal + %1$d ta kanal + + "Hech qanday kanal mavjud emas" + "Yangi" + "Sozlanmagan" + "Ko‘proq kanal manbalarini olish" + "Jonli kanallarni taklif etuvchi ilovalarni ko‘rish" + "Yangi kanal manbalari mavjud" + "Yangi manbalar yangi kanallarni taqdim etadi.\nUlarni hozir yoki kanal manbalari sozlamalarida keyinroq sozlashingiz mumkin." + "Hozir sozlash" + "OK" + + + "TV menyusiga kirish uchun ""TANLASH tugmasini bosing""." + "Hech qanday TV-kirish topilmadi" + "TV-kirishni topib bo‘lmadi" + "Tyuner turi mos kelmaydi. Agar TV-kirishdan foydalanayotgan bo‘lsangiz, “Jonli efir” ilovasini ishga tushiring." + "Sozlashni amalga oshirib bo‘lmadi." + "Bu amalni bajara oladigan ilova topilmadi." + "Barcha kanallar berkitilgan.\nTomosha qilish uchun kamida bitta kanalni tanlang." + "Video kutilmaganda yo‘q bo‘lib qoldi." + "ORQAGA tugmasi ulangan qurilmani boshqaradi. Chiqish uchun BOSHI tugmasini bosing." + "Teledasturlarni o‘qish uchun ruxsat zarur." + "Manbalarni sozlash" + "Jonli efirlar ilovalar tomonidan taqdim etiladigan translatsiya qilinadigan kanallar bilan an’anaviy televizor kanallarini uyg‘unlashtiradi. \n\nOldin o‘rnatilgan kanal manbalarini sozlash bilan boshlashingiz mumkin. Yoki jonli efirlarni taklif etadigan boshqa ilovalarni Google Play Market orqali ko‘rib chiqishingiz mumkin." + "Yozuvlar va jadvallar" + "10 daqiqa" + "30 daqiqa" + "1 soat" + "3 soat" + "Yaqinda" + "Rejalashtirilgan" + "Seriallar" + "Boshqalar" + "Bu kanalni yozib bo‘lmaydi." + "Bu dasturni yozib bo‘lmaydi." + "%1$s dasturini yozib olish rejalashtirildi" + "%1$s dasturini yozib olish boshlandi (%2$s gacha yoziladi)" + "To‘liq jadval" + + Keyingi %1$d kun + Keyingi %1$d kun + + + %1$d daqiqa + %1$d daqiqa + + + %1$d ta yangi yozuv + %1$d ta yangi yozuv + + + %1$d ta yozuv + %1$d ta yozuv + + + %1$d ta yozuv rejalashtirilgan + %1$d ta yozuv rejalashtirilgan + + "Yozib olishni bekor qilish" + "Yozib olishni to‘xtatish" + "Tomosha qilish" + "Boshidan ijro etish" + "Ijroni davom ettirish" + "O‘chirish" + "Yozuvlarni o‘chirish" + "Davom ettirish" + "%1$s-fasl" + "Jadvalni ko‘rish" + "Batafsil" + "Yozuvlarni o‘chirish" + "O‘chirib tashlash kerak bo‘lgan qismlarni tanlang. O‘chirilgach, qayta tiklab bo‘lmaydi." + "O‘chirib tashlash uchun hech narsa yo‘q." + "Ko‘rilgan qismlarni belgilash" + "Barcha qismlarni belgilash" + "Hammasini bekor qilish" + "Ko‘rilgan: %1$d daqiqa (jami: %2$d daqiqa)" + "Ko‘rilgan: %1$d soniya (jami: %2$d soniya)" + "Ko‘rilmaganlar" + + %1$d ta qism o‘chirib tashlandi (jami: %2$d ta) + %1$d ta qism o‘chirib tashlandi (jami: %2$d ta) + + "Muhimlik darajasi" + "Eng yuqori" + "Eng past" + "# %1$d" + "Kanallar" + "Har qanday" + "Muhimlilik darajasini tanlash" + "Bir vaqtning o‘zida bir nechta dasturlar yozib olinishi kerak bo‘lsa, faqat muhimlilik darajasi yuqori dasturlargina yozib olinadi." + "Saqlash" + "Bir martalik yozuvlarning muhimlilik darajasi yuqori" + "To‘xtatish" + "Yozib olish jadvalini ko‘rish" + "Faqat bu dastur" + "hozir – %1$s" + "To‘liq serial…" + "Baribir yozib olinsin" + "Bu dasturni yozib olish" + "Bu yozib olishni bekor qilish" + "Tomosha qilish" + "Yozuvlarni o‘chirish…" + "Yozib olish mumkin" + "Yozib olish rejalashtirildi" + "Yozib olish taymerida ziddiyat" + "Yozib olish" + "Yozib olib bo‘lmadi" + "Dasturlar o‘qib chiqilmoqda" + "Oxirgi yozuvlarni ko‘rish" + "“%1$s” to‘liq yozib olinmadi." + "“%1$s” va “%2$s” to‘liq yozib olinmadi." + "“%1$s”, “%2$s” va “%3$s” to‘liq yozib olinmadi." + "Xotirada joy kamligi tufayli “%1$s” to‘liq yozib olinmadi." + "Xotirada joy kamligi tufayli “%1$s” va “%2$s” to‘liq yozib olinmadi." + "Xotirada joy kamligi tufayli “%1$s”, “%2$s” va “%3$s” to‘liq yozib olinmadi." + "DVR uchun ko‘proq joy kerak" + "Dasturlarni DVR orqali yozib olish mumkin. Lekin hozir DVR ishlashi uchun qurilmangizda yetarli joy qolmadi. %1$d GB va undan katta hajmli tashqi xotira qurilmasini ulang va qurilma xotirasi sifatida formatlash uchun ko‘rsatmalarga amal qiling." + "Xotirada joy yetarli emas" + "Joy yetarli bo‘lmagani uchun bu dasturni yozib bo‘lmaydi. Eski yozuvlarni bir nechtasini o‘chirib tashlab ko‘ring." + "Xotira mavjud emas" + "Yozib olish to‘xtatilsinmi?" + "Yozib olingan kontent saqlab qo‘yiladi." + "Bu dastur bilan ixtilof borligi uchun “%1$s” dasturini yozib olish to‘xtatiladi. Yozib olingan qismi saqlab olinadi." + "Yozib olish rejalashtirildi, lekin ixtiloflar bor" + "Yozib olish jadvalida ixtiloflar bor" + "“%1$s” dasturi yozib olinadi." + "“%1$s” kanalidagi video yozib olinmoqda." + "“%1$s” dasturining faqat bir qismi yozib olinadi." + "“%1$s” va “%2$s” dasturlarining faqat bir qismi yozib olinadi." + "“%1$s”, “%2$s” va yana bitta dasturning faqat bir qismi yozib olinadi." + + %1$s, %2$s va yana %3$d ta dasturning faqat bir qismi yozib olinadi. + %1$s, %2$s va yana %3$d ta dasturning faqat bir qismi yozib olinadi. + + "Nimalar yozib olinsin?" + "Qancha vaqt yozib olinsin?" + "Allaqachon rejalashtirilgan" + "Bu dasturni allaqachon %1$s da yozib olish rejalashtirilgan." + "Dastur allaqachon yozib olingan" + "Bu dastur allaqachon yozib olingan. U DVR kutubxonasida saqlanadi." + "Seriallarni yozib olish rejalashtirildi" + + %2$s uchun %1$d ta yozuv rejalashtirilgan. + %2$s uchun %1$d ta yozuv rejalashtirilgan. + + + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun ulardan %3$d tasi yozib olinmaydi. + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun u yozib olinmaydi. + + + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun bu serialning %3$d ta qismi va boshqa seriallar yozib olinmaydi. + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun bu serialning %3$d ta qismi va boshqa seriallar yozib olinmaydi. + + + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun boshqa seriallarning 1 ta qismi yozib olinmaydi. + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun boshqa seriallarning 1 ta qismi yozib olinmaydi. + + + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun boshqa seriallarning %3$d ta qismi yozib olinmaydi. + %2$s uchun %1$d ta yozuv rejalashtirilgan. Ixtiloflar borligi uchun boshqa seriallarning %3$d ta qismi yozib olinmaydi. + + "Yozib olingan dasturni topib bo‘lmadi." + "Aloqador yozuvlar" + + %1$d ta yozuv + %1$d ta yozuv + + " / " + "%1$s yozuvi jadvaldan olib tashlandi" + "Tyuner yo‘qligi sababli qisman yozib olinadi." + "Tyuner yo‘qligi sababli yozib olinmaydi." + "Jadval bo‘sh.\nTele-yo‘lboshlovchi orqali yozib olishni rejalashtirish mumkin." + + %1$d ta yozib olish bo‘yicha ziddiyat + %1$d ta yozib olish bo‘yicha ziddiyat + + "Qismlar sozlamalari" + "Yozib olishni boshlash" + "Yozib olishni to‘xtatish" + "Yozib olish to‘xtatilsinmi?" + "Yozib olingan qismlar DVR kutubxonasida saqlanadi." + "To‘xtatish" + "Hozir hech qaysi serial qismi efirga uzatilmayapti." + "Hali serial qismi chiqmagan.\nEfirga chiqishi bilan yozib olinadi." + + (%1$d daqiqa) + (%1$d daqiqa) + + "Bugun" + "Ertaga" + "Kecha" + "Bugun %1$s" + "Ertaga %1$s" + "Ball" + "Yozib olingan dasturlar" + diff --git a/res/values-uz-v23/strings.xml b/res/values-uz-v23/strings.xml deleted file mode 100644 index 9d1c1de7..00000000 --- a/res/values-uz-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - "Kanallar" - diff --git a/res/values-uz/arrays.xml b/res/values-uz/arrays.xml deleted file mode 100644 index 71619002..00000000 --- a/res/values-uz/arrays.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - "Normal" - "To‘liq" - "Miqyos" - - - "Barcha kanallar" - "Oila va bolalar" - "Sport" - "Xarid" - "Filmlar" - "Komediya" - "Sayohat" - "Drama" - "Ta’lim" - "Tabiat va jonivorlar" - "Yangiliklar" - "O‘yinlar" - "San’at" - "Ko‘ngilochar" - "Turmush tarzi" - "Musiqa" - "Premyera" - "Fan va texnika" - - - "Jonli efir" - "Kontent bilan tanishishning sodda yo‘li" - "Qo‘chimcha kanallarni ko‘rish uchun ilovalar yuklab olish" - "Kanallar tartibini sozlang" - - - "Televizorda kanallarni ko‘rgandek, ilovalaringizda ham kontentni shunday tomosha qiling." - "Televizordagi kanallardek, ilovalaringizdagi kontentlarni \no‘zinga tanish teledasturlar va qulay interfeys yordamida ko‘ring." - "Jonli efirlarni taklif qiluvchi qo‘shimcha kanallarni qo‘shish uchun ilovalar o‘rnating. \nTV menyusidagi havolalar yordamida Google Play Marketda mos ilovalarni toping." - "Kanallar ro‘yxatini moslashtirish uchun yangi o‘rnatilgan manbalarni sozlang. \nBoshlash uchun “Sozlamalar” menyusida “Kanal manbalari” bandini tanlang." - - diff --git a/res/values-uz/rating_system_strings.xml b/res/values-uz/rating_system_strings.xml deleted file mode 100644 index 91d82748..00000000 --- a/res/values-uz/rating_system_strings.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "15 yoshdan kichik foydalanuvchilarga dasturlarni ko‘rishi uchun ota-onasining ruxsat zarur." - "19 yoshdan kichik foydalanuvchilarga dasturlarni ko‘rishi uchun ota-onasining ruxsat zarur." - "Behayo gaplar" - "Qo‘pol nutq" - "Shahvoniy tasvirlar" - "Zo‘ravonlik" - "Fantastik qahramonlarning zo‘ravonligi" - "Ushbu dastur barcha bolalar uchun mo‘ljallangan." - "Ushbu dastur 7 va unda katta yoshdagi bolalar uchun mo‘ljallangan." - "Ko‘pchilik ota-onalar ushbu datsurni barcha yoshdagilar uchun ma’qul ko‘rishadi." - "Ushbu dasturda ota-onalar yosh bolalari uchun nomunosib deb topishi mumkin bo‘lgan materiallar bor. Yosh bolalarga ota-onasi bilan birga ko‘rish tavsiya etiladi." - "Ushbu dasturda 14 yoshga to‘lmagan bolalar uchun to‘g‘ri kelmaydigan tasvirlar bo‘lishi mumkin." - "Ushbu dastur voyaga yetganlar uchun mo‘ljallangan bo‘lib, 17 yoshga to‘lmaganlar uchun tomosha qilish tavsiya etilmaydi." - "Film reytinglari" - "Keng auditoriya uchun. Istalgan yoshdagi bolalarga tomosha qilish uchun ruxsat beriladi." - "Ota-ona nazorati tavsiya etiladi. Ayrim sahnalar yosh bolalar uchun mos bo‘lmasligi mumkin." - "Ota-onalar qattiq ogohlantiriladi. Ayrim materaillar o‘smir yoshidan kichik bolalarga mos kelmasligi mumkin." - "Ko‘rish cheklangan, ayrim materiallar katta yoshdagilar uchun mo‘ljallangan. Ota-onalarga farzandlarini o‘zi bilan birga tomoshaga olib borishdan oldin film haqida qo‘shimcha ma’lumot olish qat’iy tavsiya etiladi." - "17 yoki undan kichik yoshdagilarga ruxsat berilmagan. Faqat kattalar uchun. Bolalarga ruxsat berilmaydi." - diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml deleted file mode 100644 index 55a0324f..00000000 --- a/res/values-uz/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - "mono" - "stereo" - "Boshqaruv" - "So‘nggi kanallar" - "TV sozlamalari" - "PIP sozlamalari" - "Ushbu kanal uchun ijro etish boshqaruvlari mavjud emas" - "Ijro yoki pauza" - "Oldinga tezkor o‘tkazish" - "Orqaga o‘tkazish" - "Keyingisi" - "Avvalgi" - "Tele-yo‘lboshchi" - "Yangi kanallar mavjud" - "%1$s ilovasini ochish" - "Taglavhalar" - "Ekran rejimi" - "Kadr ustida kadr" - "Yoniq" - "O‘chiq" - "Ko‘p kanalli" - "Boshqa kanallar" - "Sozlamalar" - "Manba" - "Almashtirish" - "Yoniq" - "O‘chiq" - "Ovoz" - "Asosiy oyna" - "PIP oynasi" - "Joylashuvi" - "Pastda o‘ngda" - "Tepada o‘ngda" - "Tepada chapda" - "Pastda chapda" - "Yonma-yon" - "O‘lchami" - "Katta" - "Kichik" - "Kirish manbasi" - "TV (antenna/kabel)" - "Dastur haqida ma’l. yo‘q" - "Ma’lumot yo‘q" - "Bloklangan kanal" - "Noma’lum til" - "Taglavhalar" - "O‘chiq" - "Formatlarni sozlash" - "Taglavhalar uchun tizim sozlamalari" - "Ekran rejimi" - "Ko‘p kanalli" - "mono" - "stereo" - "5.1 qamrovli" - "7.1 qamrovli" - "%d ta kanal" - "Ro‘yxatni sozlash" - "Guruhni tanlash" - "Guruhni o‘chirish" - "Guruhlash tartibi" - "Kanal manbasi" - "HD/SD" - "HD" - "SD" - "Guruhlash tartibi" - "Bu dastur bloklangan" - "Bu dastur uchun yosh cheklovi: %1$s" - "Bu kirish avtomatik qidiruvni qo‘llab-quvvatlamaydi" - "“%s” uchun avtomatik qidiruvni boshlab bo‘lmadi" - "Taglavhalar uchun tizim sozlamalarini ochib bo‘lmadi." - - %1$d ta kanal qo‘shildi - %1$d ta kanal qo‘shildi - - "Kanal qo‘shilmadi" - "Tyuner" - "Ota-ona nazorati" - "Yoniq" - "O‘chiq" - "Kanallar bloklandi" - "Barchasini bloklash" - "Blokdan chiqarish" - "Berkitilgan kanallar" - "Yosh cheklovlari" - "PIN kodni o‘zgartirish" - "Reyting tizimlari" - "Reytinglar" - "Barcha reyting tizimlari" - "Boshqa mamlakatlar" - "Hech biri" - "Hech biri" - "Hech biri" - "Qat’iy cheklovlar" - "O‘rtacha cheklovlar" - "Yengil cheklovlar" - "Boshqa" - "Kontent bolalar uchun mos" - "Kontent o‘rta yoshdagi bolalar uchun mos" - "Kontent o‘smirlar uchun mos" - "Foydalanuvchi cheklovlari" - - - "%1$s: sub-reytinglar" - "Sub-reytinglar" - "Bu kanalni ko‘rish uchun PIN kodni kiriting" - "Bu dasturni ko‘rish uchun PIN kodni kiriting" - "Bu dastur uchun yosh cheklovi: %1$s. Bu dasturni ko‘rish uchun PIN kodni kiriting" - "PIN kodni kiriting" - "Ota-ona nazoratini o‘rnatish uchun PIN-kod yarating" - "Yangi PIN kodni kiriting" - "PIN kodni kiriting" - "Joriy PIN kodni kiriting" - - Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n%1$d soniyadan keyin qayta urinib ko‘ring. - Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n%1$d soniyadan keyin qayta urinib ko‘ring. - - "PIN-kod noto‘g‘ri, qaytadan urining." - "Qaytadan urinib ko‘ring, PIN-kod noto‘g‘ri" - "Sozlamalar" - "Ro‘yxatni sozlash" - "Teledastur uchun kanallarni tanlash" - "Kanal manbalari" - "Yangi kanallar topildi" - "Ota-ona nazorati" - "Ochiq kodli DT litsenziyalari" - "Ochiq kodli DT litsenziyalari" - "Versiyasi" - "Bu kanalni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting" - "Bu dasturni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting" - "Bu dastur uchun yosh cheklovi: %1$s.\nUshbu dasturni ko‘rish uchun o‘ngga qaragan milni bosing va PIN-kodni kiriting." - "Bu kanallarni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." - "Bu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." - "Bu dastur uchun yosh cheklovi: %1$s.\nUshbu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning." - "Dastur bloklangan" - "Bu dastur uchun yosh cheklovi: %1$s" - "Faqat audio" - "Signal kuchsiz" - "Internet aloqasi yo‘q" - - Bu kanalni %1$s gacha yoqib bo‘lmaydi, chunki boshqa kanallar yozib olinmoqda. \n\nYozib olish jadvalini o‘zgartirish uchun o‘ng strelkani bosing. - Bu kanalni %1$s gacha yoqib bo‘lmaydi, chunki boshqa kanal yozib olinmoqda. \n\nYozib olish jadvalini o‘zgartirish uchun o‘ng strelkani bosing. - - "Nomsiz" - "Kanal bloklangan" - "Yangi" - "Manbalar" - - %1$d ta kanal - %1$d ta kanal - - "Hech qanday kanal mavjud emas" - "Yangi" - "Sozlanmagan" - "Ko‘proq kanal manbalarini olish" - "Jonli kanallarni taklif etuvchi ilovalarni ko‘rish" - "Yangi kanal manbalari mavjud" - "Yangi manbalar yangi kanallarni taqdim etadi.\nUlarni hozir yoki kanal manbalari sozlamalarida keyinroq sozlashingiz mumkin." - "Hozir sozlash" - "OK" - - - "TV menyusiga kirish uchun ""TANLASH tugmasini bosing""." - "Hech qanday TV-kirish topilmadi" - "TV-kirishni topib bo‘lmadi" - "PIP qo‘llab-quvvatlanmaydi" - "PIP bilan ko‘rsatish uchun hech qanday manba yo‘q" - "Tyuner turi mos kelmaydi. Agar TV-kirishdan foydalanayotgan bo‘lsangiz, “Jonli efir” ilovasini ishga tushiring." - "Sozlashni amalga oshirib bo‘lmadi." - "Bu amalni bajara oladigan ilova topilmadi." - "Barcha kanallar berkitilgan.\nTomosha qilish uchun kamida bitta kanalni tanlang." - "Video kutilmaganda yo‘q bo‘lib qoldi." - "ORQAGA tugmasi ulangan qurilmani boshqaradi. Chiqish uchun BOSHI tugmasini bosing." - "Teledasturlarni o‘qish uchun ruxsat zarur." - "Manbalarni sozlash" - "Jonli efirlar ilovalar tomonidan taqdim etiladigan translatsiya qilinadigan kanallar bilan an’anaviy televizor kanallarini uyg‘unlashtiradi. \n\nOldin o‘rnatilgan kanal manbalarini sozlash bilan boshlashingiz mumkin. Yoki jonli efirlarni taklif etadigan boshqa ilovalarni Google Play Market orqali ko‘rib chiqishingiz mumkin." - "Yozuvlar va jadvallar" - "10 daqiqa" - "30 daqiqa" - "1 soat" - "3 soat" - "Yaqinda" - "Rejalashtirilgan" - "Seriallar" - "Boshqalar" - "Bu kanalni yozib bo‘lmaydi." - "Bu dasturni yozib bo‘lmaydi." - "%1$s dasturini yozib olish rejalashtirildi" - "%1$s dasturini yozib olish boshlandi (%2$s gacha yoziladi)" - "To‘liq jadval" - - Keyingi %1$d kun - Keyingi %1$d kun - - - %1$d daqiqa - %1$d daqiqa - - - %1$d ta yangi yozuv - %1$d ta yangi yozuv - - - %1$d ta yozuv - %1$d ta yozuv - - - %1$d ta yozuv rejalashtirilgan - %1$d ta yozuv rejalashtirilgan - - "Tomosha qilish" - "Boshidan ijro etish" - "Ijroni davom ettirish" - "O‘chirish" - "Yozuvlarni o‘chirish" - "Davom ettirish" - "%1$s-fasl" - "Jadvalni ko‘rish" - "Batafsil" - "Yozuvlarni o‘chirish" - "O‘chirib tashlash kerak bo‘lgan qismlarni tanlang. O‘chirilgach, qayta tiklab bo‘lmaydi." - "O‘chirib tashlash uchun hech narsa yo‘q." - "Ko‘rilgan qismlarni belgilash" - "Barcha qismlarni belgilash" - "Hammasini bekor qilish" - "Ko‘rilgan: %1$d daqiqa (jami: %2$d daqiqa)" - "Ko‘rilgan: %1$d soniya (jami: %2$d soniya)" - "Ko‘rilmaganlar" - - %1$d ta qism o‘chirib tashlandi (jami: %2$d ta) - %1$d ta qism o‘chirib tashlandi (jami: %2$d ta) - - "Muhimlik darajasi" - "Eng yuqori" - "Eng past" - "# %1$d" - "Kanallar" - "Har qanday" - "Muhimlilik darajasini tanlash" - "Bir vaqtning o‘zida bir nechta dasturlar yozib olinishi kerak bo‘lsa, faqat muhimlilik darajasi yuqori dasturlargina yozib olinadi." - "Saqlash" - "Bir martalik yozuvlarning muhimlilik darajasi yuqori" - "Bekor q-sh" - "Bekor qilish" - "O‘chirib tashlash" - "To‘xtatish" - "Yozib olish jadvalini ko‘rish" - "Faqat bu dastur" - "hozir – %1$s" - "To‘liq serial…" - "Baribir yozib olinsin" - "Bu dasturni yozib olish" - "Bu yozib olishni bekor qilish" - "Tomosha qilish" - "Yozib olish mumkin" - "Yozib olish rejalashtirildi" - "Yozib olish taymerida ziddiyat" - "Yozib olish" - "Yozib olib bo‘lmadi" - "Yozib olish jadvallarini yaratish uchun dasturlar o‘qib chiqilmoqda" - "Dasturlar o‘qib chiqilmoqda" - - - "DVR uchun ko‘proq joy kerak" - "Dasturlarni DVR orqali yozib olish mumkin. Lekin hozir DVR ishlashi uchun qurilmangizda yetarli joy qolmadi. %1$s GB va undan katta hajmli tashqi xotira qurilmasini ulang va qurilma xotirasi sifatida formatlash uchun ko‘rsatmalarga amal qiling." - "Xotira mavjud emas" - "Xotira topilmadi. Videomagnitofonni qayta yoqishdan oldin tashqi xotirani ulang yoki undan foydalanib bo‘lmasa, xotirani o‘chirib tashlang." - "Xotira o‘chirib tashlansinmi?" - "Barcha yozib olingan kontentlar va jadvallar o‘chib ketadi." - "Yozib olish to‘xtatilsinmi?" - "Yozib olingan kontent saqlab qo‘yiladi." - - - "Yozib olish rejalashtirildi, lekin ixtiloflar bor" - "Yozib olish jadvalida ixtiloflar bor" - "“%1$s” dasturi yozib olinadi." - "“%1$s” kanalidagi video yozib olinmoqda." - "“%1$s” dasturining faqat bir qismi yozib olinadi." - "“%1$s” va “%2$s” dasturlarining faqat bir qismi yozib olinadi." - "“%1$s”, “%2$s” va yana bitta dasturning faqat bir qismi yozib olinadi." - - %1$s, %2$s va yana %3$d ta dasturning faqat bir qismi yozib olinadi. - %1$s, %2$s va yana %3$d ta dasturning faqat bir qismi yozib olinadi. - - "Nimalar yozib olinsin?" - "Qancha vaqt yozib olinsin?" - "Allaqachon rejalashtirilgan" - "Bu dasturni allaqachon %1$s da yozib olish rejalashtirilgan." - "Dastur allaqachon yozib olingan" - "Bu dastur allaqachon yozib olingan. U DVR kutubxonasida saqlanadi." - - - - - - - - - "Yozib olingan dasturni topib bo‘lmadi." - "Aloqador yozuvlar" - "(Dastur haqida ma’lumot yo‘q)" - - %1$d ta yozuv - %1$d ta yozuv - - " / " - "%1$s yozuvi jadvaldan olib tashlandi" - "Tyuner yo‘qligi sababli qisman yozib olinadi." - "Tyuner yo‘qligi sababli yozib olinmaydi." - "Jadval bo‘sh.\nTele-yo‘lboshlovchi orqali yozib olishni rejalashtirish mumkin." - - %1$d ta yozib olish bo‘yicha ziddiyat - %1$d ta yozib olish bo‘yicha ziddiyat - - "Qismlar sozlamalari" - "Yozib olishni boshlash" - "Yozib olishni to‘xtatish" - "Yozib olish to‘xtatilsinmi?" - "Yozib olingan qismlar DVR kutubxonasida saqlanadi." - "To‘xtatish" - "Hali serial qismi chiqmagan.\nEfirga chiqishi bilan yozib olinadi." - - (%1$d daqiqa) - (%1$d daqiqa) - - "Bugun" - "Ertaga" - "Kecha" - "Bugun %1$s" - "Ertaga %1$s" - "Ball" - diff --git a/res/values-v23/strings.xml b/res/values-v23/strings.xml deleted file mode 100644 index 4809682a..00000000 --- a/res/values-v23/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Channels - diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index a8c4ab09..a1aae946 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -20,9 +20,8 @@ "đơn âm" "âm thanh nổi" "Điều khiển phát" - "Kênh gần đây" + "Kênh" "Tùy chọn TV" - "Tùy chọn PIP" "Điều khiển phát không có sẵn cho kênh này" "Phát hoặc tạm dừng" "Tua đi" @@ -35,33 +34,15 @@ "Phụ đề" "Chế độ hiển thị" "PIP" - "Bật" - "Tắt" "Âm thanh đa kênh" "Tải thêm kênh" "Cài đặt" - "Nguồn" - "Hoán đổi" - "Bật" - "Tắt" - "Âm thanh" - "Cửa sổ chính" - "Cửa sổ PIP" - "Bố cục" - "Phía dưới cùng bên phải" - "Phía trên cùng bên phải" - "Phía trên cùng bên trái" - "Phía dưới cùng bên trái" - "Cạnh nhau" - "Kích thước" - "Lớn" - "Nhỏ" - "Nguồn đầu vào" "TV (ăng-ten/cáp)" "Không có thông tin chương trình" "Không có thông tin" "Kênh bị chặn" - "Tiếng không xác định" + "Ngôn ngữ không xác định" + "Phụ đề chi tiết %1$d" "Phụ đề" "Tắt" "Tùy chỉnh định dạng" @@ -83,6 +64,7 @@ "SD" "Nhóm theo" "Chương trình này đã bị chặn" + "Chương trình này chưa được xếp hạng" "Chương trình này được xếp hạng %1$s" "Đầu vào không hỗ trợ tự động quét" "Không thể bắt đầu quét tự động cho \'%s\'" @@ -92,7 +74,6 @@ Đã thêm %1$d kênh "Chưa thêm kênh nào" - "Bộ dò" "Kiểm soát của cha mẹ" "Bật" "Tắt" @@ -108,6 +89,8 @@ "Các quốc gia khác" "Không có" "Không có" + "Chưa được xếp hạng" + "Chặn chương trình chưa có hạng" "Không có" "Hạn chế cao" "Hạn chế trung bình" @@ -124,6 +107,7 @@ "Nhập mã PIN của bạn để xem kênh này" "Nhập mã PIN của bạn để xem chương trình này" "Chương trình này được xếp hạng %1$s. Nhập mã PIN của bạn để xem chương trình này" + "Chương trình này chưa được xếp hạng. Hãy nhập mã PIN của bạn để xem chương trình này" "Nhập mã PIN của bạn" "Để đặt kiểm soát của phụ huynh, hãy tạo mã PIN" "Nhập mã PIN mới" @@ -135,22 +119,31 @@ "Mã PIN đó không đúng. Hãy thử lại." "Hãy thử lại, mã PIN không khớp" + "Nhập mã ZIP của bạn." + "Ứng dụng Kênh trực tiếp sẽ sử dụng mã ZIP để cung cấp hướng dẫn chương trình đầy đủ cho các kênh truyền hình." + "Nhập mã ZIP của bạn" + "Mã ZIP không hợp lệ" "Cài đặt" "Tùy chỉnh danh sách kênh" "Chọn kênh cho hướng dẫn chương trình của bạn" "Nguồn kênh" "Đã có kênh mới" "Kiểm soát của phụ huynh" + "Chuyển dịch thời gian" + "Ghi lại trong khi xem để bạn có thể tạm dừng hoặc tua lại các chương trình trực tiếp.\nCảnh báo: Điều này có thể làm giảm tuổi thọ của bộ nhớ trong bằng cách sử dụng nhiều bộ nhớ." "Giấy phép nguồn mở" - "Giấy phép nguồn mở" + "Gửi phản hồi" "Phiên bản" "Để xem kênh này, hãy nhấn vào Quyền và nhập mã PIN của bạn" "Để xem chương trình này, hãy nhấn vào Quyền và nhập mã PIN của bạn" + "Chương trình này chưa được xếp hạng.\nĐể xem chương trình này, hãy nhấn vào phím Phải và nhập mã PIN của bạn" "Chương trình này được xếp hạng %1$s.\nĐể xem chương trình này, hãy nhấn vào Quyền và nhập mã PIN của bạn." "Để xem kênh này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định." "Để xem chương trình này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định." + "Chương trình này chưa được xếp hạng.\nĐể xem chương trình này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định." "Chương trình này được xếp hạng %1$s.\nĐể xem chương trình này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định." "Chương trình bị chặn" + "Chương trình này chưa được xếp hạng" "Chương trình này được xếp hạng %1$s" "Chỉ âm thanh" "Tín hiệu yếu" @@ -181,8 +174,6 @@ "Nhấn CHỌN"" để truy cập menu TV." "Không tìm thấy đầu vào TV nào" "Không tìm thấy đầu vào TV" - "PIP không được hỗ trợ" - "Không có đầu vào có thể hiển thị với PIP" "Loại bộ dò ko phù hợp. Hãy chạy ứng dụng Kênh trực tiếp cho đầu vào TV loại bộ dò." "Không dò được" "Không tìm thấy ứng dụng nào để xử lý tác vụ này." @@ -226,6 +217,8 @@ %1$d bản ghi được lên lịch %1$d bản ghi được lên lịch + "Hủy ghi" + "Dừng ghi" "Xem" "Phát từ đầu" "Tiếp tục phát" @@ -258,9 +251,6 @@ "Khi có quá nhiều chương trình được ghi cùng lúc, chỉ có những chương trình với mức ưu tiên cao hơn sẽ được ghi." "Lưu" "Ghi một lần có mức độ ưu tiên cao nhất" - "Hủy" - "Hủy" - "Quên" "Dừng" "Xem lịch ghi" "Chương trình duy nhất này" @@ -270,25 +260,28 @@ "Ghi chương trình này để thay thế" "Hủy lịch ghi này" "Xem ngay bây giờ" + "Xóa bản ghi…" "Có thể ghi được" "Đã lên lịch ghi" "Lịch ghi xung đột" "Đang ghi" "Ghi không thành công" - "Đang đọc các chương trình để tạo lịch ghi" - "Đang đọc chương trình" - - + "Đang đọc chương trình" + "Xem bản ghi gần đây" + "Quá trình ghi %1$s không hoàn thành." + "Quá trình ghi %1$s%2$s không hoàn thành." + "Quá trình ghi %1$s, %2$s%3$s không hoàn thành." + "Quá trình ghi %1$s đã không hoàn thành do không đủ bộ nhớ." + "Quá trình ghi %1$s%2$s đã không hoàn thành do không đủ bộ nhớ." + "Quá trình ghi %1$s, %2$s%3$s đã không hoàn thành do không đủ bộ nhớ." "DVR cần thêm bộ nhớ" - "Bạn sẽ có thể ghi các chương trình với DVR. Tuy nhiên không có đủ bộ nhớ trên thiết bị của bạn bây giờ để DVR hoạt động. Vui lòng kết nối ổ đĩa ngoài %1$sGB hoặc lớn hơn và làm theo các bước để định dạng ổ đĩa ngoài làm thiết bị lưu trữ." + "Bạn sẽ có thể ghi các chương trình với DVR. Tuy nhiên không có đủ bộ nhớ trên thiết bị của bạn bây giờ để DVR hoạt động. Vui lòng kết nối ổ đĩa ngoài %1$dGB hoặc lớn hơn và làm theo các bước để định dạng ổ đĩa ngoài làm thiết bị lưu trữ." + "Không đủ bộ nhớ" + "Chương trình này sẽ không được ghi vì không có đủ bộ nhớ. Hãy thử xóa một số bản ghi hiện có." "Thiếu bộ nhớ" - "Một số bộ nhớ được DVR sử dụng bị thiếu. Vui lòng kết nối các ổ đĩa ngoài bạn đã sử dụng trước đó để bật lại DVR. Ngoài ra bạn còn có thể chọn để quên bộ nhớ nếu bộ nhớ không còn nữa." - "Quên bộ nhớ?" - "Tất cả nội dung đã ghi và lịch ghi của bạn sẽ bị mất." "Dừng ghi?" "Nội dung đã ghi sẽ được lưu." - - + "Quá trình ghi %1$s sẽ bị dừng lại vì mâu thuẫn với chương trình này. Nội dung đã ghi sẽ được lưu." "Đã lên lịch ghi nhưng có xung đột" "Đã bắt đầu ghi nhưng có xung đột" "%1$s sẽ được ghi." @@ -306,17 +299,29 @@ "Chương trình tương tự đã được lên lịch để ghi lúc %1$s." "Đã được ghi" "Chương trình này đã được ghi. Chương trình có sẵn trong thư viện DVR." - - - - - - - - + "Đã lên lịch ghi loạt phim" + + %1$d bản ghi đã được lên lịch cho %2$s. + %1$d bản ghi đã được lên lịch cho %2$s. + + + %1$d bản ghi đã được lên lịch cho %2$s. %3$d trong số chúng sẽ không được ghi do xung đột. + %1$d bản ghi đã được lên lịch cho %2$s. Bản ghi sẽ không được ghi do xung đột. + + + %1$d bản ghi đã được lên lịch cho %2$s. %3$d tập của loạt phim này và các loạt phim khác sẽ không được ghi do xung đột. + %1$d bản ghi đã được lên lịch cho %2$s. %3$d tập của loạt phim này và các loạt phim khác sẽ không được ghi do xung đột. + + + %1$d bản ghi đã được lên lịch cho %2$s. 1 tập của loạt phim khác sẽ không được ghi do xung đột. + %1$d bản ghi đã được lên lịch cho %2$s. 1 tập của loạt phim khác sẽ không được ghi do xung đột. + + + %1$d bản ghi đã được lên lịch cho %2$s. %3$d tập của loạt phim khác sẽ không được ghi do xung đột. + %1$d bản ghi đã được lên lịch cho %2$s. %3$d tập của loạt phim khác sẽ không được ghi do xung đột. + "Không tìm thấy chương trình nào được ghi." "Bản ghi liên quan" - "(Không có mô tả chương trình)" %1$d bản ghi %1$d bản ghi @@ -336,6 +341,7 @@ "Dừng ghi loạt phim?" "Các tập đã ghi sẽ vẫn có sẵn trong thư viện DVR." "Dừng" + "Không có tập nào đang chiếu bây giờ." "Không có sẵn tập nào.\nChúng sẽ được ghi khi có sẵn." (%1$d phút) @@ -347,4 +353,5 @@ "%1$s hôm nay" "%1$s ngày mai" "Điểm" + "Chương trình đã ghi" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index e3d8ea08..2aa3e2cb 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -20,9 +20,8 @@ "单声道" "立体声" "播放控件" - "最近观看的频道" + "频道" "电视选项" - "PIP 选项" "此频道无法使用播放控件" "播放或暂停" "快进" @@ -34,34 +33,16 @@ "打开%1$s" "字幕" "显示模式" - "PIP" - "开启" - "关闭" + "画中画" "多音频" "获取更多频道" "设置" - "来源" - "切换" - "开启" - "关闭" - "声音" - "主窗口" - "PIP 窗口" - "布局" - "右下方" - "右上方" - "左上方" - "左下方" - "并排" - "尺寸" - "大" - "小" - "输入源" "电视(天线/有线)" "没有节目信息" "无信息" "已屏蔽的频道" - "未知语言" + "未知语言" + "字幕(%1$d)" "字幕" "关闭" "自定义格式" @@ -83,6 +64,7 @@ "标清" "分组依据" "此节目已被屏蔽" + "此节目未分级" "此节目的分级为:%1$s" "该输入设备不支持自动扫描" "无法为“%s”启动自动扫描" @@ -92,7 +74,6 @@ 已添加 %1$d 个频道 "未添加任何频道" - "调谐器" "家长控制" "开启" "关闭" @@ -101,13 +82,15 @@ "全部取消屏蔽" "隐藏的频道" "节目限制" - "更改PIN码" + "更改 PIN 码" "分级系统" "分级" "查看所有分级系统" "其他国家/地区" "无" "无" + "未分级" + "屏蔽未分级的节目" "无" "严格限制" "中等限制" @@ -121,36 +104,46 @@ "%1$s和二级分级" "二级分级" - "输入PIN码即可观看此频道" - "输入PIN码即可观看此节目" + "输入 PIN 码即可观看此频道" + "输入 PIN 码即可观看此节目" "该节目的分级是“%1$s”。要观看此节目,请输入您的 PIN 码" - "输入您的PIN码" - "要设置家长控制功能,请创建一个PIN码" - "请输入新的PIN码" - "确认您的PIN码" - "输入当前的PIN码" + "此节目未分级。要观看此节目,请输入您的 PIN 码" + "输入您的 PIN 码" + "要设置家长控制功能,请创建一个 PIN 码" + "请输入新的 PIN 码" + "确认您的 PIN 码" + "输入当前的 PIN 码" 您已连续 5 次输错 PIN 码。\n请在 %1$d 秒后重试。 您已连续 5 次输错 PIN 码。\n请在 %1$d 秒后重试。 - "PIN码不正确,请重试。" - "PIN码不匹配,请重试" + "PIN 码不正确,请重试。" + "PIN 码不匹配,请重试" + "请输入您的邮政编码。" + "直播频道应用将使用邮政编码提供完整的电视频道收视指南。" + "请输入您的邮政编码" + "邮政编码无效" "设置" "自定义频道列表" "为您的收视指南选择频道" "频道来源" "有新频道" "家长控制" + "时移" + "一边观看一边录制,这样您就能够让直播节目暂停或快退。\n警告:此功能会密集占用存储空间,因此可能会缩短内部存储空间的使用寿命。" "开放源代码许可" - "开放源代码许可" + "发送反馈" "版本" - "要观看此频道,请按“向右”按钮,然后输入您的PIN码" - "要观看此节目,请按“向右”按钮,然后输入您的PIN码" + "要观看此频道,请按“向右”按钮,然后输入您的 PIN 码" + "要观看此节目,请按“向右”按钮,然后输入您的 PIN 码" + "此节目未分级。\n要观看此节目,请按向右键并输入您的 PIN 码" "此节目的分级为:%1$s。\n要观看此节目,请按“向右”按钮,然后输入您的 PIN 码。" "要观看此频道,请使用默认的“电视直播”应用。" "要观看此节目,请使用默认的“电视直播”应用。" + "此节目未分级。\n要观看此节目,请使用默认的“电视直播”应用。" "该节目的分级是“%1$s”。\n要观看此节目,请使用默认的“电视直播”应用。" "节目已被屏蔽" + "此节目未分级" "此节目的分级为:%1$s" "仅提供音频" "信号弱" @@ -181,8 +174,6 @@ "按“选择”""可访问电视菜单。" "未检测到电视输入设备" "找不到该电视输入设备" - "不支持 PIP" - "没有可通过 PIP 方式显示的输入" "不支持调谐器类型。请启动支持调谐器类型电视输入端口的“直播频道”应用。" "调谐失败" "未找到可处理此操作的应用。" @@ -226,6 +217,8 @@ 已安排录制 %1$d 项内容 已安排录制 %1$d 项内容 + "取消录制" + "停止录制" "观看" "从头播放" "继续播放" @@ -258,9 +251,6 @@ "如果同一时段有太多的节目要录制,系统只会录制优先级较高的节目。" "保存" "一次性录制内容具有最高优先级" - "取消" - "取消" - "移除" "停止" "查看录制时间表" "只录这一集节目" @@ -270,25 +260,28 @@ "改录这个节目" "取消这项录制安排" "立即观看" + "删除录制的节目…" "可录制" "已排定录制时间" "录制冲突" "正在录制" "录制失败" - "正在读取节目以创建录制时间安排表" - "正在读取节目" - - + "正在读取节目" + "查看最近的录制内容" + "%1$s的录制没有完成。" + "%1$s%2$s的录制没有完成。" + "%1$s%2$s%3$s的录制没有完成。" + "由于存储空间不足,%1$s的录制没有完成。" + "由于存储空间不足,%1$s%2$s的录制没有完成。" + "由于存储空间不足,%1$s%2$s%3$s的录制没有完成。" "DVR 需要更多存储空间" - "您将可以使用 DVR 录制节目,但目前您设备上的存储空间不足,因此无法使用 DVR。请连接存储空间不小于 %1$sGB 的外部驱动器,然后按照相关步骤将其格式化为设备的存储空间。" + "您将可以使用 DVR 录制节目,但目前您设备上的存储空间不足,因此无法使用 DVR。请连接存储空间不小于 %1$dGB 的外部驱动器,然后按照相关步骤将其格式化为设备的存储空间。" + "存储空间不足" + "由于存储空间不足,系统将不会录制此节目。请尝试删除部分已录制的节目。" "无法访问存储空间" - "无法访问 DVR 使用的部分存储空间。请连接您先前使用的外部驱动器,以重新启用 DVR。如果存储空间已无法再使用,您也可以选择忽略该存储空间。" - "要移除此存储空间吗?" - "您的所有录制内容和录制安排计划都将丢失。" "要停止录制吗?" "系统将保存已录制的内容。" - - + "系统将停止录制《%1$s》,因为它与此节目存在冲突。系统将保存已录制的内容。" "已排定录制时间,但录制时间存在冲突" "已开始录制,但存在冲突" "系统将会录制《%1$s》。" @@ -306,17 +299,29 @@ "已安排在以下时间录制同一节目:%1$s。" "已录制" "此节目已完成录制并保存在 DVR 媒体库中。" - - - - - - - - + "已排定剧集录制时间" + + 已为《%2$s》排定 %1$d 项录制计划。 + 已为《%2$s》排定 %1$d 项录制计划。 + + + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制其中 %3$d 集节目。 + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制这集节目。 + + + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制此剧集和其他剧集的 %3$d 集节目。 + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制此剧集和其他剧集的 %3$d 集节目。 + + + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制其他剧集的 1 集节目。 + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制其他剧集的 1 集节目。 + + + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制其他剧集的 %3$d 集节目。 + 已为《%2$s》排定 %1$d 项录制计划。由于存在冲突,系统将无法录制其他剧集的 %3$d 集节目。 + "未找到录制的节目。" "相关录制内容" - "(没有节目说明)" %1$d 项录制内容 %1$d 项录制内容 @@ -336,6 +341,7 @@ "要停止创建剧集录制时间表吗?" "已录制的剧集仍会保存到 DVR 媒体库中。" "停止" + "目前没有任何正在播出的剧集。" "没有已录制的剧集。\n一旦有剧集可供录制,系统将立即开始录制。" (%1$d 分钟) @@ -347,4 +353,5 @@ "今天%1$s" "明天%1$s" "评分" + "已录制的节目" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 5fe984d5..05e0b9e8 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -20,9 +20,8 @@ "單聲道" "立體聲" "播放控制" - "最近觀看的頻道" + "頻道" "電視選項" - "PIP 選項" "播放控制功能不適用於此頻道" "播放或暫停" "向前快轉" @@ -35,33 +34,15 @@ "隱藏式字幕" "顯示模式" "子母畫面" - "開啟" - "關閉" "多聲道" "取得更多頻道" "設定" - "來源" - "切換" - "開啟" - "關閉" - "音效" - "主視窗" - "子母畫面視窗" - "版面配置" - "右下方" - "右上方" - "左上方" - "左下方" - "並排" - "大小" - "大" - "小" - "輸入源" "電視 (天線/連接線)" "沒有節目資訊" "無資訊" "已封鎖的頻道" - "不明語言" + "不明語言" + "隱藏式字幕 (%1$d)" "隱藏式字幕" "關閉" "自訂格式" @@ -83,6 +64,7 @@ "標清" "分組依據" "這個節目已被封鎖。" + "此節目未分級" "這個節目的評級為%1$s。" "輸入裝置不支援自動掃瞄功能" "無法啟動「%s」的自動掃瞄" @@ -92,7 +74,6 @@ 已新增 %1$d 個頻道 "尚未新增任何頻道" - "調校器" "家長監護" "開啟" "關閉" @@ -108,6 +89,8 @@ "其他國家/地區" "無" "無" + "未分級" + "封鎖未分級的節目" "無" "嚴格限制" "中等限制" @@ -124,6 +107,7 @@ "輸入您的 PIN 即可觀看這個頻道" "輸入您的 PIN 即可觀看這個節目" "此節目的內容分級是「%1$s」。如要觀看此節目,請輸入 PIN" + "此節目未分級。如要觀看此節目,請輸入您的 PIN" "請輸入您的 PIN" "如要設定家長監護,請建立 PIN" "輸入新的 PIN" @@ -135,22 +119,31 @@ "該 PIN 錯誤,請再試一次。" "PIN 碼不符,請再試一次" + "請輸入郵遞區號。" + "「直播頻道」應用程式會利用郵遞區號,提供電視頻道的完整電視節目指南。" + "請輸入郵遞區號" + "郵遞區號無效" "設定" "自訂頻道清單" "選擇頻道以建立電視節目指南" "頻道來源" "可供設定的新頻道" "家長監控設定" + "時光平移" + "錄影正在觀看的直播節目,以便暫停播放節目或倒帶。\n警告:此功能會佔用大量儲存空間,因此可能會縮短內部儲存空間的使用壽命。" "開放原始碼授權" - "開放原始碼授權" + "傳送意見" "版本" "按向右鍵並輸入您的 PIN,以觀看這個頻道" "按向右鍵並輸入您的 PIN,以觀看這個節目" + "此節目未分級。\n如要觀看此節目,請按向右鍵並輸入您的 PIN" "這個節目的評級為%1$s。\n要觀看這個節目,請按向右鍵並輸入您的 PIN。" "如要觀看此頻道,請使用預設電視直播應用程式。" "如要觀看此節目,請使用預設電視直播應用程式。" + "此節目未分級。\n如要觀看此節目,請使用預設的「直播電視」應用程式。" "此節目評級為「%1$s」。\n如要觀看此節目,請使用預設電視直播應用程式。" "節目被封鎖" + "此節目未分級" "這個節目的評級為%1$s。" "只限音效" "訊號微弱" @@ -181,8 +174,6 @@ "按 [選擇]"" 前往 [電視選單]。" "找不到電視輸入" "找不到電視輸入" - "PIP 不受支援" - "沒有可供 PIP 顯示的輸入" "調諧器類型不適用;請啟動調諧器類型電視輸入專用的「直播頻道」應用程式。" "調校失敗" "找不到可以處理這項操作的應用程式。" @@ -226,6 +217,8 @@ 已預定 %1$d 個錄影 已預定 %1$d 個錄影 + "取消錄影" + "停止錄影" "觀看" "從頭開始播放" "恢復播放" @@ -258,9 +251,6 @@ "如果同一時間有太多需要錄影的節目,系統只會錄影優先級別較高的節目。" "儲存" "單次錄影享有最高優先級別" - "取消" - "取消" - "刪除" "停止" "查看錄影時間表" "只錄影這一集" @@ -270,25 +260,28 @@ "改為錄影此節目" "取消此錄影" "立即觀看" + "刪除錄影節目…" "可錄影" "已排定錄影時間" "錄影時間有衝突" "正在錄影" "錄影失敗" - "正在讀取節目以建立錄影時間表" - "正在讀取節目" - - + "正在讀取節目" + "查看最近的錄影" + "《%1$s》未完成錄影。" + "《%1$s》和《%2$s》未完成錄影。" + "《%1$s》、《%2$s》和《%3$s》未完成錄影。" + "由於儲存空間不足,因此《%1$s》未完成錄影。" + "由於儲存空間不足,因此《%1$s》和《%2$s》未完成錄影。" + "由於儲存空間不足,因此《%1$s》、《%2$s》和《%3$s》未完成錄影。" "DVR 需要更多儲存空間" - "您可以使用 DVR 錄影節目,但裝置目前的儲存空間不足,因此無法使用 DVR。請連接 %1$sGB 或以上的外置硬碟,然後按步驟格式化,以用作裝置儲存空間。" + "您可以使用 DVR 錄影節目,但裝置目前的儲存空間不足,因此無法使用 DVR。請連接 %1$dGB 或以上的外置硬碟,然後按步驟格式化,以用作裝置儲存空間。" + "儲存空間不足" + "由於儲存空間不足,因此無法錄影此節目。請嘗試刪除部分現有的錄影節目。" "無法存取儲存空間" - "系統存取部分 DVR 使用的儲存空間。請連接您先前使用的外置磁碟,然後重新啟用 DVR。如果儲存空間已無法使用,您亦可選擇刪除儲存空間。" - "要刪除儲存空間嗎?" - "所有已錄影的內容和時間表將會遺失。" "要停止錄影嗎?" "系統將儲存已錄影的內容。" - - + "由於《%1$s》的錄影時間與此節目有衝突,因此系統將停止錄影並儲存已錄影的內容。" "已預定錄影時間,但與錄影時間有衝突" "已開始錄影,但與其他預定錄影時間有衝突" "系統將錄影《%1$s》。" @@ -306,17 +299,29 @@ "已預定在 %1$s錄影相同的節目。" "已錄影" "系統已錄影此節目並儲存在 DVR 媒體庫中。" - - - - - - - - + "已預定劇集錄影時間" + + 已為《%2$s》預定 %1$d 個錄影時間。 + 已為《%2$s》預定 %1$d 個錄影時間。 + + + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影其中 %3$d 集節目。 + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影該節目。 + + + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影此劇集和其他劇集的 %3$d 集節目。 + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影此劇集和其他劇集的 %3$d 集節目。 + + + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影其他劇集的 1 集節目。 + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影其他劇集的 1 集節目。 + + + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影其他劇集的 %3$d 集節目。 + 已為《%2$s》預定 %1$d 個錄影時間。由於發生衝突,因此系統將不會錄影其他劇集的 %3$d 集節目。 + "找不到已錄影的節目。" "相關錄影" - "(沒有節目說明)" %1$d 個錄影 %1$d 個錄影 @@ -336,6 +341,7 @@ "要停止錄影劇集嗎?" "已錄影的劇集仍將保留在 DVR 媒體庫中。" "停止" + "目前沒有任何正在播出的劇集。" "沒有可供錄影的劇集。\n系統會在劇集播出時立即錄影。" (%1$d 分鐘) @@ -347,4 +353,5 @@ "今天%1$s" "明天%1$s" "分數" + "已錄影的節目" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index e33998ba..d40ecb0e 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -20,9 +20,8 @@ "單聲道" "立體聲" "播放控制介面" - "最近觀看的頻道" + "頻道" "電視選項" - "子母畫面選項" "這個頻道無法使用播放控制介面" "播放或暫停" "快轉" @@ -35,33 +34,15 @@ "字幕" "顯示模式" "子母畫面" - "開啟" - "關閉" "多聲道" "取得更多頻道" "設定" - "來源" - "切換" - "開啟" - "關閉" - "音效" - "主視窗" - "子母畫面視窗" - "版面配置" - "右下角" - "右上角" - "左上角" - "左下角" - "並排" - "尺寸" - "大" - "小" - "輸入來源" "電視 (有線/無線)" "沒有節目資訊" "無資訊" "封鎖的頻道" - "不明語言" + "不明語言" + "隱藏式輔助字幕 (%1$d)" "字幕" "關閉" "自訂格式設定" @@ -83,6 +64,7 @@ "SD 標準畫質" "分組依據" "這個節目遭到封鎖" + "這個節目未分級" "這個節目的分級是「%1$s」。" "輸入裝置不支援自動掃描" "無法開始「%s」的自動掃描作業" @@ -92,7 +74,6 @@ 新增了 %1$d 個頻道 "尚未新增任何頻道" - "協調器" "家長監護" "開啟" "關閉" @@ -101,13 +82,15 @@ "全部解除封鎖" "隱藏的頻道" "節目限制" - "變更 PIN" + "變更 PIN 碼" "分級系統" "分級" "查看所有分級制度" "其他國家/地區" "無" "無" + "未分級" + "封鎖未分級的節目" "無" "高度限制" "中度限制" @@ -124,6 +107,7 @@ "輸入您的 PIN 即可觀看這個頻道" "輸入您的 PIN 即可觀看這個節目" "這個節目的分級是「%1$s」。如要觀看此節目,請輸入 PIN 碼" + "這個節目未分級。如要觀看此節目,請輸入你的 PIN 碼" "請輸入您的 PIN" "如要設定家長監護,請建立 PIN" "輸入新的 PIN" @@ -135,22 +119,31 @@ "該 PIN 錯誤,請再試一次。" "PIN 碼不符,請再試一次" + "請輸入你的郵遞區號。" + "直播頻道應用程式會利用郵遞區號提供完整的電視頻道節目指南。" + "請輸入你的郵遞區號" + "無效的郵遞區號" "設定" "自訂頻道清單" "為您的節目指南選擇頻道" "頻道來源" "有可用的新頻道" "家長監護" + "影片播放操作" + "在觀看時進行錄製,以便將直播節目暫停或倒轉。\n警告:這項功能會密集使用儲存空間,可能會因此縮短內部儲存空間的使用壽命。" "開放原始碼授權" - "開放原始碼授權" + "提供意見" "版本" "如要觀看這個頻道,請按向右鍵並輸入您的 PIN" "如要觀看這個節目,請按向右鍵並輸入您的 PIN" + "這個節目未分級。\n如要觀看此節目,請按向右鍵並輸入你的 PIN 碼" "這個節目的分級是「%1$s」。\n如要觀看這個節目,請按向右鍵並輸入您的 PIN" "如要觀看這個頻道,請使用預設的直播電視應用程式。" "如要觀看這個節目,請使用預設的直播電視應用程式。" + "這個節目未分級。\n如要觀看此節目,請使用預設的直播電視應用程式。" "這個節目的分級是「%1$s」。\n如要觀看這個節目,請使用預設的直播電視應用程式。" "節目遭到封鎖" + "這個節目未分級" "這個節目的分級是「%1$s」。" "僅限音訊" "訊號微弱" @@ -181,8 +174,6 @@ "按 [選取]"" 即可使用 [電視選單]。" "找不到電視輸入裝置" "找不到電視輸入裝置" - "不支援子母畫面" - "沒有可透過子母畫面顯示的輸入來源" "協調器類型不適合;請啟動協調器類型電視輸入裝置專用的 Live TV 應用程式。" "協調失敗" "找不到可以處理這個動作的應用程式。" @@ -226,6 +217,8 @@ 已預約錄製 %1$d 個節目 已預約錄製 %1$d 個節目 + "取消錄製" + "停止錄製" "觀看" "從頭開始播放" "繼續播放" @@ -258,9 +251,6 @@ "如果同一時段錄製的節目過多,系統只會錄製優先順序較高的節目。" "儲存" "只排定錄製一次的項目優先順序最高" - "取消" - "取消" - "移除" "停止" "查看錄影時間表" "只錄這一集" @@ -270,25 +260,28 @@ "改錄這個節目" "取消這項錄影預約" "立即觀看" + "刪除錄影檔案…" "可錄影" "已排定錄影時間" "錄影衝突" "錄製中" "錄製失敗" - "正在讀取節目以建立錄影時間表" - "正在讀取節目" - - + "正在讀取節目資訊" + "查看最近的錄製項目" + "《%1$s》未錄製完成。" + "《%1$s》和《%2$s》未錄製完成。" + "《%1$s》、《%2$s》和《%3$s》未錄製完成。" + "儲存空間不足,因此《%1$s》未錄製完成。" + "儲存空間不足,因此《%1$s》和《%2$s》未錄製完成。" + "儲存空間不足,因此《%1$s》、《%2$s》和《%3$s》未錄製完成。" "DVR 需要更多儲存空間" - "你可以利用 DVR 錄製節目,但目前裝置儲存空間不足,因此無法使用 DVR。請連接 %1$sGB 以上的外接式磁碟,然後按照相關步驟將該磁碟格式化為裝置儲存空間。" + "你可以利用 DVR 錄製節目,但目前裝置儲存空間不足,因此無法使用 DVR。請連接 %1$dGB 以上的外接式磁碟,然後按照相關步驟將該磁碟格式化為裝置儲存空間。" + "儲存空間不足" + "儲存空間不足,因此無法錄製這個節目。請嘗試刪除一些現有的錄影檔案。" "無法存取儲存空間" - "無法存取部分 DVR 使用的儲存空間。請連接你先前使用的外接式磁碟以重新啟用 DVR。如果儲存空間已無法使用,你也可以選擇移除儲存空間。" - "要移除儲存空間嗎?" - "所有錄製內容和錄影時間表都不會保存下來。" "要停止錄影嗎?" "系統將儲存已錄製的內容。" - - + "由於「%1$s」與這個節目發生衝突,因此系統將停止錄製。已錄製的內容會保留下來。" "已排定錄影時間,但錄影時間發生衝突" "已開始錄影,但發生衝突" "系統將會錄製《%1$s》。" @@ -306,17 +299,29 @@ "同一個節目已預約在 %1$s 錄影。" "已錄影" "這個節目已完成錄影並儲存在 DVR 媒體庫中。" - - - - - - - - + "已排定影集錄製時間" + + 已為《%2$s》排定 %1$d 個錄影時間。 + 已為《%2$s》排定 %1$d 個錄影時間。 + + + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製其中 %3$d 集節目。 + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製這集節目。 + + + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製這個影集和其他影集的 %3$d 集節目。 + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製這個影集和其他影集的 %3$d 集節目。 + + + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製其他影集的 1 集節目。 + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製其他影集的 1 集節目。 + + + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製其他影集的 %3$d 集節目。 + 已為《%2$s》排定 %1$d 個錄影時間。由於發生時間衝突,因此系統將不會錄製其他影集的 %3$d 集節目。 + "找不到錄製的節目。" "相關錄影" - "(無節目說明)" %1$d 個錄製項目 %1$d 個錄製項目 @@ -336,6 +341,7 @@ "要停止建立影集錄製時間表嗎?" "錄製完畢的集數會保存在 DVR 媒體庫中。" "停止" + "目前未播出任何劇集。" "目前沒有可供觀看的集數。\n系統會在節目播出時立即錄影。" (%1$d 分鐘) @@ -347,4 +353,5 @@ "今天:%1$s" "明天:%1$s" "分數" + "已錄製的節目" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index ff1cfb3d..f34cdbd1 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -20,9 +20,8 @@ "i-mono" "i-stereo" "Izilawuli zokudlala" - "Iziteshi zakamuva" + "Iziteshi" "Izinketho ze-TV" - "Izinketho ze-PIP" "Izilawuli zokudlala azitholakali kulesi siteshi" "Dlala noma umise isikhashana" "Ukudlulisa ngokushesha" @@ -35,33 +34,15 @@ "Amazwibela avaliwe" "Imodi yesibonisi" "I-PIP" - "Vuliwe" - "Valiwe" "Umsindo omningi" "Thola iziteshi eziningi" "Izilungiselelo" - "Umthombo" - "Shintsha" - "Vuliwe" - "Valiwe" - "Umsindo" - "Isisekelo" - "Iwindil le-PIP" - "Isakhiwo" - "Phansi ngakwesokudla" - "Phezulu ngakwesokudla" - "Phezulu ngakwesokunxele" - "Phansi ngakwesokunxele" - "ingxenye nenxenye" - "Usayizi" - "Kukhulu" - "Kuncane" - "Umthombo wokufaka" "I-TV (I-antenna/Intambo)" "Alukho ulwazi lohlelo" "Alukho ulwazi" "Isiteshi esivinjiwe" - "Ulimi olungaziwa" + "Ulimi olungaziwa" + "Imibhalo engezansi evaliwe engu-%1$d" "Amazwibela avaliwe" "Valiwe" "Yenza ngokwezifiso ukufometha" @@ -83,6 +64,7 @@ "I-SD" "Qoqa nge-" "Lolu hlelo luvinjiwe" + "Lolu hlelo alulinganiselwe" "Lolu hlelo lulinganiselwe nge-%1$s" "Kokufaka akusekeli ukuskena okuzenzakalelayo" "Ayikwazi ukuqala ukuskena ngokuzenzakalela kwe-\'%s\'" @@ -92,7 +74,6 @@ %1$d iziteshi ezingeziwe "Azikho iziteshi ezingeziwe" - "I-Tuner" "Ukulawula kwabazali" "Vuliwe" "Valiwe" @@ -108,6 +89,8 @@ "Amanye amazwe" "Lutho" "Lutho" + "Okungalinganiselwe" + "Vimbela izinhlelo ezingalinganiselwe" "Lutho" "Imikhawulo ephezulu" "Imikhawulo emaphakathi" @@ -124,6 +107,7 @@ "Faka iphinikhodi yakho ukuze ubuke lesi siteshi" "Faka iphinikhodi yakho ukuze ubuke lolu hlelo" "Lolu hlelo lulinganiselwe ku-%1$s. Faka iphini yakho ukuze ubuke lolu hlelo" + "Lolu hlelo alulinganiselwe. Faka iphinikhodi yakho ukuze ubuke lolu hlelo" "Faka iphinikhodi yakho" "Ukuze usethe izilawuli zomzali, dala iphinikhodi" "Faka iphinikhodi entsha" @@ -135,22 +119,31 @@ "Leyo phini ayilungile. Zama futhi." "Zama futhi, iphinikhodi ayifani" + "Faka ikhodi yakho ye-ZIP." + "Uhlelo lokusebenza lweziteshi ezibukhoma luzosebenzisa ikhodi ye-ZIP ukuze lunikeze umhlahlandlela wohlelo ophelele weziteshi ze-TV." + "Faka ikhodi yakho ye-ZIP" + "Ikhodi ye-ZIP engavumelekile" "Izilungiselelo" "Yenza ngezifiso uhlu lweziteshi" "Khetha iziteshi zomhlahlandlela wohlelo lwakho" "Imithombo yeziteshi" "Iziteshi ezintsha ziyatholakala" "Ukulawula kwabazali" + "I-Timeshift" + "Rekhoda ngenkathi ubukile ukuze uzokwazi ukumisa okwesikhashana noma ubuyisele emuva izinhlelo ezibukhoma.\nIsexwayiso: Lokhu kungehlisa impilo yesitoreji sangaphakathi ngokusetshenziswa kakhulu kwesitoreji." "Amalayisense womthombo ovulekile" - "Amalayisense womthombo ovulekile" + "Thumela impendulo" "Inguqulo" "Ukuze ubuke lesi siteshi, cindezela Kwesokudla uphinde ufake i-PIN yakho" "Ukuze ubuke lolu hlelo, cindezela Kwesokudla uphinde ufake i-PIN yakho" + "Lolu hlelo alulinganiselwe.\nUkuze ubuke lolu hlelo, cindezela Kwesokudla bese ufaka iphinikhodi yakho" "Lolu hlelo lulinganiselwe ngo-%1$s.\nUkuze ubuke lolu hlelo, cindezela Kwesokudla uphinde ufake i-PIN yakho." "Ukuze ubuke lesi siteshi, sebenzisa uhlelo lokusebenza lwe-TV ebukhoma." "Ukuze ubuke lolu hlelo, sebenzisa uhlelo lokusebenza oluzenzakalelayo lwe-TV ebukhoma." + "Lolu hlelo alulinganiselwe.\nUkuze ubuke lolu hlelo, sebenzisa uhlelo lokusebenza oluzenzakalelayo lwe-TV elibukhoma." "Lolu hlelo lulinganiselwe ngokungu-%1$s.\nUkuze ubuke lolu hlelo, sebenzisa uhlelo lokusebenza lwe-TV ebukhoma." "Uhlelo luvinjiwe" + "Lolu hlelo alulinganiselwe" "Lolu hlelo lulinganiselwe nge-%1$s" "Umsindo kuphela" "Isignali engaqinile" @@ -181,8 +174,6 @@ "Cindezela okuthi KHETHA"" ukuze ufinyelele imenyu ye-TV." "Akukho kokufaka kwe-TV okutholakele" "Ayikwazi ukuthola kokufaka kwe-TV" - "I-PIP ayisekelwe" - "Akutholakali okokufaka okungaboniswa nge-PIP" "uhlobo le-Tuner alufanelekile; Sicela uqalise uhlelo lokusebenza leziteshi ezibukhoma lokufakwayo kwe-TV yohlobo le-tuner." "Ukushuna kwehlulekile" "Alukho uhlelo lokusebenza olutholakalele ukuphatha lesi senzo." @@ -226,6 +217,8 @@ %1$d ukurekhodwa okushejuliwe %1$d ukurekhodwa okushejuliwe + "Khansela ukurekhoda" + "Misa ukurekhoda" "Buka" "Dlala kusuka ekuqaleni" "Qalisa ukudlala" @@ -258,9 +251,6 @@ "Uma kunezinhlelo eziningi kakhulu okufanele zirekhodwe ngesikhathi esisodwa, yilezo ezinokukhetha okuphezulu ezizorekhodwa." "Londoloza" "Ukurekhoda kwesikhathi esisodwa kunokubaluleka okuphezulu kakhulu" - "Khansela" - "Khansela" - "Khohlwa" "Misa" "Buka ishejuli yokurekhoda" "Lolu hlelo olulodwa" @@ -270,25 +260,28 @@ "Rekhoda lokhu esikhundleni" "Khansela lokhu kurekhoda" "Bukela njengamanje" + "Susa ukurekhodwa..." "Okungarekhodeka" "Ukurekhoda kushejuliwe" "Ukungqubuzana kokurekhoda" "Iyarekhoda" "Ukurekhoda kuhlulekile" - "Ifunda izinhlelo ukuze idale amashejuli okurekhoda" - "Izinhlelo ezifundayo" - - + "Izinhlelo ezifundayo" + "Buka ukurekhoda kwakamuva" + "Ukurekhoda kwe-%1$s akuphelele." + "Ukurekhoda kwe-%1$s ne-%2$s akuphelele." + "Ukurekhoda kwe-%1$s, %2$s and %3$s akuphelele." + "Ukurekhoda kwe-%1$s akuqedanga ngenxa yesitoreji esingaphelele." + "Ukurekhoda kwe-%1$s ne-%2$s akuqedanga ngenxa yesitoreji esingaphelele." + "Ukurekhoda kwe-%1$s, %2$s ne-%3$s akuqedanga ngenxa yesitoreji esingaphelele." "I-DVR idinga isitoreji esiningi" - "Uzokwazi ukurekhoda izinhlelo nge-DVR. Kodwa asikho isitoreji esanele kudivayisi yakho manje ukuze i-DVR isebenze. Sicela uxhume idrayivu yangaphandle engu-%1$sGB noma enkulu bese ulandela izinyathelo uyifomethe njengesitoreji sedivayisi." + "Uzokwazi ukurekhoda izinhlelo nge-DVR. Kodwa asikho isitoreji esanele kudivayisi yakho manje ukuze i-DVR isebenze. Sicela uxhume idrayivu yangaphandle engu-%1$dGB noma enkulu bese ulandela izinyathelo uyifomethe njengesitoreji sedivayisi." + "Indawo yokubeka ayanele" + "Lolu hlelo ngeke lirekhodwe ngoba asikho isitoreji esanele. Zama ukususa ukurekhoda okukhona." "Isitoreji esilahlekile" - "Esinye sesitoreji esisetshenziswa yi-DVR silahlekile. Sicela uxhume idrayivu engaphandle oyisebenzise ngaphambilini ukuze uphinde unike amandla i-DVR. Okunye, ungakhetha ukukhohlwa isitoreji uma singasatholakali." - "Khohlwa isitoreji?" - "Konke okuqukethwe kwakho okurekhodiwe namashejuli azolahleka." "Misa ukurekhoda?" "Okuqukethwe okurekhodiwe kuzolondolozwa." - - + "Ukurekhodwa kwe-%1$s kuzomiswa ngoba kugqubuzana nalolu hlelo. Okuqukethwe okurekhodiwe ngeke kulondolozwe." "Ukurekhoda kushejuliwe kodwa kunokugqubuzana" "Ukurekhoda kuqalile kodwa kunokugxubuzana" "%1$s izorekhodwa." @@ -306,17 +299,29 @@ "Uhlelo olufanayo seluvele luhlelwe ukurekhodwa ngo-%1$s." "Sekuvele kurekhodiwe" "Lolu hlelo seluvele lurekhodiwe. Lutholakala kulabhulali ye-DVR." - - - - - - - - + "Ukurekhodwa kochungechunge kuhleliwe" + + %1$d ukurekhodwa kushejulelwe i-%2$s. + %1$d ukurekhodwa kushejulelwe i-%2$s. + + + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d kwazo ngeke kurekhodwe ngenxa yokushayisana. + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d kwazo ngeke kurekhodwe ngenxa yokushayisana. + + + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d iziqephu zalo chungechunge nolunye uchungechunge ngeke zirekhodwe ngenxa yokushayisana. + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d iziqephu zalo chungechunge nolunye uchungechunge ngeke zirekhodwe ngenxa yokushayisana. + + + %1$d ukurekhodwa kushejulelwe i-%2$s. 1 isiqephu solunye uchungechunge ngeke sirekhodwe ngenxa yokushayisana. + %1$d ukurekhodwa kushejulelwe i-%2$s. 1 isiqephu solunye uchungechunge ngeke sirekhodwe ngenxa yokushayisana. + + + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d iziqephu zolunye uchungechunge ngeke zize zirekhodwe ngenxa yokushayisana. + %1$d ukurekhodwa kushejulelwe i-%2$s. %3$d iziqephu zolunye uchungechunge ngeke zize zirekhodwe ngenxa yokushayisana. + "Uhlelo olurekhodiwe alutholakali." "Ukurekhodwa okuhlobene" - "(Ayikho incazelo yohlelo)" %1$d ukurekhoda %1$d ukurekhoda @@ -336,6 +341,7 @@ "Misa ukurekhodwa kochungechunge" "Iziqephu ezirekhodiwe zizohlala zitholakala kulabhulali ye-DVR." "Misa" + "Azikho iziqephu ezisemoyeni okwamanje." "Azikho iziqephu ezitholakalayo.\nZizorekhodwa uma zitholakala." (%1$d amaminithi) @@ -347,4 +353,5 @@ "%1$s namhlanje" "%1$s kusasa" "Isikolo" + "Izinhlelo ezirekhodiwe" diff --git a/res/values/attr.xml b/res/values/attr.xml deleted file mode 100644 index 1261ea41..00000000 --- a/res/values/attr.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - diff --git a/res/values/attrs.xml b/res/values/attrs.xml new file mode 100644 index 00000000..a26a9ef2 --- /dev/null +++ b/res/values/attrs.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index b6b40563..e0a0b99f 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -109,11 +109,11 @@ #80EEEEEE + #99000000 #FFEEEEEE #B3EEEEEE - #B3EEEEEE #80EEEEEE @@ -121,7 +121,7 @@ @color/channel_banner_text_color - @color/channel_banner_secondary_input_label_color + #B3EEEEEE #4DEEEEEE diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 8d9e45e1..9d8941fa 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -16,8 +16,12 @@ --> + + 27dp + 48dp + - @dimen/menu_enter_offset_y_negative + -32dp 27dp 124dp 300dp @@ -41,7 +45,6 @@ 16sp 8dp 32dp - -32dp 32dp 32dp @@ -102,7 +105,6 @@ 32dp 32dp 10dp - 12dp 16sp 12sp @@ -115,7 +117,6 @@ 27dp 4dp 24sp - 0dp 48dp 0dp 100dp @@ -147,7 +148,6 @@ 238dp - -238dp 270dp -238dp 56dp @@ -193,7 +193,7 @@ 40dp 16sp 24dp - -52dp + -72dp 64dp 16dp 8dp @@ -244,23 +244,9 @@ 60dp 24dp - - 56dp - 27dp - 27dp - 240dp - 135dp - 384dp - 216dp - - - 20dp - 24dp - 56dp 32dp - 288dp 696dp @@ -274,7 +260,7 @@ 44dp 8dp 16dp - 536dp + 456dp 28sp 20sp -10sp @@ -290,6 +276,7 @@ 48dp + 324dp @@ -336,9 +323,7 @@ - 196dp - - + 132dp 1dp 960dp @@ -349,9 +334,7 @@ 32dp 14dp 15dp - 12dp 15dp - 56dp 46dp 32dp 18dp @@ -361,26 +344,31 @@ 24dp 5dp 143dp - 32dp 12dp 4dp - - 192dp - 108dp - 2dp + + 175dp + 144dp + 108dp + 2dp + 20dp + 36.5dp - 146dp - 82dp + 108dp + 81dp 27.5dp - - 48dp - 265dp - 40dp + + 36dp + + 44dp + + 4dp 219dp @@ -390,6 +378,7 @@ 20dp 12dp 12dp + 30dp 34sp 0dp @@ -401,4 +390,8 @@ button actions list width = 138dp (weight = 0.39) --> 0.39 1.15 + + + 80dp + 80dp diff --git a/res/values/google-services.xml b/res/values/google-services.xml deleted file mode 100755 index 379a3453..00000000 --- a/res/values/google-services.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - https://live-channels-1237.firebaseio.com - 399597460505 - AIzaSyDK2BtNulo2ltWIogD64y1hBWKrdg9Sa7k - AIzaSyDK2BtNulo2ltWIogD64y1hBWKrdg9Sa7k - 1:399597460505:android:6c699100869b1467 - diff --git a/res/values/integers.xml b/res/values/integers.xml index 81ccbeb7..003344e3 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -35,7 +35,10 @@ 250 50 200 - + + + 3 + 60000 250 @@ -74,6 +77,9 @@ 32 250 + + 5 + 50 15 @@ -87,4 +93,5 @@ 500 + 1 diff --git a/res/values/strings.xml b/res/values/strings.xml index a58f2d36..57049aba 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -35,6 +35,12 @@ Allows the app to receive input events from Live TV app + + customize Live TV app + + Allows the app to customize Live TV app @@ -63,11 +69,9 @@ The play means a playback of video and audio. [CHAR LIMIT=NONE] --> Play controls - Recent channels + Channels TV options - - PIP options %1$dX @@ -101,12 +105,6 @@ Display mode PIP - - On - - Off Multi-audio @@ -116,54 +114,6 @@ Settings - - Source - - Swap - - On - - Off - - Sound - - Main - - PIP window - - Layout - - Bottom right - - Top right - - Top left - - Bottom left - - Side by side - - Size - - Big - - Small - - - Input source TV (antenna/cable) @@ -175,8 +125,11 @@ Blocked channel - - Unknown language + + Unknown language + + + Closed captions %1$d @@ -238,6 +191,8 @@ This program is blocked + + This program is unrated This program is rated %1$s @@ -257,11 +212,6 @@ No channels added - - - - Tuner - @@ -304,6 +254,12 @@ [CHAR LIMIT=NONE] --> None + + Unrated + + + Block unrated programs + None @@ -349,6 +305,9 @@ This program is rated %1$s. Enter your PIN to watch this program + + This program is unrated. Enter your PIN to watch this program Enter your PIN @@ -371,6 +330,15 @@ Try again, PIN doesn\'t match + + Enter your ZIP Code. + + Live TV app will use the ZIP Code to provide a complete program guide for the TV channels. + + Enter your ZIP Code + + Invalid ZIP Code + @@ -387,10 +355,16 @@ New channels available Parental controls + + Timeshift + + Record while watching so you can pause or rewind live programs.\nWarning: This may decrease the life of the internal storage by the intensive use of the storage. Open source licenses - - Open source licenses + + Send feedback Version @@ -403,17 +377,23 @@ To watch this channel, press Right and enter your PIN To watch this program, press Right and enter your PIN + + This program is unrated.\nTo watch this program, press Right and enter your PIN This program is rated %1$s.\nTo watch this program, press Right and enter your PIN. To watch this channel, use the default Live TV app. To watch this program, use the default Live TV app. + + This program is unrated.\nTo watch this program, use the default Live TV app. This program is rated %1$s.\nTo watch this program, use the default Live TV app. Program is blocked + + This program is unrated This program is rated %1$s @@ -486,10 +466,6 @@ No TV input found Cannot find the TV input - - PIP is not supported - - There is no available input which can be shown with PIP Developer options Watch history + DVR history Fetch program guide - Send feedback Store TS for debugging: On Store TS for debugging: Off Store some TS data before exceptions/crash for debugging. + Show performance log + + + [Fail] + [Success] + [Clip] Recently watched + + DVR history + + + Live TV DVR + + Live TV DVR + + Live TV are recording. + + Live TV are updating recording schedules. + No result Search results - + Set up your sources @@ -542,25 +535,11 @@ Live channels combines the experience of traditional TV channels with streaming channels provided by apps. \n\nGet started by setting up the channel sources already installed. Or browse Google Play Store for more apps that offer live channels. - - - - Cancel recording - - Stop recording - - Record program - - Record season - - Delete schedule - Recordings & schedules Start recording - + Stop recording @@ -586,15 +565,10 @@ Series Others - None - Channel unknown - No available tuners to record this channel. - The channel is already being recorded. The channel cannot be recorded. The program cannot be recorded. - There is no item. %1$s has been scheduled to be recorded - Cancel recording - Stop and keep recording - Stop and delete recording + + Cancel recording + + Stop recording Watch @@ -655,6 +630,7 @@ Read more + @@ -709,18 +685,10 @@ - Tune - - Cancel Delete schedule Record program - Record this episode only - Done - Open DVR - - Cancel - - Forget + + Open storage settings Stop @@ -749,6 +717,8 @@ Cancel this recording Watch now + + Delete recordings… Recordable @@ -762,28 +732,37 @@ DVR Upcoming schedules The programs will not be recorded if you keep watching this channel. Cancel the recordings, or current channel will be blocked when the recording starts. - - Reading programs to create recording schedules - Reading programs - - Updating series recording - Insufficient storage space - No sufficient storage space for recording. Please clean up the storage. + Reading programs + + View recent recordings + + The recording of %1$s is incomplete. + + The recordings of %1$s and %2$s are incomplete. + + The recordings of %1$s, %2$s and %3$s are incomplete. + + The recording of %1$s didn\'t complete due to insufficient storage. + + The recordings of %1$s and %2$s didn\'t complete due to insufficient storage. + + The recordings of %1$s, %2$s and %3$s didn\'t complete due to insufficient storage. DVR needs more storage - - You will be able to record programs with DVR. However there is not enough storage on your device now for DVR to work. Please connect an external drive that is %1$sGB or larger and follow the steps to format it as device storage. + + You will be able to record programs with DVR. However there is not enough storage on your device now for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage. + + Not enough storage + + This program will not be recorded because there is not enough storage. Try deleting some existing recordings. Missing storage - Some of the storage used by DVR is missing. Please connect the external drive you used before to re-enable DVR. Alternately, you can choose to forget the storage if it\'s no longer available. - - Forget storage? - - All your recorded content and schedules will be lost. + Some of the storage used by DVR is missing. Please connect the external drive you used before to re-enable DVR. Alternately, you can forget the storage in the storage settings, if it\'s no longer available. The recording seems to be deleted. @@ -847,66 +826,53 @@ This program has already been recorded. It’s available in the DVR library. Series recording scheduled - - - %1$d recording has been scheduled for %2$s. - %1$d recordings have been scheduled for %2$s. + + + %1$d recording has been scheduled for %2$s. + %1$d recordings have been scheduled for %2$s. - + - %1$d recording has been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. - %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. + %1$d recording has been scheduled for %2$s. It will not be recorded due to conflicts. + %1$d recordings have been scheduled for %2$s. %3$d of them will not be recorded due to conflicts. - - - %1$d recording has been scheduled for %2$s. 1 episode of this series and other series will not be recorded due to conflicts. - %1$d recordings have been scheduled for %2$s. 1 episode of this series and other series will not be recorded due to conflicts. + + %1$d recording has been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. + %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. - - - %1$d recording has been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. - %1$d recordings have been scheduled for %2$s. %3$d episodes of this series and other series will not be recorded due to conflicts. - - - - %1$d recording has been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. - %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + + + %1$d recording has been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. + %1$d recordings have been scheduled for %2$s. 1 episode of other series will not be recorded due to conflicts. - - - %1$d recording has been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. - %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + + + %1$d recording has been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. + %1$d recordings have been scheduled for %2$s. %3$d episodes of other series will not be recorded due to conflicts. - Recorded Program Recorded program not found. Related recordings - - (No program description) Recording till %1$s - - - %1$s is the selected account - No account available - @@ -944,6 +910,8 @@ Recorded episodes will remain available in the DVR library. Stop + + No episodes are on air now. No episodes are available.\nThey will be recorded once they are available. @@ -974,4 +942,9 @@ Score + + + + + Recorded Programs diff --git a/res/values/styles.xml b/res/values/styles.xml index 3a34af52..84885f13 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -92,7 +92,7 @@ 14sp @color/lb_basic_card_title_text_color sans-serif-condensed - true + @integer/dvr_card_title_max_lines_folded end @@ -143,7 +143,7 @@ 60dp - - + \ No newline at end of file diff --git a/res/xml/remote_config_defaults.xml b/res/xml/remote_config_defaults.xml index 742bff21..6ecad191 100644 --- a/res/xml/remote_config_defaults.xml +++ b/res/xml/remote_config_defaults.xml @@ -15,8 +15,23 @@ ~ limitations under the License. --> + + live_channels_ac3_software_decoder + false + + live_channels_epg_host_and_port datamixer-pa.googleapis.com:443 + + + live_channels_epg_fetcher_interval_hour + 4 + + + + live_channels_epg_reader_max_channels_per_program_fetch + 20 + \ No newline at end of file diff --git a/src/com/android/exoplayer/text/SubtitleView.java b/src/com/android/exoplayer/text/SubtitleView.java index 7cde69a1..37926eda 100644 --- a/src/com/android/exoplayer/text/SubtitleView.java +++ b/src/com/android/exoplayer/text/SubtitleView.java @@ -39,14 +39,12 @@ import java.util.ArrayList; /** * Since this class does not exist in recent version of ExoPlayer and used by - * {@link com.google.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from + * {@link com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from * older version of ExoPlayer. * A view for rendering a single caption. */ @Deprecated public class SubtitleView extends View { - // TODO: Change usage of this class to up-to-date class of ExoPlayer. - /** * Ratio of inner padding to font size. */ @@ -252,11 +250,8 @@ public class SubtitleView extends View { mHasMeasurements = true; mLastMeasuredWidth = maxWidth; - mLayout = StaticLayout.Builder.obtain(mText, 0, mText.length(), mTextPaint, maxWidth) - .setAlignment(mAlignment) - .setLineSpacing(mSpacingAdd, mSpacingMult) - .setUseLineSpacingFromFallbacks(true) - .build(); + mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, + mSpacingMult, mSpacingAdd, true); return true; } diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java new file mode 100644 index 00000000..2b7817dc --- /dev/null +++ b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.ffmpeg; + +import android.content.Context; +import android.content.pm.PackageManager; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; +import com.google.android.exoplayer2.util.MimeTypes; +import com.android.tv.common.SoftPreconditions; + +import java.nio.ByteBuffer; + +/** + * Audio decoder which uses ffmpeg extension of ExoPlayer2. Since {@link FfmpegDecoder} is package + * private, expose the decoder via this class. Supported formats are AC3 and MP2. + */ +public class FfmpegAudioDecoder { + private static final int NUM_DECODER_BUFFERS = 1; + + // The largest AC3 sample size. This is bigger than the largest MP2 sample size (1729). + private static final int INITIAL_INPUT_BUFFER_SIZE = 2560; + private static boolean AVAILABLE; + + static { + AVAILABLE = + FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_AC3) + && FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_MPEG_L2); + } + + private FfmpegDecoder mDecoder; + private DecoderInputBuffer mInputBuffer; + private SimpleOutputBuffer mOutputBuffer; + private boolean mStarted; + + /** Return whether Ffmpeg based software audio decoder is available. */ + public static boolean isAvailable() { + return AVAILABLE; + } + + /** Creates an Ffmpeg based software audio decoder. */ + public FfmpegAudioDecoder(Context context) { + if (context.checkSelfPermission("android.permission.INTERNET") + == PackageManager.PERMISSION_GRANTED) { + throw new IllegalStateException("This code should run in an isolated process"); + } + } + + /** + * Decodes an audio sample. + * + * @param timeUs presentation timestamp of the sample + * @param sample data + */ + public void decode(long timeUs, byte[] sample) { + SoftPreconditions.checkState(AVAILABLE); + mInputBuffer.data.clear(); + mInputBuffer.data.put(sample); + mInputBuffer.data.flip(); + mInputBuffer.timeUs = timeUs; + mDecoder.decode(mInputBuffer, mOutputBuffer, !mStarted); + if (!mStarted) { + mStarted = true; + } + } + + /** Returns a decoded sample from decoder. */ + public ByteBuffer getDecodedSample() { + return mOutputBuffer.data; + } + + /** Returns the presentation time for the decoded sample. */ + public long getDecodedTimeUs() { + return mOutputBuffer.timeUs; + } + + /** + * Clear previous decode state if any. Prepares to decode samples of the specified encoding. + * This method should be called before using decode. + * + * @param mime audio encoding + */ + public void resetDecoderState(String mime) { + SoftPreconditions.checkState(AVAILABLE); + release(); + try { + mDecoder = + new FfmpegDecoder( + NUM_DECODER_BUFFERS, + NUM_DECODER_BUFFERS, + INITIAL_INPUT_BUFFER_SIZE, + mime, + null); + mStarted = false; + mInputBuffer = mDecoder.createInputBuffer(); + // Since native JNI requires direct buffer, we should allocate it by #allocateDirect. + mInputBuffer.data = ByteBuffer.allocateDirect(INITIAL_INPUT_BUFFER_SIZE); + mOutputBuffer = mDecoder.createOutputBuffer(); + } catch (FfmpegDecoderException e) { + // if AVAILABLE is {@code true}, this will not happen. + } + } + + /** Releases all the resource. */ + public void release() { + SoftPreconditions.checkState(AVAILABLE); + if (mDecoder != null) { + mDecoder.release(); + mInputBuffer = null; + mOutputBuffer = null; + mDecoder = null; + } + } +} diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java new file mode 100644 index 00000000..daa77340 --- /dev/null +++ b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.ffmpeg; + +import com.google.android.exoplayer2.util.LibraryLoader; +import com.google.android.exoplayer2.util.MimeTypes; + +/** + * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2 + * in order to support mp2 decoder. + * Configures and queries the underlying native library. + */ +public final class FfmpegLibrary { + + private static final LibraryLoader LOADER = + new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg"); + + private FfmpegLibrary() {} + + /** + * Overrides the names of the FFmpeg native libraries. If an application wishes to call this + * method, it must do so before calling any other method defined by this class, and before + * instantiating a {@link FfmpegAudioRenderer} instance. + */ + public static void setLibraries(String... libraries) { + LOADER.setLibraries(libraries); + } + + /** + * Returns whether the underlying library is available, loading it if necessary. + */ + public static boolean isAvailable() { + return LOADER.isAvailable(); + } + + /** + * Returns the version of the underlying library if available, or null otherwise. + */ + public static String getVersion() { + return isAvailable() ? ffmpegGetVersion() : null; + } + + /** + * Returns whether the underlying library supports the specified MIME type. + */ + public static boolean supportsFormat(String mimeType) { + if (!isAvailable()) { + return false; + } + String codecName = getCodecName(mimeType); + return codecName != null && ffmpegHasDecoder(codecName); + } + + /** + * Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. + */ + /* package */ static String getCodecName(String mimeType) { + switch (mimeType) { + case MimeTypes.AUDIO_MPEG_L2: + return "mp2"; + case MimeTypes.AUDIO_AC3: + return "ac3"; + default: + return null; + } + } + + private static native String ffmpegGetVersion(); + private static native boolean ffmpegHasDecoder(String codecName); + +} diff --git a/src/com/android/tv/ApplicationSingletons.java b/src/com/android/tv/ApplicationSingletons.java index fd125d52..ac7d4c4a 100644 --- a/src/com/android/tv/ApplicationSingletons.java +++ b/src/com/android/tv/ApplicationSingletons.java @@ -20,12 +20,15 @@ import com.android.tv.analytics.Analytics; import com.android.tv.analytics.Tracker; import com.android.tv.config.RemoteConfig; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.PreviewDataManager; import com.android.tv.data.ProgramDataManager; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.perf.PerformanceMonitor; import com.android.tv.util.AccountHelper; import com.android.tv.util.TvInputManagerHelper; @@ -38,6 +41,22 @@ public interface ApplicationSingletons { ChannelDataManager getChannelDataManager(); + /** + * Checks if the {@link ChannelDataManager} instance has been created and all the channels has + * been loaded. + */ + boolean isChannelDataManagerLoadFinished(); + + ProgramDataManager getProgramDataManager(); + + /** + * Checks if the {@link ProgramDataManager} instance has been created and the current programs + * for all the channels has been loaded. + */ + boolean isProgramDataManagerCurrentProgramsLoadFinished(); + + PreviewDataManager getPreviewDataManager(); + DvrDataManager getDvrDataManager(); DvrStorageStatusManager getDvrStorageStatusManager(); @@ -46,12 +65,12 @@ public interface ApplicationSingletons { DvrManager getDvrManager(); + RecordingScheduler getRecordingScheduler(); + DvrWatchedPositionManager getDvrWatchedPositionManager(); InputSessionManager getInputSessionManager(); - ProgramDataManager getProgramDataManager(); - Tracker getTracker(); TvInputManagerHelper getTvInputManagerHelper(); @@ -61,4 +80,8 @@ public interface ApplicationSingletons { AccountHelper getAccountHelper(); RemoteConfig getRemoteConfig(); + + boolean isRunningInMainProcess(); + + PerformanceMonitor getPerformanceMonitor(); } diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java new file mode 100644 index 00000000..4fca06ac --- /dev/null +++ b/src/com/android/tv/AudioManagerHelper.java @@ -0,0 +1,108 @@ +package com.android.tv; + +import android.app.Activity; +import android.content.Context; +import android.media.AudioManager; +import android.os.Build; + +import com.android.tv.receiver.AudioCapabilitiesReceiver; +import com.android.tv.ui.TunableTvView; + +/** + * A helper class to help {@link MainActivity} to handle audio-related stuffs. + */ +class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { + private static final float AUDIO_MAX_VOLUME = 1.0f; + private static final float AUDIO_MIN_VOLUME = 0.0f; + private static final float AUDIO_DUCKING_VOLUME = 0.3f; + + private final Activity mActivity; + private final TunableTvView mTvView; + private final AudioManager mAudioManager; + private final AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; + + private boolean mAc3PassthroughSupported; + private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; + + AudioManagerHelper(Activity activity, TunableTvView tvView) { + mActivity = activity; + mTvView = tvView; + mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); + mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(activity, + new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() { + @Override + public void onAc3PassthroughCapabilityChange(boolean capability) { + mAc3PassthroughSupported = capability; + } + }); + mAudioCapabilitiesReceiver.register(); + } + + /** + * Sets suitable volume to {@link TunableTvView} according to the current audio focus. + * If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP + * mode, this method will finish the activity. + */ + void setVolumeByAudioFocusStatus() { + if (mTvView.isPlaying()) { + switch (mAudioFocusStatus) { + case AudioManager.AUDIOFOCUS_GAIN: + mTvView.setStreamVolume(AUDIO_MAX_VOLUME); + break; + case AudioManager.AUDIOFOCUS_LOSS: + if (Features.PICTURE_IN_PICTURE.isEnabled(mActivity) + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && mActivity.isInPictureInPictureMode()) { + mActivity.finish(); + break; + } + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + mTvView.setStreamVolume(AUDIO_MIN_VOLUME); + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME); + break; + } + } + } + + /** + * Tries to request audio focus from {@link AudioManager} and set volume according to the + * returned result. + */ + void requestAudioFocus() { + int result = mAudioManager.requestAudioFocus(this, + AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? + AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; + setVolumeByAudioFocusStatus(); + } + + /** + * Abandons audio focus. + */ + void abandonAudioFocus() { + mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; + mAudioManager.abandonAudioFocus(this); + } + + /** + * Returns {@code true} if the device supports AC3 pass-through. + */ + boolean isAc3PassthroughSupported() { + return mAc3PassthroughSupported; + } + + /** + * Release the resources the helper class may occupied. + */ + void release() { + mAudioCapabilitiesReceiver.unregister(); + } + + @Override + public void onAudioFocusChange(int focusChange) { + mAudioFocusStatus = focusChange; + setVolumeByAudioFocusStatus(); + } +} diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java index 7e8e3689..2052f2e7 100644 --- a/src/com/android/tv/Features.java +++ b/src/com/android/tv/Features.java @@ -16,22 +16,29 @@ package com.android.tv; -import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; -import static com.android.tv.common.feature.FeatureUtils.AND; -import static com.android.tv.common.feature.FeatureUtils.OFF; -import static com.android.tv.common.feature.FeatureUtils.ON; -import static com.android.tv.common.feature.FeatureUtils.OR; - import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.support.annotation.VisibleForTesting; -import android.support.v4.os.BuildCompat; +import android.text.TextUtils; +import android.util.Log; import com.android.tv.common.feature.Feature; import com.android.tv.common.feature.GServiceFeature; import com.android.tv.common.feature.PropertyFeature; +import com.android.tv.config.RemoteConfig; +import com.android.tv.experiments.Experiments; +import com.android.tv.util.LocationUtils; import com.android.tv.util.PermissionUtils; +import com.android.tv.util.Utils; + +import java.util.Locale; + +import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; +import static com.android.tv.common.feature.FeatureUtils.AND; +import static com.android.tv.common.feature.FeatureUtils.OFF; +import static com.android.tv.common.feature.FeatureUtils.ON; +import static com.android.tv.common.feature.FeatureUtils.OR; /** * List of {@link Feature} for the Live TV App. @@ -39,6 +46,9 @@ import com.android.tv.util.PermissionUtils; *

Remove the {@code Feature} once it is launched. */ public final class Features { + private static final String TAG = "Features"; + private static final boolean DEBUG = false; + /** * UI for opting in to analytics. * @@ -57,17 +67,40 @@ public final class Features { public static final Feature EPG_SEARCH = new PropertyFeature("feature_tv_use_epg_search", false); - public static final Feature TUNER = new Feature() { - @Override - public boolean isEnabled(Context context) { + public static final Feature TUNER = + new Feature() { + @Override + public boolean isEnabled(Context context) { - // This is special handling just for USB Tuner. - // It does not require any N API's but relies on a improvements in N for AC3 support - // After release, change class to this to just be {@link BuildCompat#isAtLeastN()}. - return Build.VERSION.SDK_INT > Build.VERSION_CODES.M || BuildCompat.isAtLeastN(); - } + if (Utils.isDeveloper()) { + // we enable tuner for developers to test tuner in any platform. + return true; + } - }; + // This is special handling just for USB Tuner. + // It does not require any N API's but relies on a improvements in N for AC3 support + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + }; + + /** + * Use network tuner if it is available and there is no other tuner types. + */ + public static final Feature NETWORK_TUNER = + new Feature() { + @Override + public boolean isEnabled(Context context) { + if (!TUNER.isEnabled(context)) { + return false; + } + if (Utils.isDeveloper()) { + // Network tuner will be enabled for developers. + return true; + } + return Locale.US.getCountry().equalsIgnoreCase( + LocationUtils.getCurrentCountry(context)); + } + }; private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide"; /** @@ -82,22 +115,75 @@ public final class Features { } }); - public static final Feature PICTURE_IN_PICTURE = new Feature() { - private Boolean mEnabled; + public static final Feature PICTURE_IN_PICTURE = + new Feature() { + private Boolean mEnabled; - @Override - public boolean isEnabled(Context context) { - if (mEnabled == null) { - mEnabled = context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_PICTURE_IN_PICTURE); - } - return mEnabled; - } - }; + @Override + public boolean isEnabled(Context context) { + if (mEnabled == null) { + mEnabled = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && context.getPackageManager() + .hasSystemFeature( + PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + return mEnabled; + } + }; - /** - * Enable a conflict dialog between currently watched channel and upcoming recording. - */ + /** Use AC3 software decode. */ + public static final Feature AC3_SOFTWARE_DECODE = + new Feature() { + private final String[] SUPPORTED_REGIONS = {}; + + private Boolean mEnabled; + + @Override + public boolean isEnabled(Context context) { + if (mEnabled == null) { + if (mEnabled == null) { + // We will not cache the result of fallback solution. + String country = LocationUtils.getCurrentCountry(context); + for (int i = 0; i < SUPPORTED_REGIONS.length; ++i) { + if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) { + return true; + } + } + if (DEBUG) Log.d(TAG, "AC3 flag false after country check"); + return false; + } + } + if (DEBUG) Log.d(TAG, "AC3 flag " + mEnabled); + return mEnabled; + } + }; + + /** Show postal code fragment before channel scan. */ + public static final Feature ENABLE_CLOUD_EPG_REGION = + new Feature() { + private final String[] SUPPORTED_REGIONS = { + }; + + + @Override + public boolean isEnabled(Context context) { + if (!Experiments.CLOUD_EPG.get()) { + if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false"); + return false; + } + String country = LocationUtils.getCurrentCountry(context); + for (int i = 0; i < SUPPORTED_REGIONS.length; i++) { + if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) { + return true; + } + } + if (DEBUG) Log.d(TAG, "EPG flag false after country check"); + return false; + } + }; + + /** Enable a conflict dialog between currently watched channel and upcoming recording. */ public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF; /** @@ -105,6 +191,11 @@ public final class Features { */ public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON; + /** + * Enable Dvb parsers and listeners. + */ + public static final Feature ENABLE_FILE_DVB = OFF; + @VisibleForTesting public static final Feature TEST_FEATURE = new PropertyFeature("test_feature", false); diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java index e4b0f456..2978f409 100644 --- a/src/com/android/tv/InputSessionManager.java +++ b/src/com/android/tv/InputSessionManager.java @@ -37,7 +37,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnTuneListener; @@ -73,6 +72,8 @@ public class InputSessionManager { Collections.synchronizedSet(new ArraySet<>()); private final Set mOnTvViewChannelChangeListeners = new ArraySet<>(); + private final Set mOnRecordingSessionChangeListeners = + new ArraySet<>(); public InputSessionManager(Context context) { mContext = context.getApplicationContext(); @@ -113,6 +114,9 @@ public class InputSessionManager { RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs); mRecordingSessions.add(session); if (DEBUG) Log.d(TAG, "Recording session created: " + session); + for (OnRecordingSessionChangeListener listener : mOnRecordingSessionChangeListeners) { + listener.onRecordingSessionChange(true, mRecordingSessions.size()); + } return session; } @@ -123,6 +127,9 @@ public class InputSessionManager { mRecordingSessions.remove(session); session.release(); if (DEBUG) Log.d(TAG, "Recording session released: " + session); + for (OnRecordingSessionChangeListener listener : mOnRecordingSessionChangeListeners) { + listener.onRecordingSessionChange(false, mRecordingSessions.size()); + } } /** @@ -148,9 +155,17 @@ public class InputSessionManager { } } - /** - * Returns the current {@link TvView} channel. - */ + /** Adds the {@link OnRecordingSessionChangeListener}. */ + public void addOnRecordingSessionChangeListener(OnRecordingSessionChangeListener listener) { + mOnRecordingSessionChangeListeners.add(listener); + } + + /** Removes the {@link OnRecordingSessionChangeListener}. */ + public void removeRecordingSessionChangeListener(OnRecordingSessionChangeListener listener) { + mOnRecordingSessionChangeListeners.remove(listener); + } + + /** Returns the current {@link TvView} channel. */ @MainThread public Uri getCurrentTvViewChannelUri() { for (TvViewSession session : mTvViewSessions) { @@ -249,7 +264,7 @@ public class InputSessionManager { mTvView.setCallback(new DelegateTvInputCallback(mCallback) { @Override public void onConnectionFailed(String inputId) { - if (DEBUG) Log.d(TAG, "TvViewSession: commection failed"); + if (DEBUG) Log.d(TAG, "TvViewSession: connection failed"); mTuned = false; mNeedToBeRetuned = false; super.onConnectionFailed(inputId); @@ -546,4 +561,9 @@ public class InputSessionManager { public interface OnTvViewChannelChangeListener { void onTvViewChannelChange(@Nullable Uri channelUri); } + + /** Called when recording session is created or destroyed. */ + public interface OnRecordingSessionChangeListener { + void onRecordingSessionChange(boolean create, int count); + } } diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 58850b5f..ed5f79a1 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -27,14 +27,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Point; import android.hardware.display.DisplayManager; -import android.media.AudioManager; -import android.media.MediaMetadata; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; @@ -44,7 +37,6 @@ import android.media.tv.TvInputManager.TvInputCallback; import android.media.tv.TvTrackInfo; import android.media.tv.TvView.OnUnhandledInputEventListener; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -71,7 +63,6 @@ import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.Toast; -import com.android.tv.analytics.DurationTimer; import com.android.tv.analytics.SendChannelStatusRunnable; import com.android.tv.analytics.SendConfigInfoRunnable; import com.android.tv.analytics.Tracker; @@ -91,26 +82,30 @@ import com.android.tv.data.ProgramDataManager; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dialog.PinDialogFragment; +import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; import com.android.tv.dialog.SafeDismissDialogFragment; -import com.android.tv.dvr.ConflictChecker; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.ConflictChecker; import com.android.tv.dvr.ui.DvrStopRecordingFragment; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; -import com.android.tv.experiments.Experiments; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.menu.Menu; import com.android.tv.onboarding.OnboardingActivity; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; -import com.android.tv.receiver.AudioCapabilitiesReceiver; +import com.android.tv.perf.EventNames; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.StubPerformanceMonitor; +import com.android.tv.perf.TimerEvent; +import com.android.tv.recommendation.ChannelPreviewUpdater; import com.android.tv.recommendation.NotificationService; import com.android.tv.search.ProgramGuideSearchFragment; +import com.android.tv.tuner.TunerInputController; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.setup.TunerSetupActivity; import com.android.tv.tuner.tvinput.TunerTvInputService; -import com.android.tv.ui.AppLayerTvView; import com.android.tv.ui.ChannelBannerView; import com.android.tv.ui.InputBannerView; import com.android.tv.ui.KeypadChannelSwitchView; @@ -128,23 +123,22 @@ import com.android.tv.ui.sidepanel.DisplayModeFragment; import com.android.tv.ui.sidepanel.MultiAudioFragment; import com.android.tv.ui.sidepanel.SettingsFragment; import com.android.tv.ui.sidepanel.SideFragment; +import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment; import com.android.tv.util.AccountHelper; import com.android.tv.util.CaptionSettings; +import com.android.tv.util.Debug; +import com.android.tv.util.DurationTimer; import com.android.tv.util.ImageCache; -import com.android.tv.util.ImageLoader; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.PermissionUtils; -import com.android.tv.util.PipInputManager; -import com.android.tv.util.PipInputManager.PipInput; import com.android.tv.util.RecurringRunner; -import com.android.tv.util.SearchManagerHelper; import com.android.tv.util.SetupUtils; import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.TvSettings; -import com.android.tv.util.TvSettings.PipSound; import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; +import com.android.tv.util.ViewCache; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -159,8 +153,7 @@ import java.util.concurrent.TimeUnit; /** * The main activity for the Live TV app. */ -public class MainActivity extends Activity implements AudioManager.OnAudioFocusChangeListener, - OnActionClickListener { +public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener { private static final String TAG = "MainActivity"; private static final boolean DEBUG = false; @@ -175,18 +168,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private static final boolean USE_BACK_KEY_LONG_PRESS = false; - private static final float AUDIO_MAX_VOLUME = 1.0f; - private static final float AUDIO_MIN_VOLUME = 0.0f; - private static final float AUDIO_DUCKING_VOLUME = 0.3f; private static final float FRAME_RATE_FOR_FILM = 23.976f; private static final float FRAME_RATE_EPSILON = 0.1f; - private static final float MEDIA_SESSION_STOPPED_SPEED = 0.0f; - private static final float MEDIA_SESSION_PLAYING_SPEED = 1.0f; - private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1; - private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 2; private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; // Tracker screen names. @@ -211,49 +197,26 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } + private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter(); + static { + SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); + SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF); + SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_ON); + SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED); + } + private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1; - private static final int REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS = 2; private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id"; - private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession"; - // Change channels with key long press. private static final int CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS = 3000; private static final int CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED = 50; private static final int CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED = 200; private static final int CHANNEL_CHANGE_INITIAL_DELAY_MILLIS = 500; - private static final int FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS = 500; private static final int MSG_CHANNEL_DOWN_PRESSED = 1000; private static final int MSG_CHANNEL_UP_PRESSED = 1001; - private static final int MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE = 1002; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE, - UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, - UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK}) - private @interface ChannelBannerUpdateReason {} - /** - * Updates channel banner because the channel banner is forced to show. - */ - private static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; - /** - * Updates channel banner because of tuning. - */ - private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; - /** - * Updates channel banner because of fast tuning. - */ - private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; - /** - * Updates channel banner because of info updating. - */ - private static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; - /** - * Updates channel banner because the current watched channel is locked or unlocked. - */ - private static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000; @@ -261,12 +224,14 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // Delay 1 second in order not to interrupt the first tune. private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1); + private static final int UNDEFINED_TRACK_INDEX = -1; + private static final long START_UP_TIMER_RESET_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(3); + private AccessibilityManager mAccessibilityManager; private ChannelDataManager mChannelDataManager; private ProgramDataManager mProgramDataManager; private TvInputManagerHelper mTvInputManagerHelper; private ChannelTuner mChannelTuner; - private PipInputManager mPipInputManager; private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this); private TvViewUiManager mTvViewUiManager; private TimeShiftManager mTimeShiftManager; @@ -278,12 +243,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private View mContentView; private TunableTvView mTvView; - private TunableTvView mPipView; private Bundle mTuneParams; - private boolean mChannelBannerHiddenBySideFragment; - // TODO: Move the scene views into TvTransitionManager or TvOverlayManager. - private ChannelBannerView mChannelBannerView; - private KeypadChannelSwitchView mKeypadChannelSwitchView; @Nullable private Uri mInitChannelUri; @Nullable @@ -293,20 +253,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private boolean mShowSelectInputView; private TvInputInfo mInputToSetUp; private final List mMemoryManageables = new ArrayList<>(); - private MediaSession mMediaSession; - private int mNowPlayingCardWidth; - private int mNowPlayingCardHeight; + private MediaSessionWrapper mMediaSessionWrapper; private final MyOnTuneListener mOnTuneListener = new MyOnTuneListener(); private String mInputIdUnderSetup; private boolean mIsSetupActivityCalledByPopup; - private AudioManager mAudioManager; - private int mAudioFocusStatus; + private AudioManagerHelper mAudioManagerHelper; private boolean mTunePending; - private boolean mPipEnabled; - private Channel mPipChannel; - private boolean mPipSwap; - @PipSound private int mPipSound = TvSettings.PIP_SOUND_MAIN; // Default private boolean mDebugNonFullSizeScreen; private boolean mActivityResumed; private boolean mActivityStarted; @@ -316,10 +269,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private boolean mBackKeyPressed; private boolean mNeedShowBackKeyGuide; private boolean mVisibleBehind; - private boolean mAc3PassthroughSupported; private boolean mShowNewSourcesFragment = true; private String mTunerInputId; private boolean mOtherActivityLaunched; + private PerformanceMonitor mPerformanceMonitor; private boolean mIsFilmModeSet; private float mDefaultRefreshRate; @@ -331,11 +284,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private boolean mIsCurrentChannelUnblockedByUser; private boolean mWasChannelUnblockedBeforeShrunkenByUser; private Channel mChannelBeforeShrunkenTvView; - private Channel mPipChannelBeforeShrunkenTvView; private boolean mIsCompletingShrunkenTvView; - // TODO: Need to consider the case that TIS explicitly request PIN code while TV view is - // shrunken. private TvContentRating mLastAllowedRatingForCurrentChannel; private TvContentRating mAllowedRatingBeforeShrunken; @@ -346,42 +296,46 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private static final int MAX_RECENT_CHANNELS = 5; private final ArrayDeque mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS); - private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; private RecurringRunner mSendConfigInfoRecurringRunner; private RecurringRunner mChannelStatusRecurringRunner; - // A caller which started this activity. (e.g. TvSearch) - private String mSource; - private final Handler mHandler = new MainActivityHandler(this); private final Set mOnActionClickListeners = new ArraySet<>(); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); - // We need to stop TvView, when the screen is turned off. If not and TIS uses - // MediaPlayer, a device may not go to the sleep mode and audio can be heard, - // because MediaPlayer keeps playing media by its wake lock. - mScreenOffIntentReceived = true; - markCurrentChannelDuringScreenOff(); - stopAll(true); - } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { - if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); - if (!mActivityResumed && mVisibleBehind) { - // ACTION_SCREEN_ON is usually called after onResume. But, if media is played - // under launcher with requestVisibleBehind(true), onResume will not be called. - // In this case, we need to resume TvView and PipView explicitly. - resumeTvIfNeeded(); - resumePipIfNeeded(); - } - } else if (intent.getAction().equals( - TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)) { - if (DEBUG) Log.d(TAG, "Received parental control settings change"); - checkChannelLockNeeded(mTvView); - checkChannelLockNeeded(mPipView); - applyParentalControlSettings(); + switch (intent.getAction()) { + case Intent.ACTION_SCREEN_OFF: + if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); + // We need to stop TvView, when the screen is turned off. If not and TIS uses + // MediaPlayer, a device may not go to the sleep mode and audio can be heard, + // because MediaPlayer keeps playing media by its wake lock. + mScreenOffIntentReceived = true; + markCurrentChannelDuringScreenOff(); + stopAll(true); + break; + case Intent.ACTION_SCREEN_ON: + if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); + if (!mActivityResumed && mVisibleBehind) { + // ACTION_SCREEN_ON is usually called after onResume. But, if media is + // played under launcher with requestVisibleBehind(true), onResume will + // not be called. In this case, we need to resume TvView explicitly. + resumeTvIfNeeded(); + } + break; + case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED: + if (DEBUG) Log.d(TAG, "Received parental control settings change"); + applyParentalControlSettings(); + checkChannelLockNeeded(mTvView, null); + break; + case Intent.ACTION_TIME_CHANGED: + // Re-tune the current channel to prevent incorrect behavior of trick-play. + // See: b/37393628 + if (mChannelTuner.getCurrentChannel() != null) { + tune(true); + } + break; } } }; @@ -397,8 +351,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } Channel channel = mTvView.getCurrentChannel(); if (channel != null && channel.getId() == channelId) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - updateMediaSession(); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); + mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program); } } }; @@ -407,36 +362,39 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC new ChannelTuner.Listener() { @Override public void onLoadFinished() { + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "MainActivity.mChannelTunerListener.onLoadFinished"); SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable(); if (mActivityResumed) { resumeTvIfNeeded(); - resumePipIfNeeded(); } - mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); + mOverlayManager.onBrowsableChannelsUpdated(); } @Override public void onBrowsableChannelListChanged() { - mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); + mOverlayManager.onBrowsableChannelsUpdated(); } @Override public void onCurrentChannelUnavailable(Channel channel) { - // TODO: handle the case that a channel is suddenly removed from DB. + if (mChannelTuner.moveToAdjacentBrowsableChannel(true)) { + tune(true); + } else { + stopTv("onCurrentChannelUnavailable()", false); + } } @Override - public void onChannelChanged(Channel previousChannel, Channel currentChannel) { - } + public void onChannelChanged(Channel previousChannel, Channel currentChannel) {} }; - private final Runnable mRestoreMainViewRunnable = - new Runnable() { - @Override - public void run() { - restoreMainTvView(); - } - }; + private final Runnable mRestoreMainViewRunnable = new Runnable() { + @Override + public void run() { + restoreMainTvView(); + } + }; private ProgramGuideSearchFragment mSearchFragment; private final TvInputCallback mTvInputCallback = new TvInputCallback() { @@ -456,54 +414,54 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings() .isParentalControlsEnabled(); mTvView.onParentalControlChanged(parentalControlEnabled); - mPipView.onParentalControlChanged(parentalControlEnabled); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately(); + } } @Override protected void onCreate(Bundle savedInstanceState) { + TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer(); + DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER); + if (!startUpDebugTimer.isStarted() + || startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) { + // TvApplication can start by other reason before MainActivty is launched. + // In this case, we restart the timer. + startUpDebugTimer.start(); + } + startUpDebugTimer.log("MainActivity.onCreate"); if (DEBUG) Log.d(TAG,"onCreate()"); TvApplication.setCurrentRunningProcess(this, true); super.onCreate(savedInstanceState); + ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this); + if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + finishAndRemoveTask(); + return; + } + mPerformanceMonitor = applicationSingletons.getPerformanceMonitor(); - boolean isPassthroughInput = TvContract.isChannelUriForPassthroughInput(getIntent() - .getData()); - boolean skipToShowOnboarding = Intent.ACTION_VIEW.equals(getIntent().getAction()) + TvApplication tvApplication = (TvApplication) getApplication(); + mChannelDataManager = tvApplication.getChannelDataManager(); + // In API 23, TvContract.isChannelUriForPassthroughInput is hidden. + boolean isPassthroughInput = + TvContract.isChannelUriForPassthroughInput(getIntent().getData()); + boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction()) && isPassthroughInput; - if (OnboardingUtils.needToShowOnboarding(this) && !skipToShowOnboarding + boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished() + && mChannelDataManager.getChannelCount() <= 0; + if ((OnboardingUtils.isFirstRunWithCurrentVersion(this) + || channelLoadedAndNoChannelAvailable) + && !tuneToPassthroughInput && !TvCommonUtils.isRunningInTest()) { - // TODO: The onboarding is turned off in test, because tests are broken by the - // onboarding. We need to enable the feature for tests later. - startActivity(OnboardingActivity.buildIntent(this, getIntent())); - finish(); + startOnboardingActivity(); return; } - - // Check this permission for the EPG fetch. - // TODO: check {@link shouldShowRequestPermissionRationale}. - // While testing, no way to allow the permission when the dialog shows up. So we'd better - // not show the dialog. - if (!TvCommonUtils.isRunningInTest() && Utils.hasInternalTvInputs(this, true) - && checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, - PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); - } - - DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); - Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - Point size = new Point(); - display.getSize(size); - int screenWidth = size.x; - int screenHeight = size.y; - mDefaultRefreshRate = display.getRefreshRate(); - setContentView(R.layout.activity_tv); - mContentView = findViewById(android.R.id.content); + mProgramDataManager = tvApplication.getProgramDataManager(); + mTvInputManagerHelper = tvApplication.getTvInputManagerHelper(); mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view); - int shrunkenTvViewHeight = getResources().getDimensionPixelSize( - R.dimen.shrunken_tvview_height); - mTvView.initialize((AppLayerTvView) findViewById(R.id.main_tv_view), false, screenHeight, - shrunkenTvViewHeight); + mTvView.initialize(mProgramDataManager, mTvInputManagerHelper); mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() { @Override public boolean onUnhandledInputEvent(InputEvent event) { @@ -526,7 +484,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return false; } }); - long channelId = Utils.getLastWatchedChannelId(this); String inputId = Utils.getLastWatchedTunerInputId(this); if (!isPassthroughInput && inputId != null @@ -534,27 +491,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId)); } - TvApplication tvApplication = (TvApplication) getApplication(); tvApplication.getMainActivityWrapper().onMainActivityCreated(this); if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) { Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show(); } mTracker = tvApplication.getTracker(); - mTvInputManagerHelper = tvApplication.getTvInputManagerHelper(); if (Features.TUNER.isEnabled(this)) { mTvInputManagerHelper.addCallback(mTvInputCallback); } mTunerInputId = TunerTvInputService.getInputId(this); - mChannelDataManager = tvApplication.getChannelDataManager(); - mProgramDataManager = tvApplication.getProgramDataManager(); mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID, mOnCurrentProgramUpdatedListener); mProgramDataManager.setPrefetchEnabled(true); mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper); mChannelTuner.addListener(mChannelTunerListener); mChannelTuner.start(); - mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner); - mPipInputManager.start(); mMemoryManageables.add(mProgramDataManager); mMemoryManageables.add(ImageCache.getInstance()); mMemoryManageables.add(TvContentRatingCache.getInstance()); @@ -565,28 +516,29 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC new OnCurrentProgramUpdatedListener() { @Override public void onCurrentProgramUpdated(long channelId, Program program) { - updateMediaSession(); + mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(), + program); switch (mTimeShiftManager.getLastActionId()) { case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND: case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD: case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS: case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT: - updateChannelBannerAndShowIfNeeded( - UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); break; case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE: case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY: default: - updateChannelBannerAndShowIfNeeded( - UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); break; } } }); - mPipView = (TunableTvView) findViewById(R.id.pip_tunable_tv_view); - mPipView.initialize((AppLayerTvView) findViewById(R.id.pip_tv_view), true, screenHeight, - shrunkenTvViewHeight); + DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + mDefaultRefreshRate = display.getRefreshRate(); if (!PermissionUtils.hasAccessWatchedHistory(this)) { WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager( @@ -594,17 +546,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC watchedHistoryManager.start(); mTvView.setWatchedHistoryManager(watchedHistoryManager); } - mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView, + mTvViewUiManager = new TvViewUiManager(this, mTvView, (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager); - mPipView.setFixedSurfaceSize(screenWidth / 2, screenHeight / 2); - mPipView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW); - + mContentView = findViewById(android.R.id.content); ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container); - mChannelBannerView = (ChannelBannerView) getLayoutInflater().inflate( + ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate( R.layout.channel_banner, sceneContainer, false); - mKeypadChannelSwitchView = (KeypadChannelSwitchView) getLayoutInflater().inflate( - R.layout.keypad_channel_switch, sceneContainer, false); + KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView) + getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false); InputBannerView inputBannerView = (InputBannerView) getLayoutInflater() .inflate(R.layout.input_banner, sceneContainer, false); SelectInputView selectInputView = (SelectInputView) getLayoutInflater() @@ -641,26 +591,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } }); mSearchFragment = new ProgramGuideSearchFragment(); - mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, - mKeypadChannelSwitchView, mChannelBannerView, inputBannerView, + mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager, + keypadChannelSwitchView, channelBannerView, inputBannerView, selectInputView, sceneContainer, mSearchFragment); - mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; - - mMediaSession = new MediaSession(this, MEDIA_SESSION_TAG); - mMediaSession.setCallback(new MediaSession.Callback() { - @Override - public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { - // Consume the media button event here. Should not send it to other apps. - return true; - } - }); - mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | - MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); - mNowPlayingCardWidth = getResources().getDimensionPixelSize( - R.dimen.notif_card_img_max_width); - mNowPlayingCardHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); + mAudioManagerHelper = new AudioManagerHelper(this, mTvView); + mMediaSessionWrapper = new MediaSessionWrapper(this); mTvViewUiManager.restoreDisplayMode(false); if (!handleIntent(getIntent())) { @@ -668,15 +604,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return; } - mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, - new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() { - @Override - public void onAc3PassthroughCapabilityChange(boolean capability) { - mAc3PassthroughSupported = capability; - } - }); - mAudioCapabilitiesReceiver.register(); - mAccessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE); mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1), @@ -692,6 +619,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mDvrConflictChecker = new ConflictChecker(this); } initForTest(); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end"); + mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONCREATE); + } + + private void startOnboardingActivity() { + startActivity(OnboardingActivity.buildIntent(this, getIntent())); + finish(); } @Override @@ -705,32 +639,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - switch (requestCode) { - case PERMISSIONS_REQUEST_READ_TV_LISTINGS: - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Start reload of dependent data - mChannelDataManager.reload(); - mProgramDataManager.reload(); - - // Restart live channels. - Intent intent = getIntent(); - finish(); - startActivity(intent); - } else { - Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, - Toast.LENGTH_LONG).show(); - finish(); - } - break; - case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED - && Experiments.CLOUD_EPG.get()) { - EpgFetcher.getInstance(this).startImmediately(); - } else { - EpgFetcher.getInstance(this).stop(); - } - break; + if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // Start reload of dependent data + mChannelDataManager.reload(); + mProgramDataManager.reload(); + + // Restart live channels. + Intent intent = getIntent(); + finish(); + startActivity(intent); + } else { + Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, + Toast.LENGTH_LONG).show(); + finish(); + } } } @@ -781,6 +704,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC @Override protected void onStart() { + TimerEvent timer = mPerformanceMonitor.startTimer(); if (DEBUG) Log.d(TAG,"onStart()"); super.onStart(); mScreenOffIntentReceived = false; @@ -789,23 +713,25 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mMainDurationTimer.start(); applyParentalControlSettings(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - registerReceiver(mBroadcastReceiver, intentFilter); + registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER); - Intent notificationIntent = new Intent(this, NotificationService.class); - notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); - startService(notificationIntent); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + Intent notificationIntent = new Intent(this, NotificationService.class); + notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); + startService(notificationIntent); + } + TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this); + + EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded(); + mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART); } @Override protected void onResume() { + TimerEvent timer = mPerformanceMonitor.startTimer(); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start"); if (DEBUG) Log.d(TAG, "onResume()"); super.onResume(); - // Refresh the remote config, it is throttled automatically. - TvApplication.getSingletons(this).getRemoteConfig().fetch(null); if (!PermissionUtils.hasAccessAllEpg(this) && checkSelfPermission(PERMISSION_READ_TV_LISTINGS) != PackageManager.PERMISSION_GRANTED) { @@ -819,23 +745,23 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mActivityResumed = true; mShowNewSourcesFragment = true; mOtherActivityLaunched = false; - int result = mAudioManager.requestAudioFocus(MainActivity.this, - AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? - AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; - setVolumeByAudioFocusStatus(); + mAudioManagerHelper.requestAudioFocus(); if (mTvView.isPlaying()) { // Every time onResume() is called the activity will be assumed to not have requested // visible behind. requestVisibleBehind(true); } - if (Utils.hasRecordingFailedReason(getApplicationContext(), - TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)) { + Set failedScheduledRecordingInfoSet = + Utils.getFailedScheduledRecordingInfoSet(getApplicationContext()); + if (Utils.hasRecordingFailedReason( + getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE) + && !failedScheduledRecordingInfoSet.isEmpty()) { runAfterAttachedToWindow(new Runnable() { @Override public void run() { - DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this); + DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this, + failedScheduledRecordingInfoSet); } }); } @@ -843,13 +769,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (mChannelTuner.areAllChannelsLoaded()) { SetupUtils.getInstance(this).markNewChannelsBrowsable(); resumeTvIfNeeded(); - resumePipIfNeeded(); } mOverlayManager.showMenuWithTimeShiftPauseIfNeeded(); - // Note: The following codes are related to pop up an overlay UI after resume. - // When the following code is changed, please check the variable - // willShowOverlayUiAfterResume in updateChannelBannerAndShowIfNeeded. + // NOTE: The following codes are related to pop up an overlay UI after resume. When + // the following code is changed, please modify willShowOverlayUiWhenResume() accordingly. if (mInputToSetUp != null) { startSetupActivity(mInputToSetUp, false); mInputToSetUp = null; @@ -881,6 +805,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (mDvrConflictChecker != null) { mDvrConflictChecker.start(); } + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume end"); + mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONRESUME); } @Override @@ -893,18 +819,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mActivityResumed = false; mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT); mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI); - if (mPipEnabled) { - mTvViewUiManager.hidePipForPause(); - } mBackKeyPressed = false; mShowLockedChannelsTemporarily = false; mShouldTuneToTunerChannel = false; if (!mVisibleBehind) { - mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; - mAudioManager.abandonAudioFocus(this); - if (mMediaSession.isActive()) { - mMediaSession.setActive(false); - } + mAudioManagerHelper.abandonAudioFocus(); + mMediaSessionWrapper.setPlaybackState(false); mTracker.sendScreenView(""); } else { mTracker.sendScreenView(SCREEN_BEHIND_NAME); @@ -933,6 +853,32 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return state; } + @Override + public void onPinChecked(boolean checked, int type, String rating) { + if (checked) { + switch (type) { + case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL: + blockOrUnblockScreen(mTvView, false); + mIsCurrentChannelUnblockedByUser = true; + break; + case PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM: + TvContentRating unblockedRating = TvContentRating.unflattenFromString(rating); + mLastAllowedRatingForCurrentChannel = unblockedRating; + mTvView.unblockContent(unblockedRating); + break; + case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN: + mOverlayManager.getSideFragmentManager() + .show(new ParentalControlsFragment(), false); + // Pass through. + case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN: + mOverlayManager.getSideFragmentManager().showSidePanel(true); + break; + } + } else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) { + mOverlayManager.getSideFragmentManager().hideAll(false); + } + } + private void resumeTvIfNeeded() { if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()"); if (!mTvView.isPlaying() || mInitChannelUri != null @@ -962,21 +908,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mTvView.setBlockScreenType(getDesiredBlockScreenType()); } - private void resumePipIfNeeded() { - if (mPipEnabled && !(mPipView.isPlaying() && mPipView.isShown())) { - if (mPipInputManager.areInSamePipInput( - mChannelTuner.getCurrentChannel(), mPipChannel)) { - enablePipView(false, false); - } else { - if (!mPipView.isPlaying()) { - startPip(false); - } else { - mTvViewUiManager.showPipForResume(); - } - } - } - } - private void startTv(Uri channelUri) { if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri); if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri)) @@ -993,7 +924,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // TV has already started. if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) { // Simply adjust the volume without tune. - setVolumeByAudioFocusStatus(); + mAudioManagerHelper.setVolumeByAudioFocusStatus(); return; } stopTv(); @@ -1027,9 +958,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - mTvView.start(mTvInputManagerHelper); - setVolumeByAudioFocusStatus(); - tune(); + mTvView.start(); + mAudioManagerHelper.setVolumeByAudioFocusStatus(); + tune(true); } @Override @@ -1072,7 +1003,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private void stopAll(boolean keepVisibleBehind) { mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); stopTv("stopAll()", keepVisibleBehind); - stopPip(); } public TvInputManagerHelper getTvInputManagerHelper() { @@ -1127,10 +1057,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC public void startSystemCaptioningSettingsActivity() { Intent intent = new Intent(Settings.ACTION_CAPTIONING_SETTINGS); - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY); try { - startActivityForResultSafe(intent, REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS); + startActivitySafe(intent); } catch (ActivityNotFoundException e) { Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings), Toast.LENGTH_SHORT).show(); @@ -1145,10 +1073,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mProgramDataManager; } - public PipInputManager getPipInputManager() { - return mPipInputManager; - } - public TvOptionsManager getTvOptionsManager() { return mTvOptionsManager; } @@ -1184,13 +1108,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mChannelTuner.getCurrentChannelId(); } - /** - * Returns true if the current connected TV supports AC3 passthough. - */ - public boolean isAc3PassthroughSupported() { - return mAc3PassthroughSupported; - } - /** * Returns the current program which the user is watching right now.

* @@ -1218,8 +1135,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private Channel getBrowsableChannel() { - // TODO: mChannelMap could be dirty for a while when the browsablity of channels - // are changed. In that case, we shouldn't use the value from mChannelMap. Channel curChannel = mChannelTuner.getCurrentChannel(); if (curChannel != null && curChannel.isBrowsable()) { return curChannel; @@ -1254,9 +1169,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // Show ChannelSourcesFragment only if all the channels are loaded. return; } - Channel currentChannel = mChannelTuner.getCurrentChannel(); - long channelId = currentChannel == null ? Channel.INVALID_ID : currentChannel.getId(); - mOverlayManager.getSideFragmentManager().show(new SettingsFragment(channelId)); + mOverlayManager.getSideFragmentManager().show(new SettingsFragment()); } public void showMerchantCollection() { @@ -1272,19 +1185,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel(); mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser; mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel; - - if (willMainViewBeTunerInput && mChannelTuner.isCurrentChannelPassthrough() - && mPipEnabled) { - mPipChannelBeforeShrunkenTvView = mPipChannel; - enablePipView(false, false); - } else { - mPipChannelBeforeShrunkenTvView = null; - } mTvViewUiManager.startShrunkenTvView(); if (showLockedChannelsTemporarily) { mShowLockedChannelsTemporarily = true; - checkChannelLockNeeded(mTvView); + checkChannelLockNeeded(mTvView, null); } mTvView.setBlockScreenType(getDesiredBlockScreenType()); @@ -1320,23 +1225,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mIsCompletingShrunkenTvView = false; mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser; mTvView.setBlockScreenType(getDesiredBlockScreenType()); - if (mPipChannelBeforeShrunkenTvView != null) { - enablePipView(true, false); - mPipChannelBeforeShrunkenTvView = null; - } } }; mTvViewUiManager.fadeOutTvView(tuneAction); // Will automatically fade-in when video becomes available. } else { - checkChannelLockNeeded(mTvView); + checkChannelLockNeeded(mTvView, null); mIsCompletingShrunkenTvView = false; mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser; mTvView.setBlockScreenType(getDesiredBlockScreenType()); - if (mPipChannelBeforeShrunkenTvView != null) { - enablePipView(true, false); - mPipChannelBeforeShrunkenTvView = null; - } } } @@ -1344,37 +1241,41 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView; } + /** + * Returns {@code true} if the tunable tv view is blocked by resource conflict or by parental + * control, otherwise {@code false}. + */ + public boolean isScreenBlockedByResourceConflictOrParentalControl() { + return mTvView.getVideoUnavailableReason() + == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_START_SETUP_ACTIVITY: - if (resultCode == RESULT_OK) { - int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); - String text; - if (count > 0) { - text = getResources().getQuantityString(R.plurals.msg_channel_added, - count, count); - } else { - text = getString(R.string.msg_no_channel_added); - } - Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); - mInputIdUnderSetup = null; - if (mChannelTuner.getCurrentChannel() == null) { - mChannelTuner.moveToAdjacentBrowsableChannel(true); - } - if (mTunePending) { - tune(); - } + if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) { + if (resultCode == RESULT_OK) { + int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); + String text; + if (count > 0) { + text = getResources().getQuantityString(R.plurals.msg_channel_added, + count, count); } else { - mInputIdUnderSetup = null; + text = getString(R.string.msg_no_channel_added); } - if (!mIsSetupActivityCalledByPopup) { - mOverlayManager.getSideFragmentManager().showSidePanel(false); + Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); + mInputIdUnderSetup = null; + if (mChannelTuner.getCurrentChannel() == null) { + mChannelTuner.moveToAdjacentBrowsableChannel(true); } - break; - case REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS: + if (mTunePending) { + tune(true); + } + } else { + mInputIdUnderSetup = null; + } + if (!mIsSetupActivityCalledByPopup) { mOverlayManager.getSideFragmentManager().showSidePanel(false); - break; + } } if (data != null) { String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE); @@ -1418,18 +1319,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC // while gamepads support DPAD_CENTER and BACK by fallback. // Since we don't expect that TIS want to handle gamepad buttons now, // blacklist gamepad buttons and wait for next fallback keys. - // TODO) Need to consider other fallback keys (e.g. ESCAPE) + // TODO: Need to consider other fallback keys (e.g. ESCAPE) return super.dispatchKeyEvent(event); } return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event); } - @Override - public void onAudioFocusChange(int focusChange) { - mAudioFocusStatus = focusChange; - setVolumeByAudioFocusStatus(); - } - /** * Notifies the key input focus is changed to the TV view. */ @@ -1449,18 +1344,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!mTvView.isPlaying()) { mCaptionSettings = new CaptionSettings(this); } - - // Handle the passed key press, if any. Note that only the key codes that are currently - // handled in the TV app will be handled via Intent. - // TODO: Consider defining a separate intent filter as passing data of mime type - // vnd.android.cursor.item/channel isn't really necessary here. - int keyCode = intent.getIntExtra(Utils.EXTRA_KEY_KEYCODE, KeyEvent.KEYCODE_UNKNOWN); - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - if (DEBUG) Log.d(TAG, "Got an intent with keycode: " + keyCode); - KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keyCode); - onKeyUp(keyCode, event); - return true; - } mShouldTuneToTunerChannel = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false); mInitChannelUri = null; @@ -1476,9 +1359,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - // TODO: remove the checkState once N API is finalized. - SoftPreconditions.checkState(TvInputManager.ACTION_SETUP_INPUTS.equals( - "android.media.tv.action.SETUP_INPUTS")); if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) { runAfterAttachedToWindow(new Runnable() { @Override @@ -1488,17 +1368,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC }); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { Uri uri = intent.getData(); - try { - mSource = uri.getQueryParameter(Utils.PARAM_SOURCE); - } catch (UnsupportedOperationException e) { - // ignore this exception. - } - // When the URI points to the programs (directory, not an individual item), go to the - // program guide. The intention here is to respond to - // "content://android.media.tv/program", not "content://android.media.tv/program/XXX". - // Later, we might want to add handling of individual programs too. if (Utils.isProgramsUri(uri)) { - // The given data is a programs URI. Open the Program Guide. + // When the URI points to the programs (directory, not an individual item), go to + // the program guide. The intention here is to respond to + // "content://android.media.tv/program", not + // "content://android.media.tv/program/XXX". + // Later, we might want to add handling of individual programs too. mShowProgramGuide = true; return true; } @@ -1541,17 +1416,19 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } else if (mInitChannelUri != null) { // Handle the URI built by TvContract.buildChannelsUriForInput(). - // TODO: Change hard-coded "input" to TvContract.PARAM_INPUT. String inputId = mInitChannelUri.getQueryParameter("input"); long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId); if (channelId == Channel.INVALID_ID) { String[] projection = { Channels._ID }; + long time = System.currentTimeMillis(); try (Cursor cursor = getContentResolver().query(uri, projection, null, null, null)) { if (cursor != null && cursor.moveToNext()) { channelId = cursor.getLong(0); } } + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity queries DB for " + + "last channel check (" + (System.currentTimeMillis() - time) + "ms)"); } if (channelId == Channel.INVALID_ID) { // Couldn't find any channel probably because the input hasn't been set up. @@ -1567,41 +1444,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return true; } - private void setVolumeByAudioFocusStatus() { - if (mPipSound == TvSettings.PIP_SOUND_MAIN) { - setVolumeByAudioFocusStatus(mTvView); - } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW - setVolumeByAudioFocusStatus(mPipView); - } - } - - private void setVolumeByAudioFocusStatus(TunableTvView tvView) { - SoftPreconditions.checkState(tvView == mTvView || tvView == mPipView); - if (tvView.isPlaying()) { - switch (mAudioFocusStatus) { - case AudioManager.AUDIOFOCUS_GAIN: - tvView.setStreamVolume(AUDIO_MAX_VOLUME); - break; - case AudioManager.AUDIOFOCUS_LOSS: - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - tvView.setStreamVolume(AUDIO_MIN_VOLUME); - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - tvView.setStreamVolume(AUDIO_DUCKING_VOLUME); - break; - } - } - if (tvView == mTvView) { - if (mPipView != null && mPipView.isPlaying()) { - mPipView.setStreamVolume(AUDIO_MIN_VOLUME); - } - } else { // tvView == mPipView - if (mTvView != null && mTvView.isPlaying()) { - mTvView.setStreamVolume(AUDIO_MIN_VOLUME); - } - } - } - private void stopTv() { stopTv(null, false); } @@ -1617,10 +1459,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!keepVisibleBehind) { requestVisibleBehind(false); } - mAudioManager.abandonAudioFocus(this); - if (mMediaSession.isActive()) { - mMediaSession.setActive(false); - } + mAudioManagerHelper.abandonAudioFocus(); + mMediaSessionWrapper.setPlaybackState(false); } TvApplication.getSingletons(this).getMainActivityWrapper() .notifyCurrentChannelChange(this, null); @@ -1628,99 +1468,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mTunePending = false; } - private boolean isPlaying() { - return mTvView.isPlaying() && mTvView.getCurrentChannel() != null; - } - - private void startPip(final boolean fromUserInteraction) { - if (mPipChannel == null) { - Log.w(TAG, "PIP channel id is an invalid id."); - return; - } - if (DEBUG) Log.d(TAG, "startPip() " + mPipChannel); - mPipView.start(mTvInputManagerHelper); - boolean success = mPipView.tuneTo(mPipChannel, null, new OnTuneListener() { - @Override - public void onUnexpectedStop(Channel channel) { - Log.w(TAG, "The PIP is Unexpectedly stopped"); - enablePipView(false, false); - } - - @Override - public void onTuneFailed(Channel channel) { - Log.w(TAG, "Fail to start the PIP during channel tuning"); - if (fromUserInteraction) { - Toast.makeText(MainActivity.this, R.string.msg_no_pip_support, - Toast.LENGTH_SHORT).show(); - enablePipView(false, false); - } - } - - @Override - public void onStreamInfoChanged(StreamInfo info) { - mTvViewUiManager.updatePipView(); - mHandler.removeCallbacks(mRestoreMainViewRunnable); - restoreMainTvView(); - } - - @Override - public void onChannelRetuned(Uri channel) { - if (channel == null) { - return; - } - Channel currentChannel = - mChannelDataManager.getChannel(ContentUris.parseId(channel)); - if (currentChannel == null) { - Log.e(TAG, "onChannelRetuned is called from PIP input but can't find a channel" - + " with the URI " + channel); - return; - } - if (isChannelChangeKeyDownReceived()) { - // Ignore this message if the user is changing the channel. - return; - } - mPipChannel = currentChannel; - mPipView.setCurrentChannel(mPipChannel); - } - - @Override - public void onContentBlocked() { - updateMediaSession(); - } - - @Override - public void onContentAllowed() { - updateMediaSession(); - } - }); - if (!success) { - Log.w(TAG, "Fail to start the PIP"); - return; - } - if (fromUserInteraction) { - checkChannelLockNeeded(mPipView); - } - // Explicitly make the PIP view main to make the selected input an HDMI-CEC active source. - mPipView.setMain(); - scheduleRestoreMainTvView(); - mTvViewUiManager.onPipStart(); - setVolumeByAudioFocusStatus(); - } - private void scheduleRestoreMainTvView() { mHandler.removeCallbacks(mRestoreMainViewRunnable); mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS); } - private void stopPip() { - if (DEBUG) Log.d(TAG, "stopPip"); - if (mPipView.isPlaying()) { - mPipView.stop(); - mPipSwap = false; - mTvViewUiManager.onPipStop(); - } - } - /** * Says {@code text} when accessibility is turned on. */ @@ -1735,7 +1487,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - private void tune() { + private void tune(boolean updateChannelBanner) { if (DEBUG) Log.d(TAG, "tune()"); mTuneDurationTimer.start(); @@ -1748,6 +1500,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } mTunePending = false; final Channel channel = mChannelTuner.getCurrentChannel(); + SoftPreconditions.checkState(channel != null); + if (channel == null) { + return; + } if (!mChannelTuner.isCurrentChannelPassthrough()) { if (mTvInputManagerHelper.getTunerTvInputSize() == 0) { Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show(); @@ -1763,23 +1519,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } if (mChannelDataManager.getChannelCount() > 0) { mOverlayManager.showIntroDialog(); + } else { + startOnboardingActivity(); + return; } } - if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment - && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { - // Show new channel sources fragment. - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { - @Override - public void run() { - mOverlayManager.showNewSourcesFragment(); - } - }); - } - }); - } mShowNewSourcesFragment = false; if (mChannelTuner.getBrowsableChannelCount() == 0 && mChannelDataManager.getChannelCount() > 0 @@ -1791,24 +1535,24 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mOverlayManager.getSideFragmentManager().show( new CustomizeChannelListFragment()); } else { - showSettingsFragment(); + mOverlayManager.showSetupFragment(); } return; } - // TODO: need to refactor the following code to put in startTv. - if (channel == null) { - // There is no channel to tune to. - stopTv("tune()", false); - if (!mChannelDataManager.isDbLoadFinished()) { - // Wait until channel data is loaded in order to know the number of channels. - // tune() will be retried, once the channel data is loaded. - return; - } - if (mOverlayManager.getSideFragmentManager().isActive()) { - return; - } - mOverlayManager.showSetupFragment(); - return; + if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment + && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { + // Show new channel sources fragment. + runAfterAttachedToWindow(new Runnable() { + @Override + public void run() { + mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { + @Override + public void run() { + mOverlayManager.showNewSourcesFragment(); + } + }); + } + }); } setupUtils.onTuned(); if (mTuneParams != null) { @@ -1825,7 +1569,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!isUnderShrunkenTvView()) { mLastAllowedRatingForCurrentChannel = null; } - mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE); // For every tune, we need to inform the tuned channel or input to a user, // if Talkback is turned on. sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ? @@ -1852,15 +1595,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC TvApplication.getSingletons(this).getMainActivityWrapper() .notifyCurrentChannelChange(this, channel); } - checkChannelLockNeeded(mTvView); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); + // We have to provide channel here instead of using TvView's channel, because TvView's + // channel might be null when there's tuner conflict. In that case, TvView will resets + // its current channel onConnectionFailed(). + checkChannelLockNeeded(mTvView, channel); + if (updateChannelBanner) { + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); + } if (mActivityResumed) { // requestVisibleBehind should be called after onResume() is called. But, when // launcher is over the TV app and the screen is turned off and on, tune() can // be called during the pause state by mBroadcastReceiver (Intent.ACTION_SCREEN_ON). requestVisibleBehind(true); } - updateMediaSession(); + mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(), getCurrentProgram()); } // Runs the runnable after the activity is attached to window to show the fragment transition @@ -1895,136 +1644,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - private void updateMediaSession() { - if (getCurrentChannel() == null) { - mMediaSession.setActive(false); - return; - } - - // If the channel is blocked, display a lock and a short text on the Now Playing Card - if (mTvView.isScreenBlocked() || mTvView.getBlockedContentRating() != null) { - setMediaSessionPlaybackState(false); - - Bitmap art = BitmapFactory.decodeResource( - getResources(), R.drawable.ic_message_lock_preview); - updateMediaMetadata( - getResources().getString(R.string.channel_banner_locked_channel_title), art); - mMediaSession.setActive(true); - return; - } - - final Program currentProgram = getCurrentProgram(); - String cardTitleText = null; - String posterArtUri = null; - if (currentProgram != null) { - cardTitleText = currentProgram.getTitle(); - posterArtUri = currentProgram.getPosterArtUri(); - } - if (TextUtils.isEmpty(cardTitleText)) { - cardTitleText = getCurrentChannelName(); - } - updateMediaMetadata(cardTitleText, null); - setMediaSessionPlaybackState(true); - - if (posterArtUri == null) { - posterArtUri = TvContract.buildChannelLogoUri(getCurrentChannelId()).toString(); - } - updatePosterArt(getCurrentChannel(), currentProgram, cardTitleText, null, posterArtUri); - mMediaSession.setActive(true); - } - - private void updatePosterArt(Channel currentChannel, Program currentProgram, - String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) { - if (posterArt != null) { - updateMediaMetadata(cardTitleText, posterArt); - } else if (posterArtUri != null) { - ImageLoader.loadBitmap(this, posterArtUri, mNowPlayingCardWidth, mNowPlayingCardHeight, - new ProgramPosterArtCallback(this, currentChannel, - currentProgram, cardTitleText)); - } else { - updateMediaMetadata(cardTitleText, R.drawable.default_now_card); - } - } - - private static class ProgramPosterArtCallback extends - ImageLoader.ImageLoaderCallback { - private final Channel mChannel; - private final Program mProgram; - private final String mCardTitleText; - - public ProgramPosterArtCallback(MainActivity mainActivity, Channel channel, Program program, - String cardTitleText) { - super(mainActivity); - mChannel = channel; - mProgram = program; - mCardTitleText = cardTitleText; - } - - @Override - public void onBitmapLoaded(MainActivity mainActivity, @Nullable Bitmap posterArt) { - if (mainActivity.isNowPlayingProgram(mChannel, mProgram)) { - mainActivity.updatePosterArt(mChannel, mProgram, mCardTitleText, posterArt, null); - } - } - } - - private boolean isNowPlayingProgram(Channel channel, Program program) { + boolean isNowPlayingProgram(Channel channel, Program program) { return program == null ? (channel != null && getCurrentProgram() == null && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram()); } - private void updateMediaMetadata(final String title, final Bitmap posterArt) { - new AsyncTask () { - @Override - protected Void doInBackground(Void... arg0) { - MediaMetadata.Builder builder = new MediaMetadata.Builder(); - builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); - if (posterArt != null) { - builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); - } - mMediaSession.setMetadata(builder.build()); - return null; - } - }.execute(); - } - - private void updateMediaMetadata(final String title, final int imageResId) { - new AsyncTask () { - @Override - protected Void doInBackground(Void... arg0) { - MediaMetadata.Builder builder = new MediaMetadata.Builder(); - builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); - Bitmap posterArt = BitmapFactory.decodeResource(getResources(), imageResId); - if (posterArt != null) { - builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); - } - mMediaSession.setMetadata(builder.build()); - return null; - } - }.execute(); - } - - private String getCurrentChannelName() { - Channel channel = getCurrentChannel(); - if (channel == null) { - return ""; - } - if (channel.isPassthrough()) { - TvInputInfo input = getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); - return Utils.loadLabel(this, input); - } else { - return channel.getDisplayName(); - } - } - - private void setMediaSessionPlaybackState(boolean isPlaying) { - PlaybackState.Builder builder = new PlaybackState.Builder(); - builder.setState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_STOPPED, - PlaybackState.PLAYBACK_POSITION_UNKNOWN, - isPlaying ? MEDIA_SESSION_PLAYING_SPEED : MEDIA_SESSION_STOPPED_SPEED); - mMediaSession.setPlaybackState(builder.build()); - } - private void addToRecentChannels(long channelId) { if (!mRecentChannels.remove(channelId)) { if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) { @@ -2042,105 +1666,34 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mRecentChannels; } - private void checkChannelLockNeeded(TunableTvView tvView) { - Channel channel = tvView.getCurrentChannel(); - if (tvView.isPlaying() && channel != null) { + private void checkChannelLockNeeded(TunableTvView tvView, Channel currentChannel) { + if (currentChannel == null) { + currentChannel = tvView.getCurrentChannel(); + } + if (tvView.isPlaying() && currentChannel != null) { if (getParentalControlSettings().isParentalControlsEnabled() - && channel.isLocked() + && currentChannel.isLocked() && !mShowLockedChannelsTemporarily && !(isUnderShrunkenTvView() - && channel.equals(mChannelBeforeShrunkenTvView) + && currentChannel.equals(mChannelBeforeShrunkenTvView) && mWasChannelUnblockedBeforeShrunkenByUser)) { - if (DEBUG) Log.d(TAG, "Channel " + channel.getId() + " is locked"); - blockScreen(tvView); + if (DEBUG) Log.d(TAG, "Channel " + currentChannel.getId() + " is locked"); + blockOrUnblockScreen(tvView, true); } else { - unblockScreen(tvView); + blockOrUnblockScreen(tvView, false); } } } - private void blockScreen(TunableTvView tvView) { - tvView.blockScreen(); + private void blockOrUnblockScreen(TunableTvView tvView, boolean blockOrUnblock) { + tvView.blockOrUnblockScreen(blockOrUnblock); if (tvView == mTvView) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); - updateMediaSession(); - } - } - - private void unblockScreen(TunableTvView tvView) { - tvView.unblockScreen(); - if (tvView == mTvView) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); - updateMediaSession(); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + mMediaSessionWrapper.update(blockOrUnblock, getCurrentChannel(), getCurrentProgram()); } } - /** - * Shows the channel banner if it was hidden from the side fragment. - * - *

When the side fragment is visible, showing the channel banner should be put off until the - * side fragment is closed even though the channel changes. - */ - public void showChannelBannerIfHiddenBySideFragment() { - if (mChannelBannerHiddenBySideFragment) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); - } - } - - private void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { - if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); - if (!mChannelTuner.isCurrentChannelPassthrough()) { - int lockType = ChannelBannerView.LOCK_NONE; - if (mTvView.isScreenBlocked()) { - lockType = ChannelBannerView.LOCK_CHANNEL_INFO; - } else if (mTvView.getBlockedContentRating() != null - || (getParentalControlSettings().isParentalControlsEnabled() - && !mTvView.isVideoAvailable())) { - // If the parental control is enabled, do not show the program detail until the - // video becomes available. - lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; - } - if (lockType == ChannelBannerView.LOCK_NONE) { - if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { - // Do not show detailed program information while fast-tuning. - lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; - } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE - && getParentalControlSettings().isParentalControlsEnabled()) { - // If parental control is turned on, - // assumes that program is locked by default and waits for onContentAllowed. - lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; - } - } - // If lock type is not changed, we don't need to update channel banner by parental - // control. - if (!mChannelBannerView.setLockType(lockType) - && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) { - return; - } - - mChannelBannerView.updateViews(mTvView); - } - boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW - || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE - || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); - boolean noOverlayUiWhenResume = - mInputToSetUp == null && !mShowProgramGuide && !mShowSelectInputView; - if (needToShowBanner && noOverlayUiWhenResume - && mOverlayManager.getCurrentDialog() == null - && !mOverlayManager.isSetupFragmentActive() - && !mOverlayManager.isNewSourcesFragmentActive()) { - if (mChannelTuner.getCurrentChannel() == null) { - mChannelBannerHiddenBySideFragment = false; - } else if (mOverlayManager.getSideFragmentManager().isActive()) { - mChannelBannerHiddenBySideFragment = true; - } else { - mChannelBannerHiddenBySideFragment = false; - mOverlayManager.showBanner(); - } - } - updateAvailabilityToast(); - } - /** * Hide the overlays when tuning to a channel from the menu (e.g. Channels). */ @@ -2197,7 +1750,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (bestTrack != null) { String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO); if (!bestTrack.getId().equals(selectedTrack)) { - selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack); + selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack, UNDEFINED_TRACK_INDEX); } else { mTvOptionsManager.onMultiAudioChanged( Utils.getMultiAudioString(this, bestTrack, false)); @@ -2210,7 +1763,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private void applyClosedCaption() { List tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE); if (tracks == null) { - mTvOptionsManager.onClosedCaptionsChanged(null); + mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX); return; } @@ -2219,17 +1772,19 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE); TvTrackInfo alternativeTrack = null; + int alternativeTrackIndex = UNDEFINED_TRACK_INDEX; if (enabled) { String language = mCaptionSettings.getLanguage(); String trackId = mCaptionSettings.getTrackId(); - for (TvTrackInfo track : tracks) { + for (int i = 0; i < tracks.size(); i++) { + TvTrackInfo track = tracks.get(i); if (Utils.isEqualLanguage(track.getLanguage(), language)) { if (track.getId().equals(trackId)) { if (!track.getId().equals(selectedTrackId)) { - selectTrack(TvTrackInfo.TYPE_SUBTITLE, track); + selectTrack(TvTrackInfo.TYPE_SUBTITLE, track, i); } else { // Already selected. Update the option string only. - mTvOptionsManager.onClosedCaptionsChanged(track); + mTvOptionsManager.onClosedCaptionsChanged(track, i); } if (DEBUG) { Log.d(TAG, "Subtitle Track Selected {id=" + track.getId() @@ -2238,14 +1793,16 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return; } else if (alternativeTrack == null) { alternativeTrack = track; + alternativeTrackIndex = i; } } } if (alternativeTrack != null) { if (!alternativeTrack.getId().equals(selectedTrackId)) { - selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack); + selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex); } else { - mTvOptionsManager.onClosedCaptionsChanged(alternativeTrack); + mTvOptionsManager + .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex); } if (DEBUG) { Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId() @@ -2255,29 +1812,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } if (selectedTrackId != null) { - selectTrack(TvTrackInfo.TYPE_SUBTITLE, null); + selectTrack(TvTrackInfo.TYPE_SUBTITLE, null, UNDEFINED_TRACK_INDEX); if (DEBUG) Log.d(TAG, "Subtitle Track Unselected"); return; } - mTvOptionsManager.onClosedCaptionsChanged(null); - } - - /** - * Pops up the KeypadChannelSwitchView with the given key input event. - * - * @param keyCode A key code of the key event. - */ - public void showKeypadChannelSwitchView(int keyCode) { - if (mChannelTuner.areAllChannelsLoaded()) { - mOverlayManager.showKeypadChannelSwitch(); - mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); - } - } - - public void showSearchActivity() { - // HACK: Once we moved the window layer to TYPE_APPLICATION_SUB_PANEL, - // the voice button doesn't work. So we directly call the voice action. - SearchManagerHelper.getInstance(this).launchAssistAction(); + mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX); } public void showProgramGuideSearchFragment() { @@ -2295,13 +1834,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC @Override protected void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy()"); - SideFragment.releasePreloadedRecycledViews(); + Debug.getTimer(Debug.TAG_START_UP_TIMER).reset(); + SideFragment.releaseRecycledViewPool(); + ViewCache.getInstance().clear(); if (mTvView != null) { mTvView.release(); } - if (mPipView != null) { - mPipView.release(); - } if (mChannelTuner != null) { mChannelTuner.removeListener(mChannelTunerListener); mChannelTuner.stop(); @@ -2314,21 +1852,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mProgramDataManager.setPrefetchEnabled(false); } } - if (mPipInputManager != null) { - mPipInputManager.stop(); - } if (mOverlayManager != null) { mOverlayManager.release(); } - if (mKeypadChannelSwitchView != null) { - mKeypadChannelSwitchView.setChannels(null); - } mMemoryManageables.clear(); - if (mMediaSession != null) { - mMediaSession.release(); + if (mMediaSessionWrapper != null) { + mMediaSessionWrapper.release(); } - if (mAudioCapabilitiesReceiver != null) { - mAudioCapabilitiesReceiver.unregister(); + if (mAudioManagerHelper != null) { + mAudioManagerHelper.release(); } mHandler.removeCallbacksAndMessages(null); application.getMainActivityWrapper().onMainActivityDestroyed(this); @@ -2340,8 +1872,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mChannelStatusRecurringRunner.stop(); mChannelStatusRecurringRunner = null; } - if (mTvInputManagerHelper != null && Features.TUNER.isEnabled(this)) { - mTvInputManagerHelper.removeCallback(mTvInputCallback); + if (mTvInputManagerHelper != null) { + mTvInputManagerHelper.clearTvInputLabels(); + if (Features.TUNER.isEnabled(this)) { + mTvInputManagerHelper.removeCallback(mTvInputCallback); + } } super.onDestroy(); } @@ -2410,7 +1945,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC * G debug: refresh cloud epg * I KEYCODE_TV_INPUT * O debug: show display mode option - * P debug: togglePipView * S KEYCODE_CAPTIONS: select subtitle * W debug: toggle screen size * V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec @@ -2422,8 +1956,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC finishChannelChangeIfNeeded(); if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) { - showSearchActivity(); - return true; + // Prevent MainActivity from being closed by onVisibleBehindCanceled() + mOtherActivityLaunched = true; + return false; } switch (mOverlayManager.onKeyUp(keyCode, event)) { case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY: @@ -2467,12 +2002,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } else { if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { - showKeypadChannelSwitchView(keyCode); + mOverlayManager.showKeypadChannelSwitch(keyCode); return true; } switch (keyCode) { case KeyEvent.KEYCODE_DPAD_RIGHT: - if (!mTvView.isVideoAvailable() + if (!mTvView.isVideoOrAudioAvailable() && mTvView.getVideoUnavailableReason() == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) { DvrUiHelper.startSchedulesActivityForTuneConflict(this, @@ -2480,35 +2015,16 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return true; } if (!PermissionUtils.hasModifyParentalControls(this)) { - // TODO: support this feature for non-system LC app. b/23939816 return true; } PinDialogFragment dialog = null; if (mTvView.isScreenBlocked()) { - dialog = new PinDialogFragment( - PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - if (success) { - unblockScreen(mTvView); - mIsCurrentChannelUnblockedByUser = true; - } - } - }); - } else if (mTvView.getBlockedContentRating() != null) { - final TvContentRating rating = mTvView.getBlockedContentRating(); - dialog = new PinDialogFragment( - PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - if (success) { - mLastAllowedRatingForCurrentChannel = rating; - mTvView.unblockContent(rating); - } - } - }); + dialog = PinDialogFragment + .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL); + } else if (mTvView.isContentBlocked()) { + dialog = PinDialogFragment + .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, + mTvView.getBlockedContentRating().flattenToString()); } if (dialog != null) { mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog, @@ -2531,7 +2047,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return true; } if (keyCode != KeyEvent.KEYCODE_MENU) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); } if (keyCode != KeyEvent.KEYCODE_E) { mOverlayManager.showMenu(Menu.REASON_NONE); @@ -2547,6 +2064,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { break; } + // Pass through. case KeyEvent.KEYCODE_CAPTIONS: { mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); return true; @@ -2555,14 +2073,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { break; } + // Pass through. case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); return true; } - case KeyEvent.KEYCODE_GUIDE: { - mOverlayManager.showProgramGuide(); - return true; - } case KeyEvent.KEYCODE_INFO: { mOverlayManager.showBanner(); return true; @@ -2578,22 +2093,17 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC Toast.makeText(this, R.string.dvr_msg_cannot_record_program, Toast.LENGTH_SHORT).show(); } else { - if (!DvrUiHelper.checkStorageStatusAndShowErrorMessage(this, - currentChannel.getInputId())) { - return true; - } Program program = mProgramDataManager .getCurrentProgram(currentChannel.getId()); - if (program == null) { - DvrUiHelper - .showChannelRecordDurationOptions(this, currentChannel); - } else if (DvrUiHelper.handleCreateSchedule(this, program)) { - String msg = getString( - R.string.dvr_msg_current_program_scheduled, - program.getTitle(), Utils.toTimeString( - program.getEndTimeUtcMillis(), false)); - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); - } + DvrUiHelper.checkStorageStatusAndShowErrorMessage(this, + currentChannel.getInputId(), new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingCurrentProgram( + MainActivity.this, + currentChannel, program, false); + } + }); } } else { DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(), @@ -2624,7 +2134,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } if (SystemProperties.USE_DEBUG_KEYS.getValue() || BuildConfig.ENG) { switch (keyCode) { - case KeyEvent.KEYCODE_W: { + case KeyEvent.KEYCODE_W: mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen; if (mDebugNonFullSizeScreen) { FrameLayout.LayoutParams params = @@ -2632,30 +2142,23 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC params.width = 960; params.height = 540; params.gravity = Gravity.START; - mTvView.setLayoutParams(params); + mTvView.setTvViewLayoutParams(params); } else { FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mTvView.getLayoutParams(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = ViewGroup.LayoutParams.MATCH_PARENT; params.gravity = Gravity.CENTER; - mTvView.setLayoutParams(params); + mTvView.setTvViewLayoutParams(params); } return true; - } - case KeyEvent.KEYCODE_P: { - togglePipView(); - return true; - } case KeyEvent.KEYCODE_CTRL_LEFT: - case KeyEvent.KEYCODE_CTRL_RIGHT: { + case KeyEvent.KEYCODE_CTRL_RIGHT: mUseKeycodeBlacklist = !mUseKeycodeBlacklist; return true; - } - case KeyEvent.KEYCODE_O: { + case KeyEvent.KEYCODE_O: mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment()); return true; - } case KeyEvent.KEYCODE_D: mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment()); return true; @@ -2680,22 +2183,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return false; } - @Override - public void onBackPressed() { - // The activity should be returned to the caller of this activity - // when the mSource is not null. - if (!mOverlayManager.getSideFragmentManager().isActive() && isPlaying() - && mSource == null) { - // If back key would exit TV app, - // show McLauncher instead so we can get benefit of McLauncher's shyMode. - Intent startMain = new Intent(Intent.ACTION_MAIN); - startMain.addCategory(Intent.CATEGORY_HOME); - startActivity(startMain); - } else { - super.onBackPressed(); - } - } - @Override public void onUserInteraction() { super.onUserInteraction(); @@ -2725,66 +2212,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } } - public void togglePipView() { - enablePipView(!mPipEnabled, true); - mOverlayManager.getMenu().update(); - } - - public boolean isPipEnabled() { - return mPipEnabled; - } - - public void tuneToChannelForPip(Channel channel) { - if (!mPipEnabled) { - throw new IllegalStateException("tuneToChannelForPip is called when PIP is off"); - } - if (mPipChannel.equals(channel)) { - return; - } - mPipChannel = channel; - startPip(true); - } - - private void enablePipView(boolean enable, boolean fromUserInteraction) { - if (enable == mPipEnabled) { - return; - } - if (enable) { - List pipAvailableInputs = mPipInputManager.getPipInputList(true); - if (pipAvailableInputs.isEmpty()) { - Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT) - .show(); - return; - } - // TODO: choose the last pip input. - Channel pipChannel = pipAvailableInputs.get(0).getChannel(); - if (pipChannel != null) { - mPipEnabled = true; - mPipChannel = pipChannel; - startPip(fromUserInteraction); - mTvViewUiManager.restorePipSize(); - mTvViewUiManager.restorePipLayout(); - mTvOptionsManager.onPipChanged(mPipEnabled); - } else { - Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT) - .show(); - } - } else { - mPipEnabled = false; - mPipChannel = null; - // Recover the stream volume of the main TV view, if needed. - if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) { - setVolumeByAudioFocusStatus(mTvView); - mPipSound = TvSettings.PIP_SOUND_MAIN; - mTvOptionsManager.onPipSoundChanged(mPipSound); - } - stopPip(); - mTvViewUiManager.restoreDisplayMode(false); - mTvOptionsManager.onPipChanged(mPipEnabled); - } - } - - private boolean isChannelChangeKeyDownReceived() { + /** + * Returns {@code true} if one of the channel changing keys are pressed and not released yet. + */ + public boolean isChannelChangeKeyDownReceived() { return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED) || mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED); } @@ -2811,10 +2242,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (SystemProperties.LOG_KEYEVENT.getValue()) { Log.d(TAG, "dispatchKeyEventToSession(" + event + ")"); } - if (mPipEnabled && mChannelTuner.isCurrentChannelPassthrough()) { - // If PIP is enabled, key events will be used by UI. - return false; - } boolean handled = false; if (mTvView != null) { handled = mTvView.dispatchKeyEvent(event); @@ -2832,21 +2259,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private boolean isKeyEventBlocked() { - // If the current channel is passthrough channel without a PIP view, - // we always don't handle the key events in TV activity. Instead, the key event will - // be handled by the passthrough TV input. - return mChannelTuner.isCurrentChannelPassthrough() && !mPipEnabled; + // If the current channel is a passthrough channel, we don't handle the key events in TV + // activity. Instead, the key event will be handled by the passthrough TV input. + return mChannelTuner.isCurrentChannelPassthrough(); } private void tuneToLastWatchedChannelForTunerInput() { if (!mChannelTuner.isCurrentChannelPassthrough()) { return; } - if (mPipEnabled) { - if (!mPipChannel.isPassthrough()) { - enablePipView(false, true); - } - } stopTv(); startTv(null); } @@ -2857,16 +2278,17 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mTvView.reset(); } } else { - if (mPipEnabled && mPipInputManager.areInSamePipInput(channel, mPipChannel)) { - enablePipView(false, true); - } if (!mTvView.isPlaying()) { startTv(channel.getUri()); } else if (channel.equals(mTvView.getCurrentChannel())) { - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); + } else if (channel == mChannelTuner.getCurrentChannel()) { + // Channel banner is already updated in moveToAdjacentChannel + tune(false); } else if (mChannelTuner.moveToChannel(channel)) { // Channel banner would be updated inside of tune. - tune(); + tune(true); } else { showSettingsFragment(); } @@ -2883,107 +2305,25 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC */ private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) { if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) { - updateChannelBannerAndShowIfNeeded(fastTuning ? UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST - : UPDATE_CHANNEL_BANNER_REASON_TUNE); - } - } - - public Channel getPipChannel() { - return mPipChannel; - } - - /** - * Swap the main and the sub screens while in the PIP mode. - */ - public void swapPip() { - if (!mPipEnabled || mTvView == null || mPipView == null) { - Log.e(TAG, "swapPip() - not in PIP"); - mPipSwap = false; - return; - } - - Channel channel = mTvView.getCurrentChannel(); - boolean tvViewBlocked = mTvView.isScreenBlocked(); - boolean pipViewBlocked = mPipView.isScreenBlocked(); - if (channel == null || !mTvView.isPlaying()) { - // If the TV view is not currently playing or its current channel is null, swapping here - // basically means disabling the PIP mode and getting back to the full screen since - // there's no point of keeping a blank PIP screen at the bottom which is not tune-able. - enablePipView(false, true); - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT); - mPipSwap = false; - return; - } - - // Reset the TV view and tune the PIP view to the previous channel of the TV view. - mTvView.reset(); - mPipView.reset(); - Channel oldPipChannel = mPipChannel; - tuneToChannelForPip(channel); - if (tvViewBlocked) { - mPipView.blockScreen(); - } else { - mPipView.unblockScreen(); + mOverlayManager.updateChannelBannerAndShowIfNeeded(fastTuning ? + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST + : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); } - - if (oldPipChannel != null) { - // Tune the TV view to the previous PIP channel. - tuneToChannel(oldPipChannel); - } - if (pipViewBlocked) { - mTvView.blockScreen(); - } else { - mTvView.unblockScreen(); - } - if (mPipSound == TvSettings.PIP_SOUND_MAIN) { - setVolumeByAudioFocusStatus(mTvView); - } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW - setVolumeByAudioFocusStatus(mPipView); - } - mPipSwap = !mPipSwap; - mTvOptionsManager.onPipSwapChanged(mPipSwap); - } - - /** - * Toggle where the sound is coming from when the user is watching the PIP. - */ - public void togglePipSoundMode() { - if (!mPipEnabled || mTvView == null || mPipView == null) { - Log.e(TAG, "togglePipSoundMode() - not in PIP"); - return; - } - if (mPipSound == TvSettings.PIP_SOUND_MAIN) { - setVolumeByAudioFocusStatus(mPipView); - mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW; - } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW - setVolumeByAudioFocusStatus(mTvView); - mPipSound = TvSettings.PIP_SOUND_MAIN; - } - restoreMainTvView(); - mTvOptionsManager.onPipSoundChanged(mPipSound); } /** * Set the main TV view which holds HDMI-CEC active source based on the sound mode */ private void restoreMainTvView() { - if (mPipSound == TvSettings.PIP_SOUND_MAIN) { - mTvView.setMain(); - } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW - mPipView.setMain(); - } + mTvView.setMain(); } @Override public void onVisibleBehindCanceled() { stopTv("onVisibleBehindCanceled()", false); mTracker.sendScreenView(""); - mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; - mAudioManager.abandonAudioFocus(this); - if (mMediaSession.isActive()) { - mMediaSession.setActive(false); - } - stopPip(); + mAudioManagerHelper.abandonAudioFocus(); + mMediaSessionWrapper.setPlaybackState(false); mVisibleBehind = false; if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { // Workaround: in M, onStop is not called, even though it should be called after @@ -3012,13 +2352,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mTvView.getSelectedTrack(type); } - private void selectTrack(int type, TvTrackInfo track) { + private void selectTrack(int type, TvTrackInfo track, int trackIndex) { mTvView.selectTrack(type, track == null ? null : track.getId()); if (type == TvTrackInfo.TYPE_AUDIO) { mTvOptionsManager.onMultiAudioChanged(track == null ? null : Utils.getMultiAudioString(this, track, false)); } else if (type == TvTrackInfo.TYPE_SUBTITLE) { - mTvOptionsManager.onClosedCaptionsChanged(track); + mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex); } } @@ -3073,16 +2413,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private void updateAvailabilityToast() { - updateAvailabilityToast(mTvView); - } - - private void updateAvailabilityToast(StreamInfo info) { - if (info.isVideoAvailable()) { + if (mTvView.isVideoAvailable() + || mTvView.getCurrentChannel() != mChannelTuner.getCurrentChannel()) { return; } - int stringId; - switch (info.getVideoUnavailableReason()) { + switch (mTvView.getVideoUnavailableReason()) { case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED: case TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE: case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: @@ -3092,13 +2428,22 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return; case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: default: - stringId = R.string.msg_channel_unavailable_unknown; + Toast.makeText(this, R.string.msg_channel_unavailable_unknown, + Toast.LENGTH_SHORT).show(); break; } + } - Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show(); + /** + * Returns {@code true} if some overlay UI will be shown when the activity is resumed. + */ + public boolean willShowOverlayUiWhenResume() { + return mInputToSetUp != null || mShowProgramGuide || mShowSelectInputView; } + /** + * Returns the current parental control settings. + */ public ParentalControlSettings getParentalControlSettings() { return mTvInputManagerHelper.getParentalControlSettings(); } @@ -3110,6 +2455,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC return mTvInputManagerHelper.getContentRatingsManager(); } + /** + * Returns the current captioning settings. + */ public CaptionSettings getCaptionSettings() { return mCaptionSettings; } @@ -3163,6 +2511,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (mActivityStarted) { initAnimations(); initSideFragments(); + initMenuItemViews(); } } }, LAZY_INITIALIZATION_DELAY); @@ -3174,7 +2523,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } private void initSideFragments() { - SideFragment.preloadRecycledViews(this); + SideFragment.preloadItemViews(this); + } + + private void initMenuItemViews() { + mOverlayManager.getMenu().preloadItemViews(); } @Override @@ -3207,10 +2560,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC sendMessageDelayed(Message.obtain(msg), getDelay(startTime)); mainActivity.moveToAdjacentChannel(true, true); break; - case MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE: - mainActivity.updateChannelBannerAndShowIfNeeded( - UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - break; } } @@ -3225,14 +2574,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC private class MyOnTuneListener implements OnTuneListener { boolean mUnlockAllowedRatingBeforeShrunken = true; boolean mWasUnderShrunkenTvView; - long mStreamInfoUpdateTimeThresholdMs; Channel mChannel; - public MyOnTuneListener() { } - private void onTune(Channel channel, boolean wasUnderShrukenTvView) { - mStreamInfoUpdateTimeThresholdMs = - System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS; + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune"); mChannel = channel; mWasUnderShrunkenTvView = wasUnderShrukenTvView; } @@ -3249,7 +2594,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (mTvView.isFadedOut()) { mTvView.removeFadeEffect(); } - // TODO: show something to user about this error. + Toast.makeText(MainActivity.this, R.string.msg_channel_unavailable_unknown, + Toast.LENGTH_SHORT).show(); } @Override @@ -3258,29 +2604,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mTracker.sendChannelTuneTime(info.getCurrentChannel(), mTuneDurationTimer.reset()); } - // If updateChannelBanner() is called without delay, the stream info seems flickering - // when the channel is quickly changed. - if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE) - && info.isVideoAvailable()) { - if (System.currentTimeMillis() > mStreamInfoUpdateTimeThresholdMs) { - updateChannelBannerAndShowIfNeeded( - UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - } else { - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE), - mStreamInfoUpdateTimeThresholdMs - System.currentTimeMillis()); - } + if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) { + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO); } - applyDisplayRefreshRate(info.getVideoFrameRate()); - mTvViewUiManager.updateTvView(); + mTvViewUiManager.updateTvAspectRatio(); applyMultiAudio(); applyClosedCaption(); - // TODO: Send command to TIS with checking the settings in TV and CaptionManager. mOverlayManager.getMenu().onStreamInfoChanged(); if (mTvView.isVideoAvailable()) { mTvViewUiManager.fadeInTvView(); } + if (!mTvView.isContentBlocked() && !mTvView.isScreenBlocked()) { + updateAvailabilityToast(); + } mHandler.removeCallbacks(mRestoreMainViewRunnable); restoreMainTvView(); } @@ -3303,11 +2641,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC } mChannelTuner.setCurrentChannel(currentChannel); mTvView.setCurrentChannel(currentChannel); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); } @Override public void onContentBlocked() { + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "MainActivity.MyOnTuneListener.onContentBlocked removes timer"); + Debug.removeTimer(Debug.TAG_START_UP_TIMER); mTuneDurationTimer.reset(); TvContentRating rating = mTvView.getBlockedContentRating(); // When tuneTo was called while TV view was shrunken, if the channel id is the same @@ -3319,9 +2661,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView(); mTvView.unblockContent(rating); } - mChannelBannerView.setBlockingContentRating(rating); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + mOverlayManager.setBlockingContentRating(rating); mTvViewUiManager.fadeInTvView(); + mMediaSessionWrapper.update(true, getCurrentChannel(), getCurrentProgram()); } @Override @@ -3329,8 +2671,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC if (!isUnderShrunkenTvView()) { mUnlockAllowedRatingBeforeShrunken = false; } - mChannelBannerView.setBlockingContentRating(null); - updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + mOverlayManager.setBlockingContentRating(null); + mMediaSessionWrapper.update(false, getCurrentChannel(), getCurrentProgram()); } } } diff --git a/src/com/android/tv/MainActivityWrapper.java b/src/com/android/tv/MainActivityWrapper.java index 01733255..5af5079f 100644 --- a/src/com/android/tv/MainActivityWrapper.java +++ b/src/com/android/tv/MainActivityWrapper.java @@ -61,7 +61,7 @@ public final class MainActivityWrapper { * Unsets the main activity instance. */ public void onMainActivityDestroyed(@NonNull MainActivity activity) { - if (mActivity != activity) { + if (mActivity == activity) { mActivity = null; } } diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java new file mode 100644 index 00000000..da6ad2a4 --- /dev/null +++ b/src/com/android/tv/MediaSessionWrapper.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.media.tv.TvContract; +import android.media.tv.TvInputInfo; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +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.util.Utils; + +/** + * A wrapper class for {@link MediaSession} to support common operations on media sessions for + * {@link MainActivity}. + */ +class MediaSessionWrapper { + private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession"; + private static PlaybackState MEDIA_SESSION_STATE_PLAYING = new PlaybackState.Builder() + .setState(PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f) + .build(); + private static PlaybackState MEDIA_SESSION_STATE_STOPPED = new PlaybackState.Builder() + .setState(PlaybackState.STATE_STOPPED, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f) + .build(); + + private final Context mContext; + private final MediaSession mMediaSession; + private int mNowPlayingCardWidth; + private int mNowPlayingCardHeight; + + MediaSessionWrapper(Context context) { + mContext = context; + mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG); + mMediaSession.setCallback(new MediaSession.Callback() { + @Override + public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { + // Consume the media button event here. Should not send it to other apps. + return true; + } + }); + mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | + MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mNowPlayingCardWidth = mContext.getResources().getDimensionPixelSize( + R.dimen.notif_card_img_max_width); + mNowPlayingCardHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.notif_card_img_height); + } + + /** + * Sets playback state. + * + * @param isPlaying {@code true} if TV is playing, otherwise {@code false}. + */ + void setPlaybackState(boolean isPlaying) { + if (isPlaying) { + mMediaSession.setActive(true); + // setPlaybackState() has to be called after calling setActive(). b/31933276 + mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_PLAYING); + } else if (mMediaSession.isActive()) { + mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_STOPPED); + mMediaSession.setActive(false); + } + } + + /** + * Updates media session according to the current TV playback status. + * + * @param blocked {@code true} if the current channel is blocked, either by user settings or + * the current program's content ratings. + * @param currentChannel The currently playing channel. + * @param currentProgram The currently playing program. + */ + void update(boolean blocked, Channel currentChannel, Program currentProgram) { + if (currentChannel == null) { + setPlaybackState(false); + return; + } + + // If the channel is blocked, display a lock and a short text on the Now Playing Card + if (blocked) { + Bitmap art = BitmapFactory.decodeResource(mContext.getResources(), + R.drawable.ic_message_lock_preview); + updateMediaMetadata(mContext.getResources() + .getString(R.string.channel_banner_locked_channel_title), art); + setPlaybackState(true); + return; + } + + String cardTitleText = null; + String posterArtUri = null; + if (currentProgram != null) { + cardTitleText = currentProgram.getTitle(); + posterArtUri = currentProgram.getPosterArtUri(); + } + if (TextUtils.isEmpty(cardTitleText)) { + cardTitleText = getChannelName(currentChannel); + } + updateMediaMetadata(cardTitleText, null); + if (posterArtUri == null) { + posterArtUri = TvContract.buildChannelLogoUri(currentChannel.getId()).toString(); + } + updatePosterArt(currentChannel, currentProgram, cardTitleText, null, posterArtUri); + setPlaybackState(true); + } + + /** + * Releases the media session. + * + * @see MediaSession#release() + */ + void release() { + mMediaSession.release(); + } + + private String getChannelName(Channel channel) { + if (channel.isPassthrough()) { + TvInputInfo input = TvApplication.getSingletons(mContext).getTvInputManagerHelper() + .getTvInputInfo(channel.getInputId()); + return Utils.loadLabel(mContext, input); + } else { + return channel.getDisplayName(); + } + } + + private void updatePosterArt(Channel currentChannel, Program currentProgram, + String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) { + if (posterArt != null) { + updateMediaMetadata(cardTitleText, posterArt); + } else if (posterArtUri != null) { + ImageLoader.loadBitmap(mContext, posterArtUri, mNowPlayingCardWidth, + mNowPlayingCardHeight, new ProgramPosterArtCallback(this, currentChannel, + currentProgram, cardTitleText)); + } else { + updateMediaMetadata(cardTitleText, R.drawable.default_now_card); + } + } + + private void updateMediaMetadata(final String title, final Bitmap posterArt) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... arg0) { + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); + if (posterArt != null) { + builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); + } + mMediaSession.setMetadata(builder.build()); + return null; + } + }.execute(); + } + + private void updateMediaMetadata(final String title, final int imageResId) { + new AsyncTask () { + @Override + protected Void doInBackground(Void... arg0) { + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putString(MediaMetadata.METADATA_KEY_TITLE, title); + Bitmap posterArt = + BitmapFactory.decodeResource(mContext.getResources(), imageResId); + if (posterArt != null) { + builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); + } + mMediaSession.setMetadata(builder.build()); + return null; + } + }.execute(); + } + + private static class ProgramPosterArtCallback extends + ImageLoader.ImageLoaderCallback { + private final Channel mChannel; + private final Program mProgram; + private final String mCardTitleText; + + ProgramPosterArtCallback(MediaSessionWrapper sessionWrapper, Channel channel, + Program program, String cardTitleText) { + super(sessionWrapper); + mChannel = channel; + mProgram = program; + mCardTitleText = cardTitleText; + } + + @Override + public void onBitmapLoaded(MediaSessionWrapper sessionWrapper, @Nullable Bitmap posterArt) { + if (((MainActivity) sessionWrapper.mContext).isNowPlayingProgram(mChannel, mProgram)) { + sessionWrapper.updatePosterArt(mChannel, mProgram, mCardTitleText, posterArt, null); + } + } + } +} diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java index 8a263a26..f0f54413 100644 --- a/src/com/android/tv/SetupPassthroughActivity.java +++ b/src/com/android/tv/SetupPassthroughActivity.java @@ -18,19 +18,27 @@ package com.android.tv; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.Context; import android.content.Intent; import android.media.tv.TvInputInfo; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.MainThread; import android.util.Log; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonConstants; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ChannelDataManager.Listener; import com.android.tv.data.epg.EpgFetcher; import com.android.tv.experiments.Experiments; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import java.util.concurrent.TimeUnit; + /** * An activity to launch a TV input setup activity. * @@ -42,66 +50,83 @@ public class SetupPassthroughActivity extends Activity { private static final int REQUEST_START_SETUP_ACTIVITY = 200; + private static ScanTimeoutMonitor sScanTimeoutMonitor; + private TvInputInfo mTvInputInfo; private Intent mActivityAfterCompletion; + private boolean mEpgFetcherDuringScan; @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); - Intent intent = getIntent(); - SoftPreconditions.checkState( - intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP)); ApplicationSingletons appSingletons = TvApplication.getSingletons(this); TvInputManagerHelper inputManager = appSingletons.getTvInputManagerHelper(); + Intent intent = getIntent(); String inputId = intent.getStringExtra(TvCommonConstants.EXTRA_INPUT_ID); mTvInputInfo = inputManager.getTvInputInfo(inputId); - if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo); - if (mTvInputInfo == null) { - Log.w(TAG, "There is no input with the ID " + inputId + "."); - finish(); - return; - } - Intent setupIntent = intent.getExtras().getParcelable(TvCommonConstants.EXTRA_SETUP_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."); - finish(); - return; - } - SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName); mActivityAfterCompletion = intent.getParcelableExtra( TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); - if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion); - // If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during - // setupIntent.putExtras(intent.getExtras()). - Bundle extras = intent.getExtras(); - extras.remove(TvCommonConstants.EXTRA_SETUP_INTENT); - setupIntent.putExtras(extras); - try { - startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Can't find activity: " + setupIntent.getComponent()); - finish(); - return; - } - if (Utils.isInternalTvInput(this, mTvInputInfo.getId()) && Experiments.CLOUD_EPG.get()) { - EpgFetcher.getInstance(this).stop(); + boolean needToFetchEpg = Utils.isInternalTvInput(this, mTvInputInfo.getId()) + && Experiments.CLOUD_EPG.get(); + if (needToFetchEpg) { + // In case when the activity is restored, this flag should be restored as well. + mEpgFetcherDuringScan = true; } - } - - @Override - protected void onDestroy() { - if (mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId()) - && Experiments.CLOUD_EPG.get()) { - EpgFetcher.getInstance(this).start(); + if (savedInstanceState == null) { + SoftPreconditions.checkState( + intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP)); + if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo); + if (mTvInputInfo == null) { + Log.w(TAG, "There is no input with the ID " + inputId + "."); + finish(); + return; + } + Intent setupIntent = + intent.getExtras().getParcelable(TvCommonConstants.EXTRA_SETUP_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."); + finish(); + return; + } + SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName); + if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion); + // If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during + // setupIntent.putExtras(intent.getExtras()). + Bundle extras = intent.getExtras(); + extras.remove(TvCommonConstants.EXTRA_SETUP_INTENT); + setupIntent.putExtras(extras); + try { + startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Can't find activity: " + setupIntent.getComponent()); + finish(); + return; + } + if (needToFetchEpg) { + if (sScanTimeoutMonitor == null) { + sScanTimeoutMonitor = new ScanTimeoutMonitor(this); + } + sScanTimeoutMonitor.startMonitoring(); + EpgFetcher.getInstance(this).onChannelScanStarted(); + } } - super.onDestroy(); } @Override public void onActivityResult(int requestCode, final int resultCode, final Intent data) { + if (DEBUG) Log.d(TAG, "onActivityResult"); + if (sScanTimeoutMonitor != null) { + sScanTimeoutMonitor.stopMonitoring(); + } + // Note: It's not guaranteed that this method is always called after scanning. boolean setupComplete = requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK; + // Tells EpgFetcher that channel source setup is finished. + if (mEpgFetcherDuringScan) { + EpgFetcher.getInstance(this).onChannelScanFinished(); + } if (!setupComplete) { setResult(resultCode, data); finish(); @@ -122,4 +147,75 @@ public class SetupPassthroughActivity extends Activity { } }); } + + /** + * Monitors the scan progress and notifies the timeout of the scanning. + * The purpose of this monitor is to call EpgFetcher.onChannelScanFinished() in case when + * SetupPassthroughActivity.onActivityResult() is not called properly. b/36008534 + */ + @MainThread + private static class ScanTimeoutMonitor { + // Set timeout long enough. The message in Sony TV says the scanning takes about 30 minutes. + private static final long SCAN_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(30); + + private final Context mContext; + private final ChannelDataManager mChannelDataManager; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Runnable mScanTimeoutRunnable = new Runnable() { + @Override + public void run() { + Log.w(TAG, "No channels has been added for a while." + + " The scan might have finished unexpectedly."); + onScanTimedOut(); + } + }; + private final Listener mChannelDataManagerListener = new Listener() { + @Override + public void onLoadFinished() { + setupTimer(); + } + + @Override + public void onChannelListUpdated() { + setupTimer(); + } + + @Override + public void onChannelBrowsableChanged() { } + }; + private boolean mStarted; + + private ScanTimeoutMonitor(Context context) { + mContext = context.getApplicationContext(); + mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); + } + + private void startMonitoring() { + if (!mStarted) { + mStarted = true; + mChannelDataManager.addListener(mChannelDataManagerListener); + } + if (mChannelDataManager.isDbLoadFinished()) { + setupTimer(); + } + } + + private void stopMonitoring() { + if (mStarted) { + mStarted = false; + mHandler.removeCallbacks(mScanTimeoutRunnable); + mChannelDataManager.removeListener(mChannelDataManagerListener); + } + } + + private void setupTimer() { + mHandler.removeCallbacks(mScanTimeoutRunnable); + mHandler.postDelayed(mScanTimeoutRunnable, SCAN_TIMEOUT_MS); + } + + private void onScanTimedOut() { + stopMonitoring(); + EpgFetcher.getInstance(mContext).onChannelScanFinished(); + } + } } diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java index 2d6d45c4..70885936 100644 --- a/src/com/android/tv/TimeShiftManager.java +++ b/src/com/android/tv/TimeShiftManager.java @@ -161,7 +161,6 @@ public class TimeShiftManager { @TimeShiftActionId private int mLastActionId = 0; - // TODO: Remove these variables once API level 23 is available. private final Context mContext; private Program mCurrentProgram; @@ -618,6 +617,15 @@ public class TimeShiftManager { + mAvailablityChangedTimeMs); return; } + if (recordStartTimeMs > System.currentTimeMillis()) { + // The time reported by TvInputService might not consistent with system + // clock,, use system's current time instead. + Log.e(TAG, "The start time should not be earlier than the current time, " + + "reset the start time to the system's current time: {" + + "startTime: " + recordStartTimeMs + ", current time: " + + System.currentTimeMillis()); + recordStartTimeMs = System.currentTimeMillis(); + } if (mRecordStartTimeMs == recordStartTimeMs) { return; } @@ -887,10 +895,12 @@ public class TimeShiftManager { } long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION); - boolean needToLoad = addDummyPrograms(fetchStartTimeMs, - endTimeMs + PREFETCH_DURATION_FOR_NEXT); + long fetchEndTimeMs = Utils.ceilTime(endTimeMs + PREFETCH_DURATION_FOR_NEXT, + MAX_DUMMY_PROGRAM_DURATION); + removeOutdatedPrograms(fetchStartTimeMs); + boolean needToLoad = addDummyPrograms(fetchStartTimeMs, fetchEndTimeMs); if (needToLoad) { - Range period = Range.create(fetchStartTimeMs, endTimeMs); + Range period = Range.create(fetchStartTimeMs, fetchEndTimeMs); mProgramLoadQueue.add(period); startTaskIfNeeded(); } @@ -996,6 +1006,12 @@ public class TimeShiftManager { return added; } + private void removeOutdatedPrograms(long startTimeMs) { + while (mPrograms.size() > 0 && mPrograms.get(0).getEndTimeUtcMillis() <= startTimeMs) { + mPrograms.remove(0); + } + } + private void removeDummyPrograms() { for (Iterator it = mPrograms.listIterator(); it.hasNext(); ) { if (!it.next().isValid()) { @@ -1012,7 +1028,7 @@ public class TimeShiftManager { for (int i = 0, j = 0; i < mPrograms.size() && j < loadedPrograms.size(); ++j) { Program loadedProgram = loadedPrograms.get(j); // Skip previous programs. - while (program.getEndTimeUtcMillis() < loadedProgram.getStartTimeUtcMillis()) { + while (program.getEndTimeUtcMillis() <= loadedProgram.getStartTimeUtcMillis()) { // Reached end of mPrograms. if (++i == mPrograms.size()) { return; diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java index 0e18a259..0c7c0fd1 100644 --- a/src/com/android/tv/TvApplication.java +++ b/src/com/android/tv/TvApplication.java @@ -22,16 +22,20 @@ import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.StrictMode; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -49,19 +53,30 @@ import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; import com.android.tv.config.DefaultConfigManager; import com.android.tv.config.RemoteConfig; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.PreviewDataManager; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.epg.EpgFetcher; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManagerImpl; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrRecordingService; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.perf.EventNames; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.StubPerformanceMonitor; +import com.android.tv.perf.TimerEvent; +import com.android.tv.recommendation.ChannelPreviewUpdater; +import com.android.tv.recommendation.RecordedProgramPreviewUpdater; +import com.android.tv.tuner.TunerInputController; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.tuner.util.TunerInputInfoUtils; import com.android.tv.util.AccountHelper; import com.android.tv.util.Clock; +import com.android.tv.util.Debug; +import com.android.tv.util.PermissionUtils; import com.android.tv.util.SetupUtils; import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; @@ -72,17 +87,25 @@ import java.util.List; public class TvApplication extends Application implements ApplicationSingletons { private static final String TAG = "TvApplication"; private static final boolean DEBUG = false; - private RemoteConfig mRemoteConfig; + private static final TimerEvent sAppStartTimer = StubPerformanceMonitor.startBootstrapTimer(); + + /** + * An instance of {@link ApplicationSingletons}. Note that this can be set directly only for the + * test purpose. + */ + @VisibleForTesting + public static ApplicationSingletons sAppSingletons; /** * Broadcast Action: The user has updated LC to a new version that supports tuner input. - * {@link TunerInputController} will recevice this intent to check the existence of tuner - * input when the new version is first launched. + * {@link com.android.tv.tuner.TunerInputController} will recevice this intent to check + * the existence of tuner input when the new version is first launched. */ public static final String ACTION_APPLICATION_FIRST_LAUNCHED = "com.android.tv.action.APPLICATION_FIRST_LAUNCHED"; private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch"; + private RemoteConfig mRemoteConfig; private String mVersionName = ""; private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper(); @@ -92,21 +115,30 @@ public class TvApplication extends Application implements ApplicationSingletons private Tracker mTracker; private TvInputManagerHelper mTvInputManagerHelper; private ChannelDataManager mChannelDataManager; - private ProgramDataManager mProgramDataManager; + private volatile ProgramDataManager mProgramDataManager; + private PreviewDataManager mPreviewDataManager; private DvrManager mDvrManager; private DvrScheduleManager mDvrScheduleManager; private DvrDataManager mDvrDataManager; private DvrStorageStatusManager mDvrStorageStatusManager; private DvrWatchedPositionManager mDvrWatchedPositionManager; + private RecordingScheduler mRecordingScheduler; @Nullable private InputSessionManager mInputSessionManager; private AccountHelper mAccountHelper; // When this variable is null, we don't know in which process TvApplication runs. private Boolean mRunningInMainProcess; + private PerformanceMonitor mPerformanceMonitor; @Override public void onCreate() { super.onCreate(); + if (!PermissionUtils.hasInternet(this)) { + // When an isolated process starts, just skip all the initialization. + return; + } + Debug.getTimer(Debug.TAG_START_UP_TIMER).start(); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start TvApplication.onCreate"); SharedPreferencesUtils.initialize(this, new Runnable() { @Override public void run() { @@ -127,18 +159,15 @@ public class TvApplication extends Application implements ApplicationSingletons } Log.i(TAG, "Starting Live TV " + getVersionName()); - // Only set StrictMode for ENG builds because the build server only produces userdebug // builds. if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) { StrictMode.ThreadPolicy.Builder threadPolicyBuilder = new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog(); StrictMode.VmPolicy.Builder vmPolicyBuilder = - new StrictMode.VmPolicy.Builder().detectAll().penaltyLog(); + new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath(); if (!TvCommonUtils.isRunningInTest()) { threadPolicyBuilder.penaltyDialog(); - // Turn off death penalty for tests b/23355898 - vmPolicyBuilder.penaltyDeath(); } StrictMode.setThreadPolicy(threadPolicyBuilder.build()); StrictMode.setVmPolicy(vmPolicyBuilder.build()); @@ -149,13 +178,16 @@ public class TvApplication extends Application implements ApplicationSingletons mAnalytics = StubAnalytics.getInstance(this); } mTracker = mAnalytics.getDefaultTracker(); - mTvInputManagerHelper = new TvInputManagerHelper(this); - mTvInputManagerHelper.start(); + getTvInputManagerHelper(); // In SetupFragment, transitions are set in the constructor. Because the fragment can be // created in Activity.onCreate() by the framework, SetupAnimationHelper should be // initialized here before Activity.onCreate() is called. SetupAnimationHelper.initialize(this); + + Log.i(TAG, "Started Live TV " + mVersionName); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate"); + getPerformanceMonitor().stopTimer(sAppStartTimer, EventNames.APPLICATION_ONCREATE); } private void setCurrentRunningProcess(boolean isMainProcess) { @@ -163,12 +195,22 @@ public class TvApplication extends Application implements ApplicationSingletons SoftPreconditions.checkState(isMainProcess == mRunningInMainProcess); return; } + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "start TvApplication.setCurrentRunningProcess"); mRunningInMainProcess = isMainProcess; if (CommonFeatures.DVR.isEnabled(this)) { mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess); } + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // Fetch remote config + getRemoteConfig().fetch(null); + return null; + } + }.execute(); if (mRunningInMainProcess) { - mTvInputManagerHelper.addCallback(new TvInputCallback() { + getTvInputManagerHelper().addCallback(new TvInputCallback() { @Override public void onInputAdded(String inputId) { if (Features.TUNER.isEnabled(TvApplication.this) && TextUtils.equals(inputId, @@ -186,15 +228,22 @@ public class TvApplication extends Application implements ApplicationSingletons if (Features.TUNER.isEnabled(this)) { // If the tuner input service is added before the app is started, we need to // handle it here. - TunerInputInfoUtils.updateTunerInputInfo(this); + TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this); } if (CommonFeatures.DVR.isEnabled(this)) { mDvrScheduleManager = new DvrScheduleManager(this); mDvrManager = new DvrManager(this); - //NOTE: DvrRecordingService just keeps running. - DvrRecordingService.startService(this); + mRecordingScheduler = RecordingScheduler.createScheduler(this); + } + EpgFetcher.getInstance(this).startRoutineService(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ChannelPreviewUpdater.getInstance(this).startRoutineService(); + RecordedProgramPreviewUpdater.getInstance(this) + .updatePreviewDataForRecordedPrograms(); } } + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "finish TvApplication.setCurrentRunningProcess"); } private void checkTunerServiceOnFirstLaunch() { @@ -203,7 +252,7 @@ public class TvApplication extends Application implements ApplicationSingletons boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true); if (isFirstLaunch) { if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!"); - sendBroadcast(new Intent(ACTION_APPLICATION_FIRST_LAUNCHED)); + TunerInputController.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false); editor.apply(); @@ -226,6 +275,15 @@ public class TvApplication extends Application implements ApplicationSingletons return mDvrScheduleManager; } + /** + * Returns the {@link RecordingScheduler}. + */ + @Override + @Nullable + public RecordingScheduler getRecordingScheduler() { + return mRecordingScheduler; + } + /** * Returns the {@link DvrWatchedPositionManager}. */ @@ -268,24 +326,55 @@ public class TvApplication extends Application implements ApplicationSingletons @Override public ChannelDataManager getChannelDataManager() { if (mChannelDataManager == null) { - mChannelDataManager = new ChannelDataManager(this, mTvInputManagerHelper); + mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper()); mChannelDataManager.start(); } return mChannelDataManager; } + @Override + public boolean isChannelDataManagerLoadFinished() { + return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished(); + } + /** * Returns {@link ProgramDataManager}. */ @Override public ProgramDataManager getProgramDataManager() { - if (mProgramDataManager == null) { - mProgramDataManager = new ProgramDataManager(this); - mProgramDataManager.start(); + if (mProgramDataManager != null) { + return mProgramDataManager; } + Utils.runInMainThreadAndWait(new Runnable() { + @Override + public void run() { + if (mProgramDataManager == null) { + mProgramDataManager = new ProgramDataManager(TvApplication.this); + mProgramDataManager.start(); + } + } + }); return mProgramDataManager; } + @Override + public boolean isProgramDataManagerCurrentProgramsLoadFinished() { + return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished(); + } + + /** + * Returns {@link PreviewDataManager}. + */ + @TargetApi(Build.VERSION_CODES.O) + @Override + public PreviewDataManager getPreviewDataManager() { + if (mPreviewDataManager == null) { + mPreviewDataManager = new PreviewDataManager(this); + mPreviewDataManager.start(); + } + return mPreviewDataManager; + } + /** * Returns {@link DvrDataManager}. */ @@ -314,6 +403,10 @@ public class TvApplication extends Application implements ApplicationSingletons */ @Override public TvInputManagerHelper getTvInputManagerHelper() { + if (mTvInputManagerHelper == null) { + mTvInputManagerHelper = new TvInputManagerHelper(this); + mTvInputManagerHelper.start(); + } return mTvInputManagerHelper; } @@ -345,6 +438,19 @@ public class TvApplication extends Application implements ApplicationSingletons return mRemoteConfig; } + @Override + public boolean isRunningInMainProcess() { + return mRunningInMainProcess != null && mRunningInMainProcess; + } + + @Override + public PerformanceMonitor getPerformanceMonitor() { + if (mPerformanceMonitor == null) { + mPerformanceMonitor = StubPerformanceMonitor.initialize(this); + } + return mPerformanceMonitor; + } + /** * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in * {@link SelectInputActivity#onDestroy}. @@ -353,6 +459,14 @@ public class TvApplication extends Application implements ApplicationSingletons mSelectInputActivity = activity; } + public void handleGuideKey() { + if (!mMainActivityWrapper.isResumed()) { + startActivity(new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI)); + } else { + mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide(); + } + } + /** * Handles the global key KEYCODE_TV. */ @@ -471,6 +585,7 @@ public class TvApplication extends Application implements ApplicationSingletons if (packageManager.getComponentEnabledSetting(name) != newState) { packageManager.setComponentEnabledSetting(name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0); + Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV."); } SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager); } @@ -479,7 +594,11 @@ public class TvApplication extends Application implements ApplicationSingletons * Returns the @{@link ApplicationSingletons} using the application context. */ public static ApplicationSingletons getSingletons(Context context) { - return (ApplicationSingletons) context.getApplicationContext(); + // No need to be "synchronized" because this doesn't create any instance. + if (sAppSingletons == null) { + sAppSingletons = (ApplicationSingletons) context.getApplicationContext(); + } + return sAppSingletons; } /** @@ -491,6 +610,7 @@ public class TvApplication extends Application implements ApplicationSingletons * specific initializations. */ public static void setCurrentRunningProcess(Context context, boolean isMainProcess) { + // TODO(b/63064354) TvApplication should not have to know if it is "the main process" if (context.getApplicationContext() instanceof TvApplication) { TvApplication tvApplication = (TvApplication) context.getApplicationContext(); tvApplication.setCurrentRunningProcess(isMainProcess); diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java index 7871cbe7..493e039c 100644 --- a/src/com/android/tv/TvOptionsManager.java +++ b/src/com/android/tv/TvOptionsManager.java @@ -18,14 +18,13 @@ package com.android.tv; import android.content.Context; import android.media.tv.TvTrackInfo; +import android.support.annotation.IntDef; import android.util.SparseArray; import com.android.tv.data.DisplayMode; -import com.android.tv.util.TvSettings; -import com.android.tv.util.TvSettings.PipLayout; -import com.android.tv.util.TvSettings.PipSize; -import com.android.tv.util.TvSettings.PipSound; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Locale; /** @@ -33,39 +32,34 @@ import java.util.Locale; * captions and display mode. Can be also used to create MenuAction items to control such options. */ public class TvOptionsManager { + @Retention(RetentionPolicy.SOURCE) + @IntDef({OPTION_CLOSED_CAPTIONS, OPTION_DISPLAY_MODE, OPTION_SYSTEMWIDE_PIP, OPTION_MULTI_AUDIO, + OPTION_MORE_CHANNELS, OPTION_DEVELOPER, OPTION_SETTINGS}) + public @interface OptionType {} public static final int OPTION_CLOSED_CAPTIONS = 0; public static final int OPTION_DISPLAY_MODE = 1; - public static final int OPTION_IN_APP_PIP = 2; - public static final int OPTION_SYSTEMWIDE_PIP = 3; - public static final int OPTION_MULTI_AUDIO = 4; - public static final int OPTION_MORE_CHANNELS = 5; - public static final int OPTION_DEVELOPER = 6; - public static final int OPTION_SETTINGS = 7; - - public static final int OPTION_PIP_INPUT = 100; - public static final int OPTION_PIP_SWAP = 101; - public static final int OPTION_PIP_SOUND = 102; - public static final int OPTION_PIP_LAYOUT = 103 ; - public static final int OPTION_PIP_SIZE = 104; + public static final int OPTION_SYSTEMWIDE_PIP = 2; + public static final int OPTION_MULTI_AUDIO = 3; + public static final int OPTION_MORE_CHANNELS = 4; + public static final int OPTION_DEVELOPER = 5; + public static final int OPTION_SETTINGS = 6; private final Context mContext; private final SparseArray mOptionChangedListeners = new SparseArray<>(); private String mClosedCaptionsLanguage; private int mDisplayMode; - private boolean mPip; private String mMultiAudio; - private String mPipInput; - private boolean mPipSwap; - @PipSound private int mPipSound; - @PipLayout private int mPipLayout; - @PipSize private int mPipSize; public TvOptionsManager(Context context) { mContext = context; } - public String getOptionString(int option) { + /** + * Returns a suitable displayed string for the given option type under current settings. + * @param option the type of option, should be one of {@link OptionType}. + */ + public String getOptionString(@OptionType int option) { switch (option) { case OPTION_CLOSED_CAPTIONS: if (mClosedCaptionsLanguage == null) { @@ -77,101 +71,48 @@ public class TvOptionsManager { .isDisplayModeAvailable(mDisplayMode) ? DisplayMode.getLabel(mDisplayMode, mContext) : DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext); - case OPTION_IN_APP_PIP: - return mContext.getString( - mPip ? R.string.options_item_pip_on : R.string.options_item_pip_off); case OPTION_MULTI_AUDIO: return mMultiAudio; - case OPTION_PIP_INPUT: - return mPipInput; - case OPTION_PIP_SWAP: - return mContext.getString(mPipSwap ? R.string.pip_options_item_swap_on - : R.string.pip_options_item_swap_off); - case OPTION_PIP_SOUND: - if (mPipSound == TvSettings.PIP_SOUND_MAIN) { - return mContext.getString(R.string.pip_options_item_sound_main); - } else if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) { - return mContext.getString(R.string.pip_options_item_sound_pip_window); - } - break; - case OPTION_PIP_LAYOUT: - if (mPipLayout == TvSettings.PIP_LAYOUT_BOTTOM_RIGHT) { - return mContext.getString(R.string.pip_options_item_layout_bottom_right); - } else if (mPipLayout == TvSettings.PIP_LAYOUT_TOP_RIGHT) { - return mContext.getString(R.string.pip_options_item_layout_top_right); - } else if (mPipLayout == TvSettings.PIP_LAYOUT_TOP_LEFT) { - return mContext.getString(R.string.pip_options_item_layout_top_left); - } else if (mPipLayout == TvSettings.PIP_LAYOUT_BOTTOM_LEFT) { - return mContext.getString(R.string.pip_options_item_layout_bottom_left); - } else if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { - return mContext.getString(R.string.pip_options_item_layout_side_by_side); - } - break; - case OPTION_PIP_SIZE: - if (mPipSize == TvSettings.PIP_SIZE_BIG) { - return mContext.getString(R.string.pip_options_item_size_big); - } else if (mPipSize == TvSettings.PIP_SIZE_SMALL) { - return mContext.getString(R.string.pip_options_item_size_small); - } - break; } return ""; } - public void onClosedCaptionsChanged(TvTrackInfo track) { - mClosedCaptionsLanguage = (track == null) ? null - : (track.getLanguage() != null) ? track.getLanguage() - : mContext.getString(R.string.default_language); + /** + * Handles changing selection of closed caption. + */ + public void onClosedCaptionsChanged(TvTrackInfo track, int trackIndex) { + mClosedCaptionsLanguage = (track == null) ? + null : (track.getLanguage() != null) ? track.getLanguage() + : mContext.getString(R.string.closed_caption_unknown_language, trackIndex + 1); notifyOptionChanged(OPTION_CLOSED_CAPTIONS); } + /** + * Handles changing selection of display mode. + */ public void onDisplayModeChanged(int displayMode) { mDisplayMode = displayMode; notifyOptionChanged(OPTION_DISPLAY_MODE); } - public void onPipChanged(boolean pip) { - mPip = pip; - notifyOptionChanged(OPTION_IN_APP_PIP); - } - + /** + * Handles changing selection of multi-audio. + */ public void onMultiAudioChanged(String multiAudio) { mMultiAudio = multiAudio; notifyOptionChanged(OPTION_MULTI_AUDIO); } - public void onPipInputChanged(String pipInput) { - mPipInput = pipInput; - notifyOptionChanged(OPTION_PIP_INPUT); - } - - public void onPipSwapChanged(boolean pipSwap) { - mPipSwap = pipSwap; - notifyOptionChanged(OPTION_PIP_SWAP); - } - - public void onPipSoundChanged(@PipSound int pipSound) { - mPipSound = pipSound; - notifyOptionChanged(OPTION_PIP_SOUND); - } - - public void onPipLayoutChanged(@PipLayout int pipLayout) { - mPipLayout = pipLayout; - notifyOptionChanged(OPTION_PIP_LAYOUT); - } - - public void onPipSizeChanged(@PipSize int pipSize) { - mPipSize = pipSize; - notifyOptionChanged(OPTION_PIP_SIZE); - } - - private void notifyOptionChanged(int option) { + private void notifyOptionChanged(@OptionType int option) { OptionChangedListener listener = mOptionChangedListeners.get(option); if (listener != null) { - listener.onOptionChanged(getOptionString(option)); + listener.onOptionChanged(option, getOptionString(option)); } } + /** + * Sets listeners to changes of the given option type. + */ public void setOptionChangedListener(int option, OptionChangedListener listener) { mOptionChangedListeners.put(option, listener); } @@ -180,6 +121,6 @@ public class TvOptionsManager { * An interface used to monitor option changes. */ public interface OptionChangedListener { - void onOptionChanged(String newOption); + void onOptionChanged(@OptionType int optionType, String newString); } -} +} \ No newline at end of file diff --git a/src/com/android/tv/analytics/DurationTimer.java b/src/com/android/tv/analytics/DurationTimer.java deleted file mode 100644 index ad2d91f8..00000000 --- a/src/com/android/tv/analytics/DurationTimer.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.analytics; - -import android.os.SystemClock; - -/** - * Times a duration. - */ -public final class DurationTimer { - public static final long TIME_NOT_SET = -1; - - private long startTimeMs = TIME_NOT_SET; - - /** - * Returns true if the timer is running. - */ - public boolean isRunning() { - return startTimeMs != TIME_NOT_SET; - } - - /** - * Start the timer. - */ - public void start() { - startTimeMs = SystemClock.elapsedRealtime(); - } - - /** - * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not - * running. - */ - public long getDuration() { - return isRunning() ? SystemClock.elapsedRealtime() - startTimeMs : TIME_NOT_SET; - } - - /** - * Stops the timer and resets its value to {@link #TIME_NOT_SET}. - * - * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not - * running. - */ - public long reset() { - long duration = getDuration(); - startTimeMs = TIME_NOT_SET; - return duration; - } -} diff --git a/src/com/android/tv/config/DefaultConfigManager.java b/src/com/android/tv/config/DefaultConfigManager.java index f5a6e959..bbabc6d4 100644 --- a/src/com/android/tv/config/DefaultConfigManager.java +++ b/src/com/android/tv/config/DefaultConfigManager.java @@ -22,6 +22,7 @@ import android.content.Context; * Stub Remote Config. */ public class DefaultConfigManager { + public static final long DEFAULT_LONG_VALUE = 0; public static DefaultConfigManager createInstance(Context context) { return new DefaultConfigManager(); } @@ -47,6 +48,11 @@ public class DefaultConfigManager { public boolean getBoolean(String key) { return false; } + + @Override + public long getLong(String key) { + return DEFAULT_LONG_VALUE; + } } } diff --git a/src/com/android/tv/config/RemoteConfig.java b/src/com/android/tv/config/RemoteConfig.java index 0f7d2c53..f7ae87e7 100644 --- a/src/com/android/tv/config/RemoteConfig.java +++ b/src/com/android/tv/config/RemoteConfig.java @@ -45,4 +45,7 @@ public interface RemoteConfig { * Gets value as a boolean corresponding to the specified key. */ boolean getBoolean(String key); + + /** Gets value as a long corresponding to the specified key. */ + long getLong(String key); } diff --git a/src/com/android/tv/config/RemoteConfigUtils.java b/src/com/android/tv/config/RemoteConfigUtils.java new file mode 100644 index 00000000..09d85239 --- /dev/null +++ b/src/com/android/tv/config/RemoteConfigUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.config; + +import android.content.Context; +import android.util.Log; +import com.android.tv.TvApplication; + +/** A utility class to get the remote config. */ +public class RemoteConfigUtils { + private static final String TAG = "RemoteConfigUtils"; + private static final boolean DEBUG = false; + + private RemoteConfigUtils() {} + + public static long getRemoteConfig(Context context, String key, long defaultValue) { + RemoteConfig remoteConfig = TvApplication.getSingletons(context).getRemoteConfig(); + try { + long remoteValue = remoteConfig.getLong(key); + if (DEBUG) Log.d(TAG, "Got " + key + " from remote: " + remoteValue); + return remoteValue; + } catch (Exception e) { + Log.w(TAG, "Cannot get " + key + " from RemoteConfig", e); + } + if (DEBUG) Log.d(TAG, "Use default value " + defaultValue); + return defaultValue; + } +} diff --git a/src/com/android/tv/customization/TvCustomizationManager.java b/src/com/android/tv/customization/TvCustomizationManager.java index 22298a10..ed6b98ca 100644 --- a/src/com/android/tv/customization/TvCustomizationManager.java +++ b/src/com/android/tv/customization/TvCustomizationManager.java @@ -18,15 +18,18 @@ package com.android.tv.customization; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.support.annotation.IntDef; import android.text.TextUtils; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -37,6 +40,10 @@ public class TvCustomizationManager { private static final String TAG = "TvCustomizationManager"; private static final boolean DEBUG = false; + private static final String[] CUSTOMIZE_PERMISSIONS = { + "com.android.tv.permission.CUSTOMIZE_TV_APP" + }; + private static final String CATEGORY_TV_CUSTOMIZATION = "com.android.tv.category"; @@ -47,6 +54,19 @@ public class TvCustomizationManager { public static final String ID_OPTIONS_ROW = "options_row"; public static final String ID_PARTNER_ROW = "partner_row"; + @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface TRICKPLAY_MODE {} + public static final int TRICKPLAY_MODE_ENABLED = 0; + public static final int TRICKPLAY_MODE_DISABLED = 1; + public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2; + + private static final String[] TRICKPLAY_MODE_STRINGS = { + "enabled", + "disabled", + "use_external_storage_only" + }; + private static final HashMap INTENT_CATEGORY_TO_ROW_ID; static { INTENT_CATEGORY_TO_ROW_ID = new HashMap<>(); @@ -55,12 +75,19 @@ public class TvCustomizationManager { } private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title"; + private static final String RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER = + "has_linux_dvb_built_in_tuner"; + private static final String RES_ID_TRICKPLAY_MODE = "trickplay_mode"; private static final String RES_TYPE_STRING = "string"; + private static final String RES_TYPE_BOOLEAN = "bool"; + + private static String sCustomizationPackage; + private static Boolean sHasLinuxDvbBuiltInTuner; + private static @TRICKPLAY_MODE Integer sTrickplayMode; private final Context mContext; private boolean mInitialized; - private String mCustomizationPackage; private String mPartnerRowTitle; private final Map> mRowIdToCustomActionsMap = new HashMap<>(); @@ -70,6 +97,68 @@ public class TvCustomizationManager { mInitialized = false; } + /** + * Returns {@code true} if there's a customization package installed and it specifies built-in + * tuner devices are available. The built-in tuner should support DVB API to be recognized by + * Live TV. + */ + public static boolean hasLinuxDvbBuiltInTuner(Context context) { + if (sHasLinuxDvbBuiltInTuner == null) { + if (TextUtils.isEmpty(getCustomizationPackageName(context))) { + sHasLinuxDvbBuiltInTuner = false; + } else { + try { + Resources res = context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = res.getIdentifier(RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, + RES_TYPE_BOOLEAN, sCustomizationPackage); + sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId); + } catch (NameNotFoundException e) { + sHasLinuxDvbBuiltInTuner = false; + } + } + } + return sHasLinuxDvbBuiltInTuner; + } + + public static @TRICKPLAY_MODE int getTrickplayMode(Context context) { + if (sTrickplayMode == null) { + if (TextUtils.isEmpty(getCustomizationPackageName(context))) { + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + } else { + try { + String customization = null; + Resources res = context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = res.getIdentifier(RES_ID_TRICKPLAY_MODE, + RES_TYPE_STRING, sCustomizationPackage); + customization = resId == 0 ? null : res.getString(resId); + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + if (customization != null) { + for (int i = 0; i < TRICKPLAY_MODE_STRINGS.length; ++i) { + if (TRICKPLAY_MODE_STRINGS[i].equalsIgnoreCase(customization)) { + sTrickplayMode = i; + break; + } + } + } + } catch (NameNotFoundException e) { + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + } + } + } + return sTrickplayMode; + } + + private static String getCustomizationPackageName(Context context) { + if (sCustomizationPackage == null) { + List packageInfos = context.getPackageManager() + .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); + sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName; + } + return sCustomizationPackage; + } + /** * Initialize TV customization options. * Run this API only on the main thread. @@ -79,14 +168,13 @@ public class TvCustomizationManager { return; } mInitialized = true; - buildCustomActions(); - if (!TextUtils.isEmpty(mCustomizationPackage)) { + if (!TextUtils.isEmpty(getCustomizationPackageName(mContext))) { + buildCustomActions(); buildPartnerRow(); } } private void buildCustomActions() { - mCustomizationPackage = null; mRowIdToCustomActionsMap.clear(); PackageManager pm = mContext.getPackageManager(); for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) { @@ -98,16 +186,8 @@ public class TvCustomizationManager { | PackageManager.GET_META_DATA); for (ResolveInfo info : activities) { String packageName = info.activityInfo.packageName; - if (TextUtils.isEmpty(mCustomizationPackage)) { - if (DEBUG) Log.d(TAG, "Found TV customization package " + packageName); - if ((info.activityInfo.applicationInfo.flags - & ApplicationInfo.FLAG_SYSTEM) == 0) { - Log.w(TAG, "Only system app can customize TV. Ignoring " + packageName); - continue; - } - mCustomizationPackage = packageName; - } else if (!packageName.equals(mCustomizationPackage)) { - Log.w(TAG, "A customization package " + mCustomizationPackage + if (!TextUtils.equals(packageName, sCustomizationPackage)) { + Log.w(TAG, "A customization package " + sCustomizationPackage + " already exist. Ignoring " + packageName); continue; } @@ -117,7 +197,7 @@ public class TvCustomizationManager { Drawable drawable = info.loadIcon(pm); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(intentCategory); - intent.setClassName(mCustomizationPackage, info.activityInfo.name); + intent.setClassName(sCustomizationPackage, info.activityInfo.name); String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory); List actions = mRowIdToCustomActionsMap.get(rowId); @@ -159,13 +239,13 @@ public class TvCustomizationManager { Resources res; try { res = mContext.getPackageManager() - .getResourcesForApplication(mCustomizationPackage); + .getResourcesForApplication(sCustomizationPackage); } catch (NameNotFoundException e) { - Log.w(TAG, "Could not get resources for package " + mCustomizationPackage); + Log.w(TAG, "Could not get resources for package " + sCustomizationPackage); return; } int resId = res.getIdentifier( - RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, mCustomizationPackage); + RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); if (resId != 0) { mPartnerRowTitle = res.getString(resId); } diff --git a/src/com/android/tv/data/BaseProgram.java b/src/com/android/tv/data/BaseProgram.java index f420de02..4e36c80a 100644 --- a/src/com/android/tv/data/BaseProgram.java +++ b/src/com/android/tv/data/BaseProgram.java @@ -17,12 +17,17 @@ package com.android.tv.data; import android.content.Context; +import android.media.tv.TvContentRating; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.android.tv.R; import java.util.Comparator; /** * Base class for {@link com.android.tv.data.Program} and - * {@link com.android.tv.dvr.RecordedProgram}. + * {@link com.android.tv.dvr.data.RecordedProgram}. */ public abstract class BaseProgram { /** @@ -94,14 +99,29 @@ public abstract class BaseProgram { abstract public String getTitle(); /** - * Returns the program's title withe its season and episode number. + * Returns the episode title. */ - abstract public String getTitleWithEpisodeNumber(Context context); + abstract public String getEpisodeTitle(); /** * Returns the displayed title of the program episode. */ - abstract public String getEpisodeDisplayTitle(Context context); + public String getEpisodeDisplayTitle(Context context) { + if (!TextUtils.isEmpty(getEpisodeNumber())) { + String episodeTitle = getEpisodeTitle() == null ? "" : getEpisodeTitle(); + if (TextUtils.equals(getSeasonNumber(), "0")) { + // Do not show "S0: ". + return String.format(context.getResources().getString( + R.string.display_episode_title_format_no_season_number), + getEpisodeNumber(), episodeTitle); + } else { + return String.format(context.getResources().getString( + R.string.display_episode_title_format), + getSeasonNumber(), getEpisodeNumber(), episodeTitle); + } + } + return getEpisodeTitle(); + } /** * Returns the description of the program. @@ -158,6 +178,10 @@ public abstract class BaseProgram { */ abstract public int[] getCanonicalGenreIds(); + /** Returns the array of content ratings. */ + @Nullable + abstract public TvContentRating[] getContentRatings(); + /** * Returns channel's ID of the program. */ @@ -168,6 +192,13 @@ public abstract class BaseProgram { */ abstract public boolean isValid(); + /** + * Checks whether the program is episodic or not. + */ + public boolean isEpisodic() { + return getSeriesId() != null; + } + /** * Generates the series ID for the other inputs than the tuner TV input. */ diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java index 30f84236..4a391ae7 100644 --- a/src/com/android/tv/data/Channel.java +++ b/src/com/android/tv/data/Channel.java @@ -51,6 +51,16 @@ public final class Channel { public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2; public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3; + /** + * Compares the channel numbers of channels which belong to the same input. + */ + public static final Comparator CHANNEL_NUMBER_COMPARATOR = new Comparator() { + @Override + public int compare(Channel lhs, Channel rhs) { + return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); + } + }; + /** * 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. @@ -81,14 +91,21 @@ public final class Channel { TvContract.Channels.COLUMN_DESCRIPTION, TvContract.Channels.COLUMN_VIDEO_FORMAT, TvContract.Channels.COLUMN_BROWSABLE, + TvContract.Channels.COLUMN_SEARCHABLE, TvContract.Channels.COLUMN_LOCKED, TvContract.Channels.COLUMN_APP_LINK_TEXT, TvContract.Channels.COLUMN_APP_LINK_COLOR, TvContract.Channels.COLUMN_APP_LINK_ICON_URI, TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI, TvContract.Channels.COLUMN_APP_LINK_INTENT_URI, + 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. * @@ -103,28 +120,41 @@ public final class Channel { channel.mPackageName = Utils.intern(cursor.getString(index++)); channel.mInputId = Utils.intern(cursor.getString(index++)); channel.mType = Utils.intern(cursor.getString(index++)); - channel.mDisplayNumber = cursor.getString(index++); + channel.mDisplayNumber = normalizeDisplayNumber(cursor.getString(index++)); channel.mDisplayName = cursor.getString(index++); channel.mDescription = cursor.getString(index++); channel.mVideoFormat = Utils.intern(cursor.getString(index++)); channel.mBrowsable = cursor.getInt(index++) == 1; + channel.mSearchable = cursor.getInt(index++) == 1; channel.mLocked = cursor.getInt(index++) == 1; channel.mAppLinkText = cursor.getString(index++); channel.mAppLinkColor = cursor.getInt(index++); channel.mAppLinkIconUri = cursor.getString(index++); channel.mAppLinkPosterArtUri = cursor.getString(index++); channel.mAppLinkIntentUri = cursor.getString(index++); + if (Utils.isBundledInput(channel.mInputId)) { + channel.mRecordingProhibited = cursor.getInt(index++) != 0; + } return channel; } /** - * Creates a {@link Channel} object from the DVR database. + * Replaces the channel number separator with dash('-'). */ - public static Channel fromDvrCursor(Cursor c) { - Channel channel = new Channel(); - int index = -1; - channel.mDvrId = c.getLong(++index); - return channel; + public static String normalizeDisplayNumber(String string) { + if (!TextUtils.isEmpty(string)) { + int length = string.length(); + for (int i = 0; i < length; i++) { + char c = string.charAt(i); + if (c == '.' || Character.isWhitespace(c) + || Character.getType(c) == Character.DASH_PUNCTUATION) { + StringBuilder sb = new StringBuilder(string); + sb.setCharAt(i, CHANNEL_NUMBER_DELIMITER); + return sb.toString(); + } + } + } + return string; } /** ID of this channel. Matches to BaseColumns._ID. */ @@ -138,6 +168,7 @@ public final class Channel { private String mDescription; private String mVideoFormat; private boolean mBrowsable; + private boolean mSearchable; private boolean mLocked; private boolean mIsPassthrough; private String mAppLinkText; @@ -147,8 +178,10 @@ public final class Channel { private String mAppLinkIntentUri; private Intent mAppLinkIntent; private int mAppLinkType; + private String mLogoUri; + private boolean mRecordingProhibited; - private long mDvrId; + private boolean mChannelLogoExist; private Channel() { // Do nothing. @@ -187,7 +220,6 @@ public final class Channel { return mDisplayName; } - @VisibleForTesting public String getDescription() { return mDescription; } @@ -230,10 +262,14 @@ public final class Channel { } /** - * Returns an ID in DVR database. + * Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. */ - public long getDvrId() { - return mDvrId; + public String getLogoUri() { + return mLogoUri; + } + + public boolean isRecordingProhibited() { + return mRecordingProhibited; } /** @@ -266,6 +302,11 @@ public final class Channel { return mBrowsable; } + /** Checks whether this channel is searchable or not. */ + public boolean isSearchable() { + return mSearchable; + } + public boolean isLocked() { return mLocked; } @@ -278,6 +319,13 @@ public final class Channel { mLocked = locked; } + /** + * Sets channel logo uri which is got from cloud. + */ + public void setLogoUri(String logoUri) { + mLogoUri = logoUri; + } + /** * Check whether {@code other} has same read-only channel info as this. But, it cannot check two * channels have same logos. It also excludes browsable and locked, because two fields are @@ -298,7 +346,8 @@ public final class Channel { && mAppLinkColor == other.mAppLinkColor && Objects.equals(mAppLinkIconUri, other.mAppLinkIconUri) && Objects.equals(mAppLinkPosterArtUri, other.mAppLinkPosterArtUri) - && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri); + && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri) + && Objects.equals(mRecordingProhibited, other.mRecordingProhibited); } @Override @@ -314,8 +363,10 @@ public final class Channel { + ", videoFormat=" + mVideoFormat + ", isPassthrough=" + mIsPassthrough + ", browsable=" + mBrowsable + + ", searchable=" + mSearchable + ", locked=" + mLocked - + ", appLinkText=" + mAppLinkText + "}"; + + ", appLinkText=" + mAppLinkText + + ", recordingProhibited=" + mRecordingProhibited + "}"; } void copyFrom(Channel other) { @@ -332,6 +383,7 @@ public final class Channel { mVideoFormat = other.mVideoFormat; mIsPassthrough = other.mIsPassthrough; mBrowsable = other.mBrowsable; + mSearchable = other.mSearchable; mLocked = other.mLocked; mAppLinkText = other.mAppLinkText; mAppLinkColor = other.mAppLinkColor; @@ -340,6 +392,8 @@ public final class Channel { mAppLinkIntentUri = other.mAppLinkIntentUri; mAppLinkIntent = other.mAppLinkIntent; mAppLinkType = other.mAppLinkType; + mRecordingProhibited = other.mRecordingProhibited; + mChannelLogoExist = other.mChannelLogoExist; } /** @@ -389,8 +443,7 @@ public final class Channel { mChannel.mDisplayName = "name"; mChannel.mDescription = "description"; mChannel.mBrowsable = true; - mChannel.mLocked = false; - mChannel.mIsPassthrough = false; + mChannel.mSearchable = true; } public Builder(Channel other) { @@ -422,7 +475,7 @@ public final class Channel { @VisibleForTesting public Builder setDisplayNumber(String displayNumber) { - mChannel.mDisplayNumber = displayNumber; + mChannel.mDisplayNumber = normalizeDisplayNumber(displayNumber); return this; } @@ -448,6 +501,11 @@ public final class Channel { return this; } + public Builder setSearchable(boolean searchable) { + mChannel.mSearchable = searchable; + return this; + } + public Builder setLocked(boolean locked) { mChannel.mLocked = locked; return this; @@ -485,6 +543,11 @@ public final class Channel { return this; } + public Builder setRecordingProhibited(boolean recordingProhibited) { + mChannel.mRecordingProhibited = recordingProhibited; + return this; + } + public Channel build() { Channel channel = new Channel(); channel.copyFrom(mChannel); @@ -523,6 +586,21 @@ public final class Channel { ImageLoader.loadBitmap(context, uriString, maxWidth, maxHeight, callback); } + /** + * Sets if the channel logo exists. This method should be only called from + * {@link ChannelDataManager}. + */ + void setChannelLogoExist(boolean exist) { + mChannelLogoExist = exist; + } + + /** + * Returns if channel logo exists. + */ + public boolean channelLogoExists() { + return mChannelLogoExist; + } + /** * 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 @@ -655,4 +733,4 @@ public final class Channel { return label; } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java index 6f9ea6d7..6f93fbd1 100644 --- a/src/com/android/tv/data/ChannelDataManager.java +++ b/src/com/android/tv/data/ChannelDataManager.java @@ -21,13 +21,17 @@ import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.content.res.AssetFileDescriptor; import android.database.ContentObserver; +import android.database.sqlite.SQLiteException; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.media.tv.TvInputManager.TvInputCallback; +import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.support.annotation.AnyThread; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; @@ -43,6 +47,7 @@ import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -59,7 +64,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * This class is not thread-safe and under an assumption that its public methods are called in * only the main thread. */ -@MainThread +@AnyThread public class ChannelDataManager { private static final String TAG = "ChannelDataManager"; private static final boolean DEBUG = false; @@ -74,10 +79,10 @@ public class ChannelDataManager { private final List mPostRunnablesAfterChannelUpdate = new ArrayList<>(); private final Set mListeners = new CopyOnWriteArraySet<>(); - private final Map mChannelWrapperMap = new HashMap<>(); - private final Map mChannelCountMap = new HashMap<>(); + // Use container class to support multi-thread safety. This value can be set only on the main + // thread. + volatile private UnmodifiableChannelData mData = new UnmodifiableChannelData(); private final Channel.DefaultComparator mChannelComparator; - private final List mChannels = new ArrayList<>(); private final Handler mHandler; private final Set mBrowsableUpdateChannelIds = new HashSet<>(); @@ -92,15 +97,17 @@ public class ChannelDataManager { @Override public void onInputAdded(String inputId) { boolean channelAdded = false; - for (ChannelWrapper channel : mChannelWrapperMap.values()) { + ChannelData data = new ChannelData(mData); + for (ChannelWrapper channel : mData.channelWrapperMap.values()) { if (channel.mChannel.getInputId().equals(inputId)) { channel.mInputRemoved = false; - addChannel(channel.mChannel); + addChannel(data, channel.mChannel); channelAdded = true; } } if (channelAdded) { - Collections.sort(mChannels, mChannelComparator); + Collections.sort(data.channels, mChannelComparator); + mData = new UnmodifiableChannelData(data); notifyChannelListUpdated(); } } @@ -109,7 +116,7 @@ public class ChannelDataManager { public void onInputRemoved(String inputId) { boolean channelRemoved = false; ArrayList removedChannels = new ArrayList<>(); - for (ChannelWrapper channel : mChannelWrapperMap.values()) { + for (ChannelWrapper channel : mData.channelWrapperMap.values()) { if (channel.mChannel.getInputId().equals(inputId)) { channel.mInputRemoved = true; channelRemoved = true; @@ -117,13 +124,15 @@ public class ChannelDataManager { } } if (channelRemoved) { - clearChannels(); - for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) { + ChannelData data = new ChannelData(); + data.channelWrapperMap.putAll(mData.channelWrapperMap); + for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) { if (!channelWrapper.mInputRemoved) { - addChannel(channelWrapper.mChannel); + addChannel(data, channelWrapper.mChannel); } } - Collections.sort(mChannels, mChannelComparator); + Collections.sort(data.channels, mChannelComparator); + mData = new UnmodifiableChannelData(data); notifyChannelListUpdated(); for (ChannelWrapper channel : removedChannels) { channel.notifyChannelRemoved(); @@ -132,10 +141,12 @@ public class ChannelDataManager { } }; + @MainThread public ChannelDataManager(Context context, TvInputManagerHelper inputManager) { this(context, inputManager, context.getContentResolver()); } + @MainThread @VisibleForTesting ChannelDataManager(Context context, TvInputManagerHelper inputManager, ContentResolver contentResolver) { @@ -167,6 +178,7 @@ public class ChannelDataManager { /** * Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called. */ + @MainThread public void start() { if (mStarted) { return; @@ -184,6 +196,7 @@ public class ChannelDataManager { * Stops the manager. It clears manager states and runs pending DB operations. Added listeners * aren't automatically removed by this method. */ + @MainThread @VisibleForTesting public void stop() { if (!mStarted) { @@ -192,12 +205,10 @@ public class ChannelDataManager { mStarted = false; mDbLoadFinished = false; - ChannelLogoFetcher.stopFetchingChannelLogos(); mInputManager.removeCallback(mTvInputCallback); mContentResolver.unregisterContentObserver(mChannelObserver); mHandler.removeCallbacksAndMessages(null); - mChannelWrapperMap.clear(); clearChannels(); mPostRunnablesAfterChannelUpdate.clear(); if (mChannelsUpdateTask != null) { @@ -233,7 +244,7 @@ public class ChannelDataManager { * Adds a {@link ChannelListener} for a specific channel with the channel ID {@code channelId}. */ public void addChannelListener(Long channelId, ChannelListener listener) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId); + ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { return; } @@ -245,7 +256,7 @@ public class ChannelDataManager { * {@code channelId}. */ public void removeChannelListener(Long channelId, ChannelListener listener) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId); + ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { return; } @@ -263,14 +274,14 @@ public class ChannelDataManager { * Returns the number of channels. */ public int getChannelCount() { - return mChannels.size(); + return mData.channels.size(); } /** * Returns a list of channels. */ public List getChannelList() { - return Collections.unmodifiableList(mChannels); + return new ArrayList<>(mData.channels); } /** @@ -278,7 +289,7 @@ public class ChannelDataManager { */ public List getBrowsableChannelList() { List channels = new ArrayList<>(); - for (Channel channel : mChannels) { + for (Channel channel : mData.channels) { if (channel.isBrowsable()) { channels.add(channel); } @@ -292,7 +303,7 @@ public class ChannelDataManager { * @param inputId The ID of the input. */ public int getChannelCountForInput(String inputId) { - MutableInt count = mChannelCountMap.get(inputId); + MutableInt count = mData.channelCountMap.get(inputId); return count == null ? 0 : count.value; } @@ -303,17 +314,14 @@ public class ChannelDataManager { * In that case this method is used to check if the channel exists in the DB. */ public boolean doesChannelExistInDb(long channelId) { - return mChannelWrapperMap.get(channelId) != null; + return mData.channelWrapperMap.get(channelId) != null; } /** * Returns true if and only if there exists at least one channel and all channels are hidden. */ public boolean areAllChannelsHidden() { - if (mChannels.isEmpty()) { - return false; - } - for (Channel channel : mChannels) { + for (Channel channel : mData.channels) { if (channel.isBrowsable()) { return false; } @@ -325,7 +333,7 @@ public class ChannelDataManager { * Gets the channel with the channel ID {@code channelId}. */ public Channel getChannel(Long channelId) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId); + ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null || channelWrapper.mInputRemoved) { return null; } @@ -349,7 +357,7 @@ public class ChannelDataManager { */ public void updateBrowsable(Long channelId, boolean browsable, boolean skipNotifyChannelBrowsableChanged) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId); + ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { return; } @@ -407,7 +415,7 @@ public class ChannelDataManager { * The value change will be applied to DB when applyPendingDbOperation is called. */ public void updateLocked(Long channelId, boolean locked) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId); + ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { return; } @@ -427,10 +435,11 @@ public class ChannelDataManager { * to DB. */ public void applyUpdatedValuesToDb() { + ChannelData data = mData; ArrayList browsableIds = new ArrayList<>(); ArrayList unbrowsableIds = new ArrayList<>(); for (Long id : mBrowsableUpdateChannelIds) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(id); + ChannelWrapper channelWrapper = data.channelWrapperMap.get(id); if (channelWrapper == null) { continue; } @@ -452,10 +461,10 @@ public class ChannelDataManager { } editor.apply(); } else { - if (browsableIds.size() != 0) { + if (!browsableIds.isEmpty()) { updateOneColumnValue(column, 1, browsableIds); } - if (unbrowsableIds.size() != 0) { + if (!unbrowsableIds.isEmpty()) { updateOneColumnValue(column, 0, unbrowsableIds); } } @@ -464,7 +473,7 @@ public class ChannelDataManager { ArrayList lockedIds = new ArrayList<>(); ArrayList unlockedIds = new ArrayList<>(); for (Long id : mLockedUpdateChannelIds) { - ChannelWrapper channelWrapper = mChannelWrapperMap.get(id); + ChannelWrapper channelWrapper = data.channelWrapperMap.get(id); if (channelWrapper == null) { continue; } @@ -476,10 +485,10 @@ public class ChannelDataManager { channelWrapper.mLockedInDb = channelWrapper.mChannel.isLocked(); } column = TvContract.Channels.COLUMN_LOCKED; - if (lockedIds.size() != 0) { + if (!lockedIds.isEmpty()) { updateOneColumnValue(column, 1, lockedIds); } - if (unlockedIds.size() != 0) { + if (!unlockedIds.isEmpty()) { updateOneColumnValue(column, 0, unlockedIds); } mLockedUpdateChannelIds.clear(); @@ -492,22 +501,24 @@ public class ChannelDataManager { } } - private void addChannel(Channel channel) { - mChannels.add(channel); + @MainThread + private void addChannel(ChannelData data, Channel channel) { + data.channels.add(channel); String inputId = channel.getInputId(); - MutableInt count = mChannelCountMap.get(inputId); + MutableInt count = data.channelCountMap.get(inputId); if (count == null) { - mChannelCountMap.put(inputId, new MutableInt(1)); + data.channelCountMap.put(inputId, new MutableInt(1)); } else { count.value++; } } + @MainThread private void clearChannels() { - mChannels.clear(); - mChannelCountMap.clear(); + mData = new UnmodifiableChannelData(); } + @MainThread private void handleUpdateChannels() { if (mChannelsUpdateTask != null) { mChannelsUpdateTask.cancel(true); @@ -525,6 +536,9 @@ public class ChannelDataManager { } } + /** + * A listener for ChannelDataManager. The callbacks are called on the main thread. + */ public interface Listener { /** * Called when data load is finished. @@ -543,6 +557,9 @@ public class ChannelDataManager { void onChannelBrowsableChanged(); } + /** + * A listener for individual channel change. The callbacks are called on the main thread. + */ public interface ChannelListener { /** * Called when the channel has been removed in DB. @@ -590,9 +607,36 @@ public class ChannelDataManager { } } + private class CheckChannelLogoExistTask extends AsyncTask { + private final Channel mChannel; + + CheckChannelLogoExistTask(Channel channel) { + mChannel = channel; + } + + @Override + protected Boolean doInBackground(Void... params) { + try (AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor( + TvContract.buildChannelLogoUri(mChannel.getId()), "r")) { + return true; + } catch (SQLiteException | IOException | NullPointerException e) { + // File not found or asset file not found. + } + return false; + } + + @Override + protected void onPostExecute(Boolean result) { + ChannelWrapper wrapper = mData.channelWrapperMap.get(mChannel.getId()); + if (wrapper != null) { + wrapper.mChannel.setChannelLogoExist(result); + } + } + } + private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask { - public QueryAllChannelsTask(ContentResolver contentResolver) { + QueryAllChannelsTask(ContentResolver contentResolver) { super(contentResolver); } @@ -603,7 +647,9 @@ public class ChannelDataManager { if (DEBUG) Log.e(TAG, "onPostExecute with null channels"); return; } - Set removedChannelIds = new HashSet<>(mChannelWrapperMap.keySet()); + ChannelData data = new ChannelData(); + data.channelWrapperMap.putAll(mData.channelWrapperMap); + Set removedChannelIds = new HashSet<>(data.channelWrapperMap.keySet()); List removedChannelWrappers = new ArrayList<>(); List updatedChannelWrappers = new ArrayList<>(); @@ -625,13 +671,15 @@ public class ChannelDataManager { boolean newlyAdded = !removedChannelIds.remove(channelId); ChannelWrapper channelWrapper; if (newlyAdded) { + new CheckChannelLogoExistTask(channel) + .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); channelWrapper = new ChannelWrapper(channel); - mChannelWrapperMap.put(channel.getId(), channelWrapper); + data.channelWrapperMap.put(channel.getId(), channelWrapper); if (!channelWrapper.mInputRemoved) { channelAdded = true; } } else { - channelWrapper = mChannelWrapperMap.get(channelId); + channelWrapper = data.channelWrapperMap.get(channelId); if (!channelWrapper.mChannel.hasSameReadOnlyInfo(channel)) { // Channel data updated Channel oldChannel = channelWrapper.mChannel; @@ -640,9 +688,9 @@ public class ChannelDataManager { // {@link #applyUpdatedValuesToDb} is called. Therefore, the value // between DB and ChannelDataManager could be different for a while. // Therefore, we'll keep the values in ChannelDataManager. - channelWrapper.mChannel.copyFrom(channel); channel.setBrowsable(oldChannel.isBrowsable()); channel.setLocked(oldChannel.isLocked()); + channelWrapper.mChannel.copyFrom(channel); if (!channelWrapper.mInputRemoved) { channelUpdated = true; updatedChannelWrappers.add(channelWrapper); @@ -663,19 +711,19 @@ public class ChannelDataManager { } for (long id : removedChannelIds) { - ChannelWrapper channelWrapper = mChannelWrapperMap.remove(id); + ChannelWrapper channelWrapper = data.channelWrapperMap.remove(id); if (!channelWrapper.mInputRemoved) { channelRemoved = true; removedChannelWrappers.add(channelWrapper); } } - clearChannels(); - for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) { + for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) { if (!channelWrapper.mInputRemoved) { - addChannel(channelWrapper.mChannel); + addChannel(data, channelWrapper.mChannel); } } - Collections.sort(mChannels, mChannelComparator); + Collections.sort(data.channels, mChannelComparator); + mData = new UnmodifiableChannelData(data); if (!mDbLoadFinished) { mDbLoadFinished = true; @@ -693,7 +741,6 @@ public class ChannelDataManager { r.run(); } mPostRunnablesAfterChannelUpdate.clear(); - ChannelLogoFetcher.startFetchingChannelLogos(mContext); } } @@ -705,10 +752,9 @@ public class ChannelDataManager { private void updateOneColumnValue( final String columnName, final int columnValue, final List ids) { if (!PermissionUtils.hasAccessAllEpg(mContext)) { - // TODO: support this feature for non-system LC app. b/23939816 return; } - AsyncDbTask.execute(new Runnable() { + AsyncDbTask.executeOnDbThread(new Runnable() { @Override public void run() { String selection = Utils.buildSelectionForIds(Channels._ID, ids); @@ -723,6 +769,7 @@ public class ChannelDataManager { return channel.getInputId() + "|" + channel.getId(); } + @MainThread private static class ChannelDataManagerHandler extends WeakHandler { public ChannelDataManagerHandler(ChannelDataManager channelDataManager) { super(Looper.getMainLooper(), channelDataManager); @@ -735,4 +782,51 @@ public class ChannelDataManager { } } } + + /** + * Container class which includes channel data that needs to be synced. This class is + * modifiable and used for changing channel data. + * e.g. TvInputCallback, or AsyncDbTask.onPostExecute. + */ + @MainThread + private static class ChannelData { + final Map channelWrapperMap; + final Map channelCountMap; + final List channels; + + ChannelData() { + channelWrapperMap = new HashMap<>(); + channelCountMap = new HashMap<>(); + channels = new ArrayList<>(); + } + + ChannelData(ChannelData data) { + channelWrapperMap = new HashMap<>(data.channelWrapperMap); + channelCountMap = new HashMap<>(data.channelCountMap); + channels = new ArrayList<>(data.channels); + } + + ChannelData(Map channelWrapperMap, + Map channelCountMap, List channels) { + this.channelWrapperMap = channelWrapperMap; + this.channelCountMap = channelCountMap; + this.channels = channels; + } + } + + /** Unmodifiable channel data. */ + @MainThread + private static class UnmodifiableChannelData extends ChannelData { + UnmodifiableChannelData() { + super(Collections.unmodifiableMap(new HashMap<>()), + Collections.unmodifiableMap(new HashMap<>()), + Collections.unmodifiableList(new ArrayList<>())); + } + + UnmodifiableChannelData(ChannelData data) { + super(Collections.unmodifiableMap(data.channelWrapperMap), + Collections.unmodifiableMap(data.channelCountMap), + Collections.unmodifiableList(data.channels)); + } + } } diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java index 5a549f83..132cab7a 100644 --- a/src/com/android/tv/data/ChannelLogoFetcher.java +++ b/src/com/android/tv/data/ChannelLogoFetcher.java @@ -16,160 +16,74 @@ package com.android.tv.data; +import android.content.ContentProviderOperation; import android.content.Context; -import android.database.Cursor; +import android.content.OperationApplicationException; +import android.content.SharedPreferences; import android.graphics.Bitmap.CompressFormat; import android.media.tv.TvContract; -import android.media.tv.TvContract.Channels; import android.net.Uri; import android.os.AsyncTask; -import android.support.annotation.WorkerThread; +import android.os.RemoteException; +import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; -import com.android.tv.util.AsyncDbTask; +import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.util.BitmapUtils; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; import com.android.tv.util.PermissionUtils; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; +import java.util.List; /** - * Utility class for TMS data. - * This class is thread safe. + * Fetches channel logos from the cloud into the database. It's for the channels which have no logos + * or need update logos. This class is thread safe. */ public class ChannelLogoFetcher { private static final String TAG = "ChannelLogoFetcher"; private static final boolean DEBUG = false; - /** - * The name of the file which contains the TMS data. - * The file has multiple records and each of them is a string separated by '|' like - * STATION_NAME|SHORT_NAME|CALL_SIGN|LOGO_URI. - */ - private static final String TMS_US_TABLE_FILE = "tms_us.table"; - private static final String TMS_KR_TABLE_FILE = "tms_kr.table"; - private static final String FIELD_SEPARATOR = "\\|"; - private static final String NAME_SEPARATOR_FOR_TMS = "\\(|\\)|\\{|\\}|\\[|\\]"; - private static final String NAME_SEPARATOR_FOR_DB = "\\W"; - private static final int INDEX_NAME = 0; - private static final int INDEX_SHORT_NAME = 1; - private static final int INDEX_CALL_SIGN = 2; - private static final int INDEX_LOGO_URI = 3; - - private static final String COLUMN_CHANNEL_LOGO = "logo"; + private static final String PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO = + "is_first_time_fetch_channel_logo"; - private static final Object sLock = new Object(); - private static final Set sChannelIdBlackListSet = new HashSet<>(); - private static LoadChannelTask sQueryTask; private static FetchLogoTask sFetchTask; /** - * Fetch the channel logos from TMS data and insert them into TvProvider. + * Fetches the channel logos from the cloud data and insert them into TvProvider. * The previous task is canceled and a new task starts. */ - public static void startFetchingChannelLogos(Context context) { + @MainThread + public static void startFetchingChannelLogos( + Context context, List channels) { if (!PermissionUtils.hasAccessAllEpg(context)) { // TODO: support this feature for non-system LC app. b/23939816 return; } - synchronized (sLock) { - stopFetchingChannelLogos(); - if (DEBUG) Log.d(TAG, "Request to start fetching logos."); - sQueryTask = new LoadChannelTask(context); - sQueryTask.executeOnDbThread(); + if (sFetchTask != null) { + sFetchTask.cancel(true); + sFetchTask = null; } - } - - /** - * Stops the current fetching tasks. This can be called when the Activity pauses. - */ - public static void stopFetchingChannelLogos() { - synchronized (sLock) { - if (DEBUG) Log.d(TAG, "Request to stop fetching logos."); - if (sQueryTask != null) { - sQueryTask.cancel(true); - sQueryTask = null; - } - if (sFetchTask != null) { - sFetchTask.cancel(true); - sFetchTask = null; - } + if (DEBUG) Log.d(TAG, "Request to start fetching logos."); + if (channels == null || channels.isEmpty()) { + return; } + sFetchTask = new FetchLogoTask(context.getApplicationContext(), channels); + sFetchTask.execute(); } private ChannelLogoFetcher() { } - private static final class LoadChannelTask extends AsyncDbTask> { - private final Context mContext; - - public LoadChannelTask(Context context) { - mContext = context; - } - - @Override - protected List doInBackground(Void... arg) { - // Load channels which doesn't have channel logos. - if (DEBUG) Log.d(TAG, "Starts loading the channels from DB"); - String[] projection = - new String[] { Channels._ID, Channels.COLUMN_DISPLAY_NAME }; - String selection = COLUMN_CHANNEL_LOGO + " IS NULL AND " - + Channels.COLUMN_PACKAGE_NAME + "=?"; - String[] selectionArgs = new String[] { mContext.getPackageName() }; - try (Cursor c = mContext.getContentResolver().query(Channels.CONTENT_URI, - projection, selection, selectionArgs, null)) { - if (c == null) { - Log.e(TAG, "Query returns null cursor", new RuntimeException()); - return null; - } - List channels = new ArrayList<>(); - while (!isCancelled() && c.moveToNext()) { - long channelId = c.getLong(0); - if (sChannelIdBlackListSet.contains(channelId)) { - continue; - } - channels.add(new Channel.Builder().setId(c.getLong(0)) - .setDisplayName(c.getString(1).toUpperCase(Locale.getDefault())) - .build()); - } - return channels; - } - } - - @Override - protected void onPostExecute(List channels) { - synchronized (sLock) { - if (DEBUG) { - int count = channels == null ? 0 : channels.size(); - Log.d(TAG, count + " channels are loaded"); - } - if (sQueryTask == this) { - sQueryTask = null; - if (channels != null && !channels.isEmpty()) { - sFetchTask = new FetchLogoTask(mContext, channels); - sFetchTask.execute(); - } - } - } - } - } - private static final class FetchLogoTask extends AsyncTask { private final Context mContext; private final List mChannels; - public FetchLogoTask(Context context, List channels) { + private FetchLogoTask(Context context, List channels) { mContext = context; mChannels = channels; } @@ -180,83 +94,53 @@ public class ChannelLogoFetcher { if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled"); return null; } - // Load the TMS table data. - if (DEBUG) Log.d(TAG, "Loads TMS data"); - Map channelNameLogoUriMap = new HashMap<>(); - try { - channelNameLogoUriMap.putAll(readTmsFile(mContext, TMS_US_TABLE_FILE)); - if (isCancelled()) { - if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled"); - return null; + List channelsToUpdate = new ArrayList<>(); + List channelsToRemove = new ArrayList<>(); + // Updates or removes the logo by comparing the logo uri which is got from the cloud + // and the stored one. And we assume that the data got form the cloud is 100% + // correct and completed. + SharedPreferences sharedPreferences = + mContext.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_CHANNEL_LOGO_URIS, + Context.MODE_PRIVATE); + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + Map uncheckedChannels = sharedPreferences.getAll(); + boolean isFirstTimeFetchChannelLogo = sharedPreferences.getBoolean( + PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, true); + // Iterating channels. + for (Channel channel : mChannels) { + String channelIdString = Long.toString(channel.getId()); + String storedChannelLogoUri = (String) uncheckedChannels.remove(channelIdString); + if (!TextUtils.isEmpty(channel.getLogoUri()) + && !TextUtils.equals(storedChannelLogoUri, channel.getLogoUri())) { + channelsToUpdate.add(channel); + sharedPreferencesEditor.putString(channelIdString, channel.getLogoUri()); + } else if (TextUtils.isEmpty(channel.getLogoUri()) + && (!TextUtils.isEmpty(storedChannelLogoUri) + || isFirstTimeFetchChannelLogo)) { + channelsToRemove.add(channel); + sharedPreferencesEditor.remove(channelIdString); } - channelNameLogoUriMap.putAll(readTmsFile(mContext, TMS_KR_TABLE_FILE)); - } catch (IOException e) { - Log.e(TAG, "Loading TMS data failed.", e); - return null; } - if (isCancelled()) { - if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled"); - return null; + + // Removes non existing channels from SharedPreferences. + for (String channelId : uncheckedChannels.keySet()) { + sharedPreferencesEditor.remove(channelId); } - // Iterating channels. - for (Channel channel : mChannels) { + // Updates channel logos. + for (Channel channel : channelsToUpdate) { if (isCancelled()) { if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled"); return null; } - // Download the channel logo. - if (TextUtils.isEmpty(channel.getDisplayName())) { - if (DEBUG) { - Log.d(TAG, "The channel with ID (" + channel.getId() - + ") doesn't have the display name."); - } - sChannelIdBlackListSet.add(channel.getId()); - continue; - } - String channelName = channel.getDisplayName().trim(); - String logoUri = channelNameLogoUriMap.get(channelName); - if (TextUtils.isEmpty(logoUri)) { - if (DEBUG) { - Log.d(TAG, "Can't find a logo URI for channel '" + channelName + "'"); - } - // Find the candidate names. If the channel name is CNN-HD, then find CNNHD - // and CNN. Or if the channel name is KQED+, then find KQED. - String[] splitNames = channelName.split(NAME_SEPARATOR_FOR_DB); - if (splitNames.length > 1) { - StringBuilder sb = new StringBuilder(); - for (String splitName : splitNames) { - sb.append(splitName); - } - logoUri = channelNameLogoUriMap.get(sb.toString()); - if (DEBUG) { - if (TextUtils.isEmpty(logoUri)) { - Log.d(TAG, "Can't find a logo URI for channel '" + sb.toString() - + "'"); - } - } - } - if (TextUtils.isEmpty(logoUri) - && splitNames[0].length() != channelName.length()) { - logoUri = channelNameLogoUriMap.get(splitNames[0]); - if (DEBUG) { - if (TextUtils.isEmpty(logoUri)) { - Log.d(TAG, "Can't find a logo URI for channel '" + splitNames[0] - + "'"); - } - } - } - } - if (TextUtils.isEmpty(logoUri)) { - sChannelIdBlackListSet.add(channel.getId()); - continue; - } + // Downloads the channel logo. + String logoUri = channel.getLogoUri(); ScaledBitmapInfo bitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString( mContext, logoUri, Integer.MAX_VALUE, Integer.MAX_VALUE); if (bitmapInfo == null) { Log.e(TAG, "Failed to load bitmap. {channelName=" + channel.getDisplayName() + ", " + "logoUri=" + logoUri + "}"); - sChannelIdBlackListSet.add(channel.getId()); continue; } if (isCancelled()) { @@ -264,12 +148,15 @@ public class ChannelLogoFetcher { return null; } - // Insert the logo to DB. + // Inserts the logo to DB. Uri dstLogoUri = TvContract.buildChannelLogoUri(channel.getId()); try (OutputStream os = mContext.getContentResolver().openOutputStream(dstLogoUri)) { bitmapInfo.bitmap.compress(CompressFormat.PNG, 100, os); } catch (IOException e) { Log.e(TAG, "Failed to write " + logoUri + " to " + dstLogoUri, e); + // Removes it from the shared preference for the failed channels to make it + // retry next time. + sharedPreferencesEditor.remove(Long.toString(channel.getId())); continue; } if (DEBUG) { @@ -277,63 +164,35 @@ public class ChannelLogoFetcher { + dstLogoUri + "}"); } } - if (DEBUG) Log.d(TAG, "Fetching logos has been finished successfully."); - return null; - } - @WorkerThread - private Map readTmsFile(Context context, String fileName) - throws IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader( - context.getAssets().open(fileName)))) { - Map channelNameLogoUriMap = new HashMap<>(); - String line; - while ((line = reader.readLine()) != null && !isCancelled()) { - String[] data = line.split(FIELD_SEPARATOR); - if (data.length != INDEX_LOGO_URI + 1) { - if (DEBUG) Log.d(TAG, "Invalid or comment row: " + line); - continue; - } - addChannelNames(channelNameLogoUriMap, - data[INDEX_NAME].toUpperCase(Locale.getDefault()), - data[INDEX_LOGO_URI]); - addChannelNames(channelNameLogoUriMap, - data[INDEX_SHORT_NAME].toUpperCase(Locale.getDefault()), - data[INDEX_LOGO_URI]); - addChannelNames(channelNameLogoUriMap, - data[INDEX_CALL_SIGN].toUpperCase(Locale.getDefault()), - data[INDEX_LOGO_URI]); + // Removes the logos for the channels that have logos before but now + // their logo uris are null. + boolean deleteChannelLogoFailed = false; + if (!channelsToRemove.isEmpty()) { + ArrayList ops = new ArrayList<>(); + for (Channel channel : channelsToRemove) { + ops.add(ContentProviderOperation.newDelete( + TvContract.buildChannelLogoUri(channel.getId())).build()); + } + try { + mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); + } catch (RemoteException | OperationApplicationException e) { + deleteChannelLogoFailed = true; + Log.e(TAG, "Error deleting obsolete channels", e); } - return channelNameLogoUriMap; } - } - - private void addChannelNames(Map channelNameLogoUriMap, String channelName, - String logoUri) { - if (!TextUtils.isEmpty(channelName)) { - channelNameLogoUriMap.put(channelName, logoUri); - // Find the candidate names. - // If the name is like "W05AAD (W05AA-D)", then split the names into "W05AAD" and - // "W05AA-D" - String[] splitNames = channelName.split(NAME_SEPARATOR_FOR_TMS); - if (splitNames.length > 1) { - for (String name : splitNames) { - name = name.trim(); - if (channelNameLogoUriMap.get(name) == null) { - channelNameLogoUriMap.put(name, logoUri); - } - } - } + if (isFirstTimeFetchChannelLogo && !deleteChannelLogoFailed) { + sharedPreferencesEditor.putBoolean( + PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, false); } + sharedPreferencesEditor.commit(); + if (DEBUG) Log.d(TAG, "Fetching logos has been finished successfully."); + return null; } @Override protected void onPostExecute(Void result) { - synchronized (sLock) { - if (sFetchTask == this) { - sFetchTask = null; - } - } + sFetchTask = null; } } } diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java index 59021609..29054aa5 100644 --- a/src/com/android/tv/data/ChannelNumber.java +++ b/src/com/android/tv/data/ChannelNumber.java @@ -17,37 +17,38 @@ package com.android.tv.data; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.view.KeyEvent; +import com.android.tv.util.StringUtils; + +import java.util.Objects; + /** * A convenience class to handle channel number. */ public final class ChannelNumber implements Comparable { - public static final String PRIMARY_CHANNEL_DELIMITER = "-"; - public static final String[] CHANNEL_DELIMITERS = {"-", ".", " "}; - private static final int[] CHANNEL_DELIMITER_KEYCODES = { KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_PERIOD, KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_SPACE }; + /** The major part of the channel number. */ public String majorNumber; + /** The flag which indicates whether it has a delimiter or not. */ public boolean hasDelimiter; + /** The major part of the channel number. */ public String minorNumber; public ChannelNumber() { reset(); } - public ChannelNumber(String major, boolean hasDelimiter, String minor) { - setChannelNumber(major, hasDelimiter, minor); - } - public void reset() { setChannelNumber("", false, ""); } - public void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) { + private void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) { this.majorNumber = majorNumber; this.hasDelimiter = hasDelimiter; this.minorNumber = minorNumber; @@ -56,7 +57,7 @@ public final class ChannelNumber implements Comparable { @Override public String toString() { if (hasDelimiter) { - return majorNumber + PRIMARY_CHANNEL_DELIMITER + minorNumber; + return majorNumber + Channel.CHANNEL_NUMBER_DELIMITER + minorNumber; } return majorNumber; } @@ -75,6 +76,22 @@ public final class ChannelNumber implements Comparable { return major - opponentMajor; } + @Override + public boolean equals(Object obj) { + if (obj instanceof ChannelNumber) { + ChannelNumber channelNumber = (ChannelNumber) obj; + return TextUtils.equals(majorNumber, channelNumber.majorNumber) + && TextUtils.equals(minorNumber, channelNumber.minorNumber) + && hasDelimiter == channelNumber.hasDelimiter; + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return Objects.hash(majorNumber, hasDelimiter, minorNumber); + } + public static boolean isChannelNumberDelimiterKey(int keyCode) { for (int delimiterKeyCode : CHANNEL_DELIMITER_KEYCODES) { if (delimiterKeyCode == keyCode) { @@ -84,22 +101,22 @@ public final class ChannelNumber implements Comparable { return false; } + /** + * Returns the ChannelNumber instance. + *

+ * Note that all the channel number argument should be normalized by + * {@link Channel#normalizeDisplayNumber}. The channels retrieved from + * {@link ChannelDataManager} are already normalized. + */ public static ChannelNumber parseChannelNumber(String number) { if (number == null) { return null; } ChannelNumber ret = new ChannelNumber(); - int indexOfDelimiter = -1; - for (String delimiter : CHANNEL_DELIMITERS) { - indexOfDelimiter = number.indexOf(delimiter); - if (indexOfDelimiter >= 0) { - break; - } - } + int indexOfDelimiter = number.indexOf(Channel.CHANNEL_NUMBER_DELIMITER); if (indexOfDelimiter == 0 || indexOfDelimiter == number.length() - 1) { return null; - } - if (indexOfDelimiter < 0) { + } else if (indexOfDelimiter < 0) { ret.majorNumber = number; if (!isInteger(ret.majorNumber)) { return null; @@ -115,25 +132,31 @@ public final class ChannelNumber implements Comparable { return ret; } + /** + * Compares the channel numbers. + *

+ * Note that all the channel number arguments should be normalized by + * {@link Channel#normalizeDisplayNumber}. The channels retrieved from + * {@link ChannelDataManager} are already normalized. + */ public static int compare(String lhs, String rhs) { ChannelNumber lhsNumber = parseChannelNumber(lhs); ChannelNumber rhsNumber = parseChannelNumber(rhs); + // Null first if (lhsNumber == null && rhsNumber == null) { - return 0; + return StringUtils.compare(lhs, rhs); } else if (lhsNumber == null /* && rhsNumber != null */) { return -1; - } else if (lhsNumber != null && rhsNumber == null) { + } else if (rhsNumber == null) { return 1; } return lhsNumber.compareTo(rhsNumber); } - public static boolean isInteger(String string) { + private static boolean isInteger(String string) { try { Integer.parseInt(string); - } catch(NumberFormatException e) { - return false; - } catch(NullPointerException e) { + } catch(NumberFormatException | NullPointerException e) { return false; } return true; diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java index 6054f089..e33ca18f 100644 --- a/src/com/android/tv/data/InternalDataUtils.java +++ b/src/com/android/tv/data/InternalDataUtils.java @@ -21,7 +21,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.tv.data.Program.CriticScore; -import com.android.tv.dvr.RecordedProgram; +import com.android.tv.dvr.data.RecordedProgram; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java new file mode 100644 index 00000000..01a58520 --- /dev/null +++ b/src/com/android/tv/data/PreviewDataManager.java @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.data; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.tv.TvContract; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.IntDef; +import android.support.annotation.MainThread; +import android.support.media.tv.ChannelLogoUtils; +import android.support.media.tv.PreviewProgram; +import android.util.Log; +import android.util.Pair; + +import com.android.tv.R; +import com.android.tv.util.PermissionUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Class to manage the preview data. + */ +@TargetApi(Build.VERSION_CODES.O) +@MainThread +public class PreviewDataManager { + private static final String TAG = "PreviewDataManager"; + // STOPSHIP: set it to false. + private static final boolean DEBUG = true; + + /** + * Invalid preview channel ID. + */ + public static final long INVALID_PREVIEW_CHANNEL_ID = -1; + @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) + @Retention(RetentionPolicy.SOURCE) + public @interface PreviewChannelType{} + + /** + * Type of default preview channel + */ + public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1; + /** + * Type of recorded program channel + */ + public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2; + + private final Context mContext; + private final ContentResolver mContentResolver; + private boolean mLoadFinished; + private PreviewData mPreviewData = new PreviewData(); + private final Set mPreviewDataListeners = new CopyOnWriteArraySet<>(); + + private QueryPreviewDataTask mQueryPreviewTask; + private final Map mCreatePreviewChannelTasks = + new HashMap<>(); + private final Map mUpdatePreviewProgramTasks = new HashMap<>(); + + private final int mPreviewChannelLogoWidth; + private final int mPreviewChannelLogoHeight; + + public PreviewDataManager(Context context) { + mContext = context.getApplicationContext(); + mContentResolver = context.getContentResolver(); + mPreviewChannelLogoWidth = mContext.getResources().getDimensionPixelSize( + R.dimen.preview_channel_logo_width); + mPreviewChannelLogoHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.preview_channel_logo_height); + } + + /** + * Starts the preview data manager. + */ + public void start() { + if (mQueryPreviewTask == null) { + mQueryPreviewTask = new QueryPreviewDataTask(); + mQueryPreviewTask.execute(); + } + } + + /** + * Stops the preview data manager. + */ + public void stop() { + if (mQueryPreviewTask != null) { + mQueryPreviewTask.cancel(true); + } + for (CreatePreviewChannelTask createPreviewChannelTask + : mCreatePreviewChannelTasks.values()) { + createPreviewChannelTask.cancel(true); + } + for (UpdatePreviewProgramTask updatePreviewProgramTask + : mUpdatePreviewProgramTasks.values()) { + updatePreviewProgramTask.cancel(true); + } + + mQueryPreviewTask = null; + mCreatePreviewChannelTasks.clear(); + mUpdatePreviewProgramTasks.clear(); + } + + /** + * Gets preview channel ID from the preview channel type. + */ + public @PreviewChannelType long getPreviewChannelId(long previewChannelType) { + return mPreviewData.getPreviewChannelId(previewChannelType); + } + + /** + * Creates default preview channel. + */ + public void createDefaultPreviewChannel( + OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) { + createPreviewChannel(TYPE_DEFAULT_PREVIEW_CHANNEL, onPreviewChannelCreationResultListener); + } + + /** + * Creates a preview channel for specific channel type. + */ + public void createPreviewChannel(@PreviewChannelType long previewChannelType, + OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) { + CreatePreviewChannelTask currentRunningCreateTask = + mCreatePreviewChannelTasks.get(previewChannelType); + if (currentRunningCreateTask == null) { + CreatePreviewChannelTask createPreviewChannelTask = new CreatePreviewChannelTask( + previewChannelType); + createPreviewChannelTask.addOnPreviewChannelCreationResultListener( + onPreviewChannelCreationResultListener); + createPreviewChannelTask.execute(); + mCreatePreviewChannelTasks.put(previewChannelType, createPreviewChannelTask); + } else { + currentRunningCreateTask.addOnPreviewChannelCreationResultListener( + onPreviewChannelCreationResultListener); + } + } + + /** + * Returns {@code true} if the preview data is loaded. + */ + public boolean isLoadFinished() { + return mLoadFinished; + } + + /** + * Adds listener. + */ + public void addListener(PreviewDataListener previewDataListener) { + mPreviewDataListeners.add(previewDataListener); + } + + /** + * Removes listener. + */ + public void removeListener(PreviewDataListener previewDataListener) { + mPreviewDataListeners.remove(previewDataListener); + } + + /** + * Updates the preview programs table for a specific preview channel. + */ + public void updatePreviewProgramsForChannel(long previewChannelId, + Set programs, PreviewDataListener previewDataListener) { + UpdatePreviewProgramTask currentRunningUpdateTask = + mUpdatePreviewProgramTasks.get(previewChannelId); + if (currentRunningUpdateTask != null + && currentRunningUpdateTask.getPrograms().equals(programs)) { + currentRunningUpdateTask.addPreviewDataListener(previewDataListener); + return; + } + UpdatePreviewProgramTask updatePreviewProgramTask = + new UpdatePreviewProgramTask(previewChannelId, programs); + updatePreviewProgramTask.addPreviewDataListener(previewDataListener); + if (currentRunningUpdateTask != null) { + currentRunningUpdateTask.cancel(true); + currentRunningUpdateTask.saveStatus(); + updatePreviewProgramTask.addPreviewDataListeners( + currentRunningUpdateTask.getPreviewDataListeners()); + } + updatePreviewProgramTask.execute(); + mUpdatePreviewProgramTasks.put(previewChannelId, updatePreviewProgramTask); + } + + private void notifyPreviewDataLoadFinished() { + for (PreviewDataListener l : mPreviewDataListeners) { + l.onPreviewDataLoadFinished(); + } + } + + public interface PreviewDataListener { + /** + * Called when the preview data is loaded. + */ + void onPreviewDataLoadFinished(); + + /** + * Called when the preview data is updated. + */ + void onPreviewDataUpdateFinished(); + } + + public interface OnPreviewChannelCreationResultListener { + /** + * Called when the creation of preview channel is finished. + * @param createdPreviewChannelId The preview channel ID if created successfully, + * otherwise it's {@value #INVALID_PREVIEW_CHANNEL_ID}. + */ + void onPreviewChannelCreationResult(long createdPreviewChannelId); + } + + private final class QueryPreviewDataTask extends AsyncTask { + private final String PARAM_PREVIEW = "preview"; + private final String mChannelSelection = TvContract.Channels.COLUMN_PACKAGE_NAME + "=?"; + + @Override + protected PreviewData doInBackground(Void... voids) { + // Query preview channels and programs. + if (DEBUG) Log.d(TAG, "QueryPreviewDataTask.doInBackground"); + PreviewData previewData = new PreviewData(); + try { + Uri previewChannelsUri = + PreviewDataUtils.addQueryParamToUri( + TvContract.Channels.CONTENT_URI, + new Pair<>(PARAM_PREVIEW, String.valueOf(true))); + String packageName = mContext.getPackageName(); + if (PermissionUtils.hasAccessAllEpg(mContext)) { + try (Cursor cursor = + mContentResolver.query( + previewChannelsUri, + android.support.media.tv.Channel.PROJECTION, + mChannelSelection, + new String[] {packageName}, + null)) { + if (cursor != null) { + while (cursor.moveToNext()) { + android.support.media.tv.Channel previewChannel = + android.support.media.tv.Channel.fromCursor(cursor); + Long previewChannelType = previewChannel.getInternalProviderFlag1(); + if (previewChannelType != null) { + previewData.addPreviewChannelId( + previewChannelType, previewChannel.getId()); + } + } + } + } + } else { + try (Cursor cursor = + mContentResolver.query( + previewChannelsUri, + android.support.media.tv.Channel.PROJECTION, + null, + null, + null)) { + if (cursor != null) { + while (cursor.moveToNext()) { + android.support.media.tv.Channel previewChannel = + android.support.media.tv.Channel.fromCursor(cursor); + Long previewChannelType = previewChannel.getInternalProviderFlag1(); + if (previewChannel.getPackageName() == packageName + && previewChannelType != null) { + previewData.addPreviewChannelId( + previewChannelType, previewChannel.getId()); + } + } + } + } + } + + for (long previewChannelId : previewData.getAllPreviewChannelIds().values()) { + Uri previewProgramsUriForPreviewChannel = + TvContract.buildPreviewProgramsUriForChannel(previewChannelId); + try (Cursor previewProgramCursor = + mContentResolver.query( + previewProgramsUriForPreviewChannel, + PreviewProgram.PROJECTION, + null, + null, + null)) { + if (previewProgramCursor != null) { + while (previewProgramCursor.moveToNext()) { + PreviewProgram previewProgram = + PreviewProgram.fromCursor(previewProgramCursor); + previewData.addPreviewProgram(previewProgram); + } + } + } + } + } catch (SQLException e) { + Log.w(TAG, "Unable to get preview data", e); + } + return previewData; + } + + @Override + protected void onPostExecute(PreviewData result) { + super.onPostExecute(result); + if (mQueryPreviewTask == this) { + mQueryPreviewTask = null; + mPreviewData = new PreviewData(result); + mLoadFinished = true; + notifyPreviewDataLoadFinished(); + } + } + } + + private final class CreatePreviewChannelTask extends AsyncTask { + private final long mPreviewChannelType; + private Set + mOnPreviewChannelCreationResultListeners = new CopyOnWriteArraySet<>(); + + public CreatePreviewChannelTask(long previewChannelType) { + mPreviewChannelType = previewChannelType; + } + + public void addOnPreviewChannelCreationResultListener( + OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) { + if (onPreviewChannelCreationResultListener != null) { + mOnPreviewChannelCreationResultListeners.add( + onPreviewChannelCreationResultListener); + } + } + + @Override + protected Long doInBackground(Void... params) { + if (DEBUG) Log.d(TAG, "CreatePreviewChannelTask.doInBackground"); + long previewChannelId; + try { + Uri channelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI, + PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType) + .toContentValues()); + if (channelUri != null) { + previewChannelId = ContentUris.parseId(channelUri); + } else { + Log.e(TAG, "Fail to insert preview channel"); + return INVALID_PREVIEW_CHANNEL_ID; + } + } catch (UnsupportedOperationException | NumberFormatException e) { + Log.e(TAG, "Fail to get channel ID"); + return INVALID_PREVIEW_CHANNEL_ID; + } + Drawable appIcon = mContext.getApplicationInfo().loadIcon(mContext.getPackageManager()); + if (appIcon != null && appIcon instanceof BitmapDrawable) { + ChannelLogoUtils.storeChannelLogo(mContext, previewChannelId, + Bitmap.createScaledBitmap(((BitmapDrawable) appIcon).getBitmap(), + mPreviewChannelLogoWidth, mPreviewChannelLogoHeight, false)); + } + return previewChannelId; + } + + @Override + protected void onPostExecute(Long result) { + super.onPostExecute(result); + if (result != INVALID_PREVIEW_CHANNEL_ID) { + mPreviewData.addPreviewChannelId(mPreviewChannelType, result); + } + for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener + : mOnPreviewChannelCreationResultListeners) { + onPreviewChannelCreationResultListener.onPreviewChannelCreationResult(result); + } + mCreatePreviewChannelTasks.remove(mPreviewChannelType); + } + } + + /** + * Updates the whole data which belongs to the package in preview programs table for a + * specific preview channel with a set of {@link PreviewProgramContent}. + */ + private final class UpdatePreviewProgramTask extends AsyncTask { + private long mPreviewChannelId; + private Set mPrograms; + private Map mCurrentProgramId2PreviewProgramId; + private Set mPreviewDataListeners = new CopyOnWriteArraySet<>(); + + public UpdatePreviewProgramTask(long previewChannelId, + Set programs) { + mPreviewChannelId = previewChannelId; + mPrograms = programs; + if (mPreviewData.getPreviewProgramIds(previewChannelId) == null) { + mCurrentProgramId2PreviewProgramId = new HashMap<>(); + } else { + mCurrentProgramId2PreviewProgramId = new HashMap<>( + mPreviewData.getPreviewProgramIds(previewChannelId)); + } + } + + public void addPreviewDataListener(PreviewDataListener previewDataListener) { + if (previewDataListener != null) { + mPreviewDataListeners.add(previewDataListener); + } + } + + public void addPreviewDataListeners(Set previewDataListeners) { + if (previewDataListeners != null) { + mPreviewDataListeners.addAll(previewDataListeners); + } + } + + public Set getPrograms() { + return mPrograms; + } + + public Set getPreviewDataListeners() { + return mPreviewDataListeners; + } + + @Override + protected Void doInBackground(Void... params) { + if (DEBUG) Log.d(TAG, "UpdatePreviewProgamTask.doInBackground"); + Map uncheckedPrograms = new HashMap<>(mCurrentProgramId2PreviewProgramId); + for (PreviewProgramContent program : mPrograms) { + if (isCancelled()) { + return null; + } + Long existingPreviewProgramId = uncheckedPrograms.remove(program.getId()); + if (existingPreviewProgramId != null) { + if (DEBUG) Log.d(TAG, "Preview program " + existingPreviewProgramId + " " + + "already exists for program " + program.getId()); + continue; + } + try { + Uri programUri = mContentResolver.insert(TvContract.PreviewPrograms.CONTENT_URI, + PreviewDataUtils.createPreviewProgramFromContent(program) + .toContentValues()); + if (programUri != null) { + long previewProgramId = ContentUris.parseId(programUri); + mCurrentProgramId2PreviewProgramId.put(program.getId(), previewProgramId); + if (DEBUG) Log.d(TAG, "Add new preview program " + previewProgramId); + } else { + Log.e(TAG, "Fail to insert preview program"); + } + } catch (Exception e) { + Log.e(TAG, "Fail to get preview program ID"); + } + } + + for (Long key : uncheckedPrograms.keySet()) { + if (isCancelled()) { + return null; + } + try { + if (DEBUG) Log.d(TAG, "Remove preview program " + uncheckedPrograms.get(key)); + mContentResolver.delete(TvContract.buildPreviewProgramUri( + uncheckedPrograms.get(key)), null, null); + mCurrentProgramId2PreviewProgramId.remove(key); + } catch (Exception e) { + Log.e(TAG, "Fail to remove preview program " + uncheckedPrograms.get(key)); + } + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + mPreviewData.setPreviewProgramIds( + mPreviewChannelId, mCurrentProgramId2PreviewProgramId); + mUpdatePreviewProgramTasks.remove(mPreviewChannelId); + for (PreviewDataListener previewDataListener : mPreviewDataListeners) { + previewDataListener.onPreviewDataUpdateFinished(); + } + } + + public void saveStatus() { + mPreviewData.setPreviewProgramIds( + mPreviewChannelId, mCurrentProgramId2PreviewProgramId); + } + } + + /** + * Class to store the query result of preview data. + */ + private static final class PreviewData { + private Map mPreviewChannelType2Id = new HashMap<>(); + private Map> mProgramId2PreviewProgramId = new HashMap<>(); + + PreviewData() { + mPreviewChannelType2Id = new HashMap<>(); + mProgramId2PreviewProgramId = new HashMap<>(); + } + + PreviewData(PreviewData previewData) { + mPreviewChannelType2Id = new HashMap<>(previewData.mPreviewChannelType2Id); + mProgramId2PreviewProgramId = new HashMap<>(previewData.mProgramId2PreviewProgramId); + } + + public void addPreviewProgram(PreviewProgram previewProgram) { + long previewChannelId = previewProgram.getChannelId(); + Map programId2PreviewProgram = + mProgramId2PreviewProgramId.get(previewChannelId); + if (programId2PreviewProgram == null) { + programId2PreviewProgram = new HashMap<>(); + } + mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgram); + if (previewProgram.getInternalProviderId() != null) { + programId2PreviewProgram.put( + Long.parseLong(previewProgram.getInternalProviderId()), + previewProgram.getId()); + } + } + + public @PreviewChannelType long getPreviewChannelId(long previewChannelType) { + Long result = mPreviewChannelType2Id.get(previewChannelType); + return result == null ? INVALID_PREVIEW_CHANNEL_ID : result; + } + + public Map getAllPreviewChannelIds() { + return mPreviewChannelType2Id; + } + + public void addPreviewChannelId(long previewChannelType, long previewChannelId) { + mPreviewChannelType2Id.put(previewChannelType, previewChannelId); + } + + public void removePreviewChannelId(long previewChannelType) { + mPreviewChannelType2Id.remove(previewChannelType); + } + + public void removePreviewChannel(long previewChannelId) { + removePreviewChannelId(previewChannelId); + removePreviewProgramIds(previewChannelId); + } + + public Map getPreviewProgramIds(long previewChannelId) { + return mProgramId2PreviewProgramId.get(previewChannelId); + } + + public Map> getAllPreviewProgramIds() { + return mProgramId2PreviewProgramId; + } + + public void setPreviewProgramIds( + long previewChannelId, Map programId2PreviewProgramId) { + mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgramId); + } + + public void removePreviewProgramIds(long previewChannelId) { + mProgramId2PreviewProgramId.remove(previewChannelId); + } + } + + /** + * A utils class for preview data. + */ + public final static class PreviewDataUtils { + /** + * Creates a preview channel. + */ + public static android.support.media.tv.Channel createPreviewChannel( + Context context, @PreviewChannelType long previewChannelType) { + if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) { + return createRecordedProgramPreviewChannel(context, previewChannelType); + } + return createDefaultPreviewChannel(context, previewChannelType); + } + + private static android.support.media.tv.Channel createDefaultPreviewChannel( + Context context, @PreviewChannelType long previewChannelType) { + android.support.media.tv.Channel.Builder builder = + new android.support.media.tv.Channel.Builder(); + CharSequence appLabel = + context.getApplicationInfo().loadLabel(context.getPackageManager()); + CharSequence appDescription = + context.getApplicationInfo().loadDescription(context.getPackageManager()); + builder.setType(TvContract.Channels.TYPE_PREVIEW) + .setDisplayName(appLabel == null ? null : appLabel.toString()) + .setDescription(appDescription == null ? null : appDescription.toString()) + .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI) + .setInternalProviderFlag1(previewChannelType); + return builder.build(); + } + + private static android.support.media.tv.Channel createRecordedProgramPreviewChannel( + Context context, @PreviewChannelType long previewChannelType) { + android.support.media.tv.Channel.Builder builder = + new android.support.media.tv.Channel.Builder(); + builder.setType(TvContract.Channels.TYPE_PREVIEW) + .setDisplayName(context.getResources().getString( + R.string.recorded_programs_preview_channel)) + .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI) + .setInternalProviderFlag1(previewChannelType); + return builder.build(); + } + + /** + * Creates a preview program. + */ + public static PreviewProgram createPreviewProgramFromContent( + PreviewProgramContent program) { + PreviewProgram.Builder builder = new PreviewProgram.Builder(); + builder.setChannelId(program.getPreviewChannelId()) + .setType(program.getType()) + .setLive(program.getLive()) + .setTitle(program.getTitle()) + .setDescription(program.getDescription()) + .setPosterArtUri(program.getPosterArtUri()) + .setIntentUri(program.getIntentUri()) + .setPreviewVideoUri(program.getPreviewVideoUri()) + .setInternalProviderId(Long.toString(program.getId())); + return builder.build(); + } + + /** + * Appends query parameters to a Uri. + */ + public static Uri addQueryParamToUri(Uri uri, Pair param) { + return uri.buildUpon().appendQueryParameter(param.first, param.second).build(); + } + } +} diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java new file mode 100644 index 00000000..39f5051d --- /dev/null +++ b/src/com/android/tv/data/PreviewProgramContent.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.data; + +import android.content.Context; +import android.media.tv.TvContract; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Pair; + +import com.android.tv.TvApplication; +import com.android.tv.dvr.data.RecordedProgram; + +import java.util.Objects; + +/** + * A class to store the content of preview programs. + */ +public class PreviewProgramContent { + private final static String PARAM_INPUT = "input"; + + private long mId; + private long mPreviewChannelId; + private int mType; + private boolean mLive; + private String mTitle; + private String mDescription; + private Uri mPosterArtUri; + private Uri mIntentUri; + private Uri mPreviewVideoUri; + + /** + * Create preview program content from {@link Program} + */ + public static PreviewProgramContent createFromProgram(Context context, + long previewChannelId, Program program) { + Channel channel = TvApplication.getSingletons(context).getChannelDataManager() + .getChannel(program.getChannelId()); + if (channel == null) { + return null; + } + String channelDisplayName = channel.getDisplayName(); + return new PreviewProgramContent.Builder() + .setId(program.getId()) + .setPreviewChannelId(previewChannelId) + .setType(TvContract.PreviewPrograms.TYPE_CHANNEL) + .setLive(true) + .setTitle(program.getTitle()) + .setDescription(!TextUtils.isEmpty(channelDisplayName) + ? channelDisplayName : channel.getDisplayNumber()) + .setPosterArtUri(Uri.parse(program.getPosterArtUri())) + .setIntentUri(channel.getUri()) + .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri( + channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId()))) + .build(); + } + + /** + * Create preview program content from {@link RecordedProgram} + */ + public static PreviewProgramContent createFromRecordedProgram( + Context context, long previewChannelId, RecordedProgram recordedProgram) { + Channel channel = TvApplication.getSingletons(context).getChannelDataManager() + .getChannel(recordedProgram.getChannelId()); + String channelDisplayName = null; + if (channel != null) { + channelDisplayName = channel.getDisplayName(); + } + Uri recordedProgramUri = TvContract.buildRecordedProgramUri(recordedProgram.getId()); + return new PreviewProgramContent.Builder() + .setId(recordedProgram.getId()) + .setPreviewChannelId(previewChannelId) + .setType(TvContract.PreviewPrograms.TYPE_CLIP) + .setTitle(recordedProgram.getTitle()) + .setDescription(channelDisplayName != null ? channelDisplayName : "") + .setPosterArtUri(Uri.parse(recordedProgram.getPosterArtUri())) + .setIntentUri(recordedProgramUri) + .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri( + recordedProgramUri, new Pair<>(PARAM_INPUT, recordedProgram.getInputId()))) + .build(); + } + + private PreviewProgramContent() { } + + public void copyFrom(PreviewProgramContent other) { + if (this == other) { + return; + } + mId = other.mId; + mPreviewChannelId = other.mPreviewChannelId; + mType = other.mType; + mLive = other.mLive; + mTitle = other.mTitle; + mDescription = other.mDescription; + mPosterArtUri = other.mPosterArtUri; + mIntentUri = other.mIntentUri; + mPreviewVideoUri = other.mPreviewVideoUri; + } + + /** + * Returns the id, which is an identification. It usually comes from the original data which + * create the {@PreviewProgramContent}. + */ + public long getId() { + return mId; + } + + /** + * Returns the preview channel id which the preview program belongs to. + */ + public long getPreviewChannelId() { + return mPreviewChannelId; + } + + /** + * Returns the type of the preview program. + */ + public int getType() { + return mType; + } + + /** + * Returns whether the preview program is live or not. + */ + public boolean getLive() { + return mLive; + } + + /** + * Returns the title of the preview program. + */ + public String getTitle() { + return mTitle; + } + + /** + * Returns the description of the preview program. + */ + public String getDescription() { + return mDescription; + } + + /** + * Returns the poster art uri of the preview program. + */ + public Uri getPosterArtUri() { + return mPosterArtUri; + } + + /** + * Returns the intent uri of the preview program. + */ + public Uri getIntentUri() { + return mIntentUri; + } + + /** + * Returns the preview video uri of the preview program. + */ + public Uri getPreviewVideoUri() { + return mPreviewVideoUri; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof PreviewProgramContent)) { + return false; + } + PreviewProgramContent previewProgramContent = (PreviewProgramContent) other; + return previewProgramContent.mId == mId + && previewProgramContent.mPreviewChannelId == mPreviewChannelId + && previewProgramContent.mType == mType + && previewProgramContent.mLive == mLive + && Objects.equals(previewProgramContent.mTitle, mTitle) + && Objects.equals(previewProgramContent.mDescription, mDescription) + && Objects.equals(previewProgramContent.mPosterArtUri, mPosterArtUri) + && Objects.equals(previewProgramContent.mIntentUri, mIntentUri) + && Objects.equals(previewProgramContent.mPreviewVideoUri, mPreviewVideoUri); + } + + @Override + public int hashCode() { + return Objects.hash(mId, mPreviewChannelId, mType, mLive, mTitle, mDescription, + mPosterArtUri, mIntentUri, mPreviewVideoUri); + } + + public static final class Builder { + private final PreviewProgramContent mPreviewProgramContent; + + public Builder() { + mPreviewProgramContent = new PreviewProgramContent(); + } + + public Builder setId(long id) { + mPreviewProgramContent.mId = id; + return this; + } + + public Builder setPreviewChannelId(long previewChannelId) { + mPreviewProgramContent.mPreviewChannelId = previewChannelId; + return this; + } + + public Builder setType(int type) { + mPreviewProgramContent.mType = type; + return this; + } + + public Builder setLive(boolean live) { + mPreviewProgramContent.mLive = live; + return this; + } + + public Builder setTitle(String title) { + mPreviewProgramContent.mTitle = title; + return this; + } + + public Builder setDescription(String description) { + mPreviewProgramContent.mDescription = description; + return this; + } + + public Builder setPosterArtUri(Uri posterArtUri) { + mPreviewProgramContent.mPosterArtUri = posterArtUri; + return this; + } + + public Builder setIntentUri(Uri intentUri) { + mPreviewProgramContent.mIntentUri = intentUri; + return this; + } + + public Builder setPreviewVideoUri(Uri previewVideoUri) { + mPreviewProgramContent.mPreviewVideoUri = previewVideoUri; + return this; + } + + public PreviewProgramContent build() { + PreviewProgramContent previewProgramContent = new PreviewProgramContent(); + previewProgramContent.copyFrom(mPreviewProgramContent); + return previewProgramContent; + } + } +} diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java index b9cd3d8d..071c7024 100644 --- a/src/com/android/tv/data/Program.java +++ b/src/com/android/tv/data/Program.java @@ -16,21 +16,23 @@ package com.android.tv.data; +import android.annotation.SuppressLint; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.media.tv.TvContentRating; import android.media.tv.TvContract; +import android.media.tv.TvContract.Programs; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; -import android.support.v4.os.BuildCompat; import android.text.TextUtils; import android.util.Log; -import com.android.tv.R; import com.android.tv.common.BuildConfig; import com.android.tv.common.CollectionUtils; import com.android.tv.common.TvContentRatingCache; @@ -88,9 +90,11 @@ public final class Program extends BaseProgram implements Comparable, P public static final String[] PROJECTION = createProjection(); private static String[] createProjection() { - return CollectionUtils - .concatAll(PROJECTION_BASE, BuildCompat.isAtLeastN() ? PROJECTION_ADDED_IN_NYC - : PROJECTION_DEPRECATED_IN_NYC); + return CollectionUtils.concatAll( + PROJECTION_BASE, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + ? PROJECTION_ADDED_IN_NYC + : PROJECTION_DEPRECATED_IN_NYC); } /** @@ -135,7 +139,7 @@ public final class Program extends BaseProgram implements Comparable, P InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); } index++; - if (BuildCompat.isAtLeastN()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { builder.setSeasonNumber(cursor.getString(index++)); builder.setSeasonTitle(cursor.getString(index++)); builder.setEpisodeNumber(cursor.getString(index++)); @@ -213,11 +217,6 @@ public final class Program extends BaseProgram implements Comparable, P private TvContentRating[] mContentRatings; private boolean mRecordingProhibited; - /** - * TODO(DVR): Need to fill the following data. - */ - private boolean mRecordingScheduled; - private Program() { // Do nothing. } @@ -268,45 +267,11 @@ public final class Program extends BaseProgram implements Comparable, P /** * Returns the episode title. */ - public String getEpisodeTitle() { - return mEpisodeTitle; - } - - /** - * Returns season number, episode number and episode title for display. - */ @Override - public String getEpisodeDisplayTitle(Context context) { - if (!TextUtils.isEmpty(mEpisodeNumber)) { - String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; - if (TextUtils.equals(mSeasonNumber, "0")) { - // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_title_format_no_season_number), - mEpisodeNumber, episodeTitle); - } else { - return String.format(context.getResources().getString( - R.string.display_episode_title_format), - mSeasonNumber, mEpisodeNumber, episodeTitle); - } - } + public String getEpisodeTitle() { return mEpisodeTitle; } - @Override - public String getTitleWithEpisodeNumber(Context context) { - if (TextUtils.isEmpty(mTitle)) { - return mTitle; - } - if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) { - return TextUtils.isEmpty(mEpisodeNumber) ? mTitle : context.getString( - R.string.program_title_with_episode_number_no_season, mTitle, mEpisodeNumber); - } else { - return context.getString(R.string.program_title_with_episode_number, mTitle, - mSeasonNumber, mEpisodeNumber); - } - } - @Override public String getSeasonNumber() { return mSeasonNumber; @@ -361,6 +326,8 @@ public final class Program extends BaseProgram implements Comparable, P return mCriticScores; } + @Nullable + @Override public TvContentRating[] getContentRatings() { return mContentRatings; } @@ -495,6 +462,63 @@ public final class Program extends BaseProgram implements Comparable, P return builder.append("}").toString(); } + /** + * Translates a {@link Program} to {@link ContentValues} that are ready to be written into + * Database. + */ + @SuppressLint("InlinedApi") + @SuppressWarnings("deprecation") + public static ContentValues toContentValues(Program program) { + ContentValues values = new ContentValues(); + values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId()); + putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle()); + putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, + program.getSeasonNumber()); + putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + program.getEpisodeNumber()); + } else { + putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber()); + putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber()); + } + putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription()); + putValue(values, TvContract.Programs.COLUMN_LONG_DESCRIPTION, program.getLongDescription()); + putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri()); + putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri()); + String[] canonicalGenres = program.getCanonicalGenres(); + if (canonicalGenres != null && canonicalGenres.length > 0) { + putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, + TvContract.Programs.Genres.encode(canonicalGenres)); + } else { + putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, ""); + } + putValue(values, Programs.COLUMN_CONTENT_RATING, + TvContentRatingCache.contentRatingsToString(program.getContentRatings())); + values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + program.getStartTimeUtcMillis()); + values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis()); + putValue(values, TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, + InternalDataUtils.serializeInternalProviderData(program)); + return values; + } + + private static void putValue(ContentValues contentValues, String key, String value) { + if (TextUtils.isEmpty(value)) { + contentValues.putNull(key); + } else { + contentValues.put(key, value); + } + } + + private static void putValue(ContentValues contentValues, String key, byte[] value) { + if (value == null || value.length == 0) { + contentValues.putNull(key); + } else { + contentValues.put(key, value); + } + } + public void copyFrom(Program other) { if (this == other) { return; @@ -523,13 +547,6 @@ public final class Program extends BaseProgram implements Comparable, P mRecordingProhibited = other.mRecordingProhibited; } - /** - * Checks whether the program is episodic or not. - */ - public boolean isEpisodic() { - return mSeriesId != null; - } - /** * A Builder for the Program class */ @@ -799,8 +816,12 @@ public final class Program extends BaseProgram implements Comparable, P */ public Program build() { // Generate the series ID for the episodic program of other TV input. - if (TextUtils.isEmpty(mProgram.mSeriesId) + if (TextUtils.isEmpty(mProgram.mTitle)) { + // If title is null, series cannot be generated for this program. + setSeriesId(null); + } else if (TextUtils.isEmpty(mProgram.mSeriesId) && !TextUtils.isEmpty(mProgram.mEpisodeNumber)) { + // If series ID is not set, generate it for the episodic program of other TV input. setSeriesId(BaseProgram.generateSeriesId(mProgram.mPackageName, mProgram.mTitle)); } Program program = new Program(); @@ -820,17 +841,20 @@ public final class Program extends BaseProgram implements Comparable, P } /** - * Loads the program poster art and returns it via {@code callback}.

+ * Loads the program poster art and returns it via {@code callback}. *

* Note that it may directly call {@code callback} if the program poster art already is loaded. + * + * @return {@code true} if the load is complete and the callback is executed. */ @UiThread - public void loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, + public boolean loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, ImageLoader.ImageLoaderCallback callback) { if (mPosterArtUri == null) { - return; + return false; } - ImageLoader.loadBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight, callback); + return ImageLoader.loadBitmap( + context, mPosterArtUri, posterArtWidth, posterArtHeight, callback); } public static boolean isDuplicate(Program p1, Program p2) { diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java index d2af33a7..8cb5e74a 100644 --- a/src/com/android/tv/data/ProgramDataManager.java +++ b/src/com/android/tv/data/ProgramDataManager.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.support.annotation.AnyThread; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; import android.util.ArraySet; @@ -35,8 +36,6 @@ import android.util.LruCache; import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.experiments.Experiments; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.Clock; import com.android.tv.util.MultiLongSparseArray; @@ -51,6 +50,7 @@ import java.util.ListIterator; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @MainThread @@ -85,10 +85,12 @@ public class ProgramDataManager implements MemoryManageable { private final Clock mClock; private final ContentResolver mContentResolver; private boolean mStarted; + // Updated only on the main thread. + private volatile boolean mCurrentProgramsLoadFinished; private ProgramsUpdateTask mProgramsUpdateTask; private final LongSparseArray mProgramUpdateTaskMap = new LongSparseArray<>(); - private final Map mChannelIdCurrentProgramMap = new HashMap<>(); + private final Map mChannelIdCurrentProgramMap = new ConcurrentHashMap<>(); private final MultiLongSparseArray mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>(); private final Handler mHandler; @@ -109,17 +111,14 @@ public class ProgramDataManager implements MemoryManageable { private boolean mPauseProgramUpdate = false; private final LruCache mZeroLengthProgramCache = new LruCache<>(10); - private final EpgFetcher mEpgFetcher; + @MainThread public ProgramDataManager(Context context) { - this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper(), - EpgFetcher.getInstance(context)); + this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper()); } @VisibleForTesting - ProgramDataManager(ContentResolver contentResolver, Clock time, Looper looper, - EpgFetcher epgFetcher) { - mEpgFetcher = epgFetcher; + ProgramDataManager(ContentResolver contentResolver, Clock time, Looper looper) { mClock = time; mContentResolver = contentResolver; mHandler = new MyHandler(looper); @@ -175,9 +174,6 @@ public class ProgramDataManager implements MemoryManageable { } mContentResolver.registerContentObserver(Programs.CONTENT_URI, true, mProgramObserver); - if (mEpgFetcher != null && Experiments.CLOUD_EPG.get()) { - mEpgFetcher.start(); - } } /** @@ -190,10 +186,6 @@ public class ProgramDataManager implements MemoryManageable { return; } mStarted = false; - - if (mEpgFetcher != null) { - mEpgFetcher.stop(); - } mContentResolver.unregisterContentObserver(mProgramObserver); mHandler.removeCallbacksAndMessages(null); @@ -205,13 +197,23 @@ public class ProgramDataManager implements MemoryManageable { } } - /** - * Returns the current program at the specified channel. - */ + @AnyThread + public boolean isCurrentProgramsLoadFinished() { + return mCurrentProgramsLoadFinished; + } + + /** Returns the current program at the specified channel. */ + @AnyThread public Program getCurrentProgram(long channelId) { return mChannelIdCurrentProgramMap.get(channelId); } + /** Returns all the current programs. */ + @AnyThread + public List getCurrentPrograms() { + return new ArrayList<>(mChannelIdCurrentProgramMap.values()); + } + /** * Reloads program data. */ @@ -338,19 +340,19 @@ public class ProgramDataManager implements MemoryManageable { } private void notifyCurrentProgramUpdate(long channelId, Program program) { - for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners .get(channelId)) { listener.onCurrentProgramUpdated(channelId, program); - } + } for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners .get(Channel.INVALID_ID)) { listener.onCurrentProgramUpdated(channelId, program); - } + } } private void updateCurrentProgram(long channelId, Program program) { - Program previousProgram = mChannelIdCurrentProgramMap.put(channelId, program); + Program previousProgram = program == null ? mChannelIdCurrentProgramMap.remove(channelId) + : mChannelIdCurrentProgramMap.put(channelId, program); if (!Objects.equals(program, previousProgram)) { if (mPrefetchEnabled) { removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program); @@ -581,22 +583,22 @@ public class ProgramDataManager implements MemoryManageable { protected void onPostExecute(List programs) { if (DEBUG) Log.d(TAG, "ProgramsUpdateTask done"); mProgramsUpdateTask = null; - if (programs == null) { - return; - } - Set removedChannelIds = new HashSet<>(mChannelIdCurrentProgramMap.keySet()); - for (Program program : programs) { - long channelId = program.getChannelId(); - updateCurrentProgram(channelId, program); - removedChannelIds.remove(channelId); - } - for (Long channelId : removedChannelIds) { - if (mPrefetchEnabled) { - mChannelIdProgramCache.remove(channelId); + if (programs != null) { + Set removedChannelIds = new HashSet<>(mChannelIdCurrentProgramMap.keySet()); + for (Program program : programs) { + long channelId = program.getChannelId(); + updateCurrentProgram(channelId, program); + removedChannelIds.remove(channelId); + } + for (Long channelId : removedChannelIds) { + if (mPrefetchEnabled) { + mChannelIdProgramCache.remove(channelId); + } + mChannelIdCurrentProgramMap.remove(channelId); + notifyCurrentProgramUpdate(channelId, null); } - mChannelIdCurrentProgramMap.remove(channelId); - notifyCurrentProgramUpdate(channelId, null); } + mCurrentProgramsLoadFinished = true; } } diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java index fe461f14..709863cf 100644 --- a/src/com/android/tv/data/StreamInfo.java +++ b/src/com/android/tv/data/StreamInfo.java @@ -38,5 +38,9 @@ public interface StreamInfo { int getAudioChannelCount(); boolean hasClosedCaption(); boolean isVideoAvailable(); + /** + * Returns true, if video or audio is available. + */ + boolean isVideoOrAudioAvailable(); int getVideoUnavailableReason(); } diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java index 59319338..3edd7b1a 100644 --- a/src/com/android/tv/data/WatchedHistoryManager.java +++ b/src/com/android/tv/data/WatchedHistoryManager.java @@ -10,15 +10,14 @@ import android.os.Looper; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; import android.util.Log; import com.android.tv.common.SharedPreferencesUtils; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Scanner; import java.util.concurrent.TimeUnit; @@ -28,15 +27,15 @@ import java.util.concurrent.TimeUnit; * *

When there is no access to watched table of TvProvider, * this class is used to build up watched history and to compute recent channels. + *

Note that this class is not thread safe. Please use this on one thread. */ public class WatchedHistoryManager { private final static String TAG = "WatchedHistoryManager"; - private final boolean DEBUG = false; + private final static boolean DEBUG = false; private static final int MAX_HISTORY_SIZE = 10000; private static final String PREF_KEY_LAST_INDEX = "last_index"; private static final long MIN_DURATION_MS = TimeUnit.SECONDS.toMillis(10); - private static final long RECENT_CHANNEL_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); private final List mWatchedHistory = new ArrayList<>(); private final List mPendingRecords = new ArrayList<>(); @@ -92,11 +91,7 @@ public class WatchedHistoryManager { WatchedHistoryManager(Context context, int maxHistorySize) { mContext = context.getApplicationContext(); mMaxHistorySize = maxHistorySize; - if (Looper.myLooper() == null) { - mHandler = new Handler(Looper.getMainLooper()); - } else { - mHandler = new Handler(); - } + mHandler = new Handler(); } /** @@ -107,56 +102,70 @@ public class WatchedHistoryManager { return; } mStarted = true; - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - mSharedPreferences = mContext.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); - mLastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1); - if (mLastIndex >= 0 && mLastIndex < mMaxHistorySize) { - for (int i = 0; i <= mLastIndex; ++i) { - WatchedRecord record = - decode(mSharedPreferences.getString(getSharedPreferencesKey(i), - null)); - if (record != null) { - mWatchedHistory.add(record); - } - } - } else if (mLastIndex >= mMaxHistorySize) { - for (long i = mLastIndex - mMaxHistorySize + 1; i <= mLastIndex; ++i) { - WatchedRecord record = decode(mSharedPreferences.getString( - getSharedPreferencesKey(i), null)); - if (record != null) { - mWatchedHistory.add(record); - } - } + if (Looper.myLooper() == Looper.getMainLooper()) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + loadWatchedHistory(); + return null; } - return null; - } - @Override - protected void onPostExecute(Void params) { - mLoaded = true; - if (DEBUG) { - Log.d(TAG, "Loaded: size=" + mWatchedHistory.size() + " index=" + mLastIndex); + @Override + protected void onPostExecute(Void params) { + onLoadFinished(); } - if (!mPendingRecords.isEmpty()) { - Editor editor = mSharedPreferences.edit(); - for (WatchedRecord record : mPendingRecords) { - mWatchedHistory.add(record); - ++mLastIndex; - editor.putString(getSharedPreferencesKey(mLastIndex), encode(record)); - } - editor.putLong(PREF_KEY_LAST_INDEX, mLastIndex).apply(); - mPendingRecords.clear(); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + loadWatchedHistory(); + onLoadFinished(); + } + } + + @WorkerThread + private void loadWatchedHistory() { + mSharedPreferences = mContext.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); + mLastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1); + if (mLastIndex >= 0 && mLastIndex < mMaxHistorySize) { + for (int i = 0; i <= mLastIndex; ++i) { + WatchedRecord record = + decode(mSharedPreferences.getString(getSharedPreferencesKey(i), + null)); + if (record != null) { + mWatchedHistory.add(record); } - if (mListener != null) { - mListener.onLoadFinished(); + } + } else if (mLastIndex >= mMaxHistorySize) { + for (long i = mLastIndex - mMaxHistorySize + 1; i <= mLastIndex; ++i) { + WatchedRecord record = decode(mSharedPreferences.getString( + getSharedPreferencesKey(i), null)); + if (record != null) { + mWatchedHistory.add(record); } - mSharedPreferences.registerOnSharedPreferenceChangeListener( - mOnSharedPreferenceChangeListener); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void onLoadFinished() { + mLoaded = true; + if (DEBUG) { + Log.d(TAG, "Loaded: size=" + mWatchedHistory.size() + " index=" + mLastIndex); + } + if (!mPendingRecords.isEmpty()) { + Editor editor = mSharedPreferences.edit(); + for (WatchedRecord record : mPendingRecords) { + mWatchedHistory.add(record); + ++mLastIndex; + editor.putString(getSharedPreferencesKey(mLastIndex), encode(record)); + } + editor.putLong(PREF_KEY_LAST_INDEX, mLastIndex).apply(); + mPendingRecords.clear(); + } + if (mListener != null) { + mListener.onLoadFinished(); + } + mSharedPreferences.registerOnSharedPreferenceChangeListener( + mOnSharedPreferenceChangeListener); } @VisibleForTesting @@ -204,52 +213,6 @@ public class WatchedHistoryManager { return Collections.unmodifiableList(mWatchedHistory); } - /** - * Returns the list of recently watched channels. - */ - public List buildRecentChannel(ChannelDataManager channelDataManager, int maxCount) { - List list = new ArrayList<>(); - Map durationMap = new HashMap<>(); - for (int i = mWatchedHistory.size() - 1; i >= 0; --i) { - WatchedRecord record = mWatchedHistory.get(i); - long channelId = record.channelId; - Channel channel = channelDataManager.getChannel(channelId); - if (channel == null || !channel.isBrowsable()) { - continue; - } - Long duration = durationMap.get(channelId); - if (duration == null) { - duration = 0L; - } - if (duration >= RECENT_CHANNEL_THRESHOLD_MS) { - continue; - } - if (list.isEmpty()) { - // We put the first recent channel regardless of RECENT_CHANNEL_THREASHOLD. - // It has the similar functionality as the previous channel in a usual remote - // controller. - list.add(channel); - durationMap.put(channelId, RECENT_CHANNEL_THRESHOLD_MS); - } else { - duration += record.duration; - durationMap.put(channelId, duration); - if (duration >= RECENT_CHANNEL_THRESHOLD_MS) { - list.add(channel); - } - } - if (list.size() >= maxCount) { - break; - } - } - if (DEBUG) { - Log.d(TAG, "Build recent channel"); - for (Channel channel : list) { - Log.d(TAG, "recent channel: " + channel); - } - } - return list; - } - @VisibleForTesting WatchedRecord getRecord(int reverseIndex) { return mWatchedHistory.get(mWatchedHistory.size() - 1 - reverseIndex); diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java new file mode 100644 index 00000000..5693c877 --- /dev/null +++ b/src/com/android/tv/data/epg/EpgFetchHelper.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.data.epg; + +import android.content.ContentProviderOperation; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.media.tv.TvContract.Programs; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.support.annotation.WorkerThread; +import android.text.TextUtils; +import android.util.Log; + +import com.android.tv.data.Program; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** The helper class for {@link com.android.tv.data.epg.EpgFetcher} */ +class EpgFetchHelper { + private static final String TAG = "EpgFetchHelper"; + private static final boolean DEBUG = false; + + private static final long PROGRAM_QUERY_DURATION_MS = TimeUnit.DAYS.toMillis(30); + private static final int BATCH_OPERATION_COUNT = 100; + + // Value: Long + private static final String KEY_LAST_UPDATED_EPG_TIMESTAMP = + "com.android.tv.data.epg.EpgFetcher.LastUpdatedEpgTimestamp"; + // Value: String + private static final String KEY_LAST_LINEUP_ID = + "com.android.tv.data.epg.EpgFetcher.LastLineupId"; + + private static long sLastEpgUpdatedTimestamp = -1; + private static String sLastLineupId; + + private EpgFetchHelper() { } + + /** + * Updates newly fetched EPG data for the given channel to local providers. The method will + * compare the broadcasting time and try to match each newly fetched program with old programs + * of that channel in the database one by one. It will update the matched old program, or insert + * the new program if there is no matching program can be found in the database and at the same + * time remove those old programs which conflicts with the inserted one. + + * @param channelId the target channel ID. + * @param fetchedPrograms the newly fetched program data. + * @return {@code true} if new program data are successfully updated. Otherwise {@code false}. + */ + static boolean updateEpgData(Context context, long channelId, List fetchedPrograms) { + final int fetchedProgramsCount = fetchedPrograms.size(); + if (fetchedProgramsCount == 0) { + return false; + } + boolean updated = false; + long startTimeMs = System.currentTimeMillis(); + long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION_MS; + List oldPrograms = queryPrograms(context, channelId, startTimeMs, endTimeMs); + int oldProgramsIndex = 0; + int newProgramsIndex = 0; + + // Compare the new programs with old programs one by one and update/delete the old one + // or insert new program if there is no matching program in the database. + ArrayList ops = new ArrayList<>(); + while (newProgramsIndex < fetchedProgramsCount) { + Program oldProgram = oldProgramsIndex < oldPrograms.size() + ? oldPrograms.get(oldProgramsIndex) : null; + Program newProgram = fetchedPrograms.get(newProgramsIndex); + boolean addNewProgram = false; + if (oldProgram != null) { + if (oldProgram.equals(newProgram)) { + // Exact match. No need to update. Move on to the next programs. + oldProgramsIndex++; + newProgramsIndex++; + } else if (hasSameTitleAndOverlap(oldProgram, newProgram)) { + // Partial match. Update the old program with the new one. + // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There + // could be application specific settings which belong to the old program. + ops.add(ContentProviderOperation.newUpdate( + TvContract.buildProgramUri(oldProgram.getId())) + .withValues(Program.toContentValues(newProgram)) + .build()); + oldProgramsIndex++; + newProgramsIndex++; + } else if (oldProgram.getEndTimeUtcMillis() < newProgram.getEndTimeUtcMillis()) { + // No match. Remove the old program first to see if the next program in + // {@code oldPrograms} partially matches the new program. + ops.add(ContentProviderOperation.newDelete( + TvContract.buildProgramUri(oldProgram.getId())) + .build()); + oldProgramsIndex++; + } else { + // No match. The new program does not match any of the old programs. Insert + // it as a new program. + addNewProgram = true; + newProgramsIndex++; + } + } else { + // No old programs. Just insert new programs. + addNewProgram = true; + newProgramsIndex++; + } + if (addNewProgram) { + ops.add(ContentProviderOperation + .newInsert(Programs.CONTENT_URI) + .withValues(Program.toContentValues(newProgram)) + .build()); + } + // Throttle the batch operation not to cause TransactionTooLargeException. + if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) { + try { + if (DEBUG) { + int size = ops.size(); + Log.d(TAG, "Running " + size + " operations for channel " + channelId); + for (int i = 0; i < size; ++i) { + Log.d(TAG, "Operation(" + i + "): " + ops.get(i)); + } + } + context.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); + updated = true; + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Failed to insert programs.", e); + return updated; + } + ops.clear(); + } + } + if (DEBUG) { + Log.d(TAG, "Updated " + fetchedProgramsCount + " programs for channel " + channelId); + } + return updated; + } + + private static List queryPrograms(Context context, long channelId, + long startTimeMs, long endTimeMs) { + try (Cursor c = context.getContentResolver().query( + TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs), + Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) { + if (c == null) { + return Collections.emptyList(); + } + ArrayList programs = new ArrayList<>(); + while (c.moveToNext()) { + programs.add(Program.fromCursor(c)); + } + return programs; + } + } + + /** + * Returns {@code true} if the {@code oldProgram} needs to be updated with the + * {@code newProgram}. + */ + private static boolean hasSameTitleAndOverlap(Program oldProgram, Program newProgram) { + // NOTE: Here, we update the old program if it has the same title and overlaps with the + // new program. The test logic is just an example and you can modify this. E.g. check + // whether the both programs have the same program ID if your EPG supports any ID for + // the programs. + return TextUtils.equals(oldProgram.getTitle(), newProgram.getTitle()) + && oldProgram.getStartTimeUtcMillis() <= newProgram.getEndTimeUtcMillis() + && newProgram.getStartTimeUtcMillis() <= oldProgram.getEndTimeUtcMillis(); + } + + /** + * Sets the last known lineup ID into shared preferences for future usage. If channels are not + * re-scanned, EPG fetcher can directly use this value instead of checking the correct lineup ID + * every time when it needs to fetch EPG data. + */ + @WorkerThread + synchronized static void setLastLineupId(Context context, String lineupId) { + if (DEBUG) { + if (lineupId == null) { + Log.d(TAG, "Clear stored lineup id: " + sLastLineupId); + } + } + sLastLineupId = lineupId; + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(KEY_LAST_LINEUP_ID, lineupId).apply(); + } + + /** + * Gets the last known lineup ID from shared preferences. + */ + synchronized static String getLastLineupId(Context context) { + if (sLastLineupId == null) { + sLastLineupId = PreferenceManager.getDefaultSharedPreferences(context) + .getString(KEY_LAST_LINEUP_ID, null); + } + if (DEBUG) Log.d(TAG, "Last lineup is " + sLastLineupId); + return sLastLineupId; + } + + /** + * Sets the last updated timestamp of EPG data into shared preferences. If the EPG data is not + * out-dated, it's not necessary for EPG fetcher to fetch EPG again. + */ + @WorkerThread + synchronized static void setLastEpgUpdatedTimestamp(Context context, long timestamp) { + sLastEpgUpdatedTimestamp = timestamp; + PreferenceManager.getDefaultSharedPreferences(context).edit().putLong( + KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).apply(); + } + + /** + * Gets the last updated timestamp of EPG data. + */ + synchronized static long getLastEpgUpdatedTimestamp(Context context) { + if (sLastEpgUpdatedTimestamp < 0) { + sLastEpgUpdatedTimestamp = PreferenceManager.getDefaultSharedPreferences(context) + .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0); + } + return sLastEpgUpdatedTimestamp; + } +} \ No newline at end of file diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java index 3b093b6a..24f8b826 100644 --- a/src/com/android/tv/data/epg/EpgFetcher.java +++ b/src/com/android/tv/data/epg/EpgFetcher.java @@ -16,570 +16,720 @@ package com.android.tv.data.epg; -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.ContentProviderOperation; -import android.content.ContentValues; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; import android.content.Context; -import android.content.OperationApplicationException; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.location.Address; -import android.media.tv.TvContentRating; -import android.media.tv.TvContract; -import android.media.tv.TvContract.Programs; -import android.media.tv.TvContract.Programs.Genres; import android.media.tv.TvInputInfo; +import android.net.TrafficStats; +import android.os.AsyncTask; +import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; -import android.preference.PreferenceManager; +import android.support.annotation.AnyThread; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.os.BuildCompat; +import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; +import com.android.tv.ApplicationSingletons; +import com.android.tv.Features; import com.android.tv.TvApplication; -import com.android.tv.common.WeakHandler; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.TvCommonUtils; +import com.android.tv.config.RemoteConfigUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.InternalDataUtils; +import com.android.tv.data.ChannelLogoFetcher; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.perf.EventNames; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.TimerEvent; +import com.android.tv.tuner.util.PostalCodeUtils; import com.android.tv.util.LocationUtils; -import com.android.tv.util.RecurringRunner; +import com.android.tv.util.NetworkTrafficTags; import com.android.tv.util.Utils; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Objects; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** - * An utility class to fetch the EPG. This class isn't thread-safe. + * The service class to fetch EPG routinely or on-demand during channel scanning + * + *

Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one + * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on + * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}. */ public class EpgFetcher { private static final String TAG = "EpgFetcher"; private static final boolean DEBUG = false; - private static final int MSG_FETCH_EPG = 1; + private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101; + + private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10); + + private static final int REASON_EPG_READER_NOT_READY = 1; + private static final int REASON_LOCATION_INFO_UNAVAILABLE = 2; + private static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3; + private static final int REASON_NO_EPG_DATA_RETURNED = 4; + private static final int REASON_NO_NEW_EPG = 5; - private static final long EPG_PREFETCH_RECURRING_PERIOD_MS = TimeUnit.HOURS.toMillis(4); - private static final long EPG_READER_INIT_WAIT_MS = TimeUnit.MINUTES.toMillis(1); - private static final long LOCATION_INIT_WAIT_MS = TimeUnit.SECONDS.toMillis(10); - private static final long LOCATION_ERROR_WAIT_MS = TimeUnit.HOURS.toMillis(1); - private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(30); + private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10); - private static final int BATCH_OPERATION_COUNT = 100; + private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3); + private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2); - private static final String SUPPORTED_COUNTRY_CODE = Locale.US.getCountry(); - private static final String CONTENT_RATING_SEPARATOR = ","; + private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4; + private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour"; - // Value: Long - private static final String KEY_LAST_UPDATED_EPG_TIMESTAMP = - "com.android.tv.data.epg.EpgFetcher.LastUpdatedEpgTimestamp"; - // Value: String - private static final String KEY_LAST_LINEUP_ID = - "com.android.tv.data.epg.EpgFetcher.LastLineupId"; + private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1; + private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2; + private static final int MSG_FINISH_FETCH_DURING_SCAN = 3; + private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4; + + private static final int QUERY_CHANNEL_COUNT = 50; + private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3; private static EpgFetcher sInstance; private final Context mContext; private final ChannelDataManager mChannelDataManager; private final EpgReader mEpgReader; - private EpgFetcherHandler mHandler; - private RecurringRunner mRecurringRunner; - private boolean mStarted; - - private long mLastEpgTimestamp = -1; - private String mLineupId; - - public static synchronized EpgFetcher getInstance(Context context) { + private final PerformanceMonitor mPerformanceMonitor; + private FetchAsyncTask mFetchTask; + private FetchDuringScanHandler mFetchDuringScanHandler; + private long mEpgTimeStamp; + private List mPossibleLineups; + private final Object mPossibleLineupsLock = new Object(); + private final Object mFetchDuringScanHandlerLock = new Object(); + // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished. + private boolean mScanStarted; + + private final long mRoutineIntervalMs; + private final long mEpgDataExpiredTimeLimitMs; + private final long mFastFetchDurationSec; + + public static EpgFetcher getInstance(Context context) { if (sInstance == null) { - sInstance = new EpgFetcher(context.getApplicationContext()); + sInstance = new EpgFetcher(context); } return sInstance; } - /** - * Creates and returns {@link EpgReader}. - */ - public static EpgReader createEpgReader(Context context) { + /** Creates and returns {@link EpgReader}. */ + public static EpgReader createEpgReader(Context context, String region) { return new StubEpgReader(context); } private EpgFetcher(Context context) { - mContext = context; - mEpgReader = new StubEpgReader(mContext); - mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); - mChannelDataManager.addListener(new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()"); - handleChannelChanged(); + mContext = context.getApplicationContext(); + ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); + mChannelDataManager = applicationSingletons.getChannelDataManager(); + mPerformanceMonitor = applicationSingletons.getPerformanceMonitor(); + mEpgReader = createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext)); + + int remoteInteval = + (int) RemoteConfigUtils.getRemoteConfig( + context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR); + mRoutineIntervalMs = + remoteInteval < 0 + ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR) + : TimeUnit.HOURS.toMillis(remoteInteval); + mEpgDataExpiredTimeLimitMs = mRoutineIntervalMs * 2; + mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + mRoutineIntervalMs / 1000; + } + + /** + * Starts the routine service of EPG fetching. It use {@link JobScheduler} to schedule the EPG + * fetching routine. The EPG fetching routine will be started roughly every 4 hours, unless + * the channel scanning of tuner input is started. + */ + @MainThread + public void startRoutineService() { + JobScheduler jobScheduler = + (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); + for (JobInfo job : jobScheduler.getAllPendingJobs()) { + if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) { + return; } + } + JobInfo job = + new JobInfo.Builder( + EPG_ROUTINELY_FETCHING_JOB_ID, + new ComponentName(mContext, EpgFetchService.class)) + .setPeriodic(mRoutineIntervalMs) + .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL) + .setPersisted(true) + .build(); + jobScheduler.schedule(job); + Log.i(TAG, "EPG fetching routine service started."); + } + /** + * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated + * by routine fetching service due to various reasons. + */ + @MainThread + public void fetchImmediatelyIfNeeded() { + if (TvCommonUtils.isRunningInTest()) { + // Do not run EpgFetcher in test. + return; + } + new AsyncTask() { @Override - public void onChannelListUpdated() { - if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()"); - handleChannelChanged(); + protected Long doInBackground(Void... args) { + return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext); } @Override - public void onChannelBrowsableChanged() { - if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelBrowsableChanged()"); - handleChannelChanged(); + protected void onPostExecute(Long result) { + if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) + > mEpgDataExpiredTimeLimitMs) { + Log.i(TAG, "EPG data expired. Start fetching immediately."); + fetchImmediately(); + } } - }); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private void handleChannelChanged() { - if (mStarted) { - if (needToStop()) { - stop(); - } - } else { - start(); - } - } + /** + * Fetches EPG immediately. + */ + @MainThread + public void fetchImmediately() { + if (!mChannelDataManager.isDbLoadFinished()) { + mChannelDataManager.addListener(new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + executeFetchTaskIfPossible(null, null); + } - private boolean needToStop() { - return !canStart(); - } + @Override + public void onChannelListUpdated() { } - private boolean canStart() { - if (DEBUG) Log.d(TAG, "canStart()"); - boolean hasInternalTunerChannel = false; - for (TvInputInfo input : TvApplication.getSingletons(mContext).getTvInputManagerHelper() - .getTvInputInfos(true, true)) { - String inputId = input.getId(); - if (Utils.isInternalTvInput(mContext, inputId) - && mChannelDataManager.getChannelCountForInput(inputId) > 0) { - hasInternalTunerChannel = true; - break; - } - } - if (!hasInternalTunerChannel) { - if (DEBUG) Log.d(TAG, "No internal tuner channels."); - return false; + @Override + public void onChannelBrowsableChanged() { } + }); + } else { + executeFetchTaskIfPossible(null, null); } + } - if (!TextUtils.isEmpty(getLastLineupId())) { - return true; - } - if (mContext.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - if (DEBUG) Log.d(TAG, "No permission to check the current location."); - return false; + /** + * Notifies EPG fetch service that channel scanning is started. + */ + @MainThread + public void onChannelScanStarted() { + if (mScanStarted || !Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { + return; } - - try { - Address address = LocationUtils.getCurrentAddress(mContext); - if (address != null - && !TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) { - if (DEBUG) Log.d(TAG, "Country not supported: " + address.getCountryCode()); - return false; + mScanStarted = true; + stopFetchingJob(); + synchronized (mFetchDuringScanHandlerLock) { + if (mFetchDuringScanHandler == null) { + HandlerThread thread = new HandlerThread("EpgFetchDuringScan"); + thread.start(); + mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper()); } - } catch (SecurityException e) { - Log.w(TAG, "No permission to get the current location", e); - return false; - } catch (IOException e) { - Log.w(TAG, "IO Exception when getting the current location", e); + mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN); } - return true; + Log.i(TAG, "EPG fetching on channel scanning started."); } /** - * Starts fetching EPG. + * Notifies EPG fetch service that channel scanning is finished. */ @MainThread - public void start() { - if (DEBUG) Log.d(TAG, "start()"); - if (mStarted) { - if (DEBUG) Log.d(TAG, "EpgFetcher thread already started."); + public void onChannelScanFinished() { + if (!mScanStarted) { return; } - if (!canStart()) { - return; + mScanStarted = false; + mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); + } + + @MainThread + private void stopFetchingJob() { + if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job..."); + if (mFetchTask != null) { + mFetchTask.cancel(true); + mFetchTask = null; + Log.i(TAG, "EPG routinely fetching job stopped."); } - mStarted = true; - if (DEBUG) Log.d(TAG, "Starting EpgFetcher thread."); - HandlerThread handlerThread = new HandlerThread("EpgFetcher"); - handlerThread.start(); - mHandler = new EpgFetcherHandler(handlerThread.getLooper(), this); - mRecurringRunner = new RecurringRunner(mContext, EPG_PREFETCH_RECURRING_PERIOD_MS, - new EpgRunner(), null); - mRecurringRunner.start(); - if (DEBUG) Log.d(TAG, "EpgFetcher thread started successfully."); } - /** - * Starts fetching EPG immediately if possible without waiting for the timer. - */ @MainThread - public void startImmediately() { - start(); - if (mStarted) { - if (DEBUG) Log.d(TAG, "Starting fetcher immediately"); - fetchEpg(); + private boolean executeFetchTaskIfPossible(JobService service, JobParameters params) { + SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished()); + if (!TvCommonUtils.isRunningInTest() && checkFetchPrerequisite()) { + mFetchTask = new FetchAsyncTask(service, params); + mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return true; } + return false; } - /** - * Stops fetching EPG. - */ @MainThread - public void stop() { - if (DEBUG) Log.d(TAG, "stop()"); - if (!mStarted) { - return; + private boolean checkFetchPrerequisite() { + if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job."); + if (!Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { + Log.i(TAG, "Cannot start routine service: country not supported: " + + LocationUtils.getCurrentCountry(mContext)); + return false; + } + if (mFetchTask != null) { + // Fetching job is already running or ready to run, no need to start again. + return false; + } + if (mFetchDuringScanHandler != null) { + if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels."); + return false; + } + if (getTunerChannelCount() == 0) { + if (DEBUG) Log.d(TAG, "Cannot start routine service: no internal tuner channels."); + return false; } - mStarted = false; - mRecurringRunner.stop(); - mHandler.removeCallbacksAndMessages(null); - mHandler.getLooper().quit(); + if (!TextUtils.isEmpty(EpgFetchHelper.getLastLineupId(mContext))) { + return true; + } + if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { + return true; + } + return true; } - private void fetchEpg() { - fetchEpg(0); + @MainThread + private int getTunerChannelCount() { + for (TvInputInfo input : TvApplication.getSingletons(mContext) + .getTvInputManagerHelper().getTvInputInfos(true, true)) { + String inputId = input.getId(); + if (Utils.isInternalTvInput(mContext, inputId)) { + return mChannelDataManager.getChannelCountForInput(inputId); + } + } + return 0; } - private void fetchEpg(long delay) { - mHandler.removeMessages(MSG_FETCH_EPG); - mHandler.sendEmptyMessageDelayed(MSG_FETCH_EPG, delay); + @AnyThread + private void clearUnusedLineups(@Nullable String lineupId) { + synchronized (mPossibleLineupsLock) { + if (mPossibleLineups == null) { + return; + } + for (Lineup lineup : mPossibleLineups) { + if (!TextUtils.equals(lineupId, lineup.id)) { + mEpgReader.clearCachedChannels(lineup.id); + } + } + mPossibleLineups = null; + } } - private void onFetchEpg() { - if (DEBUG) Log.d(TAG, "Start fetching EPG."); + @WorkerThread + private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) { if (!mEpgReader.isAvailable()) { - if (DEBUG) Log.d(TAG, "EPG reader is not temporarily available."); - fetchEpg(EPG_READER_INIT_WAIT_MS); - return; + Log.i(TAG, "EPG reader is temporarily unavailable."); + return REASON_EPG_READER_NOT_READY; } - String lineupId = getLastLineupId(); - if (lineupId == null) { - Address address; - try { - address = LocationUtils.getCurrentAddress(mContext); - } catch (IOException e) { - if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e); - fetchEpg(LOCATION_ERROR_WAIT_MS); - return; - } catch (SecurityException e) { - Log.w(TAG, "No permission to get the current location."); - return; + // Checks the EPG Timestamp. + mEpgTimeStamp = mEpgReader.getEpgTimestamp(); + if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) { + if (DEBUG) Log.d(TAG, "No new EPG."); + return REASON_NO_NEW_EPG; + } + // Updates postal code. + boolean postalCodeChanged = false; + try { + postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext); + } catch (IOException e) { + if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e); + if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { + return REASON_LOCATION_INFO_UNAVAILABLE; } - if (address == null) { - if (DEBUG) Log.d(TAG, "Null address returned."); - fetchEpg(LOCATION_INIT_WAIT_MS); - return; + } catch (SecurityException e) { + Log.w(TAG, "No permission to get the current location."); + if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { + return REASON_LOCATION_PERMISSION_NOT_GRANTED; } - if (DEBUG) Log.d(TAG, "Current location is " + address); - - lineupId = getLineupForAddress(address); - if (lineupId != null) { - if (DEBUG) Log.d(TAG, "Saving lineup " + lineupId + "found for " + address); - setLastLineupId(lineupId); - } else { - if (DEBUG) Log.d(TAG, "No lineup found for " + address); - return; + } catch (PostalCodeUtils.NoPostalCodeException e) { + Log.i(TAG, "Cannot get address or postal code."); + return REASON_LOCATION_INFO_UNAVAILABLE; + } + // Updates possible lineups if necessary. + SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset."); + if (postalCodeChanged || forceUpdatePossibleLineups + || EpgFetchHelper.getLastLineupId(mContext) == null) { + // To prevent main thread being blocked, though theoretically it should not happen. + List possibleLineups = + mEpgReader.getLineups(PostalCodeUtils.getLastPostalCode(mContext)); + if (possibleLineups.isEmpty()) { + return REASON_NO_EPG_DATA_RETURNED; + } + for (Lineup lineup : possibleLineups) { + mEpgReader.preloadChannels(lineup.id); + } + synchronized (mPossibleLineupsLock) { + mPossibleLineups = possibleLineups; } + EpgFetchHelper.setLastLineupId(mContext, null); } + return null; + } - // Check the EPG Timestamp. - long epgTimestamp = mEpgReader.getEpgTimestamp(); - if (epgTimestamp <= getLastUpdatedEpgTimestamp()) { - if (DEBUG) Log.d(TAG, "No new EPG."); + @WorkerThread + private void batchFetchEpg(List channels, long durationSec) { + Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + channels.size()); + if (channels.size() == 0) { return; } - - boolean updated = false; - List channels = mEpgReader.getChannels(lineupId); + List queryChannelIds = new ArrayList<>(QUERY_CHANNEL_COUNT); for (Channel channel : channels) { - List programs = new ArrayList<>(mEpgReader.getPrograms(channel.getId())); - Collections.sort(programs); - if (DEBUG) { - Log.d(TAG, "Fetched " + programs.size() + " programs for channel " + channel); - } - if (updateEpg(channel.getId(), programs)) { - updated = true; + queryChannelIds.add(channel.getId()); + if (queryChannelIds.size() >= QUERY_CHANNEL_COUNT) { + batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec)); + queryChannelIds.clear(); } } - - final boolean epgUpdated = updated; - setLastUpdatedEpgTimestamp(epgTimestamp); - mHandler.removeMessages(MSG_FETCH_EPG); - if (DEBUG) Log.d(TAG, "Fetching EPG is finished."); + if (!queryChannelIds.isEmpty()) { + batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec)); + } } - @Nullable - private String getLineupForAddress(Address address) { - String lineup = null; - if (TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) { - String postalCode = address.getPostalCode(); - if (!TextUtils.isEmpty(postalCode)) { - lineup = getLineupForPostalCode(postalCode); + @WorkerThread + private void batchUpdateEpg(Map> allPrograms) { + for (Map.Entry> entry : allPrograms.entrySet()) { + List programs = entry.getValue(); + if (programs == null) { + continue; } + Collections.sort(programs); + Log.i(TAG, "Batch fetched " + programs.size() + " programs for channel " + + entry.getKey()); + EpgFetchHelper.updateEpgData(mContext, entry.getKey(), programs); } - return lineup; } @Nullable - private String getLineupForPostalCode(String postalCode) { - List lineups = mEpgReader.getLineups(postalCode); - for (Lineup lineup : lineups) { - // TODO(EPG): handle more than OTA digital - if (lineup.type == Lineup.LINEUP_BROADCAST_DIGITAL) { - if (DEBUG) Log.d(TAG, "Setting lineup to " + lineup.name + "(" + lineup.id + ")"); - return lineup.id; + @WorkerThread + private String pickBestLineupId(List currentChannelList) { + String maxLineupId = null; + synchronized (mPossibleLineupsLock) { + if (mPossibleLineups == null) { + return null; + } + int maxCount = 0; + for (Lineup lineup : mPossibleLineups) { + int count = getMatchedChannelCount(lineup.id, currentChannelList); + Log.i(TAG, lineup.name + " (" + lineup.id + ") - " + count + " matches"); + if (count > maxCount) { + maxCount = count; + maxLineupId = lineup.id; + } } } - return null; + return maxLineupId; } - private long getLastUpdatedEpgTimestamp() { - if (mLastEpgTimestamp < 0) { - mLastEpgTimestamp = PreferenceManager.getDefaultSharedPreferences(mContext).getLong( - KEY_LAST_UPDATED_EPG_TIMESTAMP, 0); + @WorkerThread + private int getMatchedChannelCount(String lineupId, List currentChannelList) { + // Construct a list of display numbers for existing channels. + if (currentChannelList.isEmpty()) { + if (DEBUG) Log.d(TAG, "No existing channel to compare"); + return 0; + } + List numbers = new ArrayList<>(currentChannelList.size()); + for (Channel channel : currentChannelList) { + // We only support channels from internal tuner inputs. + if (Utils.isInternalTvInput(mContext, channel.getInputId())) { + numbers.add(channel.getDisplayNumber()); + } } - return mLastEpgTimestamp; + numbers.retainAll(mEpgReader.getChannelNumbers(lineupId)); + return numbers.size(); } - private void setLastUpdatedEpgTimestamp(long timestamp) { - mLastEpgTimestamp = timestamp; - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putLong( - KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).commit(); - } + public static class EpgFetchService extends JobService { + private EpgFetcher mEpgFetcher; - private String getLastLineupId() { - if (mLineupId == null) { - mLineupId = PreferenceManager.getDefaultSharedPreferences(mContext) - .getString(KEY_LAST_LINEUP_ID, null); + @Override + public void onCreate() { + super.onCreate(); + TvApplication.setCurrentRunningProcess(this, true); + mEpgFetcher = EpgFetcher.getInstance(this); } - if (DEBUG) Log.d(TAG, "Last lineup_id " + mLineupId); - return mLineupId; - } - private void setLastLineupId(String lineupId) { - mLineupId = lineupId; - PreferenceManager.getDefaultSharedPreferences(mContext).edit() - .putString(KEY_LAST_LINEUP_ID, lineupId).commit(); - } + @Override + public boolean onStartJob(JobParameters params) { + if (!mEpgFetcher.mChannelDataManager.isDbLoadFinished()) { + mEpgFetcher.mChannelDataManager.addListener(new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mEpgFetcher.mChannelDataManager.removeListener(this); + if (!mEpgFetcher.executeFetchTaskIfPossible(EpgFetchService.this, params)) { + jobFinished(params, false); + } + } - private boolean updateEpg(long channelId, List newPrograms) { - final int fetchedProgramsCount = newPrograms.size(); - if (fetchedProgramsCount == 0) { + @Override + public void onChannelListUpdated() { } + + @Override + public void onChannelBrowsableChanged() { } + }); + return true; + } else { + return mEpgFetcher.executeFetchTaskIfPossible(this, params); + } + } + + @Override + public boolean onStopJob(JobParameters params) { + mEpgFetcher.stopFetchingJob(); return false; } - boolean updated = false; - long startTimeMs = System.currentTimeMillis(); - long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION; - List oldPrograms = queryPrograms(channelId, startTimeMs, endTimeMs); - Program currentOldProgram = oldPrograms.size() > 0 ? oldPrograms.get(0) : null; - int oldProgramsIndex = 0; - int newProgramsIndex = 0; - // Skip the past programs. They will be automatically removed by the system. - if (currentOldProgram != null) { - long oldStartTimeUtcMillis = currentOldProgram.getStartTimeUtcMillis(); - for (Program program : newPrograms) { - if (program.getEndTimeUtcMillis() > oldStartTimeUtcMillis) { - break; - } - newProgramsIndex++; - } + } + + private class FetchAsyncTask extends AsyncTask { + private final JobService mService; + private final JobParameters mParams; + private List mCurrentChannelList; + private TimerEvent mTimerEvent; + + private FetchAsyncTask(JobService service, JobParameters params) { + mService = service; + mParams = params; } - // Compare the new programs with old programs one by one and update/delete the old one - // or insert new program if there is no matching program in the database. - ArrayList ops = new ArrayList<>(); - while (newProgramsIndex < fetchedProgramsCount) { - // TODO: Extract to method and make test. - Program oldProgram = oldProgramsIndex < oldPrograms.size() - ? oldPrograms.get(oldProgramsIndex) : null; - Program newProgram = newPrograms.get(newProgramsIndex); - boolean addNewProgram = false; - if (oldProgram != null) { - if (oldProgram.equals(newProgram)) { - // Exact match. No need to update. Move on to the next programs. - oldProgramsIndex++; - newProgramsIndex++; - } else if (isSameTitleAndOverlap(oldProgram, newProgram)) { - // Partial match. Update the old program with the new one. - // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There - // could be application specific settings which belong to the old program. - ops.add(ContentProviderOperation.newUpdate( - TvContract.buildProgramUri(oldProgram.getId())) - .withValues(toContentValues(newProgram)) - .build()); - oldProgramsIndex++; - newProgramsIndex++; - } else if (oldProgram.getEndTimeUtcMillis() - < newProgram.getEndTimeUtcMillis()) { - // No match. Remove the old program first to see if the next program in - // {@code oldPrograms} partially matches the new program. - ops.add(ContentProviderOperation.newDelete( - TvContract.buildProgramUri(oldProgram.getId())) - .build()); - oldProgramsIndex++; + + @Override + protected void onPreExecute() { + mTimerEvent = mPerformanceMonitor.startTimer(); + mCurrentChannelList = mChannelDataManager.getChannelList(); + } + + @Override + protected Integer doInBackground(Void... args) { + final int oldTag = TrafficStats.getThreadStatsTag(); + TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH); + try { + if (DEBUG) Log.d(TAG, "Start EPG routinely fetching."); + Integer failureReason = prepareFetchEpg(false); + // InterruptedException might be caught by RPC, we should check it here. + if (failureReason != null || this.isCancelled()) { + return failureReason; + } + String lineupId = EpgFetchHelper.getLastLineupId(mContext); + lineupId = lineupId == null ? pickBestLineupId(mCurrentChannelList) : lineupId; + if (lineupId != null) { + Log.i(TAG, "Selecting the lineup " + lineupId); + // During normal fetching process, the lineup ID should be confirmed since all + // channels are known, clear up possible lineups to save resources. + EpgFetchHelper.setLastLineupId(mContext, lineupId); + clearUnusedLineups(lineupId); } else { - // No match. The new program does not match any of the old programs. Insert - // it as a new program. - addNewProgram = true; - newProgramsIndex++; + Log.i(TAG, "Failed to get lineup id"); + return REASON_NO_EPG_DATA_RETURNED; } - } else { - // No old programs. Just insert new programs. - addNewProgram = true; - newProgramsIndex++; - } - if (addNewProgram) { - ops.add(ContentProviderOperation - .newInsert(TvContract.Programs.CONTENT_URI) - .withValues(toContentValues(newProgram)) - .build()); - } - // Throttle the batch operation not to cause TransactionTooLargeException. - if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) { - try { - if (DEBUG) { - int size = ops.size(); - Log.d(TAG, "Running " + size + " operations for channel " + channelId); - for (int i = 0; i < size; ++i) { - Log.d(TAG, "Operation(" + i + "): " + ops.get(i)); - } + final List channels = mEpgReader.getChannels(lineupId); + // InterruptedException might be caught by RPC, we should check it here. + if (this.isCancelled()) { + return null; + } + if (channels.isEmpty()) { + Log.i(TAG, "Failed to get EPG channels."); + return REASON_NO_EPG_DATA_RETURNED; + } + if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) + > mEpgDataExpiredTimeLimitMs) { + batchFetchEpg(channels, mFastFetchDurationSec); + } + new Handler(mContext.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + ChannelLogoFetcher.startFetchingChannelLogos( + mContext, channels); + } + }); + for (Channel channel : channels) { + if (this.isCancelled()) { + return null; } - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); - updated = true; - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Failed to insert programs.", e); - return updated; + long channelId = channel.getId(); + List programs = new ArrayList<>(mEpgReader.getPrograms(channelId)); + // InterruptedException might be caught by RPC, we should check it here. + Collections.sort(programs); + Log.i(TAG, "Fetched " + programs.size() + " programs for channel " + channelId); + EpgFetchHelper.updateEpgData(mContext, channelId, programs); } - ops.clear(); + EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp); + if (DEBUG) Log.d(TAG, "Fetching EPG is finished."); + return null; + } finally { + TrafficStats.setThreadStatsTag(oldTag); } } - if (DEBUG) { - Log.d(TAG, "Updated " + fetchedProgramsCount + " programs for channel " + channelId); - } - return updated; - } - private List queryPrograms(long channelId, long startTimeMs, long endTimeMs) { - try (Cursor c = mContext.getContentResolver().query( - TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs), - Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) { - if (c == null) { - return Collections.emptyList(); - } - ArrayList programs = new ArrayList<>(); - while (c.moveToNext()) { - programs.add(Program.fromCursor(c)); + @Override + protected void onPostExecute(Integer failureReason) { + mFetchTask = null; + if (failureReason == null || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED + || failureReason == REASON_NO_NEW_EPG) { + jobFinished(false); + } else { + // Applies back-off policy + jobFinished(true); } - return programs; + mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK); + mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK); } - } - /** - * Returns {@code true} if the {@code oldProgram} program needs to be updated with the - * {@code newProgram} program. - */ - private boolean isSameTitleAndOverlap(Program oldProgram, Program newProgram) { - // NOTE: Here, we update the old program if it has the same title and overlaps with the - // new program. The test logic is just an example and you can modify this. E.g. check - // whether the both programs have the same program ID if your EPG supports any ID for - // the programs. - return Objects.equals(oldProgram.getTitle(), newProgram.getTitle()) - && oldProgram.getStartTimeUtcMillis() <= newProgram.getEndTimeUtcMillis() - && newProgram.getStartTimeUtcMillis() <= oldProgram.getEndTimeUtcMillis(); - } + @Override + protected void onCancelled(Integer failureReason) { + clearUnusedLineups(null); + jobFinished(false); + } - @SuppressLint("InlinedApi") - @SuppressWarnings("deprecation") - private static ContentValues toContentValues(Program program) { - ContentValues values = new ContentValues(); - values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId()); - putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle()); - if (BuildCompat.isAtLeastN()) { - putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, - program.getSeasonNumber()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, - program.getEpisodeNumber()); - } else { - putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber()); - } - putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription()); - putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri()); - putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri()); - String[] canonicalGenres = program.getCanonicalGenres(); - if (canonicalGenres != null && canonicalGenres.length > 0) { - putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, - Genres.encode(canonicalGenres)); - } else { - putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, ""); - } - TvContentRating[] ratings = program.getContentRatings(); - if (ratings != null && ratings.length > 0) { - StringBuilder sb = new StringBuilder(ratings[0].flattenToString()); - for (int i = 1; i < ratings.length; ++i) { - sb.append(CONTENT_RATING_SEPARATOR); - sb.append(ratings[i].flattenToString()); + private void jobFinished(boolean reschedule) { + if (mService != null && mParams != null) { + // Task is executed from JobService, need to report jobFinished. + mService.jobFinished(mParams, reschedule); } - putValue(values, TvContract.Programs.COLUMN_CONTENT_RATING, sb.toString()); - } else { - putValue(values, TvContract.Programs.COLUMN_CONTENT_RATING, ""); - } - values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - program.getStartTimeUtcMillis()); - values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis()); - putValue(values, TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, - InternalDataUtils.serializeInternalProviderData(program)); - return values; - } - - private static void putValue(ContentValues contentValues, String key, String value) { - if (TextUtils.isEmpty(value)) { - contentValues.putNull(key); - } else { - contentValues.put(key, value); } } - private static void putValue(ContentValues contentValues, String key, byte[] value) { - if (value == null || value.length == 0) { - contentValues.putNull(key); - } else { - contentValues.put(key, value); - } - } + @WorkerThread + private class FetchDuringScanHandler extends Handler { + private final Set mFetchedChannelIdsDuringScan = new HashSet<>(); + private String mPossibleLineupId; + + private final ChannelDataManager.Listener mDuringScanChannelListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()"); + if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP + && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + Message.obtain(FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, new ArrayList<>( + mChannelDataManager.getChannelList())).sendToTarget(); + } + } + + @Override + public void onChannelListUpdated() { + if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()"); + if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP + && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + Message.obtain(FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, + mChannelDataManager.getChannelList()).sendToTarget(); + } + } + + @Override + public void onChannelBrowsableChanged() { + // Do nothing + } + }; - private static class EpgFetcherHandler extends WeakHandler { - public EpgFetcherHandler (@NonNull Looper looper, EpgFetcher ref) { - super(looper, ref); + @AnyThread + private FetchDuringScanHandler(Looper looper) { + super(looper); } @Override - public void handleMessage(Message msg, @NonNull EpgFetcher epgFetcher) { + public void handleMessage(Message msg) { switch (msg.what) { - case MSG_FETCH_EPG: - epgFetcher.onFetchEpg(); + case MSG_PREPARE_FETCH_DURING_SCAN: + case MSG_RETRY_PREPARE_FETCH_DURING_SCAN: + onPrepareFetchDuringScan(); break; - default: - super.handleMessage(msg); + case MSG_CHANNEL_UPDATED_DURING_SCAN: + if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + onChannelUpdatedDuringScan((List) msg.obj); + } + break; + case MSG_FINISH_FETCH_DURING_SCAN: + removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN); + if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); + } else { + onFinishFetchDuringScan(); + } break; } } - } - private class EpgRunner implements Runnable { - @Override - public void run() { - fetchEpg(); + private void onPrepareFetchDuringScan() { + Integer failureReason = prepareFetchEpg(true); + if (failureReason != null) { + sendEmptyMessageDelayed( + MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS); + return; + } + mChannelDataManager.addListener(mDuringScanChannelListener); + } + + private void onChannelUpdatedDuringScan(List currentChannelList) { + String lineupId = pickBestLineupId(currentChannelList); + Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId); + if (TextUtils.isEmpty(lineupId)) { + if (TextUtils.isEmpty(mPossibleLineupId)) { + return; + } + } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) { + mFetchedChannelIdsDuringScan.clear(); + mPossibleLineupId = lineupId; + } + List currentChannelIds = new ArrayList<>(); + for (Channel channel : currentChannelList) { + currentChannelIds.add(channel.getId()); + } + mFetchedChannelIdsDuringScan.retainAll(currentChannelIds); + List newChannels = new ArrayList<>(); + for (Channel channel : mEpgReader.getChannels(mPossibleLineupId)) { + if (!mFetchedChannelIdsDuringScan.contains(channel.getId())) { + newChannels.add(channel); + mFetchedChannelIdsDuringScan.add(channel.getId()); + } + } + batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC); + } + + private void onFinishFetchDuringScan() { + mChannelDataManager.removeListener(mDuringScanChannelListener); + EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId); + clearUnusedLineups(null); + mFetchedChannelIdsDuringScan.clear(); + synchronized (mFetchDuringScanHandlerLock) { + if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) { + removeCallbacksAndMessages(null); + getLooper().quit(); + mFetchDuringScanHandler = null; + } + } + // Clear timestamp to make routine service start right away. + EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0); + Log.i(TAG, "EPG Fetching during channel scanning finished."); + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + fetchImmediately(); + } + }); } } } diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java index 4f3b6f52..c5aeca27 100644 --- a/src/com/android/tv/data/epg/EpgReader.java +++ b/src/com/android/tv/data/epg/EpgReader.java @@ -16,15 +16,17 @@ 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.dvr.SeriesInfo; +import com.android.tv.dvr.data.SeriesInfo; import java.util.List; +import java.util.Map; /** * An interface used to retrieve the EPG data. This class should be used in worker thread. @@ -42,25 +44,48 @@ public interface EpgReader { */ long getEpgTimestamp(); + /** Sets the region code. */ + void setRegionCode(String regionCode); + + /** Returns the lineups list. */ + List getLineups(@NonNull String postalCode); + /** - * Returns the channels list. + * Returns the list of channel numbers (unsorted) for the given lineup. The result is used to + * choose the most appropriate lineup among others by comparing the channel numbers of the + * existing channels on the device. + */ + List getChannelNumbers(@NonNull String lineupId); + + /** + * Returns the list of channels for the given lineup. The returned channels should map into the + * existing channels on the device. This method is usually called after selecting the lineup. */ List getChannels(@NonNull String lineupId); + /** Pre-loads and caches channels for a given lineup. */ + void preloadChannels(@NonNull String lineupId); + /** - * Returns the lineups list. + * Clears cached channels for a given lineup. */ - List getLineups(@NonNull String postalCode); + @AnyThread + void clearCachedChannels(@NonNull String lineupId); /** - * Returns the programs for the given channel. The result is sorted by the start time. - * Note that the {@code Program} doesn't have valid program ID because it's not retrieved from - * TvProvider. + * Returns the programs for the given channel. Must call {@link #getChannels(String)} + * beforehand. Note that the {@code Program} doesn't have valid program ID because it's not + * retrieved from TvProvider. */ List getPrograms(long channelId); /** - * Returns the series information for the given series ID. + * Returns the programs for the given channels. Note that the {@code Program} doesn't have valid + * program ID because it's not retrieved from TvProvider. This method is only used to get + * programs for a short duration typically. */ - SeriesInfo getSeriesInfo(String seriesId); -} + Map> getPrograms(@NonNull List channelIds, long duration); + + /** Returns the series information for the given series ID. */ + SeriesInfo getSeriesInfo(@NonNull String seriesId); +} \ No newline at end of file diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java index 64093f89..ab6935ad 100644 --- a/src/com/android/tv/data/epg/StubEpgReader.java +++ b/src/com/android/tv/data/epg/StubEpgReader.java @@ -18,13 +18,15 @@ 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.dvr.SeriesInfo; +import com.android.tv.dvr.data.SeriesInfo; import java.util.Collections; import java.util.List; +import java.util.Map; /** * A stub class to read EPG. @@ -44,22 +46,47 @@ public class StubEpgReader implements EpgReader{ } @Override - public List getChannels(String lineupId) { + public void setRegionCode(String regionCode) { + // Do nothing + } + + @Override + public List getLineups(@NonNull String postalCode) { + return Collections.emptyList(); + } + + @Override + public List getChannelNumbers(@NonNull String lineupId) { return Collections.emptyList(); } @Override - public List getLineups(String postalCode) { + public List getChannels(@NonNull String lineupId) { return Collections.emptyList(); } + @Override + public void preloadChannels(@NonNull String lineupId) { + // Do nothing + } + + @Override + public void clearCachedChannels(@NonNull String lineupId) { + // Do nothing + } + @Override public List getPrograms(long channelId) { return Collections.emptyList(); } @Override - public SeriesInfo getSeriesInfo(String seriesId) { + public Map> getPrograms(@NonNull List channelIds, long duration) { + return Collections.emptyMap(); + } + + @Override + public SeriesInfo getSeriesInfo(@NonNull String seriesId) { return null; } -} +} \ No newline at end of file diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java new file mode 100644 index 00000000..d686e6e6 --- /dev/null +++ b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dialog; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.ui.DvrUiHelper; +import com.android.tv.util.Utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Displays the DVR history. + */ +@TargetApi(VERSION_CODES.N) +public class DvrHistoryDialogFragment extends SafeDismissDialogFragment { + public static final String DIALOG_TAG = DvrHistoryDialogFragment.class.getSimpleName(); + + private static final String TRACKER_LABEL = "DVR history"; + private final List mSchedules = new ArrayList<>(); + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + DvrDataManager dataManager = singletons.getDvrDataManager(); + ChannelDataManager channelDataManager = singletons.getChannelDataManager(); + for (ScheduledRecording schedule : dataManager.getAllScheduledRecordings()) { + if (!schedule.isInProgress() && !schedule.isNotStarted()) { + mSchedules.add(schedule); + } + } + mSchedules.sort(ScheduledRecording.START_TIME_COMPARATOR.reversed()); + LayoutInflater inflater = LayoutInflater.from(getContext()); + ArrayAdapter adapter = new ArrayAdapter(getContext(), + R.layout.list_item_dvr_history, ScheduledRecording.toArray(mSchedules)) { + @NonNull + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = inflater.inflate(R.layout.list_item_dvr_history, parent, false); + ScheduledRecording schedule = mSchedules.get(position); + setText(view, R.id.state, getStateString(schedule.getState())); + setText(view, R.id.schedule_time, getRecordingTimeText(schedule)); + setText(view, R.id.program_title, DvrUiHelper.getStyledTitleWithEpisodeNumber( + getContext(), schedule, 0)); + setText(view, R.id.channel_name, getChannelNameText(schedule)); + return view; + } + + private void setText(View view, int id, CharSequence text) { + ((TextView) view.findViewById(id)).setText(text); + } + + private void setText(View view, int id, int text) { + ((TextView) view.findViewById(id)).setText(text); + } + + @SuppressLint("SwitchIntDef") + private int getStateString(@RecordingState int state) { + switch (state) { + case ScheduledRecording.STATE_RECORDING_CLIPPED: + return R.string.dvr_history_dialog_state_clip; + case ScheduledRecording.STATE_RECORDING_FAILED: + return R.string.dvr_history_dialog_state_fail; + case ScheduledRecording.STATE_RECORDING_FINISHED: + return R.string.dvr_history_dialog_state_success; + default: + break; + } + return 0; + } + + private String getChannelNameText(ScheduledRecording schedule) { + Channel channel = channelDataManager.getChannel(schedule.getChannelId()); + return channel == null ? null : + TextUtils.isEmpty(channel.getDisplayName()) ? channel.getDisplayNumber() : + channel.getDisplayName().trim() + " " + channel.getDisplayNumber(); + } + + private String getRecordingTimeText(ScheduledRecording schedule) { + return Utils.getDurationString(getContext(), schedule.getStartTimeMs(), + schedule.getEndTimeMs(), true, true, true, 0); + } + }; + ListView listView = new ListView(getActivity()); + listView.setAdapter(adapter); + return new AlertDialog.Builder(getActivity()).setTitle(R.string.dvr_history_dialog_title) + .setView(listView).create(); + } + + @Override + public String getTrackerLabel() { + return TRACKER_LABEL; + } +} diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java index d16202a1..d00422a7 100644 --- a/src/com/android/tv/dialog/FullscreenDialogFragment.java +++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java @@ -77,7 +77,7 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment { return mTrackerLabel; } - private class FullscreenDialog extends TvDialog { + private class FullscreenDialog extends Dialog { public FullscreenDialog(Context context, int theme) { super(context, theme); } diff --git a/src/com/android/tv/dialog/HalfSizedDialogFragment.java b/src/com/android/tv/dialog/HalfSizedDialogFragment.java new file mode 100644 index 00000000..315c6a93 --- /dev/null +++ b/src/com/android/tv/dialog/HalfSizedDialogFragment.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dialog; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.tv.R; + +import java.util.concurrent.TimeUnit; + +public class HalfSizedDialogFragment extends SafeDismissDialogFragment { + public static final String DIALOG_TAG = HalfSizedDialogFragment.class.getSimpleName(); + public static final String TRACKER_LABEL = "Half sized dialog"; + + private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); + + private OnActionClickListener mOnActionClickListener; + + private Handler mHandler = new Handler(); + private Runnable mAutoDismisser = new Runnable() { + @Override + public void run() { + dismiss(); + } + }; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.halfsized_dialog, container, false); + } + + @Override + public void onStart() { + super.onStart(); + mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); + } + + @Override + public void onPause() { + super.onPause(); + if (mOnActionClickListener != null) { + // Dismisses the dialog to prevent the callback being forgotten during + // fragment re-creating. + dismiss(); + } + } + + @Override + public void onStop() { + super.onStop(); + mHandler.removeCallbacks(mAutoDismisser); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) { + mHandler.removeCallbacks(mAutoDismisser); + mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); + return false; + } + }); + return dialog; + } + + @Override + public int getTheme() { + return R.style.Theme_TV_dialog_HalfSizedDialog; + } + + @Override + public String getTrackerLabel() { + return TRACKER_LABEL; + } + + /** + * Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog + * will be automatically closed when it's paused to prevent the fragment being re-created by + * the framework, which will result the listener being forgotten. + */ + public void setOnActionClickListener(OnActionClickListener listener) { + mOnActionClickListener = listener; + } + + /** + * Returns {@link OnActionClickListener} for sub-classes or any inner fragments. + */ + protected OnActionClickListener getOnActionClickListener() { + return mOnActionClickListener; + } + + /** + * An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments + * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier + * of the action user clicked. + */ + public interface OnActionClickListener { + void onActionClick(long actionId); + } +} \ No newline at end of file diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java index d9d6c73f..d5c154da 100644 --- a/src/com/android/tv/dialog/PinDialogFragment.java +++ b/src/com/android/tv/dialog/PinDialogFragment.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.res.Resources; +import android.media.tv.TvContentRating; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; @@ -45,11 +46,13 @@ import android.widget.TextView; import android.widget.Toast; import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; import com.android.tv.util.TvSettings; public class PinDialogFragment extends SafeDismissDialogFragment { private static final String TAG = "PinDialogFragment"; - private static final boolean DBG = true; + private static final boolean DEBUG = true; /** * PIN code dialog for unlock channel @@ -80,18 +83,13 @@ public class PinDialogFragment extends SafeDismissDialogFragment { */ public static final int PIN_DIALOG_TYPE_UNLOCK_DVR = 5; - private static final int PIN_DIALOG_RESULT_SUCCESS = 0; - private static final int PIN_DIALOG_RESULT_FAIL = 1; - private static final int MAX_WRONG_PIN_COUNT = 5; private static final int DISABLE_PIN_DURATION_MILLIS = 60 * 1000; // 1 minute private static final String INITIAL_TEXT = "—"; private static final String TRACKER_LABEL = "Pin dialog"; - - public interface ResultListener { - void done(boolean success); - } + private static final String ARGS_TYPE = "args_type"; + private static final String ARGS_RATING = "args_rating"; public static final String DIALOG_TAG = PinDialogFragment.class.getName(); @@ -99,8 +97,9 @@ public class PinDialogFragment extends SafeDismissDialogFragment { R.id.first, R.id.second, R.id.third, R.id.fourth }; private int mType; - private ResultListener mListener; - private int mRetCode; + private int mRequestType; + private boolean mPinChecked; + private boolean mDismissSilently; private TextView mWrongPinView; private View mEnterPinView; @@ -114,29 +113,35 @@ public class PinDialogFragment extends SafeDismissDialogFragment { private long mDisablePinUntil; private final Handler mHandler = new Handler(); - public PinDialogFragment(int type, ResultListener listener) { - this(type, listener, null); + public static PinDialogFragment create(int type) { + return create(type, null); } - public PinDialogFragment(int type, ResultListener listener, String rating) { - mType = type; - mListener = listener; - mRetCode = PIN_DIALOG_RESULT_FAIL; - mRatingString = rating; + public static PinDialogFragment create(int type, String rating) { + PinDialogFragment fragment = new PinDialogFragment(); + Bundle args = new Bundle(); + args.putInt(ARGS_TYPE, type); + args.putString(ARGS_RATING, rating); + fragment.setArguments(args); + return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mRequestType = getArguments().getInt(ARGS_TYPE, PIN_DIALOG_TYPE_ENTER_PIN); + mType = mRequestType; + mRatingString = getArguments().getString(ARGS_RATING); setStyle(STYLE_NO_TITLE, 0); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); mDisablePinUntil = TvSettings.getDisablePinUntil(getActivity()); if (ActivityManager.isUserAMonkey()) { // Skip PIN dialog half the time for monkeys if (Math.random() < 0.5) { - exit(PIN_DIALOG_RESULT_SUCCESS); + exit(true); } } + mPinChecked = false; } @Override @@ -186,7 +191,19 @@ public class PinDialogFragment extends SafeDismissDialogFragment { mTitleView.setText(R.string.pin_enter_unlock_program); break; case PIN_DIALOG_TYPE_UNLOCK_DVR: - mTitleView.setText(getString(R.string.pin_enter_unlock_dvr, mRatingString)); + TvContentRating tvContentRating = + TvContentRating.unflattenFromString(mRatingString); + if (TvContentRating.UNRATED.equals(tvContentRating)) { + mTitleView.setText(getString(R.string.pin_enter_unlock_dvr_unrated)); + } else { + mTitleView.setText( + getString( + R.string.pin_enter_unlock_dvr, + TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper() + .getContentRatingsManager() + .getDisplayNameForRating(tvContentRating))); + } break; case PIN_DIALOG_TYPE_ENTER_PIN: mTitleView.setText(R.string.pin_enter_pin); @@ -217,10 +234,6 @@ public class PinDialogFragment extends SafeDismissDialogFragment { return v; } - public void setResultListener(ResultListener listener) { - mListener = listener; - } - private final Runnable mUpdateEnterPinRunnable = new Runnable() { @Override public void run() { @@ -250,18 +263,27 @@ public class PinDialogFragment extends SafeDismissDialogFragment { } } - private void exit(int retCode) { - mRetCode = retCode; + private void exit(boolean pinChecked) { + mPinChecked = pinChecked; + dismiss(); + } + + /** Dismisses the pin dialog without calling activity listener. */ + public void dismissSilently() { + mDismissSilently = true; dismiss(); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); - if (DBG) Log.d(TAG, "onDismiss: mRetCode=" + mRetCode); - if (mListener != null) { - mListener.done(mRetCode == PIN_DIALOG_RESULT_SUCCESS); + if (DEBUG) Log.d(TAG, "onDismiss: mPinChecked=" + mPinChecked); + SoftPreconditions.checkState(getActivity() instanceof OnPinCheckedListener); + if (!mDismissSilently && getActivity() instanceof OnPinCheckedListener) { + ((OnPinCheckedListener) getActivity()).onPinChecked( + mPinChecked, mRequestType, mRatingString); } + mDismissSilently = false; } private void handleWrongPin() { @@ -279,15 +301,14 @@ public class PinDialogFragment extends SafeDismissDialogFragment { } private void done(String pin) { - if (DBG) Log.d(TAG, "done: mType=" + mType + " pin=" + pin + " stored=" + getPin()); + if (DEBUG) Log.d(TAG, "done: mType=" + mType + " pin=" + pin + " stored=" + getPin()); switch (mType) { case PIN_DIALOG_TYPE_UNLOCK_CHANNEL: case PIN_DIALOG_TYPE_UNLOCK_PROGRAM: case PIN_DIALOG_TYPE_UNLOCK_DVR: case PIN_DIALOG_TYPE_ENTER_PIN: - // TODO: Implement limited number of retrials and timeout logic. if (TextUtils.isEmpty(getPin()) || pin.equals(getPin())) { - exit(PIN_DIALOG_RESULT_SUCCESS); + exit(true); } else { resetPinInput(); handleWrongPin(); @@ -301,7 +322,7 @@ public class PinDialogFragment extends SafeDismissDialogFragment { } else { if (pin.equals(mPrevPin)) { setPin(pin); - exit(PIN_DIALOG_RESULT_SUCCESS); + exit(true); } else { if (TextUtils.isEmpty(getPin())) { mTitleView.setText(R.string.pin_enter_create_pin); @@ -332,7 +353,7 @@ public class PinDialogFragment extends SafeDismissDialogFragment { } private void setPin(String pin) { - if (DBG) Log.d(TAG, "setPin: " + pin); + if (DEBUG) Log.d(TAG, "setPin: " + pin); mPin = pin; mSharedPreferences.edit().putString(TvSettings.PREF_PIN, pin).apply(); } @@ -684,4 +705,20 @@ public class PinDialogFragment extends SafeDismissDialogFragment { : (value > mMaxValue) ? value - interval : value; } } + + /** + * A listener to the result of {@link PinDialogFragment}. Any activity requiring pin code + * checking should implement this listener to receive the result. + */ + public interface OnPinCheckedListener { + /** + * Called when {@link PinDialogFragment} is dismissed. + * + * @param checked {@code true} if the pin code entered is checked to be correct, + * otherwise {@code false}. + * @param type The dialog type regarding to what pin entering is for. + * @param rating The target rating to unblock for. + */ + void onPinChecked(boolean checked, int type, String rating); + } } diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java index f671a87d..e3390b0a 100644 --- a/src/com/android/tv/dialog/SafeDismissDialogFragment.java +++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java @@ -17,11 +17,7 @@ package com.android.tv.dialog; import android.app.Activity; -import android.app.Dialog; import android.app.DialogFragment; -import android.content.Context; -import android.os.Bundle; -import android.view.KeyEvent; import com.android.tv.MainActivity; import com.android.tv.TvApplication; @@ -38,11 +34,6 @@ public abstract class SafeDismissDialogFragment extends DialogFragment private boolean mDismissPending = false; private Tracker mTracker; - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new TvDialog(getActivity(), getTheme()); - } - @Override public void onAttach(Activity activity) { super.onAttach(activity); @@ -92,21 +83,4 @@ public abstract class SafeDismissDialogFragment extends DialogFragment super.dismiss(); } } - - protected class TvDialog extends Dialog { - public TvDialog(Context context, int theme) { - super(context, theme); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - // When a dialog is showing, key events are handled by the dialog instead of - // MainActivity. Therefore, unless a key is a global key, it should be handled here. - if (mAttached && keyCode == KeyEvent.KEYCODE_SEARCH && mActivity != null) { - mActivity.showSearchActivity(); - return true; - } - return super.onKeyUp(keyCode, event); - } - } } diff --git a/src/com/android/tv/dialog/WebDialogFragment.java b/src/com/android/tv/dialog/WebDialogFragment.java index 75f93bb2..171a256b 100644 --- a/src/com/android/tv/dialog/WebDialogFragment.java +++ b/src/com/android/tv/dialog/WebDialogFragment.java @@ -37,6 +37,7 @@ public class WebDialogFragment extends SafeDismissDialogFragment { private static final String TITLE = "TITLE"; private static final String TRACKER_LABEL = "TRACKER_LABEL"; + private WebView mWebView; private String mTrackerLabel; /** @@ -73,13 +74,21 @@ public class WebDialogFragment extends SafeDismissDialogFragment { String title = getArguments().getString(TITLE); getDialog().setTitle(title); - WebView webView = new WebView(getActivity()); - webView.setWebViewClient(new WebViewClient()); + mWebView = new WebView(getActivity()); + mWebView.setWebViewClient(new WebViewClient()); String url = getArguments().getString(URL); - webView.loadUrl(url); + mWebView.loadUrl(url); Log.d(TAG, "Loading web content from " + url); - return webView; + return mWebView; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mWebView != null) { + mWebView.destroy(); + } } @Override diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index 89661df3..a8637449 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -26,7 +26,10 @@ import android.util.Log; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Clock; import java.util.ArrayList; @@ -317,6 +320,42 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { return result; } + @Override + public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) { + List toRemove = new ArrayList<>(); + for (long rId : seriesRecordingIds) { + SeriesRecording seriesRecording = getSeriesRecording(rId); + if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) { + toRemove.add(seriesRecording); + } + } + removeSeriesRecording(SeriesRecording.toArray(toRemove)); + } + + /** + * Returns {@code true}, if the series recording is empty and can be removed. If a series + * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be + * removed. + */ + protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) { + if (!seriesRecording.isStopped()) { + return false; + } + long seriesRecordingId = seriesRecording.getId(); + for (ScheduledRecording r : getAvailableScheduledRecordings()) { + if (r.getSeriesRecordingId() == seriesRecordingId) { + return false; + } + } + String seriesId = seriesRecording.getSeriesId(); + for (RecordedProgram r : getRecordedPrograms()) { + if (seriesId.equals(r.getSeriesId())) { + return false; + } + } + return true; + } + @Override public void forgetStorage(String inputId) { } } diff --git a/src/com/android/tv/dvr/ConflictChecker.java b/src/com/android/tv/dvr/ConflictChecker.java deleted file mode 100644 index 201e379e..00000000 --- a/src/com/android/tv/dvr/ConflictChecker.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.TargetApi; -import android.content.ContentUris; -import android.media.tv.TvContract; -import android.net.Uri; -import android.os.Build; -import android.os.Message; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.ArraySet; -import android.util.Log; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.InputSessionManager; -import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener; -import com.android.tv.MainActivity; -import com.android.tv.TvApplication; -import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * Checking the runtime conflict of DVR recording. - *

- * This class runs only while the {@link MainActivity} is resumed and holds the upcoming conflicts. - */ -@TargetApi(Build.VERSION_CODES.N) -@MainThread -public class ConflictChecker { - private static final String TAG = "ConflictChecker"; - private static final boolean DEBUG = false; - - private static final int MSG_CHECK_CONFLICT = 1; - - private static final long CHECK_RETRY_PERIOD_MS = TimeUnit.SECONDS.toMillis(30); - - /** - * To show watch conflict dialog, the start time of the earliest conflicting schedule should be - * less than or equal to this time. - */ - private static final long MAX_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.MINUTES.toMillis(5); - /** - * To show watch conflict dialog, the start time of the earliest conflicting schedule should be - * greater than or equal to this time. - */ - private static final long MIN_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.SECONDS.toMillis(30); - - private final MainActivity mMainActivity; - private final ChannelDataManager mChannelDataManager; - private final DvrScheduleManager mScheduleManager; - private final InputSessionManager mSessionManager; - private final ConflictCheckerHandler mHandler = new ConflictCheckerHandler(this); - - private final List mUpcomingConflicts = new ArrayList<>(); - private final Set mOnUpcomingConflictChangeListeners = - new ArraySet<>(); - private final Map> mCheckedConflictsMap = new HashMap<>(); - - private final ScheduledRecordingListener mScheduledRecordingListener = - new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } - }; - - private final OnTvViewChannelChangeListener mOnTvViewChannelChangeListener = - new OnTvViewChannelChangeListener() { - @Override - public void onTvViewChannelChange(@Nullable Uri channelUri) { - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } - }; - - private boolean mStarted; - - public ConflictChecker(MainActivity mainActivity) { - mMainActivity = mainActivity; - ApplicationSingletons appSingletons = TvApplication.getSingletons(mainActivity); - mChannelDataManager = appSingletons.getChannelDataManager(); - mScheduleManager = appSingletons.getDvrScheduleManager(); - mSessionManager = appSingletons.getInputSessionManager(); - } - - /** - * Starts checking the conflict. - */ - public void start() { - if (mStarted) { - return; - } - mStarted = true; - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - mScheduleManager.addScheduledRecordingListener(mScheduledRecordingListener); - mSessionManager.addOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener); - } - - /** - * Stops checking the conflict. - */ - public void stop() { - if (!mStarted) { - return; - } - mStarted = false; - mSessionManager.removeOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener); - mScheduleManager.removeScheduledRecordingListener(mScheduledRecordingListener); - mHandler.removeCallbacksAndMessages(null); - } - - /** - * Returns the upcoming conflicts. - */ - public List getUpcomingConflicts() { - return new ArrayList<>(mUpcomingConflicts); - } - - /** - * Adds a {@link OnUpcomingConflictChangeListener}. - */ - public void addOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { - mOnUpcomingConflictChangeListeners.add(listener); - } - - /** - * Removes the {@link OnUpcomingConflictChangeListener}. - */ - public void removeOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { - mOnUpcomingConflictChangeListeners.remove(listener); - } - - private void notifyUpcomingConflictChanged() { - for (OnUpcomingConflictChangeListener l : mOnUpcomingConflictChangeListeners) { - l.onUpcomingConflictChange(); - } - } - - /** - * Remembers the user's decision to record while watching the channel. - */ - public void setCheckedConflictsForChannel(long mChannelId, List conflicts) { - mCheckedConflictsMap.put(mChannelId, new ArrayList<>(conflicts)); - } - - void onCheckConflict() { - // Checks the conflicting schedules and setup the next re-check time. - // If there are upcoming conflicts soon, it opens the conflict dialog. - if (DEBUG) Log.d(TAG, "Handling MSG_CHECK_CONFLICT"); - mHandler.removeMessages(MSG_CHECK_CONFLICT); - mUpcomingConflicts.clear(); - if (!mScheduleManager.isInitialized() - || !mChannelDataManager.isDbLoadFinished()) { - mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, CHECK_RETRY_PERIOD_MS); - notifyUpcomingConflictChanged(); - return; - } - if (mSessionManager.getCurrentTvViewChannelUri() == null) { - // As MainActivity is not using a tuner, no need to check the conflict. - notifyUpcomingConflictChanged(); - return; - } - Uri channelUri = mSessionManager.getCurrentTvViewChannelUri(); - if (TvContract.isChannelUriForPassthroughInput(channelUri)) { - notifyUpcomingConflictChanged(); - return; - } - long channelId = ContentUris.parseId(channelUri); - Channel channel = mChannelDataManager.getChannel(channelId); - // The conflicts caused by watching the channel. - List conflicts = mScheduleManager - .getConflictingSchedulesForWatching(channel.getId()); - long earliestToCheck = Long.MAX_VALUE; - long currentTimeMs = System.currentTimeMillis(); - for (ScheduledRecording schedule : conflicts) { - long startTimeMs = schedule.getStartTimeMs(); - if (startTimeMs < currentTimeMs + MIN_WATCH_CONFLICT_CHECK_TIME_MS) { - // The start time of the upcoming conflict remains less than the minimum - // check time. - continue; - } - if (startTimeMs > currentTimeMs + MAX_WATCH_CONFLICT_CHECK_TIME_MS) { - // The start time of the upcoming conflict remains greater than the - // maximum check time. Setup the next re-check time. - long nextCheckTimeMs = startTimeMs - MAX_WATCH_CONFLICT_CHECK_TIME_MS; - if (earliestToCheck > nextCheckTimeMs) { - earliestToCheck = nextCheckTimeMs; - } - } else { - // Found upcoming conflicts which will start soon. - mUpcomingConflicts.add(schedule); - // The schedule will be removed from the "upcoming conflict" when the - // recording is almost started. - long nextCheckTimeMs = startTimeMs - MIN_WATCH_CONFLICT_CHECK_TIME_MS; - if (earliestToCheck > nextCheckTimeMs) { - earliestToCheck = nextCheckTimeMs; - } - } - } - if (earliestToCheck != Long.MAX_VALUE) { - mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, - earliestToCheck - currentTimeMs); - } - if (DEBUG) Log.d(TAG, "upcoming conflicts: " + mUpcomingConflicts); - notifyUpcomingConflictChanged(); - if (!mUpcomingConflicts.isEmpty() - && !DvrUiHelper.isChannelWatchConflictDialogShown(mMainActivity)) { - // Don't show the conflict dialog if the user already knows. - List checkedConflicts = mCheckedConflictsMap.get( - channel.getId()); - if (checkedConflicts == null - || !checkedConflicts.containsAll(mUpcomingConflicts)) { - DvrUiHelper.showChannelWatchConflictDialog(mMainActivity, channel); - } - } - } - - private static class ConflictCheckerHandler extends WeakHandler { - ConflictCheckerHandler(ConflictChecker conflictChecker) { - super(conflictChecker); - } - - @Override - protected void handleMessage(Message msg, @NonNull ConflictChecker conflictChecker) { - switch (msg.what) { - case MSG_CHECK_CONFLICT: - conflictChecker.onCheckConflict(); - break; - } - } - } - - /** - * A listener for the change of upcoming conflicts. - */ - public interface OnUpcomingConflictChangeListener { - void onUpcomingConflictChange(); - } -} diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java index 06613667..6d400b82 100644 --- a/src/com/android/tv/dvr/DvrDataManager.java +++ b/src/com/android/tv/dvr/DvrDataManager.java @@ -21,7 +21,10 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Range; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.SeriesRecording; import java.util.Collection; import java.util.List; @@ -210,6 +213,13 @@ public interface DvrDataManager { @NonNull Collection getDisallowedProgramIds(); + /** + * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains + * any available schedules or recorded programs, and it's status is + * {@link SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings. + */ + void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds); + /** * Listens for the DVR schedules loading finished. */ diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index 46682a48..6094ca72 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -42,7 +42,11 @@ import android.util.Range; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.IdGenerator; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddScheduleTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddSeriesRecordingTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDeleteScheduleTask; @@ -51,12 +55,14 @@ import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryScheduleTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQuerySeriesRecordingTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateScheduleTask; import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateSeriesRecordingTask; +import com.android.tv.dvr.provider.DvrDbSync; +import com.android.tv.dvr.recorder.SeriesRecordingScheduler; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.AsyncDbTask.AsyncRecordedProgramQueryTask; import com.android.tv.util.Clock; import com.android.tv.util.Filter; import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.TvProviderUriMatcher; +import com.android.tv.util.TvUriMatcher; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -267,11 +273,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { removeScheduledRecording(ScheduledRecording.toArray(toDelete)); } IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId); + if (mRecordedProgramLoadFinished) { + validateSeriesRecordings(); + } mDvrLoadFinished = true; notifyDvrScheduleLoadFinished(); - mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this); - mDbSync.start(); if (isInitialized()) { + mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this); + mDbSync.start(); SeriesRecordingScheduler.getInstance(mContext).start(); } } @@ -306,8 +315,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (uri == null) { uri = RecordedPrograms.CONTENT_URI; } - int match = TvProviderUriMatcher.match(uri); - if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM) { + if (recordedPrograms == null) { + recordedPrograms = Collections.emptyList(); + } + int match = TvUriMatcher.match(uri); + if (match == TvUriMatcher.MATCH_RECORDED_PROGRAM) { if (!mRecordedProgramLoadFinished) { for (RecordedProgram recorded : recordedPrograms) { if (isInputAvailable(recorded.getInputId())) { @@ -318,7 +330,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } mRecordedProgramLoadFinished = true; notifyRecordedProgramLoadFinished(); - } else if (recordedPrograms == null || recordedPrograms.isEmpty()) { + if (isInitialized()) { + mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this); + mDbSync.start(); + } + } else if (recordedPrograms.isEmpty()) { List oldRecordedPrograms = new ArrayList<>(mRecordedPrograms.values()); mRecordedPrograms.clear(); @@ -355,19 +371,24 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } if (isInitialized()) { + validateSeriesRecordings(); SeriesRecordingScheduler.getInstance(mContext).start(); } - } else if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM_ID) { + } else if (match == TvUriMatcher.MATCH_RECORDED_PROGRAM_ID) { if (!mRecordedProgramLoadFinished) { return; } long id = ContentUris.parseId(uri); if (DEBUG) Log.d(TAG, "changed recorded program #" + id + " to " + recordedPrograms); - if (recordedPrograms == null || recordedPrograms.isEmpty()) { + if (recordedPrograms.isEmpty()) { mRecordedProgramsForRemovedInput.remove(id); RecordedProgram old = mRecordedPrograms.remove(id); if (old != null) { notifyRecordedProgramsRemoved(old); + SeriesRecording r = mSeriesId2SeriesRecordings.get(old.getSeriesId()); + if (r != null && isEmptySeriesRecording(r)) { + removeSeriesRecording(r); + } } } else { RecordedProgram recordedProgram = recordedPrograms.get(0); @@ -592,10 +613,16 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { public void removeScheduledRecording(boolean forceRemove, ScheduledRecording... schedules) { List schedulesToDelete = new ArrayList<>(); List schedulesNotToDelete = new ArrayList<>(); + Set seriesRecordingIdsToCheck = new HashSet<>(); for (ScheduledRecording r : schedules) { mScheduledRecordings.remove(r.getId()); - getDeletedScheduleMap().remove(r.getId()); + getDeletedScheduleMap().remove(r.getProgramId()); mProgramId2ScheduledRecordings.remove(r.getProgramId()); + if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET + && (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED + || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + seriesRecordingIdsToCheck.add(r.getSeriesRecordingId()); + } boolean isScheduleForRemovedInput = mScheduledRecordingsForRemovedInput.remove(r.getProgramId()) != null; // If it belongs to the series recording and it's not started yet, just mark delete @@ -614,8 +641,19 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } if (mDvrLoadFinished) { + if (mRecordedProgramLoadFinished) { + checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck); + } notifyScheduledRecordingRemoved(schedules); } + Iterator iterator = schedulesNotToDelete.iterator(); + while (iterator.hasNext()) { + ScheduledRecording r = iterator.next(); + if (!mSeriesRecordings.containsKey(r.getSeriesRecordingId())) { + iterator.remove(); + schedulesToDelete.add(r); + } + } if (!schedulesToDelete.isEmpty()) { new AsyncDeleteScheduleTask(mContext).executeOnDbThread( ScheduledRecording.toArray(schedulesToDelete)); @@ -669,6 +707,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private void updateScheduledRecording(boolean updateDb, final ScheduledRecording... schedules) { List toUpdate = new ArrayList<>(); + Set seriesRecordingIdsToCheck = new HashSet<>(); for (ScheduledRecording r : schedules) { if (!SoftPreconditions.checkState(mScheduledRecordings.containsKey(r.getId()), TAG, "Recording not found for: " + r)) { @@ -691,6 +730,13 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (programId != ScheduledRecording.ID_NOT_SET) { mProgramId2ScheduledRecordings.put(programId, r); } + if (r.getState() == ScheduledRecording.STATE_RECORDING_FAILED + && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { + // If the scheduled recording is failed, it may cause the automatically generated + // series recording for this schedule becomes invalid (with no future schedules and + // past recordings.) We should check and remove these series recordings. + seriesRecordingIdsToCheck.add(r.getSeriesRecordingId()); + } } if (toUpdate.isEmpty()) { return; @@ -702,12 +748,17 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (updateDb) { new AsyncUpdateScheduleTask(mContext).executeOnDbThread(scheduleArray); } + checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck); removeDeletedSchedules(schedules); } @Override public void updateSeriesRecording(final SeriesRecording... seriesRecordings) { for (SeriesRecording r : seriesRecordings) { + if (!SoftPreconditions.checkArgument(mSeriesRecordings.containsKey(r.getId()), TAG, + "Non Existing Series ID: " + r)) { + continue; + } SeriesRecording old1 = mSeriesRecordings.put(r.getId(), r); SeriesRecording old2 = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); SoftPreconditions.checkArgument(old1.equals(old2), TAG, "Series ID cannot be" @@ -769,14 +820,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { return r.getInputId().equals(inputId); } }); - List movedSeriesRecordings = - moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings, - new Filter() { - @Override - public boolean filter(SeriesRecording r) { - return r.getInputId().equals(inputId); - } - }); List movedRecordedPrograms = moveElements(mRecordedProgramsForRemovedInput, mRecordedPrograms, new Filter() { @@ -785,6 +828,21 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { return r.getInputId().equals(inputId); } }); + List removedSeriesRecordings = new ArrayList<>(); + List movedSeriesRecordings = + moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings, + new Filter() { + @Override + public boolean filter(SeriesRecording r) { + if (r.getInputId().equals(inputId)) { + if (!isEmptySeriesRecording(r)) { + return true; + } + removedSeriesRecordings.add(r); + } + return false; + } + }); if (!movedSchedules.isEmpty()) { for (ScheduledRecording schedule : movedSchedules) { mProgramId2ScheduledRecordings.put(schedule.getProgramId(), schedule); @@ -795,6 +853,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { mSeriesId2SeriesRecordings.put(seriesRecording.getSeriesId(), seriesRecording); } } + for (SeriesRecording r : removedSeriesRecordings) { + mSeriesRecordingsForRemovedInput.remove(r.getId()); + } + new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread( + SeriesRecording.toArray(removedSeriesRecordings)); // Notify after all the data are moved. if (!movedSchedules.isEmpty()) { notifyScheduledRecordingAdded(ScheduledRecording.toArray(movedSchedules)); @@ -811,20 +874,20 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (DEBUG) Log.d(TAG, "hideInput " + inputId); List movedSchedules = moveElements(mScheduledRecordings, mScheduledRecordingsForRemovedInput, - new Filter() { - @Override - public boolean filter(ScheduledRecording r) { - return r.getInputId().equals(inputId); - } - }); + new Filter() { + @Override + public boolean filter(ScheduledRecording r) { + return r.getInputId().equals(inputId); + } + }); List movedSeriesRecordings = moveElements(mSeriesRecordings, mSeriesRecordingsForRemovedInput, - new Filter() { - @Override - public boolean filter(SeriesRecording r) { - return r.getInputId().equals(inputId); - } - }); + new Filter() { + @Override + public boolean filter(SeriesRecording r) { + return r.getInputId().equals(inputId); + } + }); List movedRecordedPrograms = moveElements(mRecordedPrograms, mRecordedProgramsForRemovedInput, new Filter() { @@ -855,6 +918,15 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } + private void checkAndRemoveEmptySeriesRecording(Set seriesRecordingIds) { + int i = 0; + long[] rIds = new long[seriesRecordingIds.size()]; + for (long rId : seriesRecordingIds) { + rIds[i++] = rId; + } + checkAndRemoveEmptySeriesRecording(rIds); + } + @Override public void forgetStorage(String inputId) { List schedulesToDelete = new ArrayList<>(); @@ -901,6 +973,25 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { }.executeOnDbThread(); } + private void validateSeriesRecordings() { + Iterator iter = mSeriesRecordings.values().iterator(); + List removedSeriesRecordings = new ArrayList<>(); + while (iter.hasNext()) { + SeriesRecording r = iter.next(); + if (isEmptySeriesRecording(r)) { + iter.remove(); + removedSeriesRecordings.add(r); + } + } + if (!removedSeriesRecordings.isEmpty()) { + SeriesRecording[] removed = SeriesRecording.toArray(removedSeriesRecordings); + new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(removed); + if (mDvrLoadFinished) { + notifySeriesRecordingRemoved(removed); + } + } + } + private final class RecordedProgramsQueryTask extends AsyncRecordedProgramQueryTask { private final Uri mUri; diff --git a/src/com/android/tv/dvr/DvrDbSync.java b/src/com/android/tv/dvr/DvrDbSync.java deleted file mode 100644 index df181455..00000000 --- a/src/com/android/tv/dvr/DvrDbSync.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ContentUris; -import android.content.Context; -import android.database.ContentObserver; -import android.media.tv.TvContract.Programs; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -import com.android.tv.TvApplication; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask; -import com.android.tv.util.TvProviderUriMatcher; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; - -/** - * A class to synchronizes DVR DB with TvProvider. - * - *

The current implementation of AsyncDbTask allows only one task to run at a time, and all the - * other tasks are blocked until the current one finishes. As this class performs the low priority - * jobs which take long time, it should not block others if possible. For this reason, only one - * program is queried at a time and others are queued and will be executed on the other - * AsyncDbTask's after the current one finishes to minimize the execution time of one AsyncDbTask. - */ -@MainThread -@TargetApi(Build.VERSION_CODES.N) -class DvrDbSync { - private static final String TAG = "DvrDbSync"; - private static final boolean DEBUG = false; - - private final Context mContext; - private final DvrDataManagerImpl mDataManager; - private final ChannelDataManager mChannelDataManager; - private final Queue mProgramIdQueue = new LinkedList<>(); - private QueryProgramTask mQueryProgramTask; - private final SeriesRecordingScheduler mSeriesRecordingScheduler; - private final ContentObserver mContentObserver = new ContentObserver(new Handler( - Looper.getMainLooper())) { - @SuppressLint("SwitchIntDef") - @Override - public void onChange(boolean selfChange, Uri uri) { - switch (TvProviderUriMatcher.match(uri)) { - case TvProviderUriMatcher.MATCH_PROGRAM: - if (DEBUG) Log.d(TAG, "onProgramsUpdated"); - onProgramsUpdated(); - break; - case TvProviderUriMatcher.MATCH_PROGRAM_ID: - if (DEBUG) { - Log.d(TAG, "onProgramUpdated: programId=" + ContentUris.parseId(uri)); - } - onProgramUpdated(ContentUris.parseId(uri)); - break; - } - } - }; - - private final ChannelDataManager.Listener mChannelDataManagerListener = - new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - start(); - } - - @Override - public void onChannelListUpdated() { - onChannelsUpdated(); - } - - @Override - public void onChannelBrowsableChanged() { } - }; - - private final ScheduledRecordingListener mScheduleListener = new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - addProgramIdToCheckIfNeeded(schedule); - } - startNextUpdateIfNeeded(); - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - mProgramIdQueue.remove(schedule.getProgramId()); - } - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - mProgramIdQueue.remove(schedule.getProgramId()); - addProgramIdToCheckIfNeeded(schedule); - } - startNextUpdateIfNeeded(); - } - }; - - DvrDbSync(Context context, DvrDataManagerImpl dataManager) { - this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager()); - } - - @VisibleForTesting - DvrDbSync(Context context, DvrDataManagerImpl dataManager, - ChannelDataManager channelDataManager) { - mContext = context; - mDataManager = dataManager; - mChannelDataManager = channelDataManager; - mSeriesRecordingScheduler = SeriesRecordingScheduler.getInstance(context); - } - - /** - * Starts the DB sync. - */ - public void start() { - if (!mChannelDataManager.isDbLoadFinished()) { - mChannelDataManager.addListener(mChannelDataManagerListener); - return; - } - mContext.getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, - mContentObserver); - mDataManager.addScheduledRecordingListener(mScheduleListener); - onChannelsUpdated(); - onProgramsUpdated(); - } - - /** - * Stops the DB sync. - */ - public void stop() { - mProgramIdQueue.clear(); - if (mQueryProgramTask != null) { - mQueryProgramTask.cancel(true); - } - mChannelDataManager.removeListener(mChannelDataManagerListener); - mDataManager.removeScheduledRecordingListener(mScheduleListener); - mContext.getContentResolver().unregisterContentObserver(mContentObserver); - } - - private void onChannelsUpdated() { - List seriesRecordingsToUpdate = new ArrayList<>(); - for (SeriesRecording r : mDataManager.getSeriesRecordings()) { - if (r.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE - && !mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { - seriesRecordingsToUpdate.add(SeriesRecording.buildFrom(r) - .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) - .setState(SeriesRecording.STATE_SERIES_STOPPED).build()); - } - } - if (!seriesRecordingsToUpdate.isEmpty()) { - mDataManager.updateSeriesRecording( - SeriesRecording.toArray(seriesRecordingsToUpdate)); - } - List schedulesToRemove = new ArrayList<>(); - for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) { - if (!mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { - schedulesToRemove.add(r); - mProgramIdQueue.remove(r.getProgramId()); - } - } - if (!schedulesToRemove.isEmpty()) { - mDataManager.removeScheduledRecording( - ScheduledRecording.toArray(schedulesToRemove)); - } - } - - private void onProgramsUpdated() { - for (ScheduledRecording schedule : mDataManager.getAvailableScheduledRecordings()) { - addProgramIdToCheckIfNeeded(schedule); - } - startNextUpdateIfNeeded(); - } - - private void onProgramUpdated(long programId) { - addProgramIdToCheckIfNeeded(mDataManager.getScheduledRecordingForProgramId(programId)); - startNextUpdateIfNeeded(); - } - - private void addProgramIdToCheckIfNeeded(ScheduledRecording schedule) { - if (schedule == null) { - return; - } - long programId = schedule.getProgramId(); - if (programId != ScheduledRecording.ID_NOT_SET - && !mProgramIdQueue.contains(programId) - && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { - if (DEBUG) Log.d(TAG, "Program ID enqueued: " + programId); - mProgramIdQueue.offer(programId); - // There are schedules to be updated. Pause the SeriesRecordingScheduler until all the - // schedule updates finish. - // Note that the SeriesRecordingScheduler should be paused even though the program to - // check is not episodic because it can be changed to the episodic program after the - // update, which affect the SeriesRecordingScheduler. - mSeriesRecordingScheduler.pauseUpdate(); - } - } - - private void startNextUpdateIfNeeded() { - if (mQueryProgramTask != null && !mQueryProgramTask.isCancelled()) { - return; - } - if (!mProgramIdQueue.isEmpty()) { - if (DEBUG) Log.d(TAG, "Program ID dequeued: " + mProgramIdQueue.peek()); - mQueryProgramTask = new QueryProgramTask(mProgramIdQueue.poll()); - mQueryProgramTask.executeOnDbThread(); - } else { - mSeriesRecordingScheduler.resumeUpdate(); - } - } - - @VisibleForTesting - void handleUpdateProgram(Program program, long programId) { - Set seriesRecordingsToUpdate = new HashSet<>(); - ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(programId); - if (schedule != null - && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { - if (program == null) { - mDataManager.removeScheduledRecording(schedule); - if (schedule.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { - SeriesRecording seriesRecording = - mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); - if (seriesRecording != null) { - seriesRecordingsToUpdate.add(seriesRecording); - } - } - } else { - long currentTimeMs = System.currentTimeMillis(); - ScheduledRecording.Builder builder = ScheduledRecording.buildFrom(schedule) - .setEndTimeMs(program.getEndTimeUtcMillis()) - .setSeasonNumber(program.getSeasonNumber()) - .setEpisodeNumber(program.getEpisodeNumber()) - .setEpisodeTitle(program.getEpisodeTitle()) - .setProgramDescription(program.getDescription()) - .setProgramLongDescription(program.getLongDescription()) - .setProgramPosterArtUri(program.getPosterArtUri()) - .setProgramThumbnailUri(program.getThumbnailUri()); - boolean needUpdate = false; - // Check the series recording. - SeriesRecording seriesRecordingForOldSchedule = - mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); - if (program.getSeriesId() != null) { - // New program belongs to a series. - SeriesRecording seriesRecording = - mDataManager.getSeriesRecording(program.getSeriesId()); - if (seriesRecording == null) { - // The new program is episodic while the previous one isn't. - SeriesRecording newSeriesRecording = TvApplication.getSingletons(mContext) - .getDvrManager().addSeriesRecording(program, - Collections.singletonList(program), - SeriesRecording.STATE_SERIES_STOPPED); - builder.setSeriesRecordingId(newSeriesRecording.getId()); - needUpdate = true; - } else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) { - // The new program belongs to the other series. - builder.setSeriesRecordingId(seriesRecording.getId()); - needUpdate = true; - seriesRecordingsToUpdate.add(seriesRecording); - if (seriesRecordingForOldSchedule != null) { - seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); - } - } else if (!Objects.equals(schedule.getSeasonNumber(), - program.getSeasonNumber()) - || !Objects.equals(schedule.getEpisodeNumber(), - program.getEpisodeNumber())) { - // The episode number has been changed. - if (seriesRecordingForOldSchedule != null) { - seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); - } - } - } else if (seriesRecordingForOldSchedule != null) { - // Old program belongs to a series but the new one doesn't. - seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); - } - // Change start time only when the recording start time has not passed. - boolean needToChangeStartTime = schedule.getStartTimeMs() > currentTimeMs - && program.getStartTimeUtcMillis() != schedule.getStartTimeMs(); - if (needToChangeStartTime) { - builder.setStartTimeMs(program.getStartTimeUtcMillis()); - needUpdate = true; - } - if (needUpdate || schedule.getEndTimeMs() != program.getEndTimeUtcMillis() - || !Objects.equals(schedule.getSeasonNumber(), program.getSeasonNumber()) - || !Objects.equals(schedule.getEpisodeNumber(), program.getEpisodeNumber()) - || !Objects.equals(schedule.getEpisodeTitle(), program.getEpisodeTitle()) - || !Objects.equals(schedule.getProgramDescription(), - program.getDescription()) - || !Objects.equals(schedule.getProgramLongDescription(), - program.getLongDescription()) - || !Objects.equals(schedule.getProgramPosterArtUri(), - program.getPosterArtUri()) - || !Objects.equals(schedule.getProgramThumbnailUri(), - program.getThumbnailUri())) { - mDataManager.updateScheduledRecording(builder.build()); - } - if (!seriesRecordingsToUpdate.isEmpty()) { - // The series recordings will be updated after it's resumed. - mSeriesRecordingScheduler.updateSchedules(seriesRecordingsToUpdate); - } - } - } - } - - private class QueryProgramTask extends AsyncQueryProgramTask { - private final long mProgramId; - - QueryProgramTask(long programId) { - super(mContext.getContentResolver(), programId); - mProgramId = programId; - } - - @Override - protected void onCancelled(Program program) { - if (mQueryProgramTask == this) { - mQueryProgramTask = null; - } - startNextUpdateIfNeeded(); - } - - @Override - protected void onPostExecute(Program program) { - if (mQueryProgramTask == this) { - mQueryProgramTask = null; - } - handleUpdateProgram(program, mProgramId); - startNextUpdateIfNeeded(); - } - } -} diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java index 5fa6f90f..d222003d 100644 --- a/src/com/android/tv/dvr/DvrManager.java +++ b/src/com/android/tv/dvr/DvrManager.java @@ -46,7 +46,9 @@ import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener; -import com.android.tv.dvr.SeriesRecording.SeriesState; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.Utils; @@ -142,7 +144,7 @@ public class DvrManager { } private void createSeriesRecordingForRecordedProgramIfNeeded(RecordedProgram recordedProgram) { - if (recordedProgram.getSeriesId() != null) { + if (recordedProgram.isEpisodic()) { SeriesRecording seriesRecording = mDataManager.getSeriesRecording(recordedProgram.getSeriesId()); if (seriesRecording == null) { @@ -234,7 +236,7 @@ public class DvrManager { * Adds a new series recording and schedules for the programs with the initial state. */ public SeriesRecording addSeriesRecording(Program selectedProgram, - List programsToSchedule, @SeriesState int initialState) { + List programsToSchedule, @SeriesRecording.SeriesState int initialState) { Log.i(TAG, "Adding series recording for program " + selectedProgram + ", and schedules: " + programsToSchedule); if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { @@ -308,8 +310,7 @@ public class DvrManager { ScheduledRecording scheduleWithSameProgram = mDataManager.getScheduledRecordingForProgramId(program.getId()); if (scheduleWithSameProgram != null) { - if (scheduleWithSameProgram.getState() - == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { + if (scheduleWithSameProgram.isNotStarted()) { ScheduledRecording r = ScheduledRecording.buildFrom(scheduleWithSameProgram) .setSeriesRecordingId(series.getId()) .build(); @@ -337,10 +338,10 @@ public class DvrManager { */ public void updateSeriesRecording(SeriesRecording series) { if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { - SeriesRecordingScheduler scheduler = SeriesRecordingScheduler.getInstance(mAppContext); - scheduler.pauseUpdate(); SeriesRecording previousSeries = mDataManager.getSeriesRecording(series.getId()); if (previousSeries != null) { + // If the channel option of series changed, remove the existing schedules. The new + // schedules will be added by SeriesRecordingScheduler or by SeriesSettingsFragment. if (previousSeries.getChannelOption() != series.getChannelOption() || (previousSeries.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE && previousSeries.getChannelId() != series.getChannelId())) { @@ -350,6 +351,18 @@ public class DvrManager { for (ScheduledRecording schedule : schedules) { if (schedule.isNotStarted()) { schedulesToRemove.add(schedule); + } else if (schedule.isInProgress() + && series.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE + && schedule.getChannelId() != series.getChannelId()) { + stopRecording(schedule); + } + } + List deletedSchedules = + new ArrayList<>(mDataManager.getDeletedSchedules()); + for (ScheduledRecording deletedSchedule : deletedSchedules) { + if (deletedSchedule.getSeriesRecordingId() == series.getId() + && deletedSchedule.getEndTimeMs() > System.currentTimeMillis()) { + schedulesToRemove.add(deletedSchedule); } } mDataManager.removeScheduledRecording(true, @@ -363,7 +376,7 @@ public class DvrManager { List schedulesToUpdate = new ArrayList<>(); for (ScheduledRecording schedule : mDataManager.getScheduledRecordings(series.getId())) { - if (schedule.isNotStarted()) { + if (schedule.isNotStarted() || schedule.isInProgress()) { schedulesToUpdate.add(ScheduledRecording.buildFrom(schedule) .setPriority(priority).build()); } @@ -373,7 +386,6 @@ public class DvrManager { ScheduledRecording.toArray(schedulesToUpdate)); } } - scheduler.resumeUpdate(); } } @@ -399,33 +411,6 @@ public class DvrManager { mDataManager.removeSeriesRecording(series); } - /** - * Returns true, if the series recording can be removed. If a series recording is NORMAL state - * or has recordings or schedules, it cannot be removed. - */ - public boolean canRemoveSeriesRecording(long seriesRecordingId) { - SeriesRecording seriesRecording = mDataManager.getSeriesRecording(seriesRecordingId); - if (seriesRecording == null) { - return false; - } - if (!seriesRecording.isStopped()) { - return false; - } - for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) { - if (r.getSeriesRecordingId() == seriesRecordingId) { - return false; - } - } - String seriesId = seriesRecording.getSeriesId(); - SoftPreconditions.checkNotNull(seriesId); - for (RecordedProgram r : mDataManager.getRecordedPrograms()) { - if (seriesId.equals(r.getSeriesId())) { - return false; - } - } - return true; - } - /** * Stops the currently recorded program */ @@ -509,13 +494,16 @@ public class DvrManager { if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { return; } - new AsyncDbTask() { + new AsyncDbTask() { @Override - protected Void doInBackground(Void... params) { + protected Integer doInBackground(Void... params) { ContentResolver resolver = mAppContext.getContentResolver(); - int deletedCounts = resolver.delete(recordedProgram.getUri(), null, null); + return resolver.delete(recordedProgram.getUri(), null, null); + } + + @Override + protected void onPostExecute(Integer deletedCounts) { if (deletedCounts > 0) { - // TODO: executeOnExecutor should be called on the main thread. new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -524,7 +512,6 @@ public class DvrManager { } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - return null; } }.executeOnDbThread(); } @@ -539,13 +526,22 @@ public class DvrManager { dbOperations.add(ContentProviderOperation.newDelete(r.getUri()).build()); } } - new AsyncDbTask() { + new AsyncDbTask() { @Override - protected Void doInBackground(Void... params) { + protected Boolean doInBackground(Void... params) { ContentResolver resolver = mAppContext.getContentResolver(); try { resolver.applyBatch(TvContract.AUTHORITY, dbOperations); - // TODO: executeOnExecutor should be called on the main thread. + } catch (RemoteException | OperationApplicationException e) { + Log.w(TAG, "Remove recorded programs from DB failed.", e); + return false; + } + return true; + } + + @Override + protected void onPostExecute(Boolean success) { + if (success) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -555,10 +551,7 @@ public class DvrManager { return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } catch (RemoteException | OperationApplicationException e) { - Log.w(TAG, "Remove reocrded programs from DB failed.", e); } - return null; } }.executeOnDbThread(); } @@ -657,6 +650,9 @@ public class DvrManager { if (!mDataManager.isDvrScheduleLoadFinished() || channel == null) { return false; } + if (channel.isRecordingProhibited()) { + return false; + } TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId()); if (info == null) { Log.w(TAG, "Could not find TvInputInfo for " + channel); @@ -680,7 +676,12 @@ public class DvrManager { if (!mDataManager.isInitialized()) { return false; } - TvInputInfo info = Utils.getTvInputInfoForProgram(mAppContext, program); + Channel channel = TvApplication.getSingletons(mAppContext).getChannelDataManager() + .getChannel(program.getChannelId()); + if (channel == null || channel.isRecordingProhibited()) { + return false; + } + TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId()); if (info == null) { Log.w(TAG, "Could not find TvInputInfo for " + program); return false; @@ -733,6 +734,17 @@ public class DvrManager { return mDataManager.getSeriesRecording(program.getSeriesId()); } + /** + * Returns if there are valid items. Valid item contains {@link RecordedProgram}, + * available {@link ScheduledRecording} and {@link SeriesRecording}. + */ + public boolean hasValidItems() { + return !(mDataManager.getRecordedPrograms().isEmpty() + && mDataManager.getStartedRecordings().isEmpty() + && mDataManager.getNonStartedScheduledRecordings().isEmpty() + && mDataManager.getSeriesRecordings().isEmpty()); + } + @WorkerThread @VisibleForTesting // Should be public to use mock DvrManager object. @@ -840,9 +852,10 @@ public class DvrManager { } /** - * Listener internally used inside dvr package. + * Listener to stop recording request. Should only be internally used inside dvr and its + * sub-package. */ - interface Listener { + public interface Listener { void onStopRecordingRequested(ScheduledRecording scheduledRecording); } } diff --git a/src/com/android/tv/dvr/DvrPlaybackActivity.java b/src/com/android/tv/dvr/DvrPlaybackActivity.java deleted file mode 100644 index 5deda44a..00000000 --- a/src/com/android/tv/dvr/DvrPlaybackActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.util.Log; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment; - -/** - * Activity to play a {@link RecordedProgram}. - */ -public class DvrPlaybackActivity extends Activity { - private static final String TAG = "DvrPlaybackActivity"; - private static final boolean DEBUG = false; - - private DvrPlaybackOverlayFragment mOverlayFragment; - - @Override - public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); - if (DEBUG) Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_dvr_playback); - mOverlayFragment = (DvrPlaybackOverlayFragment) getFragmentManager() - .findFragmentById(R.id.dvr_playback_controls_fragment); - } - - @Override - public void onVisibleBehindCanceled() { - if (DEBUG) Log.d(TAG, "onVisibleBehindCanceled"); - super.onVisibleBehindCanceled(); - finish(); - } - - @Override - protected void onNewIntent(Intent intent) { - mOverlayFragment.onNewIntent(intent); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - float density = getResources().getDisplayMetrics().density; - mOverlayFragment.onWindowSizeChanged((int) (newConfig.screenWidthDp * density), - (int) (newConfig.screenHeightDp * density)); - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java deleted file mode 100644 index 9759a856..00000000 --- a/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.app.Activity; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; -import android.media.tv.TvContract; -import android.os.AsyncTask; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment; -import com.android.tv.util.ImageLoader; -import com.android.tv.util.TimeShiftUtils; -import com.android.tv.util.Utils; - -public class DvrPlaybackMediaSessionHelper { - private static final String TAG = "DvrPlaybackMediaSessionHelper"; - private static final boolean DEBUG = false; - - private int mNowPlayingCardWidth; - private int mNowPlayingCardHeight; - private int mSpeedLevel; - private long mProgramDurationMs; - - private Activity mActivity; - private DvrPlayer mDvrPlayer; - private MediaSession mMediaSession; - private final DvrWatchedPositionManager mDvrWatchedPositionManager; - private final ChannelDataManager mChannelDataManager; - - public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag, - DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) { - mActivity = activity; - mDvrPlayer = dvrPlayer; - mDvrWatchedPositionManager = - TvApplication.getSingletons(activity).getDvrWatchedPositionManager(); - mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager(); - mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() { - @Override - public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { - updateMediaSessionPlaybackState(); - } - - @Override - public void onPlaybackPositionChanged(long positionMs) { - updateMediaSessionPlaybackState(); - if (mDvrPlayer.isPlaybackPrepared()) { - mDvrWatchedPositionManager - .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs); - } - } - - @Override - public void onPlaybackEnded() { - // TODO: Deal with watched over recordings in DVR library - RecordedProgram nextEpisode = - overlayFragment.getNextEpisode(mDvrPlayer.getProgram()); - if (nextEpisode == null) { - mDvrPlayer.reset(); - mActivity.finish(); - } else { - Intent intent = new Intent(activity, DvrPlaybackActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId()); - mActivity.startActivity(intent); - } - } - }); - initializeMediaSession(mediaSessionTag); - } - - /** - * Stops DVR player and release media session. - */ - public void release() { - if (mDvrPlayer != null) { - mDvrPlayer.reset(); - } - if (mMediaSession != null) { - mMediaSession.release(); - } - } - - /** - * Updates media session's playback state and speed. - */ - public void updateMediaSessionPlaybackState() { - mMediaSession.setPlaybackState(new PlaybackState.Builder() - .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(), - mSpeedLevel).build()); - } - - /** - * Sets the recorded program for playback. - * - * @param program The recorded program to play. {@code null} to reset the DVR player. - */ - public void setupPlayback(RecordedProgram program, long seekPositionMs) { - if (program != null) { - mDvrPlayer.setProgram(program, seekPositionMs); - setupMediaSession(program); - } else { - mDvrPlayer.reset(); - mMediaSession.setActive(false); - } - } - - /** - * Returns the recorded program now playing. - */ - public RecordedProgram getProgram() { - return mDvrPlayer.getProgram(); - } - - /** - * Checks if the recorded program is the same as now playing one. - */ - public boolean isCurrentProgram(RecordedProgram program) { - return program != null && program.equals(getProgram()); - } - - /** - * Returns playback state. - */ - public int getPlaybackState() { - return mDvrPlayer.getPlaybackState(); - } - - /** - * Returns the underlying DVR player. - */ - public DvrPlayer getDvrPlayer() { - return mDvrPlayer; - } - - private void initializeMediaSession(String mediaSessionTag) { - mMediaSession = new MediaSession(mActivity, mediaSessionTag); - mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); - mNowPlayingCardWidth = mActivity.getResources() - .getDimensionPixelSize(R.dimen.notif_card_img_max_width); - mNowPlayingCardHeight = mActivity.getResources() - .getDimensionPixelSize(R.dimen.notif_card_img_height); - mMediaSession.setCallback(new MediaSessionCallback()); - mActivity.setMediaController( - new MediaController(mActivity, mMediaSession.getSessionToken())); - updateMediaSessionPlaybackState(); - } - - private void setupMediaSession(RecordedProgram program) { - mProgramDurationMs = program.getDurationMillis(); - String cardTitleText = program.getTitle(); - if (TextUtils.isEmpty(cardTitleText)) { - Channel channel = mChannelDataManager.getChannel(program.getChannelId()); - cardTitleText = (channel != null) ? channel.getDisplayName() - : mActivity.getString(R.string.no_program_information); - } - updateMediaMetadata(program.getId(), cardTitleText, program.getDescription(), - mProgramDurationMs, null, 0); - String posterArtUri = program.getPosterArtUri(); - if (posterArtUri == null) { - posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString(); - } - updatePosterArt(program, cardTitleText, program.getDescription(), - mProgramDurationMs, null, posterArtUri); - mMediaSession.setActive(true); - } - - private void updatePosterArt(RecordedProgram program, String cardTitleText, - String cardSubtitleText, long duration, - @Nullable Bitmap posterArt, @Nullable String posterArtUri) { - if (posterArt != null) { - updateMediaMetadata(program.getId(), cardTitleText, - cardSubtitleText, duration, posterArt, 0); - } else if (posterArtUri != null) { - ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth, - mNowPlayingCardHeight, new ProgramPosterArtCallback( - mActivity, program, cardTitleText, cardSubtitleText, duration)); - } else { - updateMediaMetadata(program.getId(), cardTitleText, - cardSubtitleText, duration, null, R.drawable.default_now_card); - } - } - - private class ProgramPosterArtCallback extends - ImageLoader.ImageLoaderCallback { - private RecordedProgram mRecordedProgram; - private String mCardTitleText; - private String mCardSubtitleText; - private long mDuration; - - public ProgramPosterArtCallback(Activity activity, RecordedProgram program, - String cardTitleText, String cardSubtitleText, long duration) { - super(activity); - mRecordedProgram = program; - mCardTitleText = cardTitleText; - mCardSubtitleText = cardSubtitleText; - mDuration = duration; - } - - @Override - public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) { - if (isCurrentProgram(mRecordedProgram)) { - updatePosterArt(mRecordedProgram, mCardTitleText, - mCardSubtitleText, mDuration, posterArt, null); - } - } - } - - private void updateMediaMetadata(final long programId, final String title, - final String subtitle, final long duration, - final Bitmap posterArt, final int imageResId) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... arg0) { - MediaMetadata.Builder builder = new MediaMetadata.Builder(); - builder.putLong(MediaMetadata.METADATA_KEY_MEDIA_ID, programId) - .putString(MediaMetadata.METADATA_KEY_TITLE, title) - .putLong(MediaMetadata.METADATA_KEY_DURATION, duration); - if (subtitle != null) { - builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle); - } - Bitmap programPosterArt = posterArt; - if (programPosterArt == null && imageResId != 0) { - programPosterArt = - BitmapFactory.decodeResource(mActivity.getResources(), imageResId); - } - if (programPosterArt != null) { - builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt); - } - mMediaSession.setMetadata(builder.build()); - return null; - } - }.execute(); - } - - // An event was triggered by MediaController.TransportControls and must be handled here. - // Here we update the media itself to act on the event that was triggered. - private class MediaSessionCallback extends MediaSession.Callback { - @Override - public void onPrepare() { - if (!mDvrPlayer.isPlaybackPrepared()) { - mDvrPlayer.prepare(true); - } - } - - @Override - public void onPlay() { - if (mDvrPlayer.isPlaybackPrepared()) { - mDvrPlayer.play(); - } - } - - @Override - public void onPause() { - if (mDvrPlayer.isPlaybackPrepared()) { - mDvrPlayer.pause(); - } - } - - @Override - public void onFastForward() { - if (!mDvrPlayer.isPlaybackPrepared()) { - return; - } - if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING) { - if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { - mSpeedLevel++; - } else { - return; - } - } else { - mSpeedLevel = 0; - } - mDvrPlayer.fastForward( - TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); - } - - @Override - public void onRewind() { - if (!mDvrPlayer.isPlaybackPrepared()) { - return; - } - if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_REWINDING) { - if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { - mSpeedLevel++; - } else { - return; - } - } else { - mSpeedLevel = 0; - } - mDvrPlayer.rewind(TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); - } - - @Override - public void onSeekTo(long positionMs) { - if (mDvrPlayer.isPlaybackPrepared()) { - mDvrPlayer.seekTo(positionMs); - } - } - } -} diff --git a/src/com/android/tv/dvr/DvrPlayer.java b/src/com/android/tv/dvr/DvrPlayer.java deleted file mode 100644 index 5656655c..00000000 --- a/src/com/android/tv/dvr/DvrPlayer.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.media.PlaybackParams; -import android.media.tv.TvContentRating; -import android.media.tv.TvInputManager; -import android.media.tv.TvTrackInfo; -import android.media.tv.TvView; -import android.media.session.PlaybackState; -import android.util.Log; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class DvrPlayer { - private static final String TAG = "DvrPlayer"; - private static final boolean DEBUG = false; - - /** - * The max rewinding speed supported by DVR player. - */ - public static final int MAX_REWIND_SPEED = 256; - /** - * The max fast-forwarding speed supported by DVR player. - */ - public static final int MAX_FAST_FORWARD_SPEED = 256; - - private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2); - private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826 - - private RecordedProgram mProgram; - private long mInitialSeekPositionMs; - private final TvView mTvView; - private DvrPlayerCallback mCallback; - private AspectRatioChangedListener mAspectRatioChangedListener; - private ContentBlockedListener mContentBlockedListener; - private float mAspectRatio = Float.NaN; - private int mPlaybackState = PlaybackState.STATE_NONE; - private long mTimeShiftCurrentPositionMs; - private boolean mPauseOnPrepared; - private final PlaybackParams mPlaybackParams = new PlaybackParams(); - private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback(); - private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; - private boolean mTimeShiftPlayAvailable; - - public static class DvrPlayerCallback { - /** - * Called when the playback position is changed. The normal updating frequency is - * around 1 sec., which is restricted to the implementation of - * {@link android.media.tv.TvInputService}. - */ - public void onPlaybackPositionChanged(long positionMs) { } - /** - * Called when the playback state or the playback speed is changed. - */ - public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { } - /** - * Called when the playback toward the end. - */ - public void onPlaybackEnded() { } - } - - public interface AspectRatioChangedListener { - /** - * Called when the Video's aspect ratio is changed. - */ - void onAspectRatioChanged(float videoAspectRatio); - } - - public interface ContentBlockedListener { - /** - * Called when the Video's aspect ratio is changed. - */ - void onContentBlocked(TvContentRating rating); - } - - public DvrPlayer(TvView tvView) { - mTvView = tvView; - mPlaybackParams.setSpeed(1.0f); - setTvViewCallbacks(); - setCallback(null); - } - - /** - * Prepares playback. - * - * @param doPlay indicates DVR player do or do not start playback after media is prepared. - */ - public void prepare(boolean doPlay) throws IllegalStateException { - if (DEBUG) Log.d(TAG, "prepare()"); - if (mProgram == null) { - throw new IllegalStateException("Recorded program not set"); - } else if (mPlaybackState != PlaybackState.STATE_NONE) { - throw new IllegalStateException("Playback is already prepared"); - } - mTvView.timeShiftPlay(mProgram.getInputId(), mProgram.getUri()); - mPlaybackState = PlaybackState.STATE_CONNECTING; - mPauseOnPrepared = !doPlay; - mCallback.onPlaybackStateChanged(mPlaybackState, 1); - } - - /** - * Resumes playback. - */ - public void play() throws IllegalStateException { - if (DEBUG) Log.d(TAG, "play()"); - if (!isPlaybackPrepared()) { - throw new IllegalStateException("Recorded program not set or video not ready yet"); - } - switch (mPlaybackState) { - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - setPlaybackSpeed(1); - break; - default: - mTvView.timeShiftResume(); - } - mPlaybackState = PlaybackState.STATE_PLAYING; - mCallback.onPlaybackStateChanged(mPlaybackState, 1); - } - - /** - * Pauses playback. - */ - public void pause() throws IllegalStateException { - if (DEBUG) Log.d(TAG, "pause()"); - if (!isPlaybackPrepared()) { - throw new IllegalStateException("Recorded program not set or playback not started yet"); - } - switch (mPlaybackState) { - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - setPlaybackSpeed(1); - // falls through - case PlaybackState.STATE_PLAYING: - mTvView.timeShiftPause(); - mPlaybackState = PlaybackState.STATE_PAUSED; - break; - default: - break; - } - mCallback.onPlaybackStateChanged(mPlaybackState, 1); - } - - /** - * Fast-forwards playback with the given speed. If the given speed is larger than - * {@value #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}. - */ - public void fastForward(int speed) throws IllegalStateException { - if (DEBUG) Log.d(TAG, "fastForward()"); - if (!isPlaybackPrepared()) { - throw new IllegalStateException("Recorded program not set or playback not started yet"); - } - if (speed <= 0) { - throw new IllegalArgumentException("Speed cannot be negative or 0"); - } - if (mTimeShiftCurrentPositionMs >= mProgram.getDurationMillis() - SEEK_POSITION_MARGIN_MS) { - return; - } - speed = Math.min(speed, MAX_FAST_FORWARD_SPEED); - if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed); - setPlaybackSpeed(speed); - mPlaybackState = PlaybackState.STATE_FAST_FORWARDING; - mCallback.onPlaybackStateChanged(mPlaybackState, speed); - } - - /** - * Rewinds playback with the given speed. If the given speed is larger than - * {@value #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}. - */ - public void rewind(int speed) throws IllegalStateException { - if (DEBUG) Log.d(TAG, "rewind()"); - if (!isPlaybackPrepared()) { - throw new IllegalStateException("Recorded program not set or playback not started yet"); - } - if (speed <= 0) { - throw new IllegalArgumentException("Speed cannot be negative or 0"); - } - if (mTimeShiftCurrentPositionMs <= REWIND_POSITION_MARGIN_MS) { - return; - } - speed = Math.min(speed, MAX_REWIND_SPEED); - if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed); - setPlaybackSpeed(-speed); - mPlaybackState = PlaybackState.STATE_REWINDING; - mCallback.onPlaybackStateChanged(mPlaybackState, speed); - } - - /** - * Seeks playback to the specified position. - */ - public void seekTo(long positionMs) throws IllegalStateException { - if (DEBUG) Log.d(TAG, "seekTo()"); - if (!isPlaybackPrepared()) { - throw new IllegalStateException("Recorded program not set or playback not started yet"); - } - if (mProgram == null || mPlaybackState == PlaybackState.STATE_NONE) { - return; - } - positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS); - if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs); - mTvView.timeShiftSeekTo(positionMs + mStartPositionMs); - if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING || - mPlaybackState == PlaybackState.STATE_REWINDING) { - mPlaybackState = PlaybackState.STATE_PLAYING; - mTvView.timeShiftResume(); - mCallback.onPlaybackStateChanged(mPlaybackState, 1); - } - } - - /** - * Resets playback. - */ - public void reset() { - if (DEBUG) Log.d(TAG, "reset()"); - mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1); - mPlaybackState = PlaybackState.STATE_NONE; - mTvView.reset(); - mTimeShiftPlayAvailable = false; - mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; - mTimeShiftCurrentPositionMs = 0; - mPlaybackParams.setSpeed(1.0f); - mProgram = null; - } - - /** - * Sets callbacks for playback. - */ - public void setCallback(DvrPlayerCallback callback) { - if (callback != null) { - mCallback = callback; - } else { - mCallback = mEmptyCallback; - } - } - - /** - * Sets listener to aspect ratio changing. - */ - public void setAspectRatioChangedListener(AspectRatioChangedListener listener) { - mAspectRatioChangedListener = listener; - } - - /** - * Sets listener to content blocking. - */ - public void setContentBlockedListener(ContentBlockedListener listener) { - mContentBlockedListener = listener; - } - - /** - * Sets recorded programs for playback. If the player is playing another program, stops it. - */ - public void setProgram(RecordedProgram program, long initialSeekPositionMs) { - if (mProgram != null && mProgram.equals(program)) { - return; - } - if (mPlaybackState != PlaybackState.STATE_NONE) { - reset(); - } - mInitialSeekPositionMs = initialSeekPositionMs; - mProgram = program; - } - - /** - * Returns the recorded program now playing. - */ - public RecordedProgram getProgram() { - return mProgram; - } - - /** - * Returns the currrent playback posistion in msecs. - */ - public long getPlaybackPosition() { - return mTimeShiftCurrentPositionMs; - } - - /** - * Returns the playback speed currently used. - */ - public int getPlaybackSpeed() { - return (int) mPlaybackParams.getSpeed(); - } - - /** - * Returns the playback state defined in {@link android.media.session.PlaybackState}. - */ - public int getPlaybackState() { - return mPlaybackState; - } - - /** - * Returns if playback of the recorded program is started. - */ - public boolean isPlaybackPrepared() { - return mPlaybackState != PlaybackState.STATE_NONE - && mPlaybackState != PlaybackState.STATE_CONNECTING; - } - - private void setPlaybackSpeed(int speed) { - mPlaybackParams.setSpeed(speed); - mTvView.timeShiftSetPlaybackParams(mPlaybackParams); - } - - private long getRealSeekPosition(long seekPositionMs, long endMarginMs) { - return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs)); - } - - private void setTvViewCallbacks() { - mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() { - @Override - public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { - if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs); - mStartPositionMs = timeMs; - if (mTimeShiftPlayAvailable) { - resumeToWatchedPositionIfNeeded(); - } - } - - @Override - public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { - if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs); - if (!mTimeShiftPlayAvailable) { - // Workaround of b/31436263 - return; - } - // Workaround of b/32211561, TIF won't report start position when TIS report - // its start position as 0. In that case, we have to do the prework of playback - // on the first time we get current position, and the start position should be 0 - // at that time. - if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) { - mStartPositionMs = 0; - resumeToWatchedPositionIfNeeded(); - } - timeMs -= mStartPositionMs; - if (mPlaybackState == PlaybackState.STATE_REWINDING - && timeMs <= REWIND_POSITION_MARGIN_MS) { - play(); - } else { - mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0); - mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs); - if (timeMs >= mProgram.getDurationMillis()) { - pause(); - mCallback.onPlaybackEnded(); - } - } - } - }); - mTvView.setCallback(new TvView.TvInputCallback() { - @Override - public void onTimeShiftStatusChanged(String inputId, int status) { - if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status); - if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE - && mPlaybackState == PlaybackState.STATE_CONNECTING) { - mTimeShiftPlayAvailable = true; - } - } - - @Override - public void onTrackSelected(String inputId, int type, String trackId) { - if (trackId == null || type != TvTrackInfo.TYPE_VIDEO - || mAspectRatioChangedListener == null) { - return; - } - List trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO); - if (trackInfos != null) { - for (TvTrackInfo trackInfo : trackInfos) { - if (trackInfo.getId().equals(trackId)) { - float videoAspectRatio = trackInfo.getVideoPixelAspectRatio() - * trackInfo.getVideoWidth() / trackInfo.getVideoHeight(); - if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio); - if (!Float.isNaN(videoAspectRatio) - && mAspectRatio != videoAspectRatio) { - mAspectRatioChangedListener - .onAspectRatioChanged(videoAspectRatio); - mAspectRatio = videoAspectRatio; - return; - } - } - } - } - } - - @Override - public void onContentBlocked(String inputId, TvContentRating rating) { - if (mContentBlockedListener != null) { - mContentBlockedListener.onContentBlocked(rating); - } - } - }); - } - - private void resumeToWatchedPositionIfNeeded() { - if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - mTvView.timeShiftSeekTo(getRealSeekPosition(mInitialSeekPositionMs, - SEEK_POSITION_MARGIN_MS) + mStartPositionMs); - mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; - } - if (mPauseOnPrepared) { - mTvView.timeShiftPause(); - mPlaybackState = PlaybackState.STATE_PAUSED; - mPauseOnPrepared = false; - } else { - mTvView.timeShiftResume(); - mPlaybackState = PlaybackState.STATE_PLAYING; - } - mCallback.onPlaybackStateChanged(mPlaybackState, 1); - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/DvrRecordingService.java b/src/com/android/tv/dvr/DvrRecordingService.java deleted file mode 100644 index 8c40aaa8..00000000 --- a/src/com/android/tv/dvr/DvrRecordingService.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.app.AlarmManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.HandlerThread; -import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.util.Clock; -import com.android.tv.util.RecurringRunner; - -/** - * DVR Scheduler service. - * - *

This service is responsible for: - *

    - *
  • Send record commands to TV inputs
  • - *
  • Wake up at proper timing for recording
  • - *
  • Deconflict schedule, handling overlapping times etc.
  • - *
  • - * - *
- * - *

The service does not stop it self. - */ -public class DvrRecordingService extends Service { - private static final String TAG = "DvrRecordingService"; - private static final boolean DEBUG = false; - public static final String HANDLER_THREAD_NAME = "DvrRecordingService-handler"; - - public static void startService(Context context) { - Intent dvrSchedulerIntent = new Intent(context, DvrRecordingService.class); - context.startService(dvrSchedulerIntent); - } - - private final Clock mClock = Clock.SYSTEM; - private RecurringRunner mReaperRunner; - - private Scheduler mScheduler; - private HandlerThread mHandlerThread; - - @Override - public void onCreate() { - TvApplication.setCurrentRunningProcess(this, true); - if (DEBUG) Log.d(TAG, "onCreate"); - super.onCreate(); - SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG); - ApplicationSingletons singletons = TvApplication.getSingletons(this); - WritableDvrDataManager dataManager = (WritableDvrDataManager) singletons.getDvrDataManager(); - - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - // mScheduler may have been set for testing. - if (mScheduler == null) { - mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME); - mHandlerThread.start(); - mScheduler = new Scheduler(mHandlerThread.getLooper(), singletons.getDvrManager(), - singletons.getInputSessionManager(), dataManager, - singletons.getChannelDataManager(), singletons.getTvInputManagerHelper(), this, - mClock, alarmManager); - mScheduler.start(); - } - mReaperRunner = new RecurringRunner(this, java.util.concurrent.TimeUnit.DAYS.toMillis(1), - new ScheduledProgramReaper(dataManager, mClock), null); - mReaperRunner.start(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (DEBUG) Log.d(TAG, "onStartCommand (" + intent + "," + flags + "," + startId + ")"); - mScheduler.update(); - return START_STICKY; - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy"); - mReaperRunner.stop(); - mScheduler.stop(); - mScheduler = null; - if (mHandlerThread != null) { - mHandlerThread.quitSafely(); - mHandlerThread = null; - } - super.onDestroy(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @VisibleForTesting - void setScheduler(Scheduler scheduler) { - Log.i(TAG, "Setting scheduler for tests to " + scheduler); - mScheduler = scheduler; - } -} diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java index a5851a75..b72117aa 100644 --- a/src/com/android/tv/dvr/DvrScheduleManager.java +++ b/src/com/android/tv/dvr/DvrScheduleManager.java @@ -24,7 +24,6 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.ArraySet; -import android.util.LongSparseArray; import android.util.Range; import com.android.tv.ApplicationSingletons; @@ -35,7 +34,10 @@ import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.recorder.InputTaskScheduler; import com.android.tv.util.CompositeComparator; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -88,9 +90,8 @@ public class DvrScheduleManager { private final Map> mInputScheduleMap = new HashMap<>(); // The inner map is a hash map from scheduled recording to its conflicting status, i.e., // the boolean value true denotes the schedule is just partially conflicting, which means - // although there's conflictit, it might still be recorded partially. - private final Map> mInputConflictInfoMap = - new HashMap<>(); + // although there's conflict, it might still be recorded partially. + private final Map> mInputConflictInfoMap = new HashMap<>(); private boolean mInitialized; @@ -171,10 +172,9 @@ public class DvrScheduleManager { mInputScheduleMap.remove(inputId); } } - Map conflictInfo = - mInputConflictInfoMap.get(inputId); + Map conflictInfo = mInputConflictInfoMap.get(inputId); if (conflictInfo != null) { - conflictInfo.remove(schedule); + conflictInfo.remove(schedule.getId()); if (conflictInfo.isEmpty()) { mInputConflictInfoMap.remove(inputId); } @@ -221,21 +221,11 @@ public class DvrScheduleManager { mInputScheduleMap.remove(inputId); } // Update conflict list as well - Map conflictInfo = - mInputConflictInfoMap.get(inputId); + Map conflictInfo = mInputConflictInfoMap.get(inputId); if (conflictInfo != null) { - // Compare ID because ScheduledRecording.equals() doesn't work if the state - // is changed. - ScheduledRecording oldSchedule = null; - for (ScheduledRecording s : conflictInfo.keySet()) { - if (s.getId() == schedule.getId()) { - oldSchedule = s; - break; - } - } - if (oldSchedule != null) { - conflictInfo.put(schedule, conflictInfo.get(oldSchedule)); - conflictInfo.remove(oldSchedule); + ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId()); + if (oldConflictInfo != null) { + oldConflictInfo.schedule = schedule; } } } @@ -317,24 +307,25 @@ public class DvrScheduleManager { List addedConflicts = new ArrayList<>(); List removedConflicts = new ArrayList<>(); for (String inputId : mInputScheduleMap.keySet()) { - Map oldConflictsInfo = mInputConflictInfoMap.get(inputId); + Map oldConflictInfo = mInputConflictInfoMap.get(inputId); Map oldConflictMap = new HashMap<>(); - if (oldConflictsInfo != null) { - for (ScheduledRecording r : oldConflictsInfo.keySet()) { - oldConflictMap.put(r.getId(), r); + if (oldConflictInfo != null) { + for (ConflictInfo conflictInfo : oldConflictInfo.values()) { + oldConflictMap.put(conflictInfo.schedule.getId(), conflictInfo.schedule); } } - Map conflictInfo = getConflictingSchedulesInfo(inputId); - if (conflictInfo.isEmpty()) { + List conflicts = getConflictingSchedulesInfo(inputId); + if (conflicts.isEmpty()) { mInputConflictInfoMap.remove(inputId); } else { - mInputConflictInfoMap.put(inputId, conflictInfo); - List conflicts = new ArrayList<>(conflictInfo.keySet()); - for (ScheduledRecording r : conflicts) { - if (oldConflictMap.remove(r.getId()) == null) { - addedConflicts.add(r); + Map conflictInfos = new HashMap<>(); + for (ConflictInfo conflictInfo : conflicts) { + conflictInfos.put(conflictInfo.schedule.getId(), conflictInfo); + if (oldConflictMap.remove(conflictInfo.schedule.getId()) == null) { + addedConflicts.add(conflictInfo.schedule); } } + mInputConflictInfoMap.put(inputId, conflictInfos); } removedConflicts.addAll(oldConflictMap.values()); } @@ -565,8 +556,7 @@ public class DvrScheduleManager { } /** - * Returns list of all conflicting scheduled recordings with schedules belonging to {@code - * seriesRecording} + * Returns list of all conflicting scheduled recordings for the given {@code seriesRecording} * recording. *

* Any empty list means there is no conflicts. @@ -581,9 +571,18 @@ public class DvrScheduleManager { if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { return Collections.emptyList(); } - List schedulesForSeries = mDataManager.getScheduledRecordings( + List scheduledRecordingForSeries = mDataManager.getScheduledRecordings( seriesRecording.getId()); - return getConflictingSchedules(input, schedulesForSeries); + List availableScheduledRecordingForSeries = new ArrayList<>(); + for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) { + if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) { + availableScheduledRecordingForSeries.add(scheduledRecording); + } + } + if (availableScheduledRecordingForSeries.isEmpty()) { + return Collections.emptyList(); + } + return getConflictingSchedules(input, availableScheduledRecordingForSeries); } /** @@ -617,16 +616,16 @@ public class DvrScheduleManager { * the given input. */ @NonNull - private Map getConflictingSchedulesInfo(String inputId) { + private List getConflictingSchedulesInfo(String inputId) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId); SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId); if (!mInitialized || input == null) { - return Collections.emptyMap(); + return Collections.emptyList(); } List schedules = mInputScheduleMap.get(input.getId()); if (schedules == null || schedules.isEmpty()) { - return Collections.emptyMap(); + return Collections.emptyList(); } return getConflictingSchedulesInfo(schedules, input.getTunerCount()); } @@ -645,8 +644,8 @@ public class DvrScheduleManager { if (!mInitialized || input == null) { return false; } - Map conflicts = mInputConflictInfoMap.get(input.getId()); - return conflicts != null && conflicts.containsKey(schedule); + Map conflicts = mInputConflictInfoMap.get(input.getId()); + return conflicts != null && conflicts.containsKey(schedule.getId()); } /** @@ -664,8 +663,12 @@ public class DvrScheduleManager { if (!mInitialized || input == null) { return false; } - Map conflicts = mInputConflictInfoMap.get(input.getId()); - return conflicts != null && conflicts.getOrDefault(schedule, false); + Map conflicts = mInputConflictInfoMap.get(input.getId()); + if (conflicts != null) { + ConflictInfo conflictInfo = conflicts.get(schedule.getId()); + return conflictInfo != null && conflictInfo.partialConflict; + } + return false; } /** @@ -813,15 +816,17 @@ public class DvrScheduleManager { @VisibleForTesting static List getConflictingSchedules( List schedules, int tunerCount, List> periods) { - List result = new ArrayList<>( - getConflictingSchedulesInfo(schedules, tunerCount, periods).keySet()); - Collections.sort(result, RESULT_COMPARATOR); + List result = new ArrayList<>(); + for (ConflictInfo conflictInfo : + getConflictingSchedulesInfo(schedules, tunerCount, periods)) { + result.add(conflictInfo.schedule); + } return result; } @VisibleForTesting - static Map getConflictingSchedulesInfo( - List schedules, int tunerCount) { + static List getConflictingSchedulesInfo(List schedules, + int tunerCount) { return getConflictingSchedulesInfo(schedules, tunerCount, null); } @@ -836,13 +841,13 @@ public class DvrScheduleManager { * to be partially recorded under the given schedules and tuner count {@code true}, * or not {@code false}. */ - private static Map getConflictingSchedulesInfo( + private static List getConflictingSchedulesInfo( List schedules, int tunerCount, List> periods) { List schedulesToCheck = new ArrayList<>(schedules); // Sort by the same order as that in InputTaskScheduler. Collections.sort(schedulesToCheck, InputTaskScheduler.getRecordingOrderComparator()); List recordings = new ArrayList<>(); - Map conflicts = new HashMap<>(); + Map conflicts = new HashMap<>(); Map modified2OriginalSchedules = new HashMap<>(); // Simulate InputTaskScheduler. while (!schedulesToCheck.isEmpty()) { @@ -853,26 +858,29 @@ public class DvrScheduleManager { if (modified2OriginalSchedules.containsKey(schedule)) { // Schedule has been modified, which means it's already conflicted. // Modify its state to partially conflicted. - conflicts.put(modified2OriginalSchedules.get(schedule), true); + ScheduledRecording originalSchedule = modified2OriginalSchedules.get(schedule); + conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true)); } } else { ScheduledRecording candidate = findReplaceableRecording(recordings, schedule); if (candidate != null) { if (!modified2OriginalSchedules.containsKey(candidate)) { - conflicts.put(candidate, true); + conflicts.put(candidate, new ConflictInfo(candidate, true)); } recordings.remove(candidate); recordings.add(schedule); if (modified2OriginalSchedules.containsKey(schedule)) { // Schedule has been modified, which means it's already conflicted. // Modify its state to partially conflicted. - conflicts.put(modified2OriginalSchedules.get(schedule), true); + ScheduledRecording originalSchedule = + modified2OriginalSchedules.get(schedule); + conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true)); } } else { if (!modified2OriginalSchedules.containsKey(schedule)) { // if schedule has been modified, it's already conflicted. // No need to add it again. - conflicts.put(schedule, false); + conflicts.put(schedule, new ConflictInfo(schedule, false)); } long earliestEndTime = getEarliestEndTime(recordings); if (earliestEndTime < schedule.getEndTimeMs()) { @@ -912,7 +920,14 @@ public class DvrScheduleManager { } } } - return conflicts; + List result = new ArrayList<>(conflicts.values()); + Collections.sort(result, new Comparator() { + @Override + public int compare(ConflictInfo lhs, ConflictInfo rhs) { + return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule); + } + }); + return result; } private static void removeFinishedRecordings(List recordings, @@ -954,6 +969,17 @@ public class DvrScheduleManager { return earliest; } + @VisibleForTesting + static class ConflictInfo { + public ScheduledRecording schedule; + public boolean partialConflict; + + ConflictInfo(ScheduledRecording schedule, boolean partialConflict) { + this.schedule = schedule; + this.partialConflict = partialConflict; + } + } + /** * A listener which is notified the initialization of schedule manager. */ @@ -970,6 +996,9 @@ public class DvrScheduleManager { public interface OnConflictStateChangeListener { /** * Called when the conflicting schedules change. + *

+ * Note that this can be called before + * {@link ScheduledRecordingListener#onScheduledRecordingAdded} is called. * * @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise * {@code false}. diff --git a/src/com/android/tv/dvr/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/DvrStartRecordingReceiver.java deleted file mode 100644 index 6d2f0d43..00000000 --- a/src/com/android/tv/dvr/DvrStartRecordingReceiver.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import com.android.tv.TvApplication; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - * Signals the DVR to start recording shows soon. - */ -public class DvrStartRecordingReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - TvApplication.setCurrentRunningProcess(context, true); - DvrRecordingService.startService(context); - } -} diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java index a653b5f4..2d41d732 100644 --- a/src/com/android/tv/dvr/DvrStorageStatusManager.java +++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.content.OperationApplicationException; import android.database.Cursor; import android.media.tv.TvContract; +import android.media.tv.TvInputInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; @@ -36,8 +37,11 @@ import android.support.annotation.IntDef; import android.support.annotation.WorkerThread; import android.util.Log; +import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.tuner.tvinput.TunerTvInputService; +import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.io.File; @@ -294,7 +298,7 @@ public class DvrStorageStatusManager { storageMounted, storageMountedDir, storageMountedCapacity); } - private class CleanUpDbTask extends AsyncTask { + private class CleanUpDbTask extends AsyncTask { private final ContentResolver mContentResolver; private CleanUpDbTask() { @@ -302,13 +306,15 @@ public class DvrStorageStatusManager { } @Override - protected Void doInBackground(Void... params) { + protected Boolean doInBackground(Void... params) { @DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus(); if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { return null; } - List ops = getDeleteOps(storageStatus - == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL); + if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { + return true; + } + List ops = getDeleteOps(); if (ops == null || ops.isEmpty()) { return null; } @@ -329,13 +335,28 @@ public class DvrStorageStatusManager { } @Override - protected void onPostExecute(Void result) { + protected void onPostExecute(Boolean forgetStorage) { + if (forgetStorage != null && forgetStorage == true) { + DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager(); + TvInputManagerHelper tvInputManagerHelper = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + List tvInputInfoList = + tvInputManagerHelper.getTvInputInfos(true, false); + if (tvInputInfoList == null || tvInputInfoList.isEmpty()) { + return; + } + for (TvInputInfo info : tvInputInfoList) { + if (Utils.isBundledInput(info.getId())) { + dvrManager.forgetStorage(info.getId()); + } + } + } if (mCleanUpDbTask == this) { mCleanUpDbTask = null; } } - private List getDeleteOps(boolean deleteAll) { + private List getDeleteOps() { List ops = new ArrayList<>(); try (Cursor c = mContentResolver.query( @@ -364,7 +385,7 @@ public class DvrStorageStatusManager { continue; } File recordedProgramDir = new File(dataUri.getPath()); - if (deleteAll || !recordedProgramDir.exists()) { + if (!recordedProgramDir.exists()) { ops.add(ContentProviderOperation.newDelete( TvContract.buildRecordedProgramUri(Long.parseLong(id))).build()); } diff --git a/src/com/android/tv/dvr/DvrUiHelper.java b/src/com/android/tv/dvr/DvrUiHelper.java deleted file mode 100644 index c0d3b0c5..00000000 --- a/src/com/android/tv/dvr/DvrUiHelper.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.media.tv.TvInputManager; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityOptionsCompat; -import android.text.TextUtils; -import android.widget.ImageView; -import android.widget.Toast; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; -import com.android.tv.data.Program; -import com.android.tv.dvr.ui.DvrDetailsActivity; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment; -import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment; -import com.android.tv.dvr.ui.DvrSchedulesActivity; -import com.android.tv.dvr.ui.DvrSeriesDeletionActivity; -import com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity; -import com.android.tv.dvr.ui.DvrSeriesSettingsActivity; -import com.android.tv.dvr.ui.DvrStopRecordingFragment; -import com.android.tv.dvr.ui.DvrStopSeriesRecordingDialogFragment; -import com.android.tv.dvr.ui.DvrStopSeriesRecordingFragment; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; -import com.android.tv.dvr.ui.list.DvrSchedulesFragment; -import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; -import com.android.tv.util.Utils; - -import java.util.Collections; -import java.util.List; - -/** - * A helper class for DVR UI. - */ -@MainThread -@TargetApi(Build.VERSION_CODES.N) -public class DvrUiHelper { - /** - * Handles the action to create the new schedule. It returns {@code true} if the schedule is - * added and there's no additional UI, otherwise {@code false}. - */ - public static boolean handleCreateSchedule(MainActivity activity, Program program) { - if (program == null) { - return false; - } - DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager(); - if (!program.isEpisodic()) { - // One time recording. - dvrManager.addSchedule(program); - if (!dvrManager.getConflictingSchedules(program).isEmpty()) { - DvrUiHelper.showScheduleConflictDialog(activity, program); - return false; - } - } else { - SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program); - if (seriesRecording == null || seriesRecording.isStopped()) { - DvrUiHelper.showScheduleDialog(activity, program); - return false; - } else { - // Show recorded program rather than the schedule. - RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(), - program.getSeasonNumber(), program.getEpisodeNumber()); - if (recordedProgram != null) { - DvrUiHelper.showAlreadyRecordedDialog(activity, program); - return false; - } - ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(), - program.getSeasonNumber(), program.getEpisodeNumber()); - if (duplicate != null - && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || duplicate.getState() - == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { - DvrUiHelper.showAlreadyScheduleDialog(activity, program); - return false; - } - // Just add the schedule. - dvrManager.addSchedule(program); - } - } - return true; - - } - - /** - * Checks if the storage status is good for recording and shows error messages if needed. - * - * @return true if the storage status is fine to be recorded for {@code inputId}. - */ - public static boolean checkStorageStatusAndShowErrorMessage(Activity activity, String inputId) { - if (!Utils.isBundledInput(inputId)) { - return true; - } - DvrStorageStatusManager dvrStorageStatusManager = - TvApplication.getSingletons(activity).getDvrStorageStatusManager(); - int status = dvrStorageStatusManager.getDvrStorageStatus(); - if (status == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { - showDvrSmallSizedStorageErrorDialog(activity); - return false; - } else if (status == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { - showDvrMissingStorageErrorDialog(activity, inputId); - return false; - } else if (status == DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT) { - // TODO: handle insufficient storage case. - return true; - } else { - return true; - } - } - - /** - * Shows the schedule dialog. - */ - public static void showScheduleDialog(MainActivity activity, Program program) { - if (SoftPreconditions.checkNotNull(program) == null) { - return; - } - Bundle args = new Bundle(); - args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); - showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true); - } - - /** - * Shows the recording duration options dialog. - */ - public static void showChannelRecordDurationOptions(MainActivity activity, Channel channel) { - if (SoftPreconditions.checkNotNull(channel) == null) { - return; - } - Bundle args = new Bundle(); - args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); - showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args); - } - - /** - * Shows the dialog which says that the new schedule conflicts with others. - */ - public static void showScheduleConflictDialog(MainActivity activity, Program program) { - if (program == null) { - return; - } - Bundle args = new Bundle(); - args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); - showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true); - } - - /** - * Shows the conflict dialog for the channel watching. - */ - public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) { - if (channel == null) { - return; - } - Bundle args = new Bundle(); - args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); - showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args); - } - - /** - * Shows DVR insufficient space error dialog. - */ - public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity) { - showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), null); - Utils.clearRecordingFailedReason(activity, - TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - } - - /** - * Shows DVR missing storage error dialog. - */ - private static void showDvrMissingStorageErrorDialog(Activity activity, String inputId) { - SoftPreconditions.checkArgument(!TextUtils.isEmpty(inputId)); - Bundle args = new Bundle(); - args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, inputId); - showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), args); - } - - /** - * Shows DVR small sized storage error dialog. - */ - public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { - showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); - } - - /** - * Shows stop recording dialog. - */ - public static void showStopRecordingDialog(Activity activity, long channelId, int reason, - HalfSizedDialogFragment.OnActionClickListener listener) { - Bundle args = new Bundle(); - args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId); - args.putInt(DvrStopRecordingFragment.KEY_REASON, reason); - DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment(); - fragment.setOnActionClickListener(listener); - showDialogFragment(activity, fragment, args); - } - - /** - * Shows "already scheduled" dialog. - */ - public static void showAlreadyScheduleDialog(MainActivity activity, Program program) { - if (program == null) { - return; - } - Bundle args = new Bundle(); - args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); - showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true); - } - - /** - * Shows "already recorded" dialog. - */ - public static void showAlreadyRecordedDialog(MainActivity activity, Program program) { - if (program == null) { - return; - } - Bundle args = new Bundle(); - args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); - showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true); - } - - private static void showDialogFragment(Activity activity, - DvrHalfSizedDialogFragment dialogFragment, Bundle args) { - showDialogFragment(activity, dialogFragment, args, false, false); - } - - private static void showDialogFragment(Activity activity, - DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, - boolean keepProgramGuide) { - dialogFragment.setArguments(args); - if (activity instanceof MainActivity) { - ((MainActivity) activity).getOverlayManager() - .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment, - keepSidePanelHistory, keepProgramGuide); - } else { - dialogFragment.show(activity.getFragmentManager(), - DvrHalfSizedDialogFragment.DIALOG_TAG); - } - } - - /** - * Checks whether channel watch conflict dialog is open or not. - */ - public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { - return activity.getOverlayManager().getCurrentDialog() instanceof - DvrChannelWatchConflictDialogFragment; - } - - private static ScheduledRecording getEarliestScheduledRecording(List - recordings) { - ScheduledRecording earlistScheduledRecording = null; - if (!recordings.isEmpty()) { - Collections.sort(recordings, - ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); - earlistScheduledRecording = recordings.get(0); - } - return earlistScheduledRecording; - } - - /** - * Shows the schedules activity to resolve the tune conflict. - */ - public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) { - if (channel == null) { - return; - } - List conflicts = TvApplication.getSingletons(context).getDvrManager() - .getConflictingSchedulesForTune(channel.getId()); - startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); - } - - /** - * Shows the schedules activity to resolve the one time recording conflict. - */ - public static void startSchedulesActivityForOneTimeRecordingConflict(Context context, - List conflicts) { - startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); - } - - /** - * Shows the schedules activity with full schedule. - */ - public static void startSchedulesActivity(Context context, ScheduledRecording - focusedScheduledRecording) { - Intent intent = new Intent(context, DvrSchedulesActivity.class); - intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, - DvrSchedulesActivity.TYPE_FULL_SCHEDULE); - if (focusedScheduledRecording != null) { - intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, - focusedScheduledRecording); - } - context.startActivity(intent); - } - - /** - * Shows the schedules activity for series recording. - */ - public static void startSchedulesActivityForSeries(Context context, - SeriesRecording seriesRecording) { - Intent intent = new Intent(context, DvrSchedulesActivity.class); - intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, - DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); - intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, - seriesRecording); - context.startActivity(intent); - } - - /** - * Shows the series settings activity. - * - * @param channelIds Channel ID list which has programs belonging to the series. - */ - public static void startSeriesSettingsActivity(Context context, long seriesRecordingId, - @Nullable long[] channelIds, boolean removeEmptySeriesSchedule, - boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog) { - Intent intent = new Intent(context, DvrSeriesSettingsActivity.class); - intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId); - intent.putExtra(DvrSeriesSettingsActivity.CHANNEL_ID_LIST, channelIds); - intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, - removeEmptySeriesSchedule); - intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent); - intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, - showViewScheduleOptionInDialog); - context.startActivity(intent); - } - - /** - * Shows "series recording scheduled" dialog activity. - */ - public static void StartSeriesScheduledDialogActivity(Context context, - SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog) { - if (seriesRecording == null) { - return; - } - Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class); - intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, - seriesRecording.getId()); - intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, - showViewScheduleOptionInDialog); - context.startActivity(intent); - } - - /** - * Shows the details activity for the DVR items. The type of DVR items may be - * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. - */ - public static void startDetailsActivity(Activity activity, Object dvrItem, - @Nullable ImageView imageView, boolean hideViewSchedule) { - if (dvrItem == null) { - return; - } - Intent intent = new Intent(activity, DvrDetailsActivity.class); - long recordingId; - int viewType; - if (dvrItem instanceof ScheduledRecording) { - ScheduledRecording schedule = (ScheduledRecording) dvrItem; - recordingId = schedule.getId(); - if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { - viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW; - } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { - viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW; - } else { - return; - } - } else if (dvrItem instanceof RecordedProgram) { - recordingId = ((RecordedProgram) dvrItem).getId(); - viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW; - } else if (dvrItem instanceof SeriesRecording) { - recordingId = ((SeriesRecording) dvrItem).getId(); - viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW; - } else { - return; - } - intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId); - intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType); - intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule); - Bundle bundle = null; - if (imageView != null) { - bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, - DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); - } - activity.startActivity(intent, bundle); - } - - /** - * Shows the cancel all dialog for series schedules list. - */ - public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, - SeriesRecording seriesRecording) { - DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = - new DvrStopSeriesRecordingDialogFragment(); - Bundle arguments = new Bundle(); - arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, - seriesRecording); - dvrStopSeriesRecordingDialogFragment.setArguments(arguments); - dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(), - DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); - } - - /** - * Shows the series deletion activity. - */ - public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) { - Intent intent = new Intent(context, DvrSeriesDeletionActivity.class); - intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId); - context.startActivity(intent); - } - - public static void showAddScheduleToast(Context context, - String title, long startTimeMs, long endTimeMs) { - String msg = (startTimeMs > System.currentTimeMillis()) ? - context.getString(R.string.dvr_msg_program_scheduled, title) - : context.getString(R.string.dvr_msg_current_program_scheduled, title, - Utils.toTimeString(endTimeMs, false)); - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); - } -} diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java index 4eada742..da6ddb1a 100644 --- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java +++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java @@ -22,6 +22,7 @@ import android.media.tv.TvInputManager; import android.support.annotation.IntDef; import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.dvr.data.RecordedProgram; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,9 +37,6 @@ import java.util.concurrent.CopyOnWriteArraySet; * It will remember and provides previous watched position of DVR playback. */ public class DvrWatchedPositionManager { - private final static String TAG = "DvrWatchedPositionManager"; - private final boolean DEBUG = false; - private SharedPreferences mWatchedPositions; private final Map mListeners = new HashMap<>(); diff --git a/src/com/android/tv/dvr/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/EpisodicProgramLoadTask.java deleted file mode 100644 index 15ca2700..00000000 --- a/src/com/android/tv/dvr/EpisodicProgramLoadTask.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.TargetApi; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.media.tv.TvContract.Programs; -import android.net.Uri; -import android.os.Build; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; - -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Program; -import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask; -import com.android.tv.util.AsyncDbTask.CursorFilter; -import com.android.tv.util.PermissionUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -/** - * A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings. - */ -@TargetApi(Build.VERSION_CODES.N) -abstract public class EpisodicProgramLoadTask { - private static final String TAG = "EpisodicProgramLoadTask"; - - private static final int PROGRAM_ID_INDEX = Program.getColumnIndex(Programs._ID); - private static final int START_TIME_INDEX = - Program.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS); - private static final int RECORDING_PROHIBITED_INDEX = - Program.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED); - - private static final String PARAM_START_TIME = "start_time"; - private static final String PARAM_END_TIME = "end_time"; - - private static final String PROGRAM_PREDICATE = - Programs.COLUMN_START_TIME_UTC_MILLIS + ">? AND " - + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; - private static final String PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM = - Programs.COLUMN_END_TIME_UTC_MILLIS + ">? AND " - + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; - private static final String CHANNEL_ID_PREDICATE = Programs.COLUMN_CHANNEL_ID + "=?"; - private static final String PROGRAM_TITLE_PREDICATE = Programs.COLUMN_TITLE + "=?"; - - private final Context mContext; - private final DvrDataManager mDataManager; - private boolean mQueryAllChannels; - private boolean mLoadCurrentProgram; - private boolean mLoadScheduledEpisode; - private boolean mLoadDisallowedProgram; - // If true, match programs with OPTION_CHANNEL_ALL. - private boolean mIgnoreChannelOption; - private final ArrayList mSeriesRecordings = new ArrayList<>(); - private AsyncProgramQueryTask mProgramQueryTask; - - /** - * - * Constructor used to load programs for one series recording with the given channel option. - */ - public EpisodicProgramLoadTask(Context context, SeriesRecording seriesRecording) { - this(context, Collections.singletonList(seriesRecording)); - } - - /** - * Constructor used to load programs for multiple series recordings. The channel option is - * {@link SeriesRecording#OPTION_CHANNEL_ALL}. - */ - public EpisodicProgramLoadTask(Context context, Collection seriesRecordings) { - mContext = context.getApplicationContext(); - mDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - mSeriesRecordings.addAll(seriesRecordings); - } - - /** - * Returns the series recordings. - */ - public List getSeriesRecordings() { - return mSeriesRecordings; - } - - /** - * Returns the program query task. It is {@code null} until it is executed. - */ - @Nullable - public AsyncProgramQueryTask getTask() { - return mProgramQueryTask; - } - - /** - * Enables loading current programs. The default value is {@code false}. - */ - public EpisodicProgramLoadTask setLoadCurrentProgram(boolean loadCurrentProgram) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); - mLoadCurrentProgram = loadCurrentProgram; - return this; - } - - /** - * Enables already schedules episodes. The default value is {@code false}. - */ - public EpisodicProgramLoadTask setLoadScheduledEpisode(boolean loadScheduledEpisode) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); - mLoadScheduledEpisode = loadScheduledEpisode; - return this; - } - - /** - * Enables loading disallowed programs whose schedules were removed manually by the user. - * The default value is {@code false}. - */ - public EpisodicProgramLoadTask setLoadDisallowedProgram(boolean loadDisallowedProgram) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); - mLoadDisallowedProgram = loadDisallowedProgram; - return this; - } - - /** - * Gives the option whether to ignore the channel option when matching programs. - * If {@code ignoreChannelOption} is {@code true}, the program will be matched with - * {@link SeriesRecording#OPTION_CHANNEL_ALL} option. - */ - public EpisodicProgramLoadTask setIgnoreChannelOption(boolean ignoreChannelOption) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); - mIgnoreChannelOption = ignoreChannelOption; - return this; - } - - /** - * Executes the task. - * - * @see com.android.tv.util.AsyncDbTask#executeOnDbThread - */ - public void execute() { - if (SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't execute task: the task is already running.")) { - mQueryAllChannels = mSeriesRecordings.size() > 1 - || mSeriesRecordings.get(0).getChannelOption() - == SeriesRecording.OPTION_CHANNEL_ALL - || mIgnoreChannelOption; - mProgramQueryTask = createTask(); - mProgramQueryTask.executeOnDbThread(); - } - } - - /** - * Cancels the task. - * - * @see android.os.AsyncTask#cancel - */ - public void cancel(boolean mayInterruptIfRunning) { - if (mProgramQueryTask != null) { - mProgramQueryTask.cancel(mayInterruptIfRunning); - } - } - - /** - * Runs on the UI thread after the program loading finishes successfully. - */ - protected void onPostExecute(List programs) { - } - - /** - * Runs on the UI thread after the program loading was canceled. - */ - protected void onCancelled(List programs) { - } - - private AsyncProgramQueryTask createTask() { - SqlParams sqlParams = createSqlParams(); - return new AsyncProgramQueryTask(mContext.getContentResolver(), sqlParams.uri, - sqlParams.selection, sqlParams.selectionArgs, null, sqlParams.filter) { - @Override - protected void onPostExecute(List programs) { - EpisodicProgramLoadTask.this.onPostExecute(programs); - } - - @Override - protected void onCancelled(List programs) { - EpisodicProgramLoadTask.this.onCancelled(programs); - } - }; - } - - private SqlParams createSqlParams() { - SqlParams sqlParams = new SqlParams(); - if (PermissionUtils.hasAccessAllEpg(mContext)) { - sqlParams.uri = Programs.CONTENT_URI; - // Base - StringBuilder selection = new StringBuilder(mLoadCurrentProgram - ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM : PROGRAM_PREDICATE); - List args = new ArrayList<>(); - args.add(Long.toString(System.currentTimeMillis())); - // Channel option - if (!mQueryAllChannels) { - selection.append(" AND ").append(CHANNEL_ID_PREDICATE); - args.add(Long.toString(mSeriesRecordings.get(0).getChannelId())); - } - // Title - if (mSeriesRecordings.size() == 1) { - selection.append(" AND ").append(PROGRAM_TITLE_PREDICATE); - args.add(mSeriesRecordings.get(0).getTitle()); - } - sqlParams.selection = selection.toString(); - sqlParams.selectionArgs = args.toArray(new String[args.size()]); - sqlParams.filter = new SeriesRecordingCursorFilter(mSeriesRecordings); - } else { - // The query includes the current program. Will be filtered if needed. - if (mQueryAllChannels) { - sqlParams.uri = Programs.CONTENT_URI.buildUpon() - .appendQueryParameter(PARAM_START_TIME, - String.valueOf(System.currentTimeMillis())) - .appendQueryParameter(PARAM_END_TIME, String.valueOf(Long.MAX_VALUE)) - .build(); - } else { - sqlParams.uri = TvContract.buildProgramsUriForChannel( - mSeriesRecordings.get(0).getChannelId(), - System.currentTimeMillis(), Long.MAX_VALUE); - } - sqlParams.selection = null; - sqlParams.selectionArgs = null; - sqlParams.filter = new SeriesRecordingCursorFilterForNonSystem(mSeriesRecordings); - } - return sqlParams; - } - - @VisibleForTesting - static boolean isEpisodeScheduled(Collection scheduledEpisodes, - ScheduledEpisode episode) { - // The episode whose season number or episode number is null will always be scheduled. - return scheduledEpisodes.contains(episode) && !TextUtils.isEmpty(episode.seasonNumber) - && !TextUtils.isEmpty(episode.episodeNumber); - } - - /** - * Filter the programs which match the series recording. The episodes which the schedules are - * already created for are filtered out too. - */ - private class SeriesRecordingCursorFilter implements CursorFilter { - private final Set mDisallowedProgramIds = new HashSet<>(); - private final Set mScheduledEpisodes = new HashSet<>(); - - SeriesRecordingCursorFilter(List seriesRecordings) { - if (!mLoadDisallowedProgram) { - mDisallowedProgramIds.addAll(mDataManager.getDisallowedProgramIds()); - } - if (!mLoadScheduledEpisode) { - Set seriesRecordingIds = new HashSet<>(); - for (SeriesRecording r : seriesRecordings) { - seriesRecordingIds.add(r.getId()); - } - for (ScheduledRecording r : mDataManager.getAllScheduledRecordings()) { - if (seriesRecordingIds.contains(r.getSeriesRecordingId()) - && r.getState() != ScheduledRecording.STATE_RECORDING_FAILED - && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) { - mScheduledEpisodes.add(new ScheduledEpisode(r)); - } - } - } - } - - @Override - @WorkerThread - public boolean filter(Cursor c) { - if (!mLoadDisallowedProgram - && mDisallowedProgramIds.contains(c.getLong(PROGRAM_ID_INDEX))) { - return false; - } - Program program = Program.fromCursor(c); - for (SeriesRecording seriesRecording : mSeriesRecordings) { - boolean programMatches; - if (mIgnoreChannelOption) { - programMatches = seriesRecording.matchProgram(program, - SeriesRecording.OPTION_CHANNEL_ALL); - } else { - programMatches = seriesRecording.matchProgram(program); - } - if (programMatches) { - return mLoadScheduledEpisode - || !isEpisodeScheduled(mScheduledEpisodes, new ScheduledEpisode( - seriesRecording.getId(), program.getSeasonNumber(), - program.getEpisodeNumber())); - } - } - return false; - } - } - - private class SeriesRecordingCursorFilterForNonSystem extends SeriesRecordingCursorFilter { - SeriesRecordingCursorFilterForNonSystem(List seriesRecordings) { - super(seriesRecordings); - } - - @Override - public boolean filter(Cursor c) { - return (mLoadCurrentProgram || c.getLong(START_TIME_INDEX) > System.currentTimeMillis()) - && c.getInt(RECORDING_PROHIBITED_INDEX) != 0 && super.filter(c); - } - } - - private static class SqlParams { - public Uri uri; - public String selection; - public String[] selectionArgs; - public CursorFilter filter; - } - - /** - * A plain java object which includes the season/episode number for the series recording. - */ - public static class ScheduledEpisode { - public final long seriesRecordingId; - public final String seasonNumber; - public final String episodeNumber; - - /** - * Create a new Builder with the values set from an existing {@link ScheduledRecording}. - */ - ScheduledEpisode(ScheduledRecording r) { - this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()); - } - - public ScheduledEpisode(long seriesRecordingId, String seasonNumber, String episodeNumber) { - this.seriesRecordingId = seriesRecordingId; - this.seasonNumber = seasonNumber; - this.episodeNumber = episodeNumber; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ScheduledEpisode)) return false; - ScheduledEpisode that = (ScheduledEpisode) o; - return seriesRecordingId == that.seriesRecordingId - && Objects.equals(seasonNumber, that.seasonNumber) - && Objects.equals(episodeNumber, that.episodeNumber); - } - - @Override - public int hashCode() { - return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber); - } - - @Override - public String toString() { - return "ScheduledEpisode{" + - "seriesRecordingId=" + seriesRecordingId + - ", seasonNumber='" + seasonNumber + - ", episodeNumber=" + episodeNumber + - '}'; - } - } -} diff --git a/src/com/android/tv/dvr/IdGenerator.java b/src/com/android/tv/dvr/IdGenerator.java deleted file mode 100644 index 0ed6362c..00000000 --- a/src/com/android/tv/dvr/IdGenerator.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * A class which generate the ID which increases sequentially. - */ -public class IdGenerator { - /** - * ID generator for the scheduled recording. - */ - public static final IdGenerator SCHEDULED_RECORDING = new IdGenerator(); - - /** - * ID generator for the series recording. - */ - public static final IdGenerator SERIES_RECORDING = new IdGenerator(); - - private final AtomicLong mMaxId = new AtomicLong(0); - - /** - * Sets the new maximum ID. - */ - public void setMaxId(long maxId) { - mMaxId.set(maxId); - } - - /** - * Returns the new ID which is greater than the existing maximum ID by 1. - */ - public long newId() { - return mMaxId.incrementAndGet(); - } -} diff --git a/src/com/android/tv/dvr/InputTaskScheduler.java b/src/com/android/tv/dvr/InputTaskScheduler.java deleted file mode 100644 index 53c89ebc..00000000 --- a/src/com/android/tv/dvr/InputTaskScheduler.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.content.Context; -import android.media.tv.TvInputInfo; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.util.ArrayMap; -import android.util.Log; -import android.util.LongSparseArray; - -import com.android.tv.InputSessionManager; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.util.Clock; -import com.android.tv.util.CompositeComparator; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * The scheduler for a TV input. - */ -public class InputTaskScheduler { - private static final String TAG = "InputTaskScheduler"; - private static final boolean DEBUG = false; - - private static final int MSG_ADD_SCHEDULED_RECORDING = 1; - private static final int MSG_REMOVE_SCHEDULED_RECORDING = 2; - private static final int MSG_UPDATE_SCHEDULED_RECORDING = 3; - private static final int MSG_BUILD_SCHEDULE = 4; - private static final int MSG_STOP_SCHEDULE = 5; - - private static final float MIN_REMAIN_DURATION_PERCENT = 0.05f; - - // The candidate comparator should be the consistent with - // DvrScheduleManager#CANDIDATE_COMPARATOR. - private static final Comparator CANDIDATE_COMPARATOR = - new CompositeComparator<>( - RecordingTask.PRIORITY_COMPARATOR, - RecordingTask.END_TIME_COMPARATOR, - RecordingTask.ID_COMPARATOR); - - /** - * Returns the comparator which the schedules are sorted with when executed. - */ - public static Comparator getRecordingOrderComparator() { - return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR; - } - - /** - * Wraps a {@link RecordingTask} removing it from {@link #mPendingRecordings} when it is done. - */ - public final class HandlerWrapper extends Handler { - public static final int MESSAGE_REMOVE = 999; - private final long mId; - private final RecordingTask mTask; - - HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording, - RecordingTask recordingTask) { - super(looper, recordingTask); - mId = scheduledRecording.getId(); - mTask = recordingTask; - mTask.setHandler(this); - } - - @Override - public void handleMessage(Message msg) { - // The RecordingTask gets a chance first. - // It must return false to pass this message to here. - if (msg.what == MESSAGE_REMOVE) { - if (DEBUG) Log.d(TAG, "done " + mId); - mPendingRecordings.remove(mId); - } - removeCallbacksAndMessages(null); - mHandler.removeMessages(MSG_BUILD_SCHEDULE); - mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); - super.handleMessage(msg); - } - } - - private TvInputInfo mInput; - private final Looper mLooper; - private final ChannelDataManager mChannelDataManager; - private final DvrManager mDvrManager; - private final WritableDvrDataManager mDataManager; - private final InputSessionManager mSessionManager; - private final Clock mClock; - private final Context mContext; - - private final LongSparseArray mPendingRecordings = new LongSparseArray<>(); - private final Map mWaitingSchedules = new ArrayMap<>(); - private final Handler mMainThreadHandler; - private final Handler mHandler; - private final Object mInputLock = new Object(); - private final RecordingTaskFactory mRecordingTaskFactory; - - public InputTaskScheduler(Context context, TvInputInfo input, Looper looper, - ChannelDataManager channelDataManager, DvrManager dvrManager, - DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock) { - this(context, input, looper, channelDataManager, dvrManager, dataManager, sessionManager, - clock, new Handler(Looper.getMainLooper()), null, null); - } - - @VisibleForTesting - InputTaskScheduler(Context context, TvInputInfo input, Looper looper, - ChannelDataManager channelDataManager, DvrManager dvrManager, - DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock, - Handler mainThreadHandler, @Nullable Handler workerThreadHandler, - RecordingTaskFactory recordingTaskFactory) { - if (DEBUG) Log.d(TAG, "Creating scheduler for " + input); - mContext = context; - mInput = input; - mLooper = looper; - mChannelDataManager = channelDataManager; - mDvrManager = dvrManager; - mDataManager = (WritableDvrDataManager) dataManager; - mSessionManager = sessionManager; - mClock = clock; - mMainThreadHandler = mainThreadHandler; - mRecordingTaskFactory = recordingTaskFactory != null ? recordingTaskFactory - : new RecordingTaskFactory() { - @Override - public RecordingTask createRecordingTask(ScheduledRecording schedule, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock) { - return new RecordingTask(mContext, schedule, channel, mDvrManager, mSessionManager, - mDataManager, mClock); - } - }; - if (workerThreadHandler == null) { - mHandler = new WorkerThreadHandler(looper); - } else { - mHandler = workerThreadHandler; - } - } - - /** - * Adds a {@link ScheduledRecording}. - */ - public void addSchedule(ScheduledRecording schedule) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule)); - } - - @VisibleForTesting - void handleAddSchedule(ScheduledRecording schedule) { - if (mPendingRecordings.get(schedule.getId()) != null - || mWaitingSchedules.containsKey(schedule.getId())) { - return; - } - mWaitingSchedules.put(schedule.getId(), schedule); - mHandler.removeMessages(MSG_BUILD_SCHEDULE); - mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); - } - - /** - * Removes the {@link ScheduledRecording}. - */ - public void removeSchedule(ScheduledRecording schedule) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule)); - } - - @VisibleForTesting - void handleRemoveSchedule(ScheduledRecording schedule) { - HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId()); - if (wrapper != null) { - wrapper.mTask.cancel(); - return; - } - if (mWaitingSchedules.containsKey(schedule.getId())) { - mWaitingSchedules.remove(schedule.getId()); - mHandler.removeMessages(MSG_BUILD_SCHEDULE); - mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); - } - } - - /** - * Updates the {@link ScheduledRecording}. - */ - public void updateSchedule(ScheduledRecording schedule) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule)); - } - - @VisibleForTesting - void handleUpdateSchedule(ScheduledRecording schedule) { - HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId()); - if (wrapper != null) { - if (schedule.getStartTimeMs() > mClock.currentTimeMillis() - && schedule.getStartTimeMs() > wrapper.mTask.getStartTimeMs()) { - // It shouldn't have started. Cancel and put to the waiting list. - // The schedules will be rebuilt when the task is removed. - // The reschedule is called in Scheduler. - wrapper.mTask.cancel(); - mWaitingSchedules.put(schedule.getId(), schedule); - return; - } - wrapper.sendMessage(wrapper.obtainMessage(RecordingTask.MSG_UDPATE_SCHEDULE, schedule)); - return; - } - if (mWaitingSchedules.containsKey(schedule.getId())) { - mWaitingSchedules.put(schedule.getId(), schedule); - mHandler.removeMessages(MSG_BUILD_SCHEDULE); - mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); - } - } - - /** - * Updates the TV input. - */ - public void updateTvInputInfo(TvInputInfo input) { - synchronized (mInputLock) { - mInput = input; - } - } - - /** - * Stops the input task scheduler. - */ - public void stop() { - mHandler.removeCallbacksAndMessages(null); - mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE); - } - - private void handleStopSchedule() { - mWaitingSchedules.clear(); - int size = mPendingRecordings.size(); - for (int i = 0; i < size; ++i) { - RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; - task.cleanUp(); - } - } - - @VisibleForTesting - void handleBuildSchedule() { - if (mWaitingSchedules.isEmpty()) { - return; - } - long currentTimeMs = mClock.currentTimeMillis(); - // Remove past schedules. - for (Iterator iter = mWaitingSchedules.values().iterator(); - iter.hasNext(); ) { - ScheduledRecording schedule = iter.next(); - if (schedule.getEndTimeMs() - currentTimeMs - <= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) { - fail(schedule); - iter.remove(); - } - } - if (mWaitingSchedules.isEmpty()) { - return; - } - // Record the schedules which should start now. - List schedulesToStart = new ArrayList<>(); - for (ScheduledRecording schedule : mWaitingSchedules.values()) { - if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED - && schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS - <= currentTimeMs && schedule.getEndTimeMs() > currentTimeMs) { - schedulesToStart.add(schedule); - } - } - // The schedules will be executed with the following order. - // 1. The schedule which starts early. It can be replaced later when the schedule with the - // higher priority needs to start. - // 2. The schedule with the higher priority. It can be replaced later when the schedule with - // the higher priority needs to start. - // 3. The schedule which was created recently. - Collections.sort(schedulesToStart, getRecordingOrderComparator()); - int tunerCount; - synchronized (mInputLock) { - tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0; - } - for (ScheduledRecording schedule : schedulesToStart) { - if (hasTaskWhichFinishEarlier(schedule)) { - // If there is a schedule which finishes earlier than the new schedule, rebuild the - // schedules after it finishes. - return; - } - if (mPendingRecordings.size() < tunerCount) { - // Tuners available. - createRecordingTask(schedule).start(); - mWaitingSchedules.remove(schedule.getId()); - } else { - // No available tuners. - RecordingTask task = getReplacableTask(schedule); - if (task != null) { - task.stop(); - // Just return. The schedules will be rebuilt after the task is stopped. - return; - } - } - } - if (mWaitingSchedules.isEmpty()) { - return; - } - // Set next scheduling. - long earliest = Long.MAX_VALUE; - for (ScheduledRecording schedule : mWaitingSchedules.values()) { - // The conflicting schedules will be removed if they end before conflicting resolved. - if (schedulesToStart.contains(schedule)) { - if (earliest > schedule.getEndTimeMs()) { - earliest = schedule.getEndTimeMs(); - } - } else { - if (earliest > schedule.getStartTimeMs() - - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) { - earliest = schedule.getStartTimeMs() - - RecordingTask.RECORDING_EARLY_START_OFFSET_MS; - } - } - } - mHandler.sendEmptyMessageDelayed(MSG_BUILD_SCHEDULE, earliest - currentTimeMs); - } - - private RecordingTask createRecordingTask(ScheduledRecording schedule) { - Channel channel = mChannelDataManager.getChannel(schedule.getChannelId()); - RecordingTask recordingTask = mRecordingTaskFactory.createRecordingTask(schedule, channel, - mDvrManager, mSessionManager, mDataManager, mClock); - HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask); - mPendingRecordings.put(schedule.getId(), handlerWrapper); - return recordingTask; - } - - private boolean hasTaskWhichFinishEarlier(ScheduledRecording schedule) { - int size = mPendingRecordings.size(); - for (int i = 0; i < size; ++i) { - RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; - if (task.getEndTimeMs() <= schedule.getStartTimeMs()) { - return true; - } - } - return false; - } - - private RecordingTask getReplacableTask(ScheduledRecording schedule) { - // Returns the recording with the following priority. - // 1. The recording with the lowest priority is returned. - // 2. If the priorities are the same, the recording which finishes early is returned. - // 3. If 1) and 2) are the same, the early created schedule is returned. - int size = mPendingRecordings.size(); - RecordingTask candidate = null; - for (int i = 0; i < size; ++i) { - RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; - if (schedule.getPriority() > task.getPriority()) { - if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, task) > 0) { - candidate = task; - } - } - } - return candidate; - } - - private void fail(ScheduledRecording schedule) { - // It's called when the scheduling has been failed without creating RecordingTask. - runOnMainHandler(new Runnable() { - @Override - public void run() { - ScheduledRecording scheduleInManager = - mDataManager.getScheduledRecording(schedule.getId()); - if (scheduleInManager != null) { - // The schedule should be updated based on the object from DataManager in case - // when it has been updated. - mDataManager.changeState(scheduleInManager, - ScheduledRecording.STATE_RECORDING_FAILED); - } - } - }); - } - - private void runOnMainHandler(Runnable runnable) { - if (Looper.myLooper() == mMainThreadHandler.getLooper()) { - runnable.run(); - } else { - mMainThreadHandler.post(runnable); - } - } - - @VisibleForTesting - interface RecordingTaskFactory { - RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock); - } - - private class WorkerThreadHandler extends Handler { - public WorkerThreadHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ADD_SCHEDULED_RECORDING: - handleAddSchedule((ScheduledRecording) msg.obj); - break; - case MSG_REMOVE_SCHEDULED_RECORDING: - handleRemoveSchedule((ScheduledRecording) msg.obj); - break; - case MSG_UPDATE_SCHEDULED_RECORDING: - handleUpdateSchedule((ScheduledRecording) msg.obj); - case MSG_BUILD_SCHEDULE: - handleBuildSchedule(); - break; - case MSG_STOP_SCHEDULE: - handleStopSchedule(); - break; - } - } - } -} diff --git a/src/com/android/tv/dvr/RecordedProgram.java b/src/com/android/tv/dvr/RecordedProgram.java deleted file mode 100644 index dd744f80..00000000 --- a/src/com/android/tv/dvr/RecordedProgram.java +++ /dev/null @@ -1,868 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static android.media.tv.TvContract.RecordedPrograms; - -import android.annotation.TargetApi; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.net.Uri; -import android.os.Build; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.tv.common.R; -import com.android.tv.data.BaseProgram; -import com.android.tv.data.GenreItems; -import com.android.tv.data.InternalDataUtils; -import com.android.tv.util.Utils; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. - */ -@TargetApi(Build.VERSION_CODES.N) -public class RecordedProgram extends BaseProgram { - public static final int ID_NOT_SET = -1; - - public final static String[] PROJECTION = { - // These are in exactly the order listed in RecordedPrograms - RecordedPrograms._ID, - RecordedPrograms.COLUMN_PACKAGE_NAME, - RecordedPrograms.COLUMN_INPUT_ID, - RecordedPrograms.COLUMN_CHANNEL_ID, - RecordedPrograms.COLUMN_TITLE, - RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, - RecordedPrograms.COLUMN_SEASON_TITLE, - RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, - RecordedPrograms.COLUMN_EPISODE_TITLE, - RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_BROADCAST_GENRE, - RecordedPrograms.COLUMN_CANONICAL_GENRE, - RecordedPrograms.COLUMN_SHORT_DESCRIPTION, - RecordedPrograms.COLUMN_LONG_DESCRIPTION, - RecordedPrograms.COLUMN_VIDEO_WIDTH, - RecordedPrograms.COLUMN_VIDEO_HEIGHT, - RecordedPrograms.COLUMN_AUDIO_LANGUAGE, - RecordedPrograms.COLUMN_CONTENT_RATING, - RecordedPrograms.COLUMN_POSTER_ART_URI, - RecordedPrograms.COLUMN_THUMBNAIL_URI, - RecordedPrograms.COLUMN_SEARCHABLE, - RecordedPrograms.COLUMN_RECORDING_DATA_URI, - RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, - RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, - RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, - RecordedPrograms.COLUMN_VERSION_NUMBER, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, - }; - - public static RecordedProgram fromCursor(Cursor cursor) { - int index = 0; - Builder builder = builder() - .setId(cursor.getLong(index++)) - .setPackageName(cursor.getString(index++)) - .setInputId(cursor.getString(index++)) - .setChannelId(cursor.getLong(index++)) - .setTitle(cursor.getString(index++)) - .setSeasonNumber(cursor.getString(index++)) - .setSeasonTitle(cursor.getString(index++)) - .setEpisodeNumber(cursor.getString(index++)) - .setEpisodeTitle(cursor.getString(index++)) - .setStartTimeUtcMillis(cursor.getLong(index++)) - .setEndTimeUtcMillis(cursor.getLong(index++)) - .setBroadcastGenres(cursor.getString(index++)) - .setCanonicalGenres(cursor.getString(index++)) - .setShortDescription(cursor.getString(index++)) - .setLongDescription(cursor.getString(index++)) - .setVideoWidth(cursor.getInt(index++)) - .setVideoHeight(cursor.getInt(index++)) - .setAudioLanguage(cursor.getString(index++)) - .setContentRating(cursor.getString(index++)) - .setPosterArtUri(cursor.getString(index++)) - .setThumbnailUri(cursor.getString(index++)) - .setSearchable(cursor.getInt(index++) == 1) - .setDataUri(cursor.getString(index++)) - .setDataBytes(cursor.getLong(index++)) - .setDurationMillis(cursor.getLong(index++)) - .setExpireTimeUtcMillis(cursor.getLong(index++)) - .setInternalProviderFlag1(cursor.getInt(index++)) - .setInternalProviderFlag2(cursor.getInt(index++)) - .setInternalProviderFlag3(cursor.getInt(index++)) - .setInternalProviderFlag4(cursor.getInt(index++)) - .setVersionNumber(cursor.getInt(index++)); - if (Utils.isInBundledPackageSet(builder.mPackageName)) { - InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); - } - return builder.build(); - } - - public static ContentValues toValues(RecordedProgram recordedProgram) { - ContentValues values = new ContentValues(); - if (recordedProgram.mId != ID_NOT_SET) { - values.put(RecordedPrograms._ID, recordedProgram.mId); - } - values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId); - values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId); - values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle); - values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber); - values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle); - values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber); - values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle); - values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, - recordedProgram.mStartTimeUtcMillis); - values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis); - values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, - safeEncode(recordedProgram.mBroadcastGenres)); - values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, - safeEncode(recordedProgram.mCanonicalGenres)); - values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription); - values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription); - if (recordedProgram.mVideoWidth == 0) { - values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH); - } else { - values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth); - } - if (recordedProgram.mVideoHeight == 0) { - values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT); - } else { - values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight); - } - values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage); - values.put(RecordedPrograms.COLUMN_CONTENT_RATING, recordedProgram.mContentRating); - values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri); - values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri); - values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0); - values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, - safeToString(recordedProgram.mDataUri)); - values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes); - values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, - recordedProgram.mDurationMillis); - values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, - recordedProgram.mExpireTimeUtcMillis); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, - InternalDataUtils.serializeInternalProviderData(recordedProgram)); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, - recordedProgram.mInternalProviderFlag1); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, - recordedProgram.mInternalProviderFlag2); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, - recordedProgram.mInternalProviderFlag3); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, - recordedProgram.mInternalProviderFlag4); - values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber); - return values; - } - - public static class Builder{ - private long mId = ID_NOT_SET; - private String mPackageName; - private String mInputId; - private long mChannelId; - private String mTitle; - private String mSeriesId; - private String mSeasonNumber; - private String mSeasonTitle; - private String mEpisodeNumber; - private String mEpisodeTitle; - private long mStartTimeUtcMillis; - private long mEndTimeUtcMillis; - private String[] mBroadcastGenres; - private String[] mCanonicalGenres; - private String mShortDescription; - private String mLongDescription; - private int mVideoWidth; - private int mVideoHeight; - private String mAudioLanguage; - private String mContentRating; - private String mPosterArtUri; - private String mThumbnailUri; - private boolean mSearchable = true; - private Uri mDataUri; - private long mDataBytes; - private long mDurationMillis; - private long mExpireTimeUtcMillis; - private int mInternalProviderFlag1; - private int mInternalProviderFlag2; - private int mInternalProviderFlag3; - private int mInternalProviderFlag4; - private int mVersionNumber; - - public Builder setId(long id) { - mId = id; - return this; - } - - public Builder setPackageName(String packageName) { - mPackageName = packageName; - return this; - } - - public Builder setInputId(String inputId) { - mInputId = inputId; - return this; - } - - public Builder setChannelId(long channelId) { - mChannelId = channelId; - return this; - } - - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - public Builder setSeriesId(String seriesId) { - mSeriesId = seriesId; - return this; - } - - public Builder setSeasonNumber(String seasonNumber) { - mSeasonNumber = seasonNumber; - return this; - } - - public Builder setSeasonTitle(String seasonTitle) { - mSeasonTitle = seasonTitle; - return this; - } - - public Builder setEpisodeNumber(String episodeNumber) { - mEpisodeNumber = episodeNumber; - return this; - } - - public Builder setEpisodeTitle(String episodeTitle) { - mEpisodeTitle = episodeTitle; - return this; - } - - public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { - mStartTimeUtcMillis = startTimeUtcMillis; - return this; - } - - public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { - mEndTimeUtcMillis = endTimeUtcMillis; - return this; - } - - public Builder setBroadcastGenres(String broadcastGenres) { - if (TextUtils.isEmpty(broadcastGenres)) { - mBroadcastGenres = null; - return this; - } - return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres)); - } - - private Builder setBroadcastGenres(String[] broadcastGenres) { - mBroadcastGenres = broadcastGenres; - return this; - } - - public Builder setCanonicalGenres(String canonicalGenres) { - if (TextUtils.isEmpty(canonicalGenres)) { - mCanonicalGenres = null; - return this; - } - return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres)); - } - - private Builder setCanonicalGenres(String[] canonicalGenres) { - mCanonicalGenres = canonicalGenres; - return this; - } - - public Builder setShortDescription(String shortDescription) { - mShortDescription = shortDescription; - return this; - } - - public Builder setLongDescription(String longDescription) { - mLongDescription = longDescription; - return this; - } - - public Builder setVideoWidth(int videoWidth) { - mVideoWidth = videoWidth; - return this; - } - - public Builder setVideoHeight(int videoHeight) { - mVideoHeight = videoHeight; - return this; - } - - public Builder setAudioLanguage(String audioLanguage) { - mAudioLanguage = audioLanguage; - return this; - } - - public Builder setContentRating(String contentRating) { - mContentRating = contentRating; - return this; - } - - private Uri toUri(String uriString) { - try { - return uriString == null ? null : Uri.parse(uriString); - } catch (Exception e) { - return null; - } - } - - public Builder setPosterArtUri(String posterArtUri) { - mPosterArtUri = posterArtUri; - return this; - } - - public Builder setThumbnailUri(String thumbnailUri) { - mThumbnailUri = thumbnailUri; - return this; - } - - public Builder setSearchable(boolean searchable) { - mSearchable = searchable; - return this; - } - - public Builder setDataUri(String dataUri) { - return setDataUri(toUri(dataUri)); - } - - public Builder setDataUri(Uri dataUri) { - mDataUri = dataUri; - return this; - } - - public Builder setDataBytes(long dataBytes) { - mDataBytes = dataBytes; - return this; - } - - public Builder setDurationMillis(long durationMillis) { - mDurationMillis = durationMillis; - return this; - } - - public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) { - mExpireTimeUtcMillis = expireTimeUtcMillis; - return this; - } - - public Builder setInternalProviderFlag1(int internalProviderFlag1) { - mInternalProviderFlag1 = internalProviderFlag1; - return this; - } - - public Builder setInternalProviderFlag2(int internalProviderFlag2) { - mInternalProviderFlag2 = internalProviderFlag2; - return this; - } - - public Builder setInternalProviderFlag3(int internalProviderFlag3) { - mInternalProviderFlag3 = internalProviderFlag3; - return this; - } - - public Builder setInternalProviderFlag4(int internalProviderFlag4) { - mInternalProviderFlag4 = internalProviderFlag4; - return this; - } - - public Builder setVersionNumber(int versionNumber) { - mVersionNumber = versionNumber; - return this; - } - - public RecordedProgram build() { - // Generate the series ID for the episodic program of other TV input. - if (TextUtils.isEmpty(mSeriesId) - && !TextUtils.isEmpty(mEpisodeNumber)) { - setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle)); - } - return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId, - mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis, - mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription, - mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRating, - mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes, - mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1, - mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4, - mVersionNumber); - } - } - - public static Builder builder() { return new Builder(); } - - public static Builder buildFrom(RecordedProgram orig) { - return builder() - .setId(orig.getId()) - .setPackageName(orig.getPackageName()) - .setInputId(orig.getInputId()) - .setChannelId(orig.getChannelId()) - .setTitle(orig.getTitle()) - .setSeriesId(orig.getSeriesId()) - .setSeasonNumber(orig.getSeasonNumber()) - .setSeasonTitle(orig.getSeasonTitle()) - .setEpisodeNumber(orig.getEpisodeNumber()) - .setEpisodeTitle(orig.getEpisodeTitle()) - .setStartTimeUtcMillis(orig.getStartTimeUtcMillis()) - .setEndTimeUtcMillis(orig.getEndTimeUtcMillis()) - .setBroadcastGenres(orig.getBroadcastGenres()) - .setCanonicalGenres(orig.getCanonicalGenres()) - .setShortDescription(orig.getDescription()) - .setLongDescription(orig.getLongDescription()) - .setVideoWidth(orig.getVideoWidth()) - .setVideoHeight(orig.getVideoHeight()) - .setAudioLanguage(orig.getAudioLanguage()) - .setContentRating(orig.getContentRating()) - .setPosterArtUri(orig.getPosterArtUri()) - .setThumbnailUri(orig.getThumbnailUri()) - .setSearchable(orig.isSearchable()) - .setInternalProviderFlag1(orig.getInternalProviderFlag1()) - .setInternalProviderFlag2(orig.getInternalProviderFlag2()) - .setInternalProviderFlag3(orig.getInternalProviderFlag3()) - .setInternalProviderFlag4(orig.getInternalProviderFlag4()) - .setVersionNumber(orig.getVersionNumber()); - } - - public static final Comparator START_TIME_THEN_ID_COMPARATOR = - new Comparator() { - @Override - public int compare(RecordedProgram lhs, RecordedProgram rhs) { - int res = - Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis()); - if (res != 0) { - return res; - } - return Long.compare(lhs.mId, rhs.mId); - } - }; - - private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); - - private final long mId; - private final String mPackageName; - private final String mInputId; - private final long mChannelId; - private final String mTitle; - private final String mSeriesId; - private final String mSeasonNumber; - private final String mSeasonTitle; - private final String mEpisodeNumber; - private final String mEpisodeTitle; - private final long mStartTimeUtcMillis; - private final long mEndTimeUtcMillis; - private final String[] mBroadcastGenres; - private final String[] mCanonicalGenres; - private final String mShortDescription; - private final String mLongDescription; - private final int mVideoWidth; - private final int mVideoHeight; - private final String mAudioLanguage; - private final String mContentRating; - private final String mPosterArtUri; - private final String mThumbnailUri; - private final boolean mSearchable; - private final Uri mDataUri; - private final long mDataBytes; - private final long mDurationMillis; - private final long mExpireTimeUtcMillis; - private final int mInternalProviderFlag1; - private final int mInternalProviderFlag2; - private final int mInternalProviderFlag3; - private final int mInternalProviderFlag4; - private final int mVersionNumber; - - private RecordedProgram(long id, String packageName, String inputId, long channelId, - String title, String seriesId, String seasonNumber, String seasonTitle, - String episodeNumber, String episodeTitle, long startTimeUtcMillis, - long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres, - String shortDescription, String longDescription, int videoWidth, int videoHeight, - String audioLanguage, String contentRating, String posterArtUri, String thumbnailUri, - boolean searchable, Uri dataUri, long dataBytes, long durationMillis, - long expireTimeUtcMillis, int internalProviderFlag1, int internalProviderFlag2, - int internalProviderFlag3, int internalProviderFlag4, int versionNumber) { - mId = id; - mPackageName = packageName; - mInputId = inputId; - mChannelId = channelId; - mTitle = title; - mSeriesId = seriesId; - mSeasonNumber = seasonNumber; - mSeasonTitle = seasonTitle; - mEpisodeNumber = episodeNumber; - mEpisodeTitle = episodeTitle; - mStartTimeUtcMillis = startTimeUtcMillis; - mEndTimeUtcMillis = endTimeUtcMillis; - mBroadcastGenres = broadcastGenres; - mCanonicalGenres = canonicalGenres; - mShortDescription = shortDescription; - mLongDescription = longDescription; - mVideoWidth = videoWidth; - mVideoHeight = videoHeight; - - mAudioLanguage = audioLanguage; - mContentRating = contentRating; - mPosterArtUri = posterArtUri; - mThumbnailUri = thumbnailUri; - mSearchable = searchable; - mDataUri = dataUri; - mDataBytes = dataBytes; - mDurationMillis = durationMillis; - mExpireTimeUtcMillis = expireTimeUtcMillis; - mInternalProviderFlag1 = internalProviderFlag1; - mInternalProviderFlag2 = internalProviderFlag2; - mInternalProviderFlag3 = internalProviderFlag3; - mInternalProviderFlag4 = internalProviderFlag4; - mVersionNumber = versionNumber; - } - - public String getAudioLanguage() { - return mAudioLanguage; - } - - public String[] getBroadcastGenres() { - return mBroadcastGenres; - } - - public String[] getCanonicalGenres() { - return mCanonicalGenres; - } - - /** - * Returns array of canonical genre ID's for this recorded program. - */ - @Override - public int[] getCanonicalGenreIds() { - if (mCanonicalGenres == null) { - return null; - } - int[] genreIds = new int[mCanonicalGenres.length]; - for (int i = 0; i < mCanonicalGenres.length; i++) { - genreIds[i] = GenreItems.getId(mCanonicalGenres[i]); - } - return genreIds; - } - - @Override - public long getChannelId() { - return mChannelId; - } - - public String getContentRating() { - return mContentRating; - } - - public Uri getDataUri() { - return mDataUri; - } - - public long getDataBytes() { - return mDataBytes; - } - - @Override - public long getDurationMillis() { - return mDurationMillis; - } - - @Override - public long getEndTimeUtcMillis() { - return mEndTimeUtcMillis; - } - - @Override - public String getEpisodeNumber() { - return mEpisodeNumber; - } - - public String getEpisodeTitle() { - return mEpisodeTitle; - } - - @Override - public String getEpisodeDisplayTitle(Context context) { - if (!TextUtils.isEmpty(mEpisodeNumber)) { - String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; - if (TextUtils.equals(mSeasonNumber, "0")) { - // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_title_format_no_season_number), - mEpisodeNumber, episodeTitle); - } else { - return String.format(context.getResources().getString( - R.string.display_episode_title_format), - mSeasonNumber, mEpisodeNumber, episodeTitle); - } - } - return mEpisodeTitle; - } - - @Nullable - @Override - public String getTitleWithEpisodeNumber(Context context) { - if (TextUtils.isEmpty(mTitle)) { - return mTitle; - } - if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) { - return TextUtils.isEmpty(mEpisodeNumber) ? mTitle : context.getString( - R.string.program_title_with_episode_number_no_season, mTitle, mEpisodeNumber); - } else { - return context.getString(R.string.program_title_with_episode_number, mTitle, - mSeasonNumber, mEpisodeNumber); - } - } - - @Nullable - public String getEpisodeDisplayNumber(Context context) { - if (!TextUtils.isEmpty(mEpisodeNumber)) { - if (TextUtils.equals(mSeasonNumber, "0")) { - // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_number_format_no_season_number), mEpisodeNumber); - } else { - return String.format(context.getResources().getString( - R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber); - } - } - return null; - } - - public long getExpireTimeUtcMillis() { - return mExpireTimeUtcMillis; - } - - public long getId() { - return mId; - } - - public String getPackageName() { - return mPackageName; - } - - public String getInputId() { - return mInputId; - } - - public int getInternalProviderFlag1() { - return mInternalProviderFlag1; - } - - public int getInternalProviderFlag2() { - return mInternalProviderFlag2; - } - - public int getInternalProviderFlag3() { - return mInternalProviderFlag3; - } - - public int getInternalProviderFlag4() { - return mInternalProviderFlag4; - } - - @Override - public String getDescription() { - return mShortDescription; - } - - @Override - public String getLongDescription() { - return mLongDescription; - } - - @Override - public String getPosterArtUri() { - return mPosterArtUri; - } - - @Override - public boolean isValid() { - return true; - } - - public boolean isSearchable() { - return mSearchable; - } - - @Override - public String getSeriesId() { - return mSeriesId; - } - - @Override - public String getSeasonNumber() { - return mSeasonNumber; - } - - public String getSeasonTitle() { - return mSeasonTitle; - } - - @Override - public long getStartTimeUtcMillis() { - return mStartTimeUtcMillis; - } - - @Override - public String getThumbnailUri() { - return mThumbnailUri; - } - - @Override - public String getTitle() { - return mTitle; - } - - public Uri getUri() { - return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId); - } - - public int getVersionNumber() { - return mVersionNumber; - } - - public int getVideoHeight() { - return mVideoHeight; - } - - public int getVideoWidth() { - return mVideoWidth; - } - - /** - * Checks whether the recording has been clipped or not. - */ - public boolean isClipped() { - return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RecordedProgram that = (RecordedProgram) o; - return Objects.equals(mId, that.mId) && - Objects.equals(mChannelId, that.mChannelId) && - Objects.equals(mSeriesId, that.mSeriesId) && - Objects.equals(mSeasonNumber, that.mSeasonNumber) && - Objects.equals(mSeasonTitle, that.mSeasonTitle) && - Objects.equals(mEpisodeNumber, that.mEpisodeNumber) && - Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) && - Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) && - Objects.equals(mVideoWidth, that.mVideoWidth) && - Objects.equals(mVideoHeight, that.mVideoHeight) && - Objects.equals(mSearchable, that.mSearchable) && - Objects.equals(mDataBytes, that.mDataBytes) && - Objects.equals(mDurationMillis, that.mDurationMillis) && - Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) && - Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) && - Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) && - Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) && - Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) && - Objects.equals(mVersionNumber, that.mVersionNumber) && - Objects.equals(mTitle, that.mTitle) && - Objects.equals(mEpisodeTitle, that.mEpisodeTitle) && - Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) && - Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) && - Objects.equals(mShortDescription, that.mShortDescription) && - Objects.equals(mLongDescription, that.mLongDescription) && - Objects.equals(mAudioLanguage, that.mAudioLanguage) && - Objects.equals(mContentRating, that.mContentRating) && - Objects.equals(mPosterArtUri, that.mPosterArtUri) && - Objects.equals(mThumbnailUri, that.mThumbnailUri); - } - - /** - * Hashes based on the ID. - */ - @Override - public int hashCode() { - return Objects.hash(mId); - } - - @Override - public String toString() { - return "RecordedProgram" - + "[" + mId + - "]{ mPackageName=" + mPackageName + - ", mInputId='" + mInputId + '\'' + - ", mChannelId='" + mChannelId + '\'' + - ", mTitle='" + mTitle + '\'' + - ", mSeriesId='" + mSeriesId + '\'' + - ", mEpisodeNumber=" + mEpisodeNumber + - ", mEpisodeTitle='" + mEpisodeTitle + '\'' + - ", mStartTimeUtcMillis=" + mStartTimeUtcMillis + - ", mEndTimeUtcMillis=" + mEndTimeUtcMillis + - ", mBroadcastGenres=" + - (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") + - ", mCanonicalGenres=" + - (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") + - ", mShortDescription='" + mShortDescription + '\'' + - ", mLongDescription='" + mLongDescription + '\'' + - ", mVideoHeight=" + mVideoHeight + - ", mVideoWidth=" + mVideoWidth + - ", mAudioLanguage='" + mAudioLanguage + '\'' + - ", mContentRating='" + mContentRating + '\'' + - ", mPosterArtUri=" + mPosterArtUri + - ", mThumbnailUri=" + mThumbnailUri + - ", mSearchable=" + mSearchable + - ", mDataUri=" + mDataUri + - ", mDataBytes=" + mDataBytes + - ", mDurationMillis=" + mDurationMillis + - ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis + - ", mInternalProviderFlag1=" + mInternalProviderFlag1 + - ", mInternalProviderFlag2=" + mInternalProviderFlag2 + - ", mInternalProviderFlag3=" + mInternalProviderFlag3 + - ", mInternalProviderFlag4=" + mInternalProviderFlag4 + - ", mSeasonNumber=" + mSeasonNumber + - ", mSeasonTitle=" + mSeasonTitle + - ", mVersionNumber=" + mVersionNumber + - '}'; - } - - @Nullable - private static String safeToString(@Nullable Object o) { - return o == null ? null : o.toString(); - } - - @Nullable - private static String safeEncode(@Nullable String[] genres) { - return genres == null ? null : TvContract.Programs.Genres.encode(genres); - } - - /** - * Returns an array containing all of the elements in the list. - */ - public static RecordedProgram[] toArray(Collection recordedPrograms) { - return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]); - } -} diff --git a/src/com/android/tv/dvr/RecordingTask.java b/src/com/android/tv/dvr/RecordingTask.java deleted file mode 100644 index c3d236b0..00000000 --- a/src/com/android/tv/dvr/RecordingTask.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.annotation.TargetApi; -import android.content.Context; -import android.media.tv.TvContract; -import android.media.tv.TvInputManager; -import android.media.tv.TvRecordingClient.RecordingCallback; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.VisibleForTesting; -import android.support.annotation.WorkerThread; -import android.util.Log; -import android.widget.Toast; - -import com.android.tv.InputSessionManager; -import com.android.tv.InputSessionManager.RecordingSession; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; -import com.android.tv.dvr.InputTaskScheduler.HandlerWrapper; -import com.android.tv.util.Clock; -import com.android.tv.util.Utils; - -import java.util.Comparator; -import java.util.concurrent.TimeUnit; - -/** - * A Handler that actually starts and stop a recording at the right time. - * - *

This is run on the looper of thread named {@value DvrRecordingService#HANDLER_THREAD_NAME}. - * There is only one looper so messages must be handled quickly or start a separate thread. - */ -@WorkerThread -@VisibleForTesting -@TargetApi(Build.VERSION_CODES.N) -public class RecordingTask extends RecordingCallback implements Handler.Callback, - DvrManager.Listener { - private static final String TAG = "RecordingTask"; - private static final boolean DEBUG = false; - - /** - * Compares the end time in ascending order. - */ - public static final Comparator END_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs()); - } - }; - - /** - * Compares ID in ascending order. - */ - public static final Comparator ID_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getScheduleId(), rhs.getScheduleId()); - } - }; - - /** - * Compares the priority in ascending order. - */ - public static final Comparator PRIORITY_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getPriority(), rhs.getPriority()); - } - }; - - @VisibleForTesting - static final int MSG_INITIALIZE = 1; - @VisibleForTesting - static final int MSG_START_RECORDING = 2; - @VisibleForTesting - static final int MSG_STOP_RECORDING = 3; - /** - * Message to update schedule. - */ - public static final int MSG_UDPATE_SCHEDULE = 4; - - /** - * The time when the start command will be sent before the recording starts. - */ - public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3); - /** - * If the recording starts later than the scheduled start time or ends before the scheduled end - * time, it's considered as clipped. - */ - private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); - - @VisibleForTesting - enum State { - NOT_STARTED, - SESSION_ACQUIRED, - CONNECTION_PENDING, - CONNECTED, - RECORDING_STARTED, - RECORDING_STOP_REQUESTED, - FINISHED, - ERROR, - RELEASED, - } - private final InputSessionManager mSessionManager; - private final DvrManager mDvrManager; - private final Context mContext; - - private final WritableDvrDataManager mDataManager; - private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - private RecordingSession mRecordingSession; - private Handler mHandler; - private ScheduledRecording mScheduledRecording; - private final Channel mChannel; - private State mState = State.NOT_STARTED; - private final Clock mClock; - private boolean mStartedWithClipping; - private Uri mRecordedProgramUri; - private boolean mCanceled; - - RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock) { - mContext = context; - mScheduledRecording = scheduledRecording; - mChannel = channel; - mSessionManager = sessionManager; - mDataManager = dataManager; - mClock = clock; - mDvrManager = dvrManager; - - if (DEBUG) Log.d(TAG, "created recording task " + mScheduledRecording); - } - - public void setHandler(Handler handler) { - mHandler = handler; - } - - @Override - public boolean handleMessage(Message msg) { - if (DEBUG) Log.d(TAG, "handleMessage " + msg); - SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null, - TAG, "Null handler trying to handle " + msg); - try { - switch (msg.what) { - case MSG_INITIALIZE: - handleInit(); - break; - case MSG_START_RECORDING: - handleStartRecording(); - break; - case MSG_STOP_RECORDING: - handleStopRecording(); - break; - case MSG_UDPATE_SCHEDULE: - handleUpdateSchedule((ScheduledRecording) msg.obj); - break; - case HandlerWrapper.MESSAGE_REMOVE: - mHandler.removeCallbacksAndMessages(null); - mHandler = null; - release(); - return false; - default: - SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg); - break; - } - return true; - } catch (Exception e) { - Log.w(TAG, "Error processing message " + msg + " for " + mScheduledRecording, e); - failAndQuit(); - } - return false; - } - - @Override - public void onDisconnected(String inputId) { - if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")"); - if (mRecordingSession != null && mState != State.FINISHED) { - failAndQuit(); - } - } - - @Override - public void onConnectionFailed(String inputId) { - if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")"); - if (mRecordingSession != null) { - failAndQuit(); - } - } - - @Override - public void onTuned(Uri channelUri) { - if (DEBUG) Log.d(TAG, "onTuned"); - if (mRecordingSession == null) { - return; - } - mState = State.CONNECTED; - if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING, - mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) { - failAndQuit(); - } - } - - @Override - public void onRecordingStopped(Uri recordedProgramUri) { - if (DEBUG) Log.d(TAG, "onRecordingStopped"); - if (mRecordingSession == null) { - return; - } - mRecordedProgramUri = recordedProgramUri; - mState = State.FINISHED; - int state = ScheduledRecording.STATE_RECORDING_FINISHED; - if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS - > mClock.currentTimeMillis()) { - state = ScheduledRecording.STATE_RECORDING_CLIPPED; - } - updateRecordingState(state); - sendRemove(); - if (mCanceled) { - removeRecordedProgram(); - } - } - - @Override - public void onError(int reason) { - if (DEBUG) Log.d(TAG, "onError reason " + reason); - if (mRecordingSession == null) { - return; - } - switch (reason) { - case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE: - mMainThreadHandler.post(new Runnable() { - @Override - public void run() { - if (TvApplication.getSingletons(mContext).getMainActivityWrapper() - .isResumed()) { - Toast.makeText(mContext.getApplicationContext(), - R.string.dvr_error_insufficient_space_description, - Toast.LENGTH_LONG) - .show(); - } else { - Utils.setRecordingFailedReason(mContext.getApplicationContext(), - TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - } - } - }); - // Pass through - default: - failAndQuit(); - break; - } - } - - private void handleInit() { - if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording); - if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) { - Log.w(TAG, "End time already past, not recording " + mScheduledRecording); - failAndQuit(); - return; - } - if (mChannel == null) { - Log.w(TAG, "Null channel for " + mScheduledRecording); - failAndQuit(); - return; - } - if (mChannel.getId() != mScheduledRecording.getChannelId()) { - Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording " - + mScheduledRecording); - failAndQuit(); - return; - } - - String inputId = mChannel.getInputId(); - mRecordingSession = mSessionManager.createRecordingSession(inputId, - "recordingTask-" + mScheduledRecording.getId(), this, - mHandler, mScheduledRecording.getEndTimeMs()); - mState = State.SESSION_ACQUIRED; - mDvrManager.addListener(this, mHandler); - mRecordingSession.tune(inputId, mChannel.getUri()); - mState = State.CONNECTION_PENDING; - } - - private void failAndQuit() { - if (DEBUG) Log.d(TAG, "failAndQuit"); - updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); - mState = State.ERROR; - sendRemove(); - } - - private void sendRemove() { - if (DEBUG) Log.d(TAG, "sendRemove"); - if (mHandler != null) { - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage( - HandlerWrapper.MESSAGE_REMOVE)); - } - } - - private void handleStartRecording() { - if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording); - long programId = mScheduledRecording.getProgramId(); - mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null - : TvContract.buildProgramUri(programId)); - updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS); - // If it starts late, it's clipped. - if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS - < mClock.currentTimeMillis()) { - mStartedWithClipping = true; - } - mState = State.RECORDING_STARTED; - - if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, - mScheduledRecording.getEndTimeMs())) { - failAndQuit(); - } - } - - private void handleStopRecording() { - if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording); - mRecordingSession.stopRecording(); - mState = State.RECORDING_STOP_REQUESTED; - } - - private void handleUpdateSchedule(ScheduledRecording schedule) { - mScheduledRecording = schedule; - // Check end time only. The start time is checked in InputTaskScheduler. - if (schedule.getEndTimeMs() != mScheduledRecording.getEndTimeMs()) { - if (mRecordingSession != null) { - mRecordingSession.setEndTimeMs(schedule.getEndTimeMs()); - } - if (mState == State.RECORDING_STARTED) { - mHandler.removeMessages(MSG_STOP_RECORDING); - if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) { - failAndQuit(); - } - } - } - } - - @VisibleForTesting - State getState() { - return mState; - } - - private long getScheduleId() { - return mScheduledRecording.getId(); - } - - /** - * Returns the priority. - */ - public long getPriority() { - return mScheduledRecording.getPriority(); - } - - /** - * Returns the start time of the recording. - */ - public long getStartTimeMs() { - return mScheduledRecording.getStartTimeMs(); - } - - /** - * Returns the end time of the recording. - */ - public long getEndTimeMs() { - return mScheduledRecording.getEndTimeMs(); - } - - private void release() { - if (mRecordingSession != null) { - mSessionManager.releaseRecordingSession(mRecordingSession); - mRecordingSession = null; - } - mDvrManager.removeListener(this); - } - - private boolean sendEmptyMessageAtAbsoluteTime(int what, long when) { - long now = mClock.currentTimeMillis(); - long delay = Math.max(0L, when - now); - if (DEBUG) { - Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000 - + " seconds to arrive at " + Utils.toIsoDateTimeString(when)); - } - return mHandler.sendEmptyMessageDelayed(what, delay); - } - - private void updateRecordingState(@ScheduledRecording.RecordingState int state) { - if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state); - mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state) - .build(); - runOnMainThread(new Runnable() { - @Override - public void run() { - ScheduledRecording schedule = mDataManager.getScheduledRecording( - mScheduledRecording.getId()); - if (schedule == null) { - // Schedule has been deleted. Delete the recorded program. - removeRecordedProgram(); - } else { - // Update the state based on the object in DataManager in case when it has been - // updated. mScheduledRecording will be updated from - // onScheduledRecordingStateChanged. - mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule) - .setState(state).build()); - } - } - }); - } - - @Override - public void onStopRecordingRequested(ScheduledRecording recording) { - if (recording.getId() != mScheduledRecording.getId()) { - return; - } - stop(); - } - - /** - * Starts the task. - */ - public void start() { - mHandler.sendEmptyMessage(MSG_INITIALIZE); - } - - /** - * Stops the task. - */ - public void stop() { - if (DEBUG) Log.d(TAG, "stop"); - switch (mState) { - case RECORDING_STARTED: - mHandler.removeMessages(MSG_STOP_RECORDING); - handleStopRecording(); - break; - case RECORDING_STOP_REQUESTED: - // Do nothing - break; - case NOT_STARTED: - case SESSION_ACQUIRED: - case CONNECTION_PENDING: - case CONNECTED: - case FINISHED: - case ERROR: - case RELEASED: - default: - sendRemove(); - break; - } - } - - /** - * Cancels the task - */ - public void cancel() { - if (DEBUG) Log.d(TAG, "cancel"); - mCanceled = true; - stop(); - removeRecordedProgram(); - } - - /** - * Clean up the task. - */ - public void cleanUp() { - if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) { - updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); - } - release(); - if (mHandler != null) { - mHandler.removeCallbacksAndMessages(null); - } - } - - @Override - public String toString() { - return getClass().getName() + "(" + mScheduledRecording + ")"; - } - - private void removeRecordedProgram() { - runOnMainThread(new Runnable() { - @Override - public void run() { - if (mRecordedProgramUri != null) { - mDvrManager.removeRecordedProgram(mRecordedProgramUri); - } - } - }); - } - - private void runOnMainThread(Runnable runnable) { - if (Looper.myLooper() == Looper.getMainLooper()) { - runnable.run(); - } else { - mMainThreadHandler.post(runnable); - } - } -} diff --git a/src/com/android/tv/dvr/ScheduledProgramReaper.java b/src/com/android/tv/dvr/ScheduledProgramReaper.java deleted file mode 100644 index cd79a631..00000000 --- a/src/com/android/tv/dvr/ScheduledProgramReaper.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; - -import com.android.tv.util.Clock; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Deletes {@link ScheduledRecording} older than {@value @DAYS} days. - */ -class ScheduledProgramReaper implements Runnable { - - @VisibleForTesting - static final int DAYS = 2; - private final WritableDvrDataManager mDvrDataManager; - private final Clock mClock; - - ScheduledProgramReaper(WritableDvrDataManager dvrDataManager, Clock clock) { - mDvrDataManager = dvrDataManager; - mClock = clock; - } - - @Override - @MainThread - public void run() { - long cutoff = mClock.currentTimeMillis() - TimeUnit.DAYS.toMillis(DAYS); - List toRemove = new ArrayList<>(); - for (ScheduledRecording r : mDvrDataManager.getAllScheduledRecordings()) { - // Do not remove the schedules if it belongs to the series recording and was finished - // successfully. The schedule is necessary for checking the scheduled episode of the - // series recording. - if (r.getEndTimeMs() < cutoff - && (r.getSeriesRecordingId() == SeriesRecording.ID_NOT_SET - || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) { - toRemove.add(r); - } - } - for (ScheduledRecording r : mDvrDataManager.getDeletedSchedules()) { - if (r.getEndTimeMs() < cutoff) { - toRemove.add(r); - } - } - if (!toRemove.isEmpty()) { - mDvrDataManager.removeScheduledRecording(ScheduledRecording.toArray(toRemove)); - } - } -} diff --git a/src/com/android/tv/dvr/ScheduledRecording.java b/src/com/android/tv/dvr/ScheduledRecording.java deleted file mode 100644 index 2bda10ea..00000000 --- a/src/com/android/tv/dvr/ScheduledRecording.java +++ /dev/null @@ -1,887 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.IntDef; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.Range; - -import com.android.tv.R; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; -import com.android.tv.data.Program; -import com.android.tv.dvr.provider.DvrContract.Schedules; -import com.android.tv.util.CompositeComparator; -import com.android.tv.util.Utils; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collection; -import java.util.Comparator; -import java.util.Objects; - -/** - * A data class for one recording contents. - */ -@VisibleForTesting -public final class ScheduledRecording implements Parcelable { - private static final String TAG = "ScheduledRecording"; - - /** - * Indicates that the ID is not assigned yet. - */ - public static final long ID_NOT_SET = 0; - - /** - * The default priority of the recording. - */ - public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; - - /** - * Compares the start time in ascending order. - */ - public static final Comparator START_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs); - } - }; - - /** - * Compares the end time in ascending order. - */ - public static final Comparator END_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs); - } - }; - - /** - * Compares ID in ascending order. The schedule with the larger ID was created later. - */ - public static final Comparator ID_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mId, rhs.mId); - } - }; - - /** - * Compares the priority in ascending order. - */ - public static final Comparator PRIORITY_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mPriority, rhs.mPriority); - } - }; - - /** - * Compares start time in ascending order and then priority in descending order and then ID in - * descending order. - */ - public static final Comparator START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR - = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(), - ID_COMPARATOR.reversed()); - - /** - * Builds scheduled recordings from programs. - */ - public static Builder builder(String inputId, Program p) { - return new Builder() - .setInputId(inputId) - .setChannelId(p.getChannelId()) - .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis()) - .setProgramId(p.getId()) - .setProgramTitle(p.getTitle()) - .setSeasonNumber(p.getSeasonNumber()) - .setEpisodeNumber(p.getEpisodeNumber()) - .setEpisodeTitle(p.getEpisodeTitle()) - .setProgramDescription(p.getDescription()) - .setProgramLongDescription(p.getLongDescription()) - .setProgramPosterArtUri(p.getPosterArtUri()) - .setProgramThumbnailUri(p.getThumbnailUri()) - .setType(TYPE_PROGRAM); - } - - public static Builder builder(String inputId, long channelId, long startTime, long endTime) { - return new Builder() - .setInputId(inputId) - .setChannelId(channelId) - .setStartTimeMs(startTime) - .setEndTimeMs(endTime) - .setType(TYPE_TIMED); - } - - /** - * Creates a new Builder with the values set from the {@link RecordedProgram}. - */ - @VisibleForTesting - public static Builder builder(RecordedProgram p) { - boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle()); - return new Builder() - .setInputId(p.getInputId()) - .setChannelId(p.getChannelId()) - .setType(isProgramRecording ? TYPE_PROGRAM : TYPE_TIMED) - .setStartTimeMs(p.getStartTimeUtcMillis()) - .setEndTimeMs(p.getEndTimeUtcMillis()) - .setProgramTitle(p.getTitle()) - .setSeasonNumber(p.getSeasonNumber()) - .setEpisodeNumber(p.getEpisodeNumber()) - .setEpisodeTitle(p.getEpisodeTitle()) - .setProgramDescription(p.getDescription()) - .setProgramLongDescription(p.getLongDescription()) - .setProgramPosterArtUri(p.getPosterArtUri()) - .setProgramThumbnailUri(p.getThumbnailUri()) - .setState(STATE_RECORDING_FINISHED); - } - - public static final class Builder { - private long mId = ID_NOT_SET; - private long mPriority = DvrScheduleManager.DEFAULT_PRIORITY; - private String mInputId; - private long mChannelId; - private long mProgramId = ID_NOT_SET; - private String mProgramTitle; - private @RecordingType int mType; - private long mStartTimeMs; - private long mEndTimeMs; - private String mSeasonNumber; - private String mEpisodeNumber; - private String mEpisodeTitle; - private String mProgramDescription; - private String mProgramLongDescription; - private String mProgramPosterArtUri; - private String mProgramThumbnailUri; - private @RecordingState int mState; - private long mSeriesRecordingId = ID_NOT_SET; - - private Builder() { } - - public Builder setId(long id) { - mId = id; - return this; - } - - public Builder setPriority(long priority) { - mPriority = priority; - return this; - } - - public Builder setInputId(String inputId) { - mInputId = inputId; - return this; - } - - public Builder setChannelId(long channelId) { - mChannelId = channelId; - return this; - } - - public Builder setProgramId(long programId) { - mProgramId = programId; - return this; - } - - public Builder setProgramTitle(String programTitle) { - mProgramTitle = programTitle; - return this; - } - - private Builder setType(@RecordingType int type) { - mType = type; - return this; - } - - public Builder setStartTimeMs(long startTimeMs) { - mStartTimeMs = startTimeMs; - return this; - } - - public Builder setEndTimeMs(long endTimeMs) { - mEndTimeMs = endTimeMs; - return this; - } - - public Builder setSeasonNumber(String seasonNumber) { - mSeasonNumber = seasonNumber; - return this; - } - - public Builder setEpisodeNumber(String episodeNumber) { - mEpisodeNumber = episodeNumber; - return this; - } - - public Builder setEpisodeTitle(String episodeTitle) { - mEpisodeTitle = episodeTitle; - return this; - } - - public Builder setProgramDescription(String description) { - mProgramDescription = description; - return this; - } - - public Builder setProgramLongDescription(String longDescription) { - mProgramLongDescription = longDescription; - return this; - } - - public Builder setProgramPosterArtUri(String programPosterArtUri) { - mProgramPosterArtUri = programPosterArtUri; - return this; - } - - public Builder setProgramThumbnailUri(String programThumbnailUri) { - mProgramThumbnailUri = programThumbnailUri; - return this; - } - - public Builder setState(@RecordingState int state) { - mState = state; - return this; - } - - public Builder setSeriesRecordingId(long seriesRecordingId) { - mSeriesRecordingId = seriesRecordingId; - return this; - } - - public ScheduledRecording build() { - return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId, - mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, - mEpisodeTitle, mProgramDescription, mProgramLongDescription, - mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId); - } - } - - /** - * Creates {@link Builder} object from the given original {@code Recording}. - */ - public static Builder buildFrom(ScheduledRecording orig) { - return new Builder() - .setId(orig.mId) - .setInputId(orig.mInputId) - .setChannelId(orig.mChannelId) - .setEndTimeMs(orig.mEndTimeMs) - .setSeriesRecordingId(orig.mSeriesRecordingId) - .setPriority(orig.mPriority) - .setProgramId(orig.mProgramId) - .setProgramTitle(orig.mProgramTitle) - .setStartTimeMs(orig.mStartTimeMs) - .setSeasonNumber(orig.getSeasonNumber()) - .setEpisodeNumber(orig.getEpisodeNumber()) - .setEpisodeTitle(orig.getEpisodeTitle()) - .setProgramDescription(orig.getProgramDescription()) - .setProgramLongDescription(orig.getProgramLongDescription()) - .setProgramPosterArtUri(orig.getProgramPosterArtUri()) - .setProgramThumbnailUri(orig.getProgramThumbnailUri()) - .setState(orig.mState).setType(orig.mType); - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED, - STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED, - STATE_RECORDING_CANCELED}) - public @interface RecordingState {} - public static final int STATE_RECORDING_NOT_STARTED = 0; - public static final int STATE_RECORDING_IN_PROGRESS = 1; - public static final int STATE_RECORDING_FINISHED = 2; - public static final int STATE_RECORDING_FAILED = 3; - public static final int STATE_RECORDING_CLIPPED = 4; - public static final int STATE_RECORDING_DELETED = 5; - public static final int STATE_RECORDING_CANCELED = 6; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({TYPE_TIMED, TYPE_PROGRAM}) - public @interface RecordingType {} - /** - * Record with given time range. - */ - public static final int TYPE_TIMED = 1; - /** - * Record with a given program. - */ - public static final int TYPE_PROGRAM = 2; - - @RecordingType private final int mType; - - /** - * Use this projection if you want to create {@link ScheduledRecording} object using - * {@link #fromCursor}. - */ - public static final String[] PROJECTION = { - // Columns must match what is read in #fromCursor - Schedules._ID, - Schedules.COLUMN_PRIORITY, - Schedules.COLUMN_TYPE, - Schedules.COLUMN_INPUT_ID, - Schedules.COLUMN_CHANNEL_ID, - Schedules.COLUMN_PROGRAM_ID, - Schedules.COLUMN_PROGRAM_TITLE, - Schedules.COLUMN_START_TIME_UTC_MILLIS, - Schedules.COLUMN_END_TIME_UTC_MILLIS, - Schedules.COLUMN_SEASON_NUMBER, - Schedules.COLUMN_EPISODE_NUMBER, - Schedules.COLUMN_EPISODE_TITLE, - Schedules.COLUMN_PROGRAM_DESCRIPTION, - Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, - Schedules.COLUMN_PROGRAM_POST_ART_URI, - Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, - Schedules.COLUMN_STATE, - Schedules.COLUMN_SERIES_RECORDING_ID}; - - /** - * Creates {@link ScheduledRecording} object from the given {@link Cursor}. - */ - public static ScheduledRecording fromCursor(Cursor c) { - int index = -1; - return new Builder() - .setId(c.getLong(++index)) - .setPriority(c.getLong(++index)) - .setType(recordingType(c.getString(++index))) - .setInputId(c.getString(++index)) - .setChannelId(c.getLong(++index)) - .setProgramId(c.getLong(++index)) - .setProgramTitle(c.getString(++index)) - .setStartTimeMs(c.getLong(++index)) - .setEndTimeMs(c.getLong(++index)) - .setSeasonNumber(c.getString(++index)) - .setEpisodeNumber(c.getString(++index)) - .setEpisodeTitle(c.getString(++index)) - .setProgramDescription(c.getString(++index)) - .setProgramLongDescription(c.getString(++index)) - .setProgramPosterArtUri(c.getString(++index)) - .setProgramThumbnailUri(c.getString(++index)) - .setState(recordingState(c.getString(++index))) - .setSeriesRecordingId(c.getLong(++index)) - .build(); - } - - public static ContentValues toContentValues(ScheduledRecording r) { - ContentValues values = new ContentValues(); - if (r.getId() != ID_NOT_SET) { - values.put(Schedules._ID, r.getId()); - } - values.put(Schedules.COLUMN_INPUT_ID, r.getInputId()); - values.put(Schedules.COLUMN_CHANNEL_ID, r.getChannelId()); - values.put(Schedules.COLUMN_PROGRAM_ID, r.getProgramId()); - values.put(Schedules.COLUMN_PROGRAM_TITLE, r.getProgramTitle()); - values.put(Schedules.COLUMN_PRIORITY, r.getPriority()); - values.put(Schedules.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs()); - values.put(Schedules.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs()); - values.put(Schedules.COLUMN_SEASON_NUMBER, r.getSeasonNumber()); - values.put(Schedules.COLUMN_EPISODE_NUMBER, r.getEpisodeNumber()); - values.put(Schedules.COLUMN_EPISODE_TITLE, r.getEpisodeTitle()); - values.put(Schedules.COLUMN_PROGRAM_DESCRIPTION, r.getProgramDescription()); - values.put(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, r.getProgramLongDescription()); - values.put(Schedules.COLUMN_PROGRAM_POST_ART_URI, r.getProgramPosterArtUri()); - values.put(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, r.getProgramThumbnailUri()); - values.put(Schedules.COLUMN_STATE, recordingState(r.getState())); - values.put(Schedules.COLUMN_TYPE, recordingType(r.getType())); - if (r.getSeriesRecordingId() != ID_NOT_SET) { - values.put(Schedules.COLUMN_SERIES_RECORDING_ID, r.getSeriesRecordingId()); - } else { - values.putNull(Schedules.COLUMN_SERIES_RECORDING_ID); - } - return values; - } - - public static ScheduledRecording fromParcel(Parcel in) { - return new Builder() - .setId(in.readLong()) - .setPriority(in.readLong()) - .setInputId(in.readString()) - .setChannelId(in.readLong()) - .setProgramId(in.readLong()) - .setProgramTitle(in.readString()) - .setType(in.readInt()) - .setStartTimeMs(in.readLong()) - .setEndTimeMs(in.readLong()) - .setSeasonNumber(in.readString()) - .setEpisodeNumber(in.readString()) - .setEpisodeTitle(in.readString()) - .setProgramDescription(in.readString()) - .setProgramLongDescription(in.readString()) - .setProgramPosterArtUri(in.readString()) - .setProgramThumbnailUri(in.readString()) - .setState(in.readInt()) - .setSeriesRecordingId(in.readLong()) - .build(); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public ScheduledRecording createFromParcel(Parcel in) { - return ScheduledRecording.fromParcel(in); - } - - @Override - public ScheduledRecording[] newArray(int size) { - return new ScheduledRecording[size]; - } - }; - - /** - * The ID internal to Live TV - */ - private long mId; - - /** - * The priority of this recording. - * - *

The highest number is recorded first. If there is a tie in priority then the higher id - * wins. - */ - private final long mPriority; - - private final String mInputId; - private final long mChannelId; - /** - * Optional id of the associated program. - */ - private final long mProgramId; - private final String mProgramTitle; - - private final long mStartTimeMs; - private final long mEndTimeMs; - private final String mSeasonNumber; - private final String mEpisodeNumber; - private final String mEpisodeTitle; - private final String mProgramDescription; - private final String mProgramLongDescription; - private final String mProgramPosterArtUri; - private final String mProgramThumbnailUri; - @RecordingState private final int mState; - private final long mSeriesRecordingId; - - private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId, - String programTitle, @RecordingType int type, long startTime, long endTime, - String seasonNumber, String episodeNumber, String episodeTitle, - String programDescription, String programLongDescription, String programPosterArtUri, - String programThumbnailUri, @RecordingState int state, long seriesRecordingId) { - mId = id; - mPriority = priority; - mInputId = inputId; - mChannelId = channelId; - mProgramId = programId; - mProgramTitle = programTitle; - mType = type; - mStartTimeMs = startTime; - mEndTimeMs = endTime; - mSeasonNumber = seasonNumber; - mEpisodeNumber = episodeNumber; - mEpisodeTitle = episodeTitle; - mProgramDescription = programDescription; - mProgramLongDescription = programLongDescription; - mProgramPosterArtUri = programPosterArtUri; - mProgramThumbnailUri = programThumbnailUri; - mState = state; - mSeriesRecordingId = seriesRecordingId; - } - - /** - * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and - * {@link #TYPE_TIMED}. - */ - @RecordingType - public int getType() { - return mType; - } - - /** - * Returns schedules' input id. - */ - public String getInputId() { - return mInputId; - } - - /** - * Returns recorded {@link Channel}. - */ - public long getChannelId() { - return mChannelId; - } - - /** - * Return the optional program id - */ - public long getProgramId() { - return mProgramId; - } - - /** - * Return the optional program Title - */ - public String getProgramTitle() { - return mProgramTitle; - } - - /** - * Returns started time. - */ - public long getStartTimeMs() { - return mStartTimeMs; - } - - /** - * Returns ended time. - */ - public long getEndTimeMs() { - return mEndTimeMs; - } - - /** - * Returns the season number. - */ - public String getSeasonNumber() { - return mSeasonNumber; - } - - /** - * Returns the episode number. - */ - public String getEpisodeNumber() { - return mEpisodeNumber; - } - - /** - * Returns the episode title. - */ - public String getEpisodeTitle() { - return mEpisodeTitle; - } - - /** - * Returns the description of program. - */ - public String getProgramDescription() { - return mProgramDescription; - } - - /** - * Returns the long description of program. - */ - public String getProgramLongDescription() { - return mProgramLongDescription; - } - - /** - * Returns the poster uri of program. - */ - public String getProgramPosterArtUri() { - return mProgramPosterArtUri; - } - - /** - * Returns the thumb nail uri of program. - */ - public String getProgramThumbnailUri() { - return mProgramThumbnailUri; - } - - /** - * Returns duration. - */ - public long getDuration() { - return mEndTimeMs - mStartTimeMs; - } - - /** - * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED}, - * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, - * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and - * {@link #STATE_RECORDING_DELETED}. - */ - @RecordingState public int getState() { - return mState; - } - - /** - * Returns the ID of the {@link SeriesRecording} including this schedule. - */ - public long getSeriesRecordingId() { - return mSeriesRecordingId; - } - - public long getId() { - return mId; - } - - /** - * Sets the ID; - */ - public void setId(long id) { - mId = id; - } - - public long getPriority() { - return mPriority; - } - - /** - * Returns season number, episode number and episode title for display. - */ - public String getEpisodeDisplayTitle(Context context) { - if (!TextUtils.isEmpty(mEpisodeNumber)) { - String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; - if (TextUtils.equals(mSeasonNumber, "0")) { - // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_title_format_no_season_number), - mEpisodeNumber, episodeTitle); - } else { - return String.format(context.getResources().getString( - R.string.display_episode_title_format), - mSeasonNumber, mEpisodeNumber, episodeTitle); - } - } - return mEpisodeTitle; - } - - /** - * Returns the program's title withe its season and episode number. - */ - public String getProgramTitleWithEpisodeNumber(Context context) { - if (TextUtils.isEmpty(mProgramTitle)) { - return mProgramTitle; - } - if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) { - return TextUtils.isEmpty(mEpisodeNumber) ? mProgramTitle : context.getString( - R.string.program_title_with_episode_number_no_season, mProgramTitle, - mEpisodeNumber); - } else { - return context.getString(R.string.program_title_with_episode_number, mProgramTitle, - mSeasonNumber, mEpisodeNumber); - } - } - - - /** - * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. - */ - private static @RecordingType int recordingType(String type) { - switch (type) { - case Schedules.TYPE_TIMED: - return TYPE_TIMED; - case Schedules.TYPE_PROGRAM: - return TYPE_PROGRAM; - default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); - return TYPE_TIMED; - } - } - - /** - * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}. - */ - private static String recordingType(@RecordingType int type) { - switch (type) { - case TYPE_TIMED: - return Schedules.TYPE_TIMED; - case TYPE_PROGRAM: - return Schedules.TYPE_PROGRAM; - default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); - return Schedules.TYPE_TIMED; - } - } - - /** - * Converts a string to a @RecordingState int, defaulting to - * {@link #STATE_RECORDING_NOT_STARTED}. - */ - private static @RecordingState int recordingState(String state) { - switch (state) { - case Schedules.STATE_RECORDING_NOT_STARTED: - return STATE_RECORDING_NOT_STARTED; - case Schedules.STATE_RECORDING_IN_PROGRESS: - return STATE_RECORDING_IN_PROGRESS; - case Schedules.STATE_RECORDING_FINISHED: - return STATE_RECORDING_FINISHED; - case Schedules.STATE_RECORDING_FAILED: - return STATE_RECORDING_FAILED; - case Schedules.STATE_RECORDING_CLIPPED: - return STATE_RECORDING_CLIPPED; - case Schedules.STATE_RECORDING_DELETED: - return STATE_RECORDING_DELETED; - case Schedules.STATE_RECORDING_CANCELED: - return STATE_RECORDING_CANCELED; - default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); - return STATE_RECORDING_NOT_STARTED; - } - } - - /** - * Converts a @RecordingState int to string, defaulting to - * {@link Schedules#STATE_RECORDING_NOT_STARTED}. - */ - private static String recordingState(@RecordingState int state) { - switch (state) { - case STATE_RECORDING_NOT_STARTED: - return Schedules.STATE_RECORDING_NOT_STARTED; - case STATE_RECORDING_IN_PROGRESS: - return Schedules.STATE_RECORDING_IN_PROGRESS; - case STATE_RECORDING_FINISHED: - return Schedules.STATE_RECORDING_FINISHED; - case STATE_RECORDING_FAILED: - return Schedules.STATE_RECORDING_FAILED; - case STATE_RECORDING_CLIPPED: - return Schedules.STATE_RECORDING_CLIPPED; - case STATE_RECORDING_DELETED: - return Schedules.STATE_RECORDING_DELETED; - case STATE_RECORDING_CANCELED: - return Schedules.STATE_RECORDING_CANCELED; - default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); - return Schedules.STATE_RECORDING_NOT_STARTED; - } - } - - /** - * Checks if the {@code period} overlaps with the recording time. - */ - public boolean isOverLapping(Range period) { - return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower(); - } - - /** - * Checks if the {@code schedule} overlaps with this schedule. - */ - public boolean isOverLapping(ScheduledRecording schedule) { - return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs(); - } - - @Override - public String toString() { - return "ScheduledRecording[" + mId - + "]" - + "(inputId=" + mInputId - + ",channelId=" + mChannelId - + ",programId=" + mProgramId - + ",programTitle=" + mProgramTitle - + ",type=" + mType - + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")" - + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")" - + ",seasonNumber=" + mSeasonNumber - + ",episodeNumber=" + mEpisodeNumber - + ",episodeTitle=" + mEpisodeTitle - + ",programDescription=" + mProgramDescription - + ",programLongDescription=" + mProgramLongDescription - + ",programPosterArtUri=" + mProgramPosterArtUri - + ",programThumbnailUri=" + mProgramThumbnailUri - + ",state=" + mState - + ",priority=" + mPriority - + ",seriesRecordingId=" + mSeriesRecordingId - + ")"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int paramInt) { - out.writeLong(mId); - out.writeLong(mPriority); - out.writeString(mInputId); - out.writeLong(mChannelId); - out.writeLong(mProgramId); - out.writeString(mProgramTitle); - out.writeInt(mType); - out.writeLong(mStartTimeMs); - out.writeLong(mEndTimeMs); - out.writeString(mSeasonNumber); - out.writeString(mEpisodeNumber); - out.writeString(mEpisodeTitle); - out.writeString(mProgramDescription); - out.writeString(mProgramLongDescription); - out.writeString(mProgramPosterArtUri); - out.writeString(mProgramThumbnailUri); - out.writeInt(mState); - out.writeLong(mSeriesRecordingId); - } - - /** - * Returns {@code true} if the recording is not started yet, otherwise @{code false}. - */ - public boolean isNotStarted() { - return mState == STATE_RECORDING_NOT_STARTED; - } - - /** - * Returns {@code true} if the recording is in progress, otherwise @{code false}. - */ - public boolean isInProgress() { - return mState == STATE_RECORDING_IN_PROGRESS; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ScheduledRecording)) { - return false; - } - ScheduledRecording r = (ScheduledRecording) obj; - return mId == r.mId - && mPriority == r.mPriority - && mChannelId == r.mChannelId - && mProgramId == r.mProgramId - && Objects.equals(mProgramTitle, r.mProgramTitle) - && mType == r.mType - && mStartTimeMs == r.mStartTimeMs - && mEndTimeMs == r.mEndTimeMs - && Objects.equals(mSeasonNumber, r.mSeasonNumber) - && Objects.equals(mEpisodeNumber, r.mEpisodeNumber) - && Objects.equals(mEpisodeTitle, r.mEpisodeTitle) - && Objects.equals(mProgramDescription, r.getProgramDescription()) - && Objects.equals(mProgramLongDescription, r.getProgramLongDescription()) - && Objects.equals(mProgramPosterArtUri, r.getProgramPosterArtUri()) - && Objects.equals(mProgramThumbnailUri, r.getProgramThumbnailUri()) - && mState == r.mState - && mSeriesRecordingId == r.mSeriesRecordingId; - } - - @Override - public int hashCode() { - return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType, - mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle, - mProgramDescription, mProgramLongDescription, mProgramPosterArtUri, - mProgramThumbnailUri, mState, mSeriesRecordingId); - } - - /** - * Returns an array containing all of the elements in the list. - */ - public static ScheduledRecording[] toArray(Collection schedules) { - return schedules.toArray(new ScheduledRecording[schedules.size()]); - } -} diff --git a/src/com/android/tv/dvr/Scheduler.java b/src/com/android/tv/dvr/Scheduler.java deleted file mode 100644 index ce78e1be..00000000 --- a/src/com/android/tv/dvr/Scheduler.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager.TvInputCallback; -import android.os.Looper; -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Range; - -import com.android.tv.InputSessionManager; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.ChannelDataManager.Listener; -import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.util.Clock; -import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * The core class to manage schedule and run actual recording. - */ -@MainThread -public class Scheduler extends TvInputCallback implements ScheduledRecordingListener { - private static final String TAG = "Scheduler"; - private static final boolean DEBUG = false; - - private final static long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(5); - @VisibleForTesting final static long MS_TO_WAKE_BEFORE_START = TimeUnit.MINUTES.toMillis(1); - - private final Looper mLooper; - private final InputSessionManager mSessionManager; - private final WritableDvrDataManager mDataManager; - private final DvrManager mDvrManager; - private final ChannelDataManager mChannelDataManager; - private final TvInputManagerHelper mInputManager; - private final Context mContext; - private final Clock mClock; - private final AlarmManager mAlarmManager; - - private final Map mInputSchedulerMap = new ArrayMap<>(); - private long mLastStartTimePendingMs; - - public Scheduler(Looper looper, DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, ChannelDataManager channelDataManager, - TvInputManagerHelper inputManager, Context context, Clock clock, - AlarmManager alarmManager) { - mLooper = looper; - mDvrManager = dvrManager; - mSessionManager = sessionManager; - mDataManager = dataManager; - mChannelDataManager = channelDataManager; - mInputManager = inputManager; - mContext = context; - mClock = clock; - mAlarmManager = alarmManager; - } - - /** - * Starts the scheduler. - */ - public void start() { - mDataManager.addScheduledRecordingListener(this); - mInputManager.addCallback(this); - if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) { - updateInternal(); - } else { - if (!mDataManager.isDvrScheduleLoadFinished()) { - mDataManager.addDvrScheduleLoadFinishedListener( - new OnDvrScheduleLoadFinishedListener() { - @Override - public void onDvrScheduleLoadFinished() { - mDataManager.removeDvrScheduleLoadFinishedListener(this); - updateInternal(); - } - }); - } - if (!mChannelDataManager.isDbLoadFinished()) { - mChannelDataManager.addListener(new Listener() { - @Override - public void onLoadFinished() { - mChannelDataManager.removeListener(this); - updateInternal(); - } - - @Override - public void onChannelListUpdated() { } - - @Override - public void onChannelBrowsableChanged() { } - }); - } - } - } - - /** - * Stops the scheduler. - */ - public void stop() { - for (InputTaskScheduler inputTaskScheduler : mInputSchedulerMap.values()) { - inputTaskScheduler.stop(); - } - mInputManager.removeCallback(this); - mDataManager.removeScheduledRecordingListener(this); - } - - private void updatePendingRecordings() { - List scheduledRecordings = mDataManager - .getScheduledRecordings(new Range<>(mLastStartTimePendingMs, - mClock.currentTimeMillis() + SOON_DURATION_IN_MS), - ScheduledRecording.STATE_RECORDING_NOT_STARTED); - for (ScheduledRecording r : scheduledRecordings) { - scheduleRecordingSoon(r); - } - } - - /** - * Start recording that will happen soon, and set the next alarm time. - */ - public void update() { - if (DEBUG) Log.d(TAG, "update"); - updateInternal(); - } - - private void updateInternal() { - if (isInitialized()) { - updatePendingRecordings(); - updateNextAlarm(); - } - } - - private boolean isInitialized() { - return mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished(); - } - - @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { - if (DEBUG) Log.d(TAG, "added " + Arrays.asList(schedules)); - if (!isInitialized()) { - return; - } - handleScheduleChange(schedules); - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { - if (DEBUG) Log.d(TAG, "removed " + Arrays.asList(schedules)); - if (!isInitialized()) { - return; - } - boolean needToUpdateAlarm = false; - for (ScheduledRecording schedule : schedules) { - InputTaskScheduler scheduler = mInputSchedulerMap.get(schedule.getInputId()); - if (scheduler != null) { - scheduler.removeSchedule(schedule); - needToUpdateAlarm = true; - } - } - if (needToUpdateAlarm) { - updateNextAlarm(); - } - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { - if (DEBUG) Log.d(TAG, "state changed " + Arrays.asList(schedules)); - if (!isInitialized()) { - return; - } - // Update the recordings. - for (ScheduledRecording schedule : schedules) { - InputTaskScheduler scheduler = mInputSchedulerMap.get(schedule.getInputId()); - if (scheduler != null) { - scheduler.updateSchedule(schedule); - } - } - handleScheduleChange(schedules); - } - - private void handleScheduleChange(ScheduledRecording... schedules) { - boolean needToUpdateAlarm = false; - for (ScheduledRecording schedule : schedules) { - if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { - if (startsWithin(schedule, SOON_DURATION_IN_MS)) { - scheduleRecordingSoon(schedule); - } else { - needToUpdateAlarm = true; - } - } - } - if (needToUpdateAlarm) { - updateNextAlarm(); - } - } - - private void scheduleRecordingSoon(ScheduledRecording schedule) { - TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); - if (input == null) { - Log.e(TAG, "Can't find input for " + schedule); - mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); - return; - } - if (!input.canRecord() || input.getTunerCount() <= 0) { - Log.e(TAG, "TV input doesn't support recording: " + input); - mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); - return; - } - InputTaskScheduler scheduler = mInputSchedulerMap.get(input.getId()); - if (scheduler == null) { - scheduler = new InputTaskScheduler(mContext, input, mLooper, mChannelDataManager, - mDvrManager, mDataManager, mSessionManager, mClock); - mInputSchedulerMap.put(input.getId(), scheduler); - } - scheduler.addSchedule(schedule); - if (mLastStartTimePendingMs < schedule.getStartTimeMs()) { - mLastStartTimePendingMs = schedule.getStartTimeMs(); - } - } - - private void updateNextAlarm() { - long nextStartTime = mDataManager.getNextScheduledStartTimeAfter( - Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis())); - if (nextStartTime != DvrDataManager.NEXT_START_TIME_NOT_FOUND) { - long wakeAt = nextStartTime - MS_TO_WAKE_BEFORE_START; - if (DEBUG) Log.d(TAG, "Set alarm to record at " + wakeAt); - Intent intent = new Intent(mContext, DvrStartRecordingReceiver.class); - PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - // This will cancel the previous alarm. - mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeAt, alarmIntent); - } else { - if (DEBUG) Log.d(TAG, "No future recording, alarm not set"); - } - } - - @VisibleForTesting - boolean startsWithin(ScheduledRecording scheduledRecording, long durationInMs) { - return mClock.currentTimeMillis() >= scheduledRecording.getStartTimeMs() - durationInMs; - } - - // No need to remove input task scheduler when the input is removed. If the input is removed - // temporarily, the scheduler should keep the non-started schedules. - @Override - public void onInputUpdated(String inputId) { - InputTaskScheduler scheduler = mInputSchedulerMap.get(inputId); - if (scheduler != null) { - scheduler.updateTvInputInfo(Utils.getTvInputInfoForInputId(mContext, inputId)); - } - } - - @Override - public void onTvInputInfoUpdated(TvInputInfo input) { - InputTaskScheduler scheduler = mInputSchedulerMap.get(input.getId()); - if (scheduler != null) { - scheduler.updateTvInputInfo(input); - } - } -} diff --git a/src/com/android/tv/dvr/SeriesInfo.java b/src/com/android/tv/dvr/SeriesInfo.java deleted file mode 100644 index 30256dc5..00000000 --- a/src/com/android/tv/dvr/SeriesInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -/** - * Series information. - */ -public class SeriesInfo { - private final String mId; - private final String mTitle; - private final String mDescription; - private final String mLongDescription; - private final int[] mCanonicalGenreIds; - private final String mPosterUri; - private final String mPhotoUri; - - public SeriesInfo(String id, String title, String description, String longDescription, - int[] canonicalGenreIds, String posterUri, String photoUri) { - this.mId = id; - this.mTitle = title; - this.mDescription = description; - this.mLongDescription = longDescription; - this.mCanonicalGenreIds = canonicalGenreIds; - this.mPosterUri = posterUri; - this.mPhotoUri = photoUri; - } - - /** Returns the ID. **/ - public String getId() { - return mId; - } - - /** Returns the title. **/ - public String getTitle() { - return mTitle; - } - - /** Returns the description. **/ - public String getDescription() { - return mDescription; - } - - /** Returns the description. **/ - public String getLongDescription() { - return mLongDescription; - } - - /** Returns the canonical genre IDs. **/ - public int[] getCanonicalGenreIds() { - return mCanonicalGenreIds; - } - - /** Returns the poster URI. **/ - public String getPosterUri() { - return mPosterUri; - } - - /** Returns the photo URI. **/ - public String getPhotoUri() { - return mPhotoUri; - } -} diff --git a/src/com/android/tv/dvr/SeriesRecording.java b/src/com/android/tv/dvr/SeriesRecording.java deleted file mode 100644 index f0690f5f..00000000 --- a/src/com/android/tv/dvr/SeriesRecording.java +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.content.ContentValues; -import android.database.Cursor; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.IntDef; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; - -import com.android.tv.data.BaseProgram; -import com.android.tv.data.Program; -import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; -import com.android.tv.util.Utils; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.Objects; - -/** - * Schedules the recording of a Series of Programs. - * - *

- * Contains the data needed to create new ScheduleRecordings as the programs become available in - * the EPG. - */ -public class SeriesRecording implements Parcelable { - /** - * Indicates that the ID is not assigned yet. - */ - public static final long ID_NOT_SET = 0; - - /** - * The default priority of this recording. - */ - public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL}) - public @interface ChannelOption {} - /** - * An option which indicates that the episodes in one channel are recorded. - */ - public static final int OPTION_CHANNEL_ONE = 0; - /** - * An option which indicates that the episodes in all the channels are recorded. - */ - public static final int OPTION_CHANNEL_ALL = 1; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED}) - public @interface SeriesState {} - - /** - * The state indicates that the series recording is a normal one. - */ - public static final int STATE_SERIES_NORMAL = 0; - - /** - * The state indicates that the series recording is stopped. - */ - public static final int STATE_SERIES_STOPPED = 1; - - /** - * Compare priority in descending order. - */ - public static final Comparator PRIORITY_COMPARATOR = - new Comparator() { - @Override - public int compare(SeriesRecording lhs, SeriesRecording rhs) { - int value = Long.compare(rhs.mPriority, lhs.mPriority); - if (value == 0) { - // New recording has the higher priority. - value = Long.compare(rhs.mId, lhs.mId); - } - return value; - } - }; - - /** - * Compare ID in ascending order. - */ - public static final Comparator ID_COMPARATOR = - new Comparator() { - @Override - public int compare(SeriesRecording lhs, SeriesRecording rhs) { - return Long.compare(lhs.mId, rhs.mId); - } - }; - - /** - * Creates a new Builder with the values set from the series information of {@link BaseProgram}. - */ - public static Builder builder(String inputId, BaseProgram p) { - return new Builder() - .setInputId(inputId) - .setSeriesId(p.getSeriesId()) - .setChannelId(p.getChannelId()) - .setTitle(p.getTitle()) - .setDescription(p.getDescription()) - .setLongDescription(p.getLongDescription()) - .setCanonicalGenreIds(p.getCanonicalGenreIds()) - .setPosterUri(p.getPosterArtUri()) - .setPhotoUri(p.getThumbnailUri()); - } - - /** - * Creates a new Builder with the values set from an existing {@link SeriesRecording}. - */ - @VisibleForTesting - public static Builder buildFrom(SeriesRecording r) { - return new Builder() - .setId(r.mId) - .setInputId(r.getInputId()) - .setChannelId(r.getChannelId()) - .setPriority(r.getPriority()) - .setTitle(r.getTitle()) - .setDescription(r.getDescription()) - .setLongDescription(r.getLongDescription()) - .setSeriesId(r.getSeriesId()) - .setStartFromEpisode(r.getStartFromEpisode()) - .setStartFromSeason(r.getStartFromSeason()) - .setChannelOption(r.getChannelOption()) - .setCanonicalGenreIds(r.getCanonicalGenreIds()) - .setPosterUri(r.getPosterUri()) - .setPhotoUri(r.getPhotoUri()) - .setState(r.getState()); - } - - /** - * Use this projection if you want to create {@link SeriesRecording} object using - * {@link #fromCursor}. - */ - public static final String[] PROJECTION = { - // Columns must match what is read in fromCursor() - SeriesRecordings._ID, - SeriesRecordings.COLUMN_INPUT_ID, - SeriesRecordings.COLUMN_CHANNEL_ID, - SeriesRecordings.COLUMN_PRIORITY, - SeriesRecordings.COLUMN_TITLE, - SeriesRecordings.COLUMN_SHORT_DESCRIPTION, - SeriesRecordings.COLUMN_LONG_DESCRIPTION, - SeriesRecordings.COLUMN_SERIES_ID, - SeriesRecordings.COLUMN_START_FROM_EPISODE, - SeriesRecordings.COLUMN_START_FROM_SEASON, - SeriesRecordings.COLUMN_CHANNEL_OPTION, - SeriesRecordings.COLUMN_CANONICAL_GENRE, - SeriesRecordings.COLUMN_POSTER_URI, - SeriesRecordings.COLUMN_PHOTO_URI, - SeriesRecordings.COLUMN_STATE - }; - /** - * Creates {@link SeriesRecording} object from the given {@link Cursor}. - */ - public static SeriesRecording fromCursor(Cursor c) { - int index = -1; - return new Builder() - .setId(c.getLong(++index)) - .setInputId(c.getString(++index)) - .setChannelId(c.getLong(++index)) - .setPriority(c.getLong(++index)) - .setTitle(c.getString(++index)) - .setDescription(c.getString(++index)) - .setLongDescription(c.getString(++index)) - .setSeriesId(c.getString(++index)) - .setStartFromEpisode(c.getInt(++index)) - .setStartFromSeason(c.getInt(++index)) - .setChannelOption(channelOption(c.getString(++index))) - .setCanonicalGenreIds(c.getString(++index)) - .setPosterUri(c.getString(++index)) - .setPhotoUri(c.getString(++index)) - .setState(seriesRecordingState(c.getString(++index))) - .build(); - } - - /** - * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings} - * and the values from {@code r}. - */ - public static ContentValues toContentValues(SeriesRecording r) { - ContentValues values = new ContentValues(); - if (r.getId() != ID_NOT_SET) { - values.put(SeriesRecordings._ID, r.getId()); - } else { - values.putNull(SeriesRecordings._ID); - } - values.put(SeriesRecordings.COLUMN_INPUT_ID, r.getInputId()); - values.put(SeriesRecordings.COLUMN_CHANNEL_ID, r.getChannelId()); - values.put(SeriesRecordings.COLUMN_PRIORITY, r.getPriority()); - values.put(SeriesRecordings.COLUMN_TITLE, r.getTitle()); - values.put(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, r.getDescription()); - values.put(SeriesRecordings.COLUMN_LONG_DESCRIPTION, r.getLongDescription()); - values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId()); - values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode()); - values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason()); - values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION, - channelOption(r.getChannelOption())); - values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE, - Utils.getCanonicalGenre(r.getCanonicalGenreIds())); - values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri()); - values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri()); - values.put(SeriesRecordings.COLUMN_STATE, seriesRecordingState(r.getState())); - return values; - } - - private static String channelOption(@ChannelOption int option) { - switch (option) { - case OPTION_CHANNEL_ONE: - return SeriesRecordings.OPTION_CHANNEL_ONE; - case OPTION_CHANNEL_ALL: - return SeriesRecordings.OPTION_CHANNEL_ALL; - } - return SeriesRecordings.OPTION_CHANNEL_ONE; - } - - @ChannelOption private static int channelOption(String option) { - switch (option) { - case SeriesRecordings.OPTION_CHANNEL_ONE: - return OPTION_CHANNEL_ONE; - case SeriesRecordings.OPTION_CHANNEL_ALL: - return OPTION_CHANNEL_ALL; - } - return OPTION_CHANNEL_ONE; - } - - private static String seriesRecordingState(@SeriesState int state) { - switch (state) { - case STATE_SERIES_NORMAL: - return SeriesRecordings.STATE_SERIES_NORMAL; - case STATE_SERIES_STOPPED: - return SeriesRecordings.STATE_SERIES_STOPPED; - } - return SeriesRecordings.STATE_SERIES_NORMAL; - } - - @SeriesState private static int seriesRecordingState(String state) { - switch (state) { - case SeriesRecordings.STATE_SERIES_NORMAL: - return STATE_SERIES_NORMAL; - case SeriesRecordings.STATE_SERIES_STOPPED: - return STATE_SERIES_STOPPED; - } - return STATE_SERIES_NORMAL; - } - - /** - * Builder for {@link SeriesRecording}. - */ - public static class Builder { - private long mId = ID_NOT_SET; - private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY; - private String mTitle; - private String mDescription; - private String mLongDescription; - private String mInputId; - private long mChannelId; - private String mSeriesId; - private int mStartFromSeason = SeriesRecordings.THE_BEGINNING; - private int mStartFromEpisode = SeriesRecordings.THE_BEGINNING; - private int mChannelOption = OPTION_CHANNEL_ONE; - private int[] mCanonicalGenreIds; - private String mPosterUri; - private String mPhotoUri; - private int mState = SeriesRecording.STATE_SERIES_NORMAL; - - /** - * @see #getId() - */ - public Builder setId(long id) { - mId = id; - return this; - } - - /** - * @see #getPriority() () - */ - public Builder setPriority(long priority) { - mPriority = priority; - return this; - } - - /** - * @see #getTitle() - */ - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - /** - * @see #getDescription() - */ - public Builder setDescription(String description) { - mDescription = description; - return this; - } - - /** - * @see #getLongDescription() - */ - public Builder setLongDescription(String longDescription) { - mLongDescription = longDescription; - return this; - } - - /** - * @see #getInputId() - */ - public Builder setInputId(String inputId) { - mInputId = inputId; - return this; - } - - /** - * @see #getChannelId() - */ - public Builder setChannelId(long channelId) { - mChannelId = channelId; - return this; - } - - /** - * @see #getSeriesId() - */ - public Builder setSeriesId(String seriesId) { - mSeriesId = seriesId; - return this; - } - - /** - * @see #getStartFromSeason() - */ - public Builder setStartFromSeason(int startFromSeason) { - mStartFromSeason = startFromSeason; - return this; - } - - /** - * @see #getChannelOption() - */ - public Builder setChannelOption(@ChannelOption int option) { - mChannelOption = option; - return this; - } - - /** - * @see #getStartFromEpisode() - */ - public Builder setStartFromEpisode(int startFromEpisode) { - mStartFromEpisode = startFromEpisode; - return this; - } - - /** - * @see #getCanonicalGenreIds() - */ - public Builder setCanonicalGenreIds(String genres) { - mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres); - return this; - } - - /** - * @see #getCanonicalGenreIds() - */ - public Builder setCanonicalGenreIds(int[] canonicalGenreIds) { - mCanonicalGenreIds = canonicalGenreIds; - return this; - } - - /** - * @see #getPosterUri() - */ - public Builder setPosterUri(String posterUri) { - mPosterUri = posterUri; - return this; - } - - /** - * @see #getPhotoUri() - */ - public Builder setPhotoUri(String photoUri) { - mPhotoUri = photoUri; - return this; - } - - /** - * @see #getState() - */ - public Builder setState(@SeriesState int state) { - mState = state; - return this; - } - - /** - * Creates a new {@link SeriesRecording}. - */ - public SeriesRecording build() { - return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription, - mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode, - mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); - } - } - - public static SeriesRecording fromParcel(Parcel in) { - return new Builder() - .setId(in.readLong()) - .setPriority(in.readLong()) - .setTitle(in.readString()) - .setDescription(in.readString()) - .setLongDescription(in.readString()) - .setInputId(in.readString()) - .setChannelId(in.readLong()) - .setSeriesId(in.readString()) - .setStartFromSeason(in.readInt()) - .setStartFromEpisode(in.readInt()) - .setChannelOption(in.readInt()) - .setCanonicalGenreIds(in.createIntArray()) - .setPosterUri(in.readString()) - .setPhotoUri(in.readString()) - .setState(in.readInt()) - .build(); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SeriesRecording createFromParcel(Parcel in) { - return SeriesRecording.fromParcel(in); - } - - @Override - public SeriesRecording[] newArray(int size) { - return new SeriesRecording[size]; - } - }; - - private long mId; - private final long mPriority; - private final String mTitle; - private final String mDescription; - private final String mLongDescription; - private final String mInputId; - private final long mChannelId; - private final String mSeriesId; - private final int mStartFromSeason; - private final int mStartFromEpisode; - @ChannelOption private final int mChannelOption; - private final int[] mCanonicalGenreIds; - private final String mPosterUri; - private final String mPhotoUri; - @SeriesState private int mState; - - /** - * The input id of this SeriesRecording. - */ - public String getInputId() { - return mInputId; - } - - /** - * The channelId to match. The channel ID might not be valid when the channel option is "ALL". - */ - public long getChannelId() { - return mChannelId; - } - - /** - * The id of this SeriesRecording. - */ - public long getId() { - return mId; - } - - /** - * Sets the ID. - */ - public void setId(long id) { - mId = id; - } - - /** - * The priority of this recording. - * - *

The highest number is recorded first. If there is a tie in mPriority then the higher mId - * wins. - */ - public long getPriority() { - return mPriority; - } - - /** - * The series title. - */ - public String getTitle() { - return mTitle; - } - - /** - * The series description. - */ - public String getDescription() { - return mDescription; - } - - /** - * The long series description. - */ - public String getLongDescription() { - return mLongDescription; - } - - /** - * SeriesId when not null is used to match programs instead of using title and channelId. - * - *

SeriesId is an opaque but stable string. - */ - public String getSeriesId() { - return mSeriesId; - } - - /** - * If not == {@link SeriesRecordings#THE_BEGINNING} and seasonNumber == startFromSeason then - * only record episodes with a episodeNumber >= this - */ - public int getStartFromEpisode() { - return mStartFromEpisode; - } - - /** - * If not == {@link SeriesRecordings#THE_BEGINNING} then only record episodes with a - * seasonNumber >= this - */ - public int getStartFromSeason() { - return mStartFromSeason; - } - - /** - * Returns the channel recording option. - */ - @ChannelOption public int getChannelOption() { - return mChannelOption; - } - - /** - * Returns the canonical genre ID's. - */ - public int[] getCanonicalGenreIds() { - return mCanonicalGenreIds; - } - - /** - * Returns the poster URI. - */ - public String getPosterUri() { - return mPosterUri; - } - - /** - * Returns the photo URI. - */ - public String getPhotoUri() { - return mPhotoUri; - } - - /** - * Returns the state of series recording. - */ - @SeriesState public int getState() { - return mState; - } - - /** - * Checks whether the series recording is stopped or not. - */ - public boolean isStopped() { - return mState == STATE_SERIES_STOPPED; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SeriesRecording)) return false; - SeriesRecording that = (SeriesRecording) o; - return mPriority == that.mPriority - && mChannelId == that.mChannelId - && mStartFromSeason == that.mStartFromSeason - && mStartFromEpisode == that.mStartFromEpisode - && Objects.equals(mId, that.mId) - && Objects.equals(mTitle, that.mTitle) - && Objects.equals(mDescription, that.mDescription) - && Objects.equals(mLongDescription, that.mLongDescription) - && Objects.equals(mSeriesId, that.mSeriesId) - && mChannelOption == that.mChannelOption - && Arrays.equals(mCanonicalGenreIds, that.mCanonicalGenreIds) - && Objects.equals(mPosterUri, that.mPosterUri) - && Objects.equals(mPhotoUri, that.mPhotoUri) - && mState == that.mState; - } - - @Override - public int hashCode() { - return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId, - mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption, - mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); - } - - @Override - public String toString() { - return "SeriesRecording{" + - "inputId=" + mInputId + - ", channelId=" + mChannelId + - ", id='" + mId + '\'' + - ", priority=" + mPriority + - ", title='" + mTitle + '\'' + - ", description='" + mDescription + '\'' + - ", longDescription='" + mLongDescription + '\'' + - ", startFromSeason=" + mStartFromSeason + - ", startFromEpisode=" + mStartFromEpisode + - ", channelOption=" + mChannelOption + - ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) + - ", posterUri=" + mPosterUri + - ", photoUri=" + mPhotoUri + - ", state=" + mState + - '}'; - } - - private SeriesRecording(long id, long priority, String title, String description, - String longDescription, String inputId, long channelId, String seriesId, - int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds, - String posterUri, String photoUri, int state) { - this.mId = id; - this.mPriority = priority; - this.mTitle = title; - this.mDescription = description; - this.mLongDescription = longDescription; - this.mInputId = inputId; - this.mChannelId = channelId; - this.mSeriesId = seriesId; - this.mStartFromSeason = startFromSeason; - this.mStartFromEpisode = startFromEpisode; - this.mChannelOption = channelOption; - this.mCanonicalGenreIds = canonicalGenreIds; - this.mPosterUri = posterUri; - this.mPhotoUri = photoUri; - this.mState = state; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int paramInt) { - out.writeLong(mId); - out.writeLong(mPriority); - out.writeString(mTitle); - out.writeString(mDescription); - out.writeString(mLongDescription); - out.writeString(mInputId); - out.writeLong(mChannelId); - out.writeString(mSeriesId); - out.writeInt(mStartFromSeason); - out.writeInt(mStartFromEpisode); - out.writeInt(mChannelOption); - out.writeIntArray(mCanonicalGenreIds); - out.writeString(mPosterUri); - out.writeString(mPhotoUri); - out.writeInt(mState); - } - - /** - * Returns an array containing all of the elements in the list. - */ - public static SeriesRecording[] toArray(Collection series) { - return series.toArray(new SeriesRecording[series.size()]); - } - - /** - * Returns {@code true} if the {@code program} is part of the series and meets the season and - * episode constraints. - */ - public boolean matchProgram(Program program) { - return matchProgram(program, mChannelOption); - } - - /** - * Returns {@code true} if the {@code program} is part of the series and meets the season and - * episode constraints. It checks the channel option only if {@code checkChannelOption} is - * {@code true}. - */ - public boolean matchProgram(Program program, @ChannelOption int channelOption) { - String seriesId = program.getSeriesId(); - long channelId = program.getChannelId(); - String seasonNumber = program.getSeasonNumber(); - String episodeNumber = program.getEpisodeNumber(); - if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE - && mChannelId != channelId)) { - return false; - } - // Season number and episode number matches if - // start_season_number < program_season_number - // || (start_season_number == program_season_number - // && start_episode_number <= program_episode_number). - if (mStartFromSeason == SeriesRecordings.THE_BEGINNING - || TextUtils.isEmpty(seasonNumber)) { - return true; - } else { - int intSeasonNumber; - try { - intSeasonNumber = Integer.valueOf(seasonNumber); - } catch (NumberFormatException e) { - return true; - } - if (intSeasonNumber > mStartFromSeason) { - return true; - } else if (intSeasonNumber < mStartFromSeason) { - return false; - } - } - if (mStartFromEpisode == SeriesRecordings.THE_BEGINNING - || TextUtils.isEmpty(episodeNumber)) { - return true; - } else { - int intEpisodeNumber; - try { - intEpisodeNumber = Integer.valueOf(episodeNumber); - } catch (NumberFormatException e) { - return true; - } - return intEpisodeNumber >= mStartFromEpisode; - } - } -} diff --git a/src/com/android/tv/dvr/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/SeriesRecordingScheduler.java deleted file mode 100644 index 5ed12ce8..00000000 --- a/src/com/android/tv/dvr/SeriesRecordingScheduler.java +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Build; -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; -import android.util.LongSparseArray; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; -import com.android.tv.common.CollectionUtils; -import com.android.tv.common.SharedPreferencesUtils; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Program; -import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; -import com.android.tv.dvr.EpisodicProgramLoadTask.ScheduledEpisode; -import com.android.tv.experiments.Experiments; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.Set; - -/** - * Creates the {@link ScheduledRecording}s for the {@link SeriesRecording}. - *

- * The current implementation assumes that the series recordings are scheduled only for one channel. - */ -@TargetApi(Build.VERSION_CODES.N) -public class SeriesRecordingScheduler { - private static final String TAG = "SeriesRecordingSchd"; - private static final boolean DEBUG = false; - - private static final String KEY_FETCHED_SERIES_IDS = - "SeriesRecordingScheduler.fetched_series_ids"; - - @SuppressLint("StaticFieldLeak") - private static SeriesRecordingScheduler sInstance; - - /** - * Creates and returns the {@link SeriesRecordingScheduler}. - */ - public static synchronized SeriesRecordingScheduler getInstance(Context context) { - if (sInstance == null) { - sInstance = new SeriesRecordingScheduler(context); - } - return sInstance; - } - - private final Context mContext; - private final DvrManager mDvrManager; - private final WritableDvrDataManager mDataManager; - private final List mScheduleTasks = new ArrayList<>(); - private final List mFetchSeriesInfoTasks = new ArrayList<>(); - private final Set mFetchedSeriesIds = new ArraySet<>(); - private final SharedPreferences mSharedPreferences; - private boolean mStarted; - private boolean mPaused; - private final Set mPendingSeriesRecordings = new ArraySet<>(); - private final Set mOnSeriesRecordingUpdatedListeners = - new CopyOnWriteArraySet<>(); - - - private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() { - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - executeFetchSeriesInfoTask(seriesRecording); - } - } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { - // Cancel the update. - for (Iterator iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { - SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR).isEmpty()) { - task.cancel(true); - iter.remove(); - } - } - } - - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - List stopped = new ArrayList<>(); - List normal = new ArrayList<>(); - for (SeriesRecording r : seriesRecordings) { - if (r.isStopped()) { - stopped.add(r); - } else { - normal.add(r); - } - } - if (!stopped.isEmpty()) { - onSeriesRecordingRemoved(SeriesRecording.toArray(stopped)); - } - if (!normal.isEmpty()) { - updateSchedules(normal); - } - } - }; - - private final ScheduledRecordingListener mScheduledRecordingListener = - new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { - // No need to update series recordings when the new schedule is added. - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { - handleScheduledRecordingChange(Arrays.asList(schedules)); - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { - List schedulesForUpdate = new ArrayList<>(); - for (ScheduledRecording r : schedules) { - if ((r.getState() == ScheduledRecording.STATE_RECORDING_FAILED - || r.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED) - && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET - && !TextUtils.isEmpty(r.getSeasonNumber()) - && !TextUtils.isEmpty(r.getEpisodeNumber())) { - schedulesForUpdate.add(r); - } - } - if (!schedulesForUpdate.isEmpty()) { - handleScheduledRecordingChange(schedulesForUpdate); - } - } - - private void handleScheduledRecordingChange(List schedules) { - if (schedules.isEmpty()) { - return; - } - Set seriesRecordingIds = new HashSet<>(); - for (ScheduledRecording r : schedules) { - if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { - seriesRecordingIds.add(r.getSeriesRecordingId()); - } - } - if (!seriesRecordingIds.isEmpty()) { - List seriesRecordings = new ArrayList<>(); - for (Long id : seriesRecordingIds) { - SeriesRecording seriesRecording = mDataManager.getSeriesRecording(id); - if (seriesRecording != null) { - seriesRecordings.add(seriesRecording); - } - } - if (!seriesRecordings.isEmpty()) { - updateSchedules(seriesRecordings); - } - } - } - }; - - private SeriesRecordingScheduler(Context context) { - mContext = context.getApplicationContext(); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDvrManager = appSingletons.getDvrManager(); - mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); - mSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); - mFetchedSeriesIds.addAll(mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, - Collections.emptySet())); - } - - /** - * Starts the scheduler. - */ - @MainThread - public void start() { - SoftPreconditions.checkState(mDataManager.isInitialized()); - if (mStarted) { - return; - } - if (DEBUG) Log.d(TAG, "start"); - mStarted = true; - mDataManager.addSeriesRecordingListener(mSeriesRecordingListener); - mDataManager.addScheduledRecordingListener(mScheduledRecordingListener); - startFetchingSeriesInfo(); - updateSchedules(mDataManager.getSeriesRecordings()); - } - - @MainThread - public void stop() { - if (!mStarted) { - return; - } - if (DEBUG) Log.d(TAG, "stop"); - mStarted = false; - for (FetchSeriesInfoTask task : mFetchSeriesInfoTasks) { - task.cancel(true); - } - mFetchSeriesInfoTasks.clear(); - for (SeriesRecordingUpdateTask task : mScheduleTasks) { - task.cancel(true); - } - mScheduleTasks.clear(); - mDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); - mDataManager.removeSeriesRecordingListener(mSeriesRecordingListener); - } - - private void startFetchingSeriesInfo() { - for (SeriesRecording seriesRecording : mDataManager.getSeriesRecordings()) { - if (!mFetchedSeriesIds.contains(seriesRecording.getSeriesId())) { - executeFetchSeriesInfoTask(seriesRecording); - } - } - } - - private void executeFetchSeriesInfoTask(SeriesRecording seriesRecording) { - if (Experiments.CLOUD_EPG.get()) { - FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording); - task.execute(); - mFetchSeriesInfoTasks.add(task); - } - } - - /** - * Pauses the updates of the series recordings. - */ - public void pauseUpdate() { - if (DEBUG) Log.d(TAG, "Schedule paused"); - if (mPaused) { - return; - } - mPaused = true; - if (!mStarted) { - return; - } - for (SeriesRecordingUpdateTask task : mScheduleTasks) { - for (SeriesRecording r : task.getSeriesRecordings()) { - mPendingSeriesRecordings.add(r.getId()); - } - task.cancel(true); - } - } - - /** - * Resumes the updates of the series recordings. - */ - public void resumeUpdate() { - if (DEBUG) Log.d(TAG, "Schedule resumed"); - if (!mPaused) { - return; - } - mPaused = false; - if (!mStarted) { - return; - } - if (!mPendingSeriesRecordings.isEmpty()) { - List seriesRecordings = new ArrayList<>(); - for (long seriesRecordingId : mPendingSeriesRecordings) { - SeriesRecording seriesRecording = - mDataManager.getSeriesRecording(seriesRecordingId); - if (seriesRecording != null) { - seriesRecordings.add(seriesRecording); - } - } - if (!seriesRecordings.isEmpty()) { - updateSchedules(seriesRecordings); - } - } - } - - /** - * Update schedules for the given series recordings. If it's paused, the update will be done - * after it's resumed. - */ - public void updateSchedules(Collection seriesRecordings) { - if (DEBUG) Log.d(TAG, "updateSchedules:" + seriesRecordings); - if (!mStarted) { - if (DEBUG) Log.d(TAG, "Not started yet."); - return; - } - if (mPaused) { - for (SeriesRecording r : seriesRecordings) { - mPendingSeriesRecordings.add(r.getId()); - } - if (DEBUG) { - Log.d(TAG, "The scheduler has been paused. Adding to the pending list. size=" - + mPendingSeriesRecordings.size()); - } - return; - } - Set previousSeriesRecordings = new HashSet<>(); - for (Iterator iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { - SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.containsAny(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR)) { - // The task is affected by the seriesRecordings - task.cancel(true); - previousSeriesRecordings.addAll(task.getSeriesRecordings()); - iter.remove(); - } - } - List seriesRecordingsToUpdate = CollectionUtils.union(seriesRecordings, - previousSeriesRecordings, SeriesRecording.ID_COMPARATOR); - for (Iterator iter = seriesRecordingsToUpdate.iterator(); - iter.hasNext(); ) { - SeriesRecording seriesRecording = mDataManager.getSeriesRecording(iter.next().getId()); - if (seriesRecording == null || seriesRecording.isStopped()) { - // Series recording has been removed or stopped. - iter.remove(); - } - } - if (seriesRecordingsToUpdate.isEmpty()) { - return; - } - if (needToReadAllChannels(seriesRecordingsToUpdate)) { - SeriesRecordingUpdateTask task = - new SeriesRecordingUpdateTask(seriesRecordingsToUpdate); - mScheduleTasks.add(task); - if (DEBUG) Log.d(TAG, "Added schedule task: " + task); - task.execute(); - } else { - for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { - SeriesRecordingUpdateTask task = new SeriesRecordingUpdateTask( - Collections.singletonList(seriesRecording)); - mScheduleTasks.add(task); - if (DEBUG) Log.d(TAG, "Added schedule task: " + task); - task.execute(); - } - } - } - - /** - * Adds {@link OnSeriesRecordingUpdatedListener}. - */ - public void addOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) { - mOnSeriesRecordingUpdatedListeners.add(listener); - } - - /** - * Removes {@link OnSeriesRecordingUpdatedListener}. - */ - public void removeOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) { - mOnSeriesRecordingUpdatedListeners.remove(listener); - } - - private boolean needToReadAllChannels(List seriesRecordingsToUpdate) { - for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { - if (seriesRecording.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ALL) { - return true; - } - } - return false; - } - - /** - * Pick one program per an episode. - * - *

Note that the programs which has been already scheduled have the highest priority, and all - * of them are added even though they are the same episodes. That's because the schedules - * should be added to the series recording. - *

If there are no existing schedules for an episode, one program which starts earlier is - * picked. - */ - private LongSparseArray> pickOneProgramPerEpisode( - List seriesRecordings, List programs) { - return pickOneProgramPerEpisode(mDataManager, seriesRecordings, programs); - } - - /** - * @see #pickOneProgramPerEpisode(List, List) - */ - @VisibleForTesting - static LongSparseArray> pickOneProgramPerEpisode( - DvrDataManager dataManager, List seriesRecordings, - List programs) { - // Initialize. - LongSparseArray> result = new LongSparseArray<>(); - Map seriesRecordingIds = new HashMap<>(); - for (SeriesRecording seriesRecording : seriesRecordings) { - result.put(seriesRecording.getId(), new ArrayList<>()); - seriesRecordingIds.put(seriesRecording.getSeriesId(), seriesRecording.getId()); - } - // Group programs by the episode. - Map> programsForEpisodeMap = new HashMap<>(); - for (Program program : programs) { - long seriesRecordingId = seriesRecordingIds.get(program.getSeriesId()); - if (TextUtils.isEmpty(program.getSeasonNumber()) - || TextUtils.isEmpty(program.getEpisodeNumber())) { - // Add all the programs if it doesn't have season number or episode number. - result.get(seriesRecordingId).add(program); - continue; - } - ScheduledEpisode episode = new ScheduledEpisode(seriesRecordingId, - program.getSeasonNumber(), program.getEpisodeNumber()); - List programsForEpisode = programsForEpisodeMap.get(episode); - if (programsForEpisode == null) { - programsForEpisode = new ArrayList<>(); - programsForEpisodeMap.put(episode, programsForEpisode); - } - programsForEpisode.add(program); - } - // Pick one program. - for (Entry> entry : programsForEpisodeMap.entrySet()) { - List programsForEpisode = entry.getValue(); - Collections.sort(programsForEpisode, new Comparator() { - @Override - public int compare(Program lhs, Program rhs) { - // Place the existing schedule first. - boolean lhsScheduled = isProgramScheduled(dataManager, lhs); - boolean rhsScheduled = isProgramScheduled(dataManager, rhs); - if (lhsScheduled && !rhsScheduled) { - return -1; - } - if (!lhsScheduled && rhsScheduled) { - return 1; - } - // Sort by the start time in ascending order. - return lhs.compareTo(rhs); - } - }); - boolean added = false; - // Add all the scheduled programs - List programsForSeries = result.get(entry.getKey().seriesRecordingId); - for (Program program : programsForEpisode) { - if (isProgramScheduled(dataManager, program)) { - programsForSeries.add(program); - added = true; - } else if (!added) { - programsForSeries.add(program); - break; - } - } - } - return result; - } - - private static boolean isProgramScheduled(DvrDataManager dataManager, Program program) { - ScheduledRecording schedule = - dataManager.getScheduledRecordingForProgramId(program.getId()); - return schedule != null && schedule.getState() - == ScheduledRecording.STATE_RECORDING_NOT_STARTED; - } - - private void updateFetchedSeries() { - mSharedPreferences.edit().putStringSet(KEY_FETCHED_SERIES_IDS, mFetchedSeriesIds).apply(); - } - - /** - * This works only for the existing series recordings. Do not use this task for the - * "adding series recording" UI. - */ - private class SeriesRecordingUpdateTask extends EpisodicProgramLoadTask { - SeriesRecordingUpdateTask(List seriesRecordings) { - super(mContext, seriesRecordings); - } - - @Override - protected void onPostExecute(List programs) { - if (DEBUG) Log.d(TAG, "onPostExecute: updating schedules with programs:" + programs); - mScheduleTasks.remove(this); - if (programs == null) { - Log.e(TAG, "Creating schedules for series recording failed: " - + getSeriesRecordings()); - return; - } - LongSparseArray> seriesProgramMap = pickOneProgramPerEpisode( - getSeriesRecordings(), programs); - for (SeriesRecording seriesRecording : getSeriesRecordings()) { - // Check the series recording is still valid. - SeriesRecording actualSeriesRecording = mDataManager.getSeriesRecording( - seriesRecording.getId()); - if (actualSeriesRecording == null || actualSeriesRecording.isStopped()) { - continue; - } - List programsToSchedule = seriesProgramMap.get(seriesRecording.getId()); - if (mDataManager.getSeriesRecording(seriesRecording.getId()) != null - && !programsToSchedule.isEmpty()) { - mDvrManager.addScheduleToSeriesRecording(seriesRecording, programsToSchedule); - } - } - if (!mOnSeriesRecordingUpdatedListeners.isEmpty()) { - for (OnSeriesRecordingUpdatedListener listener - : mOnSeriesRecordingUpdatedListeners) { - listener.onSeriesRecordingUpdated( - SeriesRecording.toArray(getSeriesRecordings())); - } - } - } - - @Override - protected void onCancelled(List programs) { - mScheduleTasks.remove(this); - } - - @Override - public String toString() { - return "SeriesRecordingUpdateTask:{" - + "series_recordings=" + getSeriesRecordings() - + "}"; - } - } - - private class FetchSeriesInfoTask extends AsyncTask { - private SeriesRecording mSeriesRecording; - - FetchSeriesInfoTask(SeriesRecording seriesRecording) { - mSeriesRecording = seriesRecording; - } - - @Override - protected SeriesInfo doInBackground(Void... voids) { - return EpgFetcher.createEpgReader(mContext) - .getSeriesInfo(mSeriesRecording.getSeriesId()); - } - - @Override - protected void onPostExecute(SeriesInfo seriesInfo) { - if (seriesInfo != null) { - mDataManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording) - .setTitle(seriesInfo.getTitle()) - .setDescription(seriesInfo.getDescription()) - .setLongDescription(seriesInfo.getLongDescription()) - .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds()) - .setPosterUri(seriesInfo.getPosterUri()) - .setPhotoUri(seriesInfo.getPhotoUri()) - .build()); - mFetchedSeriesIds.add(seriesInfo.getId()); - updateFetchedSeries(); - } - mFetchSeriesInfoTasks.remove(this); - } - - @Override - protected void onCancelled(SeriesInfo seriesInfo) { - mFetchSeriesInfoTasks.remove(this); - } - } - - /** - * A listener to notify when series recording are updated. - */ - public interface OnSeriesRecordingUpdatedListener { - void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings); - } -} diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java index bf72d912..129ba153 100644 --- a/src/com/android/tv/dvr/WritableDvrDataManager.java +++ b/src/com/android/tv/dvr/WritableDvrDataManager.java @@ -18,7 +18,9 @@ package com.android.tv.dvr; import android.support.annotation.MainThread; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.SeriesRecording; /** * Full data manager. @@ -27,7 +29,7 @@ import com.android.tv.dvr.ScheduledRecording.RecordingState; * for internal use only. Do not call them from UI directly. */ @MainThread -interface WritableDvrDataManager extends DvrDataManager { +public interface WritableDvrDataManager extends DvrDataManager { /** * Adds new recordings. */ diff --git a/src/com/android/tv/dvr/data/IdGenerator.java b/src/com/android/tv/dvr/data/IdGenerator.java new file mode 100644 index 00000000..2ade1dad --- /dev/null +++ b/src/com/android/tv/dvr/data/IdGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.data; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * A class which generate the ID which increases sequentially. + */ +public class IdGenerator { + /** + * ID generator for the scheduled recording. + */ + public static final IdGenerator SCHEDULED_RECORDING = new IdGenerator(); + + /** + * ID generator for the series recording. + */ + public static final IdGenerator SERIES_RECORDING = new IdGenerator(); + + private final AtomicLong mMaxId = new AtomicLong(0); + + /** + * Sets the new maximum ID. + */ + public void setMaxId(long maxId) { + mMaxId.set(maxId); + } + + /** + * Returns the new ID which is greater than the existing maximum ID by 1. + */ + public long newId() { + return mMaxId.incrementAndGet(); + } +} diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java new file mode 100644 index 00000000..2e953a52 --- /dev/null +++ b/src/com/android/tv/dvr/data/RecordedProgram.java @@ -0,0 +1,845 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.data; + +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContentRating; +import android.media.tv.TvContract; +import android.media.tv.TvContract.RecordedPrograms; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.android.tv.common.R; +import com.android.tv.common.TvContentRatingCache; +import com.android.tv.data.BaseProgram; +import com.android.tv.data.GenreItems; +import com.android.tv.data.InternalDataUtils; +import com.android.tv.util.Utils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. + */ +@TargetApi(Build.VERSION_CODES.N) +public class RecordedProgram extends BaseProgram { + public static final int ID_NOT_SET = -1; + + public final static String[] PROJECTION = { + // These are in exactly the order listed in RecordedPrograms + RecordedPrograms._ID, + RecordedPrograms.COLUMN_PACKAGE_NAME, + RecordedPrograms.COLUMN_INPUT_ID, + RecordedPrograms.COLUMN_CHANNEL_ID, + RecordedPrograms.COLUMN_TITLE, + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_SEASON_TITLE, + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_EPISODE_TITLE, + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_BROADCAST_GENRE, + RecordedPrograms.COLUMN_CANONICAL_GENRE, + RecordedPrograms.COLUMN_SHORT_DESCRIPTION, + RecordedPrograms.COLUMN_LONG_DESCRIPTION, + RecordedPrograms.COLUMN_VIDEO_WIDTH, + RecordedPrograms.COLUMN_VIDEO_HEIGHT, + RecordedPrograms.COLUMN_AUDIO_LANGUAGE, + RecordedPrograms.COLUMN_CONTENT_RATING, + RecordedPrograms.COLUMN_POSTER_ART_URI, + RecordedPrograms.COLUMN_THUMBNAIL_URI, + RecordedPrograms.COLUMN_SEARCHABLE, + RecordedPrograms.COLUMN_RECORDING_DATA_URI, + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + RecordedPrograms.COLUMN_VERSION_NUMBER, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + }; + + public static RecordedProgram fromCursor(Cursor cursor) { + int index = 0; + Builder builder = builder() + .setId(cursor.getLong(index++)) + .setPackageName(cursor.getString(index++)) + .setInputId(cursor.getString(index++)) + .setChannelId(cursor.getLong(index++)) + .setTitle(cursor.getString(index++)) + .setSeasonNumber(cursor.getString(index++)) + .setSeasonTitle(cursor.getString(index++)) + .setEpisodeNumber(cursor.getString(index++)) + .setEpisodeTitle(cursor.getString(index++)) + .setStartTimeUtcMillis(cursor.getLong(index++)) + .setEndTimeUtcMillis(cursor.getLong(index++)) + .setBroadcastGenres(cursor.getString(index++)) + .setCanonicalGenres(cursor.getString(index++)) + .setShortDescription(cursor.getString(index++)) + .setLongDescription(cursor.getString(index++)) + .setVideoWidth(cursor.getInt(index++)) + .setVideoHeight(cursor.getInt(index++)) + .setAudioLanguage(cursor.getString(index++)) + .setContentRatings( + TvContentRatingCache.getInstance().getRatings(cursor.getString(index++))) + .setPosterArtUri(cursor.getString(index++)) + .setThumbnailUri(cursor.getString(index++)) + .setSearchable(cursor.getInt(index++) == 1) + .setDataUri(cursor.getString(index++)) + .setDataBytes(cursor.getLong(index++)) + .setDurationMillis(cursor.getLong(index++)) + .setExpireTimeUtcMillis(cursor.getLong(index++)) + .setInternalProviderFlag1(cursor.getInt(index++)) + .setInternalProviderFlag2(cursor.getInt(index++)) + .setInternalProviderFlag3(cursor.getInt(index++)) + .setInternalProviderFlag4(cursor.getInt(index++)) + .setVersionNumber(cursor.getInt(index++)); + if (Utils.isInBundledPackageSet(builder.mPackageName)) { + InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); + } + return builder.build(); + } + + public static ContentValues toValues(RecordedProgram recordedProgram) { + ContentValues values = new ContentValues(); + if (recordedProgram.mId != ID_NOT_SET) { + values.put(RecordedPrograms._ID, recordedProgram.mId); + } + values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId); + values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId); + values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle); + values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber); + values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle); + values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber); + values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle); + values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, + recordedProgram.mStartTimeUtcMillis); + values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis); + values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, + safeEncode(recordedProgram.mBroadcastGenres)); + values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, + safeEncode(recordedProgram.mCanonicalGenres)); + values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription); + values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription); + if (recordedProgram.mVideoWidth == 0) { + values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH); + } else { + values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth); + } + if (recordedProgram.mVideoHeight == 0) { + values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT); + } else { + values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight); + } + values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage); + values.put(RecordedPrograms.COLUMN_CONTENT_RATING, + TvContentRatingCache.contentRatingsToString(recordedProgram.mContentRatings)); + values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri); + values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri); + values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0); + values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, + safeToString(recordedProgram.mDataUri)); + values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes); + values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, + recordedProgram.mDurationMillis); + values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, + recordedProgram.mExpireTimeUtcMillis); + values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + InternalDataUtils.serializeInternalProviderData(recordedProgram)); + values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + recordedProgram.mInternalProviderFlag1); + values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + recordedProgram.mInternalProviderFlag2); + values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + recordedProgram.mInternalProviderFlag3); + values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + recordedProgram.mInternalProviderFlag4); + values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber); + return values; + } + + public static class Builder{ + private long mId = ID_NOT_SET; + private String mPackageName; + private String mInputId; + private long mChannelId; + private String mTitle; + private String mSeriesId; + private String mSeasonNumber; + private String mSeasonTitle; + private String mEpisodeNumber; + private String mEpisodeTitle; + private long mStartTimeUtcMillis; + private long mEndTimeUtcMillis; + private String[] mBroadcastGenres; + private String[] mCanonicalGenres; + private String mShortDescription; + private String mLongDescription; + private int mVideoWidth; + private int mVideoHeight; + private String mAudioLanguage; + private TvContentRating[] mContentRatings; + private String mPosterArtUri; + private String mThumbnailUri; + private boolean mSearchable = true; + private Uri mDataUri; + private long mDataBytes; + private long mDurationMillis; + private long mExpireTimeUtcMillis; + private int mInternalProviderFlag1; + private int mInternalProviderFlag2; + private int mInternalProviderFlag3; + private int mInternalProviderFlag4; + private int mVersionNumber; + + public Builder setId(long id) { + mId = id; + return this; + } + + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + public Builder setInputId(String inputId) { + mInputId = inputId; + return this; + } + + public Builder setChannelId(long channelId) { + mChannelId = channelId; + return this; + } + + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + public Builder setSeriesId(String seriesId) { + mSeriesId = seriesId; + return this; + } + + public Builder setSeasonNumber(String seasonNumber) { + mSeasonNumber = seasonNumber; + return this; + } + + public Builder setSeasonTitle(String seasonTitle) { + mSeasonTitle = seasonTitle; + return this; + } + + public Builder setEpisodeNumber(String episodeNumber) { + mEpisodeNumber = episodeNumber; + return this; + } + + public Builder setEpisodeTitle(String episodeTitle) { + mEpisodeTitle = episodeTitle; + return this; + } + + public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { + mStartTimeUtcMillis = startTimeUtcMillis; + return this; + } + + public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { + mEndTimeUtcMillis = endTimeUtcMillis; + return this; + } + + public Builder setBroadcastGenres(String broadcastGenres) { + if (TextUtils.isEmpty(broadcastGenres)) { + mBroadcastGenres = null; + return this; + } + return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres)); + } + + private Builder setBroadcastGenres(String[] broadcastGenres) { + mBroadcastGenres = broadcastGenres; + return this; + } + + public Builder setCanonicalGenres(String canonicalGenres) { + if (TextUtils.isEmpty(canonicalGenres)) { + mCanonicalGenres = null; + return this; + } + return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres)); + } + + private Builder setCanonicalGenres(String[] canonicalGenres) { + mCanonicalGenres = canonicalGenres; + return this; + } + + public Builder setShortDescription(String shortDescription) { + mShortDescription = shortDescription; + return this; + } + + public Builder setLongDescription(String longDescription) { + mLongDescription = longDescription; + return this; + } + + public Builder setVideoWidth(int videoWidth) { + mVideoWidth = videoWidth; + return this; + } + + public Builder setVideoHeight(int videoHeight) { + mVideoHeight = videoHeight; + return this; + } + + public Builder setAudioLanguage(String audioLanguage) { + mAudioLanguage = audioLanguage; + return this; + } + + public Builder setContentRatings(TvContentRating[] contentRatings) { + mContentRatings = contentRatings; + return this; + } + + private Uri toUri(String uriString) { + try { + return uriString == null ? null : Uri.parse(uriString); + } catch (Exception e) { + return null; + } + } + + public Builder setPosterArtUri(String posterArtUri) { + mPosterArtUri = posterArtUri; + return this; + } + + public Builder setThumbnailUri(String thumbnailUri) { + mThumbnailUri = thumbnailUri; + return this; + } + + public Builder setSearchable(boolean searchable) { + mSearchable = searchable; + return this; + } + + public Builder setDataUri(String dataUri) { + return setDataUri(toUri(dataUri)); + } + + public Builder setDataUri(Uri dataUri) { + mDataUri = dataUri; + return this; + } + + public Builder setDataBytes(long dataBytes) { + mDataBytes = dataBytes; + return this; + } + + public Builder setDurationMillis(long durationMillis) { + mDurationMillis = durationMillis; + return this; + } + + public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) { + mExpireTimeUtcMillis = expireTimeUtcMillis; + return this; + } + + public Builder setInternalProviderFlag1(int internalProviderFlag1) { + mInternalProviderFlag1 = internalProviderFlag1; + return this; + } + + public Builder setInternalProviderFlag2(int internalProviderFlag2) { + mInternalProviderFlag2 = internalProviderFlag2; + return this; + } + + public Builder setInternalProviderFlag3(int internalProviderFlag3) { + mInternalProviderFlag3 = internalProviderFlag3; + return this; + } + + public Builder setInternalProviderFlag4(int internalProviderFlag4) { + mInternalProviderFlag4 = internalProviderFlag4; + return this; + } + + public Builder setVersionNumber(int versionNumber) { + mVersionNumber = versionNumber; + return this; + } + + public RecordedProgram build() { + if (TextUtils.isEmpty(mTitle)) { + // If title is null, series cannot be generated for this program. + setSeriesId(null); + } else if (TextUtils.isEmpty(mSeriesId) && !TextUtils.isEmpty(mEpisodeNumber)) { + // If series ID is not set, generate it for the episodic program of other TV input. + setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle)); + } + return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId, + mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis, + mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription, + mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRatings, + mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes, + mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1, + mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4, + mVersionNumber); + } + } + + public static Builder builder() { return new Builder(); } + + public static Builder buildFrom(RecordedProgram orig) { + return builder() + .setId(orig.getId()) + .setPackageName(orig.getPackageName()) + .setInputId(orig.getInputId()) + .setChannelId(orig.getChannelId()) + .setTitle(orig.getTitle()) + .setSeriesId(orig.getSeriesId()) + .setSeasonNumber(orig.getSeasonNumber()) + .setSeasonTitle(orig.getSeasonTitle()) + .setEpisodeNumber(orig.getEpisodeNumber()) + .setEpisodeTitle(orig.getEpisodeTitle()) + .setStartTimeUtcMillis(orig.getStartTimeUtcMillis()) + .setEndTimeUtcMillis(orig.getEndTimeUtcMillis()) + .setBroadcastGenres(orig.getBroadcastGenres()) + .setCanonicalGenres(orig.getCanonicalGenres()) + .setShortDescription(orig.getDescription()) + .setLongDescription(orig.getLongDescription()) + .setVideoWidth(orig.getVideoWidth()) + .setVideoHeight(orig.getVideoHeight()) + .setAudioLanguage(orig.getAudioLanguage()) + .setContentRatings(orig.getContentRatings()) + .setPosterArtUri(orig.getPosterArtUri()) + .setThumbnailUri(orig.getThumbnailUri()) + .setSearchable(orig.isSearchable()) + .setInternalProviderFlag1(orig.getInternalProviderFlag1()) + .setInternalProviderFlag2(orig.getInternalProviderFlag2()) + .setInternalProviderFlag3(orig.getInternalProviderFlag3()) + .setInternalProviderFlag4(orig.getInternalProviderFlag4()) + .setVersionNumber(orig.getVersionNumber()); + } + + public static final Comparator START_TIME_THEN_ID_COMPARATOR = + new Comparator() { + @Override + public int compare(RecordedProgram lhs, RecordedProgram rhs) { + int res = + Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis()); + if (res != 0) { + return res; + } + return Long.compare(lhs.mId, rhs.mId); + } + }; + + private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); + + private final long mId; + private final String mPackageName; + private final String mInputId; + private final long mChannelId; + private final String mTitle; + private final String mSeriesId; + private final String mSeasonNumber; + private final String mSeasonTitle; + private final String mEpisodeNumber; + private final String mEpisodeTitle; + private final long mStartTimeUtcMillis; + private final long mEndTimeUtcMillis; + private final String[] mBroadcastGenres; + private final String[] mCanonicalGenres; + private final String mShortDescription; + private final String mLongDescription; + private final int mVideoWidth; + private final int mVideoHeight; + private final String mAudioLanguage; + private final TvContentRating[] mContentRatings; + private final String mPosterArtUri; + private final String mThumbnailUri; + private final boolean mSearchable; + private final Uri mDataUri; + private final long mDataBytes; + private final long mDurationMillis; + private final long mExpireTimeUtcMillis; + private final int mInternalProviderFlag1; + private final int mInternalProviderFlag2; + private final int mInternalProviderFlag3; + private final int mInternalProviderFlag4; + private final int mVersionNumber; + + private RecordedProgram(long id, String packageName, String inputId, long channelId, + String title, String seriesId, String seasonNumber, String seasonTitle, + String episodeNumber, String episodeTitle, long startTimeUtcMillis, + long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres, + String shortDescription, String longDescription, int videoWidth, int videoHeight, + String audioLanguage, TvContentRating[] contentRatings, String posterArtUri, + String thumbnailUri, boolean searchable, Uri dataUri, long dataBytes, + long durationMillis, long expireTimeUtcMillis, int internalProviderFlag1, + int internalProviderFlag2, int internalProviderFlag3, int internalProviderFlag4, + int versionNumber) { + mId = id; + mPackageName = packageName; + mInputId = inputId; + mChannelId = channelId; + mTitle = title; + mSeriesId = seriesId; + mSeasonNumber = seasonNumber; + mSeasonTitle = seasonTitle; + mEpisodeNumber = episodeNumber; + mEpisodeTitle = episodeTitle; + mStartTimeUtcMillis = startTimeUtcMillis; + mEndTimeUtcMillis = endTimeUtcMillis; + mBroadcastGenres = broadcastGenres; + mCanonicalGenres = canonicalGenres; + mShortDescription = shortDescription; + mLongDescription = longDescription; + mVideoWidth = videoWidth; + mVideoHeight = videoHeight; + + mAudioLanguage = audioLanguage; + mContentRatings = contentRatings; + mPosterArtUri = posterArtUri; + mThumbnailUri = thumbnailUri; + mSearchable = searchable; + mDataUri = dataUri; + mDataBytes = dataBytes; + mDurationMillis = durationMillis; + mExpireTimeUtcMillis = expireTimeUtcMillis; + mInternalProviderFlag1 = internalProviderFlag1; + mInternalProviderFlag2 = internalProviderFlag2; + mInternalProviderFlag3 = internalProviderFlag3; + mInternalProviderFlag4 = internalProviderFlag4; + mVersionNumber = versionNumber; + } + + public String getAudioLanguage() { + return mAudioLanguage; + } + + public String[] getBroadcastGenres() { + return mBroadcastGenres; + } + + public String[] getCanonicalGenres() { + return mCanonicalGenres; + } + + /** + * Returns array of canonical genre ID's for this recorded program. + */ + @Override + public int[] getCanonicalGenreIds() { + if (mCanonicalGenres == null) { + return null; + } + int[] genreIds = new int[mCanonicalGenres.length]; + for (int i = 0; i < mCanonicalGenres.length; i++) { + genreIds[i] = GenreItems.getId(mCanonicalGenres[i]); + } + return genreIds; + } + + @Override + public long getChannelId() { + return mChannelId; + } + + @Nullable + @Override + public TvContentRating[] getContentRatings() { + return mContentRatings; + } + + public Uri getDataUri() { + return mDataUri; + } + + public long getDataBytes() { + return mDataBytes; + } + + @Override + public long getDurationMillis() { + return mDurationMillis; + } + + @Override + public long getEndTimeUtcMillis() { + return mEndTimeUtcMillis; + } + + @Override + public String getEpisodeNumber() { + return mEpisodeNumber; + } + + @Override + public String getEpisodeTitle() { + return mEpisodeTitle; + } + + @Nullable + public String getEpisodeDisplayNumber(Context context) { + if (!TextUtils.isEmpty(mEpisodeNumber)) { + if (TextUtils.equals(mSeasonNumber, "0")) { + // Do not show "S0: ". + return String.format(context.getResources().getString( + R.string.display_episode_number_format_no_season_number), mEpisodeNumber); + } else { + return String.format(context.getResources().getString( + R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber); + } + } + return null; + } + + public long getExpireTimeUtcMillis() { + return mExpireTimeUtcMillis; + } + + public long getId() { + return mId; + } + + public String getPackageName() { + return mPackageName; + } + + public String getInputId() { + return mInputId; + } + + public int getInternalProviderFlag1() { + return mInternalProviderFlag1; + } + + public int getInternalProviderFlag2() { + return mInternalProviderFlag2; + } + + public int getInternalProviderFlag3() { + return mInternalProviderFlag3; + } + + public int getInternalProviderFlag4() { + return mInternalProviderFlag4; + } + + @Override + public String getDescription() { + return mShortDescription; + } + + @Override + public String getLongDescription() { + return mLongDescription; + } + + @Override + public String getPosterArtUri() { + return mPosterArtUri; + } + + @Override + public boolean isValid() { + return true; + } + + public boolean isSearchable() { + return mSearchable; + } + + @Override + public String getSeriesId() { + return mSeriesId; + } + + @Override + public String getSeasonNumber() { + return mSeasonNumber; + } + + public String getSeasonTitle() { + return mSeasonTitle; + } + + @Override + public long getStartTimeUtcMillis() { + return mStartTimeUtcMillis; + } + + @Override + public String getThumbnailUri() { + return mThumbnailUri; + } + + @Override + public String getTitle() { + return mTitle; + } + + public Uri getUri() { + return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId); + } + + public int getVersionNumber() { + return mVersionNumber; + } + + public int getVideoHeight() { + return mVideoHeight; + } + + public int getVideoWidth() { + return mVideoWidth; + } + + /** + * Checks whether the recording has been clipped or not. + */ + public boolean isClipped() { + return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RecordedProgram that = (RecordedProgram) o; + return Objects.equals(mId, that.mId) && + Objects.equals(mChannelId, that.mChannelId) && + Objects.equals(mSeriesId, that.mSeriesId) && + Objects.equals(mSeasonNumber, that.mSeasonNumber) && + Objects.equals(mSeasonTitle, that.mSeasonTitle) && + Objects.equals(mEpisodeNumber, that.mEpisodeNumber) && + Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) && + Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) && + Objects.equals(mVideoWidth, that.mVideoWidth) && + Objects.equals(mVideoHeight, that.mVideoHeight) && + Objects.equals(mSearchable, that.mSearchable) && + Objects.equals(mDataBytes, that.mDataBytes) && + Objects.equals(mDurationMillis, that.mDurationMillis) && + Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) && + Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) && + Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) && + Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) && + Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) && + Objects.equals(mVersionNumber, that.mVersionNumber) && + Objects.equals(mTitle, that.mTitle) && + Objects.equals(mEpisodeTitle, that.mEpisodeTitle) && + Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) && + Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) && + Objects.equals(mShortDescription, that.mShortDescription) && + Objects.equals(mLongDescription, that.mLongDescription) && + Objects.equals(mAudioLanguage, that.mAudioLanguage) && + Arrays.equals(mContentRatings, that.mContentRatings) && + Objects.equals(mPosterArtUri, that.mPosterArtUri) && + Objects.equals(mThumbnailUri, that.mThumbnailUri); + } + + /** + * Hashes based on the ID. + */ + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public String toString() { + return "RecordedProgram" + + "[" + mId + + "]{ mPackageName=" + mPackageName + + ", mInputId='" + mInputId + '\'' + + ", mChannelId='" + mChannelId + '\'' + + ", mTitle='" + mTitle + '\'' + + ", mSeriesId='" + mSeriesId + '\'' + + ", mEpisodeNumber=" + mEpisodeNumber + + ", mEpisodeTitle='" + mEpisodeTitle + '\'' + + ", mStartTimeUtcMillis=" + mStartTimeUtcMillis + + ", mEndTimeUtcMillis=" + mEndTimeUtcMillis + + ", mBroadcastGenres=" + + (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") + + ", mCanonicalGenres=" + + (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") + + ", mShortDescription='" + mShortDescription + '\'' + + ", mLongDescription='" + mLongDescription + '\'' + + ", mVideoHeight=" + mVideoHeight + + ", mVideoWidth=" + mVideoWidth + + ", mAudioLanguage='" + mAudioLanguage + '\'' + + ", mContentRatings='" + + TvContentRatingCache.contentRatingsToString(mContentRatings) + '\'' + + ", mPosterArtUri=" + mPosterArtUri + + ", mThumbnailUri=" + mThumbnailUri + + ", mSearchable=" + mSearchable + + ", mDataUri=" + mDataUri + + ", mDataBytes=" + mDataBytes + + ", mDurationMillis=" + mDurationMillis + + ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis + + ", mInternalProviderFlag1=" + mInternalProviderFlag1 + + ", mInternalProviderFlag2=" + mInternalProviderFlag2 + + ", mInternalProviderFlag3=" + mInternalProviderFlag3 + + ", mInternalProviderFlag4=" + mInternalProviderFlag4 + + ", mSeasonNumber=" + mSeasonNumber + + ", mSeasonTitle=" + mSeasonTitle + + ", mVersionNumber=" + mVersionNumber + + '}'; + } + + @Nullable + private static String safeToString(@Nullable Object o) { + return o == null ? null : o.toString(); + } + + @Nullable + private static String safeEncode(@Nullable String[] genres) { + return genres == null ? null : TvContract.Programs.Genres.encode(genres); + } + + /** + * Returns an array containing all of the elements in the list. + */ + public static RecordedProgram[] toArray(Collection recordedPrograms) { + return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]); + } +} diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java new file mode 100644 index 00000000..5d11c0f3 --- /dev/null +++ b/src/com/android/tv/dvr/data/ScheduledRecording.java @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.IntDef; +import android.text.TextUtils; +import android.util.Range; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Channel; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.provider.DvrContract.Schedules; +import com.android.tv.util.CompositeComparator; +import com.android.tv.util.Utils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Comparator; +import java.util.Objects; + +/** + * A data class for one recording contents. + */ +public final class ScheduledRecording implements Parcelable { + private static final String TAG = "ScheduledRecording"; + + /** + * Indicates that the ID is not assigned yet. + */ + public static final long ID_NOT_SET = 0; + + /** + * The default priority of the recording. + */ + public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; + + /** + * Compares the start time in ascending order. + */ + public static final Comparator START_TIME_COMPARATOR + = new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs); + } + }; + + /** + * Compares the end time in ascending order. + */ + public static final Comparator END_TIME_COMPARATOR + = new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs); + } + }; + + /** + * Compares ID in ascending order. The schedule with the larger ID was created later. + */ + public static final Comparator ID_COMPARATOR + = new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mId, rhs.mId); + } + }; + + /** + * Compares the priority in ascending order. + */ + public static final Comparator PRIORITY_COMPARATOR + = new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mPriority, rhs.mPriority); + } + }; + + /** + * Compares start time in ascending order and then priority in descending order and then ID in + * descending order. + */ + public static final Comparator START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR + = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(), + ID_COMPARATOR.reversed()); + + /** + * Builds scheduled recordings from programs. + */ + public static Builder builder(String inputId, Program p) { + return new Builder() + .setInputId(inputId) + .setChannelId(p.getChannelId()) + .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis()) + .setProgramId(p.getId()) + .setProgramTitle(p.getTitle()) + .setSeasonNumber(p.getSeasonNumber()) + .setEpisodeNumber(p.getEpisodeNumber()) + .setEpisodeTitle(p.getEpisodeTitle()) + .setProgramDescription(p.getDescription()) + .setProgramLongDescription(p.getLongDescription()) + .setProgramPosterArtUri(p.getPosterArtUri()) + .setProgramThumbnailUri(p.getThumbnailUri()) + .setType(TYPE_PROGRAM); + } + + public static Builder builder(String inputId, long channelId, long startTime, long endTime) { + return new Builder() + .setInputId(inputId) + .setChannelId(channelId) + .setStartTimeMs(startTime) + .setEndTimeMs(endTime) + .setType(TYPE_TIMED); + } + + /** + * Creates a new Builder with the values set from the {@link RecordedProgram}. + */ + public static Builder builder(RecordedProgram p) { + boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle()); + return new Builder() + .setInputId(p.getInputId()) + .setChannelId(p.getChannelId()) + .setType(isProgramRecording ? TYPE_PROGRAM : TYPE_TIMED) + .setStartTimeMs(p.getStartTimeUtcMillis()) + .setEndTimeMs(p.getEndTimeUtcMillis()) + .setProgramTitle(p.getTitle()) + .setSeasonNumber(p.getSeasonNumber()) + .setEpisodeNumber(p.getEpisodeNumber()) + .setEpisodeTitle(p.getEpisodeTitle()) + .setProgramDescription(p.getDescription()) + .setProgramLongDescription(p.getLongDescription()) + .setProgramPosterArtUri(p.getPosterArtUri()) + .setProgramThumbnailUri(p.getThumbnailUri()) + .setState(STATE_RECORDING_FINISHED); + } + + public static final class Builder { + private long mId = ID_NOT_SET; + private long mPriority = DvrScheduleManager.DEFAULT_PRIORITY; + private String mInputId; + private long mChannelId; + private long mProgramId = ID_NOT_SET; + private String mProgramTitle; + private @RecordingType int mType; + private long mStartTimeMs; + private long mEndTimeMs; + private String mSeasonNumber; + private String mEpisodeNumber; + private String mEpisodeTitle; + private String mProgramDescription; + private String mProgramLongDescription; + private String mProgramPosterArtUri; + private String mProgramThumbnailUri; + private @RecordingState int mState; + private long mSeriesRecordingId = ID_NOT_SET; + + private Builder() { } + + public Builder setId(long id) { + mId = id; + return this; + } + + public Builder setPriority(long priority) { + mPriority = priority; + return this; + } + + public Builder setInputId(String inputId) { + mInputId = inputId; + return this; + } + + public Builder setChannelId(long channelId) { + mChannelId = channelId; + return this; + } + + public Builder setProgramId(long programId) { + mProgramId = programId; + return this; + } + + public Builder setProgramTitle(String programTitle) { + mProgramTitle = programTitle; + return this; + } + + private Builder setType(@RecordingType int type) { + mType = type; + return this; + } + + public Builder setStartTimeMs(long startTimeMs) { + mStartTimeMs = startTimeMs; + return this; + } + + public Builder setEndTimeMs(long endTimeMs) { + mEndTimeMs = endTimeMs; + return this; + } + + public Builder setSeasonNumber(String seasonNumber) { + mSeasonNumber = seasonNumber; + return this; + } + + public Builder setEpisodeNumber(String episodeNumber) { + mEpisodeNumber = episodeNumber; + return this; + } + + public Builder setEpisodeTitle(String episodeTitle) { + mEpisodeTitle = episodeTitle; + return this; + } + + public Builder setProgramDescription(String description) { + mProgramDescription = description; + return this; + } + + public Builder setProgramLongDescription(String longDescription) { + mProgramLongDescription = longDescription; + return this; + } + + public Builder setProgramPosterArtUri(String programPosterArtUri) { + mProgramPosterArtUri = programPosterArtUri; + return this; + } + + public Builder setProgramThumbnailUri(String programThumbnailUri) { + mProgramThumbnailUri = programThumbnailUri; + return this; + } + + public Builder setState(@RecordingState int state) { + mState = state; + return this; + } + + public Builder setSeriesRecordingId(long seriesRecordingId) { + mSeriesRecordingId = seriesRecordingId; + return this; + } + + public ScheduledRecording build() { + return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId, + mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, + mEpisodeTitle, mProgramDescription, mProgramLongDescription, + mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId); + } + } + + /** + * Creates {@link Builder} object from the given original {@code Recording}. + */ + public static Builder buildFrom(ScheduledRecording orig) { + return new Builder() + .setId(orig.mId) + .setInputId(orig.mInputId) + .setChannelId(orig.mChannelId) + .setEndTimeMs(orig.mEndTimeMs) + .setSeriesRecordingId(orig.mSeriesRecordingId) + .setPriority(orig.mPriority) + .setProgramId(orig.mProgramId) + .setProgramTitle(orig.mProgramTitle) + .setStartTimeMs(orig.mStartTimeMs) + .setSeasonNumber(orig.getSeasonNumber()) + .setEpisodeNumber(orig.getEpisodeNumber()) + .setEpisodeTitle(orig.getEpisodeTitle()) + .setProgramDescription(orig.getProgramDescription()) + .setProgramLongDescription(orig.getProgramLongDescription()) + .setProgramPosterArtUri(orig.getProgramPosterArtUri()) + .setProgramThumbnailUri(orig.getProgramThumbnailUri()) + .setState(orig.mState).setType(orig.mType); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED, + STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED, + STATE_RECORDING_CANCELED}) + public @interface RecordingState {} + public static final int STATE_RECORDING_NOT_STARTED = 0; + public static final int STATE_RECORDING_IN_PROGRESS = 1; + public static final int STATE_RECORDING_FINISHED = 2; + public static final int STATE_RECORDING_FAILED = 3; + public static final int STATE_RECORDING_CLIPPED = 4; + public static final int STATE_RECORDING_DELETED = 5; + public static final int STATE_RECORDING_CANCELED = 6; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_TIMED, TYPE_PROGRAM}) + public @interface RecordingType {} + /** + * Record with given time range. + */ + public static final int TYPE_TIMED = 1; + /** + * Record with a given program. + */ + public static final int TYPE_PROGRAM = 2; + + @RecordingType private final int mType; + + /** + * Use this projection if you want to create {@link ScheduledRecording} object using + * {@link #fromCursor}. + */ + public static final String[] PROJECTION = { + // Columns must match what is read in #fromCursor + Schedules._ID, + Schedules.COLUMN_PRIORITY, + Schedules.COLUMN_TYPE, + Schedules.COLUMN_INPUT_ID, + Schedules.COLUMN_CHANNEL_ID, + Schedules.COLUMN_PROGRAM_ID, + Schedules.COLUMN_PROGRAM_TITLE, + Schedules.COLUMN_START_TIME_UTC_MILLIS, + Schedules.COLUMN_END_TIME_UTC_MILLIS, + Schedules.COLUMN_SEASON_NUMBER, + Schedules.COLUMN_EPISODE_NUMBER, + Schedules.COLUMN_EPISODE_TITLE, + Schedules.COLUMN_PROGRAM_DESCRIPTION, + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, + Schedules.COLUMN_PROGRAM_POST_ART_URI, + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, + Schedules.COLUMN_STATE, + Schedules.COLUMN_SERIES_RECORDING_ID}; + + /** + * Creates {@link ScheduledRecording} object from the given {@link Cursor}. + */ + public static ScheduledRecording fromCursor(Cursor c) { + int index = -1; + return new Builder() + .setId(c.getLong(++index)) + .setPriority(c.getLong(++index)) + .setType(recordingType(c.getString(++index))) + .setInputId(c.getString(++index)) + .setChannelId(c.getLong(++index)) + .setProgramId(c.getLong(++index)) + .setProgramTitle(c.getString(++index)) + .setStartTimeMs(c.getLong(++index)) + .setEndTimeMs(c.getLong(++index)) + .setSeasonNumber(c.getString(++index)) + .setEpisodeNumber(c.getString(++index)) + .setEpisodeTitle(c.getString(++index)) + .setProgramDescription(c.getString(++index)) + .setProgramLongDescription(c.getString(++index)) + .setProgramPosterArtUri(c.getString(++index)) + .setProgramThumbnailUri(c.getString(++index)) + .setState(recordingState(c.getString(++index))) + .setSeriesRecordingId(c.getLong(++index)) + .build(); + } + + public static ContentValues toContentValues(ScheduledRecording r) { + ContentValues values = new ContentValues(); + if (r.getId() != ID_NOT_SET) { + values.put(Schedules._ID, r.getId()); + } + values.put(Schedules.COLUMN_INPUT_ID, r.getInputId()); + values.put(Schedules.COLUMN_CHANNEL_ID, r.getChannelId()); + values.put(Schedules.COLUMN_PROGRAM_ID, r.getProgramId()); + values.put(Schedules.COLUMN_PROGRAM_TITLE, r.getProgramTitle()); + values.put(Schedules.COLUMN_PRIORITY, r.getPriority()); + values.put(Schedules.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs()); + values.put(Schedules.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs()); + values.put(Schedules.COLUMN_SEASON_NUMBER, r.getSeasonNumber()); + values.put(Schedules.COLUMN_EPISODE_NUMBER, r.getEpisodeNumber()); + values.put(Schedules.COLUMN_EPISODE_TITLE, r.getEpisodeTitle()); + values.put(Schedules.COLUMN_PROGRAM_DESCRIPTION, r.getProgramDescription()); + values.put(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, r.getProgramLongDescription()); + values.put(Schedules.COLUMN_PROGRAM_POST_ART_URI, r.getProgramPosterArtUri()); + values.put(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, r.getProgramThumbnailUri()); + values.put(Schedules.COLUMN_STATE, recordingState(r.getState())); + values.put(Schedules.COLUMN_TYPE, recordingType(r.getType())); + if (r.getSeriesRecordingId() != ID_NOT_SET) { + values.put(Schedules.COLUMN_SERIES_RECORDING_ID, r.getSeriesRecordingId()); + } else { + values.putNull(Schedules.COLUMN_SERIES_RECORDING_ID); + } + return values; + } + + public static ScheduledRecording fromParcel(Parcel in) { + return new Builder() + .setId(in.readLong()) + .setPriority(in.readLong()) + .setInputId(in.readString()) + .setChannelId(in.readLong()) + .setProgramId(in.readLong()) + .setProgramTitle(in.readString()) + .setType(in.readInt()) + .setStartTimeMs(in.readLong()) + .setEndTimeMs(in.readLong()) + .setSeasonNumber(in.readString()) + .setEpisodeNumber(in.readString()) + .setEpisodeTitle(in.readString()) + .setProgramDescription(in.readString()) + .setProgramLongDescription(in.readString()) + .setProgramPosterArtUri(in.readString()) + .setProgramThumbnailUri(in.readString()) + .setState(in.readInt()) + .setSeriesRecordingId(in.readLong()) + .build(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ScheduledRecording createFromParcel(Parcel in) { + return ScheduledRecording.fromParcel(in); + } + + @Override + public ScheduledRecording[] newArray(int size) { + return new ScheduledRecording[size]; + } + }; + + /** + * The ID internal to Live TV + */ + private long mId; + + /** + * The priority of this recording. + * + *

The highest number is recorded first. If there is a tie in priority then the higher id + * wins. + */ + private final long mPriority; + + private final String mInputId; + private final long mChannelId; + /** + * Optional id of the associated program. + */ + private final long mProgramId; + private final String mProgramTitle; + + private final long mStartTimeMs; + private final long mEndTimeMs; + private final String mSeasonNumber; + private final String mEpisodeNumber; + private final String mEpisodeTitle; + private final String mProgramDescription; + private final String mProgramLongDescription; + private final String mProgramPosterArtUri; + private final String mProgramThumbnailUri; + @RecordingState private final int mState; + private final long mSeriesRecordingId; + + private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId, + String programTitle, @RecordingType int type, long startTime, long endTime, + String seasonNumber, String episodeNumber, String episodeTitle, + String programDescription, String programLongDescription, String programPosterArtUri, + String programThumbnailUri, @RecordingState int state, long seriesRecordingId) { + mId = id; + mPriority = priority; + mInputId = inputId; + mChannelId = channelId; + mProgramId = programId; + mProgramTitle = programTitle; + mType = type; + mStartTimeMs = startTime; + mEndTimeMs = endTime; + mSeasonNumber = seasonNumber; + mEpisodeNumber = episodeNumber; + mEpisodeTitle = episodeTitle; + mProgramDescription = programDescription; + mProgramLongDescription = programLongDescription; + mProgramPosterArtUri = programPosterArtUri; + mProgramThumbnailUri = programThumbnailUri; + mState = state; + mSeriesRecordingId = seriesRecordingId; + } + + /** + * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and + * {@link #TYPE_TIMED}. + */ + @RecordingType + public int getType() { + return mType; + } + + /** + * Returns schedules' input id. + */ + public String getInputId() { + return mInputId; + } + + /** + * Returns recorded {@link Channel}. + */ + public long getChannelId() { + return mChannelId; + } + + /** + * Return the optional program id + */ + public long getProgramId() { + return mProgramId; + } + + /** + * Return the optional program Title + */ + public String getProgramTitle() { + return mProgramTitle; + } + + /** + * Returns started time. + */ + public long getStartTimeMs() { + return mStartTimeMs; + } + + /** + * Returns ended time. + */ + public long getEndTimeMs() { + return mEndTimeMs; + } + + /** + * Returns the season number. + */ + public String getSeasonNumber() { + return mSeasonNumber; + } + + /** + * Returns the episode number. + */ + public String getEpisodeNumber() { + return mEpisodeNumber; + } + + /** + * Returns the episode title. + */ + public String getEpisodeTitle() { + return mEpisodeTitle; + } + + /** + * Returns the description of program. + */ + public String getProgramDescription() { + return mProgramDescription; + } + + /** + * Returns the long description of program. + */ + public String getProgramLongDescription() { + return mProgramLongDescription; + } + + /** + * Returns the poster uri of program. + */ + public String getProgramPosterArtUri() { + return mProgramPosterArtUri; + } + + /** + * Returns the thumb nail uri of program. + */ + public String getProgramThumbnailUri() { + return mProgramThumbnailUri; + } + + /** + * Returns duration. + */ + public long getDuration() { + return mEndTimeMs - mStartTimeMs; + } + + /** + * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED}, + * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, + * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and + * {@link #STATE_RECORDING_DELETED}. + */ + @RecordingState public int getState() { + return mState; + } + + /** + * Returns the ID of the {@link SeriesRecording} including this schedule. + */ + public long getSeriesRecordingId() { + return mSeriesRecordingId; + } + + public long getId() { + return mId; + } + + /** + * Sets the ID; + */ + public void setId(long id) { + mId = id; + } + + public long getPriority() { + return mPriority; + } + + /** + * Returns season number, episode number and episode title for display. + */ + public String getEpisodeDisplayTitle(Context context) { + if (!TextUtils.isEmpty(mEpisodeNumber)) { + String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; + if (TextUtils.equals(mSeasonNumber, "0")) { + // Do not show "S0: ". + return String.format(context.getResources().getString( + R.string.display_episode_title_format_no_season_number), + mEpisodeNumber, episodeTitle); + } else { + return String.format(context.getResources().getString( + R.string.display_episode_title_format), + mSeasonNumber, mEpisodeNumber, episodeTitle); + } + } + return mEpisodeTitle; + } + + /** + * Returns the program's display title, if the program title is not null, returns program title. + * Otherwise returns the channel name. + */ + public String getProgramDisplayTitle(Context context) { + if (!TextUtils.isEmpty(mProgramTitle)) { + return mProgramTitle; + } + Channel channel = TvApplication.getSingletons(context).getChannelDataManager() + .getChannel(mChannelId); + return channel != null ? channel.getDisplayName() + : context.getString(R.string.no_program_information); + } + + /** + * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. + */ + private static @RecordingType int recordingType(String type) { + switch (type) { + case Schedules.TYPE_TIMED: + return TYPE_TIMED; + case Schedules.TYPE_PROGRAM: + return TYPE_PROGRAM; + default: + SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); + return TYPE_TIMED; + } + } + + /** + * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}. + */ + private static String recordingType(@RecordingType int type) { + switch (type) { + case TYPE_TIMED: + return Schedules.TYPE_TIMED; + case TYPE_PROGRAM: + return Schedules.TYPE_PROGRAM; + default: + SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); + return Schedules.TYPE_TIMED; + } + } + + /** + * Converts a string to a @RecordingState int, defaulting to + * {@link #STATE_RECORDING_NOT_STARTED}. + */ + private static @RecordingState int recordingState(String state) { + switch (state) { + case Schedules.STATE_RECORDING_NOT_STARTED: + return STATE_RECORDING_NOT_STARTED; + case Schedules.STATE_RECORDING_IN_PROGRESS: + return STATE_RECORDING_IN_PROGRESS; + case Schedules.STATE_RECORDING_FINISHED: + return STATE_RECORDING_FINISHED; + case Schedules.STATE_RECORDING_FAILED: + return STATE_RECORDING_FAILED; + case Schedules.STATE_RECORDING_CLIPPED: + return STATE_RECORDING_CLIPPED; + case Schedules.STATE_RECORDING_DELETED: + return STATE_RECORDING_DELETED; + case Schedules.STATE_RECORDING_CANCELED: + return STATE_RECORDING_CANCELED; + default: + SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); + return STATE_RECORDING_NOT_STARTED; + } + } + + /** + * Converts a @RecordingState int to string, defaulting to + * {@link Schedules#STATE_RECORDING_NOT_STARTED}. + */ + private static String recordingState(@RecordingState int state) { + switch (state) { + case STATE_RECORDING_NOT_STARTED: + return Schedules.STATE_RECORDING_NOT_STARTED; + case STATE_RECORDING_IN_PROGRESS: + return Schedules.STATE_RECORDING_IN_PROGRESS; + case STATE_RECORDING_FINISHED: + return Schedules.STATE_RECORDING_FINISHED; + case STATE_RECORDING_FAILED: + return Schedules.STATE_RECORDING_FAILED; + case STATE_RECORDING_CLIPPED: + return Schedules.STATE_RECORDING_CLIPPED; + case STATE_RECORDING_DELETED: + return Schedules.STATE_RECORDING_DELETED; + case STATE_RECORDING_CANCELED: + return Schedules.STATE_RECORDING_CANCELED; + default: + SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); + return Schedules.STATE_RECORDING_NOT_STARTED; + } + } + + /** + * Checks if the {@code period} overlaps with the recording time. + */ + public boolean isOverLapping(Range period) { + return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower(); + } + + /** + * Checks if the {@code schedule} overlaps with this schedule. + */ + public boolean isOverLapping(ScheduledRecording schedule) { + return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs(); + } + + @Override + public String toString() { + return "ScheduledRecording[" + mId + + "]" + + "(inputId=" + mInputId + + ",channelId=" + mChannelId + + ",programId=" + mProgramId + + ",programTitle=" + mProgramTitle + + ",type=" + mType + + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")" + + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")" + + ",seasonNumber=" + mSeasonNumber + + ",episodeNumber=" + mEpisodeNumber + + ",episodeTitle=" + mEpisodeTitle + + ",programDescription=" + mProgramDescription + + ",programLongDescription=" + mProgramLongDescription + + ",programPosterArtUri=" + mProgramPosterArtUri + + ",programThumbnailUri=" + mProgramThumbnailUri + + ",state=" + mState + + ",priority=" + mPriority + + ",seriesRecordingId=" + mSeriesRecordingId + + ")"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int paramInt) { + out.writeLong(mId); + out.writeLong(mPriority); + out.writeString(mInputId); + out.writeLong(mChannelId); + out.writeLong(mProgramId); + out.writeString(mProgramTitle); + out.writeInt(mType); + out.writeLong(mStartTimeMs); + out.writeLong(mEndTimeMs); + out.writeString(mSeasonNumber); + out.writeString(mEpisodeNumber); + out.writeString(mEpisodeTitle); + out.writeString(mProgramDescription); + out.writeString(mProgramLongDescription); + out.writeString(mProgramPosterArtUri); + out.writeString(mProgramThumbnailUri); + out.writeInt(mState); + out.writeLong(mSeriesRecordingId); + } + + /** + * Returns {@code true} if the recording is not started yet, otherwise @{code false}. + */ + public boolean isNotStarted() { + return mState == STATE_RECORDING_NOT_STARTED; + } + + /** + * Returns {@code true} if the recording is in progress, otherwise @{code false}. + */ + public boolean isInProgress() { + return mState == STATE_RECORDING_IN_PROGRESS; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ScheduledRecording)) { + return false; + } + ScheduledRecording r = (ScheduledRecording) obj; + return mId == r.mId + && mPriority == r.mPriority + && mChannelId == r.mChannelId + && mProgramId == r.mProgramId + && Objects.equals(mProgramTitle, r.mProgramTitle) + && mType == r.mType + && mStartTimeMs == r.mStartTimeMs + && mEndTimeMs == r.mEndTimeMs + && Objects.equals(mSeasonNumber, r.mSeasonNumber) + && Objects.equals(mEpisodeNumber, r.mEpisodeNumber) + && Objects.equals(mEpisodeTitle, r.mEpisodeTitle) + && Objects.equals(mProgramDescription, r.getProgramDescription()) + && Objects.equals(mProgramLongDescription, r.getProgramLongDescription()) + && Objects.equals(mProgramPosterArtUri, r.getProgramPosterArtUri()) + && Objects.equals(mProgramThumbnailUri, r.getProgramThumbnailUri()) + && mState == r.mState + && mSeriesRecordingId == r.mSeriesRecordingId; + } + + @Override + public int hashCode() { + return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType, + mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle, + mProgramDescription, mProgramLongDescription, mProgramPosterArtUri, + mProgramThumbnailUri, mState, mSeriesRecordingId); + } + + /** + * Returns an array containing all of the elements in the list. + */ + public static ScheduledRecording[] toArray(Collection schedules) { + return schedules.toArray(new ScheduledRecording[schedules.size()]); + } +} diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java new file mode 100644 index 00000000..89533dbb --- /dev/null +++ b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.data; + +import android.text.TextUtils; + +import java.util.Objects; + +/** + * A plain java object which includes the season/episode number for the series recording. + */ +public class SeasonEpisodeNumber { + public final long seriesRecordingId; + public final String seasonNumber; + public final String episodeNumber; + + /** + * Creates a new Builder with the values set from an existing {@link ScheduledRecording}. + */ + public SeasonEpisodeNumber(ScheduledRecording r) { + this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()); + } + + public SeasonEpisodeNumber(long seriesRecordingId, String seasonNumber, String episodeNumber) { + this.seriesRecordingId = seriesRecordingId; + this.seasonNumber = seasonNumber; + this.episodeNumber = episodeNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SeasonEpisodeNumber) + || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) { + return false; + } + SeasonEpisodeNumber that = (SeasonEpisodeNumber) o; + return seriesRecordingId == that.seriesRecordingId + && Objects.equals(seasonNumber, that.seasonNumber) + && Objects.equals(episodeNumber, that.episodeNumber); + } + + @Override + public int hashCode() { + return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber); + } + + @Override + public String toString() { + return "SeasonEpisodeNumber{" + + "seriesRecordingId=" + seriesRecordingId + + ", seasonNumber='" + seasonNumber + + ", episodeNumber=" + episodeNumber + + '}'; + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/data/SeriesInfo.java b/src/com/android/tv/dvr/data/SeriesInfo.java new file mode 100644 index 00000000..a0dec4a4 --- /dev/null +++ b/src/com/android/tv/dvr/data/SeriesInfo.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.data; + +/** + * Series information. + */ +public class SeriesInfo { + private final String mId; + private final String mTitle; + private final String mDescription; + private final String mLongDescription; + private final int[] mCanonicalGenreIds; + private final String mPosterUri; + private final String mPhotoUri; + + public SeriesInfo(String id, String title, String description, String longDescription, + int[] canonicalGenreIds, String posterUri, String photoUri) { + this.mId = id; + this.mTitle = title; + this.mDescription = description; + this.mLongDescription = longDescription; + this.mCanonicalGenreIds = canonicalGenreIds; + this.mPosterUri = posterUri; + this.mPhotoUri = photoUri; + } + + /** Returns the ID. **/ + public String getId() { + return mId; + } + + /** Returns the title. **/ + public String getTitle() { + return mTitle; + } + + /** Returns the description. **/ + public String getDescription() { + return mDescription; + } + + /** Returns the description. **/ + public String getLongDescription() { + return mLongDescription; + } + + /** Returns the canonical genre IDs. **/ + public int[] getCanonicalGenreIds() { + return mCanonicalGenreIds; + } + + /** Returns the poster URI. **/ + public String getPosterUri() { + return mPosterUri; + } + + /** Returns the photo URI. **/ + public String getPhotoUri() { + return mPhotoUri; + } +} diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java new file mode 100644 index 00000000..822e7320 --- /dev/null +++ b/src/com/android/tv/dvr/data/SeriesRecording.java @@ -0,0 +1,755 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.data; + +import android.content.ContentValues; +import android.database.Cursor; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.IntDef; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; + +import com.android.tv.data.BaseProgram; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; +import com.android.tv.util.Utils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Objects; + +/** + * Schedules the recording of a Series of Programs. + * + *

+ * Contains the data needed to create new ScheduleRecordings as the programs become available in + * the EPG. + */ +public class SeriesRecording implements Parcelable { + /** + * Indicates that the ID is not assigned yet. + */ + public static final long ID_NOT_SET = 0; + + /** + * The default priority of this recording. + */ + public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL}) + public @interface ChannelOption {} + /** + * An option which indicates that the episodes in one channel are recorded. + */ + public static final int OPTION_CHANNEL_ONE = 0; + /** + * An option which indicates that the episodes in all the channels are recorded. + */ + public static final int OPTION_CHANNEL_ALL = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED}) + public @interface SeriesState {} + + /** + * The state indicates that the series recording is a normal one. + */ + public static final int STATE_SERIES_NORMAL = 0; + + /** + * The state indicates that the series recording is stopped. + */ + public static final int STATE_SERIES_STOPPED = 1; + + /** + * Compare priority in descending order. + */ + public static final Comparator PRIORITY_COMPARATOR = + new Comparator() { + @Override + public int compare(SeriesRecording lhs, SeriesRecording rhs) { + int value = Long.compare(rhs.mPriority, lhs.mPriority); + if (value == 0) { + // New recording has the higher priority. + value = Long.compare(rhs.mId, lhs.mId); + } + return value; + } + }; + + /** + * Compare ID in ascending order. + */ + public static final Comparator ID_COMPARATOR = + new Comparator() { + @Override + public int compare(SeriesRecording lhs, SeriesRecording rhs) { + return Long.compare(lhs.mId, rhs.mId); + } + }; + + /** + * Creates a new Builder with the values set from the series information of {@link BaseProgram}. + */ + public static Builder builder(String inputId, BaseProgram p) { + return new Builder() + .setInputId(inputId) + .setSeriesId(p.getSeriesId()) + .setChannelId(p.getChannelId()) + .setTitle(p.getTitle()) + .setDescription(p.getDescription()) + .setLongDescription(p.getLongDescription()) + .setCanonicalGenreIds(p.getCanonicalGenreIds()) + .setPosterUri(p.getPosterArtUri()) + .setPhotoUri(p.getThumbnailUri()); + } + + /** + * Creates a new Builder with the values set from an existing {@link SeriesRecording}. + */ + public static Builder buildFrom(SeriesRecording r) { + return new Builder() + .setId(r.mId) + .setInputId(r.getInputId()) + .setChannelId(r.getChannelId()) + .setPriority(r.getPriority()) + .setTitle(r.getTitle()) + .setDescription(r.getDescription()) + .setLongDescription(r.getLongDescription()) + .setSeriesId(r.getSeriesId()) + .setStartFromEpisode(r.getStartFromEpisode()) + .setStartFromSeason(r.getStartFromSeason()) + .setChannelOption(r.getChannelOption()) + .setCanonicalGenreIds(r.getCanonicalGenreIds()) + .setPosterUri(r.getPosterUri()) + .setPhotoUri(r.getPhotoUri()) + .setState(r.getState()); + } + + /** + * Use this projection if you want to create {@link SeriesRecording} object using + * {@link #fromCursor}. + */ + public static final String[] PROJECTION = { + // Columns must match what is read in fromCursor() + SeriesRecordings._ID, + SeriesRecordings.COLUMN_INPUT_ID, + SeriesRecordings.COLUMN_CHANNEL_ID, + SeriesRecordings.COLUMN_PRIORITY, + SeriesRecordings.COLUMN_TITLE, + SeriesRecordings.COLUMN_SHORT_DESCRIPTION, + SeriesRecordings.COLUMN_LONG_DESCRIPTION, + SeriesRecordings.COLUMN_SERIES_ID, + SeriesRecordings.COLUMN_START_FROM_EPISODE, + SeriesRecordings.COLUMN_START_FROM_SEASON, + SeriesRecordings.COLUMN_CHANNEL_OPTION, + SeriesRecordings.COLUMN_CANONICAL_GENRE, + SeriesRecordings.COLUMN_POSTER_URI, + SeriesRecordings.COLUMN_PHOTO_URI, + SeriesRecordings.COLUMN_STATE + }; + /** + * Creates {@link SeriesRecording} object from the given {@link Cursor}. + */ + public static SeriesRecording fromCursor(Cursor c) { + int index = -1; + return new Builder() + .setId(c.getLong(++index)) + .setInputId(c.getString(++index)) + .setChannelId(c.getLong(++index)) + .setPriority(c.getLong(++index)) + .setTitle(c.getString(++index)) + .setDescription(c.getString(++index)) + .setLongDescription(c.getString(++index)) + .setSeriesId(c.getString(++index)) + .setStartFromEpisode(c.getInt(++index)) + .setStartFromSeason(c.getInt(++index)) + .setChannelOption(channelOption(c.getString(++index))) + .setCanonicalGenreIds(c.getString(++index)) + .setPosterUri(c.getString(++index)) + .setPhotoUri(c.getString(++index)) + .setState(seriesRecordingState(c.getString(++index))) + .build(); + } + + /** + * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings} + * and the values from {@code r}. + */ + public static ContentValues toContentValues(SeriesRecording r) { + ContentValues values = new ContentValues(); + if (r.getId() != ID_NOT_SET) { + values.put(SeriesRecordings._ID, r.getId()); + } else { + values.putNull(SeriesRecordings._ID); + } + values.put(SeriesRecordings.COLUMN_INPUT_ID, r.getInputId()); + values.put(SeriesRecordings.COLUMN_CHANNEL_ID, r.getChannelId()); + values.put(SeriesRecordings.COLUMN_PRIORITY, r.getPriority()); + values.put(SeriesRecordings.COLUMN_TITLE, r.getTitle()); + values.put(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, r.getDescription()); + values.put(SeriesRecordings.COLUMN_LONG_DESCRIPTION, r.getLongDescription()); + values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId()); + values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode()); + values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason()); + values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION, + channelOption(r.getChannelOption())); + values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE, + Utils.getCanonicalGenre(r.getCanonicalGenreIds())); + values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri()); + values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri()); + values.put(SeriesRecordings.COLUMN_STATE, seriesRecordingState(r.getState())); + return values; + } + + private static String channelOption(@ChannelOption int option) { + switch (option) { + case OPTION_CHANNEL_ONE: + return SeriesRecordings.OPTION_CHANNEL_ONE; + case OPTION_CHANNEL_ALL: + return SeriesRecordings.OPTION_CHANNEL_ALL; + } + return SeriesRecordings.OPTION_CHANNEL_ONE; + } + + @ChannelOption private static int channelOption(String option) { + switch (option) { + case SeriesRecordings.OPTION_CHANNEL_ONE: + return OPTION_CHANNEL_ONE; + case SeriesRecordings.OPTION_CHANNEL_ALL: + return OPTION_CHANNEL_ALL; + } + return OPTION_CHANNEL_ONE; + } + + private static String seriesRecordingState(@SeriesState int state) { + switch (state) { + case STATE_SERIES_NORMAL: + return SeriesRecordings.STATE_SERIES_NORMAL; + case STATE_SERIES_STOPPED: + return SeriesRecordings.STATE_SERIES_STOPPED; + } + return SeriesRecordings.STATE_SERIES_NORMAL; + } + + @SeriesState private static int seriesRecordingState(String state) { + switch (state) { + case SeriesRecordings.STATE_SERIES_NORMAL: + return STATE_SERIES_NORMAL; + case SeriesRecordings.STATE_SERIES_STOPPED: + return STATE_SERIES_STOPPED; + } + return STATE_SERIES_NORMAL; + } + + /** + * Builder for {@link SeriesRecording}. + */ + public static class Builder { + private long mId = ID_NOT_SET; + private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY; + private String mTitle; + private String mDescription; + private String mLongDescription; + private String mInputId; + private long mChannelId; + private String mSeriesId; + private int mStartFromSeason = SeriesRecordings.THE_BEGINNING; + private int mStartFromEpisode = SeriesRecordings.THE_BEGINNING; + private int mChannelOption = OPTION_CHANNEL_ONE; + private int[] mCanonicalGenreIds; + private String mPosterUri; + private String mPhotoUri; + private int mState = SeriesRecording.STATE_SERIES_NORMAL; + + /** + * @see #getId() + */ + public Builder setId(long id) { + mId = id; + return this; + } + + /** + * @see #getPriority() () + */ + public Builder setPriority(long priority) { + mPriority = priority; + return this; + } + + /** + * @see #getTitle() + */ + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + /** + * @see #getDescription() + */ + public Builder setDescription(String description) { + mDescription = description; + return this; + } + + /** + * @see #getLongDescription() + */ + public Builder setLongDescription(String longDescription) { + mLongDescription = longDescription; + return this; + } + + /** + * @see #getInputId() + */ + public Builder setInputId(String inputId) { + mInputId = inputId; + return this; + } + + /** + * @see #getChannelId() + */ + public Builder setChannelId(long channelId) { + mChannelId = channelId; + return this; + } + + /** + * @see #getSeriesId() + */ + public Builder setSeriesId(String seriesId) { + mSeriesId = seriesId; + return this; + } + + /** + * @see #getStartFromSeason() + */ + public Builder setStartFromSeason(int startFromSeason) { + mStartFromSeason = startFromSeason; + return this; + } + + /** + * @see #getChannelOption() + */ + public Builder setChannelOption(@ChannelOption int option) { + mChannelOption = option; + return this; + } + + /** + * @see #getStartFromEpisode() + */ + public Builder setStartFromEpisode(int startFromEpisode) { + mStartFromEpisode = startFromEpisode; + return this; + } + + /** + * @see #getCanonicalGenreIds() + */ + public Builder setCanonicalGenreIds(String genres) { + mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres); + return this; + } + + /** + * @see #getCanonicalGenreIds() + */ + public Builder setCanonicalGenreIds(int[] canonicalGenreIds) { + mCanonicalGenreIds = canonicalGenreIds; + return this; + } + + /** + * @see #getPosterUri() + */ + public Builder setPosterUri(String posterUri) { + mPosterUri = posterUri; + return this; + } + + /** + * @see #getPhotoUri() + */ + public Builder setPhotoUri(String photoUri) { + mPhotoUri = photoUri; + return this; + } + + /** + * @see #getState() + */ + public Builder setState(@SeriesState int state) { + mState = state; + return this; + } + + /** + * Creates a new {@link SeriesRecording}. + */ + public SeriesRecording build() { + return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription, + mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode, + mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); + } + } + + public static SeriesRecording fromParcel(Parcel in) { + return new Builder() + .setId(in.readLong()) + .setPriority(in.readLong()) + .setTitle(in.readString()) + .setDescription(in.readString()) + .setLongDescription(in.readString()) + .setInputId(in.readString()) + .setChannelId(in.readLong()) + .setSeriesId(in.readString()) + .setStartFromSeason(in.readInt()) + .setStartFromEpisode(in.readInt()) + .setChannelOption(in.readInt()) + .setCanonicalGenreIds(in.createIntArray()) + .setPosterUri(in.readString()) + .setPhotoUri(in.readString()) + .setState(in.readInt()) + .build(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public SeriesRecording createFromParcel(Parcel in) { + return SeriesRecording.fromParcel(in); + } + + @Override + public SeriesRecording[] newArray(int size) { + return new SeriesRecording[size]; + } + }; + + private long mId; + private final long mPriority; + private final String mTitle; + private final String mDescription; + private final String mLongDescription; + private final String mInputId; + private final long mChannelId; + private final String mSeriesId; + private final int mStartFromSeason; + private final int mStartFromEpisode; + @ChannelOption private final int mChannelOption; + private final int[] mCanonicalGenreIds; + private final String mPosterUri; + private final String mPhotoUri; + @SeriesState private int mState; + + /** + * The input id of this SeriesRecording. + */ + public String getInputId() { + return mInputId; + } + + /** + * The channelId to match. The channel ID might not be valid when the channel option is "ALL". + */ + public long getChannelId() { + return mChannelId; + } + + /** + * The id of this SeriesRecording. + */ + public long getId() { + return mId; + } + + /** + * Sets the ID. + */ + public void setId(long id) { + mId = id; + } + + /** + * The priority of this recording. + * + *

The highest number is recorded first. If there is a tie in mPriority then the higher mId + * wins. + */ + public long getPriority() { + return mPriority; + } + + /** + * The series title. + */ + public String getTitle() { + return mTitle; + } + + /** + * The series description. + */ + public String getDescription() { + return mDescription; + } + + /** + * The long series description. + */ + public String getLongDescription() { + return mLongDescription; + } + + /** + * SeriesId when not null is used to match programs instead of using title and channelId. + * + *

SeriesId is an opaque but stable string. + */ + public String getSeriesId() { + return mSeriesId; + } + + /** + * If not == {@link SeriesRecordings#THE_BEGINNING} and seasonNumber == startFromSeason then + * only record episodes with a episodeNumber >= this + */ + public int getStartFromEpisode() { + return mStartFromEpisode; + } + + /** + * If not == {@link SeriesRecordings#THE_BEGINNING} then only record episodes with a + * seasonNumber >= this + */ + public int getStartFromSeason() { + return mStartFromSeason; + } + + /** + * Returns the channel recording option. + */ + @ChannelOption public int getChannelOption() { + return mChannelOption; + } + + /** + * Returns the canonical genre ID's. + */ + public int[] getCanonicalGenreIds() { + return mCanonicalGenreIds; + } + + /** + * Returns the poster URI. + */ + public String getPosterUri() { + return mPosterUri; + } + + /** + * Returns the photo URI. + */ + public String getPhotoUri() { + return mPhotoUri; + } + + /** + * Returns the state of series recording. + */ + @SeriesState public int getState() { + return mState; + } + + /** + * Checks whether the series recording is stopped or not. + */ + public boolean isStopped() { + return mState == STATE_SERIES_STOPPED; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SeriesRecording)) return false; + SeriesRecording that = (SeriesRecording) o; + return mPriority == that.mPriority + && mChannelId == that.mChannelId + && mStartFromSeason == that.mStartFromSeason + && mStartFromEpisode == that.mStartFromEpisode + && Objects.equals(mId, that.mId) + && Objects.equals(mTitle, that.mTitle) + && Objects.equals(mDescription, that.mDescription) + && Objects.equals(mLongDescription, that.mLongDescription) + && Objects.equals(mSeriesId, that.mSeriesId) + && mChannelOption == that.mChannelOption + && Arrays.equals(mCanonicalGenreIds, that.mCanonicalGenreIds) + && Objects.equals(mPosterUri, that.mPosterUri) + && Objects.equals(mPhotoUri, that.mPhotoUri) + && mState == that.mState; + } + + @Override + public int hashCode() { + return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId, + mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption, + mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); + } + + @Override + public String toString() { + return "SeriesRecording{" + + "inputId=" + mInputId + + ", channelId=" + mChannelId + + ", id='" + mId + '\'' + + ", priority=" + mPriority + + ", title='" + mTitle + '\'' + + ", description='" + mDescription + '\'' + + ", longDescription='" + mLongDescription + '\'' + + ", startFromSeason=" + mStartFromSeason + + ", startFromEpisode=" + mStartFromEpisode + + ", channelOption=" + mChannelOption + + ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) + + ", posterUri=" + mPosterUri + + ", photoUri=" + mPhotoUri + + ", state=" + mState + + '}'; + } + + private SeriesRecording(long id, long priority, String title, String description, + String longDescription, String inputId, long channelId, String seriesId, + int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds, + String posterUri, String photoUri, int state) { + this.mId = id; + this.mPriority = priority; + this.mTitle = title; + this.mDescription = description; + this.mLongDescription = longDescription; + this.mInputId = inputId; + this.mChannelId = channelId; + this.mSeriesId = seriesId; + this.mStartFromSeason = startFromSeason; + this.mStartFromEpisode = startFromEpisode; + this.mChannelOption = channelOption; + this.mCanonicalGenreIds = canonicalGenreIds; + this.mPosterUri = posterUri; + this.mPhotoUri = photoUri; + this.mState = state; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int paramInt) { + out.writeLong(mId); + out.writeLong(mPriority); + out.writeString(mTitle); + out.writeString(mDescription); + out.writeString(mLongDescription); + out.writeString(mInputId); + out.writeLong(mChannelId); + out.writeString(mSeriesId); + out.writeInt(mStartFromSeason); + out.writeInt(mStartFromEpisode); + out.writeInt(mChannelOption); + out.writeIntArray(mCanonicalGenreIds); + out.writeString(mPosterUri); + out.writeString(mPhotoUri); + out.writeInt(mState); + } + + /** + * Returns an array containing all of the elements in the list. + */ + public static SeriesRecording[] toArray(Collection series) { + return series.toArray(new SeriesRecording[series.size()]); + } + + /** + * Returns {@code true} if the {@code program} is part of the series and meets the season and + * episode constraints. + */ + public boolean matchProgram(Program program) { + return matchProgram(program, mChannelOption); + } + + /** + * Returns {@code true} if the {@code program} is part of the series and meets the season and + * episode constraints. It checks the channel option only if {@code checkChannelOption} is + * {@code true}. + */ + public boolean matchProgram(Program program, @ChannelOption int channelOption) { + String seriesId = program.getSeriesId(); + long channelId = program.getChannelId(); + String seasonNumber = program.getSeasonNumber(); + String episodeNumber = program.getEpisodeNumber(); + if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE + && mChannelId != channelId)) { + return false; + } + // Season number and episode number matches if + // start_season_number < program_season_number + // || (start_season_number == program_season_number + // && start_episode_number <= program_episode_number). + if (mStartFromSeason == SeriesRecordings.THE_BEGINNING + || TextUtils.isEmpty(seasonNumber)) { + return true; + } else { + int intSeasonNumber; + try { + intSeasonNumber = Integer.valueOf(seasonNumber); + } catch (NumberFormatException e) { + return true; + } + if (intSeasonNumber > mStartFromSeason) { + return true; + } else if (intSeasonNumber < mStartFromSeason) { + return false; + } + } + if (mStartFromEpisode == SeriesRecordings.THE_BEGINNING + || TextUtils.isEmpty(episodeNumber)) { + return true; + } else { + int intEpisodeNumber; + try { + intEpisodeNumber = Integer.valueOf(episodeNumber); + } catch (NumberFormatException e) { + return true; + } + return intEpisodeNumber >= mStartFromEpisode; + } + } +} diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java index 1a12fb23..c5383d02 100644 --- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java +++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java @@ -21,8 +21,8 @@ import android.database.Cursor; import android.os.AsyncTask; import android.support.annotation.Nullable; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; import com.android.tv.util.NamedThreadFactory; diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java index 2f16ba5d..8b9481a9 100644 --- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java +++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java @@ -27,8 +27,8 @@ import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java new file mode 100644 index 00000000..ff391959 --- /dev/null +++ b/src/com/android/tv/dvr/provider/DvrDbSync.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.provider; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.content.Context; +import android.database.ContentObserver; +import android.media.tv.TvContract.Programs; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.MainThread; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.tv.TvApplication; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrDataManagerImpl; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.recorder.SeriesRecordingScheduler; +import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask; +import com.android.tv.util.TvUriMatcher; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; + +/** + * A class to synchronizes DVR DB with TvProvider. + * + *

The current implementation of AsyncDbTask allows only one task to run at a time, and all the + * other tasks are blocked until the current one finishes. As this class performs the low priority + * jobs which take long time, it should not block others if possible. For this reason, only one + * program is queried at a time and others are queued and will be executed on the other + * AsyncDbTask's after the current one finishes to minimize the execution time of one AsyncDbTask. + */ +@MainThread +@TargetApi(Build.VERSION_CODES.N) +public class DvrDbSync { + private static final String TAG = "DvrDbSync"; + private static final boolean DEBUG = false; + + private final Context mContext; + private final DvrManager mDvrManager; + private final DvrDataManagerImpl mDataManager; + private final ChannelDataManager mChannelDataManager; + private final Queue mProgramIdQueue = new LinkedList<>(); + private QueryProgramTask mQueryProgramTask; + private final SeriesRecordingScheduler mSeriesRecordingScheduler; + private final ContentObserver mContentObserver = new ContentObserver(new Handler( + Looper.getMainLooper())) { + @SuppressLint("SwitchIntDef") + @Override + public void onChange(boolean selfChange, Uri uri) { + switch (TvUriMatcher.match(uri)) { + case TvUriMatcher.MATCH_PROGRAM: + if (DEBUG) Log.d(TAG, "onProgramsUpdated"); + onProgramsUpdated(); + break; + case TvUriMatcher.MATCH_PROGRAM_ID: + if (DEBUG) { + Log.d(TAG, "onProgramUpdated: programId=" + ContentUris.parseId(uri)); + } + onProgramUpdated(ContentUris.parseId(uri)); + break; + } + } + }; + + private final ChannelDataManager.Listener mChannelDataManagerListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + start(); + } + + @Override + public void onChannelListUpdated() { + onChannelsUpdated(); + } + + @Override + public void onChannelBrowsableChanged() { } + }; + + private final ScheduledRecordingListener mScheduleListener = new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + addProgramIdToCheckIfNeeded(schedule); + } + startNextUpdateIfNeeded(); + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + mProgramIdQueue.remove(schedule.getProgramId()); + } + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + mProgramIdQueue.remove(schedule.getProgramId()); + addProgramIdToCheckIfNeeded(schedule); + } + startNextUpdateIfNeeded(); + } + }; + + public DvrDbSync(Context context, DvrDataManagerImpl dataManager) { + this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager(), + TvApplication.getSingletons(context).getDvrManager(), + SeriesRecordingScheduler.getInstance(context)); + } + + @VisibleForTesting + DvrDbSync(Context context, DvrDataManagerImpl dataManager, + ChannelDataManager channelDataManager, DvrManager dvrManager, + SeriesRecordingScheduler seriesRecordingScheduler) { + mContext = context; + mDvrManager = dvrManager; + mDataManager = dataManager; + mChannelDataManager = channelDataManager; + mSeriesRecordingScheduler = seriesRecordingScheduler; + } + + /** + * Starts the DB sync. + */ + public void start() { + if (!mChannelDataManager.isDbLoadFinished()) { + mChannelDataManager.addListener(mChannelDataManagerListener); + return; + } + mContext.getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, + mContentObserver); + mDataManager.addScheduledRecordingListener(mScheduleListener); + onChannelsUpdated(); + onProgramsUpdated(); + } + + /** + * Stops the DB sync. + */ + public void stop() { + mProgramIdQueue.clear(); + if (mQueryProgramTask != null) { + mQueryProgramTask.cancel(true); + } + mChannelDataManager.removeListener(mChannelDataManagerListener); + mDataManager.removeScheduledRecordingListener(mScheduleListener); + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + } + + private void onChannelsUpdated() { + List seriesRecordingsToUpdate = new ArrayList<>(); + for (SeriesRecording r : mDataManager.getSeriesRecordings()) { + if (r.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE + && !mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { + seriesRecordingsToUpdate.add(SeriesRecording.buildFrom(r) + .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) + .setState(SeriesRecording.STATE_SERIES_STOPPED).build()); + } + } + if (!seriesRecordingsToUpdate.isEmpty()) { + mDataManager.updateSeriesRecording( + SeriesRecording.toArray(seriesRecordingsToUpdate)); + } + List schedulesToRemove = new ArrayList<>(); + for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) { + if (!mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { + schedulesToRemove.add(r); + mProgramIdQueue.remove(r.getProgramId()); + } + } + if (!schedulesToRemove.isEmpty()) { + mDataManager.removeScheduledRecording( + ScheduledRecording.toArray(schedulesToRemove)); + } + } + + private void onProgramsUpdated() { + for (ScheduledRecording schedule : mDataManager.getAvailableScheduledRecordings()) { + addProgramIdToCheckIfNeeded(schedule); + } + startNextUpdateIfNeeded(); + } + + private void onProgramUpdated(long programId) { + addProgramIdToCheckIfNeeded(mDataManager.getScheduledRecordingForProgramId(programId)); + startNextUpdateIfNeeded(); + } + + private void addProgramIdToCheckIfNeeded(ScheduledRecording schedule) { + if (schedule == null) { + return; + } + long programId = schedule.getProgramId(); + if (programId != ScheduledRecording.ID_NOT_SET + && !mProgramIdQueue.contains(programId) + && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED + || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + if (DEBUG) Log.d(TAG, "Program ID enqueued: " + programId); + mProgramIdQueue.offer(programId); + // There are schedules to be updated. Pause the SeriesRecordingScheduler until all the + // schedule updates finish. + // Note that the SeriesRecordingScheduler should be paused even though the program to + // check is not episodic because it can be changed to the episodic program after the + // update, which affect the SeriesRecordingScheduler. + mSeriesRecordingScheduler.pauseUpdate(); + } + } + + private void startNextUpdateIfNeeded() { + if (mQueryProgramTask != null && !mQueryProgramTask.isCancelled()) { + return; + } + if (!mProgramIdQueue.isEmpty()) { + if (DEBUG) Log.d(TAG, "Program ID dequeued: " + mProgramIdQueue.peek()); + mQueryProgramTask = new QueryProgramTask(mProgramIdQueue.poll()); + mQueryProgramTask.executeOnDbThread(); + } else { + mSeriesRecordingScheduler.resumeUpdate(); + } + } + + @VisibleForTesting + void handleUpdateProgram(Program program, long programId) { + Set seriesRecordingsToUpdate = new HashSet<>(); + ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(programId); + if (schedule != null + && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED + || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + if (program == null) { + mDataManager.removeScheduledRecording(schedule); + if (schedule.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { + SeriesRecording seriesRecording = + mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); + if (seriesRecording != null) { + seriesRecordingsToUpdate.add(seriesRecording); + } + } + } else { + long currentTimeMs = System.currentTimeMillis(); + ScheduledRecording.Builder builder = ScheduledRecording.buildFrom(schedule) + .setEndTimeMs(program.getEndTimeUtcMillis()) + .setSeasonNumber(program.getSeasonNumber()) + .setEpisodeNumber(program.getEpisodeNumber()) + .setEpisodeTitle(program.getEpisodeTitle()) + .setProgramDescription(program.getDescription()) + .setProgramLongDescription(program.getLongDescription()) + .setProgramPosterArtUri(program.getPosterArtUri()) + .setProgramThumbnailUri(program.getThumbnailUri()); + boolean needUpdate = false; + // Check the series recording. + SeriesRecording seriesRecordingForOldSchedule = + mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); + if (program.isEpisodic()) { + // New program belongs to a series. + SeriesRecording seriesRecording = + mDataManager.getSeriesRecording(program.getSeriesId()); + if (seriesRecording == null) { + // The new program is episodic while the previous one isn't. + SeriesRecording newSeriesRecording = mDvrManager.addSeriesRecording( + program, Collections.singletonList(program), + SeriesRecording.STATE_SERIES_STOPPED); + builder.setSeriesRecordingId(newSeriesRecording.getId()); + needUpdate = true; + } else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) { + // The new program belongs to the other series. + builder.setSeriesRecordingId(seriesRecording.getId()); + needUpdate = true; + seriesRecordingsToUpdate.add(seriesRecording); + if (seriesRecordingForOldSchedule != null) { + seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); + } + } else if (!Objects.equals(schedule.getSeasonNumber(), + program.getSeasonNumber()) + || !Objects.equals(schedule.getEpisodeNumber(), + program.getEpisodeNumber())) { + // The episode number has been changed. + if (seriesRecordingForOldSchedule != null) { + seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); + } + } + } else if (seriesRecordingForOldSchedule != null) { + // Old program belongs to a series but the new one doesn't. + seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); + } + // Change start time only when the recording is not started yet. + boolean needToChangeStartTime = + schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS + && program.getStartTimeUtcMillis() != schedule.getStartTimeMs(); + if (needToChangeStartTime) { + builder.setStartTimeMs(program.getStartTimeUtcMillis()); + needUpdate = true; + } + if (needUpdate || schedule.getEndTimeMs() != program.getEndTimeUtcMillis() + || !Objects.equals(schedule.getSeasonNumber(), program.getSeasonNumber()) + || !Objects.equals(schedule.getEpisodeNumber(), program.getEpisodeNumber()) + || !Objects.equals(schedule.getEpisodeTitle(), program.getEpisodeTitle()) + || !Objects.equals(schedule.getProgramDescription(), + program.getDescription()) + || !Objects.equals(schedule.getProgramLongDescription(), + program.getLongDescription()) + || !Objects.equals(schedule.getProgramPosterArtUri(), + program.getPosterArtUri()) + || !Objects.equals(schedule.getProgramThumbnailUri(), + program.getThumbnailUri())) { + mDataManager.updateScheduledRecording(builder.build()); + } + if (!seriesRecordingsToUpdate.isEmpty()) { + // The series recordings will be updated after it's resumed. + mSeriesRecordingScheduler.updateSchedules(seriesRecordingsToUpdate); + } + } + } + } + + private class QueryProgramTask extends AsyncQueryProgramTask { + private final long mProgramId; + + QueryProgramTask(long programId) { + super(mContext.getContentResolver(), programId); + mProgramId = programId; + } + + @Override + protected void onCancelled(Program program) { + if (mQueryProgramTask == this) { + mQueryProgramTask = null; + } + startNextUpdateIfNeeded(); + } + + @Override + protected void onPostExecute(Program program) { + if (mQueryProgramTask == this) { + mQueryProgramTask = null; + } + handleUpdateProgram(program, mProgramId); + startNextUpdateIfNeeded(); + } + } +} diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java new file mode 100644 index 00000000..ba0aca51 --- /dev/null +++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.provider; + +import android.annotation.TargetApi; +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.media.tv.TvContract.Programs; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.data.SeasonEpisodeNumber; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask; +import com.android.tv.util.AsyncDbTask.CursorFilter; +import com.android.tv.util.PermissionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings. + */ +@TargetApi(Build.VERSION_CODES.N) +abstract public class EpisodicProgramLoadTask { + private static final String TAG = "EpisodicProgramLoadTask"; + + private static final int PROGRAM_ID_INDEX = Program.getColumnIndex(Programs._ID); + private static final int START_TIME_INDEX = + Program.getColumnIndex(Programs.COLUMN_START_TIME_UTC_MILLIS); + private static final int RECORDING_PROHIBITED_INDEX = + Program.getColumnIndex(Programs.COLUMN_RECORDING_PROHIBITED); + + private static final String PARAM_START_TIME = "start_time"; + private static final String PARAM_END_TIME = "end_time"; + + private static final String PROGRAM_PREDICATE = + Programs.COLUMN_START_TIME_UTC_MILLIS + ">? AND " + + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; + private static final String PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM = + Programs.COLUMN_END_TIME_UTC_MILLIS + ">? AND " + + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; + private static final String CHANNEL_ID_PREDICATE = Programs.COLUMN_CHANNEL_ID + "=?"; + private static final String PROGRAM_TITLE_PREDICATE = Programs.COLUMN_TITLE + "=?"; + + private final Context mContext; + private final DvrDataManager mDataManager; + private boolean mQueryAllChannels; + private boolean mLoadCurrentProgram; + private boolean mLoadScheduledEpisode; + private boolean mLoadDisallowedProgram; + // If true, match programs with OPTION_CHANNEL_ALL. + private boolean mIgnoreChannelOption; + private final ArrayList mSeriesRecordings = new ArrayList<>(); + private AsyncProgramQueryTask mProgramQueryTask; + + /** + * + * Constructor used to load programs for one series recording with the given channel option. + */ + public EpisodicProgramLoadTask(Context context, SeriesRecording seriesRecording) { + this(context, Collections.singletonList(seriesRecording)); + } + + /** + * Constructor used to load programs for multiple series recordings. The channel option is + * {@link SeriesRecording#OPTION_CHANNEL_ALL}. + */ + public EpisodicProgramLoadTask(Context context, Collection seriesRecordings) { + mContext = context.getApplicationContext(); + mDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mSeriesRecordings.addAll(seriesRecordings); + } + + /** + * Returns the series recordings. + */ + public List getSeriesRecordings() { + return mSeriesRecordings; + } + + /** + * Returns the program query task. It is {@code null} until it is executed. + */ + @Nullable + public AsyncProgramQueryTask getTask() { + return mProgramQueryTask; + } + + /** + * Enables loading current programs. The default value is {@code false}. + */ + public EpisodicProgramLoadTask setLoadCurrentProgram(boolean loadCurrentProgram) { + SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + "Can't change setting after execution."); + mLoadCurrentProgram = loadCurrentProgram; + return this; + } + + /** + * Enables already schedules episodes. The default value is {@code false}. + */ + public EpisodicProgramLoadTask setLoadScheduledEpisode(boolean loadScheduledEpisode) { + SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + "Can't change setting after execution."); + mLoadScheduledEpisode = loadScheduledEpisode; + return this; + } + + /** + * Enables loading disallowed programs whose schedules were removed manually by the user. + * The default value is {@code false}. + */ + public EpisodicProgramLoadTask setLoadDisallowedProgram(boolean loadDisallowedProgram) { + SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + "Can't change setting after execution."); + mLoadDisallowedProgram = loadDisallowedProgram; + return this; + } + + /** + * Gives the option whether to ignore the channel option when matching programs. + * If {@code ignoreChannelOption} is {@code true}, the program will be matched with + * {@link SeriesRecording#OPTION_CHANNEL_ALL} option. + */ + public EpisodicProgramLoadTask setIgnoreChannelOption(boolean ignoreChannelOption) { + SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + "Can't change setting after execution."); + mIgnoreChannelOption = ignoreChannelOption; + return this; + } + + /** + * Executes the task. + * + * @see com.android.tv.util.AsyncDbTask#executeOnDbThread + */ + public void execute() { + if (SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + "Can't execute task: the task is already running.")) { + mQueryAllChannels = mSeriesRecordings.size() > 1 + || mSeriesRecordings.get(0).getChannelOption() + == SeriesRecording.OPTION_CHANNEL_ALL + || mIgnoreChannelOption; + mProgramQueryTask = createTask(); + mProgramQueryTask.executeOnDbThread(); + } + } + + /** + * Cancels the task. + * + * @see android.os.AsyncTask#cancel + */ + public void cancel(boolean mayInterruptIfRunning) { + if (mProgramQueryTask != null) { + mProgramQueryTask.cancel(mayInterruptIfRunning); + } + } + + /** + * Runs on the UI thread after the program loading finishes successfully. + */ + protected void onPostExecute(List programs) { + } + + /** + * Runs on the UI thread after the program loading was canceled. + */ + protected void onCancelled(List programs) { + } + + private AsyncProgramQueryTask createTask() { + SqlParams sqlParams = createSqlParams(); + return new AsyncProgramQueryTask(mContext.getContentResolver(), sqlParams.uri, + sqlParams.selection, sqlParams.selectionArgs, null, sqlParams.filter) { + @Override + protected void onPostExecute(List programs) { + EpisodicProgramLoadTask.this.onPostExecute(programs); + } + + @Override + protected void onCancelled(List programs) { + EpisodicProgramLoadTask.this.onCancelled(programs); + } + }; + } + + private SqlParams createSqlParams() { + SqlParams sqlParams = new SqlParams(); + if (PermissionUtils.hasAccessAllEpg(mContext)) { + sqlParams.uri = Programs.CONTENT_URI; + // Base + StringBuilder selection = new StringBuilder(mLoadCurrentProgram + ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM : PROGRAM_PREDICATE); + List args = new ArrayList<>(); + args.add(Long.toString(System.currentTimeMillis())); + // Channel option + if (!mQueryAllChannels) { + selection.append(" AND ").append(CHANNEL_ID_PREDICATE); + args.add(Long.toString(mSeriesRecordings.get(0).getChannelId())); + } + // Title + if (mSeriesRecordings.size() == 1) { + selection.append(" AND ").append(PROGRAM_TITLE_PREDICATE); + args.add(mSeriesRecordings.get(0).getTitle()); + } + sqlParams.selection = selection.toString(); + sqlParams.selectionArgs = args.toArray(new String[args.size()]); + sqlParams.filter = new SeriesRecordingCursorFilter(mSeriesRecordings); + } else { + // The query includes the current program. Will be filtered if needed. + if (mQueryAllChannels) { + sqlParams.uri = Programs.CONTENT_URI.buildUpon() + .appendQueryParameter(PARAM_START_TIME, + String.valueOf(System.currentTimeMillis())) + .appendQueryParameter(PARAM_END_TIME, String.valueOf(Long.MAX_VALUE)) + .build(); + } else { + sqlParams.uri = TvContract.buildProgramsUriForChannel( + mSeriesRecordings.get(0).getChannelId(), + System.currentTimeMillis(), Long.MAX_VALUE); + } + sqlParams.selection = null; + sqlParams.selectionArgs = null; + sqlParams.filter = new SeriesRecordingCursorFilterForNonSystem(mSeriesRecordings); + } + return sqlParams; + } + + /** + * Filter the programs which match the series recording. The episodes which the schedules are + * already created for are filtered out too. + */ + private class SeriesRecordingCursorFilter implements CursorFilter { + private final Set mDisallowedProgramIds = new HashSet<>(); + private final Set mSeasonEpisodeNumbers = new HashSet<>(); + + SeriesRecordingCursorFilter(List seriesRecordings) { + if (!mLoadDisallowedProgram) { + mDisallowedProgramIds.addAll(mDataManager.getDisallowedProgramIds()); + } + if (!mLoadScheduledEpisode) { + Set seriesRecordingIds = new HashSet<>(); + for (SeriesRecording r : seriesRecordings) { + seriesRecordingIds.add(r.getId()); + } + for (ScheduledRecording r : mDataManager.getAllScheduledRecordings()) { + if (seriesRecordingIds.contains(r.getSeriesRecordingId()) + && r.getState() != ScheduledRecording.STATE_RECORDING_FAILED + && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) { + mSeasonEpisodeNumbers.add(new SeasonEpisodeNumber(r)); + } + } + } + } + + @Override + @WorkerThread + public boolean filter(Cursor c) { + if (!mLoadDisallowedProgram + && mDisallowedProgramIds.contains(c.getLong(PROGRAM_ID_INDEX))) { + return false; + } + Program program = Program.fromCursor(c); + for (SeriesRecording seriesRecording : mSeriesRecordings) { + boolean programMatches; + if (mIgnoreChannelOption) { + programMatches = seriesRecording.matchProgram(program, + SeriesRecording.OPTION_CHANNEL_ALL); + } else { + programMatches = seriesRecording.matchProgram(program); + } + if (programMatches) { + return mLoadScheduledEpisode + || !mSeasonEpisodeNumbers.contains(new SeasonEpisodeNumber( + seriesRecording.getId(), program.getSeasonNumber(), + program.getEpisodeNumber())); + } + } + return false; + } + } + + private class SeriesRecordingCursorFilterForNonSystem extends SeriesRecordingCursorFilter { + SeriesRecordingCursorFilterForNonSystem(List seriesRecordings) { + super(seriesRecordings); + } + + @Override + public boolean filter(Cursor c) { + return (mLoadCurrentProgram || c.getLong(START_TIME_INDEX) > System.currentTimeMillis()) + && c.getInt(RECORDING_PROHIBITED_INDEX) != 0 && super.filter(c); + } + } + + private static class SqlParams { + public Uri uri; + public String selection; + public String[] selectionArgs; + public CursorFilter filter; + } +} diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java new file mode 100644 index 00000000..8aa90116 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.media.tv.TvContract; +import android.net.Uri; +import android.os.Build; +import android.os.Message; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.ArraySet; +import android.util.Log; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.InputSessionManager; +import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener; +import com.android.tv.MainActivity; +import com.android.tv.TvApplication; +import com.android.tv.common.WeakHandler; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.DvrUiHelper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Checking the runtime conflict of DVR recording. + *

+ * This class runs only while the {@link MainActivity} is resumed and holds the upcoming conflicts. + */ +@TargetApi(Build.VERSION_CODES.N) +@MainThread +public class ConflictChecker { + private static final String TAG = "ConflictChecker"; + private static final boolean DEBUG = false; + + private static final int MSG_CHECK_CONFLICT = 1; + + private static final long CHECK_RETRY_PERIOD_MS = TimeUnit.SECONDS.toMillis(30); + + /** + * To show watch conflict dialog, the start time of the earliest conflicting schedule should be + * less than or equal to this time. + */ + private static final long MAX_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.MINUTES.toMillis(5); + /** + * To show watch conflict dialog, the start time of the earliest conflicting schedule should be + * greater than or equal to this time. + */ + private static final long MIN_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.SECONDS.toMillis(30); + + private final MainActivity mMainActivity; + private final ChannelDataManager mChannelDataManager; + private final DvrScheduleManager mScheduleManager; + private final InputSessionManager mSessionManager; + private final ConflictCheckerHandler mHandler = new ConflictCheckerHandler(this); + + private final List mUpcomingConflicts = new ArrayList<>(); + private final Set mOnUpcomingConflictChangeListeners = + new ArraySet<>(); + private final Map> mCheckedConflictsMap = new HashMap<>(); + + private final ScheduledRecordingListener mScheduledRecordingListener = + new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { + if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { + if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { + if (DEBUG) Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } + }; + + private final OnTvViewChannelChangeListener mOnTvViewChannelChangeListener = + new OnTvViewChannelChangeListener() { + @Override + public void onTvViewChannelChange(@Nullable Uri channelUri) { + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } + }; + + private boolean mStarted; + + public ConflictChecker(MainActivity mainActivity) { + mMainActivity = mainActivity; + ApplicationSingletons appSingletons = TvApplication.getSingletons(mainActivity); + mChannelDataManager = appSingletons.getChannelDataManager(); + mScheduleManager = appSingletons.getDvrScheduleManager(); + mSessionManager = appSingletons.getInputSessionManager(); + } + + /** + * Starts checking the conflict. + */ + public void start() { + if (mStarted) { + return; + } + mStarted = true; + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + mScheduleManager.addScheduledRecordingListener(mScheduledRecordingListener); + mSessionManager.addOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener); + } + + /** + * Stops checking the conflict. + */ + public void stop() { + if (!mStarted) { + return; + } + mStarted = false; + mSessionManager.removeOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener); + mScheduleManager.removeScheduledRecordingListener(mScheduledRecordingListener); + mHandler.removeCallbacksAndMessages(null); + } + + /** + * Returns the upcoming conflicts. + */ + public List getUpcomingConflicts() { + return new ArrayList<>(mUpcomingConflicts); + } + + /** + * Adds a {@link OnUpcomingConflictChangeListener}. + */ + public void addOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { + mOnUpcomingConflictChangeListeners.add(listener); + } + + /** + * Removes the {@link OnUpcomingConflictChangeListener}. + */ + public void removeOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { + mOnUpcomingConflictChangeListeners.remove(listener); + } + + private void notifyUpcomingConflictChanged() { + for (OnUpcomingConflictChangeListener l : mOnUpcomingConflictChangeListeners) { + l.onUpcomingConflictChange(); + } + } + + /** + * Remembers the user's decision to record while watching the channel. + */ + public void setCheckedConflictsForChannel(long mChannelId, List conflicts) { + mCheckedConflictsMap.put(mChannelId, new ArrayList<>(conflicts)); + } + + void onCheckConflict() { + // Checks the conflicting schedules and setup the next re-check time. + // If there are upcoming conflicts soon, it opens the conflict dialog. + if (DEBUG) Log.d(TAG, "Handling MSG_CHECK_CONFLICT"); + mHandler.removeMessages(MSG_CHECK_CONFLICT); + mUpcomingConflicts.clear(); + if (!mScheduleManager.isInitialized() + || !mChannelDataManager.isDbLoadFinished()) { + mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, CHECK_RETRY_PERIOD_MS); + notifyUpcomingConflictChanged(); + return; + } + if (mSessionManager.getCurrentTvViewChannelUri() == null) { + // As MainActivity is not using a tuner, no need to check the conflict. + notifyUpcomingConflictChanged(); + return; + } + Uri channelUri = mSessionManager.getCurrentTvViewChannelUri(); + if (TvContract.isChannelUriForPassthroughInput(channelUri)) { + notifyUpcomingConflictChanged(); + return; + } + long channelId = ContentUris.parseId(channelUri); + Channel channel = mChannelDataManager.getChannel(channelId); + // The conflicts caused by watching the channel. + List conflicts = mScheduleManager + .getConflictingSchedulesForWatching(channel.getId()); + long earliestToCheck = Long.MAX_VALUE; + long currentTimeMs = System.currentTimeMillis(); + for (ScheduledRecording schedule : conflicts) { + long startTimeMs = schedule.getStartTimeMs(); + if (startTimeMs < currentTimeMs + MIN_WATCH_CONFLICT_CHECK_TIME_MS) { + // The start time of the upcoming conflict remains less than the minimum + // check time. + continue; + } + if (startTimeMs > currentTimeMs + MAX_WATCH_CONFLICT_CHECK_TIME_MS) { + // The start time of the upcoming conflict remains greater than the + // maximum check time. Setup the next re-check time. + long nextCheckTimeMs = startTimeMs - MAX_WATCH_CONFLICT_CHECK_TIME_MS; + if (earliestToCheck > nextCheckTimeMs) { + earliestToCheck = nextCheckTimeMs; + } + } else { + // Found upcoming conflicts which will start soon. + mUpcomingConflicts.add(schedule); + // The schedule will be removed from the "upcoming conflict" when the + // recording is almost started. + long nextCheckTimeMs = startTimeMs - MIN_WATCH_CONFLICT_CHECK_TIME_MS; + if (earliestToCheck > nextCheckTimeMs) { + earliestToCheck = nextCheckTimeMs; + } + } + } + if (earliestToCheck != Long.MAX_VALUE) { + mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, + earliestToCheck - currentTimeMs); + } + if (DEBUG) Log.d(TAG, "upcoming conflicts: " + mUpcomingConflicts); + notifyUpcomingConflictChanged(); + if (!mUpcomingConflicts.isEmpty() + && !DvrUiHelper.isChannelWatchConflictDialogShown(mMainActivity)) { + // Don't show the conflict dialog if the user already knows. + List checkedConflicts = mCheckedConflictsMap.get( + channel.getId()); + if (checkedConflicts == null + || !checkedConflicts.containsAll(mUpcomingConflicts)) { + DvrUiHelper.showChannelWatchConflictDialog(mMainActivity, channel); + } + } + } + + private static class ConflictCheckerHandler extends WeakHandler { + ConflictCheckerHandler(ConflictChecker conflictChecker) { + super(conflictChecker); + } + + @Override + protected void handleMessage(Message msg, @NonNull ConflictChecker conflictChecker) { + switch (msg.what) { + case MSG_CHECK_CONFLICT: + conflictChecker.onCheckConflict(); + break; + } + } + } + + /** + * A listener for the change of upcoming conflicts. + */ + public interface OnUpcomingConflictChangeListener { + void onUpcomingConflictChange(); + } +} diff --git a/src/com/android/tv/dvr/recorder/DvrRecordingService.java b/src/com/android/tv/dvr/recorder/DvrRecordingService.java new file mode 100644 index 00000000..5d324ca5 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/DvrRecordingService.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.InputSessionManager; +import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.util.Clock; +import com.android.tv.util.RecurringRunner; + +/** + * DVR recording service. This service should be a foreground service and send a notification + * to users to do long-running recording task. + * + *

This service is waken up when there's a scheduled recording coming soon and at boot completed + * since schedules have to be loaded from databases in order to set new recording alarms, which + * might take a long time. + */ +@RequiresApi(Build.VERSION_CODES.N) +public class DvrRecordingService extends Service { + private static final String TAG = "DvrRecordingService"; + private static final boolean DEBUG = false; + + private static final String DVR_NOTIFICATION_CHANNEL_ID = "dvr_notification_channel"; + private static final int ONGOING_NOTIFICATION_ID = 1; + @VisibleForTesting static final String EXTRA_START_FOR_RECORDING = "start_for_recording"; + + private static DvrRecordingService sInstance; + private NotificationChannel mNotificationChannel; + private String mContentTitle; + private String mContentTextRecording; + private String mContentTextLoading; + + /** + * Starts the service in foreground. + * + * @param startForRecording {@code true} if there are upcoming recordings in + * {@link RecordingScheduler#SOON_DURATION_IN_MS} and the service is + * started in foreground for those recordings. + */ + @MainThread + static void startForegroundService(Context context, boolean startForRecording) { + if (sInstance == null) { + Intent intent = new Intent(context, DvrRecordingService.class); + intent.putExtra(EXTRA_START_FOR_RECORDING, startForRecording); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + } else { + context.startService(intent); + } + } else { + sInstance.startForeground(startForRecording); + } + } + + @MainThread + static void stopForegroundIfNotRecording() { + if (sInstance != null) { + sInstance.stopForegroundIfNotRecordingInternal(); + } + } + + private RecurringRunner mReaperRunner; + private InputSessionManager mSessionManager; + + @VisibleForTesting boolean mIsRecording; + private boolean mForeground; + + @VisibleForTesting final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener = + new OnRecordingSessionChangeListener() { + @Override + public void onRecordingSessionChange(final boolean create, final int count) { + mIsRecording = count > 0; + if (create) { + startForeground(true); + } else { + stopForegroundIfNotRecordingInternal(); + } + } + }; + + @Override + public void onCreate() { + TvApplication.setCurrentRunningProcess(this, true); + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(); + SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG); + sInstance = this; + ApplicationSingletons singletons = TvApplication.getSingletons(this); + WritableDvrDataManager dataManager = + (WritableDvrDataManager) singletons.getDvrDataManager(); + mSessionManager = singletons.getInputSessionManager(); + mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener); + mReaperRunner = new RecurringRunner(this, java.util.concurrent.TimeUnit.DAYS.toMillis(1), + new ScheduledProgramReaper(dataManager, Clock.SYSTEM), null); + mReaperRunner.start(); + mContentTitle = getString(R.string.dvr_notification_content_title); + mContentTextRecording = getString(R.string.dvr_notification_content_text_recording); + mContentTextLoading = getString(R.string.dvr_notification_content_text_loading); + createNotificationChannel(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (DEBUG) Log.d(TAG, "onStartCommand (" + intent + "," + flags + "," + startId + ")"); + if (intent != null) { + startForeground(intent.getBooleanExtra(EXTRA_START_FOR_RECORDING, false)); + } + return START_STICKY; + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + mReaperRunner.stop(); + mSessionManager.removeRecordingSessionChangeListener(mOnRecordingSessionChangeListener); + sInstance = null; + super.onDestroy(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @VisibleForTesting + protected void stopForegroundIfNotRecordingInternal() { + if (mForeground && !mIsRecording) { + stopForeground(); + } + } + + private void startForeground(boolean hasUpcomingRecording) { + if (!mForeground || hasUpcomingRecording) { + // We may need to update notification for upcoming recordings. + mForeground = true; + startForegroundInternal(hasUpcomingRecording); + } + } + + private void stopForeground() { + stopForegroundInternal(); + mForeground = false; + } + + @VisibleForTesting + protected void startForegroundInternal(boolean hasUpcomingRecording) { + // STOPSHIP: Replace the content title with real UX strings + Notification.Builder builder = new Notification.Builder(this) + .setContentTitle(mContentTitle) + .setContentText(hasUpcomingRecording ? mContentTextRecording : mContentTextLoading) + .setSmallIcon(R.drawable.ic_dvr); + Notification notification = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build() : builder.build(); + startForeground(ONGOING_NOTIFICATION_ID, notification); + } + + @VisibleForTesting + protected void stopForegroundInternal() { + stopForeground(true); + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // STOPSHIP: Replace the channel name with real UX strings + mNotificationChannel = new NotificationChannel(DVR_NOTIFICATION_CHANNEL_ID, + getString(R.string.dvr_notification_channel_name), + NotificationManager.IMPORTANCE_LOW); + ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) + .createNotificationChannel(mNotificationChannel); + } + } +} diff --git a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java new file mode 100644 index 00000000..f1c0020b --- /dev/null +++ b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; + +import com.android.tv.TvApplication; + +/** + * Signals the DVR to start recording shows soon. + */ +@RequiresApi(Build.VERSION_CODES.N) +public class DvrStartRecordingReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + TvApplication.setCurrentRunningProcess(context, true); + RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler(); + if (scheduler != null) { + scheduler.updateAndStartServiceIfNeeded(); + } + } +} diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java new file mode 100644 index 00000000..fee4568e --- /dev/null +++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import android.content.Context; +import android.media.tv.TvInputInfo; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import android.util.Log; +import android.util.LongSparseArray; + +import com.android.tv.InputSessionManager; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.util.Clock; +import com.android.tv.util.CompositeComparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * The scheduler for a TV input. + */ +public class InputTaskScheduler { + private static final String TAG = "InputTaskScheduler"; + private static final boolean DEBUG = false; + + private static final int MSG_ADD_SCHEDULED_RECORDING = 1; + private static final int MSG_REMOVE_SCHEDULED_RECORDING = 2; + private static final int MSG_UPDATE_SCHEDULED_RECORDING = 3; + private static final int MSG_BUILD_SCHEDULE = 4; + private static final int MSG_STOP_SCHEDULE = 5; + + private static final float MIN_REMAIN_DURATION_PERCENT = 0.05f; + + // The candidate comparator should be the consistent with + // DvrScheduleManager#CANDIDATE_COMPARATOR. + private static final Comparator CANDIDATE_COMPARATOR = + new CompositeComparator<>( + RecordingTask.PRIORITY_COMPARATOR, + RecordingTask.END_TIME_COMPARATOR, + RecordingTask.ID_COMPARATOR); + + /** + * Returns the comparator which the schedules are sorted with when executed. + */ + public static Comparator getRecordingOrderComparator() { + return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR; + } + + /** + * Wraps a {@link RecordingTask} removing it from {@link #mPendingRecordings} when it is done. + */ + public final class HandlerWrapper extends Handler { + public static final int MESSAGE_REMOVE = 999; + private final long mId; + private final RecordingTask mTask; + + HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording, + RecordingTask recordingTask) { + super(looper, recordingTask); + mId = scheduledRecording.getId(); + mTask = recordingTask; + mTask.setHandler(this); + } + + @Override + public void handleMessage(Message msg) { + // The RecordingTask gets a chance first. + // It must return false to pass this message to here. + if (msg.what == MESSAGE_REMOVE) { + if (DEBUG) Log.d(TAG, "done " + mId); + mPendingRecordings.remove(mId); + } + removeCallbacksAndMessages(null); + mHandler.removeMessages(MSG_BUILD_SCHEDULE); + mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); + super.handleMessage(msg); + } + } + + private TvInputInfo mInput; + private final Looper mLooper; + private final ChannelDataManager mChannelDataManager; + private final DvrManager mDvrManager; + private final WritableDvrDataManager mDataManager; + private final InputSessionManager mSessionManager; + private final Clock mClock; + private final Context mContext; + + private final LongSparseArray mPendingRecordings = new LongSparseArray<>(); + private final Map mWaitingSchedules = new ArrayMap<>(); + private final Handler mMainThreadHandler; + private final Handler mHandler; + private final Object mInputLock = new Object(); + private final RecordingTaskFactory mRecordingTaskFactory; + + public InputTaskScheduler(Context context, TvInputInfo input, Looper looper, + ChannelDataManager channelDataManager, DvrManager dvrManager, + DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock) { + this(context, input, looper, channelDataManager, dvrManager, dataManager, sessionManager, + clock, null); + } + + @VisibleForTesting + InputTaskScheduler(Context context, TvInputInfo input, Looper looper, + ChannelDataManager channelDataManager, DvrManager dvrManager, + DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock, + RecordingTaskFactory recordingTaskFactory) { + if (DEBUG) Log.d(TAG, "Creating scheduler for " + input); + mContext = context; + mInput = input; + mLooper = looper; + mChannelDataManager = channelDataManager; + mDvrManager = dvrManager; + mDataManager = (WritableDvrDataManager) dataManager; + mSessionManager = sessionManager; + mClock = clock; + mMainThreadHandler = new Handler(Looper.getMainLooper()); + mRecordingTaskFactory = recordingTaskFactory != null ? recordingTaskFactory + : new RecordingTaskFactory() { + @Override + public RecordingTask createRecordingTask(ScheduledRecording schedule, Channel channel, + DvrManager dvrManager, InputSessionManager sessionManager, + WritableDvrDataManager dataManager, Clock clock) { + return new RecordingTask(mContext, schedule, channel, mDvrManager, mSessionManager, + mDataManager, mClock); + } + }; + mHandler = new WorkerThreadHandler(looper); + } + + /** + * Adds a {@link ScheduledRecording}. + */ + public void addSchedule(ScheduledRecording schedule) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule)); + } + + @VisibleForTesting + void handleAddSchedule(ScheduledRecording schedule) { + if (mPendingRecordings.get(schedule.getId()) != null + || mWaitingSchedules.containsKey(schedule.getId())) { + return; + } + mWaitingSchedules.put(schedule.getId(), schedule); + mHandler.removeMessages(MSG_BUILD_SCHEDULE); + mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); + } + + /** + * Removes the {@link ScheduledRecording}. + */ + public void removeSchedule(ScheduledRecording schedule) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule)); + } + + @VisibleForTesting + void handleRemoveSchedule(ScheduledRecording schedule) { + HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId()); + if (wrapper != null) { + wrapper.mTask.cancel(); + return; + } + if (mWaitingSchedules.containsKey(schedule.getId())) { + mWaitingSchedules.remove(schedule.getId()); + mHandler.removeMessages(MSG_BUILD_SCHEDULE); + mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); + } + } + + /** + * Updates the {@link ScheduledRecording}. + */ + public void updateSchedule(ScheduledRecording schedule) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule)); + } + + @VisibleForTesting + void handleUpdateSchedule(ScheduledRecording schedule) { + HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId()); + if (wrapper != null) { + if (schedule.getStartTimeMs() > mClock.currentTimeMillis() + && schedule.getStartTimeMs() > wrapper.mTask.getStartTimeMs()) { + // It shouldn't have started. Cancel and put to the waiting list. + // The schedules will be rebuilt when the task is removed. + // The reschedule is called in RecordingScheduler. + wrapper.mTask.cancel(); + mWaitingSchedules.put(schedule.getId(), schedule); + return; + } + wrapper.sendMessage(wrapper.obtainMessage(RecordingTask.MSG_UDPATE_SCHEDULE, schedule)); + return; + } + if (mWaitingSchedules.containsKey(schedule.getId())) { + mWaitingSchedules.put(schedule.getId(), schedule); + mHandler.removeMessages(MSG_BUILD_SCHEDULE); + mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); + } + } + + /** + * Updates the TV input. + */ + public void updateTvInputInfo(TvInputInfo input) { + synchronized (mInputLock) { + mInput = input; + } + } + + /** + * Stops the input task scheduler. + */ + public void stop() { + mHandler.removeCallbacksAndMessages(null); + mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE); + } + + private void handleStopSchedule() { + mWaitingSchedules.clear(); + int size = mPendingRecordings.size(); + for (int i = 0; i < size; ++i) { + RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; + task.cleanUp(); + } + } + + @VisibleForTesting + void handleBuildSchedule() { + if (mWaitingSchedules.isEmpty()) { + return; + } + long currentTimeMs = mClock.currentTimeMillis(); + // Remove past schedules. + for (Iterator iter = mWaitingSchedules.values().iterator(); + iter.hasNext(); ) { + ScheduledRecording schedule = iter.next(); + if (schedule.getEndTimeMs() - currentTimeMs + <= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) { + fail(schedule); + iter.remove(); + } + } + if (mWaitingSchedules.isEmpty()) { + return; + } + // Record the schedules which should start now. + List schedulesToStart = new ArrayList<>(); + for (ScheduledRecording schedule : mWaitingSchedules.values()) { + if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED + && schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS + <= currentTimeMs && schedule.getEndTimeMs() > currentTimeMs) { + schedulesToStart.add(schedule); + } + } + // The schedules will be executed with the following order. + // 1. The schedule which starts early. It can be replaced later when the schedule with the + // higher priority needs to start. + // 2. The schedule with the higher priority. It can be replaced later when the schedule with + // the higher priority needs to start. + // 3. The schedule which was created recently. + Collections.sort(schedulesToStart, getRecordingOrderComparator()); + int tunerCount; + synchronized (mInputLock) { + tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0; + } + for (ScheduledRecording schedule : schedulesToStart) { + if (hasTaskWhichFinishEarlier(schedule)) { + // If there is a schedule which finishes earlier than the new schedule, rebuild the + // schedules after it finishes. + return; + } + if (mPendingRecordings.size() < tunerCount) { + // Tuners available. + createRecordingTask(schedule).start(); + mWaitingSchedules.remove(schedule.getId()); + } else { + // No available tuners. + RecordingTask task = getReplacableTask(schedule); + if (task != null) { + task.stop(); + // Just return. The schedules will be rebuilt after the task is stopped. + return; + } + } + } + if (mWaitingSchedules.isEmpty()) { + return; + } + // Set next scheduling. + long earliest = Long.MAX_VALUE; + for (ScheduledRecording schedule : mWaitingSchedules.values()) { + // The conflicting schedules will be removed if they end before conflicting resolved. + if (schedulesToStart.contains(schedule)) { + if (earliest > schedule.getEndTimeMs()) { + earliest = schedule.getEndTimeMs(); + } + } else { + if (earliest > schedule.getStartTimeMs() + - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) { + earliest = schedule.getStartTimeMs() + - RecordingTask.RECORDING_EARLY_START_OFFSET_MS; + } + } + } + mHandler.sendEmptyMessageDelayed(MSG_BUILD_SCHEDULE, earliest - currentTimeMs); + } + + private RecordingTask createRecordingTask(ScheduledRecording schedule) { + Channel channel = mChannelDataManager.getChannel(schedule.getChannelId()); + RecordingTask recordingTask = mRecordingTaskFactory.createRecordingTask(schedule, channel, + mDvrManager, mSessionManager, mDataManager, mClock); + HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask); + mPendingRecordings.put(schedule.getId(), handlerWrapper); + return recordingTask; + } + + private boolean hasTaskWhichFinishEarlier(ScheduledRecording schedule) { + int size = mPendingRecordings.size(); + for (int i = 0; i < size; ++i) { + RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; + if (task.getEndTimeMs() <= schedule.getStartTimeMs()) { + return true; + } + } + return false; + } + + private RecordingTask getReplacableTask(ScheduledRecording schedule) { + // Returns the recording with the following priority. + // 1. The recording with the lowest priority is returned. + // 2. If the priorities are the same, the recording which finishes early is returned. + // 3. If 1) and 2) are the same, the early created schedule is returned. + int size = mPendingRecordings.size(); + RecordingTask candidate = null; + for (int i = 0; i < size; ++i) { + RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask; + if (schedule.getPriority() > task.getPriority()) { + if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, task) > 0) { + candidate = task; + } + } + } + return candidate; + } + + private void fail(ScheduledRecording schedule) { + // It's called when the scheduling has been failed without creating RecordingTask. + runOnMainHandler(new Runnable() { + @Override + public void run() { + ScheduledRecording scheduleInManager = + mDataManager.getScheduledRecording(schedule.getId()); + if (scheduleInManager != null) { + // The schedule should be updated based on the object from DataManager in case + // when it has been updated. + mDataManager.changeState(scheduleInManager, + ScheduledRecording.STATE_RECORDING_FAILED); + } + } + }); + } + + private void runOnMainHandler(Runnable runnable) { + if (Looper.myLooper() == mMainThreadHandler.getLooper()) { + runnable.run(); + } else { + mMainThreadHandler.post(runnable); + } + } + + @VisibleForTesting + interface RecordingTaskFactory { + RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, Channel channel, + DvrManager dvrManager, InputSessionManager sessionManager, + WritableDvrDataManager dataManager, Clock clock); + } + + private class WorkerThreadHandler extends Handler { + public WorkerThreadHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ADD_SCHEDULED_RECORDING: + handleAddSchedule((ScheduledRecording) msg.obj); + break; + case MSG_REMOVE_SCHEDULED_RECORDING: + handleRemoveSchedule((ScheduledRecording) msg.obj); + break; + case MSG_UPDATE_SCHEDULED_RECORDING: + handleUpdateSchedule((ScheduledRecording) msg.obj); + case MSG_BUILD_SCHEDULE: + handleBuildSchedule(); + break; + case MSG_STOP_SCHEDULE: + handleStopSchedule(); + break; + } + } + } +} diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java new file mode 100644 index 00000000..cbaf46b5 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager.TvInputCallback; +import android.os.Build; +import android.os.HandlerThread; +import android.os.Looper; +import android.support.annotation.MainThread; +import android.support.annotation.RequiresApi; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.InputSessionManager; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ChannelDataManager.Listener; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.util.Clock; +import com.android.tv.util.TvInputManagerHelper; +import com.android.tv.util.Utils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * The core class to manage DVR schedule and run recording task. + ** + *

This class is responsible for: + *

    + *
  • Sending record commands to TV inputs
  • + *
  • Resolving conflicting schedules, handling overlapping recording time durations, etc.
  • + *
+ * + *

This should be a singleton associated with application's main process. + */ +@RequiresApi(Build.VERSION_CODES.N) +@MainThread +public class RecordingScheduler extends TvInputCallback implements ScheduledRecordingListener { + private static final String TAG = "RecordingScheduler"; + private static final boolean DEBUG = false; + + private static final String HANDLER_THREAD_NAME = "RecordingScheduler"; + private final static long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(1); + @VisibleForTesting final static long MS_TO_WAKE_BEFORE_START = TimeUnit.SECONDS.toMillis(30); + + private final Looper mLooper; + private final InputSessionManager mSessionManager; + private final WritableDvrDataManager mDataManager; + private final DvrManager mDvrManager; + private final ChannelDataManager mChannelDataManager; + private final TvInputManagerHelper mInputManager; + private final Context mContext; + private final Clock mClock; + private final AlarmManager mAlarmManager; + + private final Map mInputSchedulerMap = new ArrayMap<>(); + private long mLastStartTimePendingMs; + + private OnDvrScheduleLoadFinishedListener mDvrScheduleLoadListener = + new OnDvrScheduleLoadFinishedListener() { + @Override + public void onDvrScheduleLoadFinished() { + mDataManager.removeDvrScheduleLoadFinishedListener(this); + if (isDbLoaded()) { + updateInternal(); + } + } + }; + + private Listener mChannelDataLoadListener = new Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + if (isDbLoaded()) { + updateInternal(); + } + } + + @Override + public void onChannelListUpdated() { } + + @Override + public void onChannelBrowsableChanged() { } + }; + + /** + * Creates a scheduler to schedule alarms for scheduled recordings and create recording tasks. + * This method should be only called once in the life-cycle of the application. + */ + public static RecordingScheduler createScheduler(Context context) { + SoftPreconditions.checkState( + TvApplication.getSingletons(context).getRecordingScheduler() == null); + HandlerThread handlerThread = new HandlerThread(HANDLER_THREAD_NAME); + handlerThread.start(); + ApplicationSingletons singletons = TvApplication.getSingletons(context); + return new RecordingScheduler(handlerThread.getLooper(), + singletons.getDvrManager(), singletons.getInputSessionManager(), + (WritableDvrDataManager) singletons.getDvrDataManager(), + singletons.getChannelDataManager(), singletons.getTvInputManagerHelper(), context, + Clock.SYSTEM, (AlarmManager) context.getSystemService(Context.ALARM_SERVICE)); + } + + @VisibleForTesting + RecordingScheduler(Looper looper, DvrManager dvrManager, InputSessionManager sessionManager, + WritableDvrDataManager dataManager, ChannelDataManager channelDataManager, + TvInputManagerHelper inputManager, Context context, Clock clock, + AlarmManager alarmManager) { + mLooper = looper; + mDvrManager = dvrManager; + mSessionManager = sessionManager; + mDataManager = dataManager; + mChannelDataManager = channelDataManager; + mInputManager = inputManager; + mContext = context; + mClock = clock; + mAlarmManager = alarmManager; + mDataManager.addScheduledRecordingListener(this); + mInputManager.addCallback(this); + if (isDbLoaded()) { + updateInternal(); + } else { + if (!mDataManager.isDvrScheduleLoadFinished()) { + mDataManager.addDvrScheduleLoadFinishedListener(mDvrScheduleLoadListener); + } + if (!mChannelDataManager.isDbLoadFinished()) { + mChannelDataManager.addListener(mChannelDataLoadListener); + } + } + } + + /** + * Start recording that will happen soon, and set the next alarm time. + */ + public void updateAndStartServiceIfNeeded() { + if (DEBUG) Log.d(TAG, "update and start service if needed"); + if (isDbLoaded()) { + updateInternal(); + } else { + // updateInternal will be called when DB is loaded. Start DvrRecordingService to + // prevent process being killed before that. + DvrRecordingService.startForegroundService(mContext, false); + } + } + + private void updateInternal() { + boolean recordingSoon = updatePendingRecordings(); + updateNextAlarm(); + if (recordingSoon) { + // Start DvrRecordingService to protect upcoming recording task from being killed. + DvrRecordingService.startForegroundService(mContext, true); + } else { + DvrRecordingService.stopForegroundIfNotRecording(); + } + } + + private boolean updatePendingRecordings() { + List scheduledRecordings = mDataManager + .getScheduledRecordings(new Range<>(mLastStartTimePendingMs, + mClock.currentTimeMillis() + SOON_DURATION_IN_MS), + ScheduledRecording.STATE_RECORDING_NOT_STARTED); + for (ScheduledRecording r : scheduledRecordings) { + scheduleRecordingSoon(r); + } + // update() may be called multiple times, under this situation, pending recordings may be + // already updated thus scheduledRecordings may have a size of 0. Therefore we also have to + // check mLastStartTimePendingMs to check if we have upcoming recordings and prevent the + // recording service being wrongly pushed back to background in updateInternal(). + return scheduledRecordings.size() > 0 + || (mLastStartTimePendingMs > mClock.currentTimeMillis() + && mLastStartTimePendingMs < mClock.currentTimeMillis() + SOON_DURATION_IN_MS); + } + + private boolean isDbLoaded() { + return mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished(); + } + + @Override + public void onScheduledRecordingAdded(ScheduledRecording... schedules) { + if (DEBUG) Log.d(TAG, "added " + Arrays.asList(schedules)); + if (!isDbLoaded()) { + return; + } + handleScheduleChange(schedules); + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { + if (DEBUG) Log.d(TAG, "removed " + Arrays.asList(schedules)); + if (!isDbLoaded()) { + return; + } + boolean needToUpdateAlarm = false; + for (ScheduledRecording schedule : schedules) { + InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(schedule.getInputId()); + if (inputTaskScheduler != null) { + inputTaskScheduler.removeSchedule(schedule); + needToUpdateAlarm = true; + } + } + if (needToUpdateAlarm) { + updateNextAlarm(); + } + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { + if (DEBUG) Log.d(TAG, "state changed " + Arrays.asList(schedules)); + if (!isDbLoaded()) { + return; + } + // Update the recordings. + for (ScheduledRecording schedule : schedules) { + InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(schedule.getInputId()); + if (inputTaskScheduler != null) { + inputTaskScheduler.updateSchedule(schedule); + } + } + handleScheduleChange(schedules); + } + + private void handleScheduleChange(ScheduledRecording... schedules) { + boolean needToUpdateAlarm = false; + for (ScheduledRecording schedule : schedules) { + if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { + if (startsWithin(schedule, SOON_DURATION_IN_MS)) { + scheduleRecordingSoon(schedule); + } else { + needToUpdateAlarm = true; + } + } + } + if (needToUpdateAlarm) { + updateNextAlarm(); + } + } + + private void scheduleRecordingSoon(ScheduledRecording schedule) { + TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); + if (input == null) { + Log.e(TAG, "Can't find input for " + schedule); + mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); + return; + } + if (!input.canRecord() || input.getTunerCount() <= 0) { + Log.e(TAG, "TV input doesn't support recording: " + input); + mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); + return; + } + InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(input.getId()); + if (inputTaskScheduler == null) { + inputTaskScheduler = new InputTaskScheduler(mContext, input, mLooper, + mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mClock); + mInputSchedulerMap.put(input.getId(), inputTaskScheduler); + } + inputTaskScheduler.addSchedule(schedule); + if (mLastStartTimePendingMs < schedule.getStartTimeMs()) { + mLastStartTimePendingMs = schedule.getStartTimeMs(); + } + } + + private void updateNextAlarm() { + long nextStartTime = mDataManager.getNextScheduledStartTimeAfter( + Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis())); + if (nextStartTime != DvrDataManager.NEXT_START_TIME_NOT_FOUND) { + long wakeAt = nextStartTime - MS_TO_WAKE_BEFORE_START; + if (DEBUG) Log.d(TAG, "Set alarm to record at " + wakeAt); + Intent intent = new Intent(mContext, DvrStartRecordingReceiver.class); + PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + // This will cancel the previous alarm. + mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeAt, alarmIntent); + } else { + if (DEBUG) Log.d(TAG, "No future recording, alarm not set"); + } + } + + @VisibleForTesting + boolean startsWithin(ScheduledRecording scheduledRecording, long durationInMs) { + return mClock.currentTimeMillis() >= scheduledRecording.getStartTimeMs() - durationInMs; + } + + // No need to remove input task schedule worker when the input is removed. If the input is + // removed temporarily, the scheduler should keep the non-started schedules. + @Override + public void onInputUpdated(String inputId) { + InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(inputId); + if (inputTaskScheduler != null) { + inputTaskScheduler.updateTvInputInfo(Utils.getTvInputInfoForInputId(mContext, inputId)); + } + } + + @Override + public void onTvInputInfoUpdated(TvInputInfo input) { + InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(input.getId()); + if (inputTaskScheduler != null) { + inputTaskScheduler.updateTvInputInfo(input); + } + } +} diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java new file mode 100644 index 00000000..14888056 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/RecordingTask.java @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.tv.TvContract; +import android.media.tv.TvInputManager; +import android.media.tv.TvRecordingClient.RecordingCallback; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; +import android.util.Log; +import android.widget.Toast; + +import com.android.tv.InputSessionManager; +import com.android.tv.InputSessionManager.RecordingSession; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Channel; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper; +import com.android.tv.util.Clock; +import com.android.tv.util.Utils; + +import java.util.Comparator; +import java.util.concurrent.TimeUnit; + +/** + * A Handler that actually starts and stop a recording at the right time. + * + *

This is run on the looper of thread named {@value DvrRecordingService#HANDLER_THREAD_NAME}. + * There is only one looper so messages must be handled quickly or start a separate thread. + */ +@WorkerThread +@TargetApi(Build.VERSION_CODES.N) +public class RecordingTask extends RecordingCallback implements Handler.Callback, + DvrManager.Listener { + private static final String TAG = "RecordingTask"; + private static final boolean DEBUG = false; + + /** + * Compares the end time in ascending order. + */ + public static final Comparator END_TIME_COMPARATOR + = new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs()); + } + }; + + /** + * Compares ID in ascending order. + */ + public static final Comparator ID_COMPARATOR + = new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getScheduleId(), rhs.getScheduleId()); + } + }; + + /** + * Compares the priority in ascending order. + */ + public static final Comparator PRIORITY_COMPARATOR + = new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getPriority(), rhs.getPriority()); + } + }; + + @VisibleForTesting + static final int MSG_INITIALIZE = 1; + @VisibleForTesting + static final int MSG_START_RECORDING = 2; + @VisibleForTesting + static final int MSG_STOP_RECORDING = 3; + /** + * Message to update schedule. + */ + public static final int MSG_UDPATE_SCHEDULE = 4; + + /** + * The time when the start command will be sent before the recording starts. + */ + public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3); + /** + * If the recording starts later than the scheduled start time or ends before the scheduled end + * time, it's considered as clipped. + */ + private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); + + @VisibleForTesting + enum State { + NOT_STARTED, + SESSION_ACQUIRED, + CONNECTION_PENDING, + CONNECTED, + RECORDING_STARTED, + RECORDING_STOP_REQUESTED, + FINISHED, + ERROR, + RELEASED, + } + private final InputSessionManager mSessionManager; + private final DvrManager mDvrManager; + private final Context mContext; + + private final WritableDvrDataManager mDataManager; + private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); + private RecordingSession mRecordingSession; + private Handler mHandler; + private ScheduledRecording mScheduledRecording; + private final Channel mChannel; + private State mState = State.NOT_STARTED; + private final Clock mClock; + private boolean mStartedWithClipping; + private Uri mRecordedProgramUri; + private boolean mCanceled; + + RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel, + DvrManager dvrManager, InputSessionManager sessionManager, + WritableDvrDataManager dataManager, Clock clock) { + mContext = context; + mScheduledRecording = scheduledRecording; + mChannel = channel; + mSessionManager = sessionManager; + mDataManager = dataManager; + mClock = clock; + mDvrManager = dvrManager; + + if (DEBUG) Log.d(TAG, "created recording task " + mScheduledRecording); + } + + public void setHandler(Handler handler) { + mHandler = handler; + } + + @Override + public boolean handleMessage(Message msg) { + if (DEBUG) Log.d(TAG, "handleMessage " + msg); + SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null, + TAG, "Null handler trying to handle " + msg); + try { + switch (msg.what) { + case MSG_INITIALIZE: + handleInit(); + break; + case MSG_START_RECORDING: + handleStartRecording(); + break; + case MSG_STOP_RECORDING: + handleStopRecording(); + break; + case MSG_UDPATE_SCHEDULE: + handleUpdateSchedule((ScheduledRecording) msg.obj); + break; + case HandlerWrapper.MESSAGE_REMOVE: + mHandler.removeCallbacksAndMessages(null); + mHandler = null; + release(); + return false; + default: + SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg); + break; + } + return true; + } catch (Exception e) { + Log.w(TAG, "Error processing message " + msg + " for " + mScheduledRecording, e); + failAndQuit(); + } + return false; + } + + @Override + public void onDisconnected(String inputId) { + if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")"); + if (mRecordingSession != null && mState != State.FINISHED) { + failAndQuit(); + } + } + + @Override + public void onConnectionFailed(String inputId) { + if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")"); + if (mRecordingSession != null) { + failAndQuit(); + } + } + + @Override + public void onTuned(Uri channelUri) { + if (DEBUG) Log.d(TAG, "onTuned"); + if (mRecordingSession == null) { + return; + } + mState = State.CONNECTED; + if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING, + mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) { + failAndQuit(); + } + } + + @Override + public void onRecordingStopped(Uri recordedProgramUri) { + if (DEBUG) Log.d(TAG, "onRecordingStopped"); + if (mRecordingSession == null) { + return; + } + mRecordedProgramUri = recordedProgramUri; + mState = State.FINISHED; + int state = ScheduledRecording.STATE_RECORDING_FINISHED; + if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS + > mClock.currentTimeMillis()) { + state = ScheduledRecording.STATE_RECORDING_CLIPPED; + } + updateRecordingState(state); + sendRemove(); + if (mCanceled) { + removeRecordedProgram(); + } + } + + @Override + public void onError(int reason) { + if (DEBUG) Log.d(TAG, "onError reason " + reason); + if (mRecordingSession == null) { + return; + } + switch (reason) { + case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE: + mMainThreadHandler.post(new Runnable() { + @Override + public void run() { + if (TvApplication.getSingletons(mContext).getMainActivityWrapper() + .isResumed()) { + ScheduledRecording scheduledRecording = mDataManager + .getScheduledRecording(mScheduledRecording.getId()); + if (scheduledRecording != null) { + Toast.makeText(mContext.getApplicationContext(), + mContext.getString(R.string + .dvr_error_insufficient_space_description_one_recording, + scheduledRecording.getProgramDisplayTitle(mContext)), + Toast.LENGTH_LONG) + .show(); + } + } else { + Utils.setRecordingFailedReason(mContext.getApplicationContext(), + TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(), + mScheduledRecording.getProgramDisplayTitle(mContext)); + } + } + }); + // Pass through + default: + failAndQuit(); + break; + } + } + + private void handleInit() { + if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording); + if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) { + Log.w(TAG, "End time already past, not recording " + mScheduledRecording); + failAndQuit(); + return; + } + if (mChannel == null) { + Log.w(TAG, "Null channel for " + mScheduledRecording); + failAndQuit(); + return; + } + if (mChannel.getId() != mScheduledRecording.getChannelId()) { + Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording " + + mScheduledRecording); + failAndQuit(); + return; + } + + String inputId = mChannel.getInputId(); + mRecordingSession = mSessionManager.createRecordingSession(inputId, + "recordingTask-" + mScheduledRecording.getId(), this, + mHandler, mScheduledRecording.getEndTimeMs()); + mState = State.SESSION_ACQUIRED; + mDvrManager.addListener(this, mHandler); + mRecordingSession.tune(inputId, mChannel.getUri()); + mState = State.CONNECTION_PENDING; + } + + private void failAndQuit() { + if (DEBUG) Log.d(TAG, "failAndQuit"); + updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); + mState = State.ERROR; + sendRemove(); + } + + private void sendRemove() { + if (DEBUG) Log.d(TAG, "sendRemove"); + if (mHandler != null) { + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage( + HandlerWrapper.MESSAGE_REMOVE)); + } + } + + private void handleStartRecording() { + if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording); + long programId = mScheduledRecording.getProgramId(); + mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null + : TvContract.buildProgramUri(programId)); + updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS); + // If it starts late, it's clipped. + if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS + < mClock.currentTimeMillis()) { + mStartedWithClipping = true; + } + mState = State.RECORDING_STARTED; + + if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, + mScheduledRecording.getEndTimeMs())) { + failAndQuit(); + } + } + + private void handleStopRecording() { + if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording); + mRecordingSession.stopRecording(); + mState = State.RECORDING_STOP_REQUESTED; + } + + private void handleUpdateSchedule(ScheduledRecording schedule) { + mScheduledRecording = schedule; + // Check end time only. The start time is checked in InputTaskScheduler. + if (schedule.getEndTimeMs() != mScheduledRecording.getEndTimeMs()) { + if (mRecordingSession != null) { + mRecordingSession.setEndTimeMs(schedule.getEndTimeMs()); + } + if (mState == State.RECORDING_STARTED) { + mHandler.removeMessages(MSG_STOP_RECORDING); + if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) { + failAndQuit(); + } + } + } + } + + @VisibleForTesting + State getState() { + return mState; + } + + private long getScheduleId() { + return mScheduledRecording.getId(); + } + + /** + * Returns the priority. + */ + public long getPriority() { + return mScheduledRecording.getPriority(); + } + + /** + * Returns the start time of the recording. + */ + public long getStartTimeMs() { + return mScheduledRecording.getStartTimeMs(); + } + + /** + * Returns the end time of the recording. + */ + public long getEndTimeMs() { + return mScheduledRecording.getEndTimeMs(); + } + + private void release() { + if (mRecordingSession != null) { + mSessionManager.releaseRecordingSession(mRecordingSession); + mRecordingSession = null; + } + mDvrManager.removeListener(this); + } + + private boolean sendEmptyMessageAtAbsoluteTime(int what, long when) { + long now = mClock.currentTimeMillis(); + long delay = Math.max(0L, when - now); + if (DEBUG) { + Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000 + + " seconds to arrive at " + Utils.toIsoDateTimeString(when)); + } + return mHandler.sendEmptyMessageDelayed(what, delay); + } + + private void updateRecordingState(@ScheduledRecording.RecordingState int state) { + if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state); + mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state) + .build(); + runOnMainThread(new Runnable() { + @Override + public void run() { + ScheduledRecording schedule = mDataManager.getScheduledRecording( + mScheduledRecording.getId()); + if (schedule == null) { + // Schedule has been deleted. Delete the recorded program. + removeRecordedProgram(); + } else { + // Update the state based on the object in DataManager in case when it has been + // updated. mScheduledRecording will be updated from + // onScheduledRecordingStateChanged. + mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule) + .setState(state).build()); + } + } + }); + } + + @Override + public void onStopRecordingRequested(ScheduledRecording recording) { + if (recording.getId() != mScheduledRecording.getId()) { + return; + } + stop(); + } + + /** + * Starts the task. + */ + public void start() { + mHandler.sendEmptyMessage(MSG_INITIALIZE); + } + + /** + * Stops the task. + */ + public void stop() { + if (DEBUG) Log.d(TAG, "stop"); + switch (mState) { + case RECORDING_STARTED: + mHandler.removeMessages(MSG_STOP_RECORDING); + handleStopRecording(); + break; + case RECORDING_STOP_REQUESTED: + // Do nothing + break; + case NOT_STARTED: + case SESSION_ACQUIRED: + case CONNECTION_PENDING: + case CONNECTED: + case FINISHED: + case ERROR: + case RELEASED: + default: + sendRemove(); + break; + } + } + + /** + * Cancels the task + */ + public void cancel() { + if (DEBUG) Log.d(TAG, "cancel"); + mCanceled = true; + stop(); + removeRecordedProgram(); + } + + /** + * Clean up the task. + */ + public void cleanUp() { + if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) { + updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); + } + release(); + if (mHandler != null) { + mHandler.removeCallbacksAndMessages(null); + } + } + + @Override + public String toString() { + return getClass().getName() + "(" + mScheduledRecording + ")"; + } + + private void removeRecordedProgram() { + runOnMainThread(new Runnable() { + @Override + public void run() { + if (mRecordedProgramUri != null) { + mDvrManager.removeRecordedProgram(mRecordedProgramUri); + } + } + }); + } + + private void runOnMainThread(Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + mMainThreadHandler.post(runnable); + } + } +} diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java new file mode 100644 index 00000000..d958c4a1 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import android.support.annotation.MainThread; +import android.support.annotation.VisibleForTesting; + +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.util.Clock; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Deletes {@link ScheduledRecording} older than {@value @DAYS} days. + */ +class ScheduledProgramReaper implements Runnable { + + @VisibleForTesting + static final int DAYS = 2; + private final WritableDvrDataManager mDvrDataManager; + private final Clock mClock; + + ScheduledProgramReaper(WritableDvrDataManager dvrDataManager, Clock clock) { + mDvrDataManager = dvrDataManager; + mClock = clock; + } + + @Override + @MainThread + public void run() { + long cutoff = mClock.currentTimeMillis() - TimeUnit.DAYS.toMillis(DAYS); + List toRemove = new ArrayList<>(); + for (ScheduledRecording r : mDvrDataManager.getAllScheduledRecordings()) { + // Do not remove the schedules if it belongs to the series recording and was finished + // successfully. The schedule is necessary for checking the scheduled episode of the + // series recording. + if (r.getEndTimeMs() < cutoff + && (r.getSeriesRecordingId() == SeriesRecording.ID_NOT_SET + || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) { + toRemove.add(r); + } + } + for (ScheduledRecording r : mDvrDataManager.getDeletedSchedules()) { + if (r.getEndTimeMs() < cutoff) { + toRemove.add(r); + } + } + if (!toRemove.isEmpty()) { + mDvrDataManager.removeScheduledRecording(ScheduledRecording.toArray(toRemove)); + } + } +} diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java new file mode 100644 index 00000000..15508c24 --- /dev/null +++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.MainThread; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.LongSparseArray; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.TvApplication; +import com.android.tv.common.CollectionUtils; +import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Program; +import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.SeasonEpisodeNumber; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesInfo; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.provider.EpisodicProgramLoadTask; +import com.android.tv.experiments.Experiments; + +import com.android.tv.util.LocationUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for + * the {@link com.android.tv.dvr.data.SeriesRecording}. + *

+ * The current implementation assumes that the series recordings are scheduled only for one channel. + */ +@TargetApi(Build.VERSION_CODES.N) +public class SeriesRecordingScheduler { + private static final String TAG = "SeriesRecordingSchd"; + private static final boolean DEBUG = false; + + private static final String KEY_FETCHED_SERIES_IDS = + "SeriesRecordingScheduler.fetched_series_ids"; + + @SuppressLint("StaticFieldLeak") + private static SeriesRecordingScheduler sInstance; + + /** + * Creates and returns the {@link SeriesRecordingScheduler}. + */ + public static synchronized SeriesRecordingScheduler getInstance(Context context) { + if (sInstance == null) { + sInstance = new SeriesRecordingScheduler(context); + } + return sInstance; + } + + private final Context mContext; + private final DvrManager mDvrManager; + private final WritableDvrDataManager mDataManager; + private final List mScheduleTasks = new ArrayList<>(); + private final LongSparseArray mFetchSeriesInfoTasks = + new LongSparseArray<>(); + private final Set mFetchedSeriesIds = new ArraySet<>(); + private final SharedPreferences mSharedPreferences; + private boolean mStarted; + private boolean mPaused; + private final Set mPendingSeriesRecordings = new ArraySet<>(); + + private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() { + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + executeFetchSeriesInfoTask(seriesRecording); + } + } + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + // Cancel the update. + for (Iterator iter = mScheduleTasks.iterator(); + iter.hasNext(); ) { + SeriesRecordingUpdateTask task = iter.next(); + if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings, + SeriesRecording.ID_COMPARATOR).isEmpty()) { + task.cancel(true); + iter.remove(); + } + } + for (SeriesRecording seriesRecording : seriesRecordings) { + FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(seriesRecording.getId()); + if (task != null) { + task.cancel(true); + mFetchSeriesInfoTasks.remove(seriesRecording.getId()); + } + } + } + + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + List stopped = new ArrayList<>(); + List normal = new ArrayList<>(); + for (SeriesRecording r : seriesRecordings) { + if (r.isStopped()) { + stopped.add(r); + } else { + normal.add(r); + } + } + if (!stopped.isEmpty()) { + onSeriesRecordingRemoved(SeriesRecording.toArray(stopped)); + } + if (!normal.isEmpty()) { + updateSchedules(normal); + } + } + }; + + private final ScheduledRecordingListener mScheduledRecordingListener = + new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... schedules) { + // No need to update series recordings when the new schedule is added. + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { + handleScheduledRecordingChange(Arrays.asList(schedules)); + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { + List schedulesForUpdate = new ArrayList<>(); + for (ScheduledRecording r : schedules) { + if ((r.getState() == ScheduledRecording.STATE_RECORDING_FAILED + || r.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED) + && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET + && !TextUtils.isEmpty(r.getSeasonNumber()) + && !TextUtils.isEmpty(r.getEpisodeNumber())) { + schedulesForUpdate.add(r); + } + } + if (!schedulesForUpdate.isEmpty()) { + handleScheduledRecordingChange(schedulesForUpdate); + } + } + + private void handleScheduledRecordingChange(List schedules) { + if (schedules.isEmpty()) { + return; + } + Set seriesRecordingIds = new HashSet<>(); + for (ScheduledRecording r : schedules) { + if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { + seriesRecordingIds.add(r.getSeriesRecordingId()); + } + } + if (!seriesRecordingIds.isEmpty()) { + List seriesRecordings = new ArrayList<>(); + for (Long id : seriesRecordingIds) { + SeriesRecording seriesRecording = mDataManager.getSeriesRecording(id); + if (seriesRecording != null) { + seriesRecordings.add(seriesRecording); + } + } + if (!seriesRecordings.isEmpty()) { + updateSchedules(seriesRecordings); + } + } + } + }; + + private SeriesRecordingScheduler(Context context) { + mContext = context.getApplicationContext(); + ApplicationSingletons appSingletons = TvApplication.getSingletons(context); + mDvrManager = appSingletons.getDvrManager(); + mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); + mSharedPreferences = context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); + mFetchedSeriesIds.addAll(mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, + Collections.emptySet())); + } + + /** + * Starts the scheduler. + */ + @MainThread + public void start() { + SoftPreconditions.checkState(mDataManager.isInitialized()); + if (mStarted) { + return; + } + if (DEBUG) Log.d(TAG, "start"); + mStarted = true; + mDataManager.addSeriesRecordingListener(mSeriesRecordingListener); + mDataManager.addScheduledRecordingListener(mScheduledRecordingListener); + startFetchingSeriesInfo(); + updateSchedules(mDataManager.getSeriesRecordings()); + } + + @MainThread + public void stop() { + if (!mStarted) { + return; + } + if (DEBUG) Log.d(TAG, "stop"); + mStarted = false; + for (int i = 0; i < mFetchSeriesInfoTasks.size(); i++) { + FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(mFetchSeriesInfoTasks.keyAt(i)); + task.cancel(true); + } + mFetchSeriesInfoTasks.clear(); + for (SeriesRecordingUpdateTask task : mScheduleTasks) { + task.cancel(true); + } + mScheduleTasks.clear(); + mDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); + mDataManager.removeSeriesRecordingListener(mSeriesRecordingListener); + } + + private void startFetchingSeriesInfo() { + for (SeriesRecording seriesRecording : mDataManager.getSeriesRecordings()) { + if (!mFetchedSeriesIds.contains(seriesRecording.getSeriesId())) { + executeFetchSeriesInfoTask(seriesRecording); + } + } + } + + private void executeFetchSeriesInfoTask(SeriesRecording seriesRecording) { + if (Experiments.CLOUD_EPG.get()) { + FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording); + task.execute(); + mFetchSeriesInfoTasks.put(seriesRecording.getId(), task); + } + } + + /** + * Pauses the updates of the series recordings. + */ + public void pauseUpdate() { + if (DEBUG) Log.d(TAG, "Schedule paused"); + if (mPaused) { + return; + } + mPaused = true; + if (!mStarted) { + return; + } + for (SeriesRecordingUpdateTask task : mScheduleTasks) { + for (SeriesRecording r : task.getSeriesRecordings()) { + mPendingSeriesRecordings.add(r.getId()); + } + task.cancel(true); + } + } + + /** + * Resumes the updates of the series recordings. + */ + public void resumeUpdate() { + if (DEBUG) Log.d(TAG, "Schedule resumed"); + if (!mPaused) { + return; + } + mPaused = false; + if (!mStarted) { + return; + } + if (!mPendingSeriesRecordings.isEmpty()) { + List seriesRecordings = new ArrayList<>(); + for (long seriesRecordingId : mPendingSeriesRecordings) { + SeriesRecording seriesRecording = + mDataManager.getSeriesRecording(seriesRecordingId); + if (seriesRecording != null) { + seriesRecordings.add(seriesRecording); + } + } + if (!seriesRecordings.isEmpty()) { + updateSchedules(seriesRecordings); + } + } + } + + /** + * Update schedules for the given series recordings. If it's paused, the update will be done + * after it's resumed. + */ + public void updateSchedules(Collection seriesRecordings) { + if (DEBUG) Log.d(TAG, "updateSchedules:" + seriesRecordings); + if (!mStarted) { + if (DEBUG) Log.d(TAG, "Not started yet."); + return; + } + if (mPaused) { + for (SeriesRecording r : seriesRecordings) { + mPendingSeriesRecordings.add(r.getId()); + } + if (DEBUG) { + Log.d(TAG, "The scheduler has been paused. Adding to the pending list. size=" + + mPendingSeriesRecordings.size()); + } + return; + } + Set previousSeriesRecordings = new HashSet<>(); + for (Iterator iter = mScheduleTasks.iterator(); + iter.hasNext(); ) { + SeriesRecordingUpdateTask task = iter.next(); + if (CollectionUtils.containsAny(task.getSeriesRecordings(), seriesRecordings, + SeriesRecording.ID_COMPARATOR)) { + // The task is affected by the seriesRecordings + task.cancel(true); + previousSeriesRecordings.addAll(task.getSeriesRecordings()); + iter.remove(); + } + } + List seriesRecordingsToUpdate = CollectionUtils.union(seriesRecordings, + previousSeriesRecordings, SeriesRecording.ID_COMPARATOR); + for (Iterator iter = seriesRecordingsToUpdate.iterator(); + iter.hasNext(); ) { + SeriesRecording seriesRecording = mDataManager.getSeriesRecording(iter.next().getId()); + if (seriesRecording == null || seriesRecording.isStopped()) { + // Series recording has been removed or stopped. + iter.remove(); + } + } + if (seriesRecordingsToUpdate.isEmpty()) { + return; + } + if (needToReadAllChannels(seriesRecordingsToUpdate)) { + SeriesRecordingUpdateTask task = + new SeriesRecordingUpdateTask(seriesRecordingsToUpdate); + mScheduleTasks.add(task); + if (DEBUG) Log.d(TAG, "Added schedule task: " + task); + task.execute(); + } else { + for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { + SeriesRecordingUpdateTask task = new SeriesRecordingUpdateTask( + Collections.singletonList(seriesRecording)); + mScheduleTasks.add(task); + if (DEBUG) Log.d(TAG, "Added schedule task: " + task); + task.execute(); + } + } + } + + private boolean needToReadAllChannels(List seriesRecordingsToUpdate) { + for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { + if (seriesRecording.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ALL) { + return true; + } + } + return false; + } + + /** + * Pick one program per an episode. + * + *

Note that the programs which has been already scheduled have the highest priority, and all + * of them are added even though they are the same episodes. That's because the schedules + * should be added to the series recording. + *

If there are no existing schedules for an episode, one program which starts earlier is + * picked. + */ + private LongSparseArray> pickOneProgramPerEpisode( + List seriesRecordings, List programs) { + return pickOneProgramPerEpisode(mDataManager, seriesRecordings, programs); + } + + /** + * @see #pickOneProgramPerEpisode(List, List) + */ + public static LongSparseArray> pickOneProgramPerEpisode( + DvrDataManager dataManager, List seriesRecordings, + List programs) { + // Initialize. + LongSparseArray> result = new LongSparseArray<>(); + Map seriesRecordingIds = new HashMap<>(); + for (SeriesRecording seriesRecording : seriesRecordings) { + result.put(seriesRecording.getId(), new ArrayList<>()); + seriesRecordingIds.put(seriesRecording.getSeriesId(), seriesRecording.getId()); + } + // Group programs by the episode. + Map> programsForEpisodeMap = new HashMap<>(); + for (Program program : programs) { + long seriesRecordingId = seriesRecordingIds.get(program.getSeriesId()); + if (TextUtils.isEmpty(program.getSeasonNumber()) + || TextUtils.isEmpty(program.getEpisodeNumber())) { + // Add all the programs if it doesn't have season number or episode number. + result.get(seriesRecordingId).add(program); + continue; + } + SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber(seriesRecordingId, + program.getSeasonNumber(), program.getEpisodeNumber()); + List programsForEpisode = programsForEpisodeMap.get(seasonEpisodeNumber); + if (programsForEpisode == null) { + programsForEpisode = new ArrayList<>(); + programsForEpisodeMap.put(seasonEpisodeNumber, programsForEpisode); + } + programsForEpisode.add(program); + } + // Pick one program. + for (Entry> entry : programsForEpisodeMap.entrySet()) { + List programsForEpisode = entry.getValue(); + Collections.sort(programsForEpisode, new Comparator() { + @Override + public int compare(Program lhs, Program rhs) { + // Place the existing schedule first. + boolean lhsScheduled = isProgramScheduled(dataManager, lhs); + boolean rhsScheduled = isProgramScheduled(dataManager, rhs); + if (lhsScheduled && !rhsScheduled) { + return -1; + } + if (!lhsScheduled && rhsScheduled) { + return 1; + } + // Sort by the start time in ascending order. + return lhs.compareTo(rhs); + } + }); + boolean added = false; + // Add all the scheduled programs + List programsForSeries = result.get(entry.getKey().seriesRecordingId); + for (Program program : programsForEpisode) { + if (isProgramScheduled(dataManager, program)) { + programsForSeries.add(program); + added = true; + } else if (!added) { + programsForSeries.add(program); + break; + } + } + } + return result; + } + + private static boolean isProgramScheduled(DvrDataManager dataManager, Program program) { + ScheduledRecording schedule = + dataManager.getScheduledRecordingForProgramId(program.getId()); + return schedule != null && schedule.getState() + == ScheduledRecording.STATE_RECORDING_NOT_STARTED; + } + + private void updateFetchedSeries() { + mSharedPreferences.edit().putStringSet(KEY_FETCHED_SERIES_IDS, mFetchedSeriesIds).apply(); + } + + /** + * This works only for the existing series recordings. Do not use this task for the + * "adding series recording" UI. + */ + private class SeriesRecordingUpdateTask extends EpisodicProgramLoadTask { + SeriesRecordingUpdateTask(List seriesRecordings) { + super(mContext, seriesRecordings); + } + + @Override + protected void onPostExecute(List programs) { + if (DEBUG) Log.d(TAG, "onPostExecute: updating schedules with programs:" + programs); + mScheduleTasks.remove(this); + if (programs == null) { + Log.e(TAG, "Creating schedules for series recording failed: " + + getSeriesRecordings()); + return; + } + LongSparseArray> seriesProgramMap = pickOneProgramPerEpisode( + getSeriesRecordings(), programs); + for (SeriesRecording seriesRecording : getSeriesRecordings()) { + // Check the series recording is still valid. + SeriesRecording actualSeriesRecording = mDataManager.getSeriesRecording( + seriesRecording.getId()); + if (actualSeriesRecording == null || actualSeriesRecording.isStopped()) { + continue; + } + List programsToSchedule = seriesProgramMap.get(seriesRecording.getId()); + if (mDataManager.getSeriesRecording(seriesRecording.getId()) != null + && !programsToSchedule.isEmpty()) { + mDvrManager.addScheduleToSeriesRecording(seriesRecording, programsToSchedule); + } + } + } + + @Override + protected void onCancelled(List programs) { + mScheduleTasks.remove(this); + } + + @Override + public String toString() { + return "SeriesRecordingUpdateTask:{" + + "series_recordings=" + getSeriesRecordings() + + "}"; + } + } + + private class FetchSeriesInfoTask extends AsyncTask { + private SeriesRecording mSeriesRecording; + + FetchSeriesInfoTask(SeriesRecording seriesRecording) { + mSeriesRecording = seriesRecording; + } + + @Override + protected SeriesInfo doInBackground(Void... voids) { + return EpgFetcher.createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext)) + .getSeriesInfo(mSeriesRecording.getSeriesId()); + } + + @Override + protected void onPostExecute(SeriesInfo seriesInfo) { + if (seriesInfo != null) { + mDataManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording) + .setTitle(seriesInfo.getTitle()) + .setDescription(seriesInfo.getDescription()) + .setLongDescription(seriesInfo.getLongDescription()) + .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds()) + .setPosterUri(seriesInfo.getPosterUri()) + .setPhotoUri(seriesInfo.getPhotoUri()) + .build()); + mFetchedSeriesIds.add(seriesInfo.getId()); + updateFetchedSeries(); + } + mFetchSeriesInfoTasks.remove(mSeriesRecording.getId()); + } + + @Override + protected void onCancelled(SeriesInfo seriesInfo) { + mFetchSeriesInfoTasks.remove(mSeriesRecording.getId()); + } + } +} diff --git a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/ActionPresenterSelector.java deleted file mode 100644 index 8b8cd5c5..00000000 --- a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.graphics.drawable.Drawable; -import android.support.v17.leanback.R; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.Presenter; -import android.support.v17.leanback.widget.PresenterSelector; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; - -// This class is adapted from Leanback's library, which does not support action icon with one-line -// label. This class modified its getPresenter method to support the above situation. -class ActionPresenterSelector extends PresenterSelector { - private final Presenter mOneLineActionPresenter = new OneLineActionPresenter(); - private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter(); - private final Presenter[] mPresenters = new Presenter[] { - mOneLineActionPresenter, mTwoLineActionPresenter}; - - @Override - public Presenter getPresenter(Object item) { - Action action = (Action) item; - if (TextUtils.isEmpty(action.getLabel2()) && action.getIcon() == null) { - return mOneLineActionPresenter; - } else { - return mTwoLineActionPresenter; - } - } - - @Override - public Presenter[] getPresenters() { - return mPresenters; - } - - static class ActionViewHolder extends Presenter.ViewHolder { - Action mAction; - Button mButton; - int mLayoutDirection; - - public ActionViewHolder(View view, int layoutDirection) { - super(view); - mButton = (Button) view.findViewById(R.id.lb_action_button); - mLayoutDirection = layoutDirection; - } - } - - class OneLineActionPresenter extends Presenter { - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.lb_action_1_line, parent, false); - return new ActionViewHolder(v, parent.getLayoutDirection()); - } - - @Override - public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { - Action action = (Action) item; - ActionViewHolder vh = (ActionViewHolder) viewHolder; - vh.mAction = action; - vh.mButton.setText(action.getLabel1()); - } - - @Override - public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { - ((ActionViewHolder) viewHolder).mAction = null; - } - } - - class TwoLineActionPresenter extends Presenter { - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.lb_action_2_lines, parent, false); - return new ActionViewHolder(v, parent.getLayoutDirection()); - } - - @Override - public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { - Action action = (Action) item; - ActionViewHolder vh = (ActionViewHolder) viewHolder; - Drawable icon = action.getIcon(); - vh.mAction = action; - - if (icon != null) { - final int startPadding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start); - final int endPadding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end); - vh.view.setPaddingRelative(startPadding, 0, endPadding, 0); - } else { - final int padding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal); - vh.view.setPaddingRelative(padding, 0, padding, 0); - } - if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) { - vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null); - } else { - vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - } - - CharSequence line1 = action.getLabel1(); - CharSequence line2 = action.getLabel2(); - if (TextUtils.isEmpty(line1)) { - vh.mButton.setText(line2); - } else if (TextUtils.isEmpty(line2)) { - vh.mButton.setText(line1); - } else { - vh.mButton.setText(line1 + "\n" + line2); - } - } - - @Override - public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { - ActionViewHolder vh = (ActionViewHolder) viewHolder; - vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - vh.view.setPadding(0, 0, 0, 0); - vh.mAction = null; - } - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java new file mode 100644 index 00000000..ec3b5065 --- /dev/null +++ b/src/com/android/tv/dvr/ui/BigArguments.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.support.annotation.NonNull; + +import com.android.tv.common.SoftPreconditions; + +import java.util.HashMap; +import java.util.Map; + +/** + * Stores the object to pass through activities/fragments. + */ +public class BigArguments { + private final static String TAG = "BigArguments"; + private static Map sBigArgumentMap = new HashMap<>(); + + /** + * Sets the argument. + */ + public static void setArgument(String name, @NonNull Object value) { + SoftPreconditions.checkState(value != null, TAG, "Set argument, but value is null"); + sBigArgumentMap.put(name, value); + } + + /** + * Returns the argument which is associated to the name. + */ + public static Object getArgument(String name) { + return sBigArgumentMap.get(name); + } + + /** + * Resets the arguments. + */ + public static void reset() { + sBigArgumentMap.clear(); + } +} diff --git a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java new file mode 100644 index 00000000..cddece73 --- /dev/null +++ b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; +import android.transition.ChangeImageTransform; +import android.transition.TransitionValues; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; + +import com.android.tv.R; + +import java.util.Map; + +/** + * TODO: Remove this class once b/32405620 is fixed. + * This class is for the workaround of b/32405620 and only for the shared element transition between + * {@link com.android.tv.dvr.ui.browse.RecordingCardView} and + * {@link com.android.tv.dvr.ui.browse.DvrDetailsActivity}. + */ +public class ChangeImageTransformWithScaledParent extends ChangeImageTransform { + private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix"; + + public ChangeImageTransformWithScaledParent(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + applyParentScale(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + applyParentScale(transitionValues); + } + + private void applyParentScale(TransitionValues transitionValues) { + View view = transitionValues.view; + Map values = transitionValues.values; + Matrix matrix = (Matrix) values.get(PROPNAME_MATRIX); + if (matrix != null && view.getId() == R.id.details_overview_image + && view instanceof ImageView) { + ImageView imageView = (ImageView) view; + if (imageView.getScaleType() == ScaleType.CENTER_INSIDE + && imageView.getDrawable() instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); + if (bitmap.getWidth() < imageView.getWidth() + && bitmap.getHeight() < imageView.getHeight()) { + float scale = imageView.getContext().getResources().getFraction( + R.fraction.lb_focus_zoom_factor_medium, 1, 1); + matrix.postScale(scale, scale, imageView.getWidth() / 2, + imageView.getHeight() / 2); + } + } + } + } +} diff --git a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java deleted file mode 100644 index 5d8e20ff..00000000 --- a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.res.Resources; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.SparseArrayObjectAdapter; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrManager; - -/** - * {@link RecordingDetailsFragment} for current recording in DVR. - */ -public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { - private static final int ACTION_STOP_RECORDING = 1; - - @Override - protected SparseArrayObjectAdapter onCreateActionsAdapter() { - SparseArrayObjectAdapter adapter = - new SparseArrayObjectAdapter(new ActionPresenterSelector()); - Resources res = getResources(); - adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING, - res.getString(R.string.epg_dvr_dialog_message_stop_recording), null, - res.getDrawable(R.drawable.lb_ic_stop))); - return adapter; - } - - @Override - protected OnActionClickedListener onCreateOnActionClickedListener() { - return new OnActionClickedListener() { - @Override - public void onActionClicked(Action action) { - if (action.getId() == ACTION_STOP_RECORDING) { - DvrManager dvrManager = TvApplication.getSingletons(getActivity()) - .getDvrManager(); - dvrManager.stopRecording(getRecording()); - } - getActivity().finish(); - } - }; - } -} diff --git a/src/com/android/tv/dvr/ui/DetailsContent.java b/src/com/android/tv/dvr/ui/DetailsContent.java deleted file mode 100644 index 19521fca..00000000 --- a/src/com/android/tv/dvr/ui/DetailsContent.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.media.tv.TvContract; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.android.tv.data.BaseProgram; -import com.android.tv.data.Channel; - -/** - * A class for details content. - */ -public class DetailsContent { - /** Constant for invalid time. */ - public static final long INVALID_TIME = -1; - - private CharSequence mTitle; - private long mStartTimeUtcMillis; - private long mEndTimeUtcMillis; - private String mDescription; - private String mLogoImageUri; - private String mBackgroundImageUri; - - private DetailsContent() { } - - /** - * Returns title. - */ - public CharSequence getTitle() { - return mTitle; - } - - /** - * Returns start time. - */ - public long getStartTimeUtcMillis() { - return mStartTimeUtcMillis; - } - - /** - * Returns end time. - */ - public long getEndTimeUtcMillis() { - return mEndTimeUtcMillis; - } - - /** - * Returns description. - */ - public String getDescription() { - return mDescription; - } - - /** - * Returns Logo image URI as a String. - */ - public String getLogoImageUri() { - return mLogoImageUri; - } - - /** - * Returns background image URI as a String. - */ - public String getBackgroundImageUri() { - return mBackgroundImageUri; - } - - /** - * Copies other details content. - */ - public void copyFrom(DetailsContent other) { - if (this == other) { - return; - } - mTitle = other.mTitle; - mStartTimeUtcMillis = other.mStartTimeUtcMillis; - mEndTimeUtcMillis = other.mEndTimeUtcMillis; - mDescription = other.mDescription; - mLogoImageUri = other.mLogoImageUri; - mBackgroundImageUri = other.mBackgroundImageUri; - } - - /** - * A class for building details content. - */ - public static final class Builder { - private final DetailsContent mDetailsContent; - - public Builder() { - mDetailsContent = new DetailsContent(); - mDetailsContent.mStartTimeUtcMillis = INVALID_TIME; - mDetailsContent.mEndTimeUtcMillis = INVALID_TIME; - } - - /** - * Sets title. - */ - public Builder setTitle(CharSequence title) { - mDetailsContent.mTitle = title; - return this; - } - - /** - * Sets start time. - */ - public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { - mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis; - return this; - } - - /** - * Sets end time. - */ - public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { - mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis; - return this; - } - - /** - * Sets description. - */ - public Builder setDescription(String description) { - mDetailsContent.mDescription = description; - return this; - } - - /** - * Sets logo image URI as a String. - */ - public Builder setLogoImageUri(String logoImageUri) { - mDetailsContent.mLogoImageUri = logoImageUri; - return this; - } - - /** - * Sets background image URI as a String. - */ - public Builder setBackgroundImageUri(String backgroundImageUri) { - mDetailsContent.mBackgroundImageUri = backgroundImageUri; - return this; - } - - /** - * Sets background image and logo image URI from program and channel. - */ - public Builder setImageUris(@Nullable BaseProgram program, @Nullable Channel channel) { - if (program != null) { - return setImageUris(program.getPosterArtUri(), program.getThumbnailUri(), channel); - } else { - return setImageUris(null, null, channel); - } - } - - /** - * Sets background image and logo image URI and channel is used for fallback images. - */ - public Builder setImageUris(@Nullable String posterArtUri, - @Nullable String thumbnailUri, @Nullable Channel channel) { - mDetailsContent.mLogoImageUri = null; - mDetailsContent.mBackgroundImageUri = null; - if (!TextUtils.isEmpty(posterArtUri) && !TextUtils.isEmpty(thumbnailUri)) { - mDetailsContent.mLogoImageUri = posterArtUri; - mDetailsContent.mBackgroundImageUri = thumbnailUri; - } else if (!TextUtils.isEmpty(posterArtUri)) { - // thumbnailUri is empty - mDetailsContent.mLogoImageUri = posterArtUri; - mDetailsContent.mBackgroundImageUri = posterArtUri; - } else if (!TextUtils.isEmpty(thumbnailUri)) { - // posterArtUri is empty - mDetailsContent.mLogoImageUri = thumbnailUri; - mDetailsContent.mBackgroundImageUri = thumbnailUri; - } - if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) { - String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()) - .toString(); - mDetailsContent.mLogoImageUri = channelLogoUri; - mDetailsContent.mBackgroundImageUri = channelLogoUri; - } - return this; - } - - /** - * Builds details content. - */ - public DetailsContent build() { - DetailsContent detailsContent = new DetailsContent(); - detailsContent.copyFrom(mDetailsContent); - return detailsContent; - } - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/DetailsContentPresenter.java deleted file mode 100644 index 175f05bc..00000000 --- a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.graphics.Paint; -import android.graphics.Paint.FontMetricsInt; -import android.support.v17.leanback.widget.Presenter; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.ui.ViewUtils; -import com.android.tv.util.Utils; - -/** - * An {@link Presenter} for rendering a detailed description of an DVR item. - * Typically this Presenter will be used in a {@link DetailsOverviewRowPresenter}. - * Most codes of this class is originated from - * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}. - * The latter class are re-used to provide a customized version of - * {@link android.support.v17.leanback.widget.DetailsOverviewRow}. - */ -public class DetailsContentPresenter extends Presenter { - /** - * The ViewHolder for the {@link DetailsContentPresenter}. - */ - public static class ViewHolder extends Presenter.ViewHolder { - final TextView mTitle; - final TextView mSubtitle; - final LinearLayout mDescriptionContainer; - final TextView mBody; - final TextView mReadMoreView; - final int mTitleMargin; - final int mUnderTitleBaselineMargin; - final int mUnderSubtitleBaselineMargin; - final int mTitleLineSpacing; - final int mBodyLineSpacing; - final int mBodyMaxLines; - final int mBodyMinLines; - final FontMetricsInt mTitleFontMetricsInt; - final FontMetricsInt mSubtitleFontMetricsInt; - final FontMetricsInt mBodyFontMetricsInt; - final int mTitleMaxLines; - - private Activity mActivity; - private boolean mFullTextMode; - private int mFullTextAnimationDuration; - private boolean mIsListeningToPreDraw; - - private ViewTreeObserver.OnPreDrawListener mPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (mSubtitle.getVisibility() == View.VISIBLE - && mSubtitle.getTop() > view.getHeight() - && mTitle.getLineCount() > 1) { - mTitle.setMaxLines(mTitle.getLineCount() - 1); - return false; - } - final int bodyLines = mBody.getLineCount(); - final int maxLines = mFullTextMode ? bodyLines : - (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines); - if (bodyLines > maxLines) { - mReadMoreView.setVisibility(View.VISIBLE); - mDescriptionContainer.setFocusable(true); - mDescriptionContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mFullTextMode = true; - mReadMoreView.setVisibility(View.GONE); - mDescriptionContainer.setFocusable(false); - mDescriptionContainer.setOnClickListener(null); - mBody.setMaxLines(bodyLines); - // Minus 1 from line difference to eliminate the space - // originally occupied by "READ MORE" - showFullText((bodyLines - maxLines - 1) * mBodyLineSpacing); - } - }); - } - if (mBody.getMaxLines() != maxLines) { - mBody.setMaxLines(maxLines); - return false; - } else { - removePreDrawListener(); - return true; - } - } - }; - - public ViewHolder(final View view) { - super(view); - mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title); - mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle); - mBody = (TextView) view.findViewById(R.id.dvr_details_description_body); - mDescriptionContainer = - (LinearLayout) view.findViewById(R.id.dvr_details_description_container); - mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more); - - FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle); - final int titleAscent = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_title_baseline); - // Ascent is negative - mTitleMargin = titleAscent + titleFontMetricsInt.ascent; - - mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_under_title_baseline_margin); - mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_under_subtitle_baseline_margin); - - mTitleLineSpacing = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_title_line_spacing); - mBodyLineSpacing = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_body_line_spacing); - - mBodyMaxLines = view.getResources().getInteger( - R.integer.lb_details_description_body_max_lines); - mBodyMinLines = view.getResources().getInteger( - R.integer.lb_details_description_body_min_lines); - mTitleMaxLines = mTitle.getMaxLines(); - - mTitleFontMetricsInt = getFontMetricsInt(mTitle); - mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle); - mBodyFontMetricsInt = getFontMetricsInt(mBody); - } - - void addPreDrawListener() { - if (!mIsListeningToPreDraw) { - mIsListeningToPreDraw = true; - view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); - } - } - - void removePreDrawListener() { - if (mIsListeningToPreDraw) { - view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); - mIsListeningToPreDraw = false; - } - } - - public TextView getTitle() { - return mTitle; - } - - public TextView getSubtitle() { - return mSubtitle; - } - - public TextView getBody() { - return mBody; - } - - private FontMetricsInt getFontMetricsInt(TextView textView) { - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setTextSize(textView.getTextSize()); - paint.setTypeface(textView.getTypeface()); - return paint.getFontMetricsInt(); - } - - private void showFullText(int heightDiff) { - final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame); - int nowHeight = ViewUtils.getLayoutHeight(detailsFrame); - Animator expandAnimator = ViewUtils.createHeightAnimator( - detailsFrame, nowHeight, nowHeight + heightDiff); - expandAnimator.setDuration(mFullTextAnimationDuration); - Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame, - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - 0f, -(heightDiff / 2))); - shiftAnimator.setDuration(mFullTextAnimationDuration); - AnimatorSet fullTextAnimator = new AnimatorSet(); - fullTextAnimator.playTogether(expandAnimator, shiftAnimator); - fullTextAnimator.start(); - } - } - - private final Activity mActivity; - private final int mFullTextAnimationDuration; - - public DetailsContentPresenter(Activity activity) { - super(); - mActivity = activity; - mFullTextAnimationDuration = mActivity.getResources() - .getInteger(R.integer.dvr_details_full_text_animation_duration); - } - - @Override - public final ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.dvr_details_description, parent, false); - return new ViewHolder(v); - } - - @Override - public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { - final ViewHolder vh = (ViewHolder) viewHolder; - final DetailsContent detailsContent = (DetailsContent) item; - - vh.mActivity = mActivity; - vh.mFullTextAnimationDuration = mFullTextAnimationDuration; - - boolean hasTitle = true; - if (TextUtils.isEmpty(detailsContent.getTitle())) { - vh.mTitle.setVisibility(View.GONE); - hasTitle = false; - } else { - vh.mTitle.setText(detailsContent.getTitle()); - vh.mTitle.setVisibility(View.VISIBLE); - vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() - + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier()); - vh.mTitle.setMaxLines(vh.mTitleMaxLines); - } - setTopMargin(vh.mTitle, vh.mTitleMargin); - - boolean hasSubtitle = true; - if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME - && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) { - vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(), - detailsContent.getStartTimeUtcMillis(), - detailsContent.getEndTimeUtcMillis(), false)); - vh.mSubtitle.setVisibility(View.VISIBLE); - if (hasTitle) { - setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin - + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent); - } else { - setTopMargin(vh.mSubtitle, 0); - } - } else { - vh.mSubtitle.setVisibility(View.GONE); - hasSubtitle = false; - } - - if (TextUtils.isEmpty(detailsContent.getDescription())) { - vh.mBody.setVisibility(View.GONE); - } else { - vh.mBody.setText(detailsContent.getDescription()); - vh.mBody.setVisibility(View.VISIBLE); - vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() - + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier()); - if (hasSubtitle) { - setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin - + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent - - vh.mBody.getPaddingTop()); - } else if (hasTitle) { - setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin - + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent - - vh.mBody.getPaddingTop()); - } else { - setTopMargin(vh.mDescriptionContainer, 0); - } - } - } - - @Override - public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { } - - @Override - public void onViewAttachedToWindow(Presenter.ViewHolder holder) { - // In case predraw listener was removed in detach, make sure - // we have the proper layout. - ViewHolder vh = (ViewHolder) holder; - vh.addPreDrawListener(); - super.onViewAttachedToWindow(holder); - } - - @Override - public void onViewDetachedFromWindow(Presenter.ViewHolder holder) { - ViewHolder vh = (ViewHolder) holder; - vh.removePreDrawListener(); - super.onViewDetachedFromWindow(holder); - } - - private void setTopMargin(View view, int topMargin) { - ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); - lp.topMargin = topMargin; - view.setLayoutParams(lp); - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java deleted file mode 100644 index 6714ecd3..00000000 --- a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.support.v17.leanback.app.BackgroundManager; - -/** - * The Background Helper. - */ -public class DetailsViewBackgroundHelper { - // Background delay serves to avoid kicking off expensive bitmap loading - // in case multiple backgrounds are set in quick succession. - private static final int SET_BACKGROUND_DELAY_MS = 100; - - private final BackgroundManager mBackgroundManager; - - class LoadBackgroundRunnable implements Runnable { - final Drawable mBackGround; - - LoadBackgroundRunnable(Drawable background) { - mBackGround = background; - } - - @Override - public void run() { - if (!mBackgroundManager.isAttached()) { - return; - } - if (mBackGround instanceof BitmapDrawable) { - mBackgroundManager.setBitmap(((BitmapDrawable) mBackGround).getBitmap()); - } - mRunnable = null; - } - } - - private LoadBackgroundRunnable mRunnable; - - private final Handler mHandler = new Handler(); - - public DetailsViewBackgroundHelper(Activity activity) { - mBackgroundManager = BackgroundManager.getInstance(activity); - mBackgroundManager.attach(activity.getWindow()); - } - - /** - * Sets the given image to background. - */ - public void setBackground(Drawable background) { - if (mRunnable != null) { - mHandler.removeCallbacks(mRunnable); - } - mRunnable = new LoadBackgroundRunnable(background); - mHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS); - } - - /** - * Sets the background color. - */ - public void setBackgroundColor(int color) { - if (mBackgroundManager.isAttached()) { - mBackgroundManager.setColor(color); - } - } - - /** - * Sets the background scrim. - */ - public void setScrim(int color) { - if (mBackgroundManager.isAttached()) { - mBackgroundManager.setDimLayer(new ColorDrawable(color)); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrActivity.java b/src/com/android/tv/dvr/ui/DvrActivity.java deleted file mode 100644 index 45fb1cf1..00000000 --- a/src/com/android/tv/dvr/ui/DvrActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.os.Bundle; - -import com.android.tv.R; -import com.android.tv.TvApplication; - -/** - * {@link android.app.Activity} for DVR UI. - */ -public class DvrActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); - super.onCreate(savedInstanceState); - setContentView(R.layout.dvr_main); - } -} diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java index 9df228d1..62327870 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java @@ -24,15 +24,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; -import android.widget.Toast; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.dvr.RecordedProgram; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.util.Utils; +import com.android.tv.dvr.data.RecordedProgram; import java.util.List; @@ -92,7 +89,7 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_RECORD_ANYWAY) { getDvrManager().addSchedule(mProgram); } else if (action.getId() == ACTION_WATCH) { @@ -100,4 +97,23 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment { } dismissDialog(); } + + @Override + public String getTrackerPrefix() { + return "onTrackedGuidedActionClicked"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_RECORD_ANYWAY) { + return "record-anyway"; + } else if (actionId == ACTION_WATCH) { + return "watch-now"; + } else if (actionId == ACTION_CANCEL) { + return "cancel-recording"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java index 78f21784..6da75e55 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java @@ -25,15 +25,12 @@ import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.text.format.DateUtils; -import android.widget.Toast; import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.util.Utils; +import com.android.tv.dvr.data.ScheduledRecording; import java.util.List; @@ -95,7 +92,7 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_RECORD_ANYWAY) { getDvrManager().addSchedule(mProgram); } else if (action.getId() == ACTION_RECORD_INSTEAD) { @@ -104,4 +101,23 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { } dismissDialog(); } + + @Override + public String getTrackerPrefix() { + return "DvrAlreadyScheduledFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_RECORD_ANYWAY) { + return "record-anyway"; + } else if (actionId == ACTION_RECORD_INSTEAD) { + return "record-instead"; + } else if (actionId == ACTION_CANCEL) { + return "cancel-recording"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/DvrBrowseFragment.java deleted file mode 100644 index a6dd31d1..00000000 --- a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.media.tv.TvInputManager.TvInputCallback; -import android.os.Bundle; -import android.os.Handler; -import android.support.v17.leanback.app.BrowseFragment; -import android.support.v17.leanback.widget.ArrayObjectAdapter; -import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.support.v17.leanback.widget.HeaderItem; -import android.support.v17.leanback.widget.ListRow; -import android.support.v17.leanback.widget.ListRowPresenter; -import android.support.v17.leanback.widget.Presenter; -import android.support.v17.leanback.widget.TitleViewAdapter; -import android.text.TextUtils; -import android.util.Log; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.GenreItems; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; -import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; -import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; -import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; - -/** - * {@link BrowseFragment} for DVR functions. - */ -public class DvrBrowseFragment extends BrowseFragment implements - RecordedProgramListener, ScheduledRecordingListener, SeriesRecordingListener, - OnDvrScheduleLoadFinishedListener, OnRecordedProgramLoadFinishedListener { - private static final String TAG = "DvrBrowseFragment"; - private static final boolean DEBUG = false; - - private static final int MAX_RECENT_ITEM_COUNT = 10; - private static final int MAX_SCHEDULED_ITEM_COUNT = 4; - - private RecordedProgramAdapter mRecentAdapter; - private ScheduleAdapter mScheduleAdapter; - private SeriesAdapter mSeriesAdapter; - private RecordedProgramAdapter[] mGenreAdapters = - new RecordedProgramAdapter[GenreItems.getGenreCount() + 1]; - private ListRow mRecentRow; - private ListRow mSeriesRow; - private ListRow[] mGenreRows = new ListRow[GenreItems.getGenreCount() + 1]; - private List mGenreLabels; - private DvrDataManager mDvrDataManager; - private DvrScheduleManager mDvrScheudleManager; - private ArrayObjectAdapter mRowsAdapter; - private ClassPresenterSelector mPresenterSelector; - private final HashMap mSeriesId2LatestProgram = new HashMap<>(); - private final Handler mHandler = new Handler(); - - private final Comparator RECORDED_PROGRAM_COMPARATOR = new Comparator() { - @Override - public int compare(Object lhs, Object rhs) { - if (lhs instanceof SeriesRecording) { - lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId()); - } - if (rhs instanceof SeriesRecording) { - rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId()); - } - if (lhs instanceof RecordedProgram) { - if (rhs instanceof RecordedProgram) { - return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed() - .compare((RecordedProgram) lhs, (RecordedProgram) rhs); - } else { - return -1; - } - } else if (rhs instanceof RecordedProgram) { - return 1; - } else { - return 0; - } - } - }; - - private final Comparator SCHEDULE_COMPARATOR = new Comparator() { - @Override - public int compare(Object lhs, Object rhs) { - if (lhs instanceof ScheduledRecording) { - if (rhs instanceof ScheduledRecording) { - return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR - .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs); - } else { - return -1; - } - } else if (rhs instanceof ScheduledRecording) { - return 1; - } else { - return 0; - } - } - }; - - private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener = - new DvrScheduleManager.OnConflictStateChangeListener() { - @Override - public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) { - if (mScheduleAdapter != null) { - for (ScheduledRecording schedule : schedules) { - onScheduledRecordingStatusChanged(schedule); - } - } - } - }; - - private final Runnable mUpdateRowsRunnable = new Runnable() { - @Override - public void run() { - updateRows(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - Context context = getContext(); - ApplicationSingletons singletons = TvApplication.getSingletons(context); - mDvrDataManager = singletons.getDvrDataManager(); - mDvrScheudleManager = singletons.getDvrScheduleManager(); - mPresenterSelector = new ClassPresenterSelector() - .addClassPresenter(ScheduledRecording.class, - new ScheduledRecordingPresenter(context)) - .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context)) - .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context)) - .addClassPresenter(FullScheduleCardHolder.class, new FullSchedulesCardPresenter()); - mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context))); - mGenreLabels.add(getString(R.string.dvr_main_others)); - setupUiElements(); - setupAdapters(); - mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener); - prepareEntranceTransition(); - if (mDvrDataManager.isInitialized()) { - startEntranceTransition(); - } else { - if (!mDvrDataManager.isDvrScheduleLoadFinished()) { - mDvrDataManager.addDvrScheduleLoadFinishedListener(this); - } - if (!mDvrDataManager.isRecordedProgramLoadFinished()) { - mDvrDataManager.addRecordedProgramLoadFinishedListener(this); - } - } - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy"); - mHandler.removeCallbacks(mUpdateRowsRunnable); - mDvrScheudleManager.removeOnConflictStateChangeListener(mOnConflictStateChangeListener); - mDvrDataManager.removeRecordedProgramListener(this); - mDvrDataManager.removeScheduledRecordingListener(this); - mDvrDataManager.removeSeriesRecordingListener(this); - mDvrDataManager.removeDvrScheduleLoadFinishedListener(this); - mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); - mRowsAdapter.clear(); - mSeriesId2LatestProgram.clear(); - for (Presenter presenter : mPresenterSelector.getPresenters()) { - if (presenter instanceof DvrItemPresenter) { - ((DvrItemPresenter) presenter).unbindAllViewHolders(); - } - } - super.onDestroy(); - } - - @Override - public void onDvrScheduleLoadFinished() { - List scheduledRecordings = mDvrDataManager.getAllScheduledRecordings(); - onScheduledRecordingAdded(ScheduledRecording.toArray(scheduledRecordings)); - List seriesRecordings = mDvrDataManager.getSeriesRecordings(); - onSeriesRecordingAdded(SeriesRecording.toArray(seriesRecordings)); - if (mDvrDataManager.isInitialized()) { - startEntranceTransition(); - } - mDvrDataManager.removeDvrScheduleLoadFinishedListener(this); - } - - @Override - public void onRecordedProgramLoadFinished() { - for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) { - handleRecordedProgramAdded(recordedProgram, true); - } - updateRows(); - if (mDvrDataManager.isInitialized()) { - startEntranceTransition(); - } - mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); - } - - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - handleRecordedProgramAdded(recordedProgram, true); - } - postUpdateRows(); - } - - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - handleRecordedProgramChanged(recordedProgram); - } - postUpdateRows(); - } - - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - handleRecordedProgramRemoved(recordedProgram); - } - postUpdateRows(); - } - - // No need to call updateRows() during ScheduledRecordings' change because - // the row for ScheduledRecordings is always displayed. - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording scheduleRecording : scheduledRecordings) { - if (needToShowScheduledRecording(scheduleRecording)) { - mScheduleAdapter.add(scheduleRecording); - } - } - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording scheduleRecording : scheduledRecordings) { - mScheduleAdapter.remove(scheduleRecording); - } - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording scheduleRecording : scheduledRecordings) { - if (needToShowScheduledRecording(scheduleRecording)) { - mScheduleAdapter.change(scheduleRecording); - } else { - mScheduleAdapter.removeWithId(scheduleRecording); - } - } - } - - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { - handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings)); - postUpdateRows(); - } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { - handleSeriesRecordingsRemoved(Arrays.asList(seriesRecordings)); - postUpdateRows(); - } - - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - handleSeriesRecordingsChanged(Arrays.asList(seriesRecordings)); - postUpdateRows(); - } - - // Workaround of b/29108300 - @Override - public void showTitle(int flags) { - flags &= ~TitleViewAdapter.SEARCH_VIEW_VISIBLE; - super.showTitle(flags); - } - - private void setupUiElements() { - setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge)); - setHeadersState(HEADERS_ENABLED); - setHeadersTransitionOnBackEnabled(false); - setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null)); - } - - private void setupAdapters() { - mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT); - mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT); - mSeriesAdapter = new SeriesAdapter(); - for (int i = 0; i < mGenreAdapters.length; i++) { - mGenreAdapters[i] = new RecordedProgramAdapter(); - } - // Schedule Recordings. - List schedules = mDvrDataManager.getAllScheduledRecordings(); - onScheduledRecordingAdded(ScheduledRecording.toArray(schedules)); - mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER); - // Recorded Programs. - for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) { - handleRecordedProgramAdded(recordedProgram, false); - } - // Series Recordings. Series recordings should be added after recorded programs, because - // we build series recordings' latest program information while adding recorded programs. - List recordings = mDvrDataManager.getSeriesRecordings(); - handleSeriesRecordingsAdded(recordings); - mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); - mRecentRow = new ListRow(new HeaderItem( - getString(R.string.dvr_main_recent)), mRecentAdapter); - mRowsAdapter.add(new ListRow(new HeaderItem( - getString(R.string.dvr_main_scheduled)), mScheduleAdapter)); - mSeriesRow = new ListRow(new HeaderItem( - getString(R.string.dvr_main_series)), mSeriesAdapter); - updateRows(); - mDvrDataManager.addRecordedProgramListener(this); - mDvrDataManager.addScheduledRecordingListener(this); - mDvrDataManager.addSeriesRecordingListener(this); - setAdapter(mRowsAdapter); - } - - private void handleRecordedProgramAdded(RecordedProgram recordedProgram, - boolean updateSeriesRecording) { - mRecentAdapter.add(recordedProgram); - String seriesId = recordedProgram.getSeriesId(); - SeriesRecording seriesRecording = null; - if (seriesId != null) { - seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); - RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); - if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR - .compare(latestProgram, recordedProgram) < 0) { - mSeriesId2LatestProgram.put(seriesId, recordedProgram); - if (updateSeriesRecording && seriesRecording != null) { - onSeriesRecordingChanged(seriesRecording); - } - } - } - if (seriesRecording == null) { - for (RecordedProgramAdapter adapter - : getGenreAdapters(recordedProgram.getCanonicalGenres())) { - adapter.add(recordedProgram); - } - } - } - - private void handleRecordedProgramRemoved(RecordedProgram recordedProgram) { - mRecentAdapter.remove(recordedProgram); - String seriesId = recordedProgram.getSeriesId(); - if (seriesId != null) { - SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); - RecordedProgram latestProgram = - mSeriesId2LatestProgram.get(recordedProgram.getSeriesId()); - if (latestProgram != null && latestProgram.getId() == recordedProgram.getId()) { - if (seriesRecording != null) { - updateLatestRecordedProgram(seriesRecording); - onSeriesRecordingChanged(seriesRecording); - } - } - } - for (RecordedProgramAdapter adapter - : getGenreAdapters(recordedProgram.getCanonicalGenres())) { - adapter.remove(recordedProgram); - } - } - - private void handleRecordedProgramChanged(RecordedProgram recordedProgram) { - mRecentAdapter.change(recordedProgram); - String seriesId = recordedProgram.getSeriesId(); - SeriesRecording seriesRecording = null; - if (seriesId != null) { - seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); - RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); - if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR - .compare(latestProgram, recordedProgram) <= 0) { - mSeriesId2LatestProgram.put(seriesId, recordedProgram); - if (seriesRecording != null) { - onSeriesRecordingChanged(seriesRecording); - } - } else if (latestProgram.getId() == recordedProgram.getId()) { - if (seriesRecording != null) { - updateLatestRecordedProgram(seriesRecording); - onSeriesRecordingChanged(seriesRecording); - } - } - } - if (seriesRecording == null) { - updateGenreAdapters(getGenreAdapters( - recordedProgram.getCanonicalGenres()), recordedProgram); - } else { - updateGenreAdapters(new ArrayList<>(), recordedProgram); - } - } - - private void handleSeriesRecordingsAdded(List seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - mSeriesAdapter.add(seriesRecording); - if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { - for (RecordedProgramAdapter adapter - : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { - adapter.add(seriesRecording); - } - } - } - } - - private void handleSeriesRecordingsRemoved(List seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - mSeriesAdapter.remove(seriesRecording); - for (RecordedProgramAdapter adapter - : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { - adapter.remove(seriesRecording); - } - } - } - - private void handleSeriesRecordingsChanged(List seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - mSeriesAdapter.change(seriesRecording); - if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { - updateGenreAdapters(getGenreAdapters( - seriesRecording.getCanonicalGenreIds()), seriesRecording); - } else { - // Remove series recording from all genre rows if it has no recorded program - updateGenreAdapters(new ArrayList<>(), seriesRecording); - } - } - } - - private List getGenreAdapters(String[] genres) { - List result = new ArrayList<>(); - if (genres == null || genres.length == 0) { - result.add(mGenreAdapters[mGenreAdapters.length - 1]); - } else { - for (String genre : genres) { - int genreId = GenreItems.getId(genre); - if(genreId >= mGenreAdapters.length) { - Log.d(TAG, "Wrong Genre ID: " + genreId); - } else { - result.add(mGenreAdapters[genreId]); - } - } - } - return result; - } - - private List getGenreAdapters(int[] genreIds) { - List result = new ArrayList<>(); - if (genreIds == null || genreIds.length == 0) { - result.add(mGenreAdapters[mGenreAdapters.length - 1]); - } else { - for (int genreId : genreIds) { - if(genreId >= mGenreAdapters.length) { - Log.d(TAG, "Wrong Genre ID: " + genreId); - } else { - result.add(mGenreAdapters[genreId]); - } - } - } - return result; - } - - private void updateGenreAdapters(List adapters, Object r) { - for (RecordedProgramAdapter adapter : mGenreAdapters) { - if (adapters.contains(adapter)) { - adapter.change(r); - } else { - adapter.remove(r); - } - } - } - - private void postUpdateRows() { - mHandler.removeCallbacks(mUpdateRowsRunnable); - mHandler.post(mUpdateRowsRunnable); - } - - private void updateRows() { - int visibleRowsCount = 1; // Schedule's Row will never be empty - if (mRecentAdapter.isEmpty()) { - mRowsAdapter.remove(mRecentRow); - } else { - if (mRowsAdapter.indexOf(mRecentRow) < 0) { - mRowsAdapter.add(0, mRecentRow); - } - visibleRowsCount++; - } - if (mSeriesAdapter.isEmpty()) { - mRowsAdapter.remove(mSeriesRow); - } else { - if (mRowsAdapter.indexOf(mSeriesRow) < 0) { - mRowsAdapter.add(visibleRowsCount, mSeriesRow); - } - visibleRowsCount++; - } - for (int i = 0; i < mGenreAdapters.length; i++) { - RecordedProgramAdapter adapter = mGenreAdapters[i]; - if (adapter != null) { - if (adapter.isEmpty()) { - mRowsAdapter.remove(mGenreRows[i]); - } else { - if (mGenreRows[i] == null || mRowsAdapter.indexOf(mGenreRows[i]) < 0) { - mGenreRows[i] = new ListRow(new HeaderItem(mGenreLabels.get(i)), adapter); - mRowsAdapter.add(visibleRowsCount, mGenreRows[i]); - } - visibleRowsCount++; - } - } - } - } - - private boolean needToShowScheduledRecording(ScheduledRecording recording) { - int state = recording.getState(); - return state == ScheduledRecording.STATE_RECORDING_IN_PROGRESS - || state == ScheduledRecording.STATE_RECORDING_NOT_STARTED; - } - - private void updateLatestRecordedProgram(SeriesRecording seriesRecording) { - RecordedProgram latestProgram = null; - for (RecordedProgram program : - mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) { - if (latestProgram == null || RecordedProgram - .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) { - latestProgram = program; - } - } - mSeriesId2LatestProgram.put(seriesRecording.getSeriesId(), latestProgram); - } - - private class ScheduleAdapter extends SortedArrayAdapter { - ScheduleAdapter(int maxItemCount) { - super(mPresenterSelector, SCHEDULE_COMPARATOR, maxItemCount); - } - - @Override - public long getId(Object item) { - if (item instanceof ScheduledRecording) { - return ((ScheduledRecording) item).getId(); - } else { - return -1; - } - } - } - - private class SeriesAdapter extends SortedArrayAdapter { - SeriesAdapter() { - super(mPresenterSelector, new Comparator() { - @Override - public int compare(SeriesRecording lhs, SeriesRecording rhs) { - if (lhs.isStopped() && !rhs.isStopped()) { - return 1; - } else if (!lhs.isStopped() && rhs.isStopped()) { - return -1; - } - return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs); - } - }); - } - - @Override - public long getId(SeriesRecording item) { - return item.getId(); - } - } - - private class RecordedProgramAdapter extends SortedArrayAdapter { - RecordedProgramAdapter() { - this(Integer.MAX_VALUE); - } - - RecordedProgramAdapter(int maxItemCount) { - super(mPresenterSelector, RECORDED_PROGRAM_COMPARATOR, maxItemCount); - } - - @Override - public long getId(Object item) { - if (item instanceof SeriesRecording) { - return ((SeriesRecording) item).getId(); - } else if (item instanceof RecordedProgram) { - return ((RecordedProgram) item).getId(); - } else { - return -1; - } - } - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java index 837d8ab2..36659412 100644 --- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java @@ -27,7 +27,7 @@ import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment; import java.util.ArrayList; @@ -85,7 +85,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); long duration = mDurations.get((int) action.getId()); long startTimeMs = System.currentTimeMillis(); @@ -106,4 +106,25 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen R.id.halfsized_dialog_host); } } + + @Override + public String getTrackerPrefix() { + return "DvrChannelRecordDurationOptionFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == 0) { + return "record-10-minutes"; + } else if (actionId == 1) { + return "record-30-minutes"; + } else if (actionId == 2) { + return "record-1-hour"; + } else if (actionId == 3) { + return "record-3-hour"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java index e7be4d0a..6f362e68 100644 --- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java +++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java @@ -34,10 +34,9 @@ import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; -import com.android.tv.dvr.ConflictChecker; -import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.recorder.ConflictChecker; +import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -85,7 +84,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_VIEW_SCHEDULES) { DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict( getContext(), getConflicts()); @@ -93,6 +92,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { dismissDialog(); } + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = getId(); + if (actionId == ACTION_VIEW_SCHEDULES) { + return "view-schedules"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } + String getConflictDescription() { List titles = new ArrayList<>(); HashSet titleSet = new HashSet<>(); @@ -185,6 +194,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null); return new Guidance(title, descriptionPrefix + " " + description, null, icon); } + + @Override + public String getTrackerPrefix() { + return "DvrProgramConflictFragment"; + } } /** @@ -236,6 +250,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null); return new Guidance(title, descriptionPrefix + " " + description, null, icon); } + + @Override + public String getTrackerPrefix() { + return "DvrChannelRecordConflictFragment"; + } } /** @@ -300,7 +319,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_CANCEL) { ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); if (checker != null) { @@ -318,6 +337,23 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { super.onGuidedActionClicked(action); } + @Override + public String getTrackerPrefix() { + return "DvrChannelWatchConflictFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_CANCEL) { + return "cancel"; + } else if (actionId == ACTION_DELETE_CONFLICT) { + return "delete"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } + @Override public void onDetach() { ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker(); diff --git a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/DvrDetailsActivity.java deleted file mode 100644 index 806c775c..00000000 --- a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v17.leanback.app.DetailsFragment; - -import com.android.tv.R; -import com.android.tv.TvApplication; - -/** - * Activity to show details view in DVR. - */ -public class DvrDetailsActivity extends Activity { - /** - * Name of record id added to the Intent. - */ - public static final String RECORDING_ID = "record_id"; - - /** - * Name of flag added to the Intent to determine if details view should hide "View schedule" - * button. - */ - public static final String HIDE_VIEW_SCHEDULE = "hide_view_schedule"; - - /** - * Name of details view's type added to the intent. - */ - public static final String DETAILS_VIEW_TYPE = "details_view_type"; - - /** - * Name of shared element between activities. - */ - public static final String SHARED_ELEMENT_NAME = "shared_element"; - - /** - * CURRENT_RECORDING_VIEW refers to Current Recordings in DVR. - */ - public static final int CURRENT_RECORDING_VIEW = 1; - - /** - * SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR. - */ - public static final int SCHEDULED_RECORDING_VIEW = 2; - - /** - * RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR. - */ - public static final int RECORDED_PROGRAM_VIEW = 3; - - /** - * SERIES_RECORDING_VIEW refers to series recording in DVR. - */ - public static final int SERIES_RECORDING_VIEW = 4; - - @Override - public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_dvr_details); - long recordId = getIntent().getLongExtra(RECORDING_ID, -1); - int detailsViewType = getIntent().getIntExtra(DETAILS_VIEW_TYPE, -1); - boolean hideViewSchedule = getIntent().getBooleanExtra(HIDE_VIEW_SCHEDULE, false); - if (recordId != -1 && detailsViewType != -1 && savedInstanceState == null) { - Bundle args = new Bundle(); - args.putLong(RECORDING_ID, recordId); - DetailsFragment detailsFragment = null; - if (detailsViewType == CURRENT_RECORDING_VIEW) { - detailsFragment = new CurrentRecordingDetailsFragment(); - } else if (detailsViewType == SCHEDULED_RECORDING_VIEW) { - args.putBoolean(HIDE_VIEW_SCHEDULE, hideViewSchedule); - detailsFragment = new ScheduledRecordingDetailsFragment(); - } else if (detailsViewType == RECORDED_PROGRAM_VIEW) { - detailsFragment = new RecordedProgramDetailsFragment(); - } else if (detailsViewType == SERIES_RECORDING_VIEW) { - detailsFragment = new SeriesRecordingDetailsFragment(); - } - detailsFragment.setArguments(args); - getFragmentManager().beginTransaction() - .replace(R.id.dvr_details_view_frame, detailsFragment).commit(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/DvrDetailsFragment.java deleted file mode 100644 index 21f9c4b4..00000000 --- a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.tv.TvContentRating; -import android.media.tv.TvInputManager; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v17.leanback.app.DetailsFragment; -import android.support.v17.leanback.widget.ArrayObjectAdapter; -import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.support.v17.leanback.widget.DetailsOverviewRow; -import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.PresenterSelector; -import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.support.v17.leanback.widget.VerticalGridView; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.TextAppearanceSpan; -import android.widget.Toast; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.BaseProgram; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dialog.PinDialogFragment; -import com.android.tv.dvr.DvrPlaybackActivity; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.parental.ParentalControlSettings; -import com.android.tv.util.ImageLoader; -import com.android.tv.util.ToastUtils; -import com.android.tv.util.Utils; - -import java.io.File; - -abstract class DvrDetailsFragment extends DetailsFragment { - private static final int LOAD_LOGO_IMAGE = 1; - private static final int LOAD_BACKGROUND_IMAGE = 2; - - protected DetailsViewBackgroundHelper mBackgroundHelper; - private ArrayObjectAdapter mRowsAdapter; - private DetailsOverviewRow mDetailsOverview; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (!onLoadRecordingDetails(getArguments())) { - getActivity().finish(); - return; - } - mBackgroundHelper = new DetailsViewBackgroundHelper(getActivity()); - setupAdapter(); - onCreateInternal(); - } - - @Override - public void onStart() { - super.onStart(); - // TODO: remove the workaround of b/30401180. - VerticalGridView container = (VerticalGridView) getActivity() - .findViewById(R.id.container_list); - // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout. - container.setItemAlignmentOffset(0); - container.setWindowAlignmentOffset( - getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top)); - } - - private void setupAdapter() { - DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter( - new DetailsContentPresenter(getActivity())); - rowPresenter.setBackgroundColor(getResources().getColor(R.color.common_tv_background, - null)); - rowPresenter.setSharedElementEnterTransition(getActivity(), - DvrDetailsActivity.SHARED_ELEMENT_NAME); - rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener()); - mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter)); - setAdapter(mRowsAdapter); - } - - /** - * Returns details views' rows adapter. - */ - protected ArrayObjectAdapter getRowsAdapter() { - return mRowsAdapter; - } - - /** - * Sets details overview. - */ - protected void setDetailsOverviewRow(DetailsContent detailsContent) { - mDetailsOverview = new DetailsOverviewRow(detailsContent); - mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); - mRowsAdapter.add(mDetailsOverview); - onLoadLogoAndBackgroundImages(detailsContent); - } - - /** - * Creates and returns presenter selector will be used by rows adaptor. - */ - protected PresenterSelector onCreatePresenterSelector( - DetailsOverviewRowPresenter rowPresenter) { - ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); - presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); - return presenterSelector; - } - - /** - * Does customized initialization of subclasses. Since {@link #onCreate(Bundle)} might finish - * activity early when it cannot fetch valid recordings, subclasses' onCreate method should not - * do anything after calling {@link #onCreate(Bundle)}. If there's something subclasses have to - * do after the super class did onCreate, it should override this method and put the codes here. - */ - protected void onCreateInternal() { } - - /** - * Updates actions of details overview. - */ - protected void updateActions() { - mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); - } - - /** - * Loads recording details according to the arguments the fragment got. - * - * @return false if cannot find valid recordings, else return true. If the return value - * is false, the detail activity and fragment will be ended. - */ - abstract boolean onLoadRecordingDetails(Bundle args); - - /** - * Creates actions users can interact with and their adaptor for this fragment. - */ - abstract SparseArrayObjectAdapter onCreateActionsAdapter(); - - /** - * Creates actions listeners to implement the behavior of the fragment after users click some - * action buttons. - */ - abstract OnActionClickedListener onCreateOnActionClickedListener(); - - /** - * Returns program title with episode number. If the program is null, returns channel name. - */ - protected CharSequence getTitleFromProgram(BaseProgram program, Channel channel) { - String titleWithEpisodeNumber = program.getTitleWithEpisodeNumber(getContext()); - SpannableString title = titleWithEpisodeNumber == null ? null - : new SpannableString(titleWithEpisodeNumber); - if (TextUtils.isEmpty(title)) { - title = new SpannableString(channel != null ? channel.getDisplayName() - : getContext().getResources().getString( - R.string.no_program_information)); - } else { - String programTitle = program.getTitle(); - title.setSpan(new TextAppearanceSpan(getContext(), - R.style.text_appearance_card_view_episode_number), programTitle == null ? 0 - : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - return title; - } - - /** - * Loads logo and background images for detail fragments. - */ - protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) { - Drawable logoDrawable = null; - Drawable backgroundDrawable = null; - if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) { - logoDrawable = getContext().getResources() - .getDrawable(R.drawable.dvr_default_poster, null); - mDetailsOverview.setImageDrawable(logoDrawable); - } - if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) { - backgroundDrawable = getContext().getResources() - .getDrawable(R.drawable.dvr_default_poster, null); - mBackgroundHelper.setBackground(backgroundDrawable); - } - if (logoDrawable != null && backgroundDrawable != null) { - return; - } - if (logoDrawable == null && backgroundDrawable == null - && detailsContent.getLogoImageUri().equals( - detailsContent.getBackgroundImageUri())) { - ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), - new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, - getContext())); - return; - } - if (logoDrawable == null) { - int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width); - int imageHeight = getResources() - .getDimensionPixelSize(R.dimen.dvr_details_poster_height); - ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), - imageWidth, imageHeight, - new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext())); - } - if (backgroundDrawable == null) { - ImageLoader.loadBitmap(getContext(), detailsContent.getBackgroundImageUri(), - new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext())); - } - } - - protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) { - if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) && - !isDataUriAccessible(recordedProgram.getDataUri())) { - // Since cleaning RecordedProgram from forgotten storage will take some time, - // ignore playback until cleaning is finished. - ToastUtils.show(getContext(), - getContext().getResources().getString(R.string.dvr_toast_recording_deleted), - Toast.LENGTH_SHORT); - return; - } - ParentalControlSettings parental = TvApplication.getSingletons(getActivity()) - .getTvInputManagerHelper().getParentalControlSettings(); - if (!parental.isParentalControlsEnabled()) { - launchPlaybackActivity(recordedProgram, seekTimeMs, false); - return; - } - ChannelDataManager channelDataManager = - TvApplication.getSingletons(getActivity()).getChannelDataManager(); - Channel channel = channelDataManager.getChannel(recordedProgram.getChannelId()); - if (channel != null && channel.isLocked()) { - checkPinToPlay(recordedProgram, seekTimeMs); - return; - } - String ratingString = recordedProgram.getContentRating(); - if (TextUtils.isEmpty(ratingString)) { - launchPlaybackActivity(recordedProgram, seekTimeMs, false); - return; - } - String[] ratingList = ratingString.split(","); - TvContentRating[] programRatings = new TvContentRating[ratingList.length]; - for (int i = 0; i < ratingList.length; i++) { - programRatings[i] = TvContentRating.unflattenFromString(ratingList[i]); - } - TvContentRating blockRatings = parental.getBlockedRating(programRatings); - if (blockRatings != null) { - checkPinToPlay(recordedProgram, seekTimeMs); - } else { - launchPlaybackActivity(recordedProgram, seekTimeMs, false); - } - } - - private boolean isDataUriAccessible(Uri dataUri) { - if (dataUri == null || dataUri.getPath() == null) { - return false; - } - try { - File recordedProgramPath = new File(dataUri.getPath()); - if (recordedProgramPath.exists()) { - return true; - } - } catch (SecurityException e) { - } - return false; - } - - private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) { - new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - if (success) { - launchPlaybackActivity(recordedProgram, seekTimeMs, true); - } - } - }).show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG); - } - - private void launchPlaybackActivity(RecordedProgram mRecordedProgram, long seekTimeMs, - boolean pinChecked) { - Intent intent = new Intent(getActivity(), DvrPlaybackActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, mRecordedProgram.getId()); - if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs); - } - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked); - getActivity().startActivity(intent); - } - - private static class MyImageLoaderCallback extends - ImageLoader.ImageLoaderCallback { - private final Context mContext; - private final int mLoadType; - - public MyImageLoaderCallback(DvrDetailsFragment fragment, - int loadType, Context context) { - super(fragment); - mLoadType = loadType; - mContext = context; - } - - @Override - public void onBitmapLoaded(DvrDetailsFragment fragment, - @Nullable Bitmap bitmap) { - Drawable drawable; - int loadType = mLoadType; - if (bitmap == null) { - Resources res = mContext.getResources(); - drawable = res.getDrawable(R.drawable.dvr_default_poster, null); - if ((loadType & LOAD_BACKGROUND_IMAGE) != 0 && !fragment.isDetached()) { - loadType &= ~LOAD_BACKGROUND_IMAGE; - fragment.mBackgroundHelper.setBackgroundColor( - res.getColor(R.color.dvr_detail_default_background)); - fragment.mBackgroundHelper.setScrim( - res.getColor(R.color.dvr_detail_default_background_scrim)); - } - } else { - drawable = new BitmapDrawable(mContext.getResources(), bitmap); - } - if (!fragment.isDetached()) { - if ((loadType & LOAD_LOGO_IMAGE) != 0) { - fragment.mDetailsOverview.setImageDrawable(drawable); - } - if ((loadType & LOAD_BACKGROUND_IMAGE) != 0) { - fragment.mBackgroundHelper.setBackground(drawable); - } - } - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java deleted file mode 100644 index 73ddcdd0..00000000 --- a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import android.text.TextUtils; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; - -import java.util.List; - -public class DvrForgetStorageErrorFragment extends DvrGuidedStepFragment { - private static final int ACTION_CANCEL = 1; - private static final int ACTION_FORGET_STORAGE = 2; - private String mInputId; - - @Override - public void onCreate(Bundle savedInstanceState) { - Bundle args = getArguments(); - if (args != null) { - mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID); - } - SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId)); - super.onCreate(savedInstanceState); - } - - @NonNull - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString(R.string.dvr_error_forget_storage_title); - String description = getResources().getString( - R.string.dvr_error_forget_storage_description); - return new Guidance(title, description, null, null); - } - - @Override - public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { - Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_CANCEL) - .title(getResources().getString(R.string.dvr_action_error_cancel)) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_FORGET_STORAGE) - .title(getResources().getString(R.string.dvr_action_error_forget_storage)) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - if (action.getId() != ACTION_FORGET_STORAGE) { - dismissDialog(); - return; - } - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); - dvrManager.forgetStorage(mInputId); - Activity activity = getActivity(); - if (activity instanceof DvrDetailsActivity) { - // Since we removed everything, just finish the activity. - activity.finish(); - } else { - dismissDialog(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java index d26e6836..ab852e10 100644 --- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java +++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java @@ -16,24 +16,43 @@ package com.android.tv.dvr.ui; +import android.app.Activity; import android.app.DialogFragment; import android.content.Context; import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; +import android.support.v17.leanback.widget.GuidanceStylist; import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.VerticalGridView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; +import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener; import com.android.tv.dialog.SafeDismissDialogFragment; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ui.HalfSizedDialogFragment.OnActionClickListener; +import com.android.tv.dvr.DvrStorageStatusManager; + +import java.util.List; + +public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { + /** + * Action ID for "recording/scheduling the program anyway". + */ + public static final int ACTION_RECORD_ANYWAY = 1; + /** + * Action ID for "deleting existed recordings". + */ + public static final int ACTION_DELETE_RECORDINGS = 2; + /** + * Action ID for "cancelling current recording request". + */ + public static final int ACTION_CANCEL_RECORDING = 3; + public static final String UNKNOWN_DVR_ACTION = "Unknown DVR Action"; -public class DvrGuidedStepFragment extends GuidedStepFragment { private DvrManager mDvrManager; private OnActionClickListener mOnActionClickListener; @@ -44,7 +63,8 @@ public class DvrGuidedStepFragment extends GuidedStepFragment { @Override public void onAttach(Context context) { super.onAttach(context); - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); + ApplicationSingletons singletons = TvApplication.getSingletons(context); + mDvrManager = singletons.getDvrManager(); } @Override @@ -64,13 +84,27 @@ public class DvrGuidedStepFragment extends GuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (mOnActionClickListener != null) { mOnActionClickListener.onActionClick(action.getId()); } dismissDialog(); } + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_RECORD_ANYWAY) { + return "record-anyway"; + } else if (actionId == ACTION_DELETE_RECORDINGS) { + return "delete-recordings"; + } else if (actionId == ACTION_CANCEL_RECORDING) { + return "cancel-recording"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } + protected void dismissDialog() { if (getActivity() instanceof MainActivity) { SafeDismissDialogFragment currentDialog = @@ -86,4 +120,76 @@ public class DvrGuidedStepFragment extends GuidedStepFragment { protected void setOnActionClickListener(OnActionClickListener listener) { mOnActionClickListener = listener; } + + /** + * The inner guided step fragment for + * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment + * .DvrNoFreeSpaceErrorDialogFragment}. + */ + public static class DvrNoFreeSpaceErrorFragment extends DvrGuidedStepFragment { + @Override + public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { + return new GuidanceStylist.Guidance(getString(R.string.dvr_error_no_free_space_title), + getString(R.string.dvr_error_no_free_space_description), null, null); + } + + @Override + public void onCreateActions(List actions, Bundle savedInstanceState) { + Activity activity = getActivity(); + actions.add(new GuidedAction.Builder(activity) + .id(ACTION_RECORD_ANYWAY) + .title(R.string.dvr_action_record_anyway) + .build()); + actions.add(new GuidedAction.Builder(activity) + .id(ACTION_DELETE_RECORDINGS) + .title(R.string.dvr_action_delete_recordings) + .build()); + actions.add(new GuidedAction.Builder(activity) + .id(ACTION_CANCEL_RECORDING) + .title(R.string.dvr_action_record_cancel) + .build()); + } + + @Override + public String getTrackerPrefix() { + return "DvrNoFreeSpaceErrorFragment"; + } + } + + /** + * The inner guided step fragment for + * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment + * .DvrSmallSizedStorageErrorDialogFragment}. + */ + public static class DvrSmallSizedStorageErrorFragment extends DvrGuidedStepFragment { + @Override + public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getResources().getString( + R.string.dvr_error_small_sized_storage_title); + String description = getResources().getString( + R.string.dvr_error_small_sized_storage_description, + DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024 + / 1024 / 1024); + return new GuidanceStylist.Guidance(title, description, null, null); + } + + @Override + public void onCreateActions(List actions, Bundle savedInstanceState) { + Activity activity = getActivity(); + actions.add(new GuidedAction.Builder(activity) + .id(GuidedAction.ACTION_ID_OK) + .title(android.R.string.ok) + .build()); + } + + @Override + public void onTrackedGuidedActionClicked(GuidedAction action) { + dismissDialog(); + } + + @Override + public String getTrackerPrefix() { + return "DvrSmallSizedStorageErrorFragment"; + } + } } \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java index 2b132db8..f8ef3850 100644 --- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java +++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java @@ -29,6 +29,7 @@ import android.view.ViewGroup; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.dvr.DvrStorageStatusManager; +import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment; import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment; import com.android.tv.guide.ProgramGuide; @@ -165,6 +166,17 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { } } + /** + * A dialog fragment to show error message when there is no enough free space to record. + */ + public static class DvrNoFreeSpaceErrorDialogFragment + extends DvrGuidedStepDialogFragment { + @Override + protected DvrGuidedStepFragment onCreateGuidedStepFragment() { + return new DvrGuidedStepFragment.DvrNoFreeSpaceErrorFragment(); + } + } + /** * A dialog fragment to show error message when the current storage is too small to * support DVR @@ -173,32 +185,7 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { extends DvrGuidedStepDialogFragment { @Override protected DvrGuidedStepFragment onCreateGuidedStepFragment() { - return new DvrGuidedStepFragment() { - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString( - R.string.dvr_error_small_sized_storage_title); - String description = getResources().getString( - R.string.dvr_error_small_sized_storage_description, - DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024 - / 1024 / 1024); - return new Guidance(title, description, null, null); - } - - @Override - public void onCreateActions(List actions, Bundle savedInstanceState) { - Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(GuidedAction.ACTION_ID_OK) - .title(android.R.string.ok) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - dismissDialog(); - } - }; + return new DvrGuidedStepFragment.DvrSmallSizedStorageErrorFragment(); } } diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java index 3b1dbfa0..182416b6 100644 --- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java +++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java @@ -17,6 +17,7 @@ package com.android.tv.dvr.ui; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; @@ -24,19 +25,67 @@ import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrDataManager; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.dvr.ui.browse.DvrBrowseActivity; +import java.util.ArrayList; import java.util.List; public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { - private static final int ACTION_DONE = 1; - private static final int ACTION_OPEN_DVR = 2; + /** + * Key for the failed scheduled recordings information. + */ + public static final String FAILED_SCHEDULED_RECORDING_INFOS = + "failed_scheduled_recording_infos"; + + private static final String TAG = "DvrInsufficientSpaceErrorFragment"; + + private static final int ACTION_VIEW_RECENT_RECORDINGS = 1; + + private ArrayList mFailedScheduledRecordingInfos; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + Bundle args = getArguments(); + if (args != null) { + mFailedScheduledRecordingInfos = + args.getStringArrayList(FAILED_SCHEDULED_RECORDING_INFOS); + } + SoftPreconditions.checkState( + mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(), + TAG, "failed scheduled recording is null"); + } @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString(R.string.dvr_error_insufficient_space_title); - String description = getResources() - .getString(R.string.dvr_error_insufficient_space_description); + String title; + String description; + int failedScheduledRecordingSize = mFailedScheduledRecordingInfos.size(); + if (failedScheduledRecordingSize == 1) { + title = getString( + R.string.dvr_error_insufficient_space_title_one_recording, + mFailedScheduledRecordingInfos.get(0)); + description = getString( + R.string.dvr_error_insufficient_space_description_one_recording, + mFailedScheduledRecordingInfos.get(0)); + } else if (failedScheduledRecordingSize == 2) { + title = getString( + R.string.dvr_error_insufficient_space_title_two_recordings, + mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1)); + description = getString( + R.string.dvr_error_insufficient_space_description_two_recordings, + mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1)); + } else { + title = getString( + R.string.dvr_error_insufficient_space_title_three_or_more_recordings, + mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1), + mFailedScheduledRecordingInfos.get(2)); + description = getString( + R.string.dvr_error_insufficient_space_description_three_or_more_recordings, + mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1), + mFailedScheduledRecordingInfos.get(2)); + } return new Guidance(title, description, null, null); } @@ -44,28 +93,38 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); actions.add(new GuidedAction.Builder(activity) - .id(ACTION_DONE) - .title(getResources().getString(R.string.dvr_action_error_done)) + .clickAction(GuidedAction.ACTION_ID_OK) .build()); - DvrDataManager dvrDataManager = TvApplication.getSingletons(getContext()) - .getDvrDataManager(); - if (!(dvrDataManager.getRecordedPrograms().isEmpty() - && dvrDataManager.getStartedRecordings().isEmpty() - && dvrDataManager.getNonStartedScheduledRecordings().isEmpty() - && dvrDataManager.getSeriesRecordings().isEmpty())) { - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_OPEN_DVR) - .title(getResources().getString(R.string.dvr_action_error_open_dvr)) - .build()); + if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) { + actions.add(new GuidedAction.Builder(activity) + .id(ACTION_VIEW_RECENT_RECORDINGS) + .title(getResources().getString( + R.string.dvr_error_insufficient_space_action_view_recent_recordings)) + .build()); } } @Override - public void onGuidedActionClicked(GuidedAction action) { - if (action.getId() == ACTION_OPEN_DVR) { - Intent intent = new Intent(getActivity(), DvrActivity.class); + public void onTrackedGuidedActionClicked(GuidedAction action) { + if (action.getId() == ACTION_VIEW_RECENT_RECORDINGS) { + Intent intent = new Intent(getActivity(), DvrBrowseActivity.class); getActivity().startActivity(intent); } dismissDialog(); } + + @Override + public String getTrackerPrefix() { + return "DvrInsufficientSpaceErrorFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_VIEW_RECENT_RECORDINGS) { + return "view-recent"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/DvrItemPresenter.java deleted file mode 100644 index 339e5d2f..00000000 --- a/src/com/android/tv/dvr/ui/DvrItemPresenter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.support.annotation.CallSuper; -import android.support.v17.leanback.widget.Presenter; -import android.view.View; -import android.view.View.OnClickListener; - -import com.android.tv.dvr.DvrUiHelper; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in - * {@link DvrBrowseFragment}. DVR items might include: {@link ScheduledRecording}, - * {@link RecordedProgram}, and {@link SeriesRecording}. - */ -public abstract class DvrItemPresenter extends Presenter { - private final Set mBoundViewHolders = new HashSet<>(); - private final OnClickListener mOnClickListener = onCreateOnClickListener(); - - @Override - @CallSuper - public void onBindViewHolder(ViewHolder viewHolder, Object o) { - viewHolder.view.setTag(o); - viewHolder.view.setOnClickListener(mOnClickListener); - mBoundViewHolders.add(viewHolder); - } - - @Override - @CallSuper - public void onUnbindViewHolder(ViewHolder viewHolder) { - mBoundViewHolders.remove(viewHolder); - } - - /** - * Unbinds all bound view holders. - */ - public void unbindAllViewHolders() { - // When browse fragments are destroyed, RecyclerView would not call presenters' - // onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks. - for (ViewHolder viewHolder : new HashSet<>(mBoundViewHolders)) { - onUnbindViewHolder(viewHolder); - } - } - - /** - * Creates {@link OnClickListener} for DVR library's card views. - */ - protected OnClickListener onCreateOnClickListener() { - return new OnClickListener() { - @Override - public void onClick(View view) { - if (view instanceof RecordingCardView) { - RecordingCardView v = (RecordingCardView) view; - DvrUiHelper.startDetailsActivity((Activity) v.getContext(), - v.getTag(), v.getImageView(), false); - } - } - }; - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java index 2e2c2849..e726995f 100644 --- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java +++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java @@ -17,29 +17,27 @@ package com.android.tv.dvr.ui; import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; +import android.provider.Settings; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; -import android.text.TextUtils; +import android.util.Log; import com.android.tv.R; -import com.android.tv.common.SoftPreconditions; +import com.android.tv.dvr.ui.browse.DvrDetailsActivity; import java.util.List; public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment { - private static final int ACTION_CANCEL = 1; - private static final int ACTION_FORGET_STORAGE = 2; - private String mInputId; + private static final String TAG = "DvrMissingStorageError"; + + private static final int ACTION_OK = 1; + private static final int ACTION_OPEN_STORAGE_SETTINGS = 2; @Override public void onCreate(Bundle savedInstanceState) { - Bundle args = getArguments(); - if (args != null) { - mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID); - } - SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId)); super.onCreate(savedInstanceState); } @@ -55,25 +53,46 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment { public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); actions.add(new GuidedAction.Builder(activity) - .id(ACTION_CANCEL) - .title(getResources().getString(R.string.dvr_action_error_cancel)) + .id(ACTION_OK) + .title(android.R.string.ok) .build()); actions.add(new GuidedAction.Builder(activity) - .id(ACTION_FORGET_STORAGE) - .title(getResources().getString(R.string.dvr_action_error_forget_storage)) + .id(ACTION_OPEN_STORAGE_SETTINGS) + .title(getResources().getString(R.string.dvr_action_error_storage_settings)) .build()); } @Override - public void onGuidedActionClicked(GuidedAction action) { - if (action.getId() == ACTION_FORGET_STORAGE) { - DvrForgetStorageErrorFragment fragment = new DvrForgetStorageErrorFragment(); - Bundle args = new Bundle(); - args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, mInputId); - fragment.setArguments(args); - GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host); + public void onTrackedGuidedActionClicked(GuidedAction action) { + Activity activity = getActivity(); + if (activity instanceof DvrDetailsActivity) { + activity.finish(); + } else { + dismissDialog(); + } + if (action.getId() != ACTION_OPEN_STORAGE_SETTINGS) { return; } - dismissDialog(); + final Intent intent = new Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS); + try { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Can't start internal storage settings activity", e); + } + } + + @Override + public String getTrackerPrefix() { + return "DvrMissingStorageErrorFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_OPEN_STORAGE_SETTINGS) { + return "open-storage-settings"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java deleted file mode 100644 index 8c4c856c..00000000 --- a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; - -import com.android.tv.R; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dvr.DvrPlaybackActivity; -import com.android.tv.util.Utils; - -/** - * This class is used to generate Views and bind Objects for related recordings in DVR playback. - */ -public class DvrPlaybackCardPresenter extends RecordedProgramPresenter { - private static final String TAG = "DvrPlaybackCardPresenter"; - private static final boolean DEBUG = false; - - private final int mRelatedRecordingCardWidth; - private final int mRelatedRecordingCardHeight; - - DvrPlaybackCardPresenter(Context context) { - super(context); - mRelatedRecordingCardWidth = - context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width); - mRelatedRecordingCardHeight = - context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_height); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Resources res = parent.getResources(); - RecordingCardView view = new RecordingCardView( - getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight); - return new ViewHolder(view); - } - - @Override - protected OnClickListener onCreateOnClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - long programId = ((RecordedProgram) v.getTag()).getId(); - if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId); - Intent intent = new Intent(getContext(), DvrPlaybackActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); - getContext().startActivity(intent); - } - }; - } - - @Override - protected String getDescription(RecordedProgram program) { - String description = program.getDescription(); - if (TextUtils.isEmpty(description)) { - description = - getContext().getResources().getString(R.string.dvr_msg_no_program_description); - } - return description; - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java deleted file mode 100644 index 0bc4ecb1..00000000 --- a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.graphics.drawable.Drawable; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaController.TransportControls; -import android.media.session.PlaybackState; -import android.support.v17.leanback.app.PlaybackControlGlue; -import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.PlaybackControlsRow; -import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; -import android.support.v17.leanback.widget.RowPresenter; -import android.text.TextUtils; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; - -import com.android.tv.R; -import com.android.tv.util.TimeShiftUtils; - -/** - * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and - * send command to the media controller. It also helps to update playback states displayed in the - * fragment according to information the media session provides. - */ -public class DvrPlaybackControlHelper extends PlaybackControlGlue { - private static final String TAG = "DvrPlaybackControlHelper"; - private static final boolean DEBUG = false; - - /** - * Indicates the ID of the media under playback is unknown. - */ - public static int UNKNOWN_MEDIA_ID = -1; - - private int mPlaybackState = PlaybackState.STATE_NONE; - private int mPlaybackSpeedLevel; - private int mPlaybackSpeedId; - private boolean mReadyToControl; - - private final MediaController mMediaController; - private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback(); - private final TransportControls mTransportControls; - private final int mExtraPaddingTopForNoDescription; - - public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) { - super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]); - mMediaController = activity.getMediaController(); - mMediaController.registerCallback(mMediaControllerCallback); - mTransportControls = mMediaController.getTransportControls(); - mExtraPaddingTopForNoDescription = activity.getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top); - } - - @Override - public PlaybackControlsRowPresenter createControlsRowAndPresenter() { - PlaybackControlsRow controlsRow = new PlaybackControlsRow(this); - setControlsRow(controlsRow); - AbstractDetailsDescriptionPresenter detailsPresenter = - new AbstractDetailsDescriptionPresenter() { - @Override - protected void onBindDescription( - AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) { - PlaybackControlGlue glue = (PlaybackControlGlue) object; - if (glue.hasValidMedia()) { - viewHolder.getTitle().setText(glue.getMediaTitle()); - viewHolder.getSubtitle().setText(glue.getMediaSubtitle()); - } else { - viewHolder.getTitle().setText(""); - viewHolder.getSubtitle().setText(""); - } - if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) { - viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(), - mExtraPaddingTopForNoDescription, - viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom()); - } - } - }; - PlaybackControlsRowPresenter presenter = - new PlaybackControlsRowPresenter(detailsPresenter) { - @Override - protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) { - super.onBindRowViewHolder(vh, item); - vh.setOnKeyListener(DvrPlaybackControlHelper.this); - } - - @Override - protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) { - super.onUnbindRowViewHolder(vh); - vh.setOnKeyListener(null); - } - }; - presenter.setProgressColor(getContext().getResources() - .getColor(R.color.play_controls_progress_bar_watched)); - presenter.setBackgroundColor(getContext().getResources() - .getColor(R.color.play_controls_body_background_enabled)); - presenter.setOnActionClickedListener(new OnActionClickedListener() { - @Override - public void onActionClicked(Action action) { - if (mReadyToControl) { - DvrPlaybackControlHelper.super.onActionClicked(action); - } - } - }); - return presenter; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (mReadyToControl) { - if (keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE && event.getAction() == KeyEvent.ACTION_DOWN - && (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING - || mPlaybackState == PlaybackState.STATE_REWINDING)) { - // Workaround of b/31489271. Clicks play/pause button first to reset play controls - // to "play" state. Then we can pass MEDIA_PAUSE to let playback be paused. - onActionClicked(getControlsRow().getActionForKeyCode(keyCode)); - } - return super.onKey(v, keyCode, event); - } - return false; - } - - @Override - public boolean hasValidMedia() { - PlaybackState playbackState = mMediaController.getPlaybackState(); - return playbackState != null; - } - - @Override - public boolean isMediaPlaying() { - PlaybackState playbackState = mMediaController.getPlaybackState(); - if (playbackState == null) { - return false; - } - int state = playbackState.getState(); - return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING - && state != PlaybackState.STATE_PAUSED; - } - - /** - * Returns the ID of the media under playback. - */ - public long getMediaId() { - MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? UNKNOWN_MEDIA_ID - : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID); - } - - @Override - public CharSequence getMediaTitle() { - MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? "" - : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); - } - - @Override - public CharSequence getMediaSubtitle() { - MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? "" - : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE); - } - - @Override - public int getMediaDuration() { - MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? 0 - : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); - } - - @Override - public Drawable getMediaArt() { - // Do not show the poster art on control row. - return null; - } - - @Override - public long getSupportedActions() { - return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND; - } - - @Override - public int getCurrentSpeedId() { - return mPlaybackSpeedId; - } - - @Override - public int getCurrentPosition() { - PlaybackState playbackState = mMediaController.getPlaybackState(); - if (playbackState == null) { - return 0; - } - return (int) playbackState.getPosition(); - } - - /** - * Unregister media controller's callback. - */ - public void unregisterCallback() { - mMediaController.unregisterCallback(mMediaControllerCallback); - } - - @Override - protected void startPlayback(int speedId) { - if (getCurrentSpeedId() == speedId) { - return; - } - if (speedId == PLAYBACK_SPEED_NORMAL) { - mTransportControls.play(); - } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) { - mTransportControls.rewind(); - } else if (speedId >= PLAYBACK_SPEED_FAST_L0){ - mTransportControls.fastForward(); - } - } - - @Override - protected void pausePlayback() { - mTransportControls.pause(); - } - - @Override - protected void skipToNext() { - // Do nothing. - } - - @Override - protected void skipToPrevious() { - // Do nothing. - } - - @Override - protected void onRowChanged(PlaybackControlsRow row) { - // Do nothing. - } - - private void onStateChanged(int state, long positionMs, int speedLevel) { - if (DEBUG) Log.d(TAG, "onStateChanged"); - getControlsRow().setCurrentTime((int) positionMs); - if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) { - // Only position is changed, no need to update controls row - return; - } - // NOTICE: The below two variables should only be used in this method. - // The only usage of them is to confirm if the state is changed or not. - mPlaybackState = state; - mPlaybackSpeedLevel = speedLevel; - switch (state) { - case PlaybackState.STATE_PLAYING: - mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL; - setFadingEnabled(true); - mReadyToControl = true; - break; - case PlaybackState.STATE_PAUSED: - mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED; - setFadingEnabled(true); - mReadyToControl = true; - break; - case PlaybackState.STATE_FAST_FORWARDING: - mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel; - setFadingEnabled(false); - mReadyToControl = true; - break; - case PlaybackState.STATE_REWINDING: - mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel; - setFadingEnabled(false); - mReadyToControl = true; - break; - case PlaybackState.STATE_CONNECTING: - setFadingEnabled(false); - mReadyToControl = false; - break; - case PlaybackState.STATE_NONE: - mReadyToControl = false; - break; - default: - setFadingEnabled(true); - break; - } - onStateChanged(); - } - - private class MediaControllerCallback extends MediaController.Callback { - @Override - public void onPlaybackStateChanged(PlaybackState state) { - if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState()); - onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed()); - } - - @Override - public void onMetadataChanged(MediaMetadata metadata) { - DvrPlaybackControlHelper.this.onMetadataChanged(); - ((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated(); - } - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java deleted file mode 100644 index 51ec93b8..00000000 --- a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Point; -import android.hardware.display.DisplayManager; -import android.media.tv.TvContentRating; -import android.os.Bundle; -import android.media.session.PlaybackState; -import android.media.tv.TvInputManager; -import android.media.tv.TvView; -import android.support.v17.leanback.app.PlaybackOverlayFragment; -import android.support.v17.leanback.widget.ArrayObjectAdapter; -import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.support.v17.leanback.widget.HeaderItem; -import android.support.v17.leanback.widget.ListRow; -import android.support.v17.leanback.widget.ListRowPresenter; -import android.support.v17.leanback.widget.PlaybackControlsRow; -import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; -import android.support.v17.leanback.widget.SinglePresenterSelector; -import android.view.Display; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; -import android.text.TextUtils; -import android.util.Log; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.BaseProgram; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dialog.PinDialogFragment; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrPlayer; -import com.android.tv.dvr.DvrPlaybackMediaSessionHelper; -import com.android.tv.parental.ContentRatingsManager; -import com.android.tv.util.Utils; - -public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment { - // TODO: Handles audio focus. Deals with block and ratings. - private static final String TAG = "DvrPlaybackOverlayFragment"; - private static final boolean DEBUG = false; - - private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession"; - private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f; - - // mProgram is only used to store program from intent. Don't use it elsewhere. - private RecordedProgram mProgram; - private DvrPlaybackMediaSessionHelper mMediaSessionHelper; - private DvrPlaybackControlHelper mPlaybackControlHelper; - private ArrayObjectAdapter mRowsAdapter; - private SortedArrayAdapter mRelatedRecordingsRowAdapter; - private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter; - private DvrDataManager mDvrDataManager; - private ContentRatingsManager mContentRatingsManager; - private TvView mTvView; - private View mBlockScreenView; - private ListRow mRelatedRecordingsRow; - private int mExtraPaddingNoRelatedRow; - private int mWindowWidth; - private int mWindowHeight; - private float mAppliedAspectRatio; - private float mWindowAspectRatio; - private boolean mPinChecked; - - @Override - public void onCreate(Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - mExtraPaddingNoRelatedRow = getActivity().getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_fragment_extra_padding_top); - mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); - mContentRatingsManager = TvApplication.getSingletons(getContext()) - .getTvInputManagerHelper().getContentRatingsManager(); - mProgram = getProgramFromIntent(getActivity().getIntent()); - if (mProgram == null) { - Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found), - Toast.LENGTH_SHORT).show(); - getActivity().finish(); - return; - } - Point size = new Point(); - ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) - .getDisplay(Display.DEFAULT_DISPLAY).getSize(size); - mWindowWidth = size.x; - mWindowHeight = size.y; - mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight; - setBackgroundType(PlaybackOverlayFragment.BG_LIGHT); - setFadingEnabled(true); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view); - mBlockScreenView = getActivity().findViewById(R.id.block_screen); - mMediaSessionHelper = new DvrPlaybackMediaSessionHelper( - getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this); - mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this); - setUpRows(); - preparePlayback(getActivity().getIntent()); - DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer(); - dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() { - @Override - public void onAspectRatioChanged(float videoAspectRatio) { - updateAspectRatio(videoAspectRatio); - } - }); - mPinChecked = getActivity().getIntent() - .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false); - dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() { - @Override - public void onContentBlocked(TvContentRating rating) { - if (mPinChecked) { - mTvView.unblockContent(rating); - return; - } - mBlockScreenView.setVisibility(View.VISIBLE); - getActivity().getMediaController().getTransportControls().pause(); - new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - if (success) { - mPinChecked = true; - mTvView.unblockContent(rating); - mBlockScreenView.setVisibility(View.GONE); - getActivity().getMediaController() - .getTransportControls().play(); - } - } - }, mContentRatingsManager.getDisplayNameForRating(rating)) - .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG); - } - }); - } - - @Override - public void onPause() { - if (DEBUG) Log.d(TAG, "onPause"); - super.onPause(); - if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING - || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) { - getActivity().getMediaController().getTransportControls().pause(); - } - if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) { - getActivity().requestVisibleBehind(false); - } else { - getActivity().requestVisibleBehind(true); - } - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy"); - mPlaybackControlHelper.unregisterCallback(); - mMediaSessionHelper.release(); - mRelatedRecordingCardPresenter.unbindAllViewHolders(); - super.onDestroy(); - } - - /** - * Passes the intent to the fragment. - */ - public void onNewIntent(Intent intent) { - mProgram = getProgramFromIntent(intent); - if (mProgram == null) { - Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found), - Toast.LENGTH_SHORT).show(); - // Continue playing the original program - return; - } - preparePlayback(intent); - } - - /** - * Should be called when windows' size is changed in order to notify DVR player - * to update it's view width/height and position. - */ - public void onWindowSizeChanged(final int windowWidth, final int windowHeight) { - mWindowWidth = windowWidth; - mWindowHeight = windowHeight; - mWindowAspectRatio = (float) mWindowWidth / mWindowHeight; - updateAspectRatio(mAppliedAspectRatio); - } - - public RecordedProgram getNextEpisode(RecordedProgram program) { - int position = mRelatedRecordingsRowAdapter.findInsertPosition(program); - if (position == mRelatedRecordingsRowAdapter.size()) { - return null; - } else { - return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position); - } - } - - void onMediaControllerUpdated() { - mRowsAdapter.notifyArrayItemRangeChanged(0, 1); - } - - private void updateAspectRatio(float videoAspectRatio) { - if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) { - // No need to change - return; - } - if (videoAspectRatio < mWindowAspectRatio) { - int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2; - ((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0); - } else { - int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2; - ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding); - } - mAppliedAspectRatio = videoAspectRatio; - } - - private void preparePlayback(Intent intent) { - mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent)); - getActivity().getMediaController().getTransportControls().prepare(); - updateRelatedRecordingsRow(); - } - - private void updateRelatedRecordingsRow() { - boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0); - mRelatedRecordingsRowAdapter.clear(); - long programId = mProgram.getId(); - String seriesId = mProgram.getSeriesId(); - if (!TextUtils.isEmpty(seriesId)) { - if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId); - for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) { - if (seriesId.equals(program.getSeriesId()) && programId != program.getId()) { - mRelatedRecordingsRowAdapter.add(program); - } - } - } - View view = getView(); - if (mRelatedRecordingsRowAdapter.size() == 0) { - mRowsAdapter.remove(mRelatedRecordingsRow); - view.setPadding(view.getPaddingLeft(), mExtraPaddingNoRelatedRow, - view.getPaddingRight(), view.getPaddingBottom()); - } else if (wasEmpty){ - mRowsAdapter.add(mRelatedRecordingsRow); - view.setPadding(view.getPaddingLeft(), 0, - view.getPaddingRight(), view.getPaddingBottom()); - } - } - - private void setUpRows() { - PlaybackControlsRowPresenter controlsRowPresenter = - mPlaybackControlHelper.createControlsRowAndPresenter(); - - ClassPresenterSelector selector = new ClassPresenterSelector(); - selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter); - selector.addClassPresenter(ListRow.class, new ListRowPresenter()); - - mRowsAdapter = new ArrayObjectAdapter(selector); - mRowsAdapter.add(mPlaybackControlHelper.getControlsRow()); - mRelatedRecordingsRow = getRelatedRecordingsRow(); - setAdapter(mRowsAdapter); - } - - private ListRow getRelatedRecordingsRow() { - mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity()); - mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter); - HeaderItem header = new HeaderItem(0, - getActivity().getString(R.string.dvr_playback_related_recordings)); - return new ListRow(header, mRelatedRecordingsRowAdapter); - } - - private RecordedProgram getProgramFromIntent(Intent intent) { - long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1); - return mDvrDataManager.getRecordedProgram(programId); - } - - private long getSeekTimeFromIntent(Intent intent) { - return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, - TvInputManager.TIME_SHIFT_INVALID_TIME); - } - - private class RelatedRecordingsAdapter extends SortedArrayAdapter { - RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) { - super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR); - } - - @Override - long getId(BaseProgram item) { - return item.getId(); - } - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java new file mode 100644 index 00000000..e4cb7243 --- /dev/null +++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.app.FragmentManager; +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v17.leanback.widget.GuidanceStylist.Guidance; +import android.support.v17.leanback.widget.GuidedAction; +import android.support.v17.leanback.widget.GuidedActionsStylist; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.data.SeriesRecording; + +import java.util.ArrayList; +import java.util.List; + +/** Fragment for DVR series recording settings. */ +public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { + /** + * Name of series recording id starting the fragment. + * Type: Long + */ + public static final String COME_FROM_SERIES_RECORDING_ID = "series_recording_id"; + + private static final int ONE_TIME_RECORDING_ID = 0; + // button action's IDs are negative. + private static final long ACTION_ID_SAVE = -100L; + + private final List mSeriesRecordings = new ArrayList<>(); + + private SeriesRecording mSelectedRecording; + private SeriesRecording mComeFromSeriesRecording; + private float mSelectedActionElevation; + private int mActionColor; + private int mSelectedActionColor; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mSeriesRecordings.clear(); + mSeriesRecordings.add(new SeriesRecording.Builder() + .setTitle(getString(R.string.dvr_priority_action_one_time_recording)) + .setPriority(Long.MAX_VALUE) + .setId(ONE_TIME_RECORDING_ID) + .build()); + DvrDataManager dvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + long comeFromSeriesRecordingId = + getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1); + for (SeriesRecording series : dvrDataManager.getSeriesRecordings()) { + if (series.getState() == SeriesRecording.STATE_SERIES_NORMAL + || series.getId() == comeFromSeriesRecordingId) { + mSeriesRecordings.add(series); + } + } + mSeriesRecordings.sort(SeriesRecording.PRIORITY_COMPARATOR); + mComeFromSeriesRecording = dvrDataManager.getSeriesRecording(comeFromSeriesRecordingId); + mSelectedActionElevation = getResources().getDimension(R.dimen.card_elevation_normal); + mActionColor = getResources().getColor(R.color.dvr_guided_step_action_text_color, null); + mSelectedActionColor = + getResources().getColor(R.color.dvr_guided_step_action_text_color_selected, null); + } + + @Override + public void onResume() { + super.onResume(); + setSelectedActionPosition(mComeFromSeriesRecording == null ? 1 + : mSeriesRecordings.indexOf(mComeFromSeriesRecording)); + } + + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String breadcrumb = mComeFromSeriesRecording == null ? null + : mComeFromSeriesRecording.getTitle(); + return new Guidance(getString(R.string.dvr_priority_title), + getString(R.string.dvr_priority_description), breadcrumb, null); + } + + @Override + public void onCreateActions(List actions, Bundle savedInstanceState) { + int position = 0; + for (SeriesRecording seriesRecording : mSeriesRecordings) { + actions.add(new GuidedAction.Builder(getActivity()) + .id(position++) + .title(seriesRecording.getTitle()) + .build()); + } + } + + @Override + public void onCreateButtonActions(List actions, Bundle savedInstanceState) { + actions.add(new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SAVE) + .title(getString(R.string.dvr_priority_button_action_save)) + .build()); + actions.add(new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); + } + + @Override + public void onTrackedGuidedActionClicked(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_ID_SAVE) { + DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + int size = mSeriesRecordings.size(); + for (int i = 1; i < size; ++i) { + long priority = DvrScheduleManager.suggestSeriesPriority(size - i); + SeriesRecording seriesRecording = mSeriesRecordings.get(i); + if (seriesRecording.getPriority() != priority) { + dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(seriesRecording) + .setPriority(priority).build()); + } + } + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.popBackStack(); + } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.popBackStack(); + } else if (mSelectedRecording == null) { + mSelectedRecording = mSeriesRecordings.get((int) actionId); + for (int i = 0; i < mSeriesRecordings.size(); ++i) { + updateItem(i); + } + } else { + mSelectedRecording = null; + for (int i = 0; i < mSeriesRecordings.size(); ++i) { + updateItem(i); + } + } + } + + @Override + public String getTrackerPrefix() { + return "DvrPrioritySettingsFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_ID_SAVE) { + return "save"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } + + @Override + public void onGuidedActionFocused(GuidedAction action) { + super.onGuidedActionFocused(action); + if (mSelectedRecording == null) { + return; + } + if (action.getId() < 0) { + mSelectedRecording = null; + for (int i = 0; i < mSeriesRecordings.size(); ++i) { + updateItem(i); + } + return; + } + int position = (int) action.getId(); + int previousPosition = mSeriesRecordings.indexOf(mSelectedRecording); + mSeriesRecordings.remove(mSelectedRecording); + mSeriesRecordings.add(position, mSelectedRecording); + updateItem(previousPosition); + updateItem(position); + notifyActionChanged(previousPosition); + notifyActionChanged(position); + } + + @Override + public GuidedActionsStylist onCreateButtonActionsStylist() { + return new DvrGuidedActionsStylist(true); + } + + @Override + public GuidedActionsStylist onCreateActionsStylist() { + return new DvrGuidedActionsStylist(false) { + @Override + public void onBindViewHolder(ViewHolder vh, GuidedAction action) { + super.onBindViewHolder(vh, action); + updateItem(vh.itemView, (int) action.getId()); + } + + @Override + public int onProvideItemLayoutId() { + return R.layout.priority_settings_action_item; + } + }; + } + + private void updateItem(int position) { + View itemView = getActionItemView(position); + if (itemView == null) { + return; + } + updateItem(itemView, position); + } + + private void updateItem(View itemView, int position) { + GuidedAction action = getActions().get(position); + action.setTitle(mSeriesRecordings.get(position).getTitle()); + boolean selected = mSelectedRecording != null + && mSeriesRecordings.indexOf(mSelectedRecording) == position; + TextView titleView = (TextView) itemView.findViewById(R.id.guidedactions_item_title); + ImageView imageView = (ImageView) itemView.findViewById(R.id.guidedactions_item_tail_image); + if (position == 0) { + // one-time recording + itemView.setBackgroundResource(R.drawable.setup_selector_background); + imageView.setVisibility(View.GONE); + itemView.setFocusable(false); + itemView.setElevation(0); + // strings.xml tag doesn't work. + titleView.setTypeface(titleView.getTypeface(), Typeface.ITALIC); + } else if (mSelectedRecording == null) { + titleView.setTextColor(mActionColor); + itemView.setBackgroundResource(R.drawable.setup_selector_background); + imageView.setImageResource(R.drawable.ic_draggable_white); + imageView.setVisibility(View.VISIBLE); + itemView.setFocusable(true); + itemView.setElevation(0); + titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); + } else if (selected) { + titleView.setTextColor(mSelectedActionColor); + itemView.setBackgroundResource(R.drawable.priority_settings_action_item_selected); + imageView.setImageResource(R.drawable.ic_dragging_grey); + imageView.setVisibility(View.VISIBLE); + itemView.setFocusable(true); + itemView.setElevation(mSelectedActionElevation); + titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); + } else { + titleView.setTextColor(mActionColor); + itemView.setBackgroundResource(R.drawable.setup_selector_background); + imageView.setVisibility(View.INVISIBLE); + itemView.setFocusable(true); + itemView.setElevation(0); + titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java index da6d1637..390e0928 100644 --- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java +++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java @@ -32,9 +32,8 @@ import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment; import com.android.tv.util.Utils; @@ -48,18 +47,26 @@ import java.util.List; */ @TargetApi(Build.VERSION_CODES.N) public class DvrScheduleFragment extends DvrGuidedStepFragment { + /** + * Key for the whether to add the current program to series. + * Type: boolean + */ + public static final String KEY_ADD_CURRENT_PROGRAM_TO_SERIES = "add_current_program_to_series"; + private static final String TAG = "DvrScheduleFragment"; private static final int ACTION_RECORD_EPISODE = 1; private static final int ACTION_RECORD_SERIES = 2; private Program mProgram; + private boolean mAddCurrentProgramToSeries; @Override public void onCreate(Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); + mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false); } DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); SoftPreconditions.checkArgument(mProgram != null && mProgram.isEpisodic(), TAG, @@ -109,7 +116,7 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_RECORD_EPISODE) { getDvrManager().addSchedule(mProgram); List conflicts = getDvrManager().getConflictingSchedules(mProgram); @@ -139,9 +146,28 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { .build(); getDvrManager().updateSeriesRecording(seriesRecording); } + DvrUiHelper.startSeriesSettingsActivity(getContext(), - seriesRecording.getId(), null, true, true, true); + seriesRecording.getId(), null, true, true, true, + mAddCurrentProgramToSeries ? mProgram : null); dismissDialog(); } } + + @Override + public String getTrackerPrefix() { + return "DvrSmallSizedStorageErrorFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_RECORD_EPISODE) { + return "record-episode"; + } else if (actionId == ACTION_RECORD_SERIES) { + return "record-series"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java deleted file mode 100644 index f6e6ac26..00000000 --- a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.os.Bundle; -import android.support.annotation.IntDef; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Program; -import com.android.tv.dvr.EpisodicProgramLoadTask; -import com.android.tv.dvr.SeriesRecording; -import com.android.tv.dvr.SeriesRecordingScheduler; -import com.android.tv.dvr.ui.list.DvrSchedulesFragment; -import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Activity to show the list of recording schedules. - */ -public class DvrSchedulesActivity extends Activity { - /** - * The key for the type of the schedules which will be listed in the list. The type of the value - * should be {@link ScheduleListType}. - */ - public static final String KEY_SCHEDULES_TYPE = "schedules_type"; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({TYPE_FULL_SCHEDULE, TYPE_SERIES_SCHEDULE}) - public @interface ScheduleListType {} - /** - * A type which means the activity will display the full scheduled recordings. - */ - public static final int TYPE_FULL_SCHEDULE = 0; - /** - * A type which means the activity will display a scheduled recording list of a series - * recording. - */ - public static final int TYPE_SERIES_SCHEDULE = 1; - - @Override - public void onCreate(final Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); - // Pass null to prevent automatically re-creating fragments - super.onCreate(null); - setContentView(R.layout.activity_dvr_schedules); - int scheduleType = getIntent().getIntExtra(KEY_SCHEDULES_TYPE, TYPE_FULL_SCHEDULE); - if (scheduleType == TYPE_FULL_SCHEDULE) { - DvrSchedulesFragment schedulesFragment = new DvrSchedulesFragment(); - schedulesFragment.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction().add( - R.id.fragment_container, schedulesFragment).commit(); - } else if (scheduleType == TYPE_SERIES_SCHEDULE) { - final ProgressDialog dialog = ProgressDialog.show(this, null, getString( - R.string.dvr_series_schedules_progress_message_reading_programs)); - SeriesRecording seriesRecording = getIntent().getExtras() - .getParcelable(DvrSeriesSchedulesFragment - .SERIES_SCHEDULES_KEY_SERIES_RECORDING); - // To get programs faster, hold the update of the series schedules. - SeriesRecordingScheduler.getInstance(this).pauseUpdate(); - new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) { - @Override - protected void onPostExecute(List programs) { - SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this).resumeUpdate(); - dialog.dismiss(); - Bundle args = getIntent().getExtras(); - args.putParcelableArrayList(DvrSeriesSchedulesFragment - .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, new ArrayList<>(programs)); - DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment(); - schedulesFragment.setArguments(args); - getFragmentManager().beginTransaction().add( - R.id.fragment_container, schedulesFragment).commit(); - } - }.setLoadCurrentProgram(true) - .setLoadDisallowedProgram(true) - .setLoadScheduledEpisode(true) - .setIgnoreChannelOption(true) - .execute(); - } else { - finish(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java index f57e4b05..667af34a 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java @@ -22,9 +22,6 @@ import android.support.v17.leanback.app.GuidedStepFragment; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.ui.SeriesDeletionFragment; -import com.android.tv.ui.sidepanel.SettingsFragment; /** * Activity to show details view in DVR. @@ -42,7 +39,7 @@ public class DvrSeriesDeletionActivity extends Activity { setContentView(R.layout.activity_dvr_series_settings); // Check savedInstanceState to prevent that activity is being showed with animation. if (savedInstanceState == null) { - SeriesDeletionFragment deletionFragment = new SeriesDeletionFragment(); + DvrSeriesDeletionFragment deletionFragment = new DvrSeriesDeletionFragment(); deletionFragment.setArguments(getIntent().getExtras()); GuidedStepFragment.addAsRoot(this, deletionFragment, R.id.dvr_settings_view_frame); } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java new file mode 100644 index 00000000..8bf8560f --- /dev/null +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.content.Context; +import android.media.tv.TvInputManager; +import android.os.Bundle; +import android.support.v17.leanback.app.GuidedStepFragment; +import android.support.v17.leanback.widget.GuidanceStylist.Guidance; +import android.support.v17.leanback.widget.GuidedAction; +import android.support.v17.leanback.widget.GuidedActionsStylist; +import android.text.TextUtils; +import android.view.ViewGroup.LayoutParams; +import android.widget.Toast; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.ui.GuidedActionsStylistWithDivider; +import com.android.tv.util.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Fragment for DVR series recording settings. + */ +public class DvrSeriesDeletionFragment extends GuidedStepFragment { + private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2); + + // Since recordings' IDs are used as its check actions' IDs, which are random positive numbers, + // negative values are used by other actions to prevent duplicated IDs. + private static final long ACTION_ID_SELECT_WATCHED = -110; + private static final long ACTION_ID_SELECT_ALL = -111; + private static final long ACTION_ID_DELETE = -112; + + private DvrDataManager mDvrDataManager; + private DvrWatchedPositionManager mDvrWatchedPositionManager; + private List mRecordings; + private final Set mWatchedRecordings = new HashSet<>(); + private boolean mAllSelected; + private long mSeriesRecordingId; + private int mOneLineActionHeight; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mSeriesRecordingId = getArguments() + .getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); + SoftPreconditions.checkArgument(mSeriesRecordingId != -1); + mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrWatchedPositionManager = + TvApplication.getSingletons(context).getDvrWatchedPositionManager(); + mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); + mOneLineActionHeight = getResources().getDimensionPixelSize( + R.dimen.dvr_settings_one_line_action_container_height); + if (mRecordings.isEmpty()) { + Toast.makeText(getActivity(), getString(R.string.dvr_series_deletion_no_recordings), + Toast.LENGTH_LONG).show(); + finishGuidedStepFragments(); + return; + } + Collections.sort(mRecordings, RecordedProgram.EPISODE_COMPARATOR); + } + + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String breadcrumb = null; + SeriesRecording series = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); + if (series != null) { + breadcrumb = series.getTitle(); + } + return new Guidance(getString(R.string.dvr_series_deletion_title), + getString(R.string.dvr_series_deletion_description), breadcrumb, null); + } + + @Override + public void onCreateActions(List actions, Bundle savedInstanceState) { + actions.add(new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SELECT_WATCHED) + .title(getString(R.string.dvr_series_select_watched)) + .build()); + actions.add(new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SELECT_ALL) + .title(getString(R.string.dvr_series_select_all)) + .build()); + actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext())); + for (RecordedProgram recording : mRecordings) { + long watchedPositionMs = + mDvrWatchedPositionManager.getWatchedPosition(recording.getId()); + String title = recording.getEpisodeDisplayTitle(getContext()); + if (TextUtils.isEmpty(title)) { + title = TextUtils.isEmpty(recording.getTitle()) ? + getString(R.string.channel_banner_no_title) : recording.getTitle(); + } + String description; + if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + description = getWatchedString(watchedPositionMs, recording.getDurationMillis()); + mWatchedRecordings.add(recording.getId()); + } else { + description = getString(R.string.dvr_series_never_watched); + } + actions.add(new GuidedAction.Builder(getActivity()) + .id(recording.getId()) + .title(title) + .description(description) + .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) + .build()); + } + } + + @Override + public void onCreateButtonActions(List actions, Bundle savedInstanceState) { + actions.add(new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_DELETE) + .title(getString(R.string.dvr_detail_delete)) + .build()); + actions.add(new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); + } + + @Override + public void onGuidedActionClicked(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_ID_DELETE) { + List idsToDelete = new ArrayList<>(); + for (GuidedAction guidedAction : getActions()) { + if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID + && guidedAction.isChecked()) { + idsToDelete.add(guidedAction.getId()); + } + } + if (!idsToDelete.isEmpty()) { + DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); + dvrManager.removeRecordedPrograms(idsToDelete); + } + Toast.makeText(getContext(), getResources().getQuantityString( + R.plurals.dvr_msg_episodes_deleted, idsToDelete.size(), idsToDelete.size(), + mRecordings.size()), Toast.LENGTH_LONG).show(); + finishGuidedStepFragments(); + } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { + finishGuidedStepFragments(); + } else if (actionId == ACTION_ID_SELECT_WATCHED) { + for (GuidedAction guidedAction : getActions()) { + if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { + long recordingId = guidedAction.getId(); + if (mWatchedRecordings.contains(recordingId)) { + guidedAction.setChecked(true); + } else { + guidedAction.setChecked(false); + } + notifyActionChanged(findActionPositionById(recordingId)); + } + } + mAllSelected = updateSelectAllState(); + } else if (actionId == ACTION_ID_SELECT_ALL) { + mAllSelected = !mAllSelected; + for (GuidedAction guidedAction : getActions()) { + if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { + guidedAction.setChecked(mAllSelected); + notifyActionChanged(findActionPositionById(guidedAction.getId())); + } + } + updateSelectAllState(action, mAllSelected); + } else { + mAllSelected = updateSelectAllState(); + } + } + + @Override + public GuidedActionsStylist onCreateButtonActionsStylist() { + return new DvrGuidedActionsStylist(true); + } + + @Override + public GuidedActionsStylist onCreateActionsStylist() { + return new GuidedActionsStylistWithDivider() { + @Override + public void onBindViewHolder(ViewHolder vh, GuidedAction action) { + super.onBindViewHolder(vh, action); + if (action.getId() == ACTION_DIVIDER) { + return; + } + LayoutParams lp = vh.itemView.getLayoutParams(); + if (action.getCheckSetId() != GuidedAction.CHECKBOX_CHECK_SET_ID) { + lp.height = mOneLineActionHeight; + } else { + vh.itemView.setLayoutParams( + new LayoutParams(lp.width, LayoutParams.WRAP_CONTENT)); + } + } + }; + } + + private String getWatchedString(long watchedPositionMs, long durationMs) { + if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) { + return getResources().getString(R.string.dvr_series_watched_info_minutes, + Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)), + Utils.getRoundOffMinsFromMs(durationMs)); + } else { + return getResources().getString(R.string.dvr_series_watched_info_seconds, + Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), + TimeUnit.MILLISECONDS.toSeconds(durationMs)); + } + } + + private boolean updateSelectAllState() { + for (GuidedAction guidedAction : getActions()) { + if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { + if (!guidedAction.isChecked()) { + if (mAllSelected) { + updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), false); + } + return false; + } + } + } + if (!mAllSelected) { + updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), true); + } + return true; + } + + private void updateSelectAllState(GuidedAction selectAll, boolean select) { + selectAll.setTitle(select ? getString(R.string.dvr_series_deselect_all) + : getString(R.string.dvr_series_select_all)); + notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL)); + } +} diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java index 1173df46..2c4bb3ea 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java @@ -25,22 +25,29 @@ import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrDataManager; +import com.android.tv.data.Program; import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.list.DvrSchedulesActivity; import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; import java.util.List; public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { + /** + * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}. + * Type: List<{@link Program}> + */ + public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs"; + private final static long SERIES_RECORDING_ID_NOT_SET = -1; private final static int ACTION_VIEW_SCHEDULES = 1; - private DvrScheduleManager mDvrScheduleManager; private SeriesRecording mSeriesRecording; private boolean mShowViewScheduleOption; + private List mPrograms; private int mSchedulesAddedCount = 0; private boolean mHasConflict = false; @@ -58,22 +65,25 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { } mShowViewScheduleOption = getArguments().getBoolean( DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION); - mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager(); mSeriesRecording = TvApplication.getSingletons(context).getDvrDataManager() .getSeriesRecording(seriesRecordingId); if (mSeriesRecording == null) { getActivity().finish(); return; } + mPrograms = (List) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS); + BigArguments.reset(); mSchedulesAddedCount = TvApplication.getSingletons(getContext()).getDvrManager() .getAvailableScheduledRecording(mSeriesRecording.getId()).size(); + DvrScheduleManager dvrScheduleManager = + TvApplication.getSingletons(context).getDvrScheduleManager(); List conflictingRecordings = - mDvrScheduleManager.getConflictingSchedules(mSeriesRecording); + dvrScheduleManager.getConflictingSchedules(mSeriesRecording); mHasConflict = !conflictingRecordings.isEmpty(); for (ScheduledRecording recording : conflictingRecordings) { if (recording.getSeriesRecordingId() == mSeriesRecording.getId()) { ++mInThisSeriesConflictCount; - } else { + } else if (recording.getPriority() < mSeriesRecording.getPriority()) { ++mOutThisSeriesConflictCount; } } @@ -106,45 +116,63 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_VIEW_SCHEDULES) { Intent intent = new Intent(getActivity(), DvrSchedulesActivity.class); intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity .TYPE_SERIES_SCHEDULE); intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, mSeriesRecording); + BigArguments.reset(); + BigArguments.setArgument(DvrSeriesSchedulesFragment + .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms); startActivity(intent); } getActivity().finish(); } + @Override + public String getTrackerPrefix() { + return "DvrMissingStorageErrorFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_VIEW_SCHEDULES) { + return "view-schedules"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } + private String getDescription() { if (!mHasConflict) { return getResources().getQuantityString( - R.plurals.dvr_series_recording_scheduled_no_conflict, mSchedulesAddedCount, + R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle()); } else { // mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means // mHasConflict is false. So we don't need to check that case. if (mInThisSeriesConflictCount != 0 && mOutThisSeriesConflictCount != 0) { - return getResources().getQuantityString(R.plurals - .dvr_series_recording_scheduled_this_and_other_series_conflict, + return getResources().getQuantityString( + R.plurals.dvr_series_scheduled_this_and_other_series_conflict, mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), mInThisSeriesConflictCount + mOutThisSeriesConflictCount); } else if (mInThisSeriesConflictCount != 0) { - return getResources().getQuantityString(R.plurals - .dvr_series_recording_scheduled_only_this_series_conflict, + return getResources().getQuantityString( + R.plurals.dvr_series_recording_scheduled_only_this_series_conflict, mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), mInThisSeriesConflictCount); } else { if (mOutThisSeriesConflictCount == 1) { - return getResources().getQuantityString(R.plurals - .dvr_series_recording_scheduled_only_other_series_one_conflict, + return getResources().getQuantityString( + R.plurals.dvr_series_scheduled_only_other_series_one_conflict, mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle()); } else { - return getResources().getQuantityString(R.plurals - .dvr_series_recording_scheduled_only_other_series_conflict, + return getResources().getQuantityString( + R.plurals.dvr_series_scheduled_only_other_series_many_conflicts, mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), mOutThisSeriesConflictCount); } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java index 3f7671b3..6dd20b3a 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java @@ -17,7 +17,6 @@ package com.android.tv.dvr.ui; import android.app.Activity; -import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; @@ -38,25 +37,34 @@ public class DvrSeriesSettingsActivity extends Activity { /** * Name of the boolean flag to decide if the series recording with empty schedule and recording * will be removed. + * Type: boolean */ public static final String REMOVE_EMPTY_SERIES_RECORDING = "remove_empty_series_recording"; /** * Name of the boolean flag to decide if the setting fragment should be translucent. + * Type: boolean */ public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent"; /** - * Name of the channel id list. If the channel list is given, we show the channels - * from the values in channel option. - * Type: Long array + * Name of the program list. The list contains the programs which belong to the series. + * Type: List<{@link com.android.tv.data.Program}> */ - public static final String CHANNEL_ID_LIST = "channel_id_list"; + public static final String PROGRAM_LIST = "program_list"; /** * Name of the boolean flag to check if the confirm dialog should show view schedule option. + * Type: boolean */ public static final String SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG = "show_view_schedule_option_in_dialog"; + /** + * Name of the current program added to series. The current program will be recorded only when + * the series recording is initialized from media controller. But for other case, the current + * program won't be recorded. + */ + public static final String CURRENT_PROGRAM = "current_program"; + @Override public void onCreate(Bundle savedInstanceState) { TvApplication.setCurrentRunningProcess(this, true); @@ -66,7 +74,7 @@ public class DvrSeriesSettingsActivity extends Activity { SoftPreconditions.checkArgument(seriesRecordingId != -1); if (savedInstanceState == null) { - SeriesSettingsFragment settingFragment = new SeriesSettingsFragment(); + DvrSeriesSettingsFragment settingFragment = new DvrSeriesSettingsFragment(); settingFragment.setArguments(getIntent().getExtras()); GuidedStepFragment.addAsRoot(this, settingFragment, R.id.dvr_settings_view_frame); } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java new file mode 100644 index 00000000..f28382da --- /dev/null +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.app.FragmentManager; +import android.content.Context; +import android.os.Bundle; +import android.support.v17.leanback.app.GuidedStepFragment; +import android.support.v17.leanback.widget.GuidanceStylist.Guidance; +import android.support.v17.leanback.widget.GuidedAction; +import android.support.v17.leanback.widget.GuidedActionsStylist; +import android.util.LongSparseArray; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeasonEpisodeNumber; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.data.SeriesRecording.ChannelOption; +import com.android.tv.dvr.recorder.SeriesRecordingScheduler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Fragment for DVR series recording settings. + */ +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; + + private static final long SUB_ACTION_ID_CHANNEL_ALL = 102; + // Each channel's action id = SUB_ACTION_ID_CHANNEL_ONE_BASE + channel id + private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500; + + private DvrDataManager mDvrDataManager; + private SeriesRecording mSeriesRecording; + private long mSeriesRecordingId; + @ChannelOption int mChannelOption; + private long mSelectedChannelId; + private int mBackStackCount; + private boolean mShowViewScheduleOptionInDialog; + private Program mCurrentProgram; + + private String mFragmentTitle; + private String mProrityActionTitle; + private String mProrityActionHighestText; + private String mProrityActionLowestText; + private String mChannelsActionTitle; + private String mChannelsActionAllText; + private LongSparseArray mId2Channel = new LongSparseArray<>(); + private List mChannels = new ArrayList<>(); + private List mPrograms; + + private GuidedAction mPriorityGuidedAction; + private GuidedAction mChannelsGuidedAction; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mBackStackCount = getFragmentManager().getBackStackEntryCount(); + mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID); + mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); + if (mSeriesRecording == null) { + getActivity().finish(); + return; + } + mShowViewScheduleOptionInDialog = getArguments().getBoolean( + DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); + mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM); + mDvrDataManager.addSeriesRecordingListener(this); + mPrograms = (List) BigArguments.getArgument( + DvrSeriesSettingsActivity.PROGRAM_LIST); + BigArguments.reset(); + if (mPrograms == null) { + getActivity().finish(); + return; + } + Set channelIds = new HashSet<>(); + ChannelDataManager channelDataManager = + TvApplication.getSingletons(context).getChannelDataManager(); + for (Program program : mPrograms) { + long channelId = program.getChannelId(); + if (channelIds.add(channelId)) { + Channel channel = channelDataManager.getChannel(channelId); + if (channel != null) { + mId2Channel.put(channel.getId(), channel); + mChannels.add(channel); + } + } + } + mChannelOption = mSeriesRecording.getChannelOption(); + mSelectedChannelId = Channel.INVALID_ID; + if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) { + Channel channel = channelDataManager.getChannel(mSeriesRecording.getChannelId()); + if (channel != null) { + mSelectedChannelId = channel.getId(); + } else { + mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; + } + } + mChannels.sort(Channel.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); + mProrityActionLowestText = getString(R.string.dvr_series_settings_priority_lowest); + mChannelsActionTitle = getString(R.string.dvr_series_settings_channels); + mChannelsActionAllText = getString(R.string.dvr_series_settings_channels_all); + } + + @Override + public void onResume() { + super.onResume(); + // To avoid the order of series's priority has changed, but series doesn't get update. + updatePriorityGuidedAction(); + } + + @Override + public void onDetach() { + super.onDetach(); + mDvrDataManager.removeSeriesRecordingListener(this); + } + + @Override + public void onDestroy() { + if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments() + .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) { + mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId); + } + super.onDestroy(); + } + + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String breadcrumb = mSeriesRecording.getTitle(); + String title = mFragmentTitle; + return new Guidance(title, null, breadcrumb, null); + } + + @Override + public void onCreateActions(List actions, Bundle savedInstanceState) { + mPriorityGuidedAction = new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_PRIORITY) + .title(mProrityActionTitle) + .build(); + actions.add(mPriorityGuidedAction); + + mChannelsGuidedAction = new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_CHANNEL) + .title(mChannelsActionTitle) + .subActions(buildChannelSubAction()) + .build(); + actions.add(mChannelsGuidedAction); + updateChannelsGuidedAction(false); + } + + @Override + public void onCreateButtonActions(List actions, Bundle savedInstanceState) { + actions.add(new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_OK) + .build()); + actions.add(new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); + } + + @Override + public void onGuidedActionClicked(GuidedAction action) { + long actionId = action.getId(); + if (actionId == GuidedAction.ACTION_ID_OK) { + if (mChannelOption != mSeriesRecording.getChannelOption() + || mSeriesRecording.isStopped() + || (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE + && mSeriesRecording.getChannelId() != mSelectedChannelId)) { + SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording) + .setChannelOption(mChannelOption) + .setState(SeriesRecording.STATE_SERIES_NORMAL); + if (mSelectedChannelId != Channel.INVALID_ID) { + builder.setChannelId(mSelectedChannelId); + } + DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + dvrManager.updateSeriesRecording(builder.build()); + if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL + || mSelectedChannelId == mCurrentProgram.getChannelId())) { + dvrManager.addSchedule(mCurrentProgram); + } + updateSchedulesToSeries(); + showConfirmDialog(); + } else { + showConfirmDialog(); + } + } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { + finishGuidedStepFragments(); + } else if (actionId == ACTION_ID_PRIORITY) { + FragmentManager fragmentManager = getFragmentManager(); + DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment(); + Bundle args = new Bundle(); + args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, + mSeriesRecording.getId()); + fragment.setArguments(args); + GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame); + } + } + + @Override + public boolean onSubGuidedActionClicked(GuidedAction action) { + long actionId = action.getId(); + if (actionId == SUB_ACTION_ID_CHANNEL_ALL) { + mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; + mSelectedChannelId = Channel.INVALID_ID; + updateChannelsGuidedAction(true); + return true; + } else if (actionId > SUB_ACTION_ID_CHANNEL_ONE_BASE) { + mChannelOption = SeriesRecording.OPTION_CHANNEL_ONE; + mSelectedChannelId = actionId - SUB_ACTION_ID_CHANNEL_ONE_BASE; + updateChannelsGuidedAction(true); + return true; + } + return false; + } + + @Override + public GuidedActionsStylist onCreateButtonActionsStylist() { + return new DvrGuidedActionsStylist(true); + } + + private void updateChannelsGuidedAction(boolean notifyActionChanged) { + if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) { + mChannelsGuidedAction.setDescription(mChannelsActionAllText); + } else if (mId2Channel.get(mSelectedChannelId) != null){ + mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId) + .getDisplayText()); + } + if (notifyActionChanged) { + notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); + } + } + + private void updatePriorityGuidedAction() { + int totalSeriesCount = 0; + int priorityOrder = 0; + for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) { + if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL + || seriesRecording.getId() == mSeriesRecording.getId()) { + ++totalSeriesCount; + } + if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL + && seriesRecording.getId() != mSeriesRecording.getId() + && seriesRecording.getPriority() > mSeriesRecording.getPriority()) { + ++priorityOrder; + } + } + if (priorityOrder == 0) { + mPriorityGuidedAction.setDescription(mProrityActionHighestText); + } else if (priorityOrder >= totalSeriesCount - 1) { + mPriorityGuidedAction.setDescription(mProrityActionLowestText); + } else { + mPriorityGuidedAction.setDescription(getString( + R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); + } + notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY)); + } + + private void updateSchedulesToSeries() { + List recordingCandidates = new ArrayList<>(); + Set scheduledEpisodes = new HashSet<>(); + for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) { + if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED + && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) { + scheduledEpisodes.add(new SeasonEpisodeNumber( + r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber())); + } + } + for (Program program : mPrograms) { + // Removes current programs and scheduled episodes out, matches the channel option. + if (program.getStartTimeUtcMillis() >= System.currentTimeMillis() + && mSeriesRecording.matchProgram(program) + && !scheduledEpisodes.contains(new SeasonEpisodeNumber( + mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) { + recordingCandidates.add(program); + } + } + if (recordingCandidates.isEmpty()) { + return; + } + List programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates) + .get(mSeriesRecordingId); + if (!programsToSchedule.isEmpty()) { + TvApplication.getSingletons(getContext()).getDvrManager() + .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule); + } + } + + private List buildChannelSubAction() { + List channelSubActions = new ArrayList<>(); + channelSubActions.add(new GuidedAction.Builder(getActivity()) + .id(SUB_ACTION_ID_CHANNEL_ALL) + .title(mChannelsActionAllText) + .build()); + for (Channel channel : mChannels) { + channelSubActions.add(new GuidedAction.Builder(getActivity()) + .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) + .title(channel.getDisplayText()) + .build()); + } + return channelSubActions; + } + + private void showConfirmDialog() { + DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording, + mShowViewScheduleOptionInDialog, mPrograms); + finishGuidedStepFragments(); + } + + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + for (SeriesRecording series : seriesRecordings) { + if (series.getId() == mSeriesRecording.getId()) { + finishGuidedStepFragments(); + return; + } + } + } + + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + if (seriesRecording.getId() == mSeriesRecordingId) { + mSeriesRecording = seriesRecording; + updatePriorityGuidedAction(); + return; + } + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java index c3867886..baa45793 100644 --- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java @@ -25,15 +25,12 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; -import android.text.TextUtils; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -131,15 +128,8 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { String title = getString(R.string.dvr_stop_recording_dialog_title); String description; if (mStopReason == REASON_ON_CONFLICT) { - String programTitle = mSchedule.getProgramTitle(); - if (TextUtils.isEmpty(programTitle)) { - ChannelDataManager channelDataManager = - TvApplication.getSingletons(getActivity()).getChannelDataManager(); - Channel channel = channelDataManager.getChannel(mSchedule.getChannelId()); - programTitle = channel.getDisplayName(); - } description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict, - mSchedule.getProgramTitle()); + mSchedule.getProgramDisplayTitle(getContext())); } else { description = getString(R.string.dvr_stop_recording_dialog_description); } @@ -158,4 +148,19 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { .clickAction(GuidedAction.ACTION_ID_CANCEL) .build()); } + + @Override + public String getTrackerPrefix() { + return "DvrStopRecordingFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == ACTION_STOP) { + return "stop"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java index feaa2357..7b56cfc1 100644 --- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java @@ -31,8 +31,8 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import java.util.ArrayList; import java.util.List; @@ -78,7 +78,7 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { } @Override - public void onGuidedActionClicked(GuidedAction action) { + public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_STOP_SERIES_RECORDING) { ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); DvrManager dvrManager = singletons.getDvrManager(); @@ -101,4 +101,18 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { } dismissDialog(); } + + @Override + public String getTrackerPrefix() { + return "DvrStopSeriesRecordingFragment"; + } + + @Override + public String getTrackerLabelForGuidedAction(GuidedAction action) { + if (action.getId() == ACTION_STOP_SERIES_RECORDING) { + return "stop"; + } else { + return super.getTrackerLabelForGuidedAction(action); + } + } } diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java new file mode 100644 index 00000000..302fd6cd --- /dev/null +++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.tv.TvInputManager; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityOptionsCompat; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.TextAppearanceSpan; +import android.widget.ImageView; +import android.widget.Toast; + +import com.android.tv.MainActivity; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.BaseProgram; +import com.android.tv.data.Channel; +import com.android.tv.data.Program; +import com.android.tv.dialog.HalfSizedDialogFragment; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrStorageStatusManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.provider.EpisodicProgramLoadTask; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment; +import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment; +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.DvrSchedulesActivity; +import com.android.tv.dvr.ui.list.DvrSchedulesFragment; +import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; +import com.android.tv.dvr.ui.playback.DvrPlaybackActivity; +import com.android.tv.util.ToastUtils; +import com.android.tv.util.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * A helper class for DVR UI. + */ +@MainThread +@TargetApi(Build.VERSION_CODES.N) +public class DvrUiHelper { + private static final String TAG = "DvrUiHelper"; + + private static ProgressDialog sProgressDialog = null; + + /** + * Checks if the storage status is good for recording and shows error messages if needed. + * + * @param recordingRequestRunnable if the storage status is OK to record or users choose to + * perform the operation anyway, this Runnable will run. + */ + public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId, + Runnable recordingRequestRunnable) { + if (Utils.isBundledInput(inputId)) { + switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager() + .getDvrStorageStatus()) { + case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL: + showDvrSmallSizedStorageErrorDialog(activity); + return; + case DvrStorageStatusManager.STORAGE_STATUS_MISSING: + showDvrMissingStorageErrorDialog(activity); + return; + case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT: + showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable); + return; + } + } + recordingRequestRunnable.run(); + } + + /** + * Shows the schedule dialog. + */ + public static void showScheduleDialog(Activity activity, Program program, + boolean addCurrentProgramToSeries) { + if (SoftPreconditions.checkNotNull(program) == null) { + return; + } + Bundle args = new Bundle(); + args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); + args.putBoolean(DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, + addCurrentProgramToSeries); + showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true); + } + + /** + * Shows the recording duration options dialog. + */ + public static void showChannelRecordDurationOptions(Activity activity, Channel channel) { + if (SoftPreconditions.checkNotNull(channel) == null) { + return; + } + Bundle args = new Bundle(); + args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); + showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args); + } + + /** + * Shows the dialog which says that the new schedule conflicts with others. + */ + public static void showScheduleConflictDialog(Activity activity, Program program) { + if (program == null) { + return; + } + Bundle args = new Bundle(); + args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); + showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true); + } + + /** + * Shows the conflict dialog for the channel watching. + */ + public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) { + if (channel == null) { + return; + } + Bundle args = new Bundle(); + args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); + showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args); + } + + /** + * Shows DVR insufficient space error dialog. + */ + public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity, + Set failedScheduledRecordingInfoSet) { + Bundle args = new Bundle(); + ArrayList failedScheduledRecordingInfoArray = + new ArrayList<>(failedScheduledRecordingInfoSet); + args.putStringArrayList(DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS, + failedScheduledRecordingInfoArray); + showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args); + Utils.clearRecordingFailedReason(activity, + TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + Utils.clearFailedScheduledRecordingInfoSet(activity); + } + + /** + * Shows DVR no free space error dialog. + * + * @param recordingRequestRunnable the recording request to be executed when users choose + * {@link DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}. + */ + public static void showDvrNoFreeSpaceErrorDialog(Activity activity, + Runnable recordingRequestRunnable) { + DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment(); + fragment.setOnActionClickListener(new HalfSizedDialogFragment.OnActionClickListener() { + @Override + public void onActionClick(long actionId) { + if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) { + recordingRequestRunnable.run(); + } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) { + Intent intent = new Intent(activity, DvrBrowseActivity.class); + activity.startActivity(intent); + } + } + }); + showDialogFragment(activity, fragment, null); + } + + /** + * Shows DVR missing storage error dialog. + */ + private static void showDvrMissingStorageErrorDialog(Activity activity) { + showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null); + } + + /** + * Shows DVR small sized storage error dialog. + */ + public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { + showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); + } + + /** + * Shows stop recording dialog. + */ + public static void showStopRecordingDialog(Activity activity, long channelId, int reason, + HalfSizedDialogFragment.OnActionClickListener listener) { + Bundle args = new Bundle(); + args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId); + args.putInt(DvrStopRecordingFragment.KEY_REASON, reason); + DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment(); + fragment.setOnActionClickListener(listener); + showDialogFragment(activity, fragment, args); + } + + /** + * Shows "already scheduled" dialog. + */ + public static void showAlreadyScheduleDialog(Activity activity, Program program) { + if (program == null) { + return; + } + Bundle args = new Bundle(); + args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); + showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true); + } + + /** + * Shows "already recorded" dialog. + */ + public static void showAlreadyRecordedDialog(Activity activity, Program program) { + if (program == null) { + return; + } + Bundle args = new Bundle(); + args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); + showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true); + } + + /** + * Handle the request of recording a current program. It will handle creating schedules and + * shows the proper dialog and toast message respectively for timed-recording and program + * recording cases. + * + * @param addProgramToSeries denotes whether the program to be recorded should be added into + * the series recording when users choose to record the entire series. + */ + public static void requestRecordingCurrentProgram(Activity activity, + Channel channel, Program program, boolean addProgramToSeries) { + if (program == null) { + DvrUiHelper.showChannelRecordDurationOptions(activity, channel); + } else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { + String msg = activity.getString(R.string.dvr_msg_current_program_scheduled, + program.getTitle(), Utils.toTimeString(program.getEndTimeUtcMillis(), false)); + Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); + } + } + + /** + * Handle the request of recording a future program. It will handle creating schedules and + * shows the proper toast message. + * + * @param addProgramToSeries denotes whether the program to be recorded should be added into + * the series recording when users choose to record the entire series. + */ + public static void requestRecordingFutureProgram(Activity activity, + Program program, boolean addProgramToSeries) { + if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { + String msg = activity.getString( + R.string.dvr_msg_program_scheduled, program.getTitle()); + ToastUtils.show(activity, msg, Toast.LENGTH_SHORT); + } + } + + /** + * Handles the action to create the new schedule. It returns {@code true} if the schedule is + * added and there's no additional UI, otherwise {@code false}. + */ + private static boolean handleCreateSchedule(Activity activity, Program program, + boolean addProgramToSeries) { + if (program == null) { + return false; + } + DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager(); + if (!program.isEpisodic()) { + // One time recording. + dvrManager.addSchedule(program); + if (!dvrManager.getConflictingSchedules(program).isEmpty()) { + DvrUiHelper.showScheduleConflictDialog(activity, program); + return false; + } + } else { + // Show recorded program rather than the schedule. + RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(), + program.getSeasonNumber(), program.getEpisodeNumber()); + if (recordedProgram != null) { + DvrUiHelper.showAlreadyRecordedDialog(activity, program); + return false; + } + ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(), + program.getSeasonNumber(), program.getEpisodeNumber()); + if (duplicate != null + && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED + || duplicate.getState() + == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + DvrUiHelper.showAlreadyScheduleDialog(activity, program); + return false; + } + SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program); + if (seriesRecording == null || seriesRecording.isStopped()) { + DvrUiHelper.showScheduleDialog(activity, program, addProgramToSeries); + return false; + } else { + // Just add the schedule. + dvrManager.addSchedule(program); + } + } + return true; + } + + private static void showDialogFragment(Activity activity, + DvrHalfSizedDialogFragment dialogFragment, Bundle args) { + showDialogFragment(activity, dialogFragment, args, false, false); + } + + private static void showDialogFragment(Activity activity, + DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, + boolean keepProgramGuide) { + dialogFragment.setArguments(args); + if (activity instanceof MainActivity) { + ((MainActivity) activity).getOverlayManager() + .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment, + keepSidePanelHistory, keepProgramGuide); + } else { + dialogFragment.show(activity.getFragmentManager(), + DvrHalfSizedDialogFragment.DIALOG_TAG); + } + } + + /** + * Checks whether channel watch conflict dialog is open or not. + */ + public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { + return activity.getOverlayManager().getCurrentDialog() instanceof + DvrChannelWatchConflictDialogFragment; + } + + private static ScheduledRecording getEarliestScheduledRecording(List + recordings) { + ScheduledRecording earlistScheduledRecording = null; + if (!recordings.isEmpty()) { + Collections.sort(recordings, + ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); + earlistScheduledRecording = recordings.get(0); + } + return earlistScheduledRecording; + } + + /** + * Launches DVR playback activity for the give recorded program. + * + * @param programId the ID of the recorded program going to be played. + * @param seekTimeMs the seek position to initial playback. + * @param pinChecked {@code true} if the pin code for parental controls has already been + * verified, otherwise {@code false}. + */ + public static void startPlaybackActivity(Context context, long programId, + long seekTimeMs, boolean pinChecked) { + Intent intent = new Intent(context, DvrPlaybackActivity.class); + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); + if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs); + } + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked); + context.startActivity(intent); + } + + /** + * Shows the schedules activity to resolve the tune conflict. + */ + public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) { + if (channel == null) { + return; + } + List conflicts = TvApplication.getSingletons(context).getDvrManager() + .getConflictingSchedulesForTune(channel.getId()); + startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); + } + + /** + * Shows the schedules activity to resolve the one time recording conflict. + */ + public static void startSchedulesActivityForOneTimeRecordingConflict(Context context, + List conflicts) { + startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); + } + + /** + * Shows the schedules activity with full schedule. + */ + public static void startSchedulesActivity(Context context, ScheduledRecording + focusedScheduledRecording) { + Intent intent = new Intent(context, DvrSchedulesActivity.class); + intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, + DvrSchedulesActivity.TYPE_FULL_SCHEDULE); + if (focusedScheduledRecording != null) { + intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, + focusedScheduledRecording); + } + context.startActivity(intent); + } + + /** + * Shows the schedules activity for series recording. + */ + public static void startSchedulesActivityForSeries(Context context, + SeriesRecording seriesRecording) { + Intent intent = new Intent(context, DvrSchedulesActivity.class); + intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, + DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); + intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, + seriesRecording); + context.startActivity(intent); + } + + /** + * Shows the series settings activity. + * + * @param programs list of programs which belong to the series. + */ + public static void startSeriesSettingsActivity(Context context, long seriesRecordingId, + @Nullable List programs, boolean removeEmptySeriesSchedule, + boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, + Program currentProgram) { + SeriesRecording series = TvApplication.getSingletons(context).getDvrDataManager() + .getSeriesRecording(seriesRecordingId); + if (series == null) { + return; + } + if (programs != null) { + startSeriesSettingsActivityInternal(context, seriesRecordingId, programs, + removeEmptySeriesSchedule, isWindowTranslucent, + showViewScheduleOptionInDialog, currentProgram); + } else { + EpisodicProgramLoadTask episodicProgramLoadTask = + new EpisodicProgramLoadTask(context, series) { + @Override + protected void onPostExecute(List loadedPrograms) { + sProgressDialog.dismiss(); + sProgressDialog = null; + startSeriesSettingsActivityInternal(context, seriesRecordingId, + loadedPrograms == null ? Collections.EMPTY_LIST : loadedPrograms, + removeEmptySeriesSchedule, isWindowTranslucent, + showViewScheduleOptionInDialog, currentProgram); + } + }.setLoadCurrentProgram(true) + .setLoadDisallowedProgram(true) + .setLoadScheduledEpisode(true) + .setIgnoreChannelOption(true); + sProgressDialog = ProgressDialog.show(context, null, context.getString( + R.string.dvr_series_progress_message_reading_programs), true, true, + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + episodicProgramLoadTask.cancel(true); + sProgressDialog = null; + } + }); + episodicProgramLoadTask.execute(); + } + } + + private static void startSeriesSettingsActivityInternal(Context context, long seriesRecordingId, + @NonNull List programs, boolean removeEmptySeriesSchedule, + boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, + Program currentProgram) { + SoftPreconditions.checkState(programs != null, + TAG, "Start series settings activity but programs is null"); + Intent intent = new Intent(context, DvrSeriesSettingsActivity.class); + intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId); + BigArguments.reset(); + BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs); + intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, + removeEmptySeriesSchedule); + intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent); + intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, + showViewScheduleOptionInDialog); + intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram); + context.startActivity(intent); + } + + /** + * Shows "series recording scheduled" dialog activity. + */ + public static void StartSeriesScheduledDialogActivity(Context context, + SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog, + List programs) { + if (seriesRecording == null) { + return; + } + Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class); + intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, + seriesRecording.getId()); + intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, + showViewScheduleOptionInDialog); + BigArguments.reset(); + BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, + programs); + context.startActivity(intent); + } + + /** + * Shows the details activity for the DVR items. The type of DVR items may be + * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. + */ + public static void startDetailsActivity(Activity activity, Object dvrItem, + @Nullable ImageView imageView, boolean hideViewSchedule) { + if (dvrItem == null) { + return; + } + Intent intent = new Intent(activity, DvrDetailsActivity.class); + long recordingId; + int viewType; + if (dvrItem instanceof ScheduledRecording) { + ScheduledRecording schedule = (ScheduledRecording) dvrItem; + recordingId = schedule.getId(); + if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { + viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW; + } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { + viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW; + } else { + return; + } + } else if (dvrItem instanceof RecordedProgram) { + recordingId = ((RecordedProgram) dvrItem).getId(); + viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW; + } else if (dvrItem instanceof SeriesRecording) { + recordingId = ((SeriesRecording) dvrItem).getId(); + viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW; + } else { + return; + } + intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId); + intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType); + intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule); + Bundle bundle = null; + if (imageView != null) { + bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, + DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); + } + activity.startActivity(intent, bundle); + } + + /** + * Shows the cancel all dialog for series schedules list. + */ + public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, + SeriesRecording seriesRecording) { + DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = + new DvrStopSeriesRecordingDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, + seriesRecording); + dvrStopSeriesRecordingDialogFragment.setArguments(arguments); + dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(), + DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); + } + + /** + * Shows the series deletion activity. + */ + public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) { + Intent intent = new Intent(context, DvrSeriesDeletionActivity.class); + intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId); + context.startActivity(intent); + } + + public static void showAddScheduleToast(Context context, + String title, long startTimeMs, long endTimeMs) { + String msg = (startTimeMs > System.currentTimeMillis()) ? + context.getString(R.string.dvr_msg_program_scheduled, title) + : context.getString(R.string.dvr_msg_current_program_scheduled, title, + Utils.toTimeString(endTimeMs, false)); + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } + + /** + * Returns the styled schedule's title with its season and episode number. + */ + public static CharSequence getStyledTitleWithEpisodeNumber(Context context, + ScheduledRecording schedule, int episodeNumberStyleResId) { + return getStyledTitleWithEpisodeNumber(context, schedule.getProgramTitle(), + schedule.getSeasonNumber(), schedule.getEpisodeNumber(), episodeNumberStyleResId); + } + + /** + * Returns the styled program's title with its season and episode number. + */ + public static CharSequence getStyledTitleWithEpisodeNumber(Context context, + BaseProgram program, int episodeNumberStyleResId) { + return getStyledTitleWithEpisodeNumber(context, program.getTitle(), + program.getSeasonNumber(), program.getEpisodeNumber(), episodeNumberStyleResId); + } + + @NonNull + public static CharSequence getStyledTitleWithEpisodeNumber(Context context, String title, + String seasonNumber, String episodeNumber, int episodeNumberStyleResId) { + if (TextUtils.isEmpty(title)) { + return ""; + } + SpannableStringBuilder builder; + if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) { + builder = TextUtils.isEmpty(episodeNumber) ? new SpannableStringBuilder(title) : + new SpannableStringBuilder(Html.fromHtml( + context.getString(R.string.program_title_with_episode_number_no_season, + title, episodeNumber))); + } else { + builder = new SpannableStringBuilder(Html.fromHtml( + context.getString(R.string.program_title_with_episode_number, + title, seasonNumber, episodeNumber))); + } + Object[] spans = builder.getSpans(0, builder.length(), Object.class); + if (spans.length > 0) { + if (episodeNumberStyleResId != 0) { + builder.setSpan(new TextAppearanceSpan(context, episodeNumberStyleResId), + builder.getSpanStart(spans[0]), builder.getSpanEnd(spans[0]), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + builder.removeSpan(spans[0]); + } + return new SpannableString(builder); + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java new file mode 100644 index 00000000..4f06ebcf --- /dev/null +++ b/src/com/android/tv/dvr/ui/FadeBackground.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.transition.Transition; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import com.android.tv.R; + +/** + * This transition fades in/out of the background of the view by changing the background color. + */ +public class FadeBackground extends Transition { + private final int mMode; + + public FadeBackground(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadeBackground); + mMode = a.getInt(R.styleable.FadeBackground_fadingMode, Visibility.MODE_IN); + a.recycle(); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { } + + @Override + public void captureEndValues(TransitionValues transitionValues) { } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + Drawable background = endValues.view.getBackground(); + if (background instanceof ColorDrawable) { + int color = ((ColorDrawable) background).getColor(); + int transparentColor = Color.argb(0, Color.red(color), Color.green(color), + Color.blue(color)); + return mMode == Visibility.MODE_OUT + ? ObjectAnimator.ofArgb(background, "color", transparentColor) + : ObjectAnimator.ofArgb(background, "color", transparentColor, color); + } + return null; + } +} diff --git a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java deleted file mode 100644 index d4d4d8ab..00000000 --- a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -/** - * Special object for schedule preview; - */ -final class FullScheduleCardHolder { - /** - * Full schedule card holder. - */ - static final FullScheduleCardHolder FULL_SCHEDULE_CARD_HOLDER = new FullScheduleCardHolder(); - - private FullScheduleCardHolder() { } -} diff --git a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java deleted file mode 100644 index 7dd85f45..00000000 --- a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.support.v17.leanback.widget.Presenter; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.util.Utils; - -import java.util.Collections; -import java.util.List; - -/** - * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. - */ -public class FullSchedulesCardPresenter extends Presenter { - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Context context = parent.getContext(); - RecordingCardView view = new RecordingCardView(context); - return new ScheduledRecordingViewHolder(view); - } - - @Override - public void onBindViewHolder(ViewHolder baseHolder, Object o) { - final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - final Context context = viewHolder.view.getContext(); - - cardView.setImage(context.getDrawable(R.drawable.dvr_full_schedule)); - cardView.setTitle(context.getString(R.string.dvr_full_schedule_card_view_title)); - List scheduledRecordings = TvApplication.getSingletons(context) - .getDvrDataManager().getAvailableScheduledRecordings(); - int fullDays = 0; - if (!scheduledRecordings.isEmpty()) { - fullDays = Utils.computeDateDifference(System.currentTimeMillis(), - Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR) - .getStartTimeMs()) + 1; - } - cardView.setContent(context.getResources().getQuantityString( - R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null); - - View.OnClickListener clickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - DvrUiHelper.startSchedulesActivity(context, null); - } - }; - baseHolder.view.setOnClickListener(clickListener); - } - - @Override - public void onUnbindViewHolder(ViewHolder baseHolder) { - ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - cardView.reset(); - } - - private static final class ScheduledRecordingViewHolder extends ViewHolder { - ScheduledRecordingViewHolder(RecordingCardView view) { - super(view); - } - } -} diff --git a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java deleted file mode 100644 index d320816e..00000000 --- a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.R; -import com.android.tv.dialog.SafeDismissDialogFragment; - -import java.util.concurrent.TimeUnit; - -public class HalfSizedDialogFragment extends SafeDismissDialogFragment { - public static final String DIALOG_TAG = HalfSizedDialogFragment.class.getSimpleName(); - public static final String TRACKER_LABEL = "Half sized dialog"; - - private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - - private OnActionClickListener mOnActionClickListener; - - private Handler mHandler = new Handler(); - private Runnable mAutoDismisser = new Runnable() { - @Override - public void run() { - dismiss(); - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.halfsized_dialog, container, false); - } - - @Override - public void onStart() { - super.onStart(); - getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() { - public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) { - mHandler.removeCallbacks(mAutoDismisser); - mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); - return false; - } - }); - mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); - } - - @Override - public void onPause() { - super.onPause(); - if (mOnActionClickListener != null) { - // Dismisses the dialog to prevent the callback being forgotten during - // fragment re-creating. - dismiss(); - } - } - - @Override - public void onStop() { - super.onStop(); - mHandler.removeCallbacks(mAutoDismisser); - } - - @Override - public int getTheme() { - return R.style.Theme_TV_dialog_HalfSizedDialog; - } - - @Override - public String getTrackerLabel() { - return TRACKER_LABEL; - } - - /** - * Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog - * will be automatically closed when it's paused to prevent the fragment being re-created by - * the framework, which will result the listener being forgotten. - */ - public void setOnActionClickListener(OnActionClickListener listener) { - mOnActionClickListener = listener; - } - - /** - * Returns {@link OnActionClickListener} for sub-classes or any inner fragments. - */ - protected OnActionClickListener getOnActionClickListener() { - return mOnActionClickListener; - } - - /** - * An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments - * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier - * of the action user clicked. - */ - public interface OnActionClickListener { - void onActionClick(long actionId); - } -} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java deleted file mode 100644 index 158bd824..00000000 --- a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.FragmentManager; -import android.content.Context; -import android.graphics.Typeface; -import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import android.support.v17.leanback.widget.GuidedActionsStylist; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.SeriesRecording; - -import java.util.ArrayList; -import java.util.List; - -/** - * Fragment for DVR series recording settings. - */ -public class PrioritySettingsFragment extends GuidedStepFragment { - /** - * Name of series recording id starting the fragment. - * Type: Long - */ - public static final String COME_FROM_SERIES_RECORDING_ID = "series_recording_id"; - - private static final int ONE_TIME_RECORDING_ID = 0; - // button action's IDs are negative. - private static final long ACTION_ID_SAVE = -100L; - - private final List mSeriesRecordings = new ArrayList<>(); - - private SeriesRecording mSelectedRecording; - private SeriesRecording mComeFromSeriesRecording; - private float mSelectedActionElevation; - private int mActionColor; - private int mSelectedActionColor; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mSeriesRecordings.clear(); - mSeriesRecordings.add(new SeriesRecording.Builder() - .setTitle(getString(R.string.dvr_priority_action_one_time_recording)) - .setPriority(Long.MAX_VALUE) - .setId(ONE_TIME_RECORDING_ID) - .build()); - DvrDataManager dvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - long comeFromSeriesRecordingId = - getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1); - for (SeriesRecording series : dvrDataManager.getSeriesRecordings()) { - if (series.getState() == SeriesRecording.STATE_SERIES_NORMAL - || series.getId() == comeFromSeriesRecordingId) { - mSeriesRecordings.add(series); - } - } - mSeriesRecordings.sort(SeriesRecording.PRIORITY_COMPARATOR); - mComeFromSeriesRecording = dvrDataManager.getSeriesRecording(comeFromSeriesRecordingId); - mSelectedActionElevation = getResources().getDimension(R.dimen.card_elevation_normal); - mActionColor = getResources().getColor(R.color.dvr_guided_step_action_text_color, null); - mSelectedActionColor = - getResources().getColor(R.color.dvr_guided_step_action_text_color_selected, null); - } - - @Override - public void onResume() { - super.onResume(); - setSelectedActionPosition(mComeFromSeriesRecording == null ? 1 - : mSeriesRecordings.indexOf(mComeFromSeriesRecording)); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String breadcrumb = mComeFromSeriesRecording == null ? null - : mComeFromSeriesRecording.getTitle(); - return new Guidance(getString(R.string.dvr_priority_title), - getString(R.string.dvr_priority_description), breadcrumb, null); - } - - @Override - public void onCreateActions(List actions, Bundle savedInstanceState) { - int position = 0; - for (SeriesRecording seriesRecording : mSeriesRecordings) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(position++) - .title(seriesRecording.getTitle()) - .build()); - } - } - - @Override - public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SAVE) - .title(getString(R.string.dvr_priority_button_action_save)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - long actionId = action.getId(); - if (actionId == ACTION_ID_SAVE) { - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); - int size = mSeriesRecordings.size(); - for (int i = 1; i < size; ++i) { - long priority = DvrScheduleManager.suggestSeriesPriority(size - i); - SeriesRecording seriesRecording = mSeriesRecordings.get(i); - if (seriesRecording.getPriority() != priority) { - dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(seriesRecording) - .setPriority(priority).build()); - } - } - FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.popBackStack(); - } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { - FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.popBackStack(); - } else if (mSelectedRecording == null) { - mSelectedRecording = mSeriesRecordings.get((int) actionId); - for (int i = 0; i < mSeriesRecordings.size(); ++i) { - updateItem(i); - } - } else { - mSelectedRecording = null; - for (int i = 0; i < mSeriesRecordings.size(); ++i) { - updateItem(i); - } - } - } - - @Override - public void onGuidedActionFocused(GuidedAction action) { - super.onGuidedActionFocused(action); - if (mSelectedRecording == null) { - return; - } - if (action.getId() < 0) { - int selectedPosition = mSeriesRecordings.indexOf(mSelectedRecording); - mSelectedRecording = null; - for (int i = 0; i < mSeriesRecordings.size(); ++i) { - updateItem(i); - } - return; - } - int position = (int) action.getId(); - int previousPosition = mSeriesRecordings.indexOf(mSelectedRecording); - mSeriesRecordings.remove(mSelectedRecording); - mSeriesRecordings.add(position, mSelectedRecording); - updateItem(previousPosition); - updateItem(position); - notifyActionChanged(previousPosition); - notifyActionChanged(position); - } - - @Override - public GuidedActionsStylist onCreateButtonActionsStylist() { - return new DvrGuidedActionsStylist(true); - } - - @Override - public GuidedActionsStylist onCreateActionsStylist() { - return new DvrGuidedActionsStylist(false) { - @Override - public void onBindViewHolder(ViewHolder vh, GuidedAction action) { - super.onBindViewHolder(vh, action); - updateItem(vh.itemView, (int) action.getId()); - } - - @Override - public int onProvideItemLayoutId() { - return R.layout.priority_settings_action_item; - } - }; - } - - private void updateItem(int position) { - View itemView = getActionItemView(position); - if (itemView == null) { - return; - } - updateItem(itemView, position); - } - - private void updateItem(View itemView, int position) { - GuidedAction action = getActions().get(position); - action.setTitle(mSeriesRecordings.get(position).getTitle()); - boolean selected = mSelectedRecording != null - && mSeriesRecordings.indexOf(mSelectedRecording) == position; - TextView titleView = (TextView) itemView.findViewById(R.id.guidedactions_item_title); - ImageView imageView = (ImageView) itemView.findViewById(R.id.guidedactions_item_tail_image); - if (position == 0) { - // one-time recording - itemView.setBackgroundResource(R.drawable.setup_selector_background); - imageView.setVisibility(View.GONE); - itemView.setFocusable(false); - itemView.setElevation(0); - // strings.xml tag doesn't work. - titleView.setTypeface(titleView.getTypeface(), Typeface.ITALIC); - } else if (mSelectedRecording == null) { - titleView.setTextColor(mActionColor); - itemView.setBackgroundResource(R.drawable.setup_selector_background); - imageView.setImageResource(R.drawable.ic_draggable_white); - imageView.setVisibility(View.VISIBLE); - itemView.setFocusable(true); - itemView.setElevation(0); - titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); - } else if (selected) { - titleView.setTextColor(mSelectedActionColor); - itemView.setBackgroundResource(R.drawable.priority_settings_action_item_selected); - imageView.setImageResource(R.drawable.ic_dragging_grey); - imageView.setVisibility(View.VISIBLE); - itemView.setFocusable(true); - itemView.setElevation(mSelectedActionElevation); - titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); - } else { - titleView.setTextColor(mActionColor); - itemView.setBackgroundResource(R.drawable.setup_selector_background); - imageView.setVisibility(View.INVISIBLE); - itemView.setFocusable(true); - itemView.setElevation(0); - titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); - } - } -} diff --git a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java deleted file mode 100644 index e698b8a2..00000000 --- a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.res.Resources; -import android.media.tv.TvInputManager; -import android.os.Bundle; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.text.TextUtils; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.RecordedProgram; - -/** - * {@link DetailsFragment} for recorded program in DVR. - */ -public class RecordedProgramDetailsFragment extends DvrDetailsFragment - implements DvrDataManager.RecordedProgramListener { - private static final int ACTION_RESUME_PLAYING = 1; - private static final int ACTION_PLAY_FROM_BEGINNING = 2; - private static final int ACTION_DELETE_RECORDING = 3; - - private DvrWatchedPositionManager mDvrWatchedPositionManager; - - private RecordedProgram mRecordedProgram; - private DetailsContent mDetailsContent; - private boolean mPaused; - private DvrDataManager mDvrDataManager; - - @Override - public void onCreate(Bundle savedInstanceState) { - mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager(); - mDvrDataManager.addRecordedProgramListener(this); - super.onCreate(savedInstanceState); - } - - @Override - public void onCreateInternal() { - mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) - .getDvrWatchedPositionManager(); - setDetailsOverviewRow(mDetailsContent); - } - - @Override - public void onResume() { - super.onResume(); - if (mPaused) { - updateActions(); - mPaused = false; - } - } - - @Override - public void onPause() { - super.onPause(); - mPaused = true; - } - - @Override - public void onDestroy() { - mDvrDataManager.removeRecordedProgramListener(this); - super.onDestroy(); - } - - @Override - protected boolean onLoadRecordingDetails(Bundle args) { - long recordedProgramId = args.getLong(DvrDetailsActivity.RECORDING_ID); - mRecordedProgram = mDvrDataManager.getRecordedProgram(recordedProgramId); - if (mRecordedProgram == null) { - // notify super class to end activity before initializing anything - return false; - } - mDetailsContent = createDetailsContent(); - return true; - } - - private DetailsContent createDetailsContent() { - Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(mRecordedProgram.getChannelId()); - String description = TextUtils.isEmpty(mRecordedProgram.getLongDescription()) - ? mRecordedProgram.getDescription() : mRecordedProgram.getLongDescription(); - return new DetailsContent.Builder() - .setTitle(getTitleFromProgram(mRecordedProgram, channel)) - .setStartTimeUtcMillis(mRecordedProgram.getStartTimeUtcMillis()) - .setEndTimeUtcMillis(mRecordedProgram.getEndTimeUtcMillis()) - .setDescription(description) - .setImageUris(mRecordedProgram, channel) - .build(); - } - - @Override - protected SparseArrayObjectAdapter onCreateActionsAdapter() { - SparseArrayObjectAdapter adapter = - new SparseArrayObjectAdapter(new ActionPresenterSelector()); - Resources res = getResources(); - if (mDvrWatchedPositionManager.getWatchedStatus(mRecordedProgram) - == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { - adapter.set(ACTION_RESUME_PLAYING, new Action(ACTION_RESUME_PLAYING, - res.getString(R.string.dvr_detail_resume_play), null, - res.getDrawable(R.drawable.lb_ic_play))); - adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, - res.getString(R.string.dvr_detail_play_from_beginning), null, - res.getDrawable(R.drawable.lb_ic_replay))); - } else { - adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, - res.getString(R.string.dvr_detail_watch), null, - res.getDrawable(R.drawable.lb_ic_play))); - } - adapter.set(ACTION_DELETE_RECORDING, new Action(ACTION_DELETE_RECORDING, - res.getString(R.string.dvr_detail_delete), null, - res.getDrawable(R.drawable.ic_delete_32dp))); - return adapter; - } - - @Override - protected OnActionClickedListener onCreateOnActionClickedListener() { - return new OnActionClickedListener() { - @Override - public void onActionClicked(Action action) { - if (action.getId() == ACTION_PLAY_FROM_BEGINNING) { - startPlayback(mRecordedProgram, TvInputManager.TIME_SHIFT_INVALID_TIME); - } else if (action.getId() == ACTION_RESUME_PLAYING) { - startPlayback(mRecordedProgram, mDvrWatchedPositionManager - .getWatchedPosition(mRecordedProgram.getId())); - } else if (action.getId() == ACTION_DELETE_RECORDING) { - DvrManager dvrManager = TvApplication - .getSingletons(getActivity()).getDvrManager(); - dvrManager.removeRecordedProgram(mRecordedProgram); - getActivity().finish(); - } - } - }; - } - - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { } - - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { } - - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - if (recordedProgram.getId() == mRecordedProgram.getId()) { - getActivity().finish(); - } - } - } -} diff --git a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java deleted file mode 100644 index 1bf34310..00000000 --- a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.content.Context; -import android.media.tv.TvContract; -import android.media.tv.TvInputManager; -import android.net.Uri; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.TextAppearanceSpan; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener; -import com.android.tv.util.Utils; - -import java.util.concurrent.TimeUnit; - -/** - * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. - */ -public class RecordedProgramPresenter extends DvrItemPresenter { - private final ChannelDataManager mChannelDataManager; - private final DvrWatchedPositionManager mDvrWatchedPositionManager; - private final Context mContext; - private String mTodayString; - private String mYesterdayString; - private final int mProgressBarColor; - private final boolean mShowEpisodeTitle; - - private static final class RecordedProgramViewHolder extends ViewHolder - implements WatchedPositionChangedListener { - private RecordedProgram mProgram; - - RecordedProgramViewHolder(RecordingCardView view, int progressColor) { - super(view); - view.setProgressBarColor(progressColor); - } - - private void setProgram(RecordedProgram program) { - mProgram = program; - } - - private void setProgressBar(long watchedPositionMs) { - ((RecordingCardView) view).setProgressBar( - (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null - : Math.min(100, (int) (100.0f * watchedPositionMs - / mProgram.getDurationMillis()))); - } - - @Override - public void onWatchedPositionChanged(long programId, long positionMs) { - if (programId == mProgram.getId()) { - setProgressBar(positionMs); - } - } - } - - public RecordedProgramPresenter(Context context, boolean showEpisodeTitle) { - mContext = context; - mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); - mTodayString = context.getString(R.string.dvr_date_today); - mYesterdayString = context.getString(R.string.dvr_date_yesterday); - mDvrWatchedPositionManager = - TvApplication.getSingletons(context).getDvrWatchedPositionManager(); - mProgressBarColor = context.getResources() - .getColor(R.color.play_controls_progress_bar_watched); - mShowEpisodeTitle = showEpisodeTitle; - } - - public RecordedProgramPresenter(Context context) { - this(context, false); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - RecordingCardView view = new RecordingCardView(mContext); - return new RecordedProgramViewHolder(view, mProgressBarColor); - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, Object o) { - final RecordedProgram program = (RecordedProgram) o; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - Channel channel = mChannelDataManager.getChannel(program.getChannelId()); - String titleString = mShowEpisodeTitle ? program.getEpisodeDisplayTitle(mContext) - : program.getTitleWithEpisodeNumber(mContext); - SpannableString title = titleString == null ? null : new SpannableString(titleString); - if (TextUtils.isEmpty(title)) { - title = new SpannableString(channel != null ? channel.getDisplayName() - : mContext.getResources().getString(R.string.no_program_information)); - } else if (!mShowEpisodeTitle) { - // TODO: Some translation may add delimiters in-between program titles, we should use - // a more robust way to get the span range. - String programTitle = program.getTitle(); - title.setSpan(new TextAppearanceSpan(mContext, - R.style.text_appearance_card_view_episode_number), programTitle == null ? 0 - : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - cardView.setTitle(title); - String imageUri = null; - boolean isChannelLogo = false; - if (program.getPosterArtUri() != null) { - imageUri = program.getPosterArtUri(); - } else if (program.getThumbnailUri() != null) { - imageUri = program.getThumbnailUri(); - } else if (channel != null) { - imageUri = TvContract.buildChannelLogoUri(channel.getId()).toString(); - isChannelLogo = true; - } - cardView.setImageUri(imageUri, isChannelLogo); - int durationMinutes = - Math.max(1, (int) TimeUnit.MILLISECONDS.toMinutes(program.getDurationMillis())); - String durationString = getContext().getResources().getQuantityString( - R.plurals.dvr_program_duration, durationMinutes, durationMinutes); - cardView.setContent(getDescription(program), durationString); - if (viewHolder instanceof RecordedProgramViewHolder) { - RecordedProgramViewHolder cardViewHolder = (RecordedProgramViewHolder) viewHolder; - cardViewHolder.setProgram(program); - mDvrWatchedPositionManager.addListener(cardViewHolder, program.getId()); - cardViewHolder - .setProgressBar(mDvrWatchedPositionManager.getWatchedPosition(program.getId())); - } - super.onBindViewHolder(viewHolder, o); - } - - @Override - public void onUnbindViewHolder(ViewHolder viewHolder) { - if (viewHolder instanceof RecordedProgramViewHolder) { - mDvrWatchedPositionManager.removeListener((RecordedProgramViewHolder) viewHolder, - ((RecordedProgramViewHolder) viewHolder).mProgram.getId()); - } - ((RecordingCardView) viewHolder.view).reset(); - super.onUnbindViewHolder(viewHolder); - } - - /** - * Returns description would be used in its card view. - */ - protected String getDescription(RecordedProgram recording) { - int dateDifference = Utils.computeDateDifference(recording.getStartTimeUtcMillis(), - System.currentTimeMillis()); - if (dateDifference == 0) { - return mTodayString; - } else if (dateDifference == 1) { - return mYesterdayString; - } else { - return Utils.getDurationString(mContext, recording.getStartTimeUtcMillis(), - recording.getStartTimeUtcMillis(), false, true, false, 0); - } - } - - /** - * Returns context. - */ - protected Context getContext() { - return mContext; - } -} diff --git a/src/com/android/tv/dvr/ui/RecordingCardView.java b/src/com/android/tv/dvr/ui/RecordingCardView.java deleted file mode 100644 index 51c3b03b..00000000 --- a/src/com/android/tv/dvr/ui/RecordingCardView.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.support.v17.leanback.widget.BaseCardView; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.android.tv.R; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.util.ImageLoader; - -/** - * A CardView for displaying info about a {@link com.android.tv.dvr.ScheduledRecording} or - * {@link RecordedProgram} or - * {@link com.android.tv.dvr.SeriesRecording}. - */ -class RecordingCardView extends BaseCardView { - private final ImageView mImageView; - private final int mImageWidth; - private final int mImageHeight; - private String mImageUri; - private final TextView mTitleView; - private final TextView mMajorContentView; - private final TextView mMinorContentView; - private final ProgressBar mProgressBar; - private final View mAffiliatedIconContainer; - private final ImageView mAffiliatedIcon; - private final Drawable mDefaultImage; - - RecordingCardView(Context context) { - this(context, - context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width), - context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_height)); - } - - RecordingCardView(Context context, int imageWidth, int imageHeight) { - super(context); - //TODO(dvr): move these to the layout XML. - setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA); - setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS); - setFocusable(true); - setFocusableInTouchMode(true); - mDefaultImage = getResources().getDrawable(R.drawable.dvr_default_poster, null); - - LayoutInflater inflater = LayoutInflater.from(getContext()); - inflater.inflate(R.layout.dvr_recording_card_view, this); - mImageView = (ImageView) findViewById(R.id.image); - mImageWidth = imageWidth; - mImageHeight = imageHeight; - mProgressBar = (ProgressBar) findViewById(R.id.recording_progress); - mAffiliatedIconContainer = findViewById(R.id.affiliated_icon_container); - mAffiliatedIcon = (ImageView) findViewById(R.id.affiliated_icon); - mTitleView = (TextView) findViewById(R.id.title); - mMajorContentView = (TextView) findViewById(R.id.content_major); - mMinorContentView = (TextView) findViewById(R.id.content_minor); - } - - void setTitle(CharSequence title) { - mTitleView.setText(title); - } - - void setContent(CharSequence majorContent, CharSequence minorContent) { - if (!TextUtils.isEmpty(majorContent)) { - mMajorContentView.setText(majorContent); - mMajorContentView.setVisibility(View.VISIBLE); - } else { - mMajorContentView.setVisibility(View.GONE); - } - if (!TextUtils.isEmpty(minorContent)) { - mMinorContentView.setText(minorContent); - mMinorContentView.setVisibility(View.VISIBLE); - } else { - mMinorContentView.setVisibility(View.GONE); - } - } - - /** - * Sets progress bar. If progress is {@code null}, hides progress bar. - */ - void setProgressBar(Integer progress) { - if (progress == null) { - mProgressBar.setVisibility(View.GONE); - } else { - mProgressBar.setProgress(progress); - mProgressBar.setVisibility(View.VISIBLE); - } - } - - /** - * Sets the color of progress bar. - */ - void setProgressBarColor(int color) { - mProgressBar.getProgressDrawable().setTint(color); - } - - void setImageUri(String uri, boolean isChannelLogo) { - if (isChannelLogo) { - mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - } else { - mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - } - mImageUri = uri; - if (TextUtils.isEmpty(uri)) { - mImageView.setImageDrawable(mDefaultImage); - } else { - ImageLoader.loadBitmap(getContext(), uri, mImageWidth, mImageHeight, - new RecordingCardImageLoaderCallback(this, uri)); - } - } - - /** - * Set image to card view. - */ - public void setImage(Drawable image) { - if (image != null) { - mImageView.setImageDrawable(image); - } - } - - public void setAffiliatedIcon(int imageResId) { - if (imageResId > 0) { - mAffiliatedIconContainer.setVisibility(View.VISIBLE); - mAffiliatedIcon.setImageResource(imageResId); - } else { - mAffiliatedIconContainer.setVisibility(View.INVISIBLE); - } - } - - /** - * Returns image view. - */ - public ImageView getImageView() { - return mImageView; - } - - private static class RecordingCardImageLoaderCallback - extends ImageLoader.ImageLoaderCallback { - private final String mUri; - - RecordingCardImageLoaderCallback(RecordingCardView referent, String uri) { - super(referent); - mUri = uri; - } - - @Override - public void onBitmapLoaded(RecordingCardView view, @Nullable Bitmap bitmap) { - if (bitmap == null || !mUri.equals(view.mImageUri)) { - view.mImageView.setImageDrawable(view.mDefaultImage); - } else { - view.mImageView.setImageDrawable(new BitmapDrawable(view.getResources(), bitmap)); - } - } - } - - public void reset() { - mTitleView.setText(null); - setContent(null, null); - mImageView.setImageDrawable(mDefaultImage); - } -} diff --git a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java deleted file mode 100644 index 4e19ec3f..00000000 --- a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.os.Bundle; -import android.support.v17.leanback.app.DetailsFragment; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.TextAppearanceSpan; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.dvr.ScheduledRecording; - -/** - * {@link DetailsFragment} for recordings in DVR. - */ -abstract class RecordingDetailsFragment extends DvrDetailsFragment { - private ScheduledRecording mRecording; - - @Override - protected void onCreateInternal() { - setDetailsOverviewRow(createDetailsContent()); - } - - @Override - protected boolean onLoadRecordingDetails(Bundle args) { - long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID); - mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager() - .getScheduledRecording(scheduledRecordingId); - return mRecording != null; - } - - /** - * Returns {@link ScheduledRecording} for the current fragment. - */ - public ScheduledRecording getRecording() { - return mRecording; - } - - private DetailsContent createDetailsContent() { - Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(mRecording.getChannelId()); - SpannableString title = mRecording.getProgramTitleWithEpisodeNumber(getContext()) == null ? - null : new SpannableString(mRecording - .getProgramTitleWithEpisodeNumber(getContext())); - if (TextUtils.isEmpty(title)) { - title = new SpannableString(channel != null ? channel.getDisplayName() - : getContext().getResources().getString( - R.string.no_program_information)); - } else { - String programTitle = mRecording.getProgramTitle(); - title.setSpan(new TextAppearanceSpan(getContext(), - R.style.text_appearance_card_view_episode_number), programTitle == null ? 0 - : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - String description = !TextUtils.isEmpty(mRecording.getProgramDescription()) ? - mRecording.getProgramDescription() : mRecording.getProgramLongDescription(); - if (TextUtils.isEmpty(description)) { - description = channel != null ? channel.getDescription() : null; - } - return new DetailsContent.Builder() - .setTitle(title) - .setStartTimeUtcMillis(mRecording.getStartTimeMs()) - .setEndTimeUtcMillis(mRecording.getEndTimeMs()) - .setDescription(description) - .setImageUris(mRecording.getProgramPosterArtUri(), - mRecording.getProgramThumbnailUri(), channel) - .build(); - } -} diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java deleted file mode 100644 index 60816bb5..00000000 --- a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.res.Resources; -import android.os.Bundle; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.text.TextUtils; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; - -/** - * {@link RecordingDetailsFragment} for scheduled recording in DVR. - */ -public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment { - private static final int ACTION_VIEW_SCHEDULE = 1; - private static final int ACTION_CANCEL = 2; - - private DvrManager mDvrManager; - private Action mScheduleAction; - private boolean mHideViewSchedule; - - @Override - public void onCreate(Bundle savedInstance) { - mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); - mHideViewSchedule = getArguments().getBoolean(DvrDetailsActivity.HIDE_VIEW_SCHEDULE); - super.onCreate(savedInstance); - } - - @Override - public void onResume() { - super.onResume(); - if (mScheduleAction != null) { - mScheduleAction.setIcon(getResources().getDrawable(getScheduleIconId())); - } - } - - @Override - protected SparseArrayObjectAdapter onCreateActionsAdapter() { - SparseArrayObjectAdapter adapter = - new SparseArrayObjectAdapter(new ActionPresenterSelector()); - Resources res = getResources(); - if (!mHideViewSchedule) { - mScheduleAction = new Action(ACTION_VIEW_SCHEDULE, - res.getString(R.string.dvr_detail_view_schedule), null, - res.getDrawable(getScheduleIconId())); - adapter.set(ACTION_VIEW_SCHEDULE, mScheduleAction); - } - adapter.set(ACTION_CANCEL, new Action(ACTION_CANCEL, - res.getString(R.string.epg_dvr_dialog_message_remove_recording_schedule), null, - res.getDrawable(R.drawable.ic_dvr_cancel_32dp))); - return adapter; - } - - @Override - protected OnActionClickedListener onCreateOnActionClickedListener() { - return new OnActionClickedListener() { - @Override - public void onActionClicked(Action action) { - long actionId = action.getId(); - if (actionId == ACTION_VIEW_SCHEDULE) { - DvrUiHelper.startSchedulesActivity(getContext(), getRecording()); - } else if (actionId == ACTION_CANCEL) { - mDvrManager.removeScheduledRecording(getRecording()); - getActivity().finish(); - } - } - }; - } - - private int getScheduleIconId() { - if (mDvrManager.isConflicting(getRecording())) { - return R.drawable.ic_warning_white_32dp; - } else { - return R.drawable.ic_schedule_32dp; - } - } -} diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java deleted file mode 100644 index 5f447f13..00000000 --- a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.content.Context; -import android.media.tv.TvContract; -import android.os.Handler; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.TextAppearanceSpan; -import android.view.ViewGroup; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.util.Utils; - -import java.util.concurrent.TimeUnit; - -/** - * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. - */ -public class ScheduledRecordingPresenter extends DvrItemPresenter { - private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5); - - private final ChannelDataManager mChannelDataManager; - private final DvrManager mDvrManager; - private final Context mContext; - private final int mProgressBarColor; - - private static final class ScheduledRecordingViewHolder extends ViewHolder { - private final Handler mHandler = new Handler(); - private ScheduledRecording mScheduledRecording; - private final Runnable mProgressBarUpdater = new Runnable() { - @Override - public void run() { - updateProgressBar(); - mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS); - } - }; - - ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) { - super(view); - view.setProgressBarColor(progressBarColor); - } - - private void updateProgressBar() { - if (mScheduledRecording == null) { - return; - } - int recordingState = mScheduledRecording.getState(); - RecordingCardView cardView = (RecordingCardView) view; - if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { - cardView.setProgressBar(Math.max(0, Math.min((int) (100 * - (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs()) - / mScheduledRecording.getDuration()), 100))); - } else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) { - cardView.setProgressBar(100); - } else { - // Hides progress bar. - cardView.setProgressBar(null); - } - } - - private void startUpdateProgressBar() { - mHandler.post(mProgressBarUpdater); - } - - private void stopUpdateProgressBar() { - mHandler.removeCallbacks(mProgressBarUpdater); - } - } - - public ScheduledRecordingPresenter(Context context) { - ApplicationSingletons singletons = TvApplication.getSingletons(context); - mChannelDataManager = singletons.getChannelDataManager(); - mDvrManager = singletons.getDvrManager(); - mContext = context; - mProgressBarColor = context.getResources() - .getColor(R.color.play_controls_recording_icon_color_on_focus); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Context context = parent.getContext(); - RecordingCardView view = new RecordingCardView(context); - return new ScheduledRecordingViewHolder(view, mProgressBarColor); - } - - @Override - public void onBindViewHolder(ViewHolder baseHolder, Object o) { - final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - final ScheduledRecording recording = (ScheduledRecording) o; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - final Context context = viewHolder.view.getContext(); - - setTitleAndImage(cardView, recording); - int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(), - recording.getStartTimeMs()); - if (dateDifference <= 0) { - cardView.setContent(mContext.getString(R.string.dvr_date_today_time, - Utils.getDurationString(context, recording.getStartTimeMs(), - recording.getEndTimeMs(), false, false, true, 0)), null); - } else if (dateDifference == 1) { - cardView.setContent(mContext.getString(R.string.dvr_date_tomorrow_time, - Utils.getDurationString(context, recording.getStartTimeMs(), - recording.getEndTimeMs(), false, false, true, 0)), null); - } else { - cardView.setContent(Utils.getDurationString(context, recording.getStartTimeMs(), - recording.getStartTimeMs(), false, true, false, 0), null); - } - if (mDvrManager.isConflicting(recording)) { - cardView.setAffiliatedIcon(R.drawable.ic_warning_white_32dp); - } else { - cardView.setAffiliatedIcon(0); - } - viewHolder.updateProgressBar(); - viewHolder.mScheduledRecording = recording; - viewHolder.startUpdateProgressBar(); - super.onBindViewHolder(viewHolder, o); - } - - @Override - public void onUnbindViewHolder(ViewHolder baseHolder) { - ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; - viewHolder.stopUpdateProgressBar(); - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - viewHolder.mScheduledRecording = null; - cardView.reset(); - super.onUnbindViewHolder(viewHolder); - } - - private void setTitleAndImage(RecordingCardView cardView, ScheduledRecording recording) { - Channel channel = mChannelDataManager.getChannel(recording.getChannelId()); - SpannableString title = recording.getProgramTitleWithEpisodeNumber(mContext) == null ? - null : new SpannableString(recording.getProgramTitleWithEpisodeNumber(mContext)); - if (TextUtils.isEmpty(title)) { - title = new SpannableString(channel != null ? channel.getDisplayName() - : mContext.getResources().getString(R.string.no_program_information)); - } else { - String programTitle = recording.getProgramTitle(); - title.setSpan(new TextAppearanceSpan(mContext, - R.style.text_appearance_card_view_episode_number), - programTitle == null ? 0 : programTitle.length(), title.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - String imageUri = recording.getProgramPosterArtUri(); - boolean isChannelLogo = false; - if (TextUtils.isEmpty(imageUri)) { - imageUri = channel != null ? - TvContract.buildChannelLogoUri(channel.getId()).toString() : null; - isChannelLogo = true; - } - cardView.setTitle(title); - cardView.setImageUri(imageUri, isChannelLogo); - } -} diff --git a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java deleted file mode 100644 index 36e3cfc1..00000000 --- a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.content.Context; -import android.media.tv.TvInputManager; -import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import android.support.v17.leanback.widget.GuidedActionsStylist; -import android.text.TextUtils; -import android.view.ViewGroup.LayoutParams; -import android.widget.Toast; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dvr.SeriesRecording; -import com.android.tv.ui.GuidedActionsStylistWithDivider; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * Fragment for DVR series recording settings. - */ -public class SeriesDeletionFragment extends GuidedStepFragment { - private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2); - - // Since recordings' IDs are used as its check actions' IDs, which are random positive numbers, - // negative values are used by other actions to prevent duplicated IDs. - private static final long ACTION_ID_SELECT_WATCHED = -110; - private static final long ACTION_ID_SELECT_ALL = -111; - private static final long ACTION_ID_DELETE = -112; - - private DvrDataManager mDvrDataManager; - private DvrWatchedPositionManager mDvrWatchedPositionManager; - private List mRecordings; - private final Set mWatchedRecordings = new HashSet<>(); - private boolean mAllSelected; - private long mSeriesRecordingId; - private int mOneLineActionHeight; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mSeriesRecordingId = getArguments() - .getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); - SoftPreconditions.checkArgument(mSeriesRecordingId != -1); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - mDvrWatchedPositionManager = - TvApplication.getSingletons(context).getDvrWatchedPositionManager(); - mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); - mOneLineActionHeight = getResources().getDimensionPixelSize( - R.dimen.dvr_settings_one_line_action_container_height); - if (mRecordings.isEmpty()) { - Toast.makeText(getActivity(), getString(R.string.dvr_series_deletion_no_recordings), - Toast.LENGTH_LONG).show(); - finishGuidedStepFragments(); - return; - } - Collections.sort(mRecordings, RecordedProgram.EPISODE_COMPARATOR); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String breadcrumb = null; - SeriesRecording series = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); - if (series != null) { - breadcrumb = series.getTitle(); - } - return new Guidance(getString(R.string.dvr_series_deletion_title), - getString(R.string.dvr_series_deletion_description), breadcrumb, null); - } - - @Override - public void onCreateActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SELECT_WATCHED) - .title(getString(R.string.dvr_series_select_watched)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SELECT_ALL) - .title(getString(R.string.dvr_series_select_all)) - .build()); - actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext())); - for (RecordedProgram recording : mRecordings) { - long watchedPositionMs = - mDvrWatchedPositionManager.getWatchedPosition(recording.getId()); - String title = recording.getEpisodeDisplayTitle(getContext()); - if (TextUtils.isEmpty(title)) { - title = TextUtils.isEmpty(recording.getTitle()) ? - getString(R.string.channel_banner_no_title) : recording.getTitle(); - } - String description; - if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - description = getWatchedString(watchedPositionMs, recording.getDurationMillis()); - mWatchedRecordings.add(recording.getId()); - } else { - description = getString(R.string.dvr_series_never_watched); - } - actions.add(new GuidedAction.Builder(getActivity()) - .id(recording.getId()) - .title(title) - .description(description) - .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) - .build()); - } - } - - @Override - public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_DELETE) - .title(getString(R.string.dvr_detail_delete)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - long actionId = action.getId(); - if (actionId == ACTION_ID_DELETE) { - List idsToDelete = new ArrayList<>(); - for (GuidedAction guidedAction : getActions()) { - if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID - && guidedAction.isChecked()) { - idsToDelete.add(guidedAction.getId()); - } - } - if (!idsToDelete.isEmpty()) { - DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); - dvrManager.removeRecordedPrograms(idsToDelete); - } - Toast.makeText(getContext(), getResources().getQuantityString( - R.plurals.dvr_msg_episodes_deleted, idsToDelete.size(), idsToDelete.size(), - mRecordings.size()), Toast.LENGTH_LONG).show(); - finishGuidedStepFragments(); - } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { - finishGuidedStepFragments(); - } else if (actionId == ACTION_ID_SELECT_WATCHED) { - for (GuidedAction guidedAction : getActions()) { - if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { - long recordingId = guidedAction.getId(); - if (mWatchedRecordings.contains(recordingId)) { - guidedAction.setChecked(true); - } else { - guidedAction.setChecked(false); - } - notifyActionChanged(findActionPositionById(recordingId)); - } - } - mAllSelected = updateSelectAllState(); - } else if (actionId == ACTION_ID_SELECT_ALL) { - mAllSelected = !mAllSelected; - for (GuidedAction guidedAction : getActions()) { - if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { - guidedAction.setChecked(mAllSelected); - notifyActionChanged(findActionPositionById(guidedAction.getId())); - } - } - updateSelectAllState(action, mAllSelected); - } else { - mAllSelected = updateSelectAllState(); - } - } - - @Override - public GuidedActionsStylist onCreateButtonActionsStylist() { - return new DvrGuidedActionsStylist(true); - } - - @Override - public GuidedActionsStylist onCreateActionsStylist() { - return new GuidedActionsStylistWithDivider() { - @Override - public void onBindViewHolder(ViewHolder vh, GuidedAction action) { - super.onBindViewHolder(vh, action); - if (action.getId() == ACTION_DIVIDER) { - return; - } - LayoutParams lp = vh.itemView.getLayoutParams(); - if (action.getCheckSetId() != GuidedAction.CHECKBOX_CHECK_SET_ID) { - lp.height = mOneLineActionHeight; - } else { - vh.itemView.setLayoutParams( - new LayoutParams(lp.width, LayoutParams.WRAP_CONTENT)); - } - } - }; - } - - private String getWatchedString(long watchedPositionMs, long durationMs) { - if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) { - return getResources().getString(R.string.dvr_series_watched_info_minutes, - Math.max(1, TimeUnit.MILLISECONDS.toMinutes(watchedPositionMs)), - TimeUnit.MILLISECONDS.toMinutes(durationMs)); - } else { - return getResources().getString(R.string.dvr_series_watched_info_seconds, - Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), - TimeUnit.MILLISECONDS.toSeconds(durationMs)); - } - } - - private boolean updateSelectAllState() { - for (GuidedAction guidedAction : getActions()) { - if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) { - if (!guidedAction.isChecked()) { - if (mAllSelected) { - updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), false); - } - return false; - } - } - } - if (!mAllSelected) { - updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), true); - } - return true; - } - - private void updateSelectAllState(GuidedAction selectAll, boolean select) { - selectAll.setTitle(select ? getString(R.string.dvr_series_deselect_all) - : getString(R.string.dvr_series_select_all)); - notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL)); - } -} diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java deleted file mode 100644 index e9e391d4..00000000 --- a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.ui; - -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.media.tv.TvInputManager; -import android.os.Bundle; -import android.support.v17.leanback.app.DetailsFragment; -import android.support.v17.leanback.widget.Action; -import android.support.v17.leanback.widget.ArrayObjectAdapter; -import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.support.v17.leanback.widget.DetailsOverviewRow; -import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; -import android.support.v17.leanback.widget.HeaderItem; -import android.support.v17.leanback.widget.ListRow; -import android.support.v17.leanback.widget.ListRowPresenter; -import android.support.v17.leanback.widget.OnActionClickedListener; -import android.support.v17.leanback.widget.PresenterSelector; -import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.text.TextUtils; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.BaseProgram; -import com.android.tv.data.Channel; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * {@link DetailsFragment} for series recording in DVR. - */ -public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implements - DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener { - private static final int ACTION_WATCH = 1; - private static final int ACTION_SERIES_SCHEDULES = 2; - private static final int ACTION_DELETE = 3; - - private DvrWatchedPositionManager mDvrWatchedPositionManager; - private DvrDataManager mDvrDataManager; - - private SeriesRecording mSeries; - // NOTICE: mRecordedPrograms should only be used in creating details fragments. - // After fragments are created, it should be cleared to save resources. - private List mRecordedPrograms; - private RecordedProgram mRecommendRecordedProgram; - private DetailsContent mDetailsContent; - private int mSeasonRowCount; - private SparseArrayObjectAdapter mActionsAdapter; - private Action mDeleteAction; - - private boolean mPaused; - private long mInitialPlaybackPositionMs; - private String mWatchLabel; - private String mResumeLabel; - private Drawable mWatchDrawable; - private RecordedProgramPresenter mRecordedProgramPresenter; - - @Override - public void onCreate(Bundle savedInstanceState) { - mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); - mWatchLabel = getString(R.string.dvr_detail_watch); - mResumeLabel = getString(R.string.dvr_detail_series_resume); - mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null); - mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true); - super.onCreate(savedInstanceState); - } - - @Override - protected void onCreateInternal() { - mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) - .getDvrWatchedPositionManager(); - setDetailsOverviewRow(mDetailsContent); - setupRecordedProgramsRow(); - mDvrDataManager.addSeriesRecordingListener(this); - mDvrDataManager.addRecordedProgramListener(this); - mRecordedPrograms = null; - } - - @Override - public void onResume() { - super.onResume(); - if (mPaused) { - updateWatchAction(); - mPaused = false; - } - } - - @Override - public void onPause() { - super.onPause(); - mPaused = true; - } - - private void updateWatchAction() { - List programs = mDvrDataManager.getRecordedPrograms(mSeries.getId()); - Collections.sort(programs, RecordedProgram.EPISODE_COMPARATOR); - mRecommendRecordedProgram = getRecommendProgram(programs); - if (mRecommendRecordedProgram == null) { - mActionsAdapter.clear(ACTION_WATCH); - } else { - String episodeStatus; - if(mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram) - == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { - episodeStatus = mResumeLabel; - mInitialPlaybackPositionMs = mDvrWatchedPositionManager - .getWatchedPosition(mRecommendRecordedProgram.getId()); - } else { - episodeStatus = mWatchLabel; - mInitialPlaybackPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; - } - String episodeDisplayNumber = mRecommendRecordedProgram.getEpisodeDisplayNumber( - getContext()); - mActionsAdapter.set(ACTION_WATCH, new Action(ACTION_WATCH, - episodeStatus, episodeDisplayNumber, mWatchDrawable)); - } - } - - @Override - protected boolean onLoadRecordingDetails(Bundle args) { - long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID); - mSeries = TvApplication.getSingletons(getActivity()).getDvrDataManager() - .getSeriesRecording(recordId); - if (mSeries == null) { - return false; - } - mRecordedPrograms = mDvrDataManager.getRecordedPrograms(mSeries.getId()); - Collections.sort(mRecordedPrograms, RecordedProgram.SEASON_REVERSED_EPISODE_COMPARATOR); - mDetailsContent = createDetailsContent(); - return true; - } - - @Override - protected PresenterSelector onCreatePresenterSelector( - DetailsOverviewRowPresenter rowPresenter) { - ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); - presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); - presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter()); - return presenterSelector; - } - - private DetailsContent createDetailsContent() { - Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(mSeries.getChannelId()); - String description = TextUtils.isEmpty(mSeries.getLongDescription()) - ? mSeries.getDescription() : mSeries.getLongDescription(); - return new DetailsContent.Builder() - .setTitle(mSeries.getTitle()) - .setDescription(description) - .setImageUris(mSeries.getPosterUri(), mSeries.getPhotoUri(), channel) - .build(); - } - - @Override - protected SparseArrayObjectAdapter onCreateActionsAdapter() { - mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); - Resources res = getResources(); - updateWatchAction(); - mActionsAdapter.set(ACTION_SERIES_SCHEDULES, new Action(ACTION_SERIES_SCHEDULES, - getString(R.string.dvr_detail_view_schedule), null, - res.getDrawable(R.drawable.ic_schedule_32dp, null))); - mDeleteAction = new Action(ACTION_DELETE, - getString(R.string.dvr_detail_series_delete), null, - res.getDrawable(R.drawable.ic_delete_32dp, null)); - if (!mRecordedPrograms.isEmpty()) { - mActionsAdapter.set(ACTION_DELETE, mDeleteAction); - } - return mActionsAdapter; - } - - private void setupRecordedProgramsRow() { - for (RecordedProgram program : mRecordedPrograms) { - addProgram(program); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - mDvrDataManager.removeSeriesRecordingListener(this); - mDvrDataManager.removeRecordedProgramListener(this); - if (mSeries != null) { - DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); - if (dvrManager.canRemoveSeriesRecording(mSeries.getId())) { - dvrManager.removeSeriesRecording(mSeries.getId()); - } - } - mRecordedProgramPresenter.unbindAllViewHolders(); - } - - @Override - protected OnActionClickedListener onCreateOnActionClickedListener() { - return new OnActionClickedListener() { - @Override - public void onActionClicked(Action action) { - if (action.getId() == ACTION_WATCH) { - startPlayback(mRecommendRecordedProgram, mInitialPlaybackPositionMs); - } else if (action.getId() == ACTION_SERIES_SCHEDULES) { - DvrUiHelper.startSchedulesActivityForSeries(getContext(), mSeries); - } else if (action.getId() == ACTION_DELETE) { - DvrUiHelper.startSeriesDeletionActivity(getContext(), mSeries.getId()); - } - } - }; - } - - /** - * The programs are sorted by season number and episode number. - */ - private RecordedProgram getRecommendProgram(List programs) { - for (int i = programs.size() - 1 ; i >= 0 ; i--) { - RecordedProgram program = programs.get(i); - int watchedStatus = mDvrWatchedPositionManager.getWatchedStatus(program); - if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_NEW) { - continue; - } - if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { - return program; - } - if (i == programs.size() - 1) { - return program; - } else { - return programs.get(i + 1); - } - } - return programs.isEmpty() ? null : programs.get(0); - } - - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } - - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - for (SeriesRecording series : seriesRecordings) { - if (mSeries.getId() == series.getId()) { - mSeries = series; - } - } - } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { - for (SeriesRecording series : seriesRecordings) { - if (series.getId() == mSeries.getId()) { - mSeries = null; - getActivity().finish(); - return; - } - } - } - - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { - addProgram(recordedProgram); - if (mActionsAdapter.lookup(ACTION_DELETE) == null) { - mActionsAdapter.set(ACTION_DELETE, mDeleteAction); - } - } - } - } - - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { - // Do nothing - } - - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { - ListRow row = getSeasonRow(recordedProgram.getSeasonNumber(), false); - if (row != null) { - SeasonRowAdapter adapter = (SeasonRowAdapter) row.getAdapter(); - adapter.remove(recordedProgram); - if (adapter.isEmpty()) { - getRowsAdapter().remove(row); - if (getRowsAdapter().size() == 1) { - // No season rows left. Only DetailsOverviewRow - mActionsAdapter.clear(ACTION_DELETE); - } - } - } - if (recordedProgram.getId() == mRecommendRecordedProgram.getId()) { - updateWatchAction(); - } - } - } - } - - private void addProgram(RecordedProgram program) { - String programSeasonNumber = - TextUtils.isEmpty(program.getSeasonNumber()) ? "" : program.getSeasonNumber(); - getOrCreateSeasonRowAdapter(programSeasonNumber).add(program); - } - - private SeasonRowAdapter getOrCreateSeasonRowAdapter(String seasonNumber) { - ListRow row = getSeasonRow(seasonNumber, true); - return (SeasonRowAdapter) row.getAdapter(); - } - - private ListRow getSeasonRow(String seasonNumber, boolean createNewRow) { - seasonNumber = TextUtils.isEmpty(seasonNumber) ? "" : seasonNumber; - ArrayObjectAdapter rowsAdaptor = getRowsAdapter(); - for (int i = rowsAdaptor.size() - 1; i >= 0; i--) { - Object row = rowsAdaptor.get(i); - if (row instanceof ListRow) { - int compareResult = BaseProgram.numberCompare(seasonNumber, - ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber); - if (compareResult == 0) { - return (ListRow) row; - } else if (compareResult < 0) { - return createNewRow ? createNewSeasonRow(seasonNumber, i + 1) : null; - } - } - } - return createNewRow ? createNewSeasonRow(seasonNumber, rowsAdaptor.size()) : null; - } - - private ListRow createNewSeasonRow(String seasonNumber, int position) { - String seasonTitle = seasonNumber.isEmpty() ? mSeries.getTitle() - : getString(R.string.dvr_detail_series_season_title, seasonNumber); - HeaderItem header = new HeaderItem(mSeasonRowCount++, seasonTitle); - ClassPresenterSelector selector = new ClassPresenterSelector(); - selector.addClassPresenter(RecordedProgram.class, mRecordedProgramPresenter); - ListRow row = new ListRow(header, new SeasonRowAdapter(selector, - new Comparator() { - @Override - public int compare(RecordedProgram lhs, RecordedProgram rhs) { - return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs); - } - }, seasonNumber)); - getRowsAdapter().add(position, row); - return row; - } - - private class SeasonRowAdapter extends SortedArrayAdapter { - private String mSeasonNumber; - - SeasonRowAdapter(PresenterSelector selector, Comparator comparator, - String seasonNumber) { - super(selector, comparator); - mSeasonNumber = seasonNumber; - } - - @Override - public long getId(RecordedProgram program) { - return program.getId(); - } - } -} diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java deleted file mode 100644 index c2c0f596..00000000 --- a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.Activity; -import android.content.Context; -import android.media.tv.TvContract; -import android.media.tv.TvInputManager; -import android.text.TextUtils; -import android.view.ViewGroup; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener; -import com.android.tv.dvr.RecordedProgram; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; - -import java.util.List; - -/** - * Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}. - */ -public class SeriesRecordingPresenter extends DvrItemPresenter { - private final ChannelDataManager mChannelDataManager; - private final DvrDataManager mDvrDataManager; - private final DvrManager mDvrManager; - private final DvrWatchedPositionManager mWatchedPositionManager; - - private static final class SeriesRecordingViewHolder extends ViewHolder implements - WatchedPositionChangedListener, ScheduledRecordingListener, RecordedProgramListener { - private SeriesRecording mSeriesRecording; - private RecordingCardView mCardView; - private DvrDataManager mDvrDataManager; - private DvrManager mDvrManager; - private DvrWatchedPositionManager mWatchedPositionManager; - - SeriesRecordingViewHolder(RecordingCardView view, DvrDataManager dvrDataManager, - DvrManager dvrManager, DvrWatchedPositionManager watchedPositionManager) { - super(view); - mCardView = view; - mDvrDataManager = dvrDataManager; - mDvrManager = dvrManager; - mWatchedPositionManager = watchedPositionManager; - } - - @Override - public void onWatchedPositionChanged(long recordedProgramId, long positionMs) { - if (positionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - mWatchedPositionManager.removeListener(this, recordedProgramId); - updateCardViewContent(); - } - } - - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording scheduledRecording : scheduledRecordings) { - if (scheduledRecording.getSeriesRecordingId() == mSeriesRecording.getId()) { - updateCardViewContent(); - return; - } - } - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording scheduledRecording : scheduledRecordings) { - if (scheduledRecording.getSeriesRecordingId() == mSeriesRecording.getId()) { - updateCardViewContent(); - return; - } - } - } - - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - boolean needToUpdateCardView = false; - for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), - mSeriesRecording.getSeriesId())) { - mDvrDataManager.removeScheduledRecordingListener(this); - mWatchedPositionManager.addListener(this, recordedProgram.getId()); - needToUpdateCardView = true; - } - } - if (needToUpdateCardView) { - updateCardViewContent(); - } - } - - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - boolean needToUpdateCardView = false; - for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), - mSeriesRecording.getSeriesId())) { - if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) - == TvInputManager.TIME_SHIFT_INVALID_TIME) { - mWatchedPositionManager.removeListener(this, recordedProgram.getId()); - } - needToUpdateCardView = true; - } - } - if (needToUpdateCardView) { - updateCardViewContent(); - } - } - - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { - // Do nothing - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { - // Do nothing - } - - public void onBound(SeriesRecording seriesRecording) { - mSeriesRecording = seriesRecording; - mDvrDataManager.addScheduledRecordingListener(this); - mDvrDataManager.addRecordedProgramListener(this); - for (RecordedProgram recordedProgram : - mDvrDataManager.getRecordedPrograms(mSeriesRecording.getId())) { - if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) - == TvInputManager.TIME_SHIFT_INVALID_TIME) { - mWatchedPositionManager.addListener(this, recordedProgram.getId()); - } - } - updateCardViewContent(); - } - - public void onUnbound() { - mDvrDataManager.removeScheduledRecordingListener(this); - mDvrDataManager.removeRecordedProgramListener(this); - mWatchedPositionManager.removeListener(this); - } - - private void updateCardViewContent() { - int count = 0; - int quantityStringID; - List recordedPrograms = - mDvrDataManager.getRecordedPrograms(mSeriesRecording.getId()); - if (recordedPrograms.size() == 0) { - count = mDvrManager.getAvailableScheduledRecording(mSeriesRecording.getId()).size(); - quantityStringID = R.plurals.dvr_count_scheduled_recordings; - } else { - for (RecordedProgram recordedProgram : recordedPrograms) { - if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) - == TvInputManager.TIME_SHIFT_INVALID_TIME) { - count++; - } - } - if (count == 0) { - count = recordedPrograms.size(); - quantityStringID = R.plurals.dvr_count_recordings; - } else { - quantityStringID = R.plurals.dvr_count_new_recordings; - } - } - mCardView.setContent(mCardView.getResources() - .getQuantityString(quantityStringID, count, count), null); - } - } - - public SeriesRecordingPresenter(Context context) { - ApplicationSingletons singletons = TvApplication.getSingletons(context); - mChannelDataManager = singletons.getChannelDataManager(); - mDvrDataManager = singletons.getDvrDataManager(); - mDvrManager = singletons.getDvrManager(); - mWatchedPositionManager = singletons.getDvrWatchedPositionManager(); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent) { - Context context = parent.getContext(); - RecordingCardView view = new RecordingCardView(context); - return new SeriesRecordingViewHolder(view, mDvrDataManager, mDvrManager, - mWatchedPositionManager); - } - - @Override - public void onBindViewHolder(ViewHolder baseHolder, Object o) { - final SeriesRecordingViewHolder viewHolder = (SeriesRecordingViewHolder) baseHolder; - final SeriesRecording seriesRecording = (SeriesRecording) o; - final RecordingCardView cardView = (RecordingCardView) viewHolder.view; - viewHolder.onBound(seriesRecording); - setTitleAndImage(cardView, seriesRecording); - super.onBindViewHolder(baseHolder, o); - } - - @Override - public void onUnbindViewHolder(ViewHolder viewHolder) { - ((RecordingCardView) viewHolder.view).reset(); - ((SeriesRecordingViewHolder) viewHolder).onUnbound(); - super.onUnbindViewHolder(viewHolder); - } - - private void setTitleAndImage(RecordingCardView cardView, SeriesRecording recording) { - cardView.setTitle(recording.getTitle()); - if (recording.getPosterUri() != null) { - cardView.setImageUri(recording.getPosterUri(), false); - } else { - Channel channel = mChannelDataManager.getChannel(recording.getChannelId()); - String imageUri = null; - if (channel != null) { - imageUri = TvContract.buildChannelLogoUri(channel.getId()).toString(); - } - cardView.setImageUri(imageUri, true); - } - } -} diff --git a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java deleted file mode 100644 index 6c05c9c6..00000000 --- a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.app.FragmentManager; -import android.app.ProgressDialog; -import android.content.Context; -import android.os.Bundle; -import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import android.support.v17.leanback.widget.GuidedActionsStylist; -import android.util.Log; -import android.util.LongSparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ProgressBar; - -import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.EpisodicProgramLoadTask; -import com.android.tv.dvr.SeriesRecording; -import com.android.tv.dvr.SeriesRecording.ChannelOption; -import com.android.tv.dvr.SeriesRecordingScheduler; -import com.android.tv.dvr.SeriesRecordingScheduler.OnSeriesRecordingUpdatedListener; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Fragment for DVR series recording settings. - */ -public class SeriesSettingsFragment 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; - - private static final long SUB_ACTION_ID_CHANNEL_ALL = 102; - // Each channel's action id = SUB_ACTION_ID_CHANNEL_ONE_BASE + channel id - private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500; - - private DvrDataManager mDvrDataManager; - private ChannelDataManager mChannelDataManager; - private DvrManager mDvrManager; - private SeriesRecording mSeriesRecording; - private long mSeriesRecordingId; - @ChannelOption int mChannelOption; - private Comparator mChannelComparator; - private long mSelectedChannelId; - private int mBackStackCount; - private boolean mShowViewScheduleOptionInDialog; - - private String mFragmentTitle; - private String mProrityActionTitle; - private String mProrityActionHighestText; - private String mProrityActionLowestText; - private String mChannelsActionTitle; - private String mChannelsActionAllText; - private LongSparseArray mId2Channel = new LongSparseArray<>(); - private List mChannels = new ArrayList<>(); - private EpisodicProgramLoadTask mEpisodicProgramLoadTask; - - private GuidedAction mPriorityGuidedAction; - private GuidedAction mChannelsGuidedAction; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mBackStackCount = getFragmentManager().getBackStackEntryCount(); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID); - mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); - if (mSeriesRecording == null) { - getActivity().finish(); - return; - } - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); - mShowViewScheduleOptionInDialog = getArguments().getBoolean( - DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); - mDvrDataManager.addSeriesRecordingListener(this); - long[] channelIds = getArguments().getLongArray(DvrSeriesSettingsActivity.CHANNEL_ID_LIST); - mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); - if (channelIds == null) { - Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId()); - if (channel != null) { - mId2Channel.put(channel.getId(), channel); - mChannels.add(channel); - } - collectChannelsInBackground(); - } else { - for (long channelId : channelIds) { - Channel channel = mChannelDataManager.getChannel(channelId); - if (channel != null) { - mId2Channel.put(channel.getId(), channel); - mChannels.add(channel); - } - } - } - mChannelOption = mSeriesRecording.getChannelOption(); - mSelectedChannelId = Channel.INVALID_ID; - if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) { - Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId()); - if (channel != null) { - mSelectedChannelId = channel.getId(); - } else { - mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; - } - } - mChannelComparator = new Channel.DefaultComparator(context, - TvApplication.getSingletons(context).getTvInputManagerHelper()); - mChannels.sort(mChannelComparator); - 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); - mProrityActionLowestText = getString(R.string.dvr_series_settings_priority_lowest); - mChannelsActionTitle = getString(R.string.dvr_series_settings_channels); - mChannelsActionAllText = getString(R.string.dvr_series_settings_channels_all); - } - - @Override - public void onDetach() { - super.onDetach(); - mDvrDataManager.removeSeriesRecordingListener(this); - if (mEpisodicProgramLoadTask != null) { - mEpisodicProgramLoadTask.cancel(true); - mEpisodicProgramLoadTask = null; - } - } - - @Override - public void onDestroy() { - DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); - if (getFragmentManager().getBackStackEntryCount() == mBackStackCount - && getArguments() - .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING) - && dvrManager.canRemoveSeriesRecording(mSeriesRecordingId)) { - dvrManager.removeSeriesRecording(mSeriesRecordingId); - } - super.onDestroy(); - } - - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String breadcrumb = mSeriesRecording.getTitle(); - String title = mFragmentTitle; - return new Guidance(title, null, breadcrumb, null); - } - - @Override - public void onCreateActions(List actions, Bundle savedInstanceState) { - mPriorityGuidedAction = new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_PRIORITY) - .title(mProrityActionTitle) - .build(); - updatePriorityGuidedAction(false); - actions.add(mPriorityGuidedAction); - - mChannelsGuidedAction = new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_CHANNEL) - .title(mChannelsActionTitle) - .subActions(buildChannelSubAction()) - .build(); - actions.add(mChannelsGuidedAction); - updateChannelsGuidedAction(false); - } - - @Override - public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_OK) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); - } - - @Override - public void onGuidedActionClicked(GuidedAction action) { - long actionId = action.getId(); - if (actionId == GuidedAction.ACTION_ID_OK) { - if (mEpisodicProgramLoadTask != null) { - mEpisodicProgramLoadTask.cancel(true); - mEpisodicProgramLoadTask = null; - } - if (mChannelOption != mSeriesRecording.getChannelOption() - || mSeriesRecording.isStopped() - || (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE - && mSeriesRecording.getChannelId() != mSelectedChannelId)) { - SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording) - .setChannelOption(mChannelOption) - .setState(SeriesRecording.STATE_SERIES_NORMAL); - if (mSelectedChannelId != Channel.INVALID_ID) { - builder.setChannelId(mSelectedChannelId); - } - TvApplication.getSingletons(getContext()).getDvrManager() - .updateSeriesRecording(builder.build()); - SeriesRecordingScheduler scheduler = - SeriesRecordingScheduler.getInstance(getContext()); - // Since dialog is used even after the fragment is closed, we should - // use application context. - ProgressDialog dialog = ProgressDialog.show(getContext(), null, getString( - R.string.dvr_series_schedules_progress_message_updating_programs)); - scheduler.addOnSeriesRecordingUpdatedListener( - new OnSeriesRecordingUpdatedListener() { - @Override - public void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - if (seriesRecording.getId() == mSeriesRecordingId) { - dialog.dismiss(); - scheduler.removeOnSeriesRecordingUpdatedListener(this); - showConfirmDialog(); - return; - } - } - } - }); - } else { - showConfirmDialog(); - } - } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { - finishGuidedStepFragments(); - } else if (actionId == ACTION_ID_PRIORITY) { - FragmentManager fragmentManager = getFragmentManager(); - PrioritySettingsFragment fragment = new PrioritySettingsFragment(); - Bundle args = new Bundle(); - args.putLong(PrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, - mSeriesRecording.getId()); - fragment.setArguments(args); - GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame); - } - } - - @Override - public boolean onSubGuidedActionClicked(GuidedAction action) { - long actionId = action.getId(); - if (actionId == SUB_ACTION_ID_CHANNEL_ALL) { - mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; - mSelectedChannelId = Channel.INVALID_ID; - updateChannelsGuidedAction(true); - return true; - } else if (actionId > SUB_ACTION_ID_CHANNEL_ONE_BASE) { - mChannelOption = SeriesRecording.OPTION_CHANNEL_ONE; - mSelectedChannelId = actionId - SUB_ACTION_ID_CHANNEL_ONE_BASE; - updateChannelsGuidedAction(true); - return true; - } - return false; - } - - @Override - public GuidedActionsStylist onCreateButtonActionsStylist() { - return new DvrGuidedActionsStylist(true); - } - - private void updateChannelsGuidedAction(boolean notifyActionChanged) { - if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) { - mChannelsGuidedAction.setDescription(mChannelsActionAllText); - } else { - mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId) - .getDisplayText()); - } - if (notifyActionChanged) { - notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); - } - } - - private void updatePriorityGuidedAction(boolean notifyActionChanged) { - int totalSeriesCount = 0; - int priorityOrder = 0; - for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) { - if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL - || seriesRecording.getId() == mSeriesRecording.getId()) { - ++totalSeriesCount; - } - if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL - && seriesRecording.getId() != mSeriesRecording.getId() - && seriesRecording.getPriority() > mSeriesRecording.getPriority()) { - ++priorityOrder; - } - } - if (priorityOrder == 0) { - mPriorityGuidedAction.setDescription(mProrityActionHighestText); - } else if (priorityOrder >= totalSeriesCount - 1) { - mPriorityGuidedAction.setDescription(mProrityActionLowestText); - } else { - mPriorityGuidedAction.setDescription(getString( - R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); - } - if (notifyActionChanged) { - notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY)); - } - } - - private void collectChannelsInBackground() { - if (mEpisodicProgramLoadTask != null) { - mEpisodicProgramLoadTask.cancel(true); - } - mEpisodicProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) { - @Override - protected void onPostExecute(List programs) { - mEpisodicProgramLoadTask = null; - Set channelIds = new HashSet<>(); - for (Program program : programs) { - channelIds.add(program.getChannelId()); - } - boolean channelAdded = false; - for (Long channelId : channelIds) { - if (mId2Channel.get(channelId) != null) { - continue; - } - Channel channel = mChannelDataManager.getChannel(channelId); - if (channel != null) { - channelAdded = true; - mId2Channel.put(channelId, channel); - mChannels.add(channel); - if (DEBUG) Log.d(TAG, "Added channel: " + channel); - } - } - if (!channelAdded) { - return; - } - mChannels.sort(mChannelComparator); - mChannelsGuidedAction.setSubActions(buildChannelSubAction()); - notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); - if (DEBUG) Log.d(TAG, "Complete EpisodicProgramLoadTask"); - } - }.setLoadCurrentProgram(true) - .setLoadDisallowedProgram(true) - .setLoadScheduledEpisode(true) - .setIgnoreChannelOption(true); - mEpisodicProgramLoadTask.execute(); - } - - private List buildChannelSubAction() { - List channelSubActions = new ArrayList<>(); - channelSubActions.add(new GuidedAction.Builder(getActivity()) - .id(SUB_ACTION_ID_CHANNEL_ALL) - .title(mChannelsActionAllText) - .build()); - for (Channel channel : mChannels) { - channelSubActions.add(new GuidedAction.Builder(getActivity()) - .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) - .title(channel.getDisplayText()) - .build()); - } - return channelSubActions; - } - - private void showConfirmDialog() { - DvrUiHelper.StartSeriesScheduledDialogActivity( - getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog); - finishGuidedStepFragments(); - } - - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { } - - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - if (seriesRecording.getId() == mSeriesRecordingId) { - mSeriesRecording = seriesRecording; - updatePriorityGuidedAction(true); - return; - } - } - } -} diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java index 393a5ff3..8c0af9ed 100644 --- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java +++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java @@ -20,11 +20,15 @@ import android.support.annotation.VisibleForTesting; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.PresenterSelector; +import com.android.tv.common.SoftPreconditions; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Keeps a set of items sorted @@ -35,16 +39,18 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { private final Comparator mComparator; private final int mMaxItemCount; private int mExtraItemCount; + private final Set mIds = new HashSet<>(); - SortedArrayAdapter(PresenterSelector presenterSelector, Comparator comparator) { + public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator comparator) { this(presenterSelector, comparator, Integer.MAX_VALUE); } - SortedArrayAdapter(PresenterSelector presenterSelector, Comparator comparator, + public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator comparator, int maxItemCount) { super(presenterSelector); mComparator = comparator; mMaxItemCount = maxItemCount; + setHasStableIds(true); } /** @@ -56,7 +62,12 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { final void setInitialItems(List items) { List itemsCopy = new ArrayList<>(items); Collections.sort(itemsCopy, mComparator); - addAll(0, itemsCopy.subList(0, Math.min(mMaxItemCount, itemsCopy.size()))); + for (T item : itemsCopy) { + add(item, true); + if (size() == mMaxItemCount) { + break; + } + } } /** @@ -82,6 +93,9 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { * the end to save search time. */ public final void add(T item, boolean insertToEnd) { + long newItemId = getId(item); + SoftPreconditions.checkState(!mIds.contains(newItemId)); + mIds.add(newItemId); int i; if (insertToEnd) { i = findInsertPosition(item); @@ -89,8 +103,9 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { i = findInsertPositionBinary(item); } super.add(i, item); - if (size() > mMaxItemCount + mExtraItemCount) { - removeItems(mMaxItemCount, size() - mMaxItemCount - mExtraItemCount); + if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) { + Object removedItem = get(mMaxItemCount); + remove(removedItem); } } @@ -100,48 +115,97 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { * They will be presented in their insertion order. */ public int addExtraItem(T item) { + long newItemId = getId(item); + SoftPreconditions.checkState(!mIds.contains(newItemId)); + mIds.add(newItemId); super.add(item); return ++mExtraItemCount; } + @Override + public boolean remove(Object item) { + return removeWithId((T) item); + } + /** * Removes an item which has the same ID as {@code item}. */ public boolean removeWithId(T item) { - int index = indexWithTypeAndId(item); - return index >= 0 && index < size() && remove(get(index)); + int index = indexWithId(item); + return index >= 0 && index < size() && removeItems(index, 1) == 1; + } + + @Override + public int removeItems(int position, int count) { + int upperBound = Math.min(position + count, size()); + for (int i = position; i < upperBound; i++) { + mIds.remove(getId((T) get(i))); + } + if (upperBound > size() - mExtraItemCount) { + mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position); + } + return super.removeItems(position, count); + } + + @Override + public void replace(int position, Object item) { + boolean wasExtra = position >= size() - mExtraItemCount; + removeItems(position, 1); + if (!wasExtra) { + add(item); + } else { + addExtraItem((T) item); + } + } + + @Override + public void clear() { + mIds.clear(); + super.clear(); } /** - * Change an item in the list. + * Changes an item in the list. * @param item The item to change. */ public final void change(T item) { - int oldIndex = indexWithTypeAndId(item); + int oldIndex = indexWithId(item); if (oldIndex != -1) { T old = (T) get(oldIndex); if (mComparator.compare(old, item) == 0) { replace(oldIndex, item); return; } - removeItems(oldIndex, 1); + remove(old); } add(item); } + /** + * Checks whether the item is in the list. + */ + public final boolean contains(T item) { + return indexWithId(item) != -1; + } + + @Override + public long getId(int position) { + return getId((T) get(position)); + } + /** * Returns the id of the the given {@code item}, which will be used in {@link #change} to * decide if the given item is already existed in the adapter. * * The id must be stable. */ - abstract long getId(T item); + protected abstract long getId(T item); - private int indexWithTypeAndId(T item) { + private int indexWithId(T item) { long id = getId(item); for (int i = 0; i < size() - mExtraItemCount; i++) { T r = (T) get(i); - if (r.getClass() == item.getClass() && getId(r) == id) { + if (getId(r) == id) { return i; } } diff --git a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java new file mode 100644 index 00000000..5fe9c478 --- /dev/null +++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui; + +import android.content.Context; +import android.support.v17.leanback.app.GuidedStepFragment; +import android.support.v17.leanback.widget.GuidedAction; + +import com.android.tv.TvApplication; +import com.android.tv.analytics.Tracker; + +/** A {@link GuidedStepFragment} with {@link Tracker} for analytics. */ +public abstract class TrackedGuidedStepFragment extends GuidedStepFragment { + private Tracker mTracker; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mTracker = TvApplication.getSingletons(context).getAnalytics().getDefaultTracker(); + } + + @Override + public void onDetach() { + mTracker = null; + super.onDetach(); + } + + @Override + public final void onGuidedActionClicked(GuidedAction action) { + super.onGuidedActionClicked(action); + if (mTracker != null) { + mTracker.sendMenuClicked( + getTrackerPrefix() + "-action-" + getTrackerLabelForGuidedAction(action)); + } + onTrackedGuidedActionClicked(action); + } + + public String getTrackerLabelForGuidedAction(GuidedAction action) { + long actionId = action.getId(); + if (actionId == GuidedAction.ACTION_ID_CANCEL) { + return "cancel"; + } else if (actionId == GuidedAction.ACTION_ID_NEXT) { + return "next"; + } else if (actionId == GuidedAction.ACTION_ID_CURRENT) { + return "current"; + } else if (actionId == GuidedAction.ACTION_ID_OK) { + return "ok"; + } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { + return "cancel"; + } else if (actionId == GuidedAction.ACTION_ID_FINISH) { + return "finish"; + } else if (actionId == GuidedAction.ACTION_ID_CONTINUE) { + return "continue"; + } else if (actionId == GuidedAction.ACTION_ID_YES) { + return "yes"; + } else if (actionId == GuidedAction.ACTION_ID_NO) { + return "no"; + } else { + return "unknown-" + actionId; + } + } + + /** Delegated from {@link #onGuidedActionClicked(GuidedAction)} */ + public abstract void onTrackedGuidedActionClicked(GuidedAction action); + + /** The prefix used for analytics tracking, Usually the class name. */ + public abstract String getTrackerPrefix(); +} diff --git a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java new file mode 100644 index 00000000..38a78f5d --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.graphics.drawable.Drawable; +import android.support.v17.leanback.R; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.Presenter; +import android.support.v17.leanback.widget.PresenterSelector; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +// This class is adapted from Leanback's library, which does not support action icon with one-line +// label. This class modified its getPresenter method to support the above situation. +class ActionPresenterSelector extends PresenterSelector { + private final Presenter mOneLineActionPresenter = new OneLineActionPresenter(); + private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter(); + private final Presenter[] mPresenters = new Presenter[] { + mOneLineActionPresenter, mTwoLineActionPresenter}; + + @Override + public Presenter getPresenter(Object item) { + Action action = (Action) item; + if (TextUtils.isEmpty(action.getLabel2()) && action.getIcon() == null) { + return mOneLineActionPresenter; + } else { + return mTwoLineActionPresenter; + } + } + + @Override + public Presenter[] getPresenters() { + return mPresenters; + } + + static class ActionViewHolder extends Presenter.ViewHolder { + Action mAction; + Button mButton; + int mLayoutDirection; + + public ActionViewHolder(View view, int layoutDirection) { + super(view); + mButton = (Button) view.findViewById(R.id.lb_action_button); + mLayoutDirection = layoutDirection; + } + } + + class OneLineActionPresenter extends Presenter { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.lb_action_1_line, parent, false); + return new ActionViewHolder(v, parent.getLayoutDirection()); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { + Action action = (Action) item; + ActionViewHolder vh = (ActionViewHolder) viewHolder; + vh.mAction = action; + vh.mButton.setText(action.getLabel1()); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + ((ActionViewHolder) viewHolder).mAction = null; + } + } + + class TwoLineActionPresenter extends Presenter { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.lb_action_2_lines, parent, false); + return new ActionViewHolder(v, parent.getLayoutDirection()); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { + Action action = (Action) item; + ActionViewHolder vh = (ActionViewHolder) viewHolder; + Drawable icon = action.getIcon(); + vh.mAction = action; + + if (icon != null) { + final int startPadding = vh.view.getResources() + .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start); + final int endPadding = vh.view.getResources() + .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end); + vh.view.setPaddingRelative(startPadding, 0, endPadding, 0); + } else { + final int padding = vh.view.getResources() + .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal); + vh.view.setPaddingRelative(padding, 0, padding, 0); + } + vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); + + CharSequence line1 = action.getLabel1(); + CharSequence line2 = action.getLabel2(); + if (TextUtils.isEmpty(line1)) { + vh.mButton.setText(line2); + } else if (TextUtils.isEmpty(line2)) { + vh.mButton.setText(line1); + } else { + vh.mButton.setText(line1 + "\n" + line2); + } + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + ActionViewHolder vh = (ActionViewHolder) viewHolder; + vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null); + vh.view.setPadding(0, 0, 0, 0); + vh.mAction = null; + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java new file mode 100644 index 00000000..bf18ddc0 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.content.res.Resources; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dialog.HalfSizedDialogFragment; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.DvrStopRecordingFragment; +import com.android.tv.dvr.ui.DvrUiHelper; + +/** + * {@link RecordingDetailsFragment} for current recording in DVR. + */ +public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { + private static final int ACTION_STOP_RECORDING = 1; + + private DvrDataManager mDvrDataManger; + private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener = + new DvrDataManager.ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... schedules) { } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + if (schedule.getId() == getRecording().getId()) { + getActivity().finish(); + return; + } + } + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + if (schedule.getId() == getRecording().getId() + && schedule.getState() + != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { + getActivity().finish(); + return; + } + } + } + }; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener); + } + + @Override + protected SparseArrayObjectAdapter onCreateActionsAdapter() { + SparseArrayObjectAdapter adapter = + new SparseArrayObjectAdapter(new ActionPresenterSelector()); + Resources res = getResources(); + adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING, + res.getString(R.string.dvr_detail_stop_recording), null, + res.getDrawable(R.drawable.lb_ic_stop))); + return adapter; + } + + @Override + protected OnActionClickedListener onCreateOnActionClickedListener() { + return new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + if (action.getId() == ACTION_STOP_RECORDING) { + DvrUiHelper.showStopRecordingDialog(getActivity(), + getRecording().getChannelId(), + DvrStopRecordingFragment.REASON_USER_STOP, + new HalfSizedDialogFragment.OnActionClickListener() { + @Override + public void onActionClick(long actionId) { + if (actionId == DvrStopRecordingFragment.ACTION_STOP) { + DvrManager dvrManager = + TvApplication.getSingletons(getContext()) + .getDvrManager(); + dvrManager.stopRecording(getRecording()); + getActivity().finish(); + } + } + }); + } + } + }; + } + + @Override + public void onDetach() { + if (mDvrDataManger != null) { + mDvrDataManger.removeScheduledRecordingListener(mScheduledRecordingListener); + } + super.onDetach(); + } +} diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java new file mode 100644 index 00000000..c1fa05d7 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.media.tv.TvContract; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.Channel; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.DvrUiHelper; + +/** + * A class for details content. + */ +class DetailsContent { + /** Constant for invalid time. */ + public static final long INVALID_TIME = -1; + + private CharSequence mTitle; + private long mStartTimeUtcMillis; + private long mEndTimeUtcMillis; + private String mDescription; + private String mLogoImageUri; + private String mBackgroundImageUri; + private boolean mUsingChannelLogo; + + static DetailsContent createFromRecordedProgram(Context context, + RecordedProgram recordedProgram) { + return new DetailsContent.Builder() + .setChannelId(recordedProgram.getChannelId()) + .setProgramTitle(recordedProgram.getTitle()) + .setSeasonNumber(recordedProgram.getSeasonNumber()) + .setEpisodeNumber(recordedProgram.getEpisodeNumber()) + .setStartTimeUtcMillis(recordedProgram.getStartTimeUtcMillis()) + .setEndTimeUtcMillis(recordedProgram.getEndTimeUtcMillis()) + .setDescription(TextUtils.isEmpty(recordedProgram.getLongDescription()) + ? recordedProgram.getDescription() : recordedProgram.getLongDescription()) + .setPosterArtUri(recordedProgram.getPosterArtUri()) + .setThumbnailUri(recordedProgram.getThumbnailUri()) + .build(context); + } + + static DetailsContent createFromSeriesRecording(Context context, + SeriesRecording seriesRecording) { + return new DetailsContent.Builder() + .setChannelId(seriesRecording.getChannelId()) + .setTitle(seriesRecording.getTitle()) + .setDescription(TextUtils.isEmpty(seriesRecording.getLongDescription()) + ? seriesRecording.getDescription() : seriesRecording.getLongDescription()) + .setPosterArtUri(seriesRecording.getPosterUri()) + .setThumbnailUri(seriesRecording.getPhotoUri()) + .build(context); + } + + static DetailsContent createFromScheduledRecording(Context context, + ScheduledRecording scheduledRecording) { + Channel channel = TvApplication.getSingletons(context).getChannelDataManager() + .getChannel(scheduledRecording.getChannelId()); + String description = !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) ? + scheduledRecording.getProgramDescription() + : scheduledRecording.getProgramLongDescription(); + if (TextUtils.isEmpty(description)) { + description = channel != null ? channel.getDescription() : null; + } + return new DetailsContent.Builder() + .setChannelId(scheduledRecording.getChannelId()) + .setProgramTitle(scheduledRecording.getProgramTitle()) + .setSeasonNumber(scheduledRecording.getSeasonNumber()) + .setEpisodeNumber(scheduledRecording.getEpisodeNumber()) + .setStartTimeUtcMillis(scheduledRecording.getStartTimeMs()) + .setEndTimeUtcMillis(scheduledRecording.getEndTimeMs()) + .setDescription(description) + .setPosterArtUri(scheduledRecording.getProgramPosterArtUri()) + .setThumbnailUri(scheduledRecording.getProgramThumbnailUri()) + .build(context); + } + + private DetailsContent() { } + + /** + * Returns title. + */ + public CharSequence getTitle() { + return mTitle; + } + + /** + * Returns start time. + */ + public long getStartTimeUtcMillis() { + return mStartTimeUtcMillis; + } + + /** + * Returns end time. + */ + public long getEndTimeUtcMillis() { + return mEndTimeUtcMillis; + } + + /** + * Returns description. + */ + public String getDescription() { + return mDescription; + } + + /** + * Returns Logo image URI as a String. + */ + public String getLogoImageUri() { + return mLogoImageUri; + } + + /** + * Returns background image URI as a String. + */ + public String getBackgroundImageUri() { + return mBackgroundImageUri; + } + + /** + * Returns if image URIs are from its channels' logo. + */ + public boolean isUsingChannelLogo() { + return mUsingChannelLogo; + } + + /** + * Copies other details content. + */ + public void copyFrom(DetailsContent other) { + if (this == other) { + return; + } + mTitle = other.mTitle; + mStartTimeUtcMillis = other.mStartTimeUtcMillis; + mEndTimeUtcMillis = other.mEndTimeUtcMillis; + mDescription = other.mDescription; + mLogoImageUri = other.mLogoImageUri; + mBackgroundImageUri = other.mBackgroundImageUri; + mUsingChannelLogo = other.mUsingChannelLogo; + } + + /** + * A class for building details content. + */ + public static final class Builder { + private final DetailsContent mDetailsContent; + + private long mChannelId; + private String mProgramTitle; + private String mSeasonNumber; + private String mEpisodeNumber; + private String mPosterArtUri; + private String mThumbnailUri; + + public Builder() { + mDetailsContent = new DetailsContent(); + mDetailsContent.mStartTimeUtcMillis = INVALID_TIME; + mDetailsContent.mEndTimeUtcMillis = INVALID_TIME; + } + + /** + * Sets title. + */ + public Builder setTitle(CharSequence title) { + mDetailsContent.mTitle = title; + return this; + } + + /** + * Sets start time. + */ + public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { + mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis; + return this; + } + + /** + * Sets end time. + */ + public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { + mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis; + return this; + } + + /** + * Sets description. + */ + public Builder setDescription(String description) { + mDetailsContent.mDescription = description; + return this; + } + + /** + * Sets logo image URI as a String. + */ + public Builder setLogoImageUri(String logoImageUri) { + mDetailsContent.mLogoImageUri = logoImageUri; + return this; + } + + /** + * Sets background image URI as a String. + */ + public Builder setBackgroundImageUri(String backgroundImageUri) { + mDetailsContent.mBackgroundImageUri = backgroundImageUri; + return this; + } + + private Builder setProgramTitle(String programTitle) { + mProgramTitle = programTitle; + return this; + } + + private Builder setSeasonNumber(String seasonNumber) { + mSeasonNumber = seasonNumber; + return this; + } + + private Builder setEpisodeNumber(String episodeNumber) { + mEpisodeNumber = episodeNumber; + return this; + } + + private Builder setChannelId(long channelId) { + mChannelId = channelId; + return this; + } + + private Builder setPosterArtUri(String posterArtUri) { + mPosterArtUri = posterArtUri; + return this; + } + + private Builder setThumbnailUri(String thumbnailUri) { + mThumbnailUri = thumbnailUri; + return this; + } + + private void createStyledTitle(Context context, Channel channel) { + CharSequence title = DvrUiHelper.getStyledTitleWithEpisodeNumber(context, + mProgramTitle, mSeasonNumber, mEpisodeNumber, + R.style.text_appearance_card_view_episode_number); + if (TextUtils.isEmpty(title)) { + mDetailsContent.mTitle = channel != null ? channel.getDisplayName() + : context.getResources().getString(R.string.no_program_information); + } else { + mDetailsContent.mTitle = title; + } + } + + private void createImageUris(@Nullable Channel channel) { + mDetailsContent.mLogoImageUri = null; + mDetailsContent.mBackgroundImageUri = null; + mDetailsContent.mUsingChannelLogo = false; + if (!TextUtils.isEmpty(mPosterArtUri) && !TextUtils.isEmpty(mThumbnailUri)) { + mDetailsContent.mLogoImageUri = mPosterArtUri; + mDetailsContent.mBackgroundImageUri = mThumbnailUri; + } else if (!TextUtils.isEmpty(mPosterArtUri)) { + // thumbnailUri is empty + mDetailsContent.mLogoImageUri = mPosterArtUri; + mDetailsContent.mBackgroundImageUri = mPosterArtUri; + } else if (!TextUtils.isEmpty(mThumbnailUri)) { + // posterArtUri is empty + mDetailsContent.mLogoImageUri = mThumbnailUri; + mDetailsContent.mBackgroundImageUri = mThumbnailUri; + } + if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) { + String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()) + .toString(); + mDetailsContent.mLogoImageUri = channelLogoUri; + mDetailsContent.mBackgroundImageUri = channelLogoUri; + mDetailsContent.mUsingChannelLogo = true; + } + } + + /** + * Builds details content. + */ + public DetailsContent build(Context context) { + Channel channel = TvApplication.getSingletons(context).getChannelDataManager() + .getChannel(mChannelId); + if (mDetailsContent.mTitle == null) { + createStyledTitle(context, channel); + } + if (mDetailsContent.mBackgroundImageUri == null + && mDetailsContent.mLogoImageUri == null) { + createImageUris(channel); + } + DetailsContent detailsContent = new DetailsContent(); + detailsContent.copyFrom(mDetailsContent); + return detailsContent; + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java new file mode 100644 index 00000000..09b57887 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.app.Activity; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.support.v17.leanback.widget.Presenter; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.tv.R; +import com.android.tv.ui.ViewUtils; +import com.android.tv.util.Utils; + +/** + * An {@link Presenter} for rendering a detailed description of an DVR item. + * Typically this Presenter will be used in a + * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. + * Most codes of this class is originated from + * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}. + * The latter class are re-used to provide a customized version of + * {@link android.support.v17.leanback.widget.DetailsOverviewRow}. + */ +class DetailsContentPresenter extends Presenter { + /** + * The ViewHolder for the {@link DetailsContentPresenter}. + */ + public static class ViewHolder extends Presenter.ViewHolder { + final TextView mTitle; + final TextView mSubtitle; + final LinearLayout mDescriptionContainer; + final TextView mBody; + final TextView mReadMoreView; + final int mTitleMargin; + final int mUnderTitleBaselineMargin; + final int mUnderSubtitleBaselineMargin; + final int mTitleLineSpacing; + final int mBodyLineSpacing; + final int mBodyMaxLines; + final int mBodyMinLines; + final FontMetricsInt mTitleFontMetricsInt; + final FontMetricsInt mSubtitleFontMetricsInt; + final FontMetricsInt mBodyFontMetricsInt; + final int mTitleMaxLines; + + private Activity mActivity; + private boolean mFullTextMode; + private int mFullTextAnimationDuration; + private boolean mIsListeningToPreDraw; + + private ViewTreeObserver.OnPreDrawListener mPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (mSubtitle.getVisibility() == View.VISIBLE + && mSubtitle.getTop() > view.getHeight() + && mTitle.getLineCount() > 1) { + mTitle.setMaxLines(mTitle.getLineCount() - 1); + return false; + } + final int bodyLines = mBody.getLineCount(); + int maxLines = mFullTextMode ? bodyLines : + (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines); + if (bodyLines > maxLines) { + mReadMoreView.setVisibility(View.VISIBLE); + mDescriptionContainer.setFocusable(true); + mDescriptionContainer.setClickable(true); + mDescriptionContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mFullTextMode = true; + mReadMoreView.setVisibility(View.GONE); + mDescriptionContainer.setFocusable(( + (AccessibilityManager) view.getContext() + .getSystemService( + Context.ACCESSIBILITY_SERVICE)) + .isEnabled()); + mDescriptionContainer.setClickable(false); + mDescriptionContainer.setOnClickListener(null); + int oldMaxLines = mBody.getMaxLines(); + mBody.setMaxLines(bodyLines); + // Minus 1 from line difference to eliminate the space + // originally occupied by "READ MORE" + showFullText((bodyLines - oldMaxLines - 1) * mBodyLineSpacing); + } + }); + } + if (mReadMoreView.getVisibility() == View.VISIBLE + && mSubtitle.getVisibility() == View.VISIBLE) { + // If both "READ MORE" and subtitle is shown, the capable maximum lines + // will be one line less. + maxLines -= 1; + } + if (mBody.getMaxLines() != maxLines) { + mBody.setMaxLines(maxLines); + return false; + } else { + removePreDrawListener(); + return true; + } + } + }; + + public ViewHolder(final View view) { + super(view); + view.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + // In case predraw listener was removed in detach, make sure + // we have the proper layout. + addPreDrawListener(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + removePreDrawListener(); + } + }); + mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title); + mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle); + mBody = (TextView) view.findViewById(R.id.dvr_details_description_body); + mDescriptionContainer = + (LinearLayout) view.findViewById(R.id.dvr_details_description_container); + // We have to explicitly set focusable to true here for accessibility, since we might + // set the view's focusable state when we need to show "READ MORE", which would remove + // the default focusable state for accessibility. + mDescriptionContainer.setFocusable(((AccessibilityManager) view.getContext() + .getSystemService(Context.ACCESSIBILITY_SERVICE)).isEnabled()); + mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more); + + FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle); + final int titleAscent = view.getResources().getDimensionPixelSize( + R.dimen.lb_details_description_title_baseline); + // Ascent is negative + mTitleMargin = titleAscent + titleFontMetricsInt.ascent; + + mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize( + R.dimen.lb_details_description_under_title_baseline_margin); + mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize( + R.dimen.dvr_details_description_under_subtitle_baseline_margin); + + mTitleLineSpacing = view.getResources().getDimensionPixelSize( + R.dimen.lb_details_description_title_line_spacing); + mBodyLineSpacing = view.getResources().getDimensionPixelSize( + R.dimen.lb_details_description_body_line_spacing); + + mBodyMaxLines = view.getResources().getInteger( + R.integer.lb_details_description_body_max_lines); + mBodyMinLines = view.getResources().getInteger( + R.integer.lb_details_description_body_min_lines); + mTitleMaxLines = mTitle.getMaxLines(); + + mTitleFontMetricsInt = getFontMetricsInt(mTitle); + mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle); + mBodyFontMetricsInt = getFontMetricsInt(mBody); + } + + void addPreDrawListener() { + if (!mIsListeningToPreDraw) { + mIsListeningToPreDraw = true; + view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); + } + } + + void removePreDrawListener() { + if (mIsListeningToPreDraw) { + view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); + mIsListeningToPreDraw = false; + } + } + + public TextView getTitle() { + return mTitle; + } + + public TextView getSubtitle() { + return mSubtitle; + } + + public TextView getBody() { + return mBody; + } + + private FontMetricsInt getFontMetricsInt(TextView textView) { + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setTextSize(textView.getTextSize()); + paint.setTypeface(textView.getTypeface()); + return paint.getFontMetricsInt(); + } + + private void showFullText(int heightDiff) { + final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame); + int nowHeight = ViewUtils.getLayoutHeight(detailsFrame); + Animator expandAnimator = ViewUtils.createHeightAnimator( + detailsFrame, nowHeight, nowHeight + heightDiff); + expandAnimator.setDuration(mFullTextAnimationDuration); + Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame, + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, + 0f, -(heightDiff / 2))); + shiftAnimator.setDuration(mFullTextAnimationDuration); + AnimatorSet fullTextAnimator = new AnimatorSet(); + fullTextAnimator.playTogether(expandAnimator, shiftAnimator); + fullTextAnimator.start(); + } + } + + private final Activity mActivity; + private final int mFullTextAnimationDuration; + + public DetailsContentPresenter(Activity activity) { + super(); + mActivity = activity; + mFullTextAnimationDuration = mActivity.getResources() + .getInteger(R.integer.dvr_details_full_text_animation_duration); + } + + @Override + public final ViewHolder onCreateViewHolder(ViewGroup parent) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.dvr_details_description, parent, false); + return new ViewHolder(v); + } + + @Override + public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { + final ViewHolder vh = (ViewHolder) viewHolder; + final DetailsContent detailsContent = (DetailsContent) item; + + vh.mActivity = mActivity; + vh.mFullTextAnimationDuration = mFullTextAnimationDuration; + + boolean hasTitle = true; + if (TextUtils.isEmpty(detailsContent.getTitle())) { + vh.mTitle.setVisibility(View.GONE); + hasTitle = false; + } else { + vh.mTitle.setText(detailsContent.getTitle()); + vh.mTitle.setVisibility(View.VISIBLE); + vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() + + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier()); + vh.mTitle.setMaxLines(vh.mTitleMaxLines); + } + setTopMargin(vh.mTitle, vh.mTitleMargin); + + boolean hasSubtitle = true; + if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME + && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) { + vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(), + detailsContent.getStartTimeUtcMillis(), + detailsContent.getEndTimeUtcMillis(), false)); + vh.mSubtitle.setVisibility(View.VISIBLE); + if (hasTitle) { + setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin + + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent); + } else { + setTopMargin(vh.mSubtitle, 0); + } + } else { + vh.mSubtitle.setVisibility(View.GONE); + hasSubtitle = false; + } + + if (TextUtils.isEmpty(detailsContent.getDescription())) { + vh.mBody.setVisibility(View.GONE); + } else { + vh.mBody.setText(detailsContent.getDescription()); + vh.mBody.setVisibility(View.VISIBLE); + vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() + + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier()); + if (hasSubtitle) { + setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin + + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent + - vh.mBody.getPaddingTop()); + } else if (hasTitle) { + setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin + + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent + - vh.mBody.getPaddingTop()); + } else { + setTopMargin(vh.mDescriptionContainer, 0); + } + } + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { } + + private void setTopMargin(View view, int topMargin) { + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.topMargin = topMargin; + view.setLayoutParams(lp); + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java new file mode 100644 index 00000000..82fe9ce3 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.app.Activity; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.support.v17.leanback.app.BackgroundManager; + +/** + * The Background Helper. + */ +class DetailsViewBackgroundHelper { + // Background delay serves to avoid kicking off expensive bitmap loading + // in case multiple backgrounds are set in quick succession. + private static final int SET_BACKGROUND_DELAY_MS = 100; + + private final BackgroundManager mBackgroundManager; + + class LoadBackgroundRunnable implements Runnable { + final Drawable mBackGround; + + LoadBackgroundRunnable(Drawable background) { + mBackGround = background; + } + + @Override + public void run() { + if (!mBackgroundManager.isAttached()) { + return; + } + if (mBackGround instanceof BitmapDrawable) { + mBackgroundManager.setBitmap(((BitmapDrawable) mBackGround).getBitmap()); + } + mRunnable = null; + } + } + + private LoadBackgroundRunnable mRunnable; + + private final Handler mHandler = new Handler(); + + public DetailsViewBackgroundHelper(Activity activity) { + mBackgroundManager = BackgroundManager.getInstance(activity); + mBackgroundManager.attach(activity.getWindow()); + } + + /** + * Sets the given image to background. + */ + public void setBackground(Drawable background) { + if (mRunnable != null) { + mHandler.removeCallbacks(mRunnable); + } + mRunnable = new LoadBackgroundRunnable(background); + mHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS); + } + + /** + * Sets the background color. + */ + public void setBackgroundColor(int color) { + if (mBackgroundManager.isAttached()) { + mBackgroundManager.setColor(color); + } + } + + /** + * Sets the background scrim. + */ + public void setScrim(int color) { + if (mBackgroundManager.isAttached()) { + mBackgroundManager.setDimLayer(new ColorDrawable(color)); + } + } +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java new file mode 100644 index 00000000..07eec107 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.app.Activity; +import android.content.Intent; +import android.media.tv.TvInputManager; +import android.os.Bundle; + +import com.android.tv.R; +import com.android.tv.TvApplication; + +/** + * {@link android.app.Activity} for DVR UI. + */ +public class DvrBrowseActivity extends Activity { + private DvrBrowseFragment mFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + TvApplication.setCurrentRunningProcess(this, true); + super.onCreate(savedInstanceState); + setContentView(R.layout.dvr_main); + mFragment = (DvrBrowseFragment) getFragmentManager().findFragmentById(R.id.dvr_frame); + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + if (TvInputManager.ACTION_VIEW_RECORDING_SCHEDULES.equals(intent.getAction())) { + mFragment.showScheduledRow(); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java new file mode 100644 index 00000000..cb3a5745 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.support.v17.leanback.app.BrowseFragment; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.HeaderItem; +import android.support.v17.leanback.widget.ListRow; +import android.support.v17.leanback.widget.Presenter; +import android.support.v17.leanback.widget.TitleViewAdapter; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.GenreItems; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; +import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; +import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.SortedArrayAdapter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * {@link BrowseFragment} for DVR functions. + */ +public class DvrBrowseFragment extends BrowseFragment implements + RecordedProgramListener, ScheduledRecordingListener, SeriesRecordingListener, + OnDvrScheduleLoadFinishedListener, OnRecordedProgramLoadFinishedListener { + private static final String TAG = "DvrBrowseFragment"; + private static final boolean DEBUG = false; + + private static final int MAX_RECENT_ITEM_COUNT = 10; + private static final int MAX_SCHEDULED_ITEM_COUNT = 4; + + private boolean mShouldShowScheduleRow; + private boolean mEntranceTransitionEnded; + + private RecordedProgramAdapter mRecentAdapter; + private ScheduleAdapter mScheduleAdapter; + private SeriesAdapter mSeriesAdapter; + private RecordedProgramAdapter[] mGenreAdapters = + new RecordedProgramAdapter[GenreItems.getGenreCount() + 1]; + private ListRow mRecentRow; + private ListRow mScheduledRow; + private ListRow mSeriesRow; + private ListRow[] mGenreRows = new ListRow[GenreItems.getGenreCount() + 1]; + private List mGenreLabels; + private DvrDataManager mDvrDataManager; + private DvrScheduleManager mDvrScheudleManager; + private ArrayObjectAdapter mRowsAdapter; + private ClassPresenterSelector mPresenterSelector; + private final HashMap mSeriesId2LatestProgram = new HashMap<>(); + private final Handler mHandler = new Handler(); + private final OnGlobalFocusChangeListener mOnGlobalFocusChangeListener = + new OnGlobalFocusChangeListener() { + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + if (oldFocus instanceof RecordingCardView) { + ((RecordingCardView) oldFocus).expandTitle(false, true); + } + if (newFocus instanceof RecordingCardView) { + // If the header transition is ongoing, expand cards immediately without + // animation to make a smooth transition. + ((RecordingCardView) newFocus).expandTitle(true, !isInHeadersTransition()); + } + } + }; + + private final Comparator RECORDED_PROGRAM_COMPARATOR = new Comparator() { + @Override + public int compare(Object lhs, Object rhs) { + if (lhs instanceof SeriesRecording) { + lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId()); + } + if (rhs instanceof SeriesRecording) { + rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId()); + } + if (lhs instanceof RecordedProgram) { + if (rhs instanceof RecordedProgram) { + return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed() + .compare((RecordedProgram) lhs, (RecordedProgram) rhs); + } else { + return -1; + } + } else if (rhs instanceof RecordedProgram) { + return 1; + } else { + return 0; + } + } + }; + + private static final Comparator SCHEDULE_COMPARATOR = new Comparator() { + @Override + public int compare(Object lhs, Object rhs) { + if (lhs instanceof ScheduledRecording) { + if (rhs instanceof ScheduledRecording) { + return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR + .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs); + } else { + return -1; + } + } else if (rhs instanceof ScheduledRecording) { + return 1; + } else { + return 0; + } + } + }; + + private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener = + new DvrScheduleManager.OnConflictStateChangeListener() { + @Override + public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) { + if (mScheduleAdapter != null) { + for (ScheduledRecording schedule : schedules) { + onScheduledRecordingConflictStatusChanged(schedule); + } + } + } + }; + + private final Runnable mUpdateRowsRunnable = new Runnable() { + @Override + public void run() { + updateRows(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + Context context = getContext(); + ApplicationSingletons singletons = TvApplication.getSingletons(context); + mDvrDataManager = singletons.getDvrDataManager(); + mDvrScheudleManager = singletons.getDvrScheduleManager(); + mPresenterSelector = new ClassPresenterSelector() + .addClassPresenter(ScheduledRecording.class, + new ScheduledRecordingPresenter(context)) + .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context)) + .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context)) + .addClassPresenter(FullScheduleCardHolder.class, + new FullSchedulesCardPresenter(context)); + mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context))); + mGenreLabels.add(getString(R.string.dvr_main_others)); + prepareUiElements(); + if (!startBrowseIfDvrInitialized()) { + if (!mDvrDataManager.isDvrScheduleLoadFinished()) { + mDvrDataManager.addDvrScheduleLoadFinishedListener(this); + } + if (!mDvrDataManager.isRecordedProgramLoadFinished()) { + mDvrDataManager.addRecordedProgramLoadFinishedListener(this); + } + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener); + } + + @Override + public void onDestroyView() { + getView().getViewTreeObserver() + .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener); + super.onDestroyView(); + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + mHandler.removeCallbacks(mUpdateRowsRunnable); + mDvrScheudleManager.removeOnConflictStateChangeListener(mOnConflictStateChangeListener); + mDvrDataManager.removeRecordedProgramListener(this); + mDvrDataManager.removeScheduledRecordingListener(this); + mDvrDataManager.removeSeriesRecordingListener(this); + mDvrDataManager.removeDvrScheduleLoadFinishedListener(this); + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + mRowsAdapter.clear(); + mSeriesId2LatestProgram.clear(); + for (Presenter presenter : mPresenterSelector.getPresenters()) { + if (presenter instanceof DvrItemPresenter) { + ((DvrItemPresenter) presenter).unbindAllViewHolders(); + } + } + super.onDestroy(); + } + + @Override + public void onDvrScheduleLoadFinished() { + startBrowseIfDvrInitialized(); + mDvrDataManager.removeDvrScheduleLoadFinishedListener(this); + } + + @Override + public void onRecordedProgramLoadFinished() { + startBrowseIfDvrInitialized(); + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + } + + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + handleRecordedProgramAdded(recordedProgram, true); + } + postUpdateRows(); + } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + handleRecordedProgramChanged(recordedProgram); + } + postUpdateRows(); + } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + handleRecordedProgramRemoved(recordedProgram); + } + postUpdateRows(); + } + + // No need to call updateRows() during ScheduledRecordings' change because + // the row for ScheduledRecordings is always displayed. + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording scheduleRecording : scheduledRecordings) { + if (needToShowScheduledRecording(scheduleRecording)) { + mScheduleAdapter.add(scheduleRecording); + } + } + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording scheduleRecording : scheduledRecordings) { + mScheduleAdapter.remove(scheduleRecording); + } + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording scheduleRecording : scheduledRecordings) { + if (needToShowScheduledRecording(scheduleRecording)) { + mScheduleAdapter.change(scheduleRecording); + } else { + mScheduleAdapter.removeWithId(scheduleRecording); + } + } + } + + private void onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + if (needToShowScheduledRecording(schedule)) { + if (mScheduleAdapter.contains(schedule)) { + mScheduleAdapter.change(schedule); + } + } else { + mScheduleAdapter.removeWithId(schedule); + } + } + } + + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { + handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings)); + postUpdateRows(); + } + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + handleSeriesRecordingsRemoved(Arrays.asList(seriesRecordings)); + postUpdateRows(); + } + + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + handleSeriesRecordingsChanged(Arrays.asList(seriesRecordings)); + postUpdateRows(); + } + + // Workaround of b/29108300 + @Override + public void showTitle(int flags) { + flags &= ~TitleViewAdapter.SEARCH_VIEW_VISIBLE; + super.showTitle(flags); + } + + @Override + protected void onEntranceTransitionEnd() { + super.onEntranceTransitionEnd(); + if (mShouldShowScheduleRow) { + showScheduledRowInternal(); + } + mEntranceTransitionEnded = true; + } + + void showScheduledRow() { + if (!mEntranceTransitionEnded) { + setHeadersState(HEADERS_HIDDEN); + mShouldShowScheduleRow = true; + } else { + showScheduledRowInternal(); + } + } + + private void showScheduledRowInternal() { + setSelectedPosition(mRowsAdapter.indexOf(mScheduledRow), true, null); + if (getHeadersState() == HEADERS_ENABLED) { + startHeadersTransition(false); + } + mShouldShowScheduleRow = false; + } + + private void prepareUiElements() { + setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge)); + setHeadersState(HEADERS_ENABLED); + setHeadersTransitionOnBackEnabled(false); + setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null)); + mRowsAdapter = new ArrayObjectAdapter(new DvrListRowPresenter(getContext())); + setAdapter(mRowsAdapter); + prepareEntranceTransition(); + } + + private boolean startBrowseIfDvrInitialized() { + if (mDvrDataManager.isInitialized()) { + // Setup rows + mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT); + mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT); + mSeriesAdapter = new SeriesAdapter(); + for (int i = 0; i < mGenreAdapters.length; i++) { + mGenreAdapters[i] = new RecordedProgramAdapter(); + } + // Schedule Recordings. + List schedules = mDvrDataManager.getAllScheduledRecordings(); + onScheduledRecordingAdded(ScheduledRecording.toArray(schedules)); + mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER); + // Recorded Programs. + for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) { + handleRecordedProgramAdded(recordedProgram, false); + } + // Series Recordings. Series recordings should be added after recorded programs, because + // we build series recordings' latest program information while adding recorded programs. + List recordings = mDvrDataManager.getSeriesRecordings(); + handleSeriesRecordingsAdded(recordings); + mRecentRow = new ListRow(new HeaderItem( + getString(R.string.dvr_main_recent)), mRecentAdapter); + mScheduledRow = new ListRow(new HeaderItem( + getString(R.string.dvr_main_scheduled)), mScheduleAdapter); + mSeriesRow = new ListRow(new HeaderItem( + getString(R.string.dvr_main_series)), mSeriesAdapter); + mRowsAdapter.add(mScheduledRow); + updateRows(); + // Initialize listeners + mDvrDataManager.addRecordedProgramListener(this); + mDvrDataManager.addScheduledRecordingListener(this); + mDvrDataManager.addSeriesRecordingListener(this); + mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener); + startEntranceTransition(); + return true; + } + return false; + } + + private void handleRecordedProgramAdded(RecordedProgram recordedProgram, + boolean updateSeriesRecording) { + mRecentAdapter.add(recordedProgram); + String seriesId = recordedProgram.getSeriesId(); + SeriesRecording seriesRecording = null; + if (seriesId != null) { + seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); + RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); + if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR + .compare(latestProgram, recordedProgram) < 0) { + mSeriesId2LatestProgram.put(seriesId, recordedProgram); + if (updateSeriesRecording && seriesRecording != null) { + onSeriesRecordingChanged(seriesRecording); + } + } + } + if (seriesRecording == null) { + for (RecordedProgramAdapter adapter + : getGenreAdapters(recordedProgram.getCanonicalGenres())) { + adapter.add(recordedProgram); + } + } + } + + private void handleRecordedProgramRemoved(RecordedProgram recordedProgram) { + mRecentAdapter.remove(recordedProgram); + String seriesId = recordedProgram.getSeriesId(); + if (seriesId != null) { + SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); + RecordedProgram latestProgram = + mSeriesId2LatestProgram.get(recordedProgram.getSeriesId()); + if (latestProgram != null && latestProgram.getId() == recordedProgram.getId()) { + if (seriesRecording != null) { + updateLatestRecordedProgram(seriesRecording); + onSeriesRecordingChanged(seriesRecording); + } + } + } + for (RecordedProgramAdapter adapter + : getGenreAdapters(recordedProgram.getCanonicalGenres())) { + adapter.remove(recordedProgram); + } + } + + private void handleRecordedProgramChanged(RecordedProgram recordedProgram) { + mRecentAdapter.change(recordedProgram); + String seriesId = recordedProgram.getSeriesId(); + SeriesRecording seriesRecording = null; + if (seriesId != null) { + seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); + RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); + if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR + .compare(latestProgram, recordedProgram) <= 0) { + mSeriesId2LatestProgram.put(seriesId, recordedProgram); + if (seriesRecording != null) { + onSeriesRecordingChanged(seriesRecording); + } + } else if (latestProgram.getId() == recordedProgram.getId()) { + if (seriesRecording != null) { + updateLatestRecordedProgram(seriesRecording); + onSeriesRecordingChanged(seriesRecording); + } + } + } + if (seriesRecording == null) { + updateGenreAdapters(getGenreAdapters( + recordedProgram.getCanonicalGenres()), recordedProgram); + } else { + updateGenreAdapters(new ArrayList<>(), recordedProgram); + } + } + + private void handleSeriesRecordingsAdded(List seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + mSeriesAdapter.add(seriesRecording); + if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { + for (RecordedProgramAdapter adapter + : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { + adapter.add(seriesRecording); + } + } + } + } + + private void handleSeriesRecordingsRemoved(List seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + mSeriesAdapter.remove(seriesRecording); + for (RecordedProgramAdapter adapter + : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { + adapter.remove(seriesRecording); + } + } + } + + private void handleSeriesRecordingsChanged(List seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + mSeriesAdapter.change(seriesRecording); + if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { + updateGenreAdapters(getGenreAdapters( + seriesRecording.getCanonicalGenreIds()), seriesRecording); + } else { + // Remove series recording from all genre rows if it has no recorded program + updateGenreAdapters(new ArrayList<>(), seriesRecording); + } + } + } + + private List getGenreAdapters(String[] genres) { + List result = new ArrayList<>(); + if (genres == null || genres.length == 0) { + result.add(mGenreAdapters[mGenreAdapters.length - 1]); + } else { + for (String genre : genres) { + int genreId = GenreItems.getId(genre); + if(genreId >= mGenreAdapters.length) { + Log.d(TAG, "Wrong Genre ID: " + genreId); + } else { + result.add(mGenreAdapters[genreId]); + } + } + } + return result; + } + + private List getGenreAdapters(int[] genreIds) { + List result = new ArrayList<>(); + if (genreIds == null || genreIds.length == 0) { + result.add(mGenreAdapters[mGenreAdapters.length - 1]); + } else { + for (int genreId : genreIds) { + if(genreId >= mGenreAdapters.length) { + Log.d(TAG, "Wrong Genre ID: " + genreId); + } else { + result.add(mGenreAdapters[genreId]); + } + } + } + return result; + } + + private void updateGenreAdapters(List adapters, Object r) { + for (RecordedProgramAdapter adapter : mGenreAdapters) { + if (adapters.contains(adapter)) { + adapter.change(r); + } else { + adapter.remove(r); + } + } + } + + private void postUpdateRows() { + mHandler.removeCallbacks(mUpdateRowsRunnable); + mHandler.post(mUpdateRowsRunnable); + } + + private void updateRows() { + int visibleRowsCount = 1; // Schedule's Row will never be empty + if (mRecentAdapter.isEmpty()) { + mRowsAdapter.remove(mRecentRow); + } else { + if (mRowsAdapter.indexOf(mRecentRow) < 0) { + mRowsAdapter.add(0, mRecentRow); + } + visibleRowsCount++; + } + if (mSeriesAdapter.isEmpty()) { + mRowsAdapter.remove(mSeriesRow); + } else { + if (mRowsAdapter.indexOf(mSeriesRow) < 0) { + mRowsAdapter.add(visibleRowsCount, mSeriesRow); + } + visibleRowsCount++; + } + for (int i = 0; i < mGenreAdapters.length; i++) { + RecordedProgramAdapter adapter = mGenreAdapters[i]; + if (adapter != null) { + if (adapter.isEmpty()) { + mRowsAdapter.remove(mGenreRows[i]); + } else { + if (mGenreRows[i] == null || mRowsAdapter.indexOf(mGenreRows[i]) < 0) { + mGenreRows[i] = new ListRow(new HeaderItem(mGenreLabels.get(i)), adapter); + mRowsAdapter.add(visibleRowsCount, mGenreRows[i]); + } + visibleRowsCount++; + } + } + } + } + + private boolean needToShowScheduledRecording(ScheduledRecording recording) { + int state = recording.getState(); + return state == ScheduledRecording.STATE_RECORDING_IN_PROGRESS + || state == ScheduledRecording.STATE_RECORDING_NOT_STARTED; + } + + private void updateLatestRecordedProgram(SeriesRecording seriesRecording) { + RecordedProgram latestProgram = null; + for (RecordedProgram program : + mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) { + if (latestProgram == null || RecordedProgram + .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) { + latestProgram = program; + } + } + mSeriesId2LatestProgram.put(seriesRecording.getSeriesId(), latestProgram); + } + + private class ScheduleAdapter extends SortedArrayAdapter { + ScheduleAdapter(int maxItemCount) { + super(mPresenterSelector, SCHEDULE_COMPARATOR, maxItemCount); + } + + @Override + public long getId(Object item) { + if (item instanceof ScheduledRecording) { + return ((ScheduledRecording) item).getId(); + } else { + return -1; + } + } + } + + private class SeriesAdapter extends SortedArrayAdapter { + SeriesAdapter() { + super(mPresenterSelector, new Comparator() { + @Override + public int compare(SeriesRecording lhs, SeriesRecording rhs) { + if (lhs.isStopped() && !rhs.isStopped()) { + return 1; + } else if (!lhs.isStopped() && rhs.isStopped()) { + return -1; + } + return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs); + } + }); + } + + @Override + public long getId(SeriesRecording item) { + return item.getId(); + } + } + + private class RecordedProgramAdapter extends SortedArrayAdapter { + RecordedProgramAdapter() { + this(Integer.MAX_VALUE); + } + + RecordedProgramAdapter(int maxItemCount) { + super(mPresenterSelector, RECORDED_PROGRAM_COMPARATOR, maxItemCount); + } + + @Override + public long getId(Object item) { + // We takes the inverse number for the ID of recorded programs to make the ID stable. + if (item instanceof SeriesRecording) { + return ((SeriesRecording) item).getId(); + } else if (item instanceof RecordedProgram) { + return -((RecordedProgram) item).getId() - 1; + } else { + return -1; + } + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java new file mode 100644 index 00000000..35d21db8 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v17.leanback.app.DetailsFragment; + +import android.transition.Transition; +import android.transition.Transition.TransitionListener; +import android.view.View; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dialog.PinDialogFragment; + +/** + * Activity to show details view in DVR. + */ +public class DvrDetailsActivity extends Activity implements PinDialogFragment.OnPinCheckedListener { + /** + * Name of record id added to the Intent. + */ + public static final String RECORDING_ID = "record_id"; + + /** + * Name of flag added to the Intent to determine if details view should hide "View schedule" + * button. + */ + public static final String HIDE_VIEW_SCHEDULE = "hide_view_schedule"; + + /** + * Name of details view's type added to the intent. + */ + public static final String DETAILS_VIEW_TYPE = "details_view_type"; + + /** + * Name of shared element between activities. + */ + public static final String SHARED_ELEMENT_NAME = "shared_element"; + + /** + * CURRENT_RECORDING_VIEW refers to Current Recordings in DVR. + */ + public static final int CURRENT_RECORDING_VIEW = 1; + + /** + * SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR. + */ + public static final int SCHEDULED_RECORDING_VIEW = 2; + + /** + * RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR. + */ + public static final int RECORDED_PROGRAM_VIEW = 3; + + /** + * SERIES_RECORDING_VIEW refers to series recording in DVR. + */ + public static final int SERIES_RECORDING_VIEW = 4; + + private PinDialogFragment.OnPinCheckedListener mOnPinCheckedListener; + + @Override + public void onCreate(Bundle savedInstanceState) { + TvApplication.setCurrentRunningProcess(this, true); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_dvr_details); + long recordId = getIntent().getLongExtra(RECORDING_ID, -1); + int detailsViewType = getIntent().getIntExtra(DETAILS_VIEW_TYPE, -1); + boolean hideViewSchedule = getIntent().getBooleanExtra(HIDE_VIEW_SCHEDULE, false); + if (recordId != -1 && detailsViewType != -1 && savedInstanceState == null) { + Bundle args = new Bundle(); + args.putLong(RECORDING_ID, recordId); + DetailsFragment detailsFragment = null; + if (detailsViewType == CURRENT_RECORDING_VIEW) { + detailsFragment = new CurrentRecordingDetailsFragment(); + } else if (detailsViewType == SCHEDULED_RECORDING_VIEW) { + args.putBoolean(HIDE_VIEW_SCHEDULE, hideViewSchedule); + detailsFragment = new ScheduledRecordingDetailsFragment(); + } else if (detailsViewType == RECORDED_PROGRAM_VIEW) { + detailsFragment = new RecordedProgramDetailsFragment(); + } else if (detailsViewType == SERIES_RECORDING_VIEW) { + detailsFragment = new SeriesRecordingDetailsFragment(); + } + detailsFragment.setArguments(args); + getFragmentManager().beginTransaction() + .replace(R.id.dvr_details_view_frame, detailsFragment).commit(); + } + + // This is a workaround for the focus on O device + addTransitionListener(); + } + + @Override + public void onPinChecked(boolean checked, int type, String rating) { + if (mOnPinCheckedListener != null) { + mOnPinCheckedListener.onPinChecked(checked, type, rating); + } + } + + void setOnPinCheckListener(PinDialogFragment.OnPinCheckedListener listener) { + mOnPinCheckedListener = listener; + } + + private void addTransitionListener() { + getWindow() + .getSharedElementEnterTransition() + .addListener( + new TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + // Do nothing + } + + @Override + public void onTransitionEnd(Transition transition) { + View actions = findViewById(R.id.details_overview_actions); + if (actions != null) { + actions.requestFocus(); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + // Do nothing + + } + + @Override + public void onTransitionPause(Transition transition) { + // Do nothing + } + + @Override + public void onTransitionResume(Transition transition) { + // Do nothing + } + }); + } +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java new file mode 100644 index 00000000..19fb7117 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.tv.TvContentRating; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v17.leanback.app.DetailsFragment; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.DetailsOverviewRow; +import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.PresenterSelector; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; +import android.support.v17.leanback.widget.VerticalGridView; +import android.text.TextUtils; +import android.widget.Toast; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +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.Utils; + +import java.io.File; + +abstract class DvrDetailsFragment extends DetailsFragment { + private static final int LOAD_LOGO_IMAGE = 1; + private static final int LOAD_BACKGROUND_IMAGE = 2; + + protected DetailsViewBackgroundHelper mBackgroundHelper; + private ArrayObjectAdapter mRowsAdapter; + private DetailsOverviewRow mDetailsOverview; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!onLoadRecordingDetails(getArguments())) { + getActivity().finish(); + return; + } + mBackgroundHelper = new DetailsViewBackgroundHelper(getActivity()); + setupAdapter(); + onCreateInternal(); + } + + @Override + public void onStart() { + super.onStart(); + // TODO: remove the workaround of b/30401180. + VerticalGridView container = (VerticalGridView) getActivity() + .findViewById(R.id.container_list); + // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout. + container.setItemAlignmentOffset(0); + container.setWindowAlignmentOffset( + getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top)); + } + + private void setupAdapter() { + DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter( + new DetailsContentPresenter(getActivity())); + rowPresenter.setBackgroundColor(getResources().getColor(R.color.common_tv_background, + null)); + rowPresenter.setSharedElementEnterTransition(getActivity(), + DvrDetailsActivity.SHARED_ELEMENT_NAME); + rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener()); + mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter)); + setAdapter(mRowsAdapter); + } + + /** + * Returns details views' rows adapter. + */ + protected ArrayObjectAdapter getRowsAdapter() { + return mRowsAdapter; + } + + /** + * Sets details overview. + */ + protected void setDetailsOverviewRow(DetailsContent detailsContent) { + mDetailsOverview = new DetailsOverviewRow(detailsContent); + mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); + mRowsAdapter.add(mDetailsOverview); + onLoadLogoAndBackgroundImages(detailsContent); + } + + /** + * Creates and returns presenter selector will be used by rows adaptor. + */ + protected PresenterSelector onCreatePresenterSelector( + DetailsOverviewRowPresenter rowPresenter) { + ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); + presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); + return presenterSelector; + } + + /** + * Does customized initialization of subclasses. Since {@link #onCreate(Bundle)} might finish + * activity early when it cannot fetch valid recordings, subclasses' onCreate method should not + * do anything after calling {@link #onCreate(Bundle)}. If there's something subclasses have to + * do after the super class did onCreate, it should override this method and put the codes here. + */ + protected void onCreateInternal() { } + + /** + * Updates actions of details overview. + */ + protected void updateActions() { + mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); + } + + /** + * Loads recording details according to the arguments the fragment got. + * + * @return false if cannot find valid recordings, else return true. If the return value + * is false, the detail activity and fragment will be ended. + */ + abstract boolean onLoadRecordingDetails(Bundle args); + + /** + * Creates actions users can interact with and their adaptor for this fragment. + */ + abstract SparseArrayObjectAdapter onCreateActionsAdapter(); + + /** + * Creates actions listeners to implement the behavior of the fragment after users click some + * action buttons. + */ + abstract OnActionClickedListener onCreateOnActionClickedListener(); + + /** + * Loads logo and background images for detail fragments. + */ + protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) { + Drawable logoDrawable = null; + Drawable backgroundDrawable = null; + if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) { + logoDrawable = getContext().getResources() + .getDrawable(R.drawable.dvr_default_poster, null); + mDetailsOverview.setImageDrawable(logoDrawable); + } + if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) { + backgroundDrawable = getContext().getResources() + .getDrawable(R.drawable.dvr_default_poster, null); + mBackgroundHelper.setBackground(backgroundDrawable); + } + if (logoDrawable != null && backgroundDrawable != null) { + return; + } + if (logoDrawable == null && backgroundDrawable == null + && detailsContent.getLogoImageUri().equals( + detailsContent.getBackgroundImageUri())) { + ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), + new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, + getContext())); + return; + } + if (logoDrawable == null) { + int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width); + int imageHeight = getResources() + .getDimensionPixelSize(R.dimen.dvr_details_poster_height); + ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), + imageWidth, imageHeight, + new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext())); + } + if (backgroundDrawable == null) { + ImageLoader.loadBitmap(getContext(), detailsContent.getBackgroundImageUri(), + new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext())); + } + } + + protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) { + if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) && + !isDataUriAccessible(recordedProgram.getDataUri())) { + // Since cleaning RecordedProgram from forgotten storage will take some time, + // ignore playback until cleaning is finished. + ToastUtils.show(getContext(), + getContext().getResources().getString(R.string.dvr_toast_recording_deleted), + Toast.LENGTH_SHORT); + return; + } + long programId = recordedProgram.getId(); + ParentalControlSettings parental = TvApplication.getSingletons(getActivity()) + .getTvInputManagerHelper().getParentalControlSettings(); + if (!parental.isParentalControlsEnabled()) { + DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false); + return; + } + ChannelDataManager channelDataManager = + TvApplication.getSingletons(getActivity()).getChannelDataManager(); + Channel channel = channelDataManager.getChannel(recordedProgram.getChannelId()); + if (channel != null && channel.isLocked()) { + checkPinToPlay(recordedProgram, seekTimeMs); + return; + } + TvContentRating[] ratings = recordedProgram.getContentRatings(); + TvContentRating blockRatings = parental.getBlockedRating(ratings); + if (blockRatings != null) { + checkPinToPlay(recordedProgram, seekTimeMs); + } else { + DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false); + } + } + + private boolean isDataUriAccessible(Uri dataUri) { + if (dataUri == null || dataUri.getPath() == null) { + return false; + } + try { + File recordedProgramPath = new File(dataUri.getPath()); + if (recordedProgramPath.exists()) { + return true; + } + } catch (SecurityException e) { + } + return false; + } + + private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) { + SoftPreconditions.checkState(getActivity() instanceof DvrDetailsActivity); + if (getActivity() instanceof DvrDetailsActivity) { + ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(new OnPinCheckedListener() { + @Override + public void onPinChecked(boolean checked, int type, String rating) { + ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(null); + if (checked && type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) { + DvrUiHelper.startPlaybackActivity(getContext(), recordedProgram.getId(), + seekTimeMs, true); + } + } + }); + PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) + .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG); + } + } + + private static class MyImageLoaderCallback extends + ImageLoader.ImageLoaderCallback { + private final Context mContext; + private final int mLoadType; + + public MyImageLoaderCallback(DvrDetailsFragment fragment, + int loadType, Context context) { + super(fragment); + mLoadType = loadType; + mContext = context; + } + + @Override + public void onBitmapLoaded(DvrDetailsFragment fragment, + @Nullable Bitmap bitmap) { + Drawable drawable; + int loadType = mLoadType; + if (bitmap == null) { + Resources res = mContext.getResources(); + drawable = res.getDrawable(R.drawable.dvr_default_poster, null); + if ((loadType & LOAD_BACKGROUND_IMAGE) != 0 && !fragment.isDetached()) { + loadType &= ~LOAD_BACKGROUND_IMAGE; + fragment.mBackgroundHelper.setBackgroundColor( + res.getColor(R.color.dvr_detail_default_background)); + fragment.mBackgroundHelper.setScrim( + res.getColor(R.color.dvr_detail_default_background_scrim)); + } + } else { + drawable = new BitmapDrawable(mContext.getResources(), bitmap); + } + if (!fragment.isDetached()) { + if ((loadType & LOAD_LOGO_IMAGE) != 0) { + fragment.mDetailsOverview.setImageDrawable(drawable); + } + if ((loadType & LOAD_BACKGROUND_IMAGE) != 0) { + fragment.mBackgroundHelper.setBackground(drawable); + } + } + } + } +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java new file mode 100644 index 00000000..df0e61c1 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.CallSuper; +import android.support.v17.leanback.widget.Presenter; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; + +import com.android.tv.common.SoftPreconditions; +import com.android.tv.dvr.ui.DvrUiHelper; + +import java.util.HashSet; +import java.util.Set; + +/** + * An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in + * {@link DvrBrowseFragment}. DVR items might include: + * {@link com.android.tv.dvr.data.ScheduledRecording}, + * {@link com.android.tv.dvr.data.RecordedProgram}, and + * {@link com.android.tv.dvr.data.SeriesRecording}. + */ +public abstract class DvrItemPresenter extends Presenter { + protected final Context mContext; + private final Set mBoundViewHolders = new HashSet<>(); + private final OnClickListener mOnClickListener = onCreateOnClickListener(); + + protected class DvrItemViewHolder extends ViewHolder { + DvrItemViewHolder(RecordingCardView view) { + super(view); + } + + protected RecordingCardView getView() { + return (RecordingCardView) view; + } + + protected void onBound(T item) { } + + protected void onUnbound() { } + } + + DvrItemPresenter(Context context) { + mContext = context; + } + + @Override + public final ViewHolder onCreateViewHolder(ViewGroup parent) { + return onCreateDvrItemViewHolder(); + } + + @Override + public final void onBindViewHolder(ViewHolder baseHolder, Object item) { + DvrItemViewHolder viewHolder; + T dvrItem; + try { + viewHolder = (DvrItemViewHolder) baseHolder; + Class itemType = (Class) item.getClass(); + dvrItem = itemType.cast(item); + } catch (ClassCastException e) { + SoftPreconditions.checkState(false); + return; + } + viewHolder.view.setTag(item); + viewHolder.view.setOnClickListener(mOnClickListener); + onBindDvrItemViewHolder(viewHolder, dvrItem); + viewHolder.onBound(dvrItem); + mBoundViewHolders.add(viewHolder); + } + + @Override + @CallSuper + public void onUnbindViewHolder(ViewHolder baseHolder) { + DvrItemViewHolder viewHolder = (DvrItemViewHolder) baseHolder; + mBoundViewHolders.remove(viewHolder); + viewHolder.onUnbound(); + viewHolder.view.setTag(null); + viewHolder.view.setOnClickListener(null); + } + + /** + * Unbinds all bound view holders. + */ + public void unbindAllViewHolders() { + // When browse fragments are destroyed, RecyclerView would not call presenters' + // onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks. + for (ViewHolder viewHolder : new HashSet<>(mBoundViewHolders)) { + onUnbindViewHolder(viewHolder); + } + } + + /** + * This method will be called when a {@link DvrItemViewHolder} is needed to be created. + */ + abstract protected DvrItemViewHolder onCreateDvrItemViewHolder(); + + /** + * This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item. + */ + abstract protected void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item); + + /** + * Returns context. + */ + protected Context getContext() { + return mContext; + } + + /** + * Creates {@link OnClickListener} for DVR library's card views. + */ + protected OnClickListener onCreateOnClickListener() { + return new OnClickListener() { + @Override + public void onClick(View view) { + if (view instanceof RecordingCardView) { + RecordingCardView v = (RecordingCardView) view; + DvrUiHelper.startDetailsActivity((Activity) v.getContext(), + v.getTag(), v.getImageView(), false); + } + } + }; + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java new file mode 100644 index 00000000..37a72eaf --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.support.v17.leanback.widget.ListRowPresenter; +import android.view.ViewGroup; + +import com.android.tv.R; + +/** A list row presenter to display expand/fold card views list. */ +public class DvrListRowPresenter extends ListRowPresenter { + public DvrListRowPresenter(Context context) { + super(); + setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + setExpandedRowHeight( + context.getResources() + .getDimensionPixelSize(R.dimen.dvr_library_expanded_row_height)); + } +} diff --git a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java new file mode 100644 index 00000000..311137a9 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +/** + * Special object for schedule preview; + */ +final class FullScheduleCardHolder { + /** + * Full schedule card holder. + */ + static final FullScheduleCardHolder FULL_SCHEDULE_CARD_HOLDER = new FullScheduleCardHolder(); + + private FullScheduleCardHolder() { } +} diff --git a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java new file mode 100644 index 00000000..94c67eec --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.DvrUiHelper; +import com.android.tv.util.Utils; + +import java.util.Collections; +import java.util.List; + +/** + * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. + */ +class FullSchedulesCardPresenter extends DvrItemPresenter { + private final Drawable mIconDrawable; + private final String mCardTitleText; + + FullSchedulesCardPresenter(Context context) { + super(context); + mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule); + mCardTitleText = mContext.getString(R.string.dvr_full_schedule_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); + List scheduledRecordings = TvApplication.getSingletons(mContext) + .getDvrDataManager().getAvailableScheduledRecordings(); + int fullDays = 0; + if (!scheduledRecordings.isEmpty()) { + fullDays = Utils.computeDateDifference(System.currentTimeMillis(), + Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR) + .getStartTimeMs()) + 1; + } + cardView.setContent(mContext.getResources().getQuantityString( + R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null); + } + + @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.startSchedulesActivity(mContext, null); + } + }; + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java new file mode 100644 index 00000000..eb9cb26c --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.res.Resources; +import android.media.tv.TvInputManager; +import android.os.Bundle; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.data.RecordedProgram; + +/** + * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR. + */ +public class RecordedProgramDetailsFragment extends DvrDetailsFragment + implements DvrDataManager.RecordedProgramListener { + private static final int ACTION_RESUME_PLAYING = 1; + private static final int ACTION_PLAY_FROM_BEGINNING = 2; + private static final int ACTION_DELETE_RECORDING = 3; + + private DvrWatchedPositionManager mDvrWatchedPositionManager; + + private RecordedProgram mRecordedProgram; + private boolean mPaused; + private DvrDataManager mDvrDataManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager(); + mDvrDataManager.addRecordedProgramListener(this); + super.onCreate(savedInstanceState); + } + + @Override + public void onCreateInternal() { + mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) + .getDvrWatchedPositionManager(); + setDetailsOverviewRow(DetailsContent + .createFromRecordedProgram(getContext(), mRecordedProgram)); + } + + @Override + public void onResume() { + super.onResume(); + if (mPaused) { + updateActions(); + mPaused = false; + } + } + + @Override + public void onPause() { + super.onPause(); + mPaused = true; + } + + @Override + public void onDestroy() { + mDvrDataManager.removeRecordedProgramListener(this); + super.onDestroy(); + } + + @Override + protected boolean onLoadRecordingDetails(Bundle args) { + long recordedProgramId = args.getLong(DvrDetailsActivity.RECORDING_ID); + mRecordedProgram = mDvrDataManager.getRecordedProgram(recordedProgramId); + return mRecordedProgram != null; + } + + @Override + protected SparseArrayObjectAdapter onCreateActionsAdapter() { + SparseArrayObjectAdapter adapter = + new SparseArrayObjectAdapter(new ActionPresenterSelector()); + Resources res = getResources(); + if (mDvrWatchedPositionManager.getWatchedStatus(mRecordedProgram) + == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { + adapter.set(ACTION_RESUME_PLAYING, new Action(ACTION_RESUME_PLAYING, + res.getString(R.string.dvr_detail_resume_play), null, + res.getDrawable(R.drawable.lb_ic_play))); + adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, + res.getString(R.string.dvr_detail_play_from_beginning), null, + res.getDrawable(R.drawable.lb_ic_replay))); + } else { + adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, + res.getString(R.string.dvr_detail_watch), null, + res.getDrawable(R.drawable.lb_ic_play))); + } + adapter.set(ACTION_DELETE_RECORDING, new Action(ACTION_DELETE_RECORDING, + res.getString(R.string.dvr_detail_delete), null, + res.getDrawable(R.drawable.ic_delete_32dp))); + return adapter; + } + + @Override + protected OnActionClickedListener onCreateOnActionClickedListener() { + return new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + if (action.getId() == ACTION_PLAY_FROM_BEGINNING) { + startPlayback(mRecordedProgram, TvInputManager.TIME_SHIFT_INVALID_TIME); + } else if (action.getId() == ACTION_RESUME_PLAYING) { + startPlayback(mRecordedProgram, mDvrWatchedPositionManager + .getWatchedPosition(mRecordedProgram.getId())); + } else if (action.getId() == ACTION_DELETE_RECORDING) { + DvrManager dvrManager = TvApplication + .getSingletons(getActivity()).getDvrManager(); + dvrManager.removeRecordedProgram(mRecordedProgram); + getActivity().finish(); + } + } + }; + } + + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (recordedProgram.getId() == mRecordedProgram.getId()) { + getActivity().finish(); + } + } + } +} diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java new file mode 100644 index 00000000..5fe162b6 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.media.tv.TvInputManager; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.util.Utils; + +/** + * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. + */ +public class RecordedProgramPresenter extends DvrItemPresenter { + private final DvrWatchedPositionManager mDvrWatchedPositionManager; + private String mTodayString; + private String mYesterdayString; + private final int mProgressBarColor; + private final boolean mShowEpisodeTitle; + private final boolean mExpandTitleWhenFocused; + + protected final class RecordedProgramViewHolder extends DvrItemViewHolder + implements WatchedPositionChangedListener { + private RecordedProgram mProgram; + private boolean mShowProgress; + + public RecordedProgramViewHolder(RecordingCardView view, Integer progressColor) { + super(view); + if (progressColor == null) { + mShowProgress = false; + } else { + mShowProgress = true; + view.setProgressBarColor(progressColor); + } + } + + private void setProgressBar(long watchedPositionMs) { + ((RecordingCardView) view).setProgressBar( + (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null + : Math.min(100, (int) (100.0f * watchedPositionMs + / mProgram.getDurationMillis()))); + } + + @Override + public void onWatchedPositionChanged(long programId, long positionMs) { + if (programId == mProgram.getId()) { + setProgressBar(positionMs); + } + } + + @Override + protected void onBound(RecordedProgram program) { + mProgram = program; + if (mShowProgress) { + mDvrWatchedPositionManager.addListener(this, program.getId()); + setProgressBar(mDvrWatchedPositionManager.getWatchedPosition(program.getId())); + } else { + getView().setProgressBar(null); + } + } + + @Override + protected void onUnbound() { + if (mShowProgress) { + mDvrWatchedPositionManager.removeListener(this, mProgram.getId()); + } + getView().reset(); + } + } + + RecordedProgramPresenter(Context context, boolean showEpisodeTitle, + boolean expandTitleWhenFocused) { + super(context); + mTodayString = mContext.getString(R.string.dvr_date_today); + mYesterdayString = mContext.getString(R.string.dvr_date_yesterday); + mDvrWatchedPositionManager = + TvApplication.getSingletons(mContext).getDvrWatchedPositionManager(); + mProgressBarColor = mContext.getResources() + .getColor(R.color.play_controls_progress_bar_watched); + mShowEpisodeTitle = showEpisodeTitle; + mExpandTitleWhenFocused = expandTitleWhenFocused; + } + + public RecordedProgramPresenter(Context context) { + this(context, false, false); + } + + @Override + public DvrItemViewHolder onCreateDvrItemViewHolder() { + return new RecordedProgramViewHolder( + new RecordingCardView(mContext, mExpandTitleWhenFocused), mProgressBarColor); + } + + @Override + public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, RecordedProgram program) { + final RecordedProgramViewHolder viewHolder = (RecordedProgramViewHolder) baseHolder; + final RecordingCardView cardView = viewHolder.getView(); + DetailsContent details = DetailsContent.createFromRecordedProgram(mContext, program); + cardView.setTitle(mShowEpisodeTitle ? + program.getEpisodeDisplayTitle(mContext) : details.getTitle()); + cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo()); + cardView.setContent(generateMajorContent(program), generateMinorContent(program)); + cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri()); + } + + private String generateMajorContent(RecordedProgram program) { + int dateDifference = Utils.computeDateDifference(program.getStartTimeUtcMillis(), + System.currentTimeMillis()); + if (dateDifference == 0) { + return mTodayString; + } else if (dateDifference == 1) { + return mYesterdayString; + } else { + return Utils.getDurationString(mContext, program.getStartTimeUtcMillis(), + program.getStartTimeUtcMillis(), false, true, false, 0); + } + } + + private String generateMinorContent(RecordedProgram program) { + int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis())); + return mContext.getResources().getQuantityString( + R.plurals.dvr_program_duration, durationMinutes, durationMinutes); + } +} diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java new file mode 100644 index 00000000..767addc8 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v17.leanback.widget.BaseCardView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +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; + +/** + * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} + * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. + */ +public class RecordingCardView extends BaseCardView { + // This value should be the same with + // android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS + private final static int ANIMATION_DURATION = 150; + private final ImageView mImageView; + private final int mImageWidth; + private final int mImageHeight; + private String mImageUri; + private final TextView mMajorContentView; + private final TextView mMinorContentView; + private final ProgressBar mProgressBar; + private final View mAffiliatedIconContainer; + private final ImageView mAffiliatedIcon; + private final Drawable mDefaultImage; + private final FrameLayout mTitleArea; + private final TextView mFoldedTitleView; + private final TextView mExpandedTitleView; + private final ValueAnimator mExpandTitleAnimator; + private final int mFoldedTitleHeight; + private final int mExpandedTitleHeight; + private final boolean mExpandTitleWhenFocused; + private boolean mExpanded; + private String mDetailBackgroundImageUri; + + public RecordingCardView(Context context) { + this(context, false); + } + + public RecordingCardView(Context context, boolean expandTitleWhenFocused) { + this(context, context.getResources().getDimensionPixelSize( + R.dimen.dvr_library_card_image_layout_width), context.getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height), + expandTitleWhenFocused); + } + + public RecordingCardView(Context context, int imageWidth, int imageHeight, + boolean expandTitleWhenFocused) { + super(context); + //TODO(dvr): move these to the layout XML. + setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA); + setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS); + setFocusable(true); + setFocusableInTouchMode(true); + mDefaultImage = getResources().getDrawable(R.drawable.dvr_default_poster, null); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.dvr_recording_card_view, this); + mImageView = (ImageView) findViewById(R.id.image); + mImageWidth = imageWidth; + mImageHeight = imageHeight; + mProgressBar = (ProgressBar) findViewById(R.id.recording_progress); + mAffiliatedIconContainer = findViewById(R.id.affiliated_icon_container); + mAffiliatedIcon = (ImageView) findViewById(R.id.affiliated_icon); + mMajorContentView = (TextView) findViewById(R.id.content_major); + mMinorContentView = (TextView) findViewById(R.id.content_minor); + mTitleArea = (FrameLayout) findViewById(R.id.title_area); + mFoldedTitleView = (TextView) findViewById(R.id.title_one_line); + mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines); + mFoldedTitleHeight = getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height); + mExpandedTitleHeight = getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height); + mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION); + mExpandTitleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + float value = (Float) valueAnimator.getAnimatedValue(); + mExpandedTitleView.setAlpha(value); + mFoldedTitleView.setAlpha(1.0f - value); + ViewUtils.setLayoutHeight(mTitleArea, (int) (mFoldedTitleHeight + + (mExpandedTitleHeight - mFoldedTitleHeight) * value)); + } + }); + mExpandTitleWhenFocused = expandTitleWhenFocused; + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + // Preload the background image going to be used in detail fragments here to prevent + // loading and drawing background images during activity transitions. + if (gainFocus) { + if (!TextUtils.isEmpty(mDetailBackgroundImageUri)) { + ImageLoader.loadBitmap(getContext(), mDetailBackgroundImageUri, + Integer.MAX_VALUE, Integer.MAX_VALUE, null); + } + } + if (mExpandTitleWhenFocused) { + if (gainFocus) { + expandTitle(true, true); + } else { + expandTitle(false, true); + } + } + } + + /** + * Expands/folds the title area to show program title with two/one lines. + * + * @param expand {@code true} to expand the title area, or {@code false} to fold it. + * @param withAnimation {@code true} to expand/fold with animation. + */ + public void expandTitle(boolean expand, boolean withAnimation) { + if (expand != mExpanded && mFoldedTitleView.getLayout().getEllipsisCount(0) > 0) { + if (withAnimation) { + if (expand) { + mExpandTitleAnimator.start(); + } else { + mExpandTitleAnimator.reverse(); + } + } else { + if (expand) { + mFoldedTitleView.setAlpha(0.0f); + mExpandedTitleView.setAlpha(1.0f); + ViewUtils.setLayoutHeight(mTitleArea, mExpandedTitleHeight); + } else { + mFoldedTitleView.setAlpha(1.0f); + mExpandedTitleView.setAlpha(0.0f); + ViewUtils.setLayoutHeight(mTitleArea, mFoldedTitleHeight); + } + } + mExpanded = expand; + } + } + + void setTitle(CharSequence title) { + mFoldedTitleView.setText(title); + mExpandedTitleView.setText(title); + } + + void setContent(CharSequence majorContent, CharSequence minorContent) { + if (!TextUtils.isEmpty(majorContent)) { + mMajorContentView.setText(majorContent); + mMajorContentView.setVisibility(View.VISIBLE); + } else { + mMajorContentView.setVisibility(View.GONE); + } + if (!TextUtils.isEmpty(minorContent)) { + mMinorContentView.setText(minorContent); + mMinorContentView.setVisibility(View.VISIBLE); + } else { + mMinorContentView.setVisibility(View.GONE); + } + } + + /** + * Sets progress bar. If progress is {@code null}, hides progress bar. + */ + void setProgressBar(Integer progress) { + if (progress == null) { + mProgressBar.setVisibility(View.GONE); + } else { + mProgressBar.setProgress(progress); + mProgressBar.setVisibility(View.VISIBLE); + } + } + + /** + * Sets the color of progress bar. + */ + void setProgressBarColor(int color) { + mProgressBar.getProgressDrawable().setTint(color); + } + + /** + * Sets the image URI of the poster should be shown on the card view. + + * @param isChannelLogo {@code true} if the image is from channels' logo. + */ + void setImageUri(String uri, boolean isChannelLogo) { + if (isChannelLogo) { + mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + } else { + mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + } + mImageUri = uri; + if (TextUtils.isEmpty(uri)) { + mImageView.setImageDrawable(mDefaultImage); + } else { + ImageLoader.loadBitmap(getContext(), uri, mImageWidth, mImageHeight, + new RecordingCardImageLoaderCallback(this, uri)); + } + } + + /** + * Sets the {@link Drawable} of the poster should be shown on the card view. + */ + public void setImage(Drawable image) { + if (image != null) { + mImageView.setImageDrawable(image); + } + } + + /** + * Sets the affiliated icon of the card view, which will be displayed at the lower-right corner + * of the poster. + */ + public void setAffiliatedIcon(int imageResId) { + if (imageResId > 0) { + mAffiliatedIconContainer.setVisibility(View.VISIBLE); + mAffiliatedIcon.setImageResource(imageResId); + } else { + mAffiliatedIconContainer.setVisibility(View.INVISIBLE); + } + } + + /** + * Sets the background image URI of the card view, which will be displayed as background when + * the view is clicked and shows its details fragment. + */ + public void setDetailBackgroundImageUri(String uri) { + mDetailBackgroundImageUri = uri; + } + + /** + * Returns image view. + */ + public ImageView getImageView() { + return mImageView; + } + + private static class RecordingCardImageLoaderCallback + extends ImageLoader.ImageLoaderCallback { + private final String mUri; + + RecordingCardImageLoaderCallback(RecordingCardView referent, String uri) { + super(referent); + mUri = uri; + } + + @Override + public void onBitmapLoaded(RecordingCardView view, @Nullable Bitmap bitmap) { + if (bitmap == null || !mUri.equals(view.mImageUri)) { + view.mImageView.setImageDrawable(view.mDefaultImage); + } else { + view.mImageView.setImageDrawable(new BitmapDrawable(view.getResources(), bitmap)); + } + } + } + + public void reset() { + mFoldedTitleView.setText(null); + mExpandedTitleView.setText(null); + setContent(null, null); + mImageView.setImageDrawable(mDefaultImage); + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java new file mode 100644 index 00000000..56ec357f --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.os.Bundle; +import android.support.v17.leanback.app.DetailsFragment; + +import com.android.tv.TvApplication; +import com.android.tv.dvr.data.ScheduledRecording; + +/** + * {@link DetailsFragment} for recordings in DVR. + */ +abstract class RecordingDetailsFragment extends DvrDetailsFragment { + private ScheduledRecording mRecording; + + @Override + protected void onCreateInternal() { + setDetailsOverviewRow(DetailsContent + .createFromScheduledRecording(getContext(), mRecording)); + } + + @Override + protected boolean onLoadRecordingDetails(Bundle args) { + long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID); + mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager() + .getScheduledRecording(scheduledRecordingId); + return mRecording != null; + } + + /** + * Returns {@link ScheduledRecording} for the current fragment. + */ + public ScheduledRecording getRecording() { + return mRecording; + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java new file mode 100644 index 00000000..958f8bf8 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.res.Resources; +import android.os.Bundle; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; +import android.text.TextUtils; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.ui.DvrUiHelper; + +/** + * {@link RecordingDetailsFragment} for scheduled recording in DVR. + */ +public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment { + private static final int ACTION_VIEW_SCHEDULE = 1; + private static final int ACTION_CANCEL = 2; + + private DvrManager mDvrManager; + private Action mScheduleAction; + private boolean mHideViewSchedule; + + @Override + public void onCreate(Bundle savedInstance) { + mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + mHideViewSchedule = getArguments().getBoolean(DvrDetailsActivity.HIDE_VIEW_SCHEDULE); + super.onCreate(savedInstance); + } + + @Override + public void onResume() { + super.onResume(); + if (mScheduleAction != null) { + mScheduleAction.setIcon(getResources().getDrawable(getScheduleIconId())); + } + } + + @Override + protected SparseArrayObjectAdapter onCreateActionsAdapter() { + SparseArrayObjectAdapter adapter = + new SparseArrayObjectAdapter(new ActionPresenterSelector()); + Resources res = getResources(); + if (!mHideViewSchedule) { + mScheduleAction = new Action(ACTION_VIEW_SCHEDULE, + res.getString(R.string.dvr_detail_view_schedule), null, + res.getDrawable(getScheduleIconId())); + adapter.set(ACTION_VIEW_SCHEDULE, mScheduleAction); + } + adapter.set(ACTION_CANCEL, new Action(ACTION_CANCEL, + res.getString(R.string.dvr_detail_cancel_recording), null, + res.getDrawable(R.drawable.ic_dvr_cancel_32dp))); + return adapter; + } + + @Override + protected OnActionClickedListener onCreateOnActionClickedListener() { + return new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + long actionId = action.getId(); + if (actionId == ACTION_VIEW_SCHEDULE) { + DvrUiHelper.startSchedulesActivity(getContext(), getRecording()); + } else if (actionId == ACTION_CANCEL) { + mDvrManager.removeScheduledRecording(getRecording()); + getActivity().finish(); + } + } + }; + } + + private int getScheduleIconId() { + if (mDvrManager.isConflicting(getRecording())) { + return R.drawable.ic_warning_white_32dp; + } else { + return R.drawable.ic_schedule_32dp; + } + } +} diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java new file mode 100644 index 00000000..273d3d19 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.os.Handler; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.util.Utils; + +import java.util.concurrent.TimeUnit; + +/** + * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. + */ +class ScheduledRecordingPresenter extends DvrItemPresenter { + private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5); + + private final DvrManager mDvrManager; + private final int mProgressBarColor; + + private final class ScheduledRecordingViewHolder extends DvrItemViewHolder { + private final Handler mHandler = new Handler(); + private ScheduledRecording mScheduledRecording; + private final Runnable mProgressBarUpdater = new Runnable() { + @Override + public void run() { + updateProgressBar(); + mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS); + } + }; + + ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) { + super(view); + view.setProgressBarColor(progressBarColor); + } + + @Override + protected void onBound(ScheduledRecording recording) { + mScheduledRecording = recording; + updateProgressBar(); + startUpdateProgressBar(); + } + + @Override + protected void onUnbound() { + stopUpdateProgressBar(); + mScheduledRecording = null; + getView().reset(); + } + + private void updateProgressBar() { + if (mScheduledRecording == null) { + return; + } + int recordingState = mScheduledRecording.getState(); + RecordingCardView cardView = (RecordingCardView) view; + if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { + cardView.setProgressBar(Math.max(0, Math.min((int) (100 * + (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs()) + / mScheduledRecording.getDuration()), 100))); + } else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) { + cardView.setProgressBar(100); + } else { + // Hides progress bar. + cardView.setProgressBar(null); + } + } + + private void startUpdateProgressBar() { + mHandler.post(mProgressBarUpdater); + } + + private void stopUpdateProgressBar() { + mHandler.removeCallbacks(mProgressBarUpdater); + } + } + + public ScheduledRecordingPresenter(Context context) { + super(context); + mDvrManager = TvApplication.getSingletons(mContext).getDvrManager(); + mProgressBarColor = mContext.getResources() + .getColor(R.color.play_controls_recording_icon_color_on_focus); + } + + @Override + public DvrItemViewHolder onCreateDvrItemViewHolder() { + return new ScheduledRecordingViewHolder(new RecordingCardView(mContext), mProgressBarColor); + } + + @Override + public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, + ScheduledRecording recording) { + final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; + final RecordingCardView cardView = viewHolder.getView(); + DetailsContent details = DetailsContent.createFromScheduledRecording(mContext, recording); + cardView.setTitle(details.getTitle()); + cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo()); + cardView.setAffiliatedIcon(mDvrManager.isConflicting(recording) ? + R.drawable.ic_warning_white_32dp : 0); + cardView.setContent(generateMajorContent(recording), null); + cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri()); + } + + private String generateMajorContent(ScheduledRecording recording) { + int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(), + recording.getStartTimeMs()); + if (dateDifference <= 0) { + return mContext.getString(R.string.dvr_date_today_time, + Utils.getDurationString(mContext, recording.getStartTimeMs(), + recording.getEndTimeMs(), false, false, true, 0)); + } else if (dateDifference == 1) { + return mContext.getString(R.string.dvr_date_tomorrow_time, + Utils.getDurationString(mContext, recording.getStartTimeMs(), + recording.getEndTimeMs(), false, false, true, 0)); + } else { + return Utils.getDurationString(mContext, recording.getStartTimeMs(), + recording.getStartTimeMs(), false, true, false, 0); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java new file mode 100644 index 00000000..c2aa8e98 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.media.tv.TvInputManager; +import android.os.Bundle; +import android.support.v17.leanback.app.DetailsFragment; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.DetailsOverviewRow; +import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; +import android.support.v17.leanback.widget.HeaderItem; +import android.support.v17.leanback.widget.ListRow; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.PresenterSelector; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; +import android.text.TextUtils; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.BaseProgram; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.DvrUiHelper; +import com.android.tv.dvr.ui.SortedArrayAdapter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * {@link DetailsFragment} for series recording in DVR. + */ +public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implements + DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener { + private static final int ACTION_WATCH = 1; + private static final int ACTION_SERIES_SCHEDULES = 2; + private static final int ACTION_DELETE = 3; + + private DvrWatchedPositionManager mDvrWatchedPositionManager; + private DvrDataManager mDvrDataManager; + + private SeriesRecording mSeries; + // NOTICE: mRecordedPrograms should only be used in creating details fragments. + // After fragments are created, it should be cleared to save resources. + private List mRecordedPrograms; + private RecordedProgram mRecommendRecordedProgram; + private int mSeasonRowCount; + private SparseArrayObjectAdapter mActionsAdapter; + private Action mDeleteAction; + + private boolean mPaused; + private long mInitialPlaybackPositionMs; + private String mWatchLabel; + private String mResumeLabel; + private Drawable mWatchDrawable; + private RecordedProgramPresenter mRecordedProgramPresenter; + + @Override + public void onCreate(Bundle savedInstanceState) { + mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); + mWatchLabel = getString(R.string.dvr_detail_watch); + mResumeLabel = getString(R.string.dvr_detail_series_resume); + mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null); + mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true, true); + super.onCreate(savedInstanceState); + } + + @Override + protected void onCreateInternal() { + mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) + .getDvrWatchedPositionManager(); + setDetailsOverviewRow(DetailsContent.createFromSeriesRecording(getContext(), mSeries)); + setupRecordedProgramsRow(); + mDvrDataManager.addSeriesRecordingListener(this); + mDvrDataManager.addRecordedProgramListener(this); + mRecordedPrograms = null; + } + + @Override + public void onResume() { + super.onResume(); + if (mPaused) { + updateWatchAction(); + mPaused = false; + } + } + + @Override + public void onPause() { + super.onPause(); + mPaused = true; + } + + private void updateWatchAction() { + List programs = mDvrDataManager.getRecordedPrograms(mSeries.getId()); + Collections.sort(programs, RecordedProgram.EPISODE_COMPARATOR); + mRecommendRecordedProgram = getRecommendProgram(programs); + if (mRecommendRecordedProgram == null) { + mActionsAdapter.clear(ACTION_WATCH); + } else { + String episodeStatus; + if(mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram) + == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { + episodeStatus = mResumeLabel; + mInitialPlaybackPositionMs = mDvrWatchedPositionManager + .getWatchedPosition(mRecommendRecordedProgram.getId()); + } else { + episodeStatus = mWatchLabel; + mInitialPlaybackPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + } + String episodeDisplayNumber = mRecommendRecordedProgram.getEpisodeDisplayNumber( + getContext()); + mActionsAdapter.set(ACTION_WATCH, new Action(ACTION_WATCH, + episodeStatus, episodeDisplayNumber, mWatchDrawable)); + } + } + + @Override + protected boolean onLoadRecordingDetails(Bundle args) { + long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID); + mSeries = TvApplication.getSingletons(getActivity()).getDvrDataManager() + .getSeriesRecording(recordId); + if (mSeries == null) { + return false; + } + mRecordedPrograms = mDvrDataManager.getRecordedPrograms(mSeries.getId()); + Collections.sort(mRecordedPrograms, RecordedProgram.SEASON_REVERSED_EPISODE_COMPARATOR); + return true; + } + + @Override + protected PresenterSelector onCreatePresenterSelector( + DetailsOverviewRowPresenter rowPresenter) { + ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); + presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); + presenterSelector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext())); + return presenterSelector; + } + + @Override + protected SparseArrayObjectAdapter onCreateActionsAdapter() { + mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); + Resources res = getResources(); + updateWatchAction(); + mActionsAdapter.set(ACTION_SERIES_SCHEDULES, new Action(ACTION_SERIES_SCHEDULES, + getString(R.string.dvr_detail_view_schedule), null, + res.getDrawable(R.drawable.ic_schedule_32dp, null))); + mDeleteAction = new Action(ACTION_DELETE, + getString(R.string.dvr_detail_series_delete), null, + res.getDrawable(R.drawable.ic_delete_32dp, null)); + if (!mRecordedPrograms.isEmpty()) { + mActionsAdapter.set(ACTION_DELETE, mDeleteAction); + } + return mActionsAdapter; + } + + private void setupRecordedProgramsRow() { + for (RecordedProgram program : mRecordedPrograms) { + addProgram(program); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + mDvrDataManager.removeSeriesRecordingListener(this); + mDvrDataManager.removeRecordedProgramListener(this); + if (mSeries != null) { + mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeries.getId()); + } + mRecordedProgramPresenter.unbindAllViewHolders(); + } + + @Override + protected OnActionClickedListener onCreateOnActionClickedListener() { + return new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + if (action.getId() == ACTION_WATCH) { + startPlayback(mRecommendRecordedProgram, mInitialPlaybackPositionMs); + } else if (action.getId() == ACTION_SERIES_SCHEDULES) { + DvrUiHelper.startSchedulesActivityForSeries(getContext(), mSeries); + } else if (action.getId() == ACTION_DELETE) { + DvrUiHelper.startSeriesDeletionActivity(getContext(), mSeries.getId()); + } + } + }; + } + + /** + * The programs are sorted by season number and episode number. + */ + private RecordedProgram getRecommendProgram(List programs) { + for (int i = programs.size() - 1 ; i >= 0 ; i--) { + RecordedProgram program = programs.get(i); + int watchedStatus = mDvrWatchedPositionManager.getWatchedStatus(program); + if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_NEW) { + continue; + } + if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { + return program; + } + if (i == programs.size() - 1) { + return program; + } else { + return programs.get(i + 1); + } + } + return programs.isEmpty() ? null : programs.get(0); + } + + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + for (SeriesRecording series : seriesRecordings) { + if (mSeries.getId() == series.getId()) { + mSeries = series; + } + } + } + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + for (SeriesRecording series : seriesRecordings) { + if (series.getId() == mSeries.getId()) { + getActivity().finish(); + return; + } + } + } + + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { + addProgram(recordedProgram); + if (mActionsAdapter.lookup(ACTION_DELETE) == null) { + mActionsAdapter.set(ACTION_DELETE, mDeleteAction); + } + } + } + } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + // Do nothing + } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { + ListRow row = getSeasonRow(recordedProgram.getSeasonNumber(), false); + if (row != null) { + SeasonRowAdapter adapter = (SeasonRowAdapter) row.getAdapter(); + adapter.remove(recordedProgram); + if (adapter.isEmpty()) { + getRowsAdapter().remove(row); + if (getRowsAdapter().size() == 1) { + // No season rows left. Only DetailsOverviewRow + mActionsAdapter.clear(ACTION_DELETE); + } + } + } + if (recordedProgram.getId() == mRecommendRecordedProgram.getId()) { + updateWatchAction(); + } + } + } + } + + private void addProgram(RecordedProgram program) { + String programSeasonNumber = + TextUtils.isEmpty(program.getSeasonNumber()) ? "" : program.getSeasonNumber(); + getOrCreateSeasonRowAdapter(programSeasonNumber).add(program); + } + + private SeasonRowAdapter getOrCreateSeasonRowAdapter(String seasonNumber) { + ListRow row = getSeasonRow(seasonNumber, true); + return (SeasonRowAdapter) row.getAdapter(); + } + + private ListRow getSeasonRow(String seasonNumber, boolean createNewRow) { + seasonNumber = TextUtils.isEmpty(seasonNumber) ? "" : seasonNumber; + ArrayObjectAdapter rowsAdaptor = getRowsAdapter(); + for (int i = rowsAdaptor.size() - 1; i >= 0; i--) { + Object row = rowsAdaptor.get(i); + if (row instanceof ListRow) { + int compareResult = BaseProgram.numberCompare(seasonNumber, + ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber); + if (compareResult == 0) { + return (ListRow) row; + } else if (compareResult < 0) { + return createNewRow ? createNewSeasonRow(seasonNumber, i + 1) : null; + } + } + } + return createNewRow ? createNewSeasonRow(seasonNumber, rowsAdaptor.size()) : null; + } + + private ListRow createNewSeasonRow(String seasonNumber, int position) { + String seasonTitle = seasonNumber.isEmpty() ? mSeries.getTitle() + : getString(R.string.dvr_detail_series_season_title, seasonNumber); + HeaderItem header = new HeaderItem(mSeasonRowCount++, seasonTitle); + ClassPresenterSelector selector = new ClassPresenterSelector(); + selector.addClassPresenter(RecordedProgram.class, mRecordedProgramPresenter); + ListRow row = new ListRow(header, new SeasonRowAdapter(selector, + new Comparator() { + @Override + public int compare(RecordedProgram lhs, RecordedProgram rhs) { + return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs); + } + }, seasonNumber)); + getRowsAdapter().add(position, row); + return row; + } + + private class SeasonRowAdapter extends SortedArrayAdapter { + private String mSeasonNumber; + + SeasonRowAdapter(PresenterSelector selector, Comparator comparator, + String seasonNumber) { + super(selector, comparator); + mSeasonNumber = seasonNumber; + } + + @Override + public long getId(RecordedProgram program) { + return program.getId(); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java new file mode 100644 index 00000000..e508259d --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.media.tv.TvInputManager; +import android.text.TextUtils; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; +import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; + +import java.util.List; + +/** + * Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}. + */ +class SeriesRecordingPresenter extends DvrItemPresenter { + private final DvrDataManager mDvrDataManager; + private final DvrManager mDvrManager; + private final DvrWatchedPositionManager mWatchedPositionManager; + + private final class SeriesRecordingViewHolder extends DvrItemViewHolder implements + WatchedPositionChangedListener, ScheduledRecordingListener, RecordedProgramListener { + private SeriesRecording mSeriesRecording; + private RecordingCardView mCardView; + private DvrDataManager mDvrDataManager; + private DvrManager mDvrManager; + private DvrWatchedPositionManager mWatchedPositionManager; + + SeriesRecordingViewHolder(RecordingCardView view, DvrDataManager dvrDataManager, + DvrManager dvrManager, DvrWatchedPositionManager watchedPositionManager) { + super(view); + mCardView = view; + mDvrDataManager = dvrDataManager; + mDvrManager = dvrManager; + mWatchedPositionManager = watchedPositionManager; + } + + @Override + public void onWatchedPositionChanged(long recordedProgramId, long positionMs) { + if (positionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + mWatchedPositionManager.removeListener(this, recordedProgramId); + updateCardViewContent(); + } + } + + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording scheduledRecording : scheduledRecordings) { + if (scheduledRecording.getSeriesRecordingId() == mSeriesRecording.getId()) { + updateCardViewContent(); + return; + } + } + } + + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording scheduledRecording : scheduledRecordings) { + if (scheduledRecording.getSeriesRecordingId() == mSeriesRecording.getId()) { + updateCardViewContent(); + return; + } + } + } + + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + boolean needToUpdateCardView = false; + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), + mSeriesRecording.getSeriesId())) { + mDvrDataManager.removeScheduledRecordingListener(this); + mWatchedPositionManager.addListener(this, recordedProgram.getId()); + needToUpdateCardView = true; + } + } + if (needToUpdateCardView) { + updateCardViewContent(); + } + } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + boolean needToUpdateCardView = false; + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), + mSeriesRecording.getSeriesId())) { + if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) + == TvInputManager.TIME_SHIFT_INVALID_TIME) { + mWatchedPositionManager.removeListener(this, recordedProgram.getId()); + } + needToUpdateCardView = true; + } + } + if (needToUpdateCardView) { + updateCardViewContent(); + } + } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + // Do nothing + } + + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { + // Do nothing + } + + @Override + protected void onBound(SeriesRecording seriesRecording) { + mSeriesRecording = seriesRecording; + mDvrDataManager.addScheduledRecordingListener(this); + mDvrDataManager.addRecordedProgramListener(this); + for (RecordedProgram recordedProgram : + mDvrDataManager.getRecordedPrograms(mSeriesRecording.getId())) { + if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) + == TvInputManager.TIME_SHIFT_INVALID_TIME) { + mWatchedPositionManager.addListener(this, recordedProgram.getId()); + } + } + updateCardViewContent(); + } + + @Override + protected void onUnbound() { + mDvrDataManager.removeScheduledRecordingListener(this); + mDvrDataManager.removeRecordedProgramListener(this); + mWatchedPositionManager.removeListener(this); + getView().reset(); + } + + private void updateCardViewContent() { + int count = 0; + int quantityStringID; + List recordedPrograms = + mDvrDataManager.getRecordedPrograms(mSeriesRecording.getId()); + if (recordedPrograms.size() == 0) { + count = mDvrManager.getAvailableScheduledRecording(mSeriesRecording.getId()).size(); + quantityStringID = R.plurals.dvr_count_scheduled_recordings; + } else { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) + == TvInputManager.TIME_SHIFT_INVALID_TIME) { + count++; + } + } + if (count == 0) { + count = recordedPrograms.size(); + quantityStringID = R.plurals.dvr_count_recordings; + } else { + quantityStringID = R.plurals.dvr_count_new_recordings; + } + } + mCardView.setContent(mCardView.getResources() + .getQuantityString(quantityStringID, count, count), null); + } + } + + public SeriesRecordingPresenter(Context context) { + super(context); + ApplicationSingletons singletons = TvApplication.getSingletons(context); + mDvrDataManager = singletons.getDvrDataManager(); + mDvrManager = singletons.getDvrManager(); + mWatchedPositionManager = singletons.getDvrWatchedPositionManager(); + } + + @Override + public DvrItemViewHolder onCreateDvrItemViewHolder() { + return new SeriesRecordingViewHolder(new RecordingCardView(mContext), mDvrDataManager, + mDvrManager, mWatchedPositionManager); + } + + @Override + public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, SeriesRecording series) { + final SeriesRecordingViewHolder viewHolder = (SeriesRecordingViewHolder) baseHolder; + final RecordingCardView cardView = viewHolder.getView(); + viewHolder.onBound(series); + DetailsContent details = DetailsContent.createFromSeriesRecording(mContext, series); + cardView.setTitle(details.getTitle()); + cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo()); + cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri()); + } + + @Override + public void onUnbindViewHolder(ViewHolder viewHolder) { + ((RecordingCardView) viewHolder.view).reset(); + ((SeriesRecordingViewHolder) viewHolder).onUnbound(); + super.onUnbindViewHolder(viewHolder); + } +} diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java index d28f026c..b9407b15 100644 --- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java @@ -29,7 +29,7 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; /** * A base fragment to show the list of schedule recordings. @@ -40,7 +40,8 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment /** * The key for scheduled recording which has be selected in the list. */ - public static String SCHEDULES_KEY_SCHEDULED_RECORDING = "schedules_key_scheduled_recording"; + public static final String SCHEDULES_KEY_SCHEDULED_RECORDING = + "schedules_key_scheduled_recording"; private ScheduleRowAdapter mRowsAdapter; private TextView mEmptyInfoScreenView; diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java new file mode 100644 index 00000000..a0410bb3 --- /dev/null +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.list; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.os.Bundle; +import android.support.annotation.IntDef; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.Program; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.provider.EpisodicProgramLoadTask; +import com.android.tv.dvr.recorder.SeriesRecordingScheduler; +import com.android.tv.dvr.ui.BigArguments; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; + +/** + * Activity to show the list of recording schedules. + */ +public class DvrSchedulesActivity extends Activity { + /** + * The key for the type of the schedules which will be listed in the list. The type of the value + * should be {@link ScheduleListType}. + */ + public static final String KEY_SCHEDULES_TYPE = "schedules_type"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_FULL_SCHEDULE, TYPE_SERIES_SCHEDULE}) + public @interface ScheduleListType {} + /** + * A type which means the activity will display the full scheduled recordings. + */ + public static final int TYPE_FULL_SCHEDULE = 0; + /** + * A type which means the activity will display a scheduled recording list of a series + * recording. + */ + public static final int TYPE_SERIES_SCHEDULE = 1; + + @Override + public void onCreate(final Bundle savedInstanceState) { + TvApplication.setCurrentRunningProcess(this, true); + // Pass null to prevent automatically re-creating fragments + super.onCreate(null); + setContentView(R.layout.activity_dvr_schedules); + int scheduleType = getIntent().getIntExtra(KEY_SCHEDULES_TYPE, TYPE_FULL_SCHEDULE); + if (scheduleType == TYPE_FULL_SCHEDULE) { + DvrSchedulesFragment schedulesFragment = new DvrSchedulesFragment(); + schedulesFragment.setArguments(getIntent().getExtras()); + getFragmentManager().beginTransaction().add( + R.id.fragment_container, schedulesFragment).commit(); + } else if (scheduleType == TYPE_SERIES_SCHEDULE) { + if (BigArguments.getArgument(DvrSeriesSchedulesFragment + .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) != null) { + // The programs will be passed to the DvrSeriesSchedulesFragment, so don't need + // to reset the BigArguments. + showDvrSeriesSchedulesFragment(getIntent().getExtras()); + } else { + final ProgressDialog dialog = ProgressDialog.show(this, null, getString( + R.string.dvr_series_progress_message_reading_programs)); + SeriesRecording seriesRecording = getIntent().getExtras() + .getParcelable(DvrSeriesSchedulesFragment + .SERIES_SCHEDULES_KEY_SERIES_RECORDING); + // To get programs faster, hold the update of the series schedules. + SeriesRecordingScheduler.getInstance(this).pauseUpdate(); + new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) { + @Override + protected void onPostExecute(List programs) { + SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this) + .resumeUpdate(); + dialog.dismiss(); + Bundle args = getIntent().getExtras(); + BigArguments.reset(); + BigArguments.setArgument( + DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, + programs == null ? Collections.EMPTY_LIST : programs); + showDvrSeriesSchedulesFragment(args); + } + }.setLoadCurrentProgram(true) + .setLoadDisallowedProgram(true) + .setLoadScheduledEpisode(true) + .setIgnoreChannelOption(true) + .execute(); + } + } else { + finish(); + } + } + + private void showDvrSeriesSchedulesFragment(Bundle args) { + DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment(); + schedulesFragment.setArguments(args); + getFragmentManager().beginTransaction().add( + R.id.fragment_container, schedulesFragment).commit(); + } +} diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java index 722c9b6e..3cbb500a 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java @@ -18,12 +18,9 @@ package com.android.tv.dvr.ui.list; import android.os.Bundle; import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import com.android.tv.R; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter; /** diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java index 42a1e72b..57e7a88f 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java @@ -17,6 +17,7 @@ package com.android.tv.dvr.ui.list; import android.annotation.TargetApi; +import android.content.Context; import android.database.ContentObserver; import android.media.tv.TvContract.Programs; import android.net.Uri; @@ -35,11 +36,13 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; -import com.android.tv.dvr.EpisodicProgramLoadTask; -import com.android.tv.dvr.SeriesRecording; -import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.provider.EpisodicProgramLoadTask; +import com.android.tv.dvr.ui.BigArguments; +import java.util.Collections; import java.util.List; /** @@ -47,20 +50,22 @@ import java.util.List; */ @TargetApi(Build.VERSION_CODES.N) public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { - private static final String TAG = "DvrSeriesSchedulesFragment"; /** * The key for series recording whose scheduled recording list will be displayed. + * Type: {@link SeriesRecording} */ public static final String SERIES_SCHEDULES_KEY_SERIES_RECORDING = "series_schedules_key_series_recording"; /** - * The key for programs belong to the series recording whose scheduled recording - * list will be displayed. + * The key for programs which belong to the series recording whose scheduled recording list + * will be displayed. + * Type: List<{@link Program}> */ public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS = "series_schedules_key_series_programs"; private ChannelDataManager mChannelDataManager; + private DvrDataManager mDvrDataManager; private SeriesRecording mSeriesRecording; private List mPrograms; private EpisodicProgramLoadTask mProgramLoadTask; @@ -87,20 +92,22 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { && getRowsAdapter() instanceof SeriesScheduleRowAdapter) { ((SeriesScheduleRowAdapter) getRowsAdapter()) .onSeriesRecordingUpdated(r); + mSeriesRecording = r; + updateEmptyMessage(); return; } } } }; - private final ContentObserver mContentObserver = - new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - executeProgramLoadingTask(); - } - }; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final ContentObserver mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + executeProgramLoadingTask(); + } + }; private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() { @Override @@ -120,17 +127,28 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { } @Override - public void onCreate(Bundle savedInstanceState) { + public void onAttach(Context context) { + super.onAttach(context); Bundle args = getArguments(); if (args != null) { mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING); - mPrograms = args.getParcelableArrayList(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS); + mPrograms = (List) BigArguments.getArgument( + SERIES_SCHEDULES_KEY_SERIES_PROGRAMS); + BigArguments.reset(); } + if (args == null || mPrograms == null) { + getActivity().finish(); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); - singletons.getDvrDataManager().addSeriesRecordingListener(mSeriesRecordingListener); mChannelDataManager = singletons.getChannelDataManager(); mChannelDataManager.addListener(mChannelListener); + mDvrDataManager = singletons.getDvrDataManager(); + mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener); getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, mContentObserver); } @@ -144,8 +162,16 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { private void onProgramsUpdated() { ((SeriesScheduleRowAdapter) getRowsAdapter()).setPrograms(mPrograms); + updateEmptyMessage(); + } + + private void updateEmptyMessage() { if (mPrograms == null || mPrograms.isEmpty()) { - showEmptyMessage(R.string.dvr_series_schedules_empty_state); + if (mSeriesRecording.getState() == SeriesRecording.STATE_SERIES_STOPPED) { + showEmptyMessage(R.string.dvr_series_schedules_stopped_empty_state); + } else { + showEmptyMessage(R.string.dvr_series_schedules_empty_state); + } } else { hideEmptyMessage(); } @@ -158,15 +184,15 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { mProgramLoadTask = null; } getContext().getContentResolver().unregisterContentObserver(mContentObserver); + mHandler.removeCallbacksAndMessages(null); mChannelDataManager.removeListener(mChannelListener); - TvApplication.getSingletons(getContext()).getDvrDataManager() - .removeSeriesRecordingListener(mSeriesRecordingListener); + mDvrDataManager.removeSeriesRecordingListener(mSeriesRecordingListener); super.onDestroy(); } @Override public SchedulesHeaderRowPresenter onCreateHeaderRowPresenter() { - return new SeriesRecordingHeaderRowPresenter(getContext()); + return new SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter(getContext()); } @Override @@ -195,7 +221,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) { @Override protected void onPostExecute(List programs) { - mPrograms = programs; + mPrograms = programs == null ? Collections.EMPTY_LIST : programs; onProgramsUpdated(); } }; @@ -205,4 +231,4 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { .setIgnoreChannelOption(true) .execute(); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java index 23aebf59..2af832ec 100644 --- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java +++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java @@ -19,13 +19,14 @@ package com.android.tv.dvr.ui.list; import android.content.Context; import com.android.tv.data.Program; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.ScheduledRecording.Builder; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.Builder; +import com.android.tv.dvr.ui.DvrUiHelper; /** * A class for the episodic program. */ -public class EpisodicProgramRow extends ScheduleRow { +class EpisodicProgramRow extends ScheduleRow { private final String mInputId; private final Program mProgram; @@ -65,7 +66,7 @@ public class EpisodicProgramRow extends ScheduleRow { @Override public String getProgramTitleWithEpisodeNumber(Context context) { - return mProgram.getTitleWithEpisodeNumber(context); + return DvrUiHelper.getStyledTitleWithEpisodeNumber(context, mProgram, 0).toString(); } @Override diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java index 3fc92e8a..91ba393a 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java @@ -20,12 +20,13 @@ import android.content.Context; import android.support.annotation.Nullable; import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.DvrUiHelper; /** * A class for schedule recording row. */ -public class ScheduleRow { +class ScheduleRow { private final SchedulesHeaderRow mHeaderRow; @Nullable private ScheduledRecording mSchedule; private boolean mStopRecordingRequested; @@ -166,7 +167,8 @@ public class ScheduleRow { * Returns the program title with episode number. */ public String getProgramTitleWithEpisodeNumber(Context context) { - return mSchedule != null ? mSchedule.getProgramTitleWithEpisodeNumber(context) : null; + return mSchedule != null ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context, + mSchedule, 0).toString() : null; } /** diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java index 9cc82653..97d60473 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java @@ -30,8 +30,8 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit; /** * An adapter for {@link ScheduleRow}. */ -public class ScheduleRowAdapter extends ArrayObjectAdapter { +class ScheduleRowAdapter extends ArrayObjectAdapter { private static final String TAG = "ScheduleRowAdapter"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java index 1257e725..dc4e3c41 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java @@ -42,25 +42,24 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; +import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrStopRecordingFragment; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -import java.util.concurrent.TimeUnit; /** * A RowPresenter for {@link ScheduleRow}. */ @TargetApi(Build.VERSION_CODES.N) -public class ScheduleRowPresenter extends RowPresenter { +class ScheduleRowPresenter extends RowPresenter { private static final String TAG = "ScheduleRowPresenter"; @Retention(RetentionPolicy.SOURCE) @@ -345,7 +344,9 @@ public class ScheduleRowPresenter extends RowPresenter { viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - onInfoClicked(row); + if (isInfoClickable(row)) { + onInfoClicked(row); + } } }); @@ -366,8 +367,7 @@ public class ScheduleRowPresenter extends RowPresenter { viewHolder.mTimeView.setText(onGetRecordingTimeText(row)); String programInfoText = onGetProgramInfoText(row); if (TextUtils.isEmpty(programInfoText)) { - int durationMins = - Math.max((int) TimeUnit.MILLISECONDS.toMinutes(row.getDuration()), 1); + int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration())); programInfoText = mContext.getResources().getQuantityString( R.plurals.dvr_schedules_recording_duration, durationMins, durationMins); } @@ -403,6 +403,7 @@ public class ScheduleRowPresenter extends RowPresenter { } else { viewHolder.whiteBackInfo(); } + viewHolder.mInfoContainer.setFocusable(isInfoClickable(row)); updateActionContainer(viewHolder, viewHolder.isSelected()); } @@ -454,11 +455,13 @@ public class ScheduleRowPresenter extends RowPresenter { /** * Called when user click Info in {@link ScheduleRow}. */ - protected void onInfoClicked(ScheduleRow scheduleRow) { - ScheduledRecording schedule = scheduleRow.getSchedule(); - if (schedule != null) { - DvrUiHelper.startDetailsActivity((Activity) mContext, schedule, null, true); - } + protected void onInfoClicked(ScheduleRow row) { + DvrUiHelper.startDetailsActivity((Activity) mContext, row.getSchedule(), null, true); + } + + private boolean isInfoClickable(ScheduleRow row) { + return row.getSchedule() != null + && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress()); } /** @@ -545,7 +548,7 @@ public class ScheduleRowPresenter extends RowPresenter { // This row has been deleted. return; } - if (row.isOnAir() && row.isRecordingInProgress() && !row.isStopRecordingRequested()) { + if (row.isRecordingInProgress() && !row.isStopRecordingRequested()) { row.setStopRecordingRequested(true); mDvrManager.stopRecording(row.getSchedule()); CharSequence deletedInfo = onGetProgramInfoText(row); @@ -670,10 +673,9 @@ public class ScheduleRowPresenter extends RowPresenter { hideActionView(viewHolder.mFirstActionContainer, View.GONE); } }; - if (mLastFocusedViewId == R.id.action_first_container - || mLastFocusedViewId == R.id.action_second_container) { - mLastFocusedViewId = R.id.info_container; - } + mLastFocusedViewId = R.id.info_container; + SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG, + "No focusable view in this row: " + viewHolder); break; } View view = viewHolder.view.findViewById(mLastFocusedViewId); @@ -683,8 +685,10 @@ public class ScheduleRowPresenter extends RowPresenter { // requestFocus() explicitly. if (view.hasFocus()) { viewHolder.mPendingAnimationRunnable.run(); - } else { + } else if (view.isFocusable()){ view.requestFocus(); + } else { + viewHolder.view.requestFocus(); } } } else { @@ -737,10 +741,10 @@ public class ScheduleRowPresenter extends RowPresenter { @ScheduleRowAction protected int[] getAvailableActions(ScheduleRow row) { if (row.getSchedule() != null) { - if (row.isOnAir()) { - if (row.isRecordingInProgress()) { - return new int[] {ACTION_STOP_RECORDING}; - } else if (row.isRecordingNotStarted()) { + if (row.isRecordingInProgress()) { + return new int[]{ACTION_STOP_RECORDING}; + } else if (row.isOnAir()) { + if (row.isRecordingNotStarted()) { if (canResolveConflict()) { // The "START" action can change the conflict states. return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_START_RECORDING}; diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java index 0fb0924d..715ecb8c 100644 --- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java +++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java @@ -16,12 +16,15 @@ package com.android.tv.dvr.ui.list; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.data.Program; +import com.android.tv.dvr.data.SeriesRecording; + +import java.util.List; /** * A base class for the rows for schedules' header. */ -public abstract class SchedulesHeaderRow { +abstract class SchedulesHeaderRow { private String mTitle; private String mDescription; private int mItemCount; @@ -98,11 +101,20 @@ public abstract class SchedulesHeaderRow { */ public static class SeriesRecordingHeaderRow extends SchedulesHeaderRow { private SeriesRecording mSeriesRecording; + private List mPrograms; public SeriesRecordingHeaderRow(String title, String description, int itemCount, - SeriesRecording series) { + SeriesRecording series, List programs) { super(title, description, itemCount); mSeriesRecording = series; + mPrograms = programs; + } + + /** + * Returns the list of programs which belong to the series. + */ + public List getPrograms() { + return mPrograms; } /** @@ -119,4 +131,4 @@ public abstract class SchedulesHeaderRow { mSeriesRecording = seriesRecording; } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java index 69c33a96..fe2033ba 100644 --- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java @@ -30,15 +30,14 @@ import android.widget.TextView; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.SeriesRecording; -import com.android.tv.dvr.ui.DvrSchedulesActivity; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow; /** * A base class for RowPresenter for {@link SchedulesHeaderRow} */ -public abstract class SchedulesHeaderRowPresenter extends RowPresenter { +abstract class SchedulesHeaderRowPresenter extends RowPresenter { private Context mContext; public SchedulesHeaderRowPresenter(Context context) { @@ -79,7 +78,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter { } /** - * A presenter for {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}. + * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}. */ public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter { public DateHeaderRowPresenter(Context context) { @@ -93,7 +92,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter { /** * A ViewHolder for - * {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}. + * {@link SchedulesHeaderRow.DateHeaderRow}. */ public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder { public DateHeaderRowViewHolder(Context context, ViewGroup parent) { @@ -152,9 +151,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter { headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - // TODO: pass channel list for settings. DvrUiHelper.startSeriesSettingsActivity(getContext(), - header.getSeriesRecording().getId(), null, false, false, false); + header.getSeriesRecording().getId(), + header.getPrograms(), false, false, false, null); } }); headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() { @@ -169,9 +168,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter { .build(); TvApplication.getSingletons(getContext()).getDvrManager() .updateSeriesRecording(seriesRecording); - // TODO: pass channel list for settings. DvrUiHelper.startSeriesSettingsActivity(getContext(), - header.getSeriesRecording().getId(), null, false, false, false); + header.getSeriesRecording().getId(), + header.getPrograms(), false, false, false, null); } else { DvrUiHelper.showCancelAllSeriesRecordingDialog( (DvrSchedulesActivity) view.getContext(), @@ -182,11 +181,8 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter { } private void setTextDrawable(TextView textView, Drawable drawableStart) { - if (mLtr) { - textView.setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null); - } else { - textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableStart, null); - } + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null, + null); } /** diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java index 3b493774..6b6de8b8 100644 --- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java @@ -31,8 +31,8 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; -import com.android.tv.dvr.SeriesRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow; import com.android.tv.util.Utils; @@ -46,7 +46,7 @@ import java.util.Map; * An adapter for series schedule row. */ @TargetApi(Build.VERSION_CODES.N) -public class SeriesScheduleRowAdapter extends ScheduleRowAdapter { +class SeriesScheduleRowAdapter extends ScheduleRowAdapter { private static final String TAG = "SeriesRowAdapter"; private static final boolean DEBUG = false; @@ -96,7 +96,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter { Collections.sort(sortedPrograms); List rows = new ArrayList<>(); mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(), - null, sortedPrograms.size(), mSeriesRecording); + null, sortedPrograms.size(), mSeriesRecording, programs); for (Program program : sortedPrograms) { ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(program.getId()); @@ -145,7 +145,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter { if (index != -1) { EpisodicProgramRow row = (EpisodicProgramRow) get(index); if (!row.isStartRecordingRequested()) { - row.setSchedule(schedule); + setScheduleToRow(row, schedule); notifyArrayItemRangeChanged(index, 1); } } @@ -195,12 +195,10 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter { if (!isStartOrStopRequested()) { executePendingUpdate(); } - row.setSchedule(schedule); + setScheduleToRow(row, schedule); } - } else if (willBeKept(schedule)) { - row.setSchedule(schedule); } else { - row.setSchedule(null); + setScheduleToRow(row, schedule); } notifyArrayItemRangeChanged(index, 1); } @@ -213,6 +211,14 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter { } } + private void setScheduleToRow(ScheduleRow row, ScheduledRecording schedule) { + if (schedule != null && willBeKept(schedule)) { + row.setSchedule(schedule); + } else { + row.setSchedule(null); + } + } + private int findRowIndexByProgramId(long programId) { for (int i = 0; i < size(); i++) { Object item = get(i); diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java index 5d88579a..c8503e0d 100644 --- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java @@ -22,13 +22,13 @@ import android.view.ViewGroup; import com.android.tv.R; import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.DvrUiHelper; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.Utils; /** * A RowPresenter for series schedule row. */ -public class SeriesScheduleRowPresenter extends ScheduleRowPresenter { +class SeriesScheduleRowPresenter extends ScheduleRowPresenter { private static final String TAG = "SeriesRowPresenter"; private boolean mLtr; @@ -74,13 +74,8 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter { viewHolder.getProgramTitleView().setCompoundDrawablePadding(getContext() .getResources().getDimensionPixelOffset( R.dimen.dvr_schedules_warning_icon_padding)); - if (mLtr) { - viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_warning_gray600_36dp, 0, 0, 0); - } else { - viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds( - 0, 0, R.drawable.ic_warning_gray600_36dp, 0); - } + viewHolder.getProgramTitleView().setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.ic_warning_gray600_36dp, 0, 0, 0); } else { viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } @@ -88,9 +83,7 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter { @Override protected void onInfoClicked(ScheduleRow row) { - if (row.getSchedule() != null) { - DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule()); - } + DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule()); } @Override diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java new file mode 100644 index 00000000..6824cfe2 --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.playback; + +import android.app.Activity; +import android.content.ContentUris; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.util.Utils; + +/** + * Activity to play a {@link RecordedProgram}. + */ +public class DvrPlaybackActivity extends Activity implements OnPinCheckedListener { + private static final String TAG = "DvrPlaybackActivity"; + private static final boolean DEBUG = false; + + private DvrPlaybackOverlayFragment mOverlayFragment; + private OnPinCheckedListener mOnPinCheckedListener; + + @Override + public void onCreate(Bundle savedInstanceState) { + TvApplication.setCurrentRunningProcess(this, true); + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + setIntent(createProgramIntent(getIntent())); + setContentView(R.layout.activity_dvr_playback); + mOverlayFragment = (DvrPlaybackOverlayFragment) getFragmentManager() + .findFragmentById(R.id.dvr_playback_controls_fragment); + } + + @Override + public void onVisibleBehindCanceled() { + if (DEBUG) Log.d(TAG, "onVisibleBehindCanceled"); + super.onVisibleBehindCanceled(); + finish(); + } + + @Override + protected void onNewIntent(Intent intent) { + setIntent(createProgramIntent(intent)); + mOverlayFragment.onNewIntent(createProgramIntent(intent)); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + float density = getResources().getDisplayMetrics().density; + mOverlayFragment.onWindowSizeChanged((int) (newConfig.screenWidthDp * density), + (int) (newConfig.screenHeightDp * density)); + } + + private Intent createProgramIntent(Intent intent) { + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + Uri uri = intent.getData(); + long recordedProgramId = ContentUris.parseId(uri); + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, recordedProgramId); + } + return intent; + } + + @Override + public void onPinChecked(boolean checked, int type, String rating) { + if (mOnPinCheckedListener != null) { + mOnPinCheckedListener.onPinChecked(checked, type, rating); + } + } + + void setOnPinCheckListener(OnPinCheckedListener listener) { + mOnPinCheckedListener = listener; + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java new file mode 100644 index 00000000..8ef0041d --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.playback; + +import android.content.Context; + +import com.android.tv.R; +import com.android.tv.dvr.ui.browse.RecordedProgramPresenter; +import com.android.tv.dvr.ui.browse.RecordingCardView; + +/** + * This class is used to generate Views and bind Objects for related recordings in DVR playback. + */ +class DvrPlaybackCardPresenter extends RecordedProgramPresenter { + private final int mRelatedRecordingCardWidth; + private final int mRelatedRecordingCardHeight; + + DvrPlaybackCardPresenter(Context context) { + super(context); + mRelatedRecordingCardWidth = + context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width); + mRelatedRecordingCardHeight = + context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_height); + } + + @Override + public DvrItemViewHolder onCreateDvrItemViewHolder() { + return new RecordedProgramViewHolder(new RecordingCardView( + getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true), null); + } +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java new file mode 100644 index 00000000..1a6ae187 --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.playback; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaController.TransportControls; +import android.media.session.PlaybackState; +import android.media.tv.TvTrackInfo; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v17.leanback.media.PlaybackControlGlue; +import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.PlaybackControlsRow; +import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction; +import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; +import android.support.v17.leanback.widget.RowPresenter; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import com.android.tv.R; +import com.android.tv.util.TimeShiftUtils; +import java.util.ArrayList; + +/** + * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and + * send command to the media controller. It also helps to update playback states displayed in the + * fragment according to information the media session provides. + */ +class DvrPlaybackControlHelper extends PlaybackControlGlue { + private static final String TAG = "DvrPlaybackControlHelpr"; + private static final boolean DEBUG = false; + + private static final int AUDIO_ACTION_ID = 1001; + + private int mPlaybackState = PlaybackState.STATE_NONE; + private int mPlaybackSpeedLevel; + private int mPlaybackSpeedId; + private boolean mReadyToControl; + + private final DvrPlaybackOverlayFragment mFragment; + private final MediaController mMediaController; + private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback(); + private final TransportControls mTransportControls; + private final int mExtraPaddingTopForNoDescription; + private final MultiAction mClosedCaptioningAction; + private final MultiAction mMultiAudioAction; + private ArrayObjectAdapter mSecondaryActionsAdapter; + + DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) { + super(activity, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]); + mFragment = overlayFragment; + mMediaController = activity.getMediaController(); + mMediaController.registerCallback(mMediaControllerCallback); + mTransportControls = mMediaController.getTransportControls(); + mExtraPaddingTopForNoDescription = activity.getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top); + mClosedCaptioningAction = new ClosedCaptioningAction(activity); + mMultiAudioAction = new MultiAudioAction(activity); + createControlsRowPresenter(); + } + + void createControlsRow() { + PlaybackControlsRow controlsRow = new PlaybackControlsRow(this); + setControlsRow(controlsRow); + mSecondaryActionsAdapter = (ArrayObjectAdapter) controlsRow.getSecondaryActionsAdapter(); + } + + private void createControlsRowPresenter() { + AbstractDetailsDescriptionPresenter detailsPresenter = + new AbstractDetailsDescriptionPresenter() { + @Override + protected void onBindDescription( + AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) { + PlaybackControlGlue glue = (PlaybackControlGlue) object; + if (glue.hasValidMedia()) { + viewHolder.getTitle().setText(glue.getMediaTitle()); + viewHolder.getSubtitle().setText(glue.getMediaSubtitle()); + } else { + viewHolder.getTitle().setText(""); + viewHolder.getSubtitle().setText(""); + } + if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) { + viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(), + mExtraPaddingTopForNoDescription, + viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom()); + } + } + }; + PlaybackControlsRowPresenter presenter = + new PlaybackControlsRowPresenter(detailsPresenter) { + @Override + protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) { + super.onBindRowViewHolder(vh, item); + vh.setOnKeyListener(DvrPlaybackControlHelper.this); + } + + @Override + protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) { + super.onUnbindRowViewHolder(vh); + vh.setOnKeyListener(null); + } + }; + presenter.setProgressColor(getContext().getResources() + .getColor(R.color.play_controls_progress_bar_watched)); + presenter.setBackgroundColor(getContext().getResources() + .getColor(R.color.play_controls_body_background_enabled)); + setControlsRowPresenter(presenter); + } + + @Override + public void onActionClicked(Action action) { + if (mReadyToControl) { + int trackType; + if (action.getId() == mClosedCaptioningAction.getId()) { + trackType = TvTrackInfo.TYPE_SUBTITLE; + } else if (action.getId() == AUDIO_ACTION_ID) { + trackType = TvTrackInfo.TYPE_AUDIO; + } else { + super.onActionClicked(action); + return; + } + ArrayList trackInfos = mFragment.getTracks(trackType); + if (!trackInfos.isEmpty()) { + showSideFragment(trackInfos, mFragment.getSelectedTrackId(trackType)); + } + } + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return mReadyToControl && super.onKey(v, keyCode, event); + } + + @Override + public boolean hasValidMedia() { + PlaybackState playbackState = mMediaController.getPlaybackState(); + return playbackState != null; + } + + @Override + public boolean isMediaPlaying() { + PlaybackState playbackState = mMediaController.getPlaybackState(); + if (playbackState == null) { + return false; + } + int state = playbackState.getState(); + return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING + && state != PlaybackState.STATE_PAUSED; + } + + /** + * Returns the ID of the media under playback. + */ + public String getMediaId() { + MediaMetadata mediaMetadata = mMediaController.getMetadata(); + return mediaMetadata == null ? null + : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); + } + + @Override + public CharSequence getMediaTitle() { + MediaMetadata mediaMetadata = mMediaController.getMetadata(); + return mediaMetadata == null ? "" + : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); + } + + @Override + public CharSequence getMediaSubtitle() { + MediaMetadata mediaMetadata = mMediaController.getMetadata(); + return mediaMetadata == null ? "" + : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE); + } + + @Override + public int getMediaDuration() { + MediaMetadata mediaMetadata = mMediaController.getMetadata(); + return mediaMetadata == null ? 0 + : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); + } + + @Override + public Drawable getMediaArt() { + // Do not show the poster art on control row. + return null; + } + + @Override + public long getSupportedActions() { + return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND; + } + + @Override + public int getCurrentSpeedId() { + return mPlaybackSpeedId; + } + + @Override + public int getCurrentPosition() { + PlaybackState playbackState = mMediaController.getPlaybackState(); + if (playbackState == null) { + return 0; + } + return (int) playbackState.getPosition(); + } + + /** + * Unregister media controller's callback. + */ + void unregisterCallback() { + mMediaController.unregisterCallback(mMediaControllerCallback); + } + + /** + * Update the secondary controls row. + * @param hasClosedCaption {@code true} to show the closed caption selection button, + * {@code false} to hide it. + * @param hasMultiAudio {@code true} to show the audio track selection button, + * {@code false} to hide it. + */ + void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) { + if (hasClosedCaption) { + if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) { + mSecondaryActionsAdapter.add(0, mClosedCaptioningAction); + } + } else { + mSecondaryActionsAdapter.remove(mClosedCaptioningAction); + } + if (hasMultiAudio) { + if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) { + mSecondaryActionsAdapter.add(mMultiAudioAction); + } + } else { + mSecondaryActionsAdapter.remove(mMultiAudioAction); + } + getHost().notifyPlaybackRowChanged(); + } + + @Nullable + Boolean hasSecondaryRow() { + if (mSecondaryActionsAdapter == null) { + return null; + } + return mSecondaryActionsAdapter.size() != 0; + } + + @Override + public void play(int speedId) { + if (getCurrentSpeedId() == speedId) { + return; + } + if (speedId == PLAYBACK_SPEED_NORMAL) { + mTransportControls.play(); + } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) { + mTransportControls.rewind(); + } else if (speedId >= PLAYBACK_SPEED_FAST_L0){ + mTransportControls.fastForward(); + } + } + + @Override + public void pause() { + mTransportControls.pause(); + } + + /** + * Notifies closed caption being enabled/disabled to update related UI. + */ + void onSubtitleTrackStateChanged(boolean enabled) { + mClosedCaptioningAction.setIndex(enabled ? + ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF); + } + + private void onStateChanged(int state, long positionMs, int speedLevel) { + if (DEBUG) Log.d(TAG, "onStateChanged"); + getControlsRow().setCurrentTime((int) positionMs); + if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) { + // Only position is changed, no need to update controls row + return; + } + // NOTICE: The below two variables should only be used in this method. + // The only usage of them is to confirm if the state is changed or not. + mPlaybackState = state; + mPlaybackSpeedLevel = speedLevel; + switch (state) { + case PlaybackState.STATE_PLAYING: + mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL; + setFadingEnabled(true); + mReadyToControl = true; + break; + case PlaybackState.STATE_PAUSED: + mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED; + setFadingEnabled(true); + mReadyToControl = true; + break; + case PlaybackState.STATE_FAST_FORWARDING: + mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel; + setFadingEnabled(false); + mReadyToControl = true; + break; + case PlaybackState.STATE_REWINDING: + mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel; + setFadingEnabled(false); + mReadyToControl = true; + break; + case PlaybackState.STATE_CONNECTING: + setFadingEnabled(false); + mReadyToControl = false; + break; + case PlaybackState.STATE_NONE: + mReadyToControl = false; + break; + default: + setFadingEnabled(true); + break; + } + onStateChanged(); + } + + private void showSideFragment(ArrayList trackInfos, String selectedTrackId) { + Bundle args = new Bundle(); + args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos); + args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId); + DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment(); + sideFragment.setArguments(args); + mFragment.getFragmentManager().beginTransaction() + .hide(mFragment) + .replace(R.id.dvr_playback_side_fragment, sideFragment) + .addToBackStack(null) + .commit(); + } + + private class MediaControllerCallback extends MediaController.Callback { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState()); + onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed()); + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + DvrPlaybackControlHelper.this.onMetadataChanged(); + } + } + + private static class MultiAudioAction extends MultiAction { + MultiAudioAction(Context context) { + super(AUDIO_ACTION_ID); + setDrawables(new Drawable[]{context.getDrawable(R.drawable.ic_tvoption_multi_track)}); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java new file mode 100644 index 00000000..843d2dbe --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.playback; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.media.tv.TvContract; +import android.os.AsyncTask; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +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; + +class DvrPlaybackMediaSessionHelper { + private static final String TAG = "DvrPlaybackMediaSessionHelper"; + private static final boolean DEBUG = false; + + private int mNowPlayingCardWidth; + private int mNowPlayingCardHeight; + private int mSpeedLevel; + private long mProgramDurationMs; + + private Activity mActivity; + private DvrPlayer mDvrPlayer; + private MediaSession mMediaSession; + private final DvrWatchedPositionManager mDvrWatchedPositionManager; + private final ChannelDataManager mChannelDataManager; + + public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag, + DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) { + mActivity = activity; + mDvrPlayer = dvrPlayer; + mDvrWatchedPositionManager = + TvApplication.getSingletons(activity).getDvrWatchedPositionManager(); + mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager(); + mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() { + @Override + public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { + updateMediaSessionPlaybackState(); + } + + @Override + public void onPlaybackPositionChanged(long positionMs) { + updateMediaSessionPlaybackState(); + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrWatchedPositionManager + .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs); + } + } + + @Override + public void onPlaybackEnded() { + // TODO: Deal with watched over recordings in DVR library + RecordedProgram nextEpisode = + overlayFragment.getNextEpisode(mDvrPlayer.getProgram()); + if (nextEpisode == null) { + mDvrPlayer.reset(); + mActivity.finish(); + } else { + Intent intent = new Intent(activity, DvrPlaybackActivity.class); + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId()); + mActivity.startActivity(intent); + } + } + }); + initializeMediaSession(mediaSessionTag); + } + + /** + * Stops DVR player and release media session. + */ + public void release() { + if (mDvrPlayer != null) { + mDvrPlayer.reset(); + } + if (mMediaSession != null) { + mMediaSession.release(); + mMediaSession = null; + } + } + + /** + * Updates media session's playback state and speed. + */ + public void updateMediaSessionPlaybackState() { + mMediaSession.setPlaybackState(new PlaybackState.Builder() + .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(), + mSpeedLevel).build()); + } + + /** + * Sets the recorded program for playback. + * + * @param program The recorded program to play. {@code null} to reset the DVR player. + */ + public void setupPlayback(RecordedProgram program, long seekPositionMs) { + if (program != null) { + mDvrPlayer.setProgram(program, seekPositionMs); + setupMediaSession(program); + } else { + mDvrPlayer.reset(); + mMediaSession.setActive(false); + } + } + + /** + * Returns the recorded program now playing. + */ + public RecordedProgram getProgram() { + return mDvrPlayer.getProgram(); + } + + /** + * Checks if the recorded program is the same as now playing one. + */ + public boolean isCurrentProgram(RecordedProgram program) { + return program != null && program.equals(getProgram()); + } + + /** + * Returns playback state. + */ + public int getPlaybackState() { + return mDvrPlayer.getPlaybackState(); + } + + /** + * Returns the underlying DVR player. + */ + public DvrPlayer getDvrPlayer() { + return mDvrPlayer; + } + + private void initializeMediaSession(String mediaSessionTag) { + mMediaSession = new MediaSession(mActivity, mediaSessionTag); + mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mNowPlayingCardWidth = mActivity.getResources() + .getDimensionPixelSize(R.dimen.notif_card_img_max_width); + mNowPlayingCardHeight = mActivity.getResources() + .getDimensionPixelSize(R.dimen.notif_card_img_height); + mMediaSession.setCallback(new MediaSessionCallback()); + mActivity.setMediaController( + new MediaController(mActivity, mMediaSession.getSessionToken())); + updateMediaSessionPlaybackState(); + } + + private void setupMediaSession(RecordedProgram program) { + mProgramDurationMs = program.getDurationMillis(); + String cardTitleText = program.getTitle(); + if (TextUtils.isEmpty(cardTitleText)) { + Channel channel = mChannelDataManager.getChannel(program.getChannelId()); + cardTitleText = (channel != null) ? channel.getDisplayName() + : mActivity.getString(R.string.no_program_information); + } + final MediaMetadata currentMetadata = updateMetadataTextInfo(program.getId(), cardTitleText, + program.getDescription(), mProgramDurationMs); + String posterArtUri = program.getPosterArtUri(); + if (posterArtUri == null) { + posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString(); + } + updatePosterArt(program, currentMetadata, null, posterArtUri); + mMediaSession.setActive(true); + } + + private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata, + @Nullable Bitmap posterArt, @Nullable String posterArtUri) { + if (posterArt != null) { + updateMetadataImageInfo(program, currentMetadata, posterArt, 0); + } else if (posterArtUri != null) { + ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth, + mNowPlayingCardHeight, + new ProgramPosterArtCallback(mActivity, program, currentMetadata)); + } else { + updateMetadataImageInfo(program, currentMetadata, null, R.drawable.default_now_card); + } + } + + private class ProgramPosterArtCallback extends + ImageLoader.ImageLoaderCallback { + private final RecordedProgram mRecordedProgram; + private final MediaMetadata mCurrentMetadata; + + public ProgramPosterArtCallback(Activity activity, RecordedProgram program, + MediaMetadata metadata) { + super(activity); + mRecordedProgram = program; + mCurrentMetadata = metadata; + } + + @Override + public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) { + if (isCurrentProgram(mRecordedProgram)) { + updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null); + } + } + } + + private MediaMetadata updateMetadataTextInfo(final long programId, final String title, + final String subtitle, final long duration) { + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(programId)) + .putString(MediaMetadata.METADATA_KEY_TITLE, title) + .putLong(MediaMetadata.METADATA_KEY_DURATION, duration); + if (subtitle != null) { + builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle); + } + MediaMetadata metadata = builder.build(); + mMediaSession.setMetadata(metadata); + return metadata; + } + + private void updateMetadataImageInfo(final RecordedProgram program, + final MediaMetadata currentMetadata, final Bitmap posterArt, final int imageResId) { + if (mMediaSession != null && (posterArt != null || imageResId != 0)) { + MediaMetadata.Builder builder = new MediaMetadata.Builder(currentMetadata); + if (posterArt != null) { + builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt); + mMediaSession.setMetadata(builder.build()); + } else { + new AsyncTask() { + @Override + protected Bitmap doInBackground(Void... arg0) { + return BitmapFactory.decodeResource(mActivity.getResources(), imageResId); + } + + @Override + protected void onPostExecute(Bitmap programPosterArt) { + if (mMediaSession != null && programPosterArt != null + && isCurrentProgram(program)) { + builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt); + mMediaSession.setMetadata(builder.build()); + } + } + }.execute(); + } + } + } + + // An event was triggered by MediaController.TransportControls and must be handled here. + // Here we update the media itself to act on the event that was triggered. + private class MediaSessionCallback extends MediaSession.Callback { + @Override + public void onPrepare() { + if (!mDvrPlayer.isPlaybackPrepared()) { + mDvrPlayer.prepare(true); + } + } + + @Override + public void onPlay() { + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrPlayer.play(); + } + } + + @Override + public void onPause() { + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrPlayer.pause(); + } + } + + @Override + public void onFastForward() { + if (!mDvrPlayer.isPlaybackPrepared()) { + return; + } + if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING) { + if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { + mSpeedLevel++; + } else { + return; + } + } else { + mSpeedLevel = 0; + } + mDvrPlayer.fastForward( + TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); + } + + @Override + public void onRewind() { + if (!mDvrPlayer.isPlaybackPrepared()) { + return; + } + if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_REWINDING) { + if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { + mSpeedLevel++; + } else { + return; + } + } else { + mSpeedLevel = 0; + } + mDvrPlayer.rewind(TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); + } + + @Override + public void onSeekTo(long positionMs) { + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrPlayer.seekTo(positionMs); + } + } + } +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java new file mode 100644 index 00000000..783ae682 --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.playback; + +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.hardware.display.DisplayManager; +import android.media.tv.TvContentRating; +import android.media.tv.TvTrackInfo; +import android.os.Bundle; +import android.media.session.PlaybackState; +import android.media.tv.TvInputManager; +import android.media.tv.TvView; +import android.support.v17.leanback.app.PlaybackFragment; +import android.support.v17.leanback.app.PlaybackFragmentGlueHost; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.BaseOnItemViewClickedListener; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.HeaderItem; +import android.support.v17.leanback.widget.ListRow; +import android.support.v17.leanback.widget.Presenter; +import android.support.v17.leanback.widget.RowPresenter; +import android.support.v17.leanback.widget.SinglePresenterSelector; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import android.util.Log; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.BaseProgram; +import com.android.tv.dialog.PinDialogFragment; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.SortedArrayAdapter; +import com.android.tv.dvr.ui.browse.DvrListRowPresenter; +import com.android.tv.dvr.ui.browse.RecordingCardView; +import com.android.tv.parental.ContentRatingsManager; +import com.android.tv.util.TvSettings; +import com.android.tv.util.TvTrackInfoUtils; +import com.android.tv.util.Utils; + +import java.util.List; +import java.util.ArrayList; + +public class DvrPlaybackOverlayFragment extends PlaybackFragment { + // TODO: Handles audio focus. Deals with block and ratings. + private static final String TAG = "DvrPlaybackOverlayFrag"; + private static final boolean DEBUG = false; + + private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession"; + private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f; + + // mProgram is only used to store program from intent. Don't use it elsewhere. + private RecordedProgram mProgram; + private DvrPlayer mDvrPlayer; + private DvrPlaybackMediaSessionHelper mMediaSessionHelper; + private DvrPlaybackControlHelper mPlaybackControlHelper; + private ArrayObjectAdapter mRowsAdapter; + private SortedArrayAdapter mRelatedRecordingsRowAdapter; + private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter; + private DvrDataManager mDvrDataManager; + private ContentRatingsManager mContentRatingsManager; + private TvView mTvView; + private View mBlockScreenView; + private ListRow mRelatedRecordingsRow; + private int mVerticalPaddingBase; + private int mPaddingWithoutRelatedRow; + private int mPaddingWithoutSecondaryRow; + private int mWindowWidth; + private int mWindowHeight; + private float mAppliedAspectRatio; + private float mWindowAspectRatio; + private boolean mPinChecked; + private boolean mStarted; + private DvrPlayer.OnTrackSelectedListener mOnSubtitleTrackSelectedListener = + new DvrPlayer.OnTrackSelectedListener() { + @Override + public void onTrackSelected(String selectedTrackId) { + mPlaybackControlHelper.onSubtitleTrackStateChanged(selectedTrackId != null); + mRowsAdapter.notifyArrayItemRangeChanged(0, 1); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + mVerticalPaddingBase = getActivity().getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base); + mPaddingWithoutRelatedRow = getActivity().getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row); + mPaddingWithoutSecondaryRow = getActivity().getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row); + mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); + mContentRatingsManager = TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper().getContentRatingsManager(); + if (!mDvrDataManager.isRecordedProgramLoadFinished()) { + mDvrDataManager.addRecordedProgramLoadFinishedListener( + new DvrDataManager.OnRecordedProgramLoadFinishedListener() { + @Override + public void onRecordedProgramLoadFinished() { + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + if (handleIntent(getActivity().getIntent(), true)) { + setUpRows(); + preparePlayback(getActivity().getIntent()); + } + } + } + ); + } else if (!handleIntent(getActivity().getIntent(), true)) { + return; + } + Point size = new Point(); + ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) + .getDisplay(Display.DEFAULT_DISPLAY).getSize(size); + mWindowWidth = size.x; + mWindowHeight = size.y; + mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight; + setBackgroundType(PlaybackFragment.BG_LIGHT); + setFadingEnabled(true); + } + + @Override + public void onStart() { + super.onStart(); + mStarted = true; + updateVerticalPosition(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view); + mBlockScreenView = getActivity().findViewById(R.id.block_screen); + mDvrPlayer = new DvrPlayer(mTvView); + mMediaSessionHelper = new DvrPlaybackMediaSessionHelper( + getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this); + mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this); + mRelatedRecordingsRow = getRelatedRecordingsRow(); + mDvrPlayer.setOnTracksAvailabilityChangedListener( + new DvrPlayer.OnTracksAvailabilityChangedListener() { + @Override + public void onTracksAvailabilityChanged(boolean hasClosedCaption, + boolean hasMultiAudio) { + mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio); + if (hasClosedCaption) { + mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, + mOnSubtitleTrackSelectedListener); + selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE); + } else { + mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null); + } + if (hasMultiAudio) { + selectBestMatchedTrack(TvTrackInfo.TYPE_AUDIO); + } + updateVerticalPosition(); + mPlaybackControlHelper.getHost().notifyPlaybackRowChanged(); + } + }); + mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() { + @Override + public void onAspectRatioChanged(float videoAspectRatio) { + updateAspectRatio(videoAspectRatio); + } + }); + mPinChecked = getActivity().getIntent() + .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false); + mDvrPlayer.setOnContentBlockedListener( + new DvrPlayer.OnContentBlockedListener() { + @Override + public void onContentBlocked(TvContentRating contentRating) { + if (mPinChecked) { + mTvView.unblockContent(contentRating); + return; + } + mBlockScreenView.setVisibility(View.VISIBLE); + getActivity().getMediaController().getTransportControls().pause(); + ((DvrPlaybackActivity) getActivity()) + .setOnPinCheckListener( + new PinDialogFragment.OnPinCheckedListener() { + @Override + public void onPinChecked( + boolean checked, int type, String rating) { + ((DvrPlaybackActivity) getActivity()) + .setOnPinCheckListener(null); + if (checked) { + mPinChecked = true; + mTvView.unblockContent(contentRating); + mBlockScreenView.setVisibility(View.GONE); + getActivity() + .getMediaController() + .getTransportControls() + .play(); + } + } + }); + PinDialogFragment.create( + PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR, + contentRating.flattenToString()) + .show( + getActivity().getFragmentManager(), + PinDialogFragment.DIALOG_TAG); + } + }); + setOnItemViewClickedListener(new BaseOnItemViewClickedListener() { + @Override + public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, + RowPresenter.ViewHolder rowViewHolder, Object row) { + if (itemViewHolder.view instanceof RecordingCardView) { + setFadingEnabled(false); + long programId = ((RecordedProgram) itemViewHolder.view.getTag()).getId(); + if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId); + Intent intent = new Intent(getContext(), DvrPlaybackActivity.class); + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); + getContext().startActivity(intent); + } + } + }); + if (mProgram != null) { + setUpRows(); + preparePlayback(getActivity().getIntent()); + } + } + + @Override + public void onPause() { + if (DEBUG) Log.d(TAG, "onPause"); + super.onPause(); + if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING + || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) { + getActivity().getMediaController().getTransportControls().pause(); + } + if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) { + getActivity().requestVisibleBehind(false); + } else { + getActivity().requestVisibleBehind(true); + } + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + mPlaybackControlHelper.unregisterCallback(); + mMediaSessionHelper.release(); + mRelatedRecordingCardPresenter.unbindAllViewHolders(); + super.onDestroy(); + } + + /** + * Passes the intent to the fragment. + */ + public void onNewIntent(Intent intent) { + if (mDvrDataManager.isRecordedProgramLoadFinished() && handleIntent(intent, false)) { + preparePlayback(intent); + } + } + + /** + * Should be called when windows' size is changed in order to notify DVR player + * to update it's view width/height and position. + */ + public void onWindowSizeChanged(final int windowWidth, final int windowHeight) { + mWindowWidth = windowWidth; + mWindowHeight = windowHeight; + mWindowAspectRatio = (float) mWindowWidth / mWindowHeight; + updateAspectRatio(mAppliedAspectRatio); + } + + /** + * Returns next recorded episode in the same series as now playing program. + */ + public RecordedProgram getNextEpisode(RecordedProgram program) { + int position = mRelatedRecordingsRowAdapter.findInsertPosition(program); + if (position == mRelatedRecordingsRowAdapter.size()) { + return null; + } else { + return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position); + } + } + + /** + * Returns the tracks of the give type of the current playback. + + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} + * or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}. + */ + public ArrayList getTracks(int trackType) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + return mDvrPlayer.getAudioTracks(); + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + return mDvrPlayer.getSubtitleTracks(); + } + return null; + } + + /** + * Returns the ID of the selected track of the given type. + */ + public String getSelectedTrackId(int trackType) { + return mDvrPlayer.getSelectedTrackId(trackType); + } + + /** + * Returns the language setting of the given track type. + + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} + * or {@link TvTrackInfo#TYPE_AUDIO}. + * @return {@code null} if no language has been set for the given track type. + */ + TvTrackInfo getTrackSetting(int trackType) { + return TvSettings.getDvrPlaybackTrackSettings(getContext(), trackType); + } + + /** + * Selects the given audio or subtitle track for DVR playback. + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} + * or {@link TvTrackInfo#TYPE_AUDIO}. + * @param selectedTrack {@code null} to disable the audio or subtitle track according to + * trackType. + */ + void selectTrack(int trackType, TvTrackInfo selectedTrack) { + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrPlayer.selectTrack(trackType, selectedTrack); + } + } + + private boolean handleIntent(Intent intent, boolean finishActivity) { + mProgram = getProgramFromIntent(intent); + if (mProgram == null) { + Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found), + Toast.LENGTH_SHORT).show(); + if (finishActivity) { + getActivity().finish(); + } + return false; + } + return true; + } + + private void selectBestMatchedTrack(int trackType) { + TvTrackInfo selectedTrack = getTrackSetting(trackType); + if (selectedTrack != null) { + TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType), + selectedTrack.getId(), selectedTrack.getLanguage(), + trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0); + if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils + .isEqualLanguage(bestMatchedTrack.getLanguage(), + selectedTrack.getLanguage()))) { + selectTrack(trackType, bestMatchedTrack); + return; + } + } + if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + // Disables closed captioning if there's no matched language. + selectTrack(TvTrackInfo.TYPE_SUBTITLE, null); + } + } + + private void updateAspectRatio(float videoAspectRatio) { + if (videoAspectRatio <= 0) { + // We don't have video's width or height information, use window's aspect ratio. + videoAspectRatio = mWindowAspectRatio; + } + if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) { + // No need to change + return; + } + if (Math.abs(mWindowAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) { + ((ViewGroup) mTvView.getParent()).setPadding(0, 0, 0, 0); + } else if (videoAspectRatio < mWindowAspectRatio) { + int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2; + ((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0); + } else { + int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2; + ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding); + } + mAppliedAspectRatio = videoAspectRatio; + } + + private void preparePlayback(Intent intent) { + mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent)); + mPlaybackControlHelper.updateSecondaryRow(false, false); + getActivity().getMediaController().getTransportControls().prepare(); + updateRelatedRecordingsRow(); + } + + private void updateRelatedRecordingsRow() { + boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0); + mRelatedRecordingsRowAdapter.clear(); + long programId = mProgram.getId(); + String seriesId = mProgram.getSeriesId(); + SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); + if (seriesRecording != null) { + if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId); + List relatedPrograms = + mDvrDataManager.getRecordedPrograms(seriesRecording.getId()); + for (RecordedProgram program : relatedPrograms) { + if (programId != program.getId()) { + mRelatedRecordingsRowAdapter.add(program); + } + } + } + if (mRelatedRecordingsRowAdapter.size() == 0) { + mRowsAdapter.remove(mRelatedRecordingsRow); + } else if (wasEmpty){ + mRowsAdapter.add(mRelatedRecordingsRow); + } + updateVerticalPosition(); + mRowsAdapter.notifyArrayItemRangeChanged(1, 1); + } + + private void setUpRows() { + mPlaybackControlHelper.createControlsRow(); + mPlaybackControlHelper.setHost(new PlaybackFragmentGlueHost(this)); + mRowsAdapter = (ArrayObjectAdapter) getAdapter(); + ClassPresenterSelector selector = + (ClassPresenterSelector) mRowsAdapter.getPresenterSelector(); + selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext())); + mRowsAdapter.setPresenterSelector(selector); + if (mStarted) { + // If it's started before setting up rows, vertical position has not been updated and + // should be updated here. + updateVerticalPosition(); + } + } + + private ListRow getRelatedRecordingsRow() { + mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity()); + mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter); + HeaderItem header = new HeaderItem(0, + getActivity().getString(R.string.dvr_playback_related_recordings)); + return new ListRow(header, mRelatedRecordingsRowAdapter); + } + + private RecordedProgram getProgramFromIntent(Intent intent) { + long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1); + return mDvrDataManager.getRecordedProgram(programId); + } + + private long getSeekTimeFromIntent(Intent intent) { + return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, + TvInputManager.TIME_SHIFT_INVALID_TIME); + } + + private void updateVerticalPosition() { + Boolean hasSecondaryRow = mPlaybackControlHelper.hasSecondaryRow(); + if (hasSecondaryRow == null) { + return; + } + + int verticalPadding = mVerticalPaddingBase; + if (mRelatedRecordingsRowAdapter.size() == 0) { + verticalPadding += mPaddingWithoutRelatedRow; + } + if (!hasSecondaryRow) { + verticalPadding += mPaddingWithoutSecondaryRow; + } + Fragment fragment = getChildFragmentManager().findFragmentById(R.id.playback_controls_dock); + View view = fragment == null ? null : fragment.getView(); + if (view != null) { + view.setTranslationY(verticalPadding); + } + } + + private class RelatedRecordingsAdapter extends SortedArrayAdapter { + RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) { + super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR); + } + + @Override + public long getId(BaseProgram item) { + return item.getId(); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java new file mode 100644 index 00000000..e49870f1 --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.playback; + +import android.media.tv.TvTrackInfo; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v17.leanback.app.GuidedStepFragment; +import android.support.v17.leanback.widget.GuidedAction; +import android.text.TextUtils; +import android.transition.Transition; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.tv.R; +import com.android.tv.util.TvSettings; + +import java.util.List; +import java.util.Locale; + +/** + * Fragment for DVR playback closed-caption/multi-audio settings. + */ +public class DvrPlaybackSideFragment extends GuidedStepFragment { + /** + * The tag for passing track infos to side fragments. + */ + public static final String TRACK_INFOS = "dvr_key_track_infos"; + /** + * The tag for passing selected track's ID to side fragments. + */ + public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id"; + + private static final int ACTION_ID_NO_SUBTITLE = -1; + private static final int CHECK_SET_ID = 1; + + private List mTrackInfos; + private String mSelectedTrackId; + private TvTrackInfo mSelectedTrack; + private int mTrackType; + private DvrPlaybackOverlayFragment mOverlayFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + mTrackInfos = getArguments().getParcelableArrayList(TRACK_INFOS); + mTrackType = mTrackInfos.get(0).getType(); + mSelectedTrackId = getArguments().getString(SELECTED_TRACK_ID); + mOverlayFragment = ((DvrPlaybackOverlayFragment) getFragmentManager() + .findFragmentById(R.id.dvr_playback_controls_fragment)); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View backgroundView = super.onCreateBackgroundView(inflater, container, savedInstanceState); + backgroundView.setBackgroundColor(getResources() + .getColor(R.color.lb_playback_controls_background_light)); + return backgroundView; + } + + @Override + public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { + if (mTrackType == TvTrackInfo.TYPE_SUBTITLE) { + actions.add(new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_NO_SUBTITLE) + .title(getString(R.string.closed_caption_option_item_off)) + .checkSetId(CHECK_SET_ID) + .checked(mSelectedTrackId == null) + .build()); + } + for (int i = 0; i < mTrackInfos.size(); i++) { + TvTrackInfo info = mTrackInfos.get(i); + boolean checked = TextUtils.equals(info.getId(), mSelectedTrackId); + GuidedAction action = new GuidedAction.Builder(getActivity()) + .id(i) + .title(getTrackLabel(info, i)) + .checkSetId(CHECK_SET_ID) + .checked(checked) + .build(); + actions.add(action); + if (checked) { + mSelectedTrack = info; + } + } + } + + @Override + public void onGuidedActionFocused(GuidedAction action) { + int actionId = (int) action.getId(); + mOverlayFragment.selectTrack(mTrackType, actionId < 0 ? null : mTrackInfos.get(actionId)); + } + + @Override + public void onGuidedActionClicked(GuidedAction action) { + int actionId = (int) action.getId(); + mSelectedTrack = actionId < 0 ? null : mTrackInfos.get(actionId); + TvSettings.setDvrPlaybackTrackSettings(getContext(), mTrackType, mSelectedTrack); + getFragmentManager().popBackStack(); + } + + @Override + public void onStart() { + super.onStart(); + // Workaround: when overlay fragment is faded out, any focus will lost due to overlay + // fragment's implementation. So we disable overlay fragment's fading here to prevent + // losing focus while users are interacting with the side fragment. + mOverlayFragment.setFadingEnabled(false); + } + + @Override + public void onStop() { + super.onStop(); + // We disable fading of overlay fragment to prevent side fragment from losing focus, + // therefore we should resume it here. + mOverlayFragment.setFadingEnabled(true); + mOverlayFragment.selectTrack(mTrackType, mSelectedTrack); + } + + private String getTrackLabel(TvTrackInfo track, int trackIndex) { + if (track.getLanguage() != null) { + return new Locale(track.getLanguage()).getDisplayName(); + } + return track.getType() == TvTrackInfo.TYPE_SUBTITLE ? + getString(R.string.closed_caption_unknown_language, trackIndex + 1) + : getString(R.string.multi_audio_unknown_language); + } + + @Override + protected void onProvideFragmentTransitions() { + super.onProvideFragmentTransitions(); + // Excludes the background scrim from transition to prevent the blinking caused by + // hiding the overlay fragment and sliding in the side fragment at the same time. + Transition t = getEnterTransition(); + if (t != null) { + t.excludeTarget(R.id.guidedstep_background, true); + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java new file mode 100644 index 00000000..7226c666 --- /dev/null +++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java @@ -0,0 +1,583 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.playback; + +import android.media.PlaybackParams; +import android.media.session.PlaybackState; +import android.media.tv.TvContentRating; +import android.media.tv.TvInputManager; +import android.media.tv.TvTrackInfo; +import android.media.tv.TvView; +import android.text.TextUtils; +import android.util.Log; + +import com.android.tv.dvr.data.RecordedProgram; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +class DvrPlayer { + private static final String TAG = "DvrPlayer"; + private static final boolean DEBUG = false; + + /** + * The max rewinding speed supported by DVR player. + */ + public static final int MAX_REWIND_SPEED = 256; + /** + * The max fast-forwarding speed supported by DVR player. + */ + public static final int MAX_FAST_FORWARD_SPEED = 256; + + private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2); + private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826 + + private RecordedProgram mProgram; + private long mInitialSeekPositionMs; + private final TvView mTvView; + private DvrPlayerCallback mCallback; + private OnAspectRatioChangedListener mOnAspectRatioChangedListener; + private OnContentBlockedListener mOnContentBlockedListener; + private OnTracksAvailabilityChangedListener mOnTracksAvailabilityChangedListener; + private OnTrackSelectedListener mOnAudioTrackSelectedListener; + private OnTrackSelectedListener mOnSubtitleTrackSelectedListener; + private String mSelectedAudioTrackId; + private String mSelectedSubtitleTrackId; + private float mAspectRatio = Float.NaN; + private int mPlaybackState = PlaybackState.STATE_NONE; + private long mTimeShiftCurrentPositionMs; + private boolean mPauseOnPrepared; + private boolean mHasClosedCaption; + private boolean mHasMultiAudio; + private final PlaybackParams mPlaybackParams = new PlaybackParams(); + private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback(); + private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + private boolean mTimeShiftPlayAvailable; + + public static class DvrPlayerCallback { + /** + * Called when the playback position is changed. The normal updating frequency is + * around 1 sec., which is restricted to the implementation of + * {@link android.media.tv.TvInputService}. + */ + public void onPlaybackPositionChanged(long positionMs) { } + /** + * Called when the playback state or the playback speed is changed. + */ + public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { } + /** + * Called when the playback toward the end. + */ + public void onPlaybackEnded() { } + } + + public interface OnAspectRatioChangedListener { + /** + * Called when the Video's aspect ratio is changed. + * + * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. + * Listeners should handle it carefully. + */ + void onAspectRatioChanged(float videoAspectRatio); + } + + public interface OnContentBlockedListener { + /** + * Called when the Video's aspect ratio is changed. + */ + void onContentBlocked(TvContentRating rating); + } + + public interface OnTracksAvailabilityChangedListener { + /** + * Called when the Video's subtitle or audio tracks are changed. + */ + void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio); + } + + public interface OnTrackSelectedListener { + /** + * Called when certain subtitle or audio track is selected. + */ + void onTrackSelected(String selectedTrackId); + } + + public DvrPlayer(TvView tvView) { + mTvView = tvView; + mTvView.setCaptionEnabled(true); + mPlaybackParams.setSpeed(1.0f); + setTvViewCallbacks(); + setCallback(null); + } + + /** + * Prepares playback. + * + * @param doPlay indicates DVR player do or do not start playback after media is prepared. + */ + public void prepare(boolean doPlay) throws IllegalStateException { + if (DEBUG) Log.d(TAG, "prepare()"); + if (mProgram == null) { + throw new IllegalStateException("Recorded program not set"); + } else if (mPlaybackState != PlaybackState.STATE_NONE) { + throw new IllegalStateException("Playback is already prepared"); + } + mTvView.timeShiftPlay(mProgram.getInputId(), mProgram.getUri()); + mPlaybackState = PlaybackState.STATE_CONNECTING; + mPauseOnPrepared = !doPlay; + mCallback.onPlaybackStateChanged(mPlaybackState, 1); + } + + /** + * Resumes playback. + */ + public void play() throws IllegalStateException { + if (DEBUG) Log.d(TAG, "play()"); + if (!isPlaybackPrepared()) { + throw new IllegalStateException("Recorded program not set or video not ready yet"); + } + switch (mPlaybackState) { + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_REWINDING: + setPlaybackSpeed(1); + break; + default: + mTvView.timeShiftResume(); + } + mPlaybackState = PlaybackState.STATE_PLAYING; + mCallback.onPlaybackStateChanged(mPlaybackState, 1); + } + + /** + * Pauses playback. + */ + public void pause() throws IllegalStateException { + if (DEBUG) Log.d(TAG, "pause()"); + if (!isPlaybackPrepared()) { + throw new IllegalStateException("Recorded program not set or playback not started yet"); + } + switch (mPlaybackState) { + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_REWINDING: + setPlaybackSpeed(1); + // falls through + case PlaybackState.STATE_PLAYING: + mTvView.timeShiftPause(); + mPlaybackState = PlaybackState.STATE_PAUSED; + break; + default: + break; + } + mCallback.onPlaybackStateChanged(mPlaybackState, 1); + } + + /** + * Fast-forwards playback with the given speed. If the given speed is larger than + * {@value #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}. + */ + public void fastForward(int speed) throws IllegalStateException { + if (DEBUG) Log.d(TAG, "fastForward()"); + if (!isPlaybackPrepared()) { + throw new IllegalStateException("Recorded program not set or playback not started yet"); + } + if (speed <= 0) { + throw new IllegalArgumentException("Speed cannot be negative or 0"); + } + if (mTimeShiftCurrentPositionMs >= mProgram.getDurationMillis() - SEEK_POSITION_MARGIN_MS) { + return; + } + speed = Math.min(speed, MAX_FAST_FORWARD_SPEED); + if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed); + setPlaybackSpeed(speed); + mPlaybackState = PlaybackState.STATE_FAST_FORWARDING; + mCallback.onPlaybackStateChanged(mPlaybackState, speed); + } + + /** + * Rewinds playback with the given speed. If the given speed is larger than + * {@value #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}. + */ + public void rewind(int speed) throws IllegalStateException { + if (DEBUG) Log.d(TAG, "rewind()"); + if (!isPlaybackPrepared()) { + throw new IllegalStateException("Recorded program not set or playback not started yet"); + } + if (speed <= 0) { + throw new IllegalArgumentException("Speed cannot be negative or 0"); + } + if (mTimeShiftCurrentPositionMs <= REWIND_POSITION_MARGIN_MS) { + return; + } + speed = Math.min(speed, MAX_REWIND_SPEED); + if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed); + setPlaybackSpeed(-speed); + mPlaybackState = PlaybackState.STATE_REWINDING; + mCallback.onPlaybackStateChanged(mPlaybackState, speed); + } + + /** + * Seeks playback to the specified position. + */ + public void seekTo(long positionMs) throws IllegalStateException { + if (DEBUG) Log.d(TAG, "seekTo()"); + if (!isPlaybackPrepared()) { + throw new IllegalStateException("Recorded program not set or playback not started yet"); + } + if (mProgram == null || mPlaybackState == PlaybackState.STATE_NONE) { + return; + } + positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS); + if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs); + mTvView.timeShiftSeekTo(positionMs + mStartPositionMs); + if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING || + mPlaybackState == PlaybackState.STATE_REWINDING) { + mPlaybackState = PlaybackState.STATE_PLAYING; + mTvView.timeShiftResume(); + mCallback.onPlaybackStateChanged(mPlaybackState, 1); + } + } + + /** + * Resets playback. + */ + public void reset() { + if (DEBUG) Log.d(TAG, "reset()"); + mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1); + mPlaybackState = PlaybackState.STATE_NONE; + mTvView.reset(); + mTimeShiftPlayAvailable = false; + mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + mTimeShiftCurrentPositionMs = 0; + mPlaybackParams.setSpeed(1.0f); + mProgram = null; + mSelectedAudioTrackId = null; + mSelectedSubtitleTrackId = null; + } + + /** + * Sets callbacks for playback. + */ + public void setCallback(DvrPlayerCallback callback) { + if (callback != null) { + mCallback = callback; + } else { + mCallback = mEmptyCallback; + } + } + + /** + * Sets the listener to aspect ratio changing. + */ + public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) { + mOnAspectRatioChangedListener = listener; + } + + /** + * Sets the listener to content blocking. + */ + public void setOnContentBlockedListener(OnContentBlockedListener listener) { + mOnContentBlockedListener = listener; + } + + /** + * Sets the listener to tracks changing. + */ + public void setOnTracksAvailabilityChangedListener( + OnTracksAvailabilityChangedListener listener) { + mOnTracksAvailabilityChangedListener = listener; + } + + /** + * Sets the listener to tracks of the given type being selected. + * + * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} + * or {@link TvTrackInfo#TYPE_SUBTITLE}. + */ + public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + mOnAudioTrackSelectedListener = listener; + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + mOnSubtitleTrackSelectedListener = listener; + } + } + + /** + * Gets the listener to tracks of the given type being selected. + */ + public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + return mOnAudioTrackSelectedListener; + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + return mOnSubtitleTrackSelectedListener; + } + return null; + } + + /** + * Sets recorded programs for playback. If the player is playing another program, stops it. + */ + public void setProgram(RecordedProgram program, long initialSeekPositionMs) { + if (mProgram != null && mProgram.equals(program)) { + return; + } + if (mPlaybackState != PlaybackState.STATE_NONE) { + reset(); + } + mInitialSeekPositionMs = initialSeekPositionMs; + mProgram = program; + } + + /** + * Returns the recorded program now playing. + */ + public RecordedProgram getProgram() { + return mProgram; + } + + /** + * Returns the currrent playback posistion in msecs. + */ + public long getPlaybackPosition() { + return mTimeShiftCurrentPositionMs; + } + + /** + * Returns the playback speed currently used. + */ + public int getPlaybackSpeed() { + return (int) mPlaybackParams.getSpeed(); + } + + /** + * Returns the playback state defined in {@link android.media.session.PlaybackState}. + */ + public int getPlaybackState() { + return mPlaybackState; + } + + /** + * Returns the subtitle tracks of the current playback. + */ + public ArrayList getSubtitleTracks() { + return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE)); + } + + /** + * Returns the audio tracks of the current playback. + */ + public ArrayList getAudioTracks() { + return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO)); + } + + /** + * Returns the ID of the selected track of the given type. + */ + public String getSelectedTrackId(int trackType) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + return mSelectedAudioTrackId; + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + return mSelectedSubtitleTrackId; + } + return null; + } + + /** + * Returns if playback of the recorded program is started. + */ + public boolean isPlaybackPrepared() { + return mPlaybackState != PlaybackState.STATE_NONE + && mPlaybackState != PlaybackState.STATE_CONNECTING; + } + + /** + * Selects the given track. + * + * @return ID of the selected track. + */ + String selectTrack(int trackType, TvTrackInfo selectedTrack) { + String oldSelectedTrackId = getSelectedTrackId(trackType); + String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId(); + if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) { + if (selectedTrack == null) { + mTvView.selectTrack(trackType, null); + return null; + } else { + List tracks = mTvView.getTracks(trackType); + if (tracks != null && tracks.contains(selectedTrack)) { + mTvView.selectTrack(trackType, newSelectedTrackId); + return newSelectedTrackId; + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) { + // Track not found, disabled closed caption. + mTvView.selectTrack(trackType, null); + return null; + } + } + } + return oldSelectedTrackId; + } + + private void setSelectedTrackId(int trackType, String trackId) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + mSelectedAudioTrackId = trackId; + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + mSelectedSubtitleTrackId = trackId; + } + } + + private void setPlaybackSpeed(int speed) { + mPlaybackParams.setSpeed(speed); + mTvView.timeShiftSetPlaybackParams(mPlaybackParams); + } + + private long getRealSeekPosition(long seekPositionMs, long endMarginMs) { + return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs)); + } + + private void setTvViewCallbacks() { + mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() { + @Override + public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { + if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs); + mStartPositionMs = timeMs; + if (mTimeShiftPlayAvailable) { + resumeToWatchedPositionIfNeeded(); + } + } + + @Override + public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { + if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs); + if (!mTimeShiftPlayAvailable) { + // Workaround of b/31436263 + return; + } + // Workaround of b/32211561, TIF won't report start position when TIS report + // its start position as 0. In that case, we have to do the prework of playback + // on the first time we get current position, and the start position should be 0 + // at that time. + if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) { + mStartPositionMs = 0; + resumeToWatchedPositionIfNeeded(); + } + timeMs -= mStartPositionMs; + if (mPlaybackState == PlaybackState.STATE_REWINDING + && timeMs <= REWIND_POSITION_MARGIN_MS) { + play(); + } else { + mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0); + mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs); + if (timeMs >= mProgram.getDurationMillis()) { + pause(); + mCallback.onPlaybackEnded(); + } + } + } + }); + mTvView.setCallback(new TvView.TvInputCallback() { + @Override + public void onTimeShiftStatusChanged(String inputId, int status) { + if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status); + if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE + && mPlaybackState == PlaybackState.STATE_CONNECTING) { + mTimeShiftPlayAvailable = true; + if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + // onTimeShiftStatusChanged is sometimes called after + // onTimeShiftStartPositionChanged is called. In this case, + // resumeToWatchedPositionIfNeeded needs to be called here. + resumeToWatchedPositionIfNeeded(); + } + } + } + + @Override + public void onTracksChanged(String inputId, List tracks) { + boolean hasClosedCaption = + !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty(); + boolean hasMultiAudio = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1; + if ((hasClosedCaption != mHasClosedCaption || hasMultiAudio != mHasMultiAudio) + && mOnTracksAvailabilityChangedListener != null) { + mOnTracksAvailabilityChangedListener + .onTracksAvailabilityChanged(hasClosedCaption, hasMultiAudio); + } + mHasClosedCaption = hasClosedCaption; + mHasMultiAudio = hasMultiAudio; + } + + @Override + public void onTrackSelected(String inputId, int type, String trackId) { + if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) { + setSelectedTrackId(type, trackId); + OnTrackSelectedListener listener = getOnTrackSelectedListener(type); + if (listener != null) { + listener.onTrackSelected(trackId); + } + } else if (type == TvTrackInfo.TYPE_VIDEO && trackId != null + && mOnAspectRatioChangedListener != null) { + List trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO); + if (trackInfos != null) { + for (TvTrackInfo trackInfo : trackInfos) { + if (trackInfo.getId().equals(trackId)) { + float videoAspectRatio; + int videoWidth = trackInfo.getVideoWidth(); + int videoHeight = trackInfo.getVideoHeight(); + if (videoWidth > 0 && videoHeight > 0) { + videoAspectRatio = trackInfo.getVideoPixelAspectRatio() + * trackInfo.getVideoWidth() / trackInfo.getVideoHeight(); + } else { + // Aspect ratio is unknown. Pass the message to listeners. + videoAspectRatio = 0; + } + if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio); + if (mAspectRatio != videoAspectRatio || videoAspectRatio == 0) { + mOnAspectRatioChangedListener + .onAspectRatioChanged(videoAspectRatio); + mAspectRatio = videoAspectRatio; + return; + } + } + } + } + } + } + + @Override + public void onContentBlocked(String inputId, TvContentRating rating) { + if (mOnContentBlockedListener != null) { + mOnContentBlockedListener.onContentBlocked(rating); + } + } + }); + } + + private void resumeToWatchedPositionIfNeeded() { + if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + mTvView.timeShiftSeekTo(getRealSeekPosition(mInitialSeekPositionMs, + SEEK_POSITION_MARGIN_MS) + mStartPositionMs); + mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + } + if (mPauseOnPrepared) { + mTvView.timeShiftPause(); + mPlaybackState = PlaybackState.STATE_PAUSED; + mPauseOnPrepared = false; + } else { + mTvView.timeShiftResume(); + mPlaybackState = PlaybackState.STATE_PLAYING; + } + mCallback.onPlaybackStateChanged(mPlaybackState, 1); + } +} \ No newline at end of file diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/src/com/android/tv/experiments/ExperimentFlag.java index 8f60c2b5..c0cbd643 100644 --- a/src/com/android/tv/experiments/ExperimentFlag.java +++ b/src/com/android/tv/experiments/ExperimentFlag.java @@ -16,12 +16,19 @@ package com.android.tv.experiments; +import android.support.annotation.VisibleForTesting; /** * Experiments return values based on user, device and other criteria. */ public final class ExperimentFlag { - private final T mDefaultValue; + + private static boolean sAllowOverrides = false; + + @VisibleForTesting + public static void initForTest() { + sAllowOverrides = true; + } /** Returns a boolean experiment */ public static ExperimentFlag createFlag( @@ -30,6 +37,11 @@ public final class ExperimentFlag { defaultValue); } + private final T mDefaultValue; + + private T mOverrideValue = null; + private boolean mOverridden = false; + private ExperimentFlag( T defaultValue) { mDefaultValue = defaultValue; @@ -37,6 +49,22 @@ public final class ExperimentFlag { /** Returns value for this experiment */ public T get() { - return mDefaultValue; + return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue; } + + @VisibleForTesting + public void override(T t) { + if (sAllowOverrides) { + mOverridden = true; + mOverrideValue = t; + } + } + + @VisibleForTesting + public void resetOverride() { + mOverridden = false; + } + + + } diff --git a/src/com/android/tv/experiments/Experiments.java b/src/com/android/tv/experiments/Experiments.java index f16c8d1e..53cce979 100644 --- a/src/com/android/tv/experiments/Experiments.java +++ b/src/com/android/tv/experiments/Experiments.java @@ -23,12 +23,15 @@ import com.android.tv.common.BuildConfig; /** * Set of experiments visible in AOSP. * - *

- * This file is maintained by hand. + *

This file is maintained by hand. */ public final class Experiments { public static final ExperimentFlag CLOUD_EPG = createFlag( - false); + true); + + public static final ExperimentFlag ENABLE_UNRATED_CONTENT_SETTINGS = + createFlag( + false); /** * Allow developer features such as the dev menu and other aids. diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java index 2913599c..ce19eb2d 100644 --- a/src/com/android/tv/guide/GenreListAdapter.java +++ b/src/com/android/tv/guide/GenreListAdapter.java @@ -17,6 +17,7 @@ package com.android.tv.guide; import android.content.Context; +import android.support.annotation.MainThread; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; @@ -32,7 +33,7 @@ import java.util.List; /** * Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. */ -public class GenreListAdapter extends RecyclerView.Adapter { +class GenreListAdapter extends RecyclerView.Adapter { private static final String TAG = "GenreListAdapter"; private static final boolean DEBUG = false; @@ -41,7 +42,7 @@ public class GenreListAdapter extends RecyclerView.Adapter focusables = new ArrayList<>(); + findFocusables(programRow, focusables); + + if (keepCurrentProgramFocused) { + // Select the current program if possible. + for (int i = 0; i < focusables.size(); ++i) { + View focusable = focusables.get(i); + if (focusable instanceof ProgramItemView + && isCurrentProgram((ProgramItemView) focusable)) { + return focusable; + } + } + } + + // Find the largest focusable among fully overlapped focusables. + int maxFullyOverlappedWidth = Integer.MIN_VALUE; + int maxPartiallyOverlappedWidth = Integer.MIN_VALUE; + int nextFocusIndex = INVALID_INDEX; + for (int i = 0; i < focusables.size(); ++i) { + View focusable = focusables.get(i); + Rect focusableRect = new Rect(); + focusable.getGlobalVisibleRect(focusableRect); + if (focusableRect.left <= focusRangeLeft && focusRangeRight <= focusableRect.right) { + // the old focused range is fully inside the focusable, return directly. + return focusable; + } else if (focusRangeLeft <= focusableRect.left + && focusableRect.right <= focusRangeRight) { + // the focusable is fully inside the old focused range, choose the widest one. + int width = focusableRect.width(); + if (width > maxFullyOverlappedWidth) { + nextFocusIndex = i; + maxFullyOverlappedWidth = width; + } + } else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) { + int overlappedWidth = (focusRangeLeft <= focusableRect.left) ? + focusRangeRight - focusableRect.left + : focusableRect.right - focusRangeLeft; + if (overlappedWidth > maxPartiallyOverlappedWidth) { + nextFocusIndex = i; + maxPartiallyOverlappedWidth = overlappedWidth; + } + } + } + if (nextFocusIndex != INVALID_INDEX) { + return focusables.get(nextFocusIndex); + } + return null; + } + + /** + * Returns {@code true} if the program displayed in the give + * {@link com.android.tv.guide.ProgramItemView} is a current program. + */ + static boolean isCurrentProgram(ProgramItemView view) { + return view.getTableEntry().isCurrentProgram(); + } + + /** + * Returns {@code true} if the given view is a descendant of the give container. + */ + static boolean isDescendant(ViewGroup container, View view) { + if (view == null) { + return false; + } + for (ViewParent p = view.getParent(); p != null; p = p.getParent()) { + if (p == container) { + return true; + } + } + return false; + } + + private static void findFocusables(View v, ArrayList outFocusable) { + if (v.isFocusable()) { + outFocusable.add(v); + } + if (v instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) v; + for (int i = 0; i < viewGroup.getChildCount(); ++i) { + findFocusables(viewGroup.getChildAt(i), outFocusable); + } + } + } + private GuideUtils() { } } diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java index 77de5827..58436425 100644 --- a/src/com/android/tv/guide/ProgramGrid.java +++ b/src/com/android/tv/guide/ProgramGrid.java @@ -20,17 +20,15 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.support.v17.leanback.widget.VerticalGridView; -import android.support.v7.widget.RecyclerView.LayoutManager; import android.util.AttributeSet; import android.util.Log; +import android.util.Range; import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; import com.android.tv.R; import com.android.tv.ui.OnRepeatedKeyInterceptListener; -import java.util.ArrayList; import java.util.concurrent.TimeUnit; /** @@ -52,7 +50,7 @@ public class ProgramGrid extends VerticalGridView { clearUpDownFocusState(newFocus); } mNextFocusByUpDown = null; - if (newFocus != ProgramGrid.this && contains(newFocus)) { + if (GuideUtils.isDescendant(ProgramGrid.this, newFocus)) { mLastFocusedView = newFocus; } } @@ -90,8 +88,9 @@ public class ProgramGrid extends VerticalGridView { private View mLastFocusedView; private final Rect mTempRect = new Rect(); + private int mLastUpDownDirection; - private boolean mKeepCurrentProgram; + private boolean mKeepCurrentProgramFocused; private ChildFocusListener mChildFocusListener; private final OnRepeatedKeyInterceptListener mOnRepeatedKeyInterceptListener; @@ -132,21 +131,6 @@ public class ProgramGrid extends VerticalGridView { setOnKeyInterceptListener(mOnRepeatedKeyInterceptListener); } - /** - * Initializes ProgramGrid. It should be called before the view is actually attached to - * Window. - */ - public void initialize(ProgramManager programManager) { - mProgramManager = programManager; - } - - /** - * Registers a listener focus events occurring on children to the {@code ProgramGrid}. - */ - public void setChildFocusListener(ChildFocusListener childFocusListener) { - mChildFocusListener = childFocusListener; - } - @Override public void requestChildFocus(View child, View focused) { if (mChildFocusListener != null) { @@ -173,11 +157,11 @@ public class ProgramGrid extends VerticalGridView { @Override public View focusSearch(View focused, int direction) { mNextFocusByUpDown = null; - if (focused == null || !contains(focused)) { + if (focused == null || (focused != this && !GuideUtils.isDescendant(this, focused))) { return super.focusSearch(focused, direction); } if (direction == View.FOCUS_UP || direction == View.FOCUS_DOWN) { - updateUpDownFocusState(focused); + updateUpDownFocusState(focused, direction); View nextFocus = focusFind(focused, direction); if (nextFocus != null) { return nextFocus; @@ -186,15 +170,85 @@ public class ProgramGrid extends VerticalGridView { return super.focusSearch(focused, direction); } + @Override + public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + if (mLastFocusedView != null && mLastFocusedView.isShown()) { + if (mLastFocusedView.requestFocus()) { + return true; + } + } + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + // It is required to properly handle OnRepeatedKeyInterceptListener. If the focused + // item's are at the almost end of screen, focus change to the next item doesn't work. + // It restricts that a focus item's position cannot be too far from the desired position. + View focusedView = findFocus(); + if (focusedView != null && mOnRepeatedKeyInterceptListener.isFocusAccelerated()) { + int[] location = new int[2]; + getLocationOnScreen(location); + int[] focusedLocation = new int[2]; + focusedView.getLocationOnScreen(focusedLocation); + int y = focusedLocation[1] - location[1]; + int minY = (mSelectionRow - 1) * mRowHeight; + if (y < minY) scrollBy(0, y - minY); + int maxY = (mSelectionRow + 1) * mRowHeight + mDetailHeight; + if (y > maxY) scrollBy(0, y - maxY); + } + updateInputLogo(); + } + + @Override + public void onViewRemoved(View view) { + // It is required to ensure input logo showing when the scroll is moved to most bottom. + updateInputLogo(); + } + + /** + * Initializes ProgramGrid. It should be called before the view is actually attached to + * Window. + */ + void initialize(ProgramManager programManager) { + mProgramManager = programManager; + } + + /** + * Registers a listener focus events occurring on children to the {@code ProgramGrid}. + */ + void setChildFocusListener(ChildFocusListener childFocusListener) { + mChildFocusListener = childFocusListener; + } + + void onItemSelectionReset() { + getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); + } + /** * Resets focus states. If the logic to keep the last focus needs to be cleared, it should * be called. */ - public void resetFocusState() { + void resetFocusState() { mLastFocusedView = null; clearUpDownFocusState(null); } + /** Returns the currently focused item's horizontal range. */ + Range getFocusRange() { + return new Range<>(mFocusRangeLeft, mFocusRangeRight); + } + + /** Returns if the next focused item should be the current program if possible. */ + boolean isKeepCurrentProgramFocused() { + return mKeepCurrentProgramFocused; + } + + /** Returns the last up/down move direction of browsing */ + int getLastUpDownDirection() { + return mLastUpDownDirection; + } + private View focusFind(View focused, int direction) { int focusedChildIndex = getFocusedChildIndex(); if (focusedChildIndex == INVALID_INDEX) { @@ -204,85 +258,26 @@ public class ProgramGrid extends VerticalGridView { int nextChildIndex = direction == View.FOCUS_UP ? focusedChildIndex - 1 : focusedChildIndex + 1; if (nextChildIndex < 0 || nextChildIndex >= getChildCount()) { - return focused; - } - View nextChild = getChildAt(nextChildIndex); - ArrayList focusables = new ArrayList<>(); - findFocusables(nextChild, focusables); - - int index = INVALID_INDEX; - if (mKeepCurrentProgram) { - // Select the current program if possible. - for (int i = 0; i < focusables.size(); ++i) { - View focusable = focusables.get(i); - if (!(focusable instanceof ProgramItemView)) { - continue; - } - if (((ProgramItemView) focusable).getTableEntry().isCurrentProgram()) { - index = i; - break; - } - } - if (index != INVALID_INDEX) { - mNextFocusByUpDown = focusables.get(index); - return mNextFocusByUpDown; - } else { - mKeepCurrentProgram = false; - } - } - - // Find the largest focusable among fully overlapped focusables. - int maxWidth = Integer.MIN_VALUE; - for (int i = 0; i < focusables.size(); ++i) { - View focusable = focusables.get(i); - Rect focusableRect = mTempRect; - focusable.getGlobalVisibleRect(focusableRect); - if (mFocusRangeLeft <= focusableRect.left && focusableRect.right <= mFocusRangeRight) { - int width = focusableRect.width(); - if (width > maxWidth) { - index = i; - maxWidth = width; - } - } else if (focusableRect.left <= mFocusRangeLeft - && mFocusRangeRight <= focusableRect.right) { - // focusableRect contains [mLeft, mRight]. - index = i; - break; - } - } - if (index != INVALID_INDEX) { - mNextFocusByUpDown = focusables.get(index); - return mNextFocusByUpDown; - } - - // Find the largest overlapped view among partially overlapped focusables. - maxWidth = Integer.MIN_VALUE; - for (int i = 0; i < focusables.size(); ++i) { - View focusable = focusables.get(i); - Rect focusableRect = mTempRect; - focusable.getGlobalVisibleRect(focusableRect); - if (mFocusRangeLeft <= focusableRect.left && focusableRect.left <= mFocusRangeRight) { - int overlappedWidth = mFocusRangeRight - focusableRect.left; - if (overlappedWidth > maxWidth) { - index = i; - maxWidth = overlappedWidth; - } - } else if (mFocusRangeLeft <= focusableRect.right - && focusableRect.right <= mFocusRangeRight) { - int overlappedWidth = focusableRect.right - mFocusRangeLeft; - if (overlappedWidth > maxWidth) { - index = i; - maxWidth = overlappedWidth; - } + // Wraparound if reached head or end + if (getSelectedPosition() == 0) { + scrollToPosition(getAdapter().getItemCount() - 1); + return null; + } else if (getSelectedPosition() == getAdapter().getItemCount() - 1) { + scrollToPosition(0); + return null; } + return focused; } - if (index != INVALID_INDEX) { - mNextFocusByUpDown = focusables.get(index); - return mNextFocusByUpDown; + View nextFocusedProgram = GuideUtils.findNextFocusedProgram(getChildAt(nextChildIndex), + mFocusRangeLeft, mFocusRangeRight, mKeepCurrentProgramFocused); + if (nextFocusedProgram != null) { + nextFocusedProgram.getGlobalVisibleRect(mTempRect); + mNextFocusByUpDown = nextFocusedProgram; + + } else { + Log.w(TAG, "focusFind doesn't find proper focusable"); } - - Log.w(TAG, "focusFind doesn't find proper focusable"); - return null; + return nextFocusedProgram; } // Returned value is not the position of VerticalGridView. But it's the index of ViewGroup @@ -296,7 +291,8 @@ public class ProgramGrid extends VerticalGridView { return INVALID_INDEX; } - private void updateUpDownFocusState(View focused) { + private void updateUpDownFocusState(View focused, int direction) { + mLastUpDownDirection = direction; int rightMostFocusablePosition = getRightMostFocusablePosition(); Rect focusedRect = mTempRect; @@ -319,11 +315,13 @@ public class ProgramGrid extends VerticalGridView { } private void clearUpDownFocusState(View focus) { + mLastUpDownDirection = 0; mFocusRangeLeft = 0; mFocusRangeRight = getRightMostFocusablePosition(); mNextFocusByUpDown = null; - mKeepCurrentProgram = focus != null && focus instanceof ProgramItemView - && ((ProgramItemView) focus).getTableEntry().isCurrentProgram(); + // If focus is not a program item, drop focus to the current program when back to the grid + mKeepCurrentProgramFocused = !(focus instanceof ProgramItemView) + || GuideUtils.isCurrentProgram((ProgramItemView) focus); } private int getRightMostFocusablePosition() { @@ -333,56 +331,6 @@ public class ProgramGrid extends VerticalGridView { return mTempRect.right - GuideUtils.convertMillisToPixel(FOCUS_AREA_RIGHT_MARGIN_MILLIS); } - private boolean contains(View v) { - if (v == this) { - return true; - } - if (v == null || v == v.getRootView()) { - return false; - } - return contains((View) v.getParent()); - } - - public void onItemSelectionReset() { - getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); - } - - @Override - public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - if (mLastFocusedView != null && mLastFocusedView.isShown()) { - if (mLastFocusedView.requestFocus()) { - return true; - } - } - return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - // It is required to properly handle OnRepeatedKeyInterceptListener. If the focused - // item's are at the almost end of screen, focus change to the next item doesn't work. - // It restricts that a focus item's position cannot be too far from the desired position. - View focusedView = findFocus(); - if (focusedView != null && mOnRepeatedKeyInterceptListener.isFocusAccelerated()) { - int[] location = new int[2]; - getLocationOnScreen(location); - int[] focusedLocation = new int[2]; - focusedView.getLocationOnScreen(focusedLocation); - int y = focusedLocation[1] - location[1]; - int minY = (mSelectionRow - 1) * mRowHeight; - if (y < minY) scrollBy(0, y - minY); - int maxY = (mSelectionRow + 1) * mRowHeight + mDetailHeight; - if (y > maxY) scrollBy(0, y - maxY); - } - updateInputLogo(); - } - - @Override - public void onViewRemoved(View view) { - // It is required to ensure input logo showing when the scroll is moved to most bottom. - updateInputLogo(); - } - private int getFirstVisibleChildIndex() { final LayoutManager mLayoutManager = getLayoutManager(); int top = mLayoutManager.getPaddingTop(); @@ -398,7 +346,7 @@ public class ProgramGrid extends VerticalGridView { return -1; } - public void updateInputLogo() { + private void updateInputLogo() { int childCount = getChildCount(); if (childCount == 0) { return; @@ -409,25 +357,13 @@ public class ProgramGrid extends VerticalGridView { } View childView = getChildAt(firstVisibleChildIndex); int childAdapterPosition = getChildAdapterPosition(childView); - ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView)) + ((ProgramTableAdapter.ProgramRowViewHolder) getChildViewHolder(childView)) .updateInputLogo(childAdapterPosition, true); for (int i = firstVisibleChildIndex + 1; i < childCount; i++) { childView = getChildAt(i); - ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView)) + ((ProgramTableAdapter.ProgramRowViewHolder) getChildViewHolder(childView)) .updateInputLogo(childAdapterPosition, false); childAdapterPosition = getChildAdapterPosition(childView); } } - - private static void findFocusables(View v, ArrayList outFocusable) { - if (v.isFocusable()) { - outFocusable.add(v); - } - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - for (int i = 0; i < viewGroup.getChildCount(); ++i) { - findFocusables(viewGroup.getChildAt(i), outFocusable); - } - } - } } diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index 120b3dba..dd5444e2 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -48,7 +48,7 @@ import com.android.tv.ChannelTuner; import com.android.tv.Features; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; import com.android.tv.data.ChannelDataManager; @@ -143,6 +143,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private int mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS; private boolean mIsDuringResetRowSelection; private final Handler mHandler = new ProgramGuideHandler(this); + private boolean mActive; private final Runnable mHideRunnable = new Runnable() { @Override @@ -217,7 +218,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y)); mSidePanelGridView.setWindowAlignmentOffsetPercent( VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); - // TODO: Remove this check when we ship TV with epg search enabled. + if (Features.EPG_SEARCH.isEnabled(mActivity)) { mSearchOrb = (SearchOrbView) mContainer.findViewById( R.id.program_guide_side_panel_search_orb); @@ -250,8 +251,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item)); mTimelineRow.setAdapter(mTimeListAdapter); - ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, - mProgramManager, this); + ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, this); programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { @@ -304,13 +304,6 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { R.animator.program_guide_side_panel_enter_full, 0, R.animator.program_guide_table_enter_full); - mShowAnimatorFull.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - ((ViewGroup) mSidePanel).setDescendantFocusability( - ViewGroup.FOCUS_AFTER_DESCENDANTS); - } - }); mShowAnimatorPartial = createAnimator( R.animator.program_guide_side_panel_enter_partial, @@ -383,34 +376,6 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); } - private void updateGuidePosition() { - // Align EPG at vertical center, if EPG table height is less than the screen size. - Resources res = mActivity.getResources(); - int screenHeight = mContainer.getHeight(); - if (screenHeight <= 0) { - // mContainer is not initialized yet. - return; - } - int startPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start); - int topPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_top); - int bottomPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_bottom); - int tableHeight = res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) - + mDetailHeight + mRowHeight * mGrid.getAdapter().getItemCount() + topPadding - + bottomPadding; - if (tableHeight > screenHeight) { - // EPG height is longer that the screen height. - mTable.setPaddingRelative(startPadding, topPadding, 0, 0); - LayoutParams layoutParams = mTable.getLayoutParams(); - layoutParams.height = LayoutParams.WRAP_CONTENT; - mTable.setLayoutParams(layoutParams); - } else { - mTable.setPaddingRelative(startPadding, topPadding, 0, bottomPadding); - LayoutParams layoutParams = mTable.getLayoutParams(); - layoutParams.height = tableHeight; - mTable.setLayoutParams(layoutParams); - } - } - @Override public void onRequestChildFocus(View oldFocus, View newFocus) { if (oldFocus != null && newFocus != null) { @@ -431,40 +396,6 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId, - int tableAnimResId) { - List animatorList = new ArrayList<>(); - - Animator sidePanelAnimator = AnimatorInflater.loadAnimator(mActivity, sidePanelAnimResId); - sidePanelAnimator.setTarget(mSidePanel); - animatorList.add(sidePanelAnimator); - - if (sidePanelGridAnimResId != 0) { - Animator sidePanelGridAnimator = AnimatorInflater.loadAnimator(mActivity, - sidePanelGridAnimResId); - sidePanelGridAnimator.setTarget(mSidePanelGridView); - sidePanelGridAnimator.addListener( - new HardwareLayerAnimatorListenerAdapter(mSidePanelGridView)); - animatorList.add(sidePanelGridAnimator); - } - Animator tableAnimator = AnimatorInflater.loadAnimator(mActivity, tableAnimResId); - tableAnimator.setTarget(mTable); - tableAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable)); - animatorList.add(tableAnimator); - - AnimatorSet set = new AnimatorSet(); - set.playTogether(animatorList); - return set; - } - - /** - * Returns {@code true} if the program guide should process the input events. - */ - public boolean isActive() { - return mContainer.getVisibility() == View.VISIBLE && !mHideAnimatorFull.isStarted() - && !mHideAnimatorPartial.isStarted(); - } - /** * Show the program guide. This reveals the side panel, and the program guide table is shown * partially. @@ -494,14 +425,11 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mTimeListAdapter.update(mStartUtcTime); mTimelineRow.resetScroll(); + mContainer.setVisibility(View.VISIBLE); + mActive = true; if (!mShowGuidePartial) { - // Avoid changing focus from the genre side panel to the grid during animation. - // The descendant focus is changed to FOCUS_AFTER_DESCENDANTS after the animation. - ((ViewGroup) mSidePanel).setDescendantFocusability( - ViewGroup.FOCUS_BLOCK_DESCENDANTS); + mTable.requestFocus(); } - - mContainer.setVisibility(View.VISIBLE); positionCurrentTimeIndicator(); mSidePanelGridView.setSelectedPosition(0); if (DEBUG) { @@ -536,13 +464,13 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } }); } + updateGuidePosition(); runnableAfterAnimatorReady.run(); if (mShowGuidePartial) { mShowAnimatorPartial.start(); } else { mShowAnimatorFull.start(); } - updateGuidePosition(); } }; mContainer.getViewTreeObserver().addOnGlobalLayoutListener(mOnLayoutListenerForShow); @@ -564,7 +492,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { cancelHide(); mProgramManager.programGuideVisibilityChanged(false); mProgramManager.removeListener(mProgramManagerListener); - if (isFull()) { + mActive = false; + if (!mShowGuidePartial) { mHideAnimatorFull.start(); } else { mHideAnimatorPartial.start(); @@ -587,50 +516,21 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } + /** + * Schedules hiding the program guide. + */ public void scheduleHide() { cancelHide(); mHandler.postDelayed(mHideRunnable, mShowDurationMillis); } /** - * Returns the scroll offset of the time line row in pixels. - */ - public int getTimelineRowScrollOffset() { - return mTimelineRow.getScrollOffset(); - } - - /** - * Cancel hiding the program guide. + * Cancels hiding the program guide. */ public void cancelHide() { mHandler.removeCallbacks(mHideRunnable); } - // Returns if program table is full screen mode. - private boolean isFull() { - return mPartialToFullAnimator.isStarted() || mTable.getTranslationX() == 0; - } - - private void startFull() { - if (isFull() || mAccessibilityManager.isEnabled()) { - // If accessibility service is enabled, focus cannot be moved to side panel due to it's - // hidden. Therefore, we don't hide side panel when accessibility service is enabled. - return; - } - mShowGuidePartial = false; - mSharedPreference.edit().putBoolean(KEY_SHOW_GUIDE_PARTIAL, mShowGuidePartial).apply(); - mPartialToFullAnimator.start(); - } - - private void startPartial() { - if (!isFull()) { - return; - } - mShowGuidePartial = true; - mSharedPreference.edit().putBoolean(KEY_SHOW_GUIDE_PARTIAL, mShowGuidePartial).apply(); - mFullToPartialAnimator.start(); - } - /** * Process the {@code KEYCODE_BACK} key event. */ @@ -639,16 +539,30 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } /** - * Gets {@link VerticalGridView} for "genre select" side panel. + * Returns {@code true} if the program guide should process the input events. */ - public VerticalGridView getSidePanel() { - return mSidePanelGridView; + public boolean isActive() { + return mActive; + } + + /** + * Returns {@code true} if the program guide is shown, i.e. showing animation is done and + * hiding animation is not started yet. + */ + public boolean isRunningAnimation() { + return mShowAnimatorPartial.isStarted() || mShowAnimatorFull.isStarted() + || mHideAnimatorPartial.isStarted() || mHideAnimatorFull.isStarted(); + } + + /** Returns if program table is in full screen mode. **/ + boolean isFull() { + return !mShowGuidePartial; } /** * Requests change genre to {@code genreId}. */ - public void requestGenreChange(int genreId) { + void requestGenreChange(int genreId) { if (mLastRequestedGenreId == genreId) { // When Recycler.onLayout() removes its children to recycle, // View tries to find next focus candidate immediately @@ -679,6 +593,104 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mProgramTableFadeOutAnimator.start(); } + /** + * Returns the scroll offset of the time line row in pixels. + */ + int getTimelineRowScrollOffset() { + return mTimelineRow.getScrollOffset(); + } + + /** Returns the program grid view that hold all component views. */ + ProgramGrid getProgramGrid() { + return mGrid; + } + + /** + * Gets {@link VerticalGridView} for "genre select" side panel. + */ + VerticalGridView getSidePanel() { + return mSidePanelGridView; + } + + /** Returns the program manager the program guide is using to provide program information. */ + ProgramManager getProgramManager() { + return mProgramManager; + } + + private void updateGuidePosition() { + // Align EPG at vertical center, if EPG table height is less than the screen size. + Resources res = mActivity.getResources(); + int screenHeight = mContainer.getHeight(); + if (screenHeight <= 0) { + // mContainer is not initialized yet. + return; + } + int startPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start); + int topPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_top); + int bottomPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_bottom); + int tableHeight = res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) + + mDetailHeight + mRowHeight * mGrid.getAdapter().getItemCount() + topPadding + + bottomPadding; + if (tableHeight > screenHeight) { + // EPG height is longer that the screen height. + mTable.setPaddingRelative(startPadding, topPadding, 0, 0); + LayoutParams layoutParams = mTable.getLayoutParams(); + layoutParams.height = LayoutParams.WRAP_CONTENT; + mTable.setLayoutParams(layoutParams); + } else { + mTable.setPaddingRelative(startPadding, topPadding, 0, bottomPadding); + LayoutParams layoutParams = mTable.getLayoutParams(); + layoutParams.height = tableHeight; + mTable.setLayoutParams(layoutParams); + } + } + + private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId, + int tableAnimResId) { + List animatorList = new ArrayList<>(); + + Animator sidePanelAnimator = AnimatorInflater.loadAnimator(mActivity, sidePanelAnimResId); + sidePanelAnimator.setTarget(mSidePanel); + animatorList.add(sidePanelAnimator); + + if (sidePanelGridAnimResId != 0) { + Animator sidePanelGridAnimator = AnimatorInflater.loadAnimator(mActivity, + sidePanelGridAnimResId); + sidePanelGridAnimator.setTarget(mSidePanelGridView); + sidePanelGridAnimator.addListener( + new HardwareLayerAnimatorListenerAdapter(mSidePanelGridView)); + animatorList.add(sidePanelGridAnimator); + } + Animator tableAnimator = AnimatorInflater.loadAnimator(mActivity, tableAnimResId); + tableAnimator.setTarget(mTable); + tableAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable)); + animatorList.add(tableAnimator); + + AnimatorSet set = new AnimatorSet(); + set.playTogether(animatorList); + return set; + } + + private void startFull() { + if (!mShowGuidePartial || mAccessibilityManager.isEnabled()) { + // If accessibility service is enabled, focus cannot be moved to side panel due to it's + // hidden. Therefore, we don't hide side panel when accessibility service is enabled. + return; + } + mShowGuidePartial = false; + mSharedPreference.edit().putBoolean(KEY_SHOW_GUIDE_PARTIAL, mShowGuidePartial).apply(); + mPartialToFullAnimator.start(); + } + + private void startPartial() { + if (mShowGuidePartial) { + return; + } + mShowGuidePartial = true; + mSharedPreference.edit().putBoolean(KEY_SHOW_GUIDE_PARTIAL, mShowGuidePartial).apply(); + mFullToPartialAnimator.start(); + } + private void startCurrentTimeIndicator(long initialDelay) { mHandler.postDelayed(mUpdateTimeIndicator, initialDelay); } @@ -775,10 +787,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mDetailInAnimator.cancel(); } - int direction = 0; - if (outRow != null && inRow != null) { - // -1 means the selection goes downwards and 1 goes upwards - direction = outRow.getTop() < inRow.getTop() ? -1 : 1; + int operationDirection = mGrid.getLastUpDownDirection(); + int animationPadding = 0; + if (operationDirection == View.FOCUS_UP) { + animationPadding = mDetailPadding; + } else if (operationDirection == View.FOCUS_DOWN) { + animationPadding = -mDetailPadding; } View outDetail = outRow != null ? outRow.findViewById(R.id.detail) : null; @@ -788,7 +802,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Animator fadeOutAnimator = ObjectAnimator.ofPropertyValuesHolder(outDetailContent, PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f), PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - outDetailContent.getTranslationY(), direction * mDetailPadding)); + outDetailContent.getTranslationY(), animationPadding)); fadeOutAnimator.setStartDelay(0); fadeOutAnimator.setDuration(mAnimationDuration); fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent)); @@ -842,8 +856,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { }); Animator fadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(inDetailContent, PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - direction * -mDetailPadding, 0f)); + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -animationPadding, 0f)); fadeInAnimator.setDuration(mAnimationDuration); fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent)); @@ -910,7 +923,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } private static class ProgramGuideHandler extends WeakHandler { - public ProgramGuideHandler(ProgramGuide ref) { + ProgramGuideHandler(ProgramGuide ref) { super(ref); } diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index 4c7a4404..b23d578c 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -44,8 +44,8 @@ import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; @@ -73,6 +73,7 @@ public class ProgramItemView extends TextView { private static TextAppearanceSpan sEpisodeTitleStyle; private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle; + private ProgramGuide mProgramGuide; private DvrManager mDvrManager; private TableEntry mTableEntry; private int mMaxWidthForRipple; @@ -106,18 +107,19 @@ public class ProgramItemView extends TextView { }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0 : view.getResources() .getInteger(R.integer.program_guide_ripple_anim_duration)); - } else if (CommonFeatures.DVR.isEnabled(view.getContext())) { + } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) { DvrManager dvrManager = singletons.getDvrManager(); if (entry.entryStartUtcMillis > System.currentTimeMillis() && dvrManager.isProgramRecordable(entry.program)) { if (entry.scheduledRecording == null) { - if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity, - channel.getInputId()) - && DvrUiHelper.handleCreateSchedule(tvActivity, entry.program)) { - String msg = view.getContext().getString( - R.string.dvr_msg_program_scheduled, entry.program.getTitle()); - ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); - } + DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity, + channel.getInputId(), new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingFutureProgram(tvActivity, + entry.program, false); + } + }); } else { dvrManager.removeScheduledRecording(entry.scheduledRecording); String msg = view.getResources().getString( @@ -158,6 +160,11 @@ public class ProgramItemView extends TextView { } if (entry.isCurrentProgram()) { Drawable background = getBackground(); + if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) { + // If program guide is not active or is during showing/hiding, + // the animation is unnecessary, skip it. + background.jumpToCurrentState(); + } int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); } @@ -247,8 +254,9 @@ public class ProgramItemView extends TextView { } @SuppressLint("SwitchIntDef") - public void setValues(TableEntry entry, int selectedGenreId, long fromUtcMillis, - long toUtcMillis, String gapTitle) { + public void setValues(ProgramGuide programGuide, TableEntry entry, int selectedGenreId, + long fromUtcMillis, long toUtcMillis, String gapTitle) { + mProgramGuide = programGuide; mTableEntry = entry; ViewGroup.LayoutParams layoutParams = getLayoutParams(); @@ -376,6 +384,7 @@ public class ProgramItemView extends TextView { } setTag(null); + mProgramGuide = null; mTableEntry = null; } diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index 03aea5ad..c1fcdd40 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -32,11 +32,12 @@ import com.android.tv.guide.ProgramManager.TableEntry; * Adapts a program list for a specific channel from {@link ProgramManager} to a row of the program * guide table. */ -public class ProgramListAdapter extends RecyclerView.Adapter +class ProgramListAdapter extends RecyclerView.Adapter implements TableEntriesUpdatedListener { private static final String TAG = "ProgramListAdapter"; private static final boolean DEBUG = false; + private final ProgramGuide mProgramGuide; private final ProgramManager mProgramManager; private final int mChannelIndex; private final String mNoInfoProgramTitle; @@ -44,9 +45,10 @@ public class ProgramListAdapter extends RecyclerView.Adapter current; - } - - /** - * Returns if this program has the genre. - */ - public boolean hasGenre(int genreId) { - return !isGap() && program.hasGenre(genreId); - } - - /** - * Returns the width of table entry, in pixels. - */ - public int getWidth() { - return GuideUtils.convertMillisToPixel(entryStartUtcMillis, entryEndUtcMillis); - } - - @Override - public String toString() { - return "TableEntry{" - + "hashCode=" + hashCode() - + ", channelId=" + channelId - + ", program=" + program - + ", startTime=" + Utils.toTimeString(entryStartUtcMillis) - + ", endTimeTime=" + Utils.toTimeString(entryEndUtcMillis) + "}"; - } - } - private List mChannels = new ArrayList<>(); private final Map> mChannelIdEntriesMap = new HashMap<>(); private final List> mGenreChannelList = new ArrayList<>(); @@ -293,7 +192,7 @@ public class ProgramManager { mDvrScheduleManager = dvrScheduleManager; } - public void programGuideVisibilityChanged(boolean visible) { + void programGuideVisibilityChanged(boolean visible) { mProgramDataManager.setPauseProgramUpdate(visible); if (visible) { mChannelDataManager.addListener(mChannelDataManagerListener); @@ -325,87 +224,51 @@ public class ProgramManager { /** * Adds a {@link Listener}. */ - public void addListener(Listener listener) { + void addListener(Listener listener) { mListeners.add(listener); } /** * Registers a listener to be invoked when table entries are updated. */ - public void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { + void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.add(listener); } /** * Registers a listener to be invoked when a table entry is changed. */ - public void addTableEntryChangedListener(TableEntryChangedListener listener) { + void addTableEntryChangedListener(TableEntryChangedListener listener) { mTableEntryChangedListeners.add(listener); } /** * Removes a {@link Listener}. */ - public void removeListener(Listener listener) { + void removeListener(Listener listener) { mListeners.remove(listener); } /** * Removes a previously installed table entries update listener. */ - public void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { + void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.remove(listener); } /** * Removes a previously installed table entry changed listener. */ - public void removeTableEntryChangedListener(TableEntryChangedListener listener) { + void removeTableEntryChangedListener(TableEntryChangedListener listener) { mTableEntryChangedListeners.remove(listener); } - /** - * Build genre filters based on the current programs. - * This categories channels by its current program's canonical genres - * and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list - * with built channel list. - * This is expected to be called whenever program guide is shown. - */ - public void buildGenreFilters() { - if (DEBUG) Log.d(TAG, "buildGenreFilters"); - - mGenreChannelList.clear(); - for (int i = 0; i < GenreItems.getGenreCount(); i++) { - mGenreChannelList.add(new ArrayList<>()); - } - for (Channel channel : mChannels) { - // TODO: Use programs in visible area instead of using current programs only. - Program currentProgram = mProgramDataManager.getCurrentProgram(channel.getId()); - if (currentProgram != null && currentProgram.getCanonicalGenres() != null) { - for (String genre : currentProgram.getCanonicalGenres()) { - mGenreChannelList.get(GenreItems.getId(genre)).add(channel); - } - } - } - mGenreChannelList.set(GenreItems.ID_ALL_CHANNELS, mChannels); - mFilteredGenreIds.clear(); - mFilteredGenreIds.add(0); - for (int i = 1; i < GenreItems.getGenreCount(); i++) { - if (mGenreChannelList.get(i).size() > 0) { - mFilteredGenreIds.add(i); - } - } - mSelectedGenreId = GenreItems.ID_ALL_CHANNELS; - mFilteredChannels = mChannels; - notifyGenresUpdated(); - } - /** * Resets channel list with given genre. * Caller should call {@link #buildGenreFilters()} prior to call this API to make * This notifies channel updates to listeners. */ - public void resetChannelListWithGenre(int genreId) { + void resetChannelListWithGenre(int genreId) { if (genreId == mSelectedGenreId) { return; } @@ -421,14 +284,155 @@ public class ProgramManager { notifyChannelsUpdated(); } + /** + * Update the initial time range to manage. It updates program entries and genre as well. + */ + void updateInitialTimeRange(long startUtcMillis, long endUtcMillis) { + mStartUtcMillis = startUtcMillis; + if (endUtcMillis > mEndUtcMillis) { + mEndUtcMillis = endUtcMillis; + } + + mProgramDataManager.setPrefetchTimeRange(mStartUtcMillis); + updateChannels(true); + setTimeRange(startUtcMillis, endUtcMillis); + } + + + /** + * Shifts the time range by the given time. Also makes ProgramGuide scroll the views. + */ + void shiftTime(long timeMillisToScroll) { + long fromUtcMillis = mFromUtcMillis + timeMillisToScroll; + long toUtcMillis = mToUtcMillis + timeMillisToScroll; + if (fromUtcMillis < mStartUtcMillis) { + fromUtcMillis = mStartUtcMillis; + toUtcMillis += mStartUtcMillis - fromUtcMillis; + } + if (toUtcMillis > mEndUtcMillis) { + fromUtcMillis -= toUtcMillis - mEndUtcMillis; + toUtcMillis = mEndUtcMillis; + } + setTimeRange(fromUtcMillis, toUtcMillis); + } + + /** + * Returned the scrolled(shifted) time in milliseconds. + */ + long getShiftedTime() { + return mFromUtcMillis - mStartUtcMillis; + } + + /** + * Returns the start time set by {@link #updateInitialTimeRange}. + */ + long getStartTime() { + return mStartUtcMillis; + } + + /** + * Returns the program index of the program with {@code entryId} or -1 if not found. + */ + int getProgramIdIndex(long channelId, long entryId) { + List entries = mChannelIdEntriesMap.get(channelId); + if (entries != null) { + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getId() == entryId) { + return i; + } + } + } + return -1; + } + + /** + * Returns the program index of the program at {@code time} or -1 if not found. + */ + int getProgramIndexAtTime(long channelId, long time) { + List entries = mChannelIdEntriesMap.get(channelId); + for (int i = 0; i < entries.size(); ++i) { + TableEntry entry = entries.get(i); + if (entry.entryStartUtcMillis <= time + && time < entry.entryEndUtcMillis) { + return i; + } + } + return -1; + } + + /** + * Returns the start time of currently managed time range, in UTC millisecond. + */ + long getFromUtcMillis() { + return mFromUtcMillis; + } + + /** + * Returns the end time of currently managed time range, in UTC millisecond. + */ + long getToUtcMillis() { + return mToUtcMillis; + } + + /** + * Returns the number of the currently managed channels. + */ + int getChannelCount() { + return mFilteredChannels.size(); + } + + /** + * Returns a {@link Channel} at a given {@code channelIndex} of the currently managed channels. + * Returns {@code null} if such a channel is not found. + */ + Channel getChannel(int channelIndex) { + if (channelIndex < 0 || channelIndex >= getChannelCount()) { + return null; + } + return mFilteredChannels.get(channelIndex); + } + + /** + * Returns the index of provided {@link Channel} within the currently managed channels. + * Returns -1 if such a channel is not found. + */ + int getChannelIndex(Channel channel) { + return mFilteredChannels.indexOf(channel); + } + + /** + * Returns the index of channel with {@code channelId} within the currently managed channels. + * Returns -1 if such a channel is not found. + */ + int getChannelIndex(long channelId) { + return getChannelIndex(mChannelDataManager.getChannel(channelId)); + } + + /** + * Returns the number of "entries", which lies within the currently managed time range, for a + * given {@code channelId}. + */ + int getTableEntryCount(long channelId) { + return mChannelIdEntriesMap.get(channelId).size(); + } + + /** + * Returns an entry as {@link Program} for a given {@code channelId} and {@code index} of + * entries within the currently managed time range. Returned {@link Program} can be a dummy one + * (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs. + */ + TableEntry getTableEntry(long channelId, int index) { + return mChannelIdEntriesMap.get(channelId).get(index); + } + /** * Returns list genre ID's which has a channel. */ - public List getFilteredGenreIds() { + List getFilteredGenreIds() { return mFilteredGenreIds; } - public int getSelectedGenreId() { + int getSelectedGenreId() { return mSelectedGenreId; } @@ -439,11 +443,24 @@ public class ProgramManager { mChannels = mChannelDataManager.getBrowsableChannelList(); mSelectedGenreId = GenreItems.ID_ALL_CHANNELS; mFilteredChannels = mChannels; + updateTableEntriesWithoutNotification(clearPreviousTableEntries); + // Channel update notification should be called after updating table entries, so that + // the listener can get the entries. notifyChannelsUpdated(); - updateTableEntries(clearPreviousTableEntries); + notifyTableEntriesUpdated(); + buildGenreFilters(); } private void updateTableEntries(boolean clear) { + updateTableEntriesWithoutNotification(clear); + notifyTableEntriesUpdated(); + buildGenreFilters(); + } + + /** + * Updates the table entries without notifying the change. + */ + private void updateTableEntriesWithoutNotification(boolean clear) { if (clear) { mChannelIdEntriesMap.clear(); } @@ -491,46 +508,41 @@ public class ProgramManager { } } } + } - notifyTableEntriesUpdated(); - buildGenreFilters(); - } - - private void notifyGenresUpdated() { - for (Listener listener : mListeners) { - listener.onGenresUpdated(); - } - } - - private void notifyChannelsUpdated() { - for (Listener listener : mListeners) { - listener.onChannelsUpdated(); - } - } + /** + * Build genre filters based on the current programs. + * This categories channels by its current program's canonical genres + * and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list + * with built channel list. + * This is expected to be called whenever program guide is shown. + */ + private void buildGenreFilters() { + if (DEBUG) Log.d(TAG, "buildGenreFilters"); - private void notifyTimeRangeUpdated() { - for (Listener listener : mListeners) { - listener.onTimeRangeUpdated(); + mGenreChannelList.clear(); + for (int i = 0; i < GenreItems.getGenreCount(); i++) { + mGenreChannelList.add(new ArrayList<>()); } - } - - private void notifyTableEntriesUpdated() { - for (TableEntriesUpdatedListener listener : mTableEntriesUpdatedListeners) { - listener.onTableEntriesUpdated(); + for (Channel channel : mChannels) { + Program currentProgram = mProgramDataManager.getCurrentProgram(channel.getId()); + if (currentProgram != null && currentProgram.getCanonicalGenres() != null) { + for (String genre : currentProgram.getCanonicalGenres()) { + mGenreChannelList.get(GenreItems.getId(genre)).add(channel); + } + } } - } - - private void notifyTableEntryUpdated(TableEntry entry) { - for (TableEntryChangedListener listener : mTableEntryChangedListeners) { - listener.onTableEntryChanged(entry); + mGenreChannelList.set(GenreItems.ID_ALL_CHANNELS, mChannels); + mFilteredGenreIds.clear(); + mFilteredGenreIds.add(0); + for (int i = 1; i < GenreItems.getGenreCount(); i++) { + if (mGenreChannelList.get(i).size() > 0) { + mFilteredGenreIds.add(i); + } } - } - - private void updateEntry(TableEntry old, TableEntry newEntry) { - List entries = mChannelIdEntriesMap.get(old.channelId); - int index = entries.indexOf(old); - entries.set(index, newEntry); - notifyTableEntryUpdated(newEntry); + mSelectedGenreId = GenreItems.ID_ALL_CHANNELS; + mFilteredChannels = mChannels; + notifyGenresUpdated(); } @Nullable @@ -551,32 +563,11 @@ public class ProgramManager { return null; } - /** - * Returns the start time of currently managed time range, in UTC millisecond. - */ - public long getFromUtcMillis() { - return mFromUtcMillis; - } - - /** - * Returns the end time of currently managed time range, in UTC millisecond. - */ - public long getToUtcMillis() { - return mToUtcMillis; - } - - /** - * Update the initial time range to manage. It updates program entries and genre as well. - */ - public void updateInitialTimeRange(long startUtcMillis, long endUtcMillis) { - mStartUtcMillis = startUtcMillis; - if (endUtcMillis > mEndUtcMillis) { - mEndUtcMillis = endUtcMillis; - } - - mProgramDataManager.setPrefetchTimeRange(mStartUtcMillis); - updateChannels(true); - setTimeRange(startUtcMillis, endUtcMillis); + private void updateEntry(TableEntry old, TableEntry newEntry) { + List entries = mChannelIdEntriesMap.get(old.channelId); + int index = entries.indexOf(old); + entries.set(index, newEntry); + notifyTableEntryUpdated(newEntry); } private void setTimeRange(long fromUtcMillis, long toUtcMillis) { @@ -592,57 +583,6 @@ public class ProgramManager { } } - /** - * Returns the number of the currently managed channels. - */ - public int getChannelCount() { - return mFilteredChannels.size(); - } - - /** - * Returns a {@link Channel} at a given {@code channelIndex} of the currently managed channels. - * Returns {@code null} if such a channel is not found. - */ - public Channel getChannel(int channelIndex) { - if (channelIndex < 0 || channelIndex >= getChannelCount()) { - return null; - } - return mFilteredChannels.get(channelIndex); - } - - /** - * Returns the index of provided {@link Channel} within the currently managed channels. - * Returns -1 if such a channel is not found. - */ - public int getChannelIndex(Channel channel) { - return mFilteredChannels.indexOf(channel); - } - - /** - * Returns the index of channel with {@code channelId} within the currently managed channels. - * Returns -1 if such a channel is not found. - */ - public int getChannelIndex(long channelId) { - return getChannelIndex(mChannelDataManager.getChannel(channelId)); - } - - /** - * Returns the number of "entries", which lies within the currently managed time range, for a - * given {@code channelId}. - */ - public int getTableEntryCount(long channelId) { - return mChannelIdEntriesMap.get(channelId).size(); - } - - /** - * Returns an entry as {@link Program} for a given {@code channelId} and {@code index} of - * entries within the currently managed time range. Returned {@link Program} can be a dummy one - * (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs. - */ - public TableEntry getTableEntry(long channelId, int index) { - return mChannelIdEntriesMap.get(channelId).get(index); - } - private List createProgramEntries(long channelId, boolean parentalControlsEnabled) { List entries = new ArrayList<>(); boolean channelLocked = parentalControlsEnabled @@ -690,89 +630,159 @@ public class ProgramManager { return entries; } - public interface Listener { - void onGenresUpdated(); - void onChannelsUpdated(); - void onTimeRangeUpdated(); + private void notifyGenresUpdated() { + for (Listener listener : mListeners) { + listener.onGenresUpdated(); + } } - public interface TableEntriesUpdatedListener { - void onTableEntriesUpdated(); + private void notifyChannelsUpdated() { + for (Listener listener : mListeners) { + listener.onChannelsUpdated(); + } } - public interface TableEntryChangedListener { - void onTableEntryChanged(TableEntry entry); + private void notifyTimeRangeUpdated() { + for (Listener listener : mListeners) { + listener.onTimeRangeUpdated(); + } } - public static class ListenerAdapter implements Listener { - @Override - public void onGenresUpdated() { } - - @Override - public void onChannelsUpdated() { } + private void notifyTableEntriesUpdated() { + for (TableEntriesUpdatedListener listener : mTableEntriesUpdatedListeners) { + listener.onTableEntriesUpdated(); + } + } - @Override - public void onTimeRangeUpdated() { } + private void notifyTableEntryUpdated(TableEntry entry) { + for (TableEntryChangedListener listener : mTableEntryChangedListeners) { + listener.onTableEntryChanged(entry); + } } /** - * Shifts the time range by the given time. Also makes ProgramGuide scroll the views. + * Entry for program guide table. An "entry" can be either an actual program or a gap between + * programs. This is needed for {@link ProgramListAdapter} because + * {@link android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items. */ - public void shiftTime(long timeMillisToScroll) { - long fromUtcMillis = mFromUtcMillis + timeMillisToScroll; - long toUtcMillis = mToUtcMillis + timeMillisToScroll; - if (fromUtcMillis < mStartUtcMillis) { - fromUtcMillis = mStartUtcMillis; - toUtcMillis += mStartUtcMillis - fromUtcMillis; + static class TableEntry { + /** Channel ID which this entry is included. */ + final long channelId; + + /** Program corresponding to the entry. {@code null} means that this entry is a gap. */ + final Program program; + + final ScheduledRecording scheduledRecording; + + /** Start time of entry in UTC milliseconds. */ + final long entryStartUtcMillis; + + /** End time of entry in UTC milliseconds */ + final long entryEndUtcMillis; + + private final boolean mIsBlocked; + + private TableEntry(long channelId, long startUtcMillis, long endUtcMillis) { + this(channelId, null, startUtcMillis, endUtcMillis, false); } - if (toUtcMillis > mEndUtcMillis) { - fromUtcMillis -= toUtcMillis - mEndUtcMillis; - toUtcMillis = mEndUtcMillis; + + private TableEntry(long channelId, long startUtcMillis, long endUtcMillis, + boolean blocked) { + this(channelId, null, null, startUtcMillis, endUtcMillis, blocked); + } + + private TableEntry(long channelId, Program program, long entryStartUtcMillis, + long entryEndUtcMillis, boolean isBlocked) { + this(channelId, program, null, entryStartUtcMillis, entryEndUtcMillis, isBlocked); + } + + private TableEntry(long channelId, Program program, ScheduledRecording scheduledRecording, + long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) { + this.channelId = channelId; + this.program = program; + this.scheduledRecording = scheduledRecording; + this.entryStartUtcMillis = entryStartUtcMillis; + this.entryEndUtcMillis = entryEndUtcMillis; + mIsBlocked = isBlocked; + } + + /** + * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. + */ + long getId() { + // using a negative entryEndUtcMillis keeps it from conflicting with program Id + return program != null ? program.getId() : -entryEndUtcMillis; + } + + /** + * Returns true if this is a gap. + */ + boolean isGap() { + return !Program.isValid(program); + } + + /** + * Returns true if this channel is blocked. + */ + boolean isBlocked() { + return mIsBlocked; + } + + /** + * Returns true if this program is on the air. + */ + boolean isCurrentProgram() { + long current = System.currentTimeMillis(); + return entryStartUtcMillis <= current && entryEndUtcMillis > current; + } + + /** + * Returns if this program has the genre. + */ + boolean hasGenre(int genreId) { + return !isGap() && program.hasGenre(genreId); + } + + /** + * Returns the width of table entry, in pixels. + */ + int getWidth() { + return GuideUtils.convertMillisToPixel(entryStartUtcMillis, entryEndUtcMillis); + } + + @Override + public String toString() { + return "TableEntry{" + + "hashCode=" + hashCode() + + ", channelId=" + channelId + + ", program=" + program + + ", startTime=" + Utils.toTimeString(entryStartUtcMillis) + + ", endTimeTime=" + Utils.toTimeString(entryEndUtcMillis) + "}"; } - setTimeRange(fromUtcMillis, toUtcMillis); } - /** - * Returned the scrolled(shifted) time in milliseconds. - */ - public long getShiftedTime() { - return mFromUtcMillis - mStartUtcMillis; + interface Listener { + void onGenresUpdated(); + void onChannelsUpdated(); + void onTimeRangeUpdated(); } - /** - * Returns the start time set by {@link #updateInitialTimeRange}. - */ - public long getStartTime() { - return mStartUtcMillis; + interface TableEntriesUpdatedListener { + void onTableEntriesUpdated(); } - /** - * Returns the program index of the program with {@code entryId} or -1 if not found. - */ - public int getProgramIdIndex(long channelId, long entryId) { - List entries = mChannelIdEntriesMap.get(channelId); - if (entries != null) { - for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getId() == entryId) { - return i; - } - } - } - return -1; + interface TableEntryChangedListener { + void onTableEntryChanged(TableEntry entry); } - /** - * Returns the program index of the program at {@code time} or -1 if not found. - */ - public int getProgramIndexAtTime(long channelId, long time) { - List entries = mChannelIdEntriesMap.get(channelId); - for (int i = 0; i < entries.size(); ++i) { - TableEntry entry = entries.get(i); - if (entry.entryStartUtcMillis <= time - && time < entry.entryEndUtcMillis) { - return i; - } - } - return -1; + static class ListenerAdapter implements Listener { + @Override + public void onGenresUpdated() { } + + @Override + public void onChannelsUpdated() { } + + @Override + public void onTimeRangeUpdated() { } } } diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index 2c98ab2d..fefc724c 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -21,9 +21,11 @@ import android.graphics.Rect; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.util.Log; +import android.util.Range; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import com.android.tv.MainActivity; import com.android.tv.data.Channel; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.Utils; @@ -37,6 +39,7 @@ public class ProgramRow extends TimelineGridView { private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1); private static final long HALF_HOUR_MILLIS = ONE_HOUR_MILLIS / 2; + private ProgramGuide mProgramGuide; private ProgramManager mProgramManager; private boolean mKeepFocusToCurrentProgram; @@ -44,8 +47,8 @@ public class ProgramRow extends TimelineGridView { interface ChildFocusListener { /** - * Is called after focus is moved. It used {@link ChildFocusListener#isChild} to decide if - * old and new focuses are listener's children. + * Is called after focus is moved. Caller should check if old and new focuses are + * listener's children. * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. */ void onChildFocus(View oldFocus, View newFocus); @@ -213,7 +216,6 @@ public class ProgramRow extends TimelineGridView { // so give focus back in onChildAttachedToWindow(). mKeepFocusToCurrentProgram = true; } - // TODO: Try to keep focus for non-current program. } super.onChildDetachedFromWindow(child); } @@ -237,16 +239,18 @@ public class ProgramRow extends TimelineGridView { @Override public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - // Give focus to the current program by default. - // Note that this logic is used only if requestFocus() is called to the ProgramRow, - // so focus finding logic will not be blocked by this. - View currentProgram = getCurrentProgramView(); - if (currentProgram != null) { - return currentProgram.requestFocus(); + ProgramGrid programGrid = mProgramGuide.getProgramGrid(); + + // Give focus according to the previous focused range + Range focusRange = programGrid.getFocusRange(); + View nextFocus = GuideUtils.findNextFocusedProgram(this, focusRange.getLower(), + focusRange.getUpper(), programGrid.isKeepCurrentProgramFocused()); + + if (nextFocus != null) { + return nextFocus.requestFocus(); } if (DEBUG) Log.d(TAG, "onRequestFocusInDescendants"); - boolean result = super.onRequestFocusInDescendants(direction, previouslyFocusedRect); if (!result) { // The default focus search logic of LeanbackLibrary is sometimes failed. @@ -276,10 +280,11 @@ public class ProgramRow extends TimelineGridView { } /** - * Sets the instance of {@link ProgramManager} + * Sets the instance of {@link ProgramGuide} */ - public void setProgramManager(ProgramManager programManager) { - mProgramManager = programManager; + public void setProgramGuide(ProgramGuide programGuide) { + mProgramGuide = programGuide; + mProgramManager = programGuide.getProgramManager(); } /** @@ -300,7 +305,7 @@ public class ProgramRow extends TimelineGridView { .scrollToPositionWithOffset(position, offset); // Workaround to b/31598505. When a program's duration is too long, // RecyclerView.onScrolled() will not be called after scrollToPositionWithOffset(). - // Therefore we have to update children's visible areas by ourselves in theis case. + // Therefore we have to update children's visible areas by ourselves in this case. // Since scrollToPositionWithOffset() will call requestLayout(), we can listen to this // behavior to ensure program items' visible areas are correctly updated after layouts // are adjusted, i.e., scrolling is over. diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index e4a67972..99f853b1 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -45,19 +45,21 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.tv.R; import com.android.tv.TvApplication; +import com.android.tv.common.TvCommonUtils; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.Program.CriticScore; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; +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; @@ -73,7 +75,7 @@ import java.util.List; /** * Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */ -public class ProgramTableAdapter extends RecyclerView.Adapter +class ProgramTableAdapter extends RecyclerView.Adapter implements ProgramManager.TableEntryChangedListener { private static final String TAG = "ProgramTableAdapter"; private static final boolean DEBUG = false; @@ -112,8 +114,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter payloads) { + public void onBindViewHolder(ProgramRowViewHolder holder, int position, List payloads) { if (!payloads.isEmpty()) { holder.updateDetailView(); } else { @@ -225,11 +226,11 @@ public class ProgramTableAdapter extends RecyclerView.Adapter createCriticScoreLogoCallback( - ProgramRowHolder holder, final long programId, ImageView logoView) { - return new ImageLoaderCallback(holder) { + private static ImageLoaderCallback createCriticScoreLogoCallback( + ProgramRowViewHolder holder, final long programId, ImageView logoView) { + return new ImageLoaderCallback(holder) { @Override - public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logoImage) { + public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logoImage) { if (logoImage == null || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null || holder.mSelectedEntry.program.getId() != programId) { @@ -776,11 +780,11 @@ public class ProgramTableAdapter extends RecyclerView.Adapter createProgramPosterArtCallback( - ProgramRowHolder holder, final Program program) { - return new ImageLoaderCallback(holder) { + private static ImageLoaderCallback createProgramPosterArtCallback( + ProgramRowViewHolder holder, final Program program) { + return new ImageLoaderCallback(holder) { @Override - public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap posterArt) { + public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap posterArt) { if (posterArt == null || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null) { return; @@ -794,11 +798,11 @@ public class ProgramTableAdapter extends RecyclerView.Adapter createChannelLogoLoadedCallback( - ProgramRowHolder holder, final long channelId) { - return new ImageLoaderCallback(holder) { + private static ImageLoaderCallback createChannelLogoLoadedCallback( + ProgramRowViewHolder holder, final long channelId) { + return new ImageLoaderCallback(holder) { @Override - public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) { + public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { if (logo == null || holder.mChannel == null || holder.mChannel.getId() != channelId) { return; @@ -808,11 +812,11 @@ public class ProgramTableAdapter extends RecyclerView.Adapter createTvInputLogoLoadedCallback( - final TvInputInfo info, ProgramRowHolder holder) { - return new ImageLoaderCallback(holder) { + private static ImageLoaderCallback createTvInputLogoLoadedCallback( + final TvInputInfo info, ProgramRowViewHolder holder) { + return new ImageLoaderCallback(holder) { @Override - public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) { + public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { if (logo != null && holder.mChannel != null && info.getId() .equals(holder.mChannel.getInputId())) { holder.updateInputLogoInternal(logo); diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java index 868fed46..d9e96a40 100644 --- a/src/com/android/tv/guide/TimeListAdapter.java +++ b/src/com/android/tv/guide/TimeListAdapter.java @@ -16,6 +16,7 @@ package com.android.tv.guide; +import android.content.Context; import android.content.res.Resources; import android.support.v7.widget.RecyclerView; import android.text.format.DateFormat; @@ -25,26 +26,40 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.tv.R; +import com.android.tv.util.Utils; import java.util.Date; +import java.util.Locale; import java.util.concurrent.TimeUnit; /** * Adapts the time range from {@link ProgramManager} to the timeline header row of the program * guide table. */ -public class TimeListAdapter extends RecyclerView.Adapter { +class TimeListAdapter extends RecyclerView.Adapter { private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30); + + // Ex. 3:00 AM + private static final String TIME_PATTERN_SAME_DAY = "h:mm a"; + // Ex. Oct 21, 3:00 AM + private static final String TIME_PATTERN_DIFFERENT_DAY = "MMM d, h:mm a"; + private static int sRowHeaderOverlapping; // Nearest half hour at or before the start time. private long mStartUtcMs; + private final String mTimePatternSameDay; + private final String mTimePatternDifferentDay; - public TimeListAdapter(Resources res) { + TimeListAdapter(Resources res) { if (sRowHeaderOverlapping == 0) { sRowHeaderOverlapping = Math.abs(res.getDimensionPixelOffset( R.dimen.program_guide_table_header_row_overlap)); } + Locale locale = res.getConfiguration().locale; + mTimePatternSameDay = DateFormat.getBestDateTimePattern(locale, TIME_PATTERN_SAME_DAY); + mTimePatternDifferentDay = + DateFormat.getBestDateTimePattern(locale, TIME_PATTERN_DIFFERENT_DAY); } public void update(long startTimeMs) { @@ -68,10 +83,14 @@ public class TimeListAdapter extends RecyclerView.Adapter, Parcelable { + // Name of the third-party library. + private final String mLibraryName; + // Byte offset in the file to the start of the license text. + private final long mLicenseOffset; + // Byte length of the license text. + private final int mLicenseLength; + // Path to the archive that has bundled licenses. + // Empty string if the license is bundled in the apk itself. + private final String mPath; + + /** + * Create an object representing a stored license. The text for all licenses is stored in a + * single file, so the offset and length describe this license's position within the file. + * + * @param path a path to an .apk-compatible archive that contains the license. An empty string + * in case the license is contained within the app itself. + */ + static License create(String libraryName, long licenseOffset, int licenseLength, String path) { + return new License(libraryName, licenseOffset, licenseLength, path); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public License createFromParcel(Parcel in) { + return new License(in); + } + + @Override + public License[] newArray(int size) { + return new License[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mLibraryName); + dest.writeLong(mLicenseOffset); + dest.writeInt(mLicenseLength); + dest.writeString(mPath); + } + + @Override + public int compareTo(License o) { + return mLibraryName.compareToIgnoreCase(o.getLibraryName()); + } + + @Override + public String toString() { + return getLibraryName(); + } + + private License(String libraryName, long licenseOffset, int licenseLength, String path) { + this.mLibraryName = libraryName; + this.mLicenseOffset = licenseOffset; + this.mLicenseLength = licenseLength; + this.mPath = path; + } + + private License(Parcel in) { + mLibraryName = in.readString(); + mLicenseOffset = in.readLong(); + mLicenseLength = in.readInt(); + mPath = in.readString(); + } + + String getLibraryName() { + return mLibraryName; + } + + long getLicenseOffset() { + return mLicenseOffset; + } + + int getLicenseLength() { + return mLicenseLength; + } + + public String getPath() { + return mPath; + } +} diff --git a/src/com/android/tv/license/LicenseDialogFragment.java b/src/com/android/tv/license/LicenseDialogFragment.java new file mode 100644 index 00000000..b0e09776 --- /dev/null +++ b/src/com/android/tv/license/LicenseDialogFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.license; + +import android.app.DialogFragment; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.text.method.ScrollingMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.TextView; + +import com.android.tv.R; +import com.android.tv.dialog.SafeDismissDialogFragment; + +/** A DialogFragment that shows a License in a text view. */ +public class LicenseDialogFragment extends SafeDismissDialogFragment { + public static final String DIALOG_TAG = LicenseDialogFragment.class.getSimpleName(); + + private static final String LICENSE = "LICENSE"; + + private License mLicense; + private String mTrackerLabel; + + /** + * Create a new LicenseDialogFragment to show a particular license. + * + * @param license The License to show. + */ + public static LicenseDialogFragment newInstance(License license) { + LicenseDialogFragment f = new LicenseDialogFragment(); + Bundle args = new Bundle(); + args.putParcelable(LICENSE, license); + f.setArguments(args); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mLicense = getArguments().getParcelable(LICENSE); + String title = mLicense.getLibraryName(); + mTrackerLabel = getArguments().getString(title + "_license"); + int style = + TextUtils.isEmpty(title) + ? DialogFragment.STYLE_NO_TITLE + : DialogFragment.STYLE_NORMAL; + setStyle(style, 0); + } + + @Nullable + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + TextView textView = new TextView(getActivity()); + String licenseText = Licenses.getLicenseText(getContext(), mLicense); + textView.setText(licenseText != null ? licenseText : ""); + textView.setMovementMethod(new ScrollingMovementMethod()); + int verticalOverscan = + getResources().getDimensionPixelSize(R.dimen.vertical_overscan_safe_margin); + int horizontalOverscan = + getResources().getDimensionPixelSize(R.dimen.horizontal_overscan_safe_margin); + textView.setPadding( + horizontalOverscan, verticalOverscan, horizontalOverscan, verticalOverscan); + return textView; + } + + @Override + public void onStart() { + super.onStart(); + // Ensure the dialog is fullscreen, even if the TextView doesn't have its content yet. + getDialog().getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + getDialog().setTitle(mLicense.getLibraryName()); + } + + @Override + public String getTrackerLabel() { + return mTrackerLabel; + } +} diff --git a/src/com/android/tv/license/LicenseSideFragment.java b/src/com/android/tv/license/LicenseSideFragment.java new file mode 100644 index 00000000..fd92467c --- /dev/null +++ b/src/com/android/tv/license/LicenseSideFragment.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.license; + +import android.content.Context; + +import com.android.tv.R; +import com.android.tv.ui.sidepanel.ActionItem; +import com.android.tv.ui.sidepanel.SideFragment; + +import java.util.ArrayList; +import java.util.List; + +/** Opens a dialog showing open source licenses. */ +public final class LicenseSideFragment extends SideFragment { + + public static final String TRACKER_LABEL = "Open Source Licenses"; + + public class LicenseActionItem extends ActionItem { + private final License license; + + public LicenseActionItem(License license) { + super(license.getLibraryName()); + this.license = license; + } + + @Override + protected void onSelected() { + LicenseDialogFragment dialog = LicenseDialogFragment.newInstance(license); + getMainActivity() + .getOverlayManager() + .showDialogFragment(LicenseDialogFragment.DIALOG_TAG, dialog, true); + } + } + + private List licenses; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + licenses = toActionItems(Licenses.getLicenses(context)); + } + + private List toActionItems(ArrayList licenses) { + List items = new ArrayList<>(licenses.size()); + for (License license : licenses) { + items.add(new LicenseActionItem(license)); + } + return items; + } + + @Override + protected String getTitle() { + return getResources().getString(R.string.settings_menu_licenses); + } + + @Override + public String getTrackerLabel() { + return TRACKER_LABEL; + } + + @Override + protected List getItemList() { + return licenses; + } +} diff --git a/src/com/android/tv/license/LicenseUtils.java b/src/com/android/tv/license/LicenseUtils.java index b972aad6..cf3fe751 100644 --- a/src/com/android/tv/license/LicenseUtils.java +++ b/src/com/android/tv/license/LicenseUtils.java @@ -26,21 +26,9 @@ import java.io.InputStream; * Utilities for showing open source licenses. */ public final class LicenseUtils { - public final static String LICENSE_FILE = "file:///android_asset/licenses.html"; public final static String RATING_SOURCE_FILE = "file:///android_asset/rating_sources.html"; - private final static File licenseFile = new File(LICENSE_FILE); - /** - * Checks if the license.html asset is include in the apk. - */ - public static boolean hasLicenses(AssetManager am) { - try (InputStream is = am.open("licenses.html")) { - return true; - } catch (IOException e) { - return false; - } - } /** * Checks if the rating_attribution.html asset is include in the apk. diff --git a/src/com/android/tv/license/Licenses.java b/src/com/android/tv/license/Licenses.java new file mode 100644 index 00000000..4b8a7ffc --- /dev/null +++ b/src/com/android/tv/license/Licenses.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.license; + +import android.content.Context; +import android.support.annotation.RawRes; + +import com.android.tv.R; +import com.android.tv.common.SoftPreconditions; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; + +/** + * A helper for extracting licenses embedded using + * third_party_licenses.build:third_party_licenses(). + */ +public final class Licenses { + + public static final String TAG = "Licenses"; + public static boolean hasLicenses(Context context) { + return !getTextFromResource( + context.getApplicationContext(), R.raw.third_party_license_metadata, 0, -1) + .isEmpty(); + } + + /** Return the licenses bundled into this app. */ + public static ArrayList getLicenses(Context context) { + return getLicenseListFromMetadata( + getTextFromResource( + context.getApplicationContext(), R.raw.third_party_license_metadata, 0, -1), + ""); + } + + /** + * Returns a list of {@link License}s parsed from a license metadata file. + * + * @param metadata a {@code String} containing the contents of a license metadata file. + * @param filePath a path to a package archive with licenses or empty string for the app package + */ + private static ArrayList getLicenseListFromMetadata(String metadata, String filePath) { + String[] entries = metadata.split("\n"); + ArrayList licenses = new ArrayList(entries.length); + for (String entry : entries) { + int delimiter = entry.indexOf(' '); + String[] licenseLocation = entry.substring(0, delimiter).split(":"); + SoftPreconditions.checkState( + licenseLocation.length == 2 && delimiter > 0, + TAG, + "Invalid license meta-data line:\n" + entry); + long licenseOffset = Long.parseLong(licenseLocation[0]); + int licenseLength = Integer.parseInt(licenseLocation[1]); + licenses.add( + License.create( + entry.substring(delimiter + 1), + licenseOffset, + licenseLength, + filePath)); + } + Collections.sort(licenses); + return licenses; + } + + /** Return the text of a bundled license file. */ + public static String getLicenseText(Context context, License license) { + long offset = license.getLicenseOffset(); + int length = license.getLicenseLength(); + return getTextFromResource(context, R.raw.third_party_licenses, offset, length); + } + + private static String getTextFromResource( + Context context, @RawRes int resourcesIdentifier, long offset, int length) { + InputStream stream = + context.getApplicationContext().getResources().openRawResource(resourcesIdentifier); + return getTextFromInputStream(stream, offset, length); + } + + private static String getTextFromInputStream(InputStream stream, long offset, int length) { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream textArray = new ByteArrayOutputStream(); + + try { + stream.skip(offset); + int bytesRemaining = length > 0 ? length : Integer.MAX_VALUE; + int bytes = 0; + + while (bytesRemaining > 0 + && (bytes = stream.read(buffer, 0, Math.min(bytesRemaining, buffer.length))) + != -1) { + textArray.write(buffer, 0, bytes); + bytesRemaining -= bytes; + } + stream.close(); + } catch (IOException e) { + throw new RuntimeException("Failed to read license or metadata text.", e); + } + try { + return textArray.toString("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException( + "Unsupported encoding UTF8. This should always be supported.", e); + } + } +} diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java index 54892cac..2fd70bfb 100644 --- a/src/com/android/tv/menu/ActionCardView.java +++ b/src/com/android/tv/menu/ActionCardView.java @@ -19,8 +19,8 @@ package com.android.tv.menu; import android.content.Context; import android.util.AttributeSet; import android.util.Log; -import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; import com.android.tv.R; @@ -28,7 +28,7 @@ import com.android.tv.R; /** * A view to render an item of TV options. */ -public class ActionCardView extends FrameLayout implements ItemListRowView.CardView { +public class ActionCardView extends RelativeLayout implements ItemListRowView.CardView { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -66,7 +66,7 @@ public class ActionCardView extends FrameLayout implements ItemListRowView.CardV } mIconView.setImageDrawable(action.getDrawable(getContext())); mLabelView.setText(action.getActionName(getContext())); - mStateView.setText(action.getActionDescription(getContext())); + mStateView.setText(action.getActionDescription()); if (action.isEnabled()) { setEnabled(true); setFocusable(true); diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java index bfb5e3f1..94ccd37f 100644 --- a/src/com/android/tv/menu/AppLinkCardView.java +++ b/src/com/android/tv/menu/AppLinkCardView.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.AsyncTask; import android.support.annotation.Nullable; import android.support.v7.graphics.Palette; import android.text.TextUtils; @@ -40,10 +41,12 @@ import com.android.tv.util.BitmapUtils; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; +import java.util.Objects; + /** * A view to render an app link card. */ -public class AppLinkCardView extends BaseCardView { +public class AppLinkCardView extends BaseCardView { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -53,9 +56,9 @@ public class AppLinkCardView extends BaseCardView { private final int mIconHeight; private final int mIconPadding; private final int mIconColorFilter; + private final Drawable mDefaultDrawable; private ImageView mImageView; - private View mGradientView; private TextView mAppInfoView; private View mMetaViewHolder; private Channel mChannel; @@ -82,6 +85,7 @@ public class AppLinkCardView extends BaseCardView { mPackageManager = context.getPackageManager(); mTvInputManagerHelper = ((MainActivity) context).getTvInputManagerHelper(); mIconColorFilter = getResources().getColor(R.color.app_link_card_icon_color_filter, null); + mDefaultDrawable = getResources().getDrawable(R.drawable.ic_recent_thumbnail_default, null); } /** @@ -92,65 +96,153 @@ public class AppLinkCardView extends BaseCardView { } @Override - public void onBind(Channel channel, boolean selected) { + public void onBind(ChannelsRowItem item, boolean selected) { + Channel newChannel = item.getChannel(); + boolean channelChanged = !Objects.equals(mChannel, newChannel); + String previousPosterArtUri = mChannel == null ? null : mChannel.getAppLinkPosterArtUri(); + boolean posterArtChanged = previousPosterArtUri == null + || newChannel.getAppLinkPosterArtUri() == null + || !TextUtils.equals(previousPosterArtUri, newChannel.getAppLinkPosterArtUri()); + mChannel = newChannel; if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected + Log.d(TAG, "onBind(channelName=" + mChannel.getDisplayName() + ", selected=" + selected + ")"); } - mChannel = channel; ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()); - int linkType = mChannel.getAppLinkType(getContext()); - mIntent = mChannel.getAppLinkIntent(getContext()); + if (channelChanged) { + int linkType = mChannel.getAppLinkType(getContext()); + mIntent = mChannel.getAppLinkIntent(getContext()); - switch (linkType) { - case Channel.APP_LINK_TYPE_CHANNEL: - setText(mChannel.getAppLinkText()); - mAppInfoView.setVisibility(VISIBLE); - mGradientView.setVisibility(VISIBLE); - mAppInfoView.setCompoundDrawablePadding(mIconPadding); - mAppInfoView.setCompoundDrawables(null, null, null, null); - mAppInfoView.setText(mPackageManager.getApplicationLabel(appInfo)); - if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) { - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, - mIconWidth, mIconHeight, createChannelLogoCallback(this, mChannel, - Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON)); - } else if (appInfo.icon != 0) { - Drawable appIcon = mPackageManager.getApplicationIcon(appInfo); - BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); - appIcon.setBounds(0, 0, mIconWidth, mIconHeight); - mAppInfoView.setCompoundDrawables(appIcon, null, null, null); - } - break; - case Channel.APP_LINK_TYPE_APP: - setText(getContext().getString( - R.string.channels_item_app_link_app_launcher, - mPackageManager.getApplicationLabel(appInfo))); - mAppInfoView.setVisibility(GONE); - mGradientView.setVisibility(GONE); - break; - default: - mAppInfoView.setVisibility(GONE); - mGradientView.setVisibility(GONE); - Log.d(TAG, "Should not be here."); - } + CharSequence appLabel; + switch (linkType) { + case Channel.APP_LINK_TYPE_CHANNEL: + setText(mChannel.getAppLinkText()); + mAppInfoView.setVisibility(VISIBLE); + mAppInfoView.setCompoundDrawablePadding(mIconPadding); + mAppInfoView.setCompoundDrawablesRelative(null, null, null, null); + appLabel = mTvInputManagerHelper + .getTvInputApplicationLabel(mChannel.getInputId()); + if (appLabel != null) { + mAppInfoView.setText(appLabel); + } else { + new AsyncTask() { + private final String mLoadTvInputId = mChannel.getInputId(); - if (mChannel.getAppLinkColor() == 0) { - mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background); - } else { - mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor()); - } + @Override + protected CharSequence doInBackground(Void... params) { + if (appInfo != null) { + return mPackageManager.getApplicationLabel(appInfo); + } + return null; + } - if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) { - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, - mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel, - Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); - } else { - setCardImageWithBanner(appInfo); + @Override + protected void onPostExecute(CharSequence appLabel) { + mTvInputManagerHelper.setTvInputApplicationLabel( + mLoadTvInputId, appLabel); + if (mLoadTvInputId != mChannel.getInputId() + || !isAttachedToWindow()) { + return; + } + mAppInfoView.setText(appLabel); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) { + mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, + mIconWidth, mIconHeight, createChannelLogoCallback( + this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON)); + } else if (appInfo.icon != 0) { + Drawable appIcon = mTvInputManagerHelper + .getTvInputApplicationIcon(mChannel.getInputId()); + if (appIcon != null) { + BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); + appIcon.setBounds(0, 0, mIconWidth, mIconHeight); + mAppInfoView.setCompoundDrawablesRelative(appIcon, null, null, null); + } else { + new AsyncTask() { + private final String mLoadTvInputId = mChannel.getInputId(); + + @Override + protected Drawable doInBackground(Void... params) { + return mPackageManager.getApplicationIcon(appInfo); + } + + @Override + protected void onPostExecute(Drawable appIcon) { + mTvInputManagerHelper.setTvInputApplicationIcon( + mLoadTvInputId, appIcon); + if (!mLoadTvInputId.equals(mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); + appIcon.setBounds(0, 0, mIconWidth, mIconHeight); + mAppInfoView.setCompoundDrawablesRelative( + appIcon, null, null, null); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + break; + case Channel.APP_LINK_TYPE_APP: + appLabel = mTvInputManagerHelper + .getTvInputApplicationLabel(mChannel.getInputId()); + if (appLabel != null) { + setText(getContext() + .getString(R.string.channels_item_app_link_app_launcher, appLabel)); + } else { + new AsyncTask() { + private final String mLoadTvInputId = mChannel.getInputId(); + + @Override + protected CharSequence doInBackground(Void... params) { + if (appInfo != null) { + return mPackageManager.getApplicationLabel(appInfo); + } + return null; + } + + @Override + protected void onPostExecute(CharSequence appLabel) { + mTvInputManagerHelper.setTvInputApplicationLabel( + mLoadTvInputId, appLabel); + if (!mLoadTvInputId.equals(mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + setText(getContext() + .getString( + R.string.channels_item_app_link_app_launcher, + appLabel)); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + mAppInfoView.setVisibility(GONE); + break; + default: + mAppInfoView.setVisibility(GONE); + Log.d(TAG, "Should not be here."); + } + + if (mChannel.getAppLinkColor() == 0) { + mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background); + } else { + mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor()); + } } - // Call super.onBind() at the end intentionally. In order to correctly handle extension of - // text view, text should be set before calling super.onBind. - super.onBind(channel, selected); + if (posterArtChanged) { + mImageView.setImageDrawable(mDefaultDrawable); + mImageView.setForeground(null); + if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) { + mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, + mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel, + Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); + } else { + setCardImageWithBanner(appInfo); + } + } + super.onBind(item, selected); } private static ImageLoader.ImageLoaderCallback createChannelLogoCallback( @@ -182,13 +274,14 @@ public class AppLinkCardView extends BaseCardView { } } BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable); - mAppInfoView.setCompoundDrawables(drawable, null, null, null); + mAppInfoView.setCompoundDrawablesRelative(drawable, null, null, null); } else if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART) { if (bitmap == null) { setCardImageWithBanner( mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId())); } else { mImageView.setImageBitmap(bitmap); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); if (mChannel.getAppLinkColor() == 0) { extractAndSetMetaViewBackgroundColor(bitmap); } @@ -200,7 +293,6 @@ public class AppLinkCardView extends BaseCardView { protected void onFinishInflate() { super.onFinishInflate(); mImageView = (ImageView) findViewById(R.id.image); - mGradientView = findViewById(R.id.image_gradient); mAppInfoView = (TextView) findViewById(R.id.app_info); mMetaViewHolder = findViewById(R.id.app_link_text_holder); } @@ -209,37 +301,85 @@ public class AppLinkCardView extends BaseCardView { // 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner, // 5) Application icon, and 6) default image. private void setCardImageWithBanner(ApplicationInfo appInfo) { - Drawable banner = null; - if (mIntent != null) { - try { - banner = mPackageManager.getActivityBanner(mIntent); - if (banner == null) { - banner = mPackageManager.getActivityIcon(mIntent); + new AsyncTask() { + private String mLoadTvInputId = mChannel.getInputId(); + @Override + protected Drawable doInBackground(Void... params) { + Drawable banner = null; + if (mIntent != null) { + try { + banner = mPackageManager.getActivityBanner(mIntent); + if (banner == null) { + banner = mPackageManager.getActivityIcon(mIntent); + } + } catch (PackageManager.NameNotFoundException e) { + // do nothing. + } } - } catch (PackageManager.NameNotFoundException e) { - // do nothing. + return banner; } - } - if (banner == null && appInfo != null) { - if (appInfo.banner != 0) { - banner = mPackageManager.getApplicationBanner(appInfo); - } - if (banner == null && appInfo.icon != 0) { - banner = mPackageManager.getApplicationIcon(appInfo); + @Override + protected void onPostExecute(Drawable banner) { + if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) { + return; + } + if (banner != null) { + setCardImageWithBannerInternal(banner); + } else { + setCardImageWithApplicationInfoBanner(appInfo); + } } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void setCardImageWithApplicationInfoBanner(ApplicationInfo appInfo) { + Drawable appBanner = + mTvInputManagerHelper.getTvInputApplicationBanner(mChannel.getInputId()); + if (appBanner != null) { + setCardImageWithBannerInternal(appBanner); + } else { + new AsyncTask() { + private final String mLoadTvInputId = mChannel.getInputId(); + @Override + protected Drawable doInBackground(Void... params) { + Drawable banner = null; + if (appInfo != null) { + if (appInfo.banner != 0) { + banner = mPackageManager.getApplicationBanner(appInfo); + } + if (banner == null && appInfo.icon != 0) { + banner = mPackageManager.getApplicationIcon(appInfo); + } + } + return banner; + } + + @Override + protected void onPostExecute(Drawable banner) { + mTvInputManagerHelper.setTvInputApplicationBanner(mLoadTvInputId, banner); + if (!TextUtils.equals(mLoadTvInputId, mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + setCardImageWithBannerInternal(banner); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + } + private void setCardImageWithBannerInternal(Drawable banner) { if (banner == null) { - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); + mImageView.setImageDrawable(mDefaultDrawable); mImageView.setBackgroundResource(R.color.channel_card); } else { - Bitmap bitmap = - Bitmap.createBitmap(mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap( + mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight); banner.draw(canvas); mImageView.setImageDrawable(banner); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); if (mChannel.getAppLinkColor() == 0) { extractAndSetMetaViewBackgroundColor(bitmap); } diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java index c6a34a5d..4c5e6c78 100644 --- a/src/com/android/tv/menu/BaseCardView.java +++ b/src/com/android/tv/menu/BaseCardView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Outline; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -35,9 +36,6 @@ import com.android.tv.R; * A base class to render a card. */ public abstract class BaseCardView extends LinearLayout implements ItemListRowView.CardView { - private static final String TAG = "BaseCardView"; - private static final boolean DEBUG = false; - private static final float SCALE_FACTOR_0F = 0f; private static final float SCALE_FACTOR_1F = 1f; @@ -57,6 +55,11 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo private TextView mTextViewFocused; private final int mCardImageWidth; private final float mCardHeight; + private boolean mSelected; + + private int mTextResId; + private String mTextString; + private boolean mTextChanged; public BaseCardView(Context context) { this(context, null); @@ -103,23 +106,9 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo /** * Called when the view is displayed. - * - * Before onBind is called, this view's text should be set to determine if it'll be extended - * or not in focus state. */ @Override public void onBind(T item, boolean selected) { - if (mTextView != null && mTextViewFocused != null) { - mTextViewFocused.measure( - MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1; - if (mExtendViewOnFocus) { - setTextViewFocusedAlpha(selected ? 1f : 0f); - } else { - setTextViewFocusedAlpha(1f); - } - } setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } @@ -128,6 +117,7 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo @Override public void onSelected() { + mSelected = true; if (isAttachedToWindow() && getVisibility() == View.VISIBLE) { startFocusAnimation(SCALE_FACTOR_1F); } else { @@ -138,6 +128,7 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo @Override public void onDeselected() { + mSelected = false; if (isAttachedToWindow() && getVisibility() == View.VISIBLE) { startFocusAnimation(SCALE_FACTOR_0F); } else { @@ -150,11 +141,17 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo * Sets text of this card view. */ public void setText(int resId) { - if (mTextViewFocused != null) { - mTextViewFocused.setText(resId); - } - if (mTextView != null) { - mTextView.setText(resId); + if (mTextResId != resId) { + mTextResId = resId; + mTextString = null; + mTextChanged = true; + if (mTextViewFocused != null) { + mTextViewFocused.setText(resId); + } + if (mTextView != null) { + mTextView.setText(resId); + } + onTextViewUpdated(); } } @@ -162,12 +159,33 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo * Sets text of this card view. */ public void setText(String text) { - if (mTextViewFocused != null) { - mTextViewFocused.setText(text); + if (!TextUtils.equals(text, mTextString)) { + mTextString = text; + mTextResId = 0; + mTextChanged = true; + if (mTextViewFocused != null) { + mTextViewFocused.setText(text); + } + if (mTextView != null) { + mTextView.setText(text); + } + onTextViewUpdated(); } - if (mTextView != null) { - mTextView.setText(text); + } + + private void onTextViewUpdated() { + if (mTextView != null && mTextViewFocused != null) { + mTextViewFocused.measure( + MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1; + if (mExtendViewOnFocus) { + setTextViewFocusedAlpha(mSelected ? 1f : 0f); + } else { + setTextViewFocusedAlpha(1f); + } } + setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } /** @@ -209,12 +227,18 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo setScaleX(scale); setScaleY(scale); setTranslationZ(mFocusTranslationZ * animatedValue); - if (mExtendViewOnFocus) { + if (mTextView != null && mTextViewFocused != null) { ViewGroup.LayoutParams params = mTextView.getLayoutParams(); - params.height = Math.round(mTextViewHeight - + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue); - setTextViewLayoutParams(params); - setTextViewFocusedAlpha(animatedValue); + int height = mExtendViewOnFocus ? Math.round(mTextViewHeight + + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue) + : (int) mTextViewHeight; + if (height != params.height) { + params.height = height; + setTextViewLayoutParams(params); + } + if (mExtendViewOnFocus) { + setTextViewFocusedAlpha(animatedValue); + } } } diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java index 1c8015a6..2ecb6af7 100644 --- a/src/com/android/tv/menu/ChannelCardView.java +++ b/src/com/android/tv/menu/ChannelCardView.java @@ -34,10 +34,12 @@ import com.android.tv.data.Program; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.ImageLoader; +import java.util.Objects; + /** * A view to render channel card. */ -public class ChannelCardView extends BaseCardView { +public class ChannelCardView extends BaseCardView { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -45,11 +47,11 @@ public class ChannelCardView extends BaseCardView { private final int mCardImageHeight; private ImageView mImageView; - private View mGradientView; private TextView mChannelNumberNameView; private ProgressBar mProgressBar; private Channel mChannel; private Program mProgram; + private String mPosterArtUri; private final MainActivity mMainActivity; public ChannelCardView(Context context) { @@ -71,39 +73,72 @@ public class ChannelCardView extends BaseCardView { protected void onFinishInflate() { super.onFinishInflate(); mImageView = (ImageView) findViewById(R.id.image); - mGradientView = findViewById(R.id.image_gradient); + mImageView.setBackgroundResource(R.color.channel_card); mChannelNumberNameView = (TextView) findViewById(R.id.channel_number_and_name); mProgressBar = (ProgressBar) findViewById(R.id.progress); } @Override - public void onBind(Channel channel, boolean selected) { + public void onBind(ChannelsRowItem item, boolean selected) { if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected - + ")"); + Log.d(TAG, "onBind(channelName=" + item.getChannel().getDisplayName() + ", selected=" + + selected + ")"); } - mChannel = channel; - mProgram = null; - mChannelNumberNameView.setText(mChannel.getDisplayText()); - mChannelNumberNameView.setVisibility(VISIBLE); - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); - mImageView.setBackgroundResource(R.color.channel_card); - mGradientView.setVisibility(View.GONE); - mProgressBar.setVisibility(GONE); + updateChannel(item); + updateProgram(); + super.onBind(item, selected); + } - setTextViewEnabled(true); - if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() - && mChannel.isLocked()) { + private void updateChannel(ChannelsRowItem item) { + if (!item.getChannel().equals(mChannel)) { + mChannel = item.getChannel(); + mChannelNumberNameView.setText(mChannel.getDisplayText()); + mChannelNumberNameView.setVisibility(VISIBLE); + } + } + + private void updateProgram() { + ParentalControlSettings parental = mMainActivity.getParentalControlSettings(); + if (parental.isParentalControlsEnabled() && mChannel.isLocked()) { setText(R.string.program_title_for_blocked_channel); - return; + mProgram = null; + } else { + Program currentProgram = + mMainActivity.getProgramDataManager().getCurrentProgram(mChannel.getId()); + if (!Objects.equals(currentProgram, mProgram)) { + mProgram = currentProgram; + if (mProgram == null || TextUtils.isEmpty(mProgram.getTitle())) { + setTextViewEnabled(false); + setText(R.string.program_title_for_no_information); + } else { + setTextViewEnabled(true); + setText(mProgram.getTitle()); + } + } + } + if (mProgram == null) { + mProgressBar.setVisibility(GONE); + setPosterArt(null); } else { - setText(""); + // Update progress. + mProgressBar.setVisibility(View.VISIBLE); + long startTime = mProgram.getStartTimeUtcMillis(); + long endTime = mProgram.getEndTimeUtcMillis(); + long currTime = System.currentTimeMillis(); + if (currTime <= startTime) { + mProgressBar.setProgress(0); + } else if (currTime >= endTime) { + mProgressBar.setProgress(100); + } else { + mProgressBar.setProgress( + (int) (100 * (currTime - startTime) / (endTime - startTime))); + } + // Update image. + if (!parental.isParentalControlsEnabled() + || !parental.isRatingBlocked(mProgram.getContentRatings())) { + setPosterArt(mProgram.getPosterArtUri()); + } } - - updateProgramInformation(); - // Call super.onBind() at the end intentionally. In order to correctly handle extension of - // text view, text should be set before calling super.onBind. - super.onBind(channel, selected); } private static ImageLoader.ImageLoaderCallback createProgramPosterArtCallback( @@ -121,49 +156,20 @@ public class ChannelCardView extends BaseCardView { }; } - private void updatePosterArt(Bitmap posterArt) { - mImageView.setImageBitmap(posterArt); - mGradientView.setVisibility(View.VISIBLE); - } - - private void updateProgramInformation() { - if (mChannel == null) { - return; - } - mProgram = mMainActivity.getProgramDataManager().getCurrentProgram(mChannel.getId()); - if (mProgram == null || TextUtils.isEmpty(mProgram.getTitle())) { - setTextViewEnabled(false); - setText(R.string.program_title_for_no_information); - } else { - setText(mProgram.getTitle()); - } - - if (mProgram == null) { - return; - } - - long startTime = mProgram.getStartTimeUtcMillis(); - long endTime = mProgram.getEndTimeUtcMillis(); - long currTime = System.currentTimeMillis(); - mProgressBar.setVisibility(View.VISIBLE); - if (currTime <= startTime) { - mProgressBar.setProgress(0); - } else if (currTime >= endTime) { - mProgressBar.setProgress(100); - } else { - mProgressBar.setProgress((int) (100 * (currTime - startTime) / (endTime - startTime))); + private void setPosterArt(String posterArtUri) { + if (!TextUtils.equals(mPosterArtUri, posterArtUri)) { + mPosterArtUri = posterArtUri; + if (posterArtUri == null + || !mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight, + createProgramPosterArtCallback(this, mProgram))) { + mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); + mImageView.setForeground(null); + } } + } - if (!(getContext() instanceof MainActivity)) { - Log.e(TAG, "Fails to check program's content rating."); - return; - } - ParentalControlSettings parental = mMainActivity.getParentalControlSettings(); - if ((!parental.isParentalControlsEnabled() - || !parental.isRatingBlocked(mProgram.getContentRatings())) - && !TextUtils.isEmpty(mProgram.getPosterArtUri())) { - mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight, - createProgramPosterArtCallback(this, mProgram)); - } + private void updatePosterArt(Bitmap posterArt) { + mImageView.setImageBitmap(posterArt); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); } -} +} \ No newline at end of file diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java index f932d75d..bc5d6cfb 100644 --- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java +++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java @@ -97,12 +97,13 @@ public class ChannelsPosterPrefetcher { // This executes on the main thread, but since the item list is expected to be about 5 items // and ImageLoader spawns an async task so this is fast enough. 1 ms in local testing. - List channelList = mChannelsAdapter.getItemList(); - if (channelList != null) { - for (Channel channel : channelList) { + List items = mChannelsAdapter.getItemList(); + if (items != null) { + for (ChannelsRowItem item : items) { if (isCanceled) { return; } + Channel channel = item.getChannel(); if (!Channel.isValid(channel)) { continue; } @@ -116,7 +117,7 @@ public class ChannelsPosterPrefetcher { } if (DEBUG) { Log.d(TAG, "doPrefetchImages() finished. ImageLoader may still have async tasks for " - + "channels " + channelList); + + "channels " + items); } } diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java index dedf0993..490d73de 100644 --- a/src/com/android/tv/menu/ChannelsRow.java +++ b/src/com/android/tv/menu/ChannelsRow.java @@ -26,8 +26,14 @@ import com.android.tv.recommendation.Recommender; public class ChannelsRow extends ItemListRow { public static final String ID = ChannelsRow.class.getName(); - private static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5; - private static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10; + /** + * Minimum count for recent channels. + */ + public static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5; + /** + * Maximum count for recent channels. + */ + public static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10; private Recommender mTvRecommendation; private ChannelsRowAdapter mChannelsAdapter; diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java index c8e1bd05..7ff44ea6 100644 --- a/src/com/android/tv/menu/ChannelsRowAdapter.java +++ b/src/com/android/tv/menu/ChannelsRowAdapter.java @@ -31,17 +31,15 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.recommendation.Recommender; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; /** * An adapter of the Channels row. */ -public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter { - private static final String TAG = "ChannelsRowAdapter"; - +public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter { // There are four special cards: guide, setup, dvr, applink. private static final int SIZE_OF_VIEW_TYPE = 5; @@ -51,7 +49,6 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter private final DvrDataManager mDvrDataManager; private final int mMaxCount; private final int mMinCount; - private final int[] mViewType = new int[SIZE_OF_VIEW_TYPE]; private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() { @Override @@ -113,14 +110,12 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter mRecommender = recommender; mMinCount = minCount; mMaxCount = maxCount; + setHasStableIds(true); } @Override public int getItemViewType(int position) { - if (position >= SIZE_OF_VIEW_TYPE) { - return R.layout.menu_card_channel; - } - return mViewType[position]; + return getItemList().get(position).getLayoutId(); } @Override @@ -129,9 +124,12 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter } @Override - public void onBindViewHolder(MyViewHolder viewHolder, int position) { - super.onBindViewHolder(viewHolder, position); + public long getItemId(int position) { + return getItemList().get(position).getItemId(); + } + @Override + public void onBindViewHolder(MyViewHolder viewHolder, int position) { int viewType = getItemViewType(position); if (viewType == R.layout.menu_card_guide) { viewHolder.itemView.setOnClickListener(mGuideOnClickListener); @@ -144,80 +142,158 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter SimpleCardView view = (SimpleCardView) viewHolder.itemView; view.setText(R.string.channels_item_dvr); } else { - viewHolder.itemView.setTag(getItemList().get(position)); + viewHolder.itemView.setTag(getItemList().get(position).getChannel()); viewHolder.itemView.setOnClickListener(mChannelOnClickListener); } + super.onBindViewHolder(viewHolder, position); } @Override public void update() { - List channelList = new ArrayList<>(); - Channel dummyChannel = new Channel.Builder().build(); - // For guide item - channelList.add(dummyChannel); - // For setup item - TvInputManagerHelper inputManager = TvApplication.getSingletons(mContext) - .getTvInputManagerHelper(); - boolean showSetupCard = SetupUtils.getInstance(mContext).hasNewInput(inputManager); - Channel currentChannel = getMainActivity().getCurrentChannel(); - boolean showAppLinkCard = currentChannel != null - && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE - // Sometimes applicationInfo can be null. b/28932537 - && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null; - boolean showDvrCard = false; + if (getItemCount() == 0) { + createItems(); + } else { + updateItems(); + } + } + + private void createItems() { + List items = new ArrayList<>(); + items.add(ChannelsRowItem.GUIDE_ITEM); + if (needToShowSetupItem()) { + items.add(ChannelsRowItem.SETUP_ITEM); + } + if (needToShowDvrItem()) { + items.add(ChannelsRowItem.DVR_ITEM); + } + if (needToShowAppLinkItem()) { + ChannelsRowItem.APP_LINK_ITEM.setChannel( + new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + items.add(ChannelsRowItem.APP_LINK_ITEM); + } + for (Channel channel : getRecentChannels()) { + items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); + } + setItemList(items); + } + + private void updateItems() { + List items = getItemList(); + // The current index of the item list to iterate. It starts from 1 because the first item + // (GUIDE) is always visible and not updated. + int currentIndex = 1; + if (updateItem(needToShowSetupItem(), ChannelsRowItem.SETUP_ITEM, currentIndex)) { + ++currentIndex; + } + if (updateItem(needToShowDvrItem(), ChannelsRowItem.DVR_ITEM, currentIndex)) { + ++currentIndex; + } + if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) { + if (!getMainActivity().getCurrentChannel() + .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) { + ChannelsRowItem.APP_LINK_ITEM.setChannel( + new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + notifyItemChanged(currentIndex); + } + ++currentIndex; + } + int numOldChannels = items.size() - currentIndex; + if (numOldChannels > 0) { + while (items.size() > currentIndex) { + items.remove(items.size() - 1); + } + notifyItemRangeRemoved(currentIndex, numOldChannels); + } + for (Channel channel : getRecentChannels()) { + items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); + } + int numNewChannels = items.size() - currentIndex; + if (numNewChannels > 0) { + notifyItemRangeInserted(currentIndex, numNewChannels); + } + } + + /** + * Returns {@code true} if the item should be shown. + */ + private boolean updateItem(boolean needToShow, ChannelsRowItem item, int index) { + List items = getItemList(); + boolean isItemInList = index < items.size() && item.equals(items.get(index)); + if (needToShow && !isItemInList) { + items.add(index, item); + notifyItemInserted(index); + } else if (!needToShow && isItemInList) { + items.remove(index); + notifyItemRemoved(index); + } + return needToShow; + } + + private boolean needToShowSetupItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + return SetupUtils.getInstance(mContext).hasNewInput(inputManager); + } + + private boolean needToShowDvrItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); if (mDvrDataManager != null) { for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) { if (info.canRecord()) { - showDvrCard = true; - break; + return true; } } } + return false; + } - mViewType[0] = R.layout.menu_card_guide; - int index = 1; - if (showSetupCard) { - channelList.add(dummyChannel); - mViewType[index++] = R.layout.menu_card_setup; - } - if (showDvrCard) { - channelList.add(dummyChannel); - mViewType[index++] = R.layout.menu_card_dvr; - } - if (showAppLinkCard) { - channelList.add(currentChannel); - mViewType[index++] = R.layout.menu_card_app_link; - } - for ( ; index < mViewType.length; ++index) { - mViewType[index] = R.layout.menu_card_channel; - } - channelList.addAll(getRecentChannels()); - setItemList(channelList); + private boolean needToShowAppLinkItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + Channel currentChannel = getMainActivity().getCurrentChannel(); + return currentChannel != null + && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE + // Sometimes applicationInfo can be null. b/28932537 + && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null; } private List getRecentChannels() { List channelList = new ArrayList<>(); + long currentChannelId = getMainActivity().getCurrentChannelId(); + ArrayDeque recentChannels = getMainActivity().getRecentChannels(); + // Add the last watched channel as the first one. + for (long channelId : recentChannels) { + if (addChannelToList( + channelList, mRecommender.getChannel(channelId), currentChannelId)) { + break; + } + } + // Add the recommended channels. for (Channel channel : mRecommender.recommendChannels(mMaxCount)) { - if (channel.isBrowsable()) { - channelList.add(channel); + if (channelList.size() >= mMaxCount) { + break; } + addChannelToList(channelList, channel, currentChannelId); } - int count = channelList.size(); // If the number of recommended channels is not enough, add more from the recent channel // list. - if (count < mMinCount) { - for (long channelId : getMainActivity().getRecentChannels()) { - Channel channel = mRecommender.getChannel(channelId); - if (channel == null || channelList.contains(channel) - || !channel.isBrowsable()) { - continue; - } - channelList.add(channel); - if (++count >= mMinCount) { - break; - } + for (long channelId : recentChannels) { + if (channelList.size() >= mMinCount) { + break; } + addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId); } return channelList; } + + private static boolean addChannelToList( + List channelList, Channel channel, long currentChannelId) { + if (channel == null || channel.getId() == currentChannelId + || channelList.contains(channel) || !channel.isBrowsable()) { + return false; + } + channelList.add(channel); + return true; + } } diff --git a/src/com/android/tv/menu/ChannelsRowItem.java b/src/com/android/tv/menu/ChannelsRowItem.java new file mode 100644 index 00000000..c35189ec --- /dev/null +++ b/src/com/android/tv/menu/ChannelsRowItem.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.menu; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.android.tv.R; +import com.android.tv.data.Channel; + +/** + * A class for the items in channels row. + */ +public class ChannelsRowItem { + /** The item ID for guide item */ + public static final int GUIDE_ITEM_ID = -1; + /** The item ID for setup item */ + public static final int SETUP_ITEM_ID = -2; + /** The item ID for DVR item */ + public static final int DVR_ITEM_ID = -3; + /** The item ID for app link item */ + public static final int APP_LINK_ITEM_ID = -4; + + /** The item which represents the guide. */ + public static final ChannelsRowItem GUIDE_ITEM = + new ChannelsRowItem(GUIDE_ITEM_ID, R.layout.menu_card_guide); + /** The item which represents the setup. */ + public static final ChannelsRowItem SETUP_ITEM = + new ChannelsRowItem(SETUP_ITEM_ID, R.layout.menu_card_setup); + /** The item which represents the DVR. */ + public static final ChannelsRowItem DVR_ITEM = + new ChannelsRowItem(DVR_ITEM_ID, R.layout.menu_card_dvr); + /** The item which represents the app link. */ + public static final ChannelsRowItem APP_LINK_ITEM = + new ChannelsRowItem(APP_LINK_ITEM_ID, R.layout.menu_card_app_link); + + private final long mItemId; + @NonNull private Channel mChannel; + private final int mLayoutId; + + public ChannelsRowItem(@NonNull Channel channel, int layoutId) { + this(channel.getId(), layoutId); + mChannel = channel; + } + + private ChannelsRowItem(long itemId, int layoutId) { + mItemId = itemId; + mLayoutId = layoutId; + } + + /** + * Returns the channel for this item. + */ + @NonNull + public Channel getChannel() { + return mChannel; + } + + /** + * Sets the channel. + */ + public void setChannel(@NonNull Channel channel) { + mChannel = channel; + } + + /** + * Returns the layout resource ID to represent this item. + */ + public int getLayoutId() { + return mLayoutId; + } + + /** + * Returns the unique ID for this item. + */ + public long getItemId() { + return mItemId; + } + + @Override + public String toString() { + return "ChannelsRowItem{" + + "itemId=" + mItemId + + ", layoutId=" + mLayoutId + + ", channel=" + mChannel + "}"; + } +} diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java index 4919c595..cbeee936 100644 --- a/src/com/android/tv/menu/ItemListRowView.java +++ b/src/com/android/tv/menu/ItemListRowView.java @@ -28,6 +28,7 @@ import android.view.ViewGroup; import com.android.tv.MainActivity; import com.android.tv.R; +import com.android.tv.util.ViewCache; import java.util.Collections; import java.util.List; @@ -69,6 +70,8 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe protected void onFinishInflate() { super.onFinishInflate(); mListView = (HorizontalGridView) getContentsView(); + // Disable the position change animation of the cards. + mListView.setItemAnimator(null); } @Override @@ -194,9 +197,24 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe return mItemList.size(); } + /** + * Returns the position of the item. + */ + protected int getItemPosition(T item) { + return mItemList.indexOf(item); + } + + /** + * Returns {@code true} if the item list contains the item, otherwise {@code false}. + */ + protected boolean containsItem(T item) { + return mItemList.contains(item); + } + @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = mLayoutInflater.inflate(getLayoutResId(viewType), parent, false); + View view = ViewCache.getInstance().getOrCreateView( + mLayoutInflater, getLayoutResId(viewType), parent); return new MyViewHolder(view); } diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java index 1160a5b5..e373de61 100644 --- a/src/com/android/tv/menu/Menu.java +++ b/src/com/android/tv/menu/Menu.java @@ -26,24 +26,28 @@ import android.os.Message; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.v17.leanback.widget.HorizontalGridView; import android.util.Log; import com.android.tv.ChannelTuner; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.TvOptionsManager; import com.android.tv.analytics.Tracker; import com.android.tv.common.TvCommonUtils; import com.android.tv.common.WeakHandler; import com.android.tv.menu.MenuRowFactory.PartnerRow; -import com.android.tv.menu.MenuRowFactory.PipOptionsRow; import com.android.tv.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; +import com.android.tv.util.DurationTimer; +import com.android.tv.util.ViewCache; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * A class which controls the menu. @@ -81,10 +85,21 @@ public class Menu { sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_NEXT } + private static final Map PRELOAD_VIEW_IDS = new HashMap<>(); + static { + PRELOAD_VIEW_IDS.put(R.layout.menu_card_guide, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_setup, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_dvr, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_app_link, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_channel, ChannelsRow.MAX_COUNT_FOR_RECENT_CHANNELS); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_action, 7); + } + private static final String SCREEN_NAME = "Menu"; private static final int MSG_HIDE_MENU = 1000; + private final Context mContext; private final IMenuView mMenuView; private final Tracker mTracker; private final DurationTimer mVisibleTimer = new DurationTimer(); @@ -103,15 +118,16 @@ public class Menu { @VisibleForTesting Menu(Context context, IMenuView menuView, MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { - this(context, null, menuView, menuRowFactory, onMenuVisibilityChangeListener); + this(context, null, null, menuView, menuRowFactory, onMenuVisibilityChangeListener); } - public Menu(Context context, TunableTvView tvView, IMenuView menuView, - MenuRowFactory menuRowFactory, + public Menu(Context context, TunableTvView tvView, TvOptionsManager optionsManager, + IMenuView menuView, MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { + mContext = context; mMenuView = menuView; mTracker = TvApplication.getSingletons(context).getTracker(); - mMenuUpdater = new MenuUpdater(context, tvView, this); + mMenuUpdater = new MenuUpdater(this, tvView, optionsManager); Resources res = context.getResources(); mShowDurationMillis = res.getInteger(R.integer.menu_show_duration); mOnMenuVisibilityChangeListener = onMenuVisibilityChangeListener; @@ -130,7 +146,6 @@ public class Menu { addMenuRow(menuRowFactory.createMenuRow(this, ChannelsRow.class)); addMenuRow(menuRowFactory.createMenuRow(this, PartnerRow.class)); addMenuRow(menuRowFactory.createMenuRow(this, TvOptionsRow.class)); - addMenuRow(menuRowFactory.createMenuRow(this, PipOptionsRow.class)); mMenuView.setMenuRows(mMenuRows); } @@ -159,6 +174,16 @@ public class Menu { mHandler.removeCallbacksAndMessages(null); } + /** + * Preloads the item view used for the menu. + */ + public void preloadItemViews() { + HorizontalGridView fakeParent = new HorizontalGridView(mContext); + for (int id : PRELOAD_VIEW_IDS.keySet()) { + ViewCache.getInstance().putView(mContext, id, fakeParent, PRELOAD_VIEW_IDS.get(id)); + } + } + /** * Shows the main menu. * @@ -293,9 +318,7 @@ public class Menu { */ public void onStreamInfoChanged() { if (DEBUG) Log.d(TAG, "update options row in main menu"); - for (MenuRow row : mMenuRows) { - row.onStreamInfoChanged(); - } + mMenuUpdater.onStreamInfoChanged(); } @VisibleForTesting diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java index 0d59552a..b4356059 100644 --- a/src/com/android/tv/menu/MenuAction.java +++ b/src/com/android/tv/menu/MenuAction.java @@ -20,9 +20,9 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; -import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvOptionsManager; +import com.android.tv.TvOptionsManager.OptionType; /** * A class to define possible actions from main menu. @@ -36,12 +36,9 @@ public class MenuAction { public static final MenuAction SELECT_DISPLAY_MODE_ACTION = new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE, R.drawable.ic_tvoption_aspect); - public static final MenuAction PIP_IN_APP_ACTION = - new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_IN_APP_PIP, - R.drawable.ic_tvoption_pip); public static final MenuAction SYSTEMWIDE_PIP_ACTION = new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP, - R.drawable.ic_pip_option_layout2); + R.drawable.ic_tvoption_pip); public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION = new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO, R.drawable.ic_tvoption_multi_track); @@ -51,34 +48,36 @@ public class MenuAction { public static final MenuAction DEV_ACTION = new MenuAction(R.string.options_item_developer, TvOptionsManager.OPTION_DEVELOPER, R.drawable.ic_developer_mode_tv_white_48dp); - // TODO: Change the icon. public static final MenuAction SETTINGS_ACTION = new MenuAction(R.string.options_item_settings, TvOptionsManager.OPTION_SETTINGS, R.drawable.ic_settings); - // Actions in the PIP option row. - public static final MenuAction PIP_SELECT_INPUT_ACTION = - new MenuAction(R.string.pip_options_item_source, TvOptionsManager.OPTION_PIP_INPUT, - R.drawable.ic_pip_option_input); - public static final MenuAction PIP_SWAP_ACTION = - new MenuAction(R.string.pip_options_item_swap, TvOptionsManager.OPTION_PIP_SWAP, - R.drawable.ic_pip_option_swap); - public static final MenuAction PIP_SOUND_ACTION = - new MenuAction(R.string.pip_options_item_sound, TvOptionsManager.OPTION_PIP_SOUND, - R.drawable.ic_pip_option_swap_audio); - public static final MenuAction PIP_LAYOUT_ACTION = - new MenuAction(R.string.pip_options_item_layout, TvOptionsManager.OPTION_PIP_LAYOUT, - R.drawable.ic_pip_option_layout1); - public static final MenuAction PIP_SIZE_ACTION = - new MenuAction(R.string.pip_options_item_size, TvOptionsManager.OPTION_PIP_SIZE, - R.drawable.ic_pip_option_size); private final String mActionName; private final int mActionNameResId; - private final int mType; + @OptionType private final int mType; + private String mActionDescription; private Drawable mDrawable; private int mDrawableResId; private boolean mEnabled = true; + /** + * Sets the action description. Returns {@code trye} if the description is changed. + */ + public static boolean setActionDescription(MenuAction action, String actionDescription) { + String oldDescription = action.mActionDescription; + action.mActionDescription = actionDescription; + return !TextUtils.equals(action.mActionDescription, oldDescription); + } + + /** + * Enables or disables the action. Returns {@code true} if the value is changed. + */ + public static boolean setEnabled(MenuAction action, boolean enabled) { + boolean changed = action.mEnabled != enabled; + action.mEnabled = enabled; + return changed; + } + public MenuAction(int actionNameResId, int type, int drawableResId) { mActionName = null; mActionNameResId = actionNameResId; @@ -102,11 +101,11 @@ public class MenuAction { return context.getString(mActionNameResId); } - public String getActionDescription(Context context) { - return ((MainActivity) context).getTvOptionsManager().getOptionString(mType); + public String getActionDescription() { + return mActionDescription; } - public int getType() { + @OptionType public int getType() { return mType; } @@ -120,28 +119,10 @@ public class MenuAction { return mDrawable; } - /** - * Sets drawable resource id. - * - * @return {@code true} if drawable is changed. - */ - public boolean setDrawableResId(int resId) { - if (mDrawableResId == resId) { - return false; - } - mDrawable = null; - mDrawableResId = resId; - return true; - } - public boolean isEnabled() { return mEnabled; } - public void setEnabled(boolean enabled) { - mEnabled = enabled; - } - public int getActionNameResId() { return mActionNameResId; } diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java index 6c767247..173d4004 100644 --- a/src/com/android/tv/menu/MenuLayoutManager.java +++ b/src/com/android/tv/menu/MenuLayoutManager.java @@ -28,6 +28,7 @@ import android.support.annotation.UiThread; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.Property; import android.view.View; @@ -56,12 +57,14 @@ public class MenuLayoutManager { // The visible duration of the title before it is hidden. private static final long TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS = TimeUnit.SECONDS.toMillis(2); + private static final int INVALID_POSITION = -1; private final MenuView mMenuView; private final List mMenuRows = new ArrayList<>(); private final List mMenuRowViews = new ArrayList<>(); private final List mRemovingRowViews = new ArrayList<>(); - private int mSelectedPosition = -1; + private int mSelectedPosition = INVALID_POSITION; + private int mPendingSelectedPosition = INVALID_POSITION; private final int mRowAlignFromBottom; private final int mRowContentsPaddingTop; @@ -130,8 +133,8 @@ public class MenuLayoutManager { MenuRowView currentView = mMenuRowViews.get(mSelectedPosition); if (currentView.getVisibility() == View.GONE) { // If the selected row is not visible, select the first visible row. - int firstVisiblePosition = findNextVisiblePosition(-1); - if (firstVisiblePosition != -1) { + int firstVisiblePosition = findNextVisiblePosition(INVALID_POSITION); + if (firstVisiblePosition != INVALID_POSITION) { mSelectedPosition = firstVisiblePosition; } else { // No rows are visible. @@ -157,6 +160,10 @@ public class MenuLayoutManager { view.onDeselected(); } } + + if (mPendingSelectedPosition != INVALID_POSITION) { + setSelectedPositionSmooth(mPendingSelectedPosition); + } } private int findNextVisiblePosition(int start) { @@ -166,7 +173,7 @@ public class MenuLayoutManager { return i; } } - return -1; + return INVALID_POSITION; } private void dumpChildren(String prefix) { @@ -327,6 +334,7 @@ public class MenuLayoutManager { mMenuRowViews.get(mSelectedPosition).onDeselected(); } mSelectedPosition = position; + mPendingSelectedPosition = INVALID_POSITION; if (Utils.isIndexValid(mMenuRowViews, mSelectedPosition)) { mMenuRowViews.get(mSelectedPosition).onSelected(false); } @@ -380,14 +388,29 @@ public class MenuLayoutManager { // again from the intermediate state. mTitleFadeOutAnimator.cancel(); } - final int oldPosition = mSelectedPosition; - mSelectedPosition = position; if (DEBUG) dumpChildren("startRowAnimation()"); - MenuRowView currentView = mMenuRowViews.get(position); // Show the children of the next row. - currentView.getTitleView().setVisibility(View.VISIBLE); - currentView.getContentsView().setVisibility(View.VISIBLE); + final MenuRowView currentView = mMenuRowViews.get(position); + TextView currentTitleView = currentView.getTitleView(); + View currentContentsView = currentView.getContentsView(); + currentTitleView.setVisibility(View.VISIBLE); + currentContentsView.setVisibility(View.VISIBLE); + if (currentView instanceof PlayControlsRowView) { + ((PlayControlsRowView) currentView).onPreselected(); + } + // When contents view's visibility is gone, layouting might be delayed until it's shown and + // thus cause onBindViewHolder() and menu action updating occurs in front of users' sight. + // Therefore we call requestLayout() here if there are pending adapter updates. + if (currentContentsView instanceof RecyclerView + && ((RecyclerView) currentContentsView).hasPendingAdapterUpdates()) { + currentContentsView.requestLayout(); + mPendingSelectedPosition = position; + return; + } + final int oldPosition = mSelectedPosition; + mSelectedPosition = position; + mPendingSelectedPosition = INVALID_POSITION; // Request focus after the new contents view shows up. mMenuView.requestFocus(); if (mTempTitleViewForOld == null) { @@ -407,7 +430,7 @@ public class MenuLayoutManager { // Old row. MenuRow oldRow = mMenuRows.get(oldPosition); - MenuRowView oldView = mMenuRowViews.get(oldPosition); + final MenuRowView oldView = mMenuRowViews.get(oldPosition); View oldContentsView = oldView.getContentsView(); // Old contents view. animators.add(createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) @@ -468,8 +491,6 @@ public class MenuLayoutManager { } // Current row. Rect currentLayoutRect = new Rect(layouts.get(position)); - TextView currentTitleView = currentView.getTitleView(); - View currentContentsView = currentView.getContentsView(); currentContentsView.setAlpha(0.0f); if (scrollDown) { // Current title view. @@ -529,7 +550,7 @@ public class MenuLayoutManager { int nextPosition; if (scrollDown) { nextPosition = findNextVisiblePosition(position); - if (nextPosition != -1) { + if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); Rect nextLayoutRect = layouts.get(nextPosition); animators.add(createTranslationYAnimator(nextView, @@ -539,7 +560,7 @@ public class MenuLayoutManager { } } else { nextPosition = findNextVisiblePosition(oldPosition); - if (nextPosition != -1) { + if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); animators.add(createTranslationYAnimator(nextView, 0, mRowScrollUpAnimationOffset)); animators.add(createAlphaAnimator(nextView, @@ -572,9 +593,8 @@ public class MenuLayoutManager { for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { holder.property.set(holder.view, holder.value); } - oldTitleView.setVisibility(View.VISIBLE); - mMenuRowViews.get(oldPosition).onDeselected(); - mMenuRowViews.get(position).onSelected(true); + oldView.onDeselected(); + currentView.onSelected(true); mTempTitleViewForOld.setVisibility(View.GONE); mTempTitleViewForCurrent.setVisibility(View.GONE); layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(), diff --git a/src/com/android/tv/menu/MenuRow.java b/src/com/android/tv/menu/MenuRow.java index 6f98e615..47804f11 100644 --- a/src/com/android/tv/menu/MenuRow.java +++ b/src/com/android/tv/menu/MenuRow.java @@ -122,11 +122,6 @@ public abstract class MenuRow { */ public void onRecentChannelsChanged() { } - /** - * This method is called when stream information is changed. - */ - public void onStreamInfoChanged() { } - /** * Returns whether to hide the title when the row is selected. */ diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java index c67a0e04..570cfb8f 100644 --- a/src/com/android/tv/menu/MenuRowFactory.java +++ b/src/com/android/tv/menu/MenuRowFactory.java @@ -67,8 +67,6 @@ public class MenuRowFactory { } else if (TvOptionsRow.class.equals(key)) { return new TvOptionsRow(mMainActivity, menu, mTvCustomizationManager .getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW)); - } else if (PipOptionsRow.class.equals(key)) { - return new PipOptionsRow(mMainActivity, menu); } return null; } @@ -77,36 +75,15 @@ public class MenuRowFactory { * A menu row which represents the TV options row. */ public static class TvOptionsRow extends ItemListRow { + /** + * The ID of the row. + */ + public static final String ID = TvOptionsRow.class.getName(); + private TvOptionsRow(Context context, Menu menu, List customActions) { super(context, menu, R.string.menu_title_options, R.dimen.action_card_height, new TvOptionsRowAdapter(context, customActions)); } - - @Override - public void onStreamInfoChanged() { - if (getMenu().isActive()) { - update(); - } - } - } - - /** - * A menu row which represents the PIP options row. - */ - public static class PipOptionsRow extends ItemListRow { - private final MainActivity mMainActivity; - - private PipOptionsRow(Context context, Menu menu) { - super(context, menu, R.string.menu_title_pip_options, R.dimen.action_card_height, - new PipOptionsRowAdapter(context)); - mMainActivity = (MainActivity) context; - } - - @Override - public boolean isVisible() { - // TODO: Remove the dependency on MainActivity. - return super.isVisible() && mMainActivity.isPipEnabled(); - } } /** diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java index 075b299e..18416c85 100644 --- a/src/com/android/tv/menu/MenuUpdater.java +++ b/src/com/android/tv/menu/MenuUpdater.java @@ -16,11 +16,14 @@ package com.android.tv.menu; -import android.content.Context; import android.support.annotation.Nullable; 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.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener; @@ -30,10 +33,10 @@ import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener; *

As the menu is updated when it shows up, this class handles only the dynamic updates. */ public class MenuUpdater { - // Can be null for testing. - @Nullable - private final TunableTvView mTvView; private final Menu mMenu; + // Can be null for testing. + @Nullable private final TunableTvView mTvView; + @Nullable private final TvOptionsManager mOptionsManager; private ChannelTuner mChannelTuner; private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() { @@ -42,7 +45,7 @@ public class MenuUpdater { @Override public void onBrowsableChannelListChanged() { - mMenu.update(); + mMenu.update(ChannelsRow.ID); } @Override @@ -53,10 +56,17 @@ public class MenuUpdater { mMenu.update(ChannelsRow.ID); } }; + private final OptionChangedListener mOptionChangeListener = new OptionChangedListener() { + @Override + public void onOptionChanged(@OptionType int optionType, String newString) { + mMenu.update(TvOptionsRow.ID); + } + }; - public MenuUpdater(Context context, TunableTvView tvView, Menu menu) { - mTvView = tvView; + public MenuUpdater(Menu menu, TunableTvView tvView, TvOptionsManager optionsManager) { mMenu = menu; + mTvView = tvView; + mOptionsManager = optionsManager; if (mTvView != null) { mTvView.setOnScreenBlockedListener(new OnScreenBlockingChangedListener() { @Override @@ -65,11 +75,18 @@ public class MenuUpdater { } }); } + if (mOptionsManager != null) { + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, + mOptionChangeListener); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, + mOptionChangeListener); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, + mOptionChangeListener); + } } /** - * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready - * or not available any more. + * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready. */ public void setChannelTuner(ChannelTuner channelTuner) { if (mChannelTuner != null) { @@ -79,7 +96,13 @@ public class MenuUpdater { if (mChannelTuner != null) { mChannelTuner.addListener(mChannelTunerListener); } - mMenu.update(); + } + + /** + * Called when the stream information changes. + */ + public void onStreamInfoChanged() { + mMenu.update(TvOptionsRow.ID); } /** @@ -92,5 +115,10 @@ public class MenuUpdater { if (mTvView != null) { mTvView.setOnScreenBlockedListener(null); } + if (mOptionsManager != null) { + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, null); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, null); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, null); + } } } diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java index 93bd0a4d..dd6194a1 100644 --- a/src/com/android/tv/menu/OptionsRowAdapter.java +++ b/src/com/android/tv/menu/OptionsRowAdapter.java @@ -21,8 +21,6 @@ import android.view.View; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.TvOptionsManager; -import com.android.tv.TvOptionsManager.OptionChangedListener; import com.android.tv.analytics.Tracker; import java.util.List; @@ -66,12 +64,9 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< public void update() { if (mActionList == null) { mActionList = createActions(); - updateActions(); setItemList(mActionList); } else { - if (updateActions()) { - setItemList(mActionList); - } + updateActions(); } } @@ -81,7 +76,7 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< } protected abstract List createActions(); - protected abstract boolean updateActions(); + protected abstract void updateActions(); protected abstract void executeAction(int type); /** @@ -93,37 +88,6 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< return mActionList.get(position); } - /** - * Sets the action at the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void setAction(int position, MenuAction action) { - mActionList.set(position, action); - } - - /** - * Adds an action to the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void addAction(int position, MenuAction action) { - mActionList.add(position, action); - } - - /** - * Removes an action at the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void removeAction(int position) { - mActionList.remove(position); - } - - protected int getActionSize() { - return mActionList.size(); - } - @Override public void onBindViewHolder(MyViewHolder viewHolder, int position) { super.onBindViewHolder(viewHolder, position); @@ -139,14 +103,4 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< // be preserved. return mActionList.get(position).getType(); } - - protected void setOptionChangedListener(final MenuAction action) { - TvOptionsManager om = getMainActivity().getTvOptionsManager(); - om.setOptionChangedListener(action.getType(), new OptionChangedListener() { - @Override - public void onOptionChanged(String newOption) { - setItemList(mActionList); - } - }); - } } diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java index f3e09f80..c8249a4c 100644 --- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java +++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java @@ -38,8 +38,7 @@ public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter { } @Override - protected boolean updateActions() { + protected void updateActions() { // TODO: Support adding description for custom actions. - return false; } } diff --git a/src/com/android/tv/menu/PipOptionsRowAdapter.java b/src/com/android/tv/menu/PipOptionsRowAdapter.java deleted file mode 100644 index 87203e9d..00000000 --- a/src/com/android/tv/menu/PipOptionsRowAdapter.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.menu; - -import android.content.Context; -import android.text.TextUtils; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.TvOptionsManager; -import com.android.tv.ui.TvViewUiManager; -import com.android.tv.ui.sidepanel.PipInputSelectorFragment; -import com.android.tv.util.PipInputManager.PipInput; -import com.android.tv.util.TvSettings; - -import java.util.ArrayList; -import java.util.List; - -/* - * An adapter of PIP options. - */ -public class PipOptionsRowAdapter extends OptionsRowAdapter { - private static final int[] DRAWABLE_ID_FOR_LAYOUT = { - R.drawable.ic_pip_option_layout1, - R.drawable.ic_pip_option_layout2, - R.drawable.ic_pip_option_layout3, - R.drawable.ic_pip_option_layout4, - R.drawable.ic_pip_option_layout5 }; - - private final TvOptionsManager mTvOptionsManager; - private final TvViewUiManager mTvViewUiManager; - - public PipOptionsRowAdapter(Context context) { - super(context); - mTvOptionsManager = getMainActivity().getTvOptionsManager(); - mTvViewUiManager = getMainActivity().getTvViewUiManager(); - } - - @Override - protected List createActions() { - List actionList = new ArrayList<>(); - actionList.add(MenuAction.PIP_SELECT_INPUT_ACTION); - actionList.add(MenuAction.PIP_SWAP_ACTION); - actionList.add(MenuAction.PIP_SOUND_ACTION); - actionList.add(MenuAction.PIP_LAYOUT_ACTION); - actionList.add(MenuAction.PIP_SIZE_ACTION); - for (MenuAction action : actionList) { - setOptionChangedListener(action); - } - return actionList; - } - - @Override - public boolean updateActions() { - boolean changed = false; - if (updateSelectInputAction()) { - changed = true; - } - if (updateLayoutAction()) { - changed = true; - } - if (updateSizeAction()) { - changed = true; - } - return changed; - } - - private boolean updateSelectInputAction() { - String oldInputLabel = mTvOptionsManager.getOptionString(TvOptionsManager.OPTION_PIP_INPUT); - - MainActivity tvActivity = getMainActivity(); - PipInput newInput = tvActivity.getPipInputManager().getPipInput(tvActivity.getPipChannel()); - String newInputLabel = newInput == null ? null : newInput.getLabel(); - - if (!TextUtils.equals(oldInputLabel, newInputLabel)) { - mTvOptionsManager.onPipInputChanged(newInputLabel); - return true; - } - return false; - } - - private boolean updateLayoutAction() { - return MenuAction.PIP_LAYOUT_ACTION.setDrawableResId( - DRAWABLE_ID_FOR_LAYOUT[mTvViewUiManager.getPipLayout()]); - } - - private boolean updateSizeAction() { - boolean oldEnabled = MenuAction.PIP_SIZE_ACTION.isEnabled(); - boolean newEnabled = mTvViewUiManager.getPipLayout() != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE; - if (oldEnabled != newEnabled) { - MenuAction.PIP_SIZE_ACTION.setEnabled(newEnabled); - return true; - } - return false; - } - - @Override - protected void executeAction(int type) { - switch (type) { - case TvOptionsManager.OPTION_PIP_INPUT: - getMainActivity().getOverlayManager().getSideFragmentManager().show( - new PipInputSelectorFragment()); - break; - case TvOptionsManager.OPTION_PIP_SWAP: - getMainActivity().swapPip(); - break; - case TvOptionsManager.OPTION_PIP_SOUND: - getMainActivity().togglePipSoundMode(); - break; - case TvOptionsManager.OPTION_PIP_LAYOUT: - int oldLayout = mTvViewUiManager.getPipLayout(); - int newLayout = (oldLayout + 1) % (TvSettings.PIP_LAYOUT_LAST + 1); - mTvViewUiManager.setPipLayout(newLayout, true); - MenuAction.PIP_LAYOUT_ACTION.setDrawableResId(DRAWABLE_ID_FOR_LAYOUT[newLayout]); - break; - case TvOptionsManager.OPTION_PIP_SIZE: - int oldSize = mTvViewUiManager.getPipSize(); - int newSize = (oldSize + 1) % (TvSettings.PIP_SIZE_LAST + 1); - mTvViewUiManager.setPipSize(newSize, true); - break; - } - } -} diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java index aff39db3..77715f28 100644 --- a/src/com/android/tv/menu/PlayControlsButton.java +++ b/src/com/android/tv/menu/PlayControlsButton.java @@ -39,6 +39,9 @@ public class PlayControlsButton extends FrameLayout { private final int mIconColor; private int mIconFocusedColor; + private int mImageResourceId; + private int mTintColor; + public PlayControlsButton(Context context) { this(context, null); } @@ -67,10 +70,21 @@ public class PlayControlsButton extends FrameLayout { * Sets the resource ID of the image to be displayed in the center of this control. */ public void setImageResId(int imageResId) { - mIcon.setImageResource(imageResId); - // Since on foucus changing, icons' color should be switched with animation, + int newTintColor = hasFocus() ? mIconFocusedColor : mIconColor; + if (mImageResourceId != imageResId) { + mImageResourceId = imageResId; + mIcon.setImageResource(imageResId); + updateTint(newTintColor); + } else if (newTintColor != mTintColor) { + updateTint(newTintColor); + } + } + + private void updateTint(int tintColor) { + mTintColor = tintColor; + // Since on focus changing, icons' color should be switched with animation, // as a result, selectors cannot be used to switch colors in this case. - mIcon.getDrawable().setTint(hasFocus() ? mIconFocusedColor : mIconColor); + mIcon.getDrawable().setTint(tintColor); } /** @@ -117,7 +131,9 @@ public class PlayControlsButton extends FrameLayout { } else { mIcon.setVisibility(View.GONE); mLabel.setVisibility(View.VISIBLE); - mLabel.setText(label); + if (!TextUtils.equals(mLabel.getText(), label)) { + mLabel.setText(label); + } } } diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index a620d4dd..4d766788 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -18,10 +18,10 @@ package com.android.tv.menu; import android.content.Context; import android.content.res.Resources; +import android.text.TextUtils; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; -import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; @@ -34,17 +34,16 @@ 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.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrStopRecordingFragment; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.menu.Menu.MenuShowReason; import com.android.tv.ui.TunableTvView; -import com.android.tv.util.Utils; public class PlayControlsRowView extends MenuRowView { private static final int NORMAL_WIDTH_MAX_BUTTON_COUNT = 5; @@ -53,14 +52,10 @@ public class PlayControlsRowView extends MenuRowView { private final int mTimeTextLeftMargin; private final int mTimelineWidth; // Views - private View mBackgroundView; + private TextView mBackgroundView; private View mTimeIndicator; private TextView mTimeText; - private View mProgressEmptyBefore; - private View mProgressWatched; - private View mProgressBuffered; - private View mProgressEmptyAfter; - private View mControlBar; + private PlaybackProgressBar mProgress; private PlayControlsButton mJumpPreviousButton; private PlayControlsButton mRewindButton; private PlayControlsButton mPlayPauseButton; @@ -69,7 +64,6 @@ public class PlayControlsRowView extends MenuRowView { private PlayControlsButton mRecordButton; private TextView mProgramStartTimeText; private TextView mProgramEndTimeText; - private View mUnavailableMessageText; private TunableTvView mTvView; private TimeShiftManager mTimeShiftManager; private final DvrDataManager mDvrDataManager; @@ -83,6 +77,8 @@ public class PlayControlsRowView extends MenuRowView { private final int mNormalButtonMargin; private final int mCompactButtonMargin; + private final String mUnavailableMessage; + private final ScheduledRecordingListener mScheduledRecordingListener = new ScheduledRecordingListener() { @Override @@ -138,6 +134,7 @@ public class PlayControlsRowView extends MenuRowView { mDvrManager = null; } mMainActivity = (MainActivity) context; + mUnavailableMessage = res.getString(R.string.play_controls_unavailable); } @Override @@ -171,14 +168,10 @@ public class PlayControlsRowView extends MenuRowView { super.onFinishInflate(); // Clip the ViewGroup(body) to the rounded rectangle of outline. findViewById(R.id.body).setClipToOutline(true); - mBackgroundView = findViewById(R.id.background); + mBackgroundView = (TextView) findViewById(R.id.background); mTimeIndicator = findViewById(R.id.time_indicator); mTimeText = (TextView) findViewById(R.id.time_text); - mProgressEmptyBefore = findViewById(R.id.timeline_bg_start); - mProgressWatched = findViewById(R.id.watched); - mProgressBuffered = findViewById(R.id.buffered); - mProgressEmptyAfter = findViewById(R.id.timeline_bg_end); - mControlBar = findViewById(R.id.play_control_bar); + mProgress = (PlaybackProgressBar) findViewById(R.id.progress); mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous); mRewindButton = (PlayControlsButton) findViewById(R.id.rewind); mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause); @@ -187,7 +180,6 @@ public class PlayControlsRowView extends MenuRowView { mRecordButton = (PlayControlsButton) findViewById(R.id.record); mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time); mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time); - mUnavailableMessageText = findViewById(R.id.unavailable_text); initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous, R.string.play_controls_description_skip_previous, null, new Runnable() { @@ -195,7 +187,7 @@ public class PlayControlsRowView extends MenuRowView { public void run() { if (mTimeShiftManager.isAvailable()) { mTimeShiftManager.jumpToPrevious(); - updateControls(); + updateControls(true); } } }); @@ -235,7 +227,7 @@ public class PlayControlsRowView extends MenuRowView { public void run() { if (mTimeShiftManager.isAvailable()) { mTimeShiftManager.jumpToNext(); - updateControls(); + updateControls(true); } } }); @@ -265,18 +257,17 @@ public class PlayControlsRowView extends MenuRowView { if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) { Toast.makeText(mMainActivity, R.string.dvr_msg_cannot_record_channel, Toast.LENGTH_SHORT).show(); - } else if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity, - currentChannel.getInputId())) { + } else { Program program = TvApplication.getSingletons(mMainActivity).getProgramDataManager() .getCurrentProgram(currentChannel.getId()); - if (program == null) { - DvrUiHelper.showChannelRecordDurationOptions(mMainActivity, currentChannel); - } else if (DvrUiHelper.handleCreateSchedule(mMainActivity, program)) { - String msg = mMainActivity.getString(R.string.dvr_msg_current_program_scheduled, - program.getTitle(), - Utils.toTimeString(program.getEndTimeUtcMillis(), false)); - Toast.makeText(mMainActivity, msg, Toast.LENGTH_SHORT).show(); - } + DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity, + currentChannel.getInputId(), new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingCurrentProgram(mMainActivity, + currentChannel, program, true); + } + }); } } else if (currentChannel != null) { DvrUiHelper.showStopRecordingDialog(mMainActivity, currentChannel.getId(), @@ -318,39 +309,37 @@ public class PlayControlsRowView extends MenuRowView { @Override public void onAvailabilityChanged() { updateMenuVisibility(); - if (isShown()) { - PlayControlsRowView.this.updateAll(); - } + PlayControlsRowView.this.updateAll(false); } @Override public void onPlayStatusChanged(int status) { updateMenuVisibility(); - if (mTimeShiftManager.isAvailable() && isShown()) { - updateControls(); + if (mTimeShiftManager.isAvailable()) { + updateControls(false); } } @Override public void onRecordTimeRangeChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { - updateControls(); + if (mTimeShiftManager.isAvailable()) { + updateControls(false); } } @Override public void onCurrentPositionChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { + if (mTimeShiftManager.isAvailable()) { initializeTimeline(); - updateControls(); + updateControls(false); } } @Override public void onProgramInfoChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { + if (mTimeShiftManager.isAvailable()) { initializeTimeline(); - updateControls(); + updateControls(false); } } @@ -372,7 +361,8 @@ public class PlayControlsRowView extends MenuRowView { } } }); - updateAll(); + // force update to initialize everything + updateAll(true); } private void initializeTimeline() { @@ -380,6 +370,8 @@ public class PlayControlsRowView extends MenuRowView { mTimeShiftManager.getCurrentPositionMs()); mProgramStartTimeMs = program.getStartTimeUtcMillis(); mProgramEndTimeMs = program.getEndTimeUtcMillis(); + mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs); + updateRecTimeText(); SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs); } @@ -389,10 +381,13 @@ public class PlayControlsRowView extends MenuRowView { getMenu().setKeepVisible(keepMenuVisible); } + public void onPreselected() { + updateControls(true); + } + @Override public void onSelected(boolean showTitle) { super.onSelected(showTitle); - updateControls(); postHideRippleAnimation(); } @@ -474,28 +469,32 @@ public class PlayControlsRowView extends MenuRowView { * Updates the view contents. It is called from the PlayControlsRow. */ public void update() { - updateAll(); + updateAll(false); } - private void updateAll() { + private void updateAll(boolean forceUpdate) { if (mTimeShiftManager.isAvailable() && !mTvView.isScreenBlocked()) { setEnabled(true); initializeTimeline(); mBackgroundView.setEnabled(true); + setTextIfNeeded(mBackgroundView, null); } else { setEnabled(false); mBackgroundView.setEnabled(false); + setTextIfNeeded(mBackgroundView, mUnavailableMessage); } - updateControls(); + // force the controls be updated no matter it's visible or not. + updateControls(forceUpdate); } - private void updateControls() { - updateTime(); - updateProgress(); - updateRecTimeText(); - updateButtons(); - updateRecordButton(); - updateButtonMargin(); + private void updateControls(boolean forceUpdate) { + if (forceUpdate || getContentsView().isShown()) { + updateTime(); + updateProgress(); + updateButtons(); + updateRecordButton(); + updateButtonMargin(); + } } private void updateTime() { @@ -504,70 +503,39 @@ public class PlayControlsRowView extends MenuRowView { mTimeIndicator.setVisibility(View.VISIBLE); } else { mTimeText.setVisibility(View.INVISIBLE); - mTimeIndicator.setVisibility(View.INVISIBLE); + mTimeIndicator.setVisibility(View.GONE); return; } long currentPositionMs = mTimeShiftManager.getCurrentPositionMs(); - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) mTimeText.getLayoutParams(); int currentTimePositionPixel = convertDurationToPixel(currentPositionMs - mProgramStartTimeMs); - params.leftMargin = currentTimePositionPixel + mTimeTextLeftMargin; - mTimeText.setLayoutParams(params); - mTimeText.setText(getTimeString(currentPositionMs)); - params = (ViewGroup.MarginLayoutParams) mTimeIndicator.getLayoutParams(); - params.leftMargin = currentTimePositionPixel + mTimeIndicatorLeftMargin; - mTimeIndicator.setLayoutParams(params); + mTimeText.setTranslationX(currentTimePositionPixel + mTimeTextLeftMargin); + setTextIfNeeded(mTimeText, getTimeString(currentPositionMs)); + mTimeIndicator.setTranslationX(currentTimePositionPixel + mTimeIndicatorLeftMargin); } private void updateProgress() { if (isEnabled()) { - mProgressWatched.setVisibility(View.VISIBLE); - mProgressBuffered.setVisibility(View.VISIBLE); - mProgressEmptyAfter.setVisibility(View.VISIBLE); - } else { - mProgressWatched.setVisibility(View.INVISIBLE); - mProgressBuffered.setVisibility(View.INVISIBLE); - mProgressEmptyAfter.setVisibility(View.INVISIBLE); - if (mProgramStartTimeMs < mProgramEndTimeMs) { - layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, mProgramEndTimeMs); - } else { - // Not initialized yet. - layoutProgress(mProgressEmptyBefore, mTimelineWidth); - } - return; - } - - long progressStartTimeMs = Math.min(mProgramEndTimeMs, + long progressStartTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs())); - long currentPlayingTimeMs = Math.min(mProgramEndTimeMs, + long currentPlayingTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs())); - long progressEndTimeMs = Math.min(mProgramEndTimeMs, + long progressEndTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs())); - - layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, progressStartTimeMs); - layoutProgress(mProgressWatched, progressStartTimeMs, currentPlayingTimeMs); - layoutProgress(mProgressBuffered, currentPlayingTimeMs, progressEndTimeMs); - } - - private void layoutProgress(View progress, long progressStartTimeMs, long progressEndTimeMs) { - layoutProgress(progress, Math.max(0, - convertDurationToPixel(progressEndTimeMs - progressStartTimeMs)) + 1); - } - - private void layoutProgress(View progress, int width) { - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) progress.getLayoutParams(); - params.width = width; - progress.setLayoutParams(params); + mProgress.setProgressRange(progressStartTimeMs - mProgramStartTimeMs, + progressEndTimeMs - mProgramStartTimeMs); + mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs); + } else { + mProgress.setProgressRange(0, 0); + } } private void updateRecTimeText() { if (isEnabled()) { mProgramStartTimeText.setVisibility(View.VISIBLE); - mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs)); + setTextIfNeeded(mProgramStartTimeText, getTimeString(mProgramStartTimeMs)); mProgramEndTimeText.setVisibility(View.VISIBLE); - mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs)); + setTextIfNeeded(mProgramEndTimeText, getTimeString(mProgramEndTimeMs)); } else { mProgramStartTimeText.setVisibility(View.GONE); mProgramEndTimeText.setVisibility(View.GONE); @@ -576,11 +544,17 @@ public class PlayControlsRowView extends MenuRowView { private void updateButtons() { if (isEnabled()) { - mControlBar.setVisibility(View.VISIBLE); - mUnavailableMessageText.setVisibility(View.GONE); + mPlayPauseButton.setVisibility(View.VISIBLE); + mJumpPreviousButton.setVisibility(View.VISIBLE); + mJumpNextButton.setVisibility(View.VISIBLE); + mRewindButton.setVisibility(View.VISIBLE); + mFastForwardButton.setVisibility(View.VISIBLE); } else { - mControlBar.setVisibility(View.INVISIBLE); - mUnavailableMessageText.setVisibility(View.VISIBLE); + mPlayPauseButton.setVisibility(View.GONE); + mJumpPreviousButton.setVisibility(View.GONE); + mJumpNextButton.setVisibility(View.GONE); + mRewindButton.setVisibility(View.GONE); + mFastForwardButton.setVisibility(View.GONE); return; } @@ -622,6 +596,12 @@ public class PlayControlsRowView extends MenuRowView { } private void updateRecordButton() { + if (isEnabled()) { + mRecordButton.setVisibility(VISIBLE); + } else { + mRecordButton.setVisibility(GONE); + return; + } if (!(mDvrManager != null && mDvrManager.isChannelRecordable(mMainActivity.getCurrentChannel()))) { mRecordButton.setVisibility(View.GONE); @@ -682,4 +662,10 @@ public class PlayControlsRowView extends MenuRowView { mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); } } + + private void setTextIfNeeded(TextView textView, String text) { + if (!TextUtils.equals(textView.getText(), text)) { + textView.setText(text); + } + } } diff --git a/src/com/android/tv/menu/PlaybackProgressBar.java b/src/com/android/tv/menu/PlaybackProgressBar.java new file mode 100644 index 00000000..e8061bc6 --- /dev/null +++ b/src/com/android/tv/menu/PlaybackProgressBar.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.menu; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.util.AttributeSet; +import android.view.View; + +import com.android.tv.R; + +/** + * A progress bar control which has two progresses which start in the middle of the control. + */ +public class PlaybackProgressBar extends View { + private final LayerDrawable mProgressDrawable; + private final Drawable mPrimaryDrawable; + private final Drawable mSecondaryDrawable; + private long mMax = 100; + private long mProgressStart = 0; + private long mProgressEnd = 0; + private long mProgress = 0; + + public PlaybackProgressBar(Context context) { + this(context, null); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes); + mProgressDrawable = + (LayerDrawable) a.getDrawable(R.styleable.PlaybackProgressBar_progressDrawable); + mPrimaryDrawable = mProgressDrawable.findDrawableByLayerId(android.R.id.progress); + mSecondaryDrawable = + mProgressDrawable.findDrawableByLayerId(android.R.id.secondaryProgress); + a.recycle(); + refreshProgress(); + } + + @Override + protected void onDraw(Canvas canvas) { + final int saveCount = canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + mProgressDrawable.draw(canvas); + canvas.restoreToCount(saveCount); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + refreshProgress(); + } + + public void setMax(long max) { + if (max < 0) { + max = 0; + } + if (max != mMax) { + mMax = max; + if (mProgressStart > max) { + mProgressStart = max; + } + if (mProgressEnd > max) { + mProgressEnd = max; + } + if (mProgress > max) { + mProgress = max; + } + refreshProgress(); + } + } + + /** + * Sets the start and end position of the progress. + */ + public void setProgressRange(long start, long end) { + start = constrain(start, 0, mMax); + end = constrain(end, start, mMax); + mProgress = constrain(mProgress, start, end); + if (start != mProgressStart || end != mProgressEnd) { + mProgressStart = start; + mProgressEnd = end; + setProgressLevels(); + } + } + + /** + * Sets the progress position. + */ + public void setProgress(long progress) { + progress = constrain(progress, mProgressStart, mProgressEnd); + if (progress != mProgress) { + mProgress = progress; + setProgressLevels(); + } + } + + private long constrain(long value, long min, long max) { + return Math.min(Math.max(value, min), max); + } + + private void refreshProgress() { + int width = getWidth() - getPaddingStart() - getPaddingEnd(); + int height = getHeight() - getPaddingTop() - getPaddingBottom(); + mProgressDrawable.setBounds(0, 0, width, height); + setProgressLevels(); + } + + private void setProgressLevels() { + boolean progressUpdated = setProgressBound(mPrimaryDrawable, mProgressStart, mProgress); + progressUpdated |= setProgressBound(mSecondaryDrawable, mProgress, mProgressEnd); + if (progressUpdated) { + postInvalidate(); + } + } + + private boolean setProgressBound(Drawable drawable, long start, long end) { + Rect oldBounds = drawable.getBounds(); + if (mMax == 0) { + if (!isEqualRect(oldBounds, 0, 0, 0, 0)) { + drawable.setBounds(0, 0, 0, 0); + return true; + } + return false; + } + int width = mProgressDrawable.getBounds().width(); + int height = mProgressDrawable.getBounds().height(); + int left = (int) (width * start / mMax); + int right = (int) (width * end / mMax); + if (!isEqualRect(oldBounds, left, 0, right, height)) { + drawable.setBounds(left, 0, right, height); + return true; + } + return false; + } + + private boolean isEqualRect(Rect rect, int left, int top, int right, int bottom) { + return rect.left == left && rect.top == top && rect.right == right && rect.bottom == bottom; + } +} diff --git a/src/com/android/tv/menu/SimpleCardView.java b/src/com/android/tv/menu/SimpleCardView.java index c99834be..fc5192da 100644 --- a/src/com/android/tv/menu/SimpleCardView.java +++ b/src/com/android/tv/menu/SimpleCardView.java @@ -19,12 +19,10 @@ package com.android.tv.menu; import android.content.Context; import android.util.AttributeSet; -import com.android.tv.data.Channel; - /** * A view to render a guide card. */ -public class SimpleCardView extends BaseCardView { +public class SimpleCardView extends BaseCardView { public SimpleCardView(Context context) { this(context, null, 0); diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java index fb062246..6e035f22 100644 --- a/src/com/android/tv/menu/TvOptionsRowAdapter.java +++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java @@ -21,7 +21,6 @@ import android.media.tv.TvTrackInfo; import android.support.annotation.VisibleForTesting; import com.android.tv.Features; -import com.android.tv.R; import com.android.tv.TvOptionsManager; import com.android.tv.customization.CustomAction; import com.android.tv.data.DisplayMode; @@ -30,7 +29,7 @@ import com.android.tv.ui.sidepanel.ClosedCaptionFragment; import com.android.tv.ui.sidepanel.DeveloperOptionFragment; import com.android.tv.ui.sidepanel.DisplayModeFragment; import com.android.tv.ui.sidepanel.MultiAudioFragment; -import com.android.tv.util.PipInputManager; +import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -39,12 +38,6 @@ import java.util.List; * An adapter of options. */ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { - private static final boolean ENABLE_IN_APP_PIP = false; - - private int mPositionPipAction; - // If mInAppPipAction is false, system-wide PIP is used. - private boolean mInAppPipAction = true; - public TvOptionsRowAdapter(Context context, List customActions) { super(context, customActions); } @@ -53,123 +46,73 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { protected List createBaseActions() { List actionList = new ArrayList<>(); actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION); - setOptionChangedListener(MenuAction.SELECT_CLOSED_CAPTION_ACTION); actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION); - setOptionChangedListener(MenuAction.SELECT_DISPLAY_MODE_ACTION); - actionList.add(MenuAction.PIP_IN_APP_ACTION); - setOptionChangedListener(MenuAction.PIP_IN_APP_ACTION); - mPositionPipAction = actionList.size() - 1; + if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { + actionList.add(MenuAction.SYSTEMWIDE_PIP_ACTION); + } actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); - setOptionChangedListener(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); actionList.add(MenuAction.MORE_CHANNELS_ACTION); - if (DeveloperOptionFragment.shouldShow()) { + if (Utils.isDeveloper()) { actionList.add(MenuAction.DEV_ACTION); } actionList.add(MenuAction.SETTINGS_ACTION); - if (getCustomActions() != null) { - // Adjust Pip action position which will be changed by applying custom actions. - for (CustomAction customAction : getCustomActions()) { - if (customAction.isFront()) { - mPositionPipAction++; - } - } - } - + updateClosedCaptionAction(); + updatePipAction(); + updateMultiAudioAction(); + updateDisplayModeAction(); return actionList; } @Override - protected boolean updateActions() { - boolean changed = false; + protected void updateActions() { + if (updateClosedCaptionAction()) { + notifyItemChanged(getItemPosition(MenuAction.SELECT_CLOSED_CAPTION_ACTION)); + } if (updatePipAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SYSTEMWIDE_PIP_ACTION)); } if (updateMultiAudioAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION)); } if (updateDisplayModeAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SELECT_DISPLAY_MODE_ACTION)); } - return changed; } - private boolean updatePipAction() { - // There are four states. - // Case 1. The device doesn't even have any input for PIP. (e.g. OTT box without HDMI input) - // => Remove the icon. - // Case 2. The device has one or more inputs for PIP but none of them are currently - // available. - // => Show the icon but disable it. - // Case 3. The device has one or more available PIP inputs and now it's tuned off. - // => Show the icon with "Off". - // Case 4. The device has one or more available PIP inputs but it's already turned on. - // => Show the icon with "On". - - boolean changed = false; - - // Case 1 - PipInputManager pipInputManager = getMainActivity().getPipInputManager(); - if (ENABLE_IN_APP_PIP && pipInputManager.getPipInputSize(false) > 1) { - if (!mInAppPipAction) { - removeAction(mPositionPipAction); - addAction(mPositionPipAction, MenuAction.PIP_IN_APP_ACTION); - mInAppPipAction = true; - changed = true; - } - } else { - if (mInAppPipAction) { - removeAction(mPositionPipAction); - mInAppPipAction = false; - if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { - addAction(mPositionPipAction, MenuAction.SYSTEMWIDE_PIP_ACTION); - } - return true; - } - return false; - } + @VisibleForTesting + private boolean updateClosedCaptionAction() { + return updateActionDescription(MenuAction.SELECT_CLOSED_CAPTION_ACTION); + } - // Case 2 - boolean isPipEnabled = getMainActivity().isPipEnabled(); - boolean oldEnabled = MenuAction.PIP_IN_APP_ACTION.isEnabled(); - boolean newEnabled = pipInputManager.getPipInputSize(true) > 0; - if (oldEnabled != newEnabled) { - // Should not disable the item if the PIP is already turned on so that the user can - // force exit it. - if (newEnabled || !isPipEnabled) { - MenuAction.PIP_IN_APP_ACTION.setEnabled(newEnabled); - changed = true; - } + private boolean updatePipAction() { + if (containsItem(MenuAction.SYSTEMWIDE_PIP_ACTION)) { + return MenuAction.setEnabled(MenuAction.SYSTEMWIDE_PIP_ACTION, + !getMainActivity().isScreenBlockedByResourceConflictOrParentalControl()); } - - // Case 3 & 4 - we just need to update the icon. - MenuAction.PIP_IN_APP_ACTION.setDrawableResId( - isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off); - return changed; + return false; } - @VisibleForTesting boolean updateMultiAudioAction() { List audioTracks = getMainActivity().getTracks(TvTrackInfo.TYPE_AUDIO); - boolean oldEnabled = MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled(); - boolean newEnabled = audioTracks != null && audioTracks.size() > 1; - if (oldEnabled != newEnabled) { - MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.setEnabled(newEnabled); - return true; - } - return false; + boolean enabled = audioTracks != null && audioTracks.size() > 1; + // Use "|" operator for non-short-circuit evaluation. + return MenuAction.setEnabled(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION, enabled) + | updateActionDescription(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); } private boolean updateDisplayModeAction() { TvViewUiManager uiManager = getMainActivity().getTvViewUiManager(); - boolean oldEnabled = MenuAction.SELECT_DISPLAY_MODE_ACTION.isEnabled(); - boolean newEnabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) + boolean enabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM); - if (oldEnabled != newEnabled) { - MenuAction.SELECT_DISPLAY_MODE_ACTION.setEnabled(newEnabled); - return true; - } - return false; + // Use "|" operator for non-short-circuit evaluation. + return MenuAction.setEnabled(MenuAction.SELECT_DISPLAY_MODE_ACTION, enabled) + | updateActionDescription(MenuAction.SELECT_DISPLAY_MODE_ACTION); + } + + private boolean updateActionDescription(MenuAction action) { + return MenuAction.setActionDescription(action, + getMainActivity().getTvOptionsManager().getOptionString(action.getType())); } @Override @@ -183,9 +126,6 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { getMainActivity().getOverlayManager().getSideFragmentManager() .show(new DisplayModeFragment()); break; - case TvOptionsManager.OPTION_IN_APP_PIP: - getMainActivity().togglePipView(); - break; case TvOptionsManager.OPTION_SYSTEMWIDE_PIP: getMainActivity().enterPictureInPictureMode(); break; @@ -205,4 +145,4 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { break; } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java index 7607822c..f56daec5 100644 --- a/src/com/android/tv/onboarding/SetupSourcesFragment.java +++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java @@ -38,6 +38,7 @@ import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.TvInputNewComparator; +import com.android.tv.tuner.TunerInputController; import com.android.tv.ui.GuidedActionsStylistWithDivider; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; @@ -204,6 +205,7 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { mChannelDataManager.addListener(mChannelDataManagerListener); super.onCreate(savedInstanceState); mParentFragment = (SetupSourcesFragment) getParentFragment(); + TunerInputController.executeNetworkTunerDiscoveryAsyncTask(getContext()); } @Override diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java index 6b5d6635..5672b793 100644 --- a/src/com/android/tv/parental/ContentRatingSystem.java +++ b/src/com/android/tv/parental/ContentRatingSystem.java @@ -110,6 +110,15 @@ public class ContentRatingSystem { return mRatings; } + public Rating getRating(String name) { + for (Rating rating : mRatings) { + if (TextUtils.equals(rating.getName(), name)) { + return rating; + } + } + return null; + } + public List getSubRatings(){ return mSubRatings; } diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java index 57c25f48..c3bb8c0f 100644 --- a/src/com/android/tv/parental/ContentRatingsManager.java +++ b/src/com/android/tv/parental/ContentRatingsManager.java @@ -20,7 +20,10 @@ import android.content.Context; import android.media.tv.TvContentRating; import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputManager; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import com.android.tv.R; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; @@ -52,6 +55,19 @@ public class ContentRatingsManager { } } + /** + * Returns the content rating system with the give ID. + */ + @Nullable + public ContentRatingSystem getContentRatingSystem(String contentRatingSystemId) { + for (ContentRatingSystem ratingSystem : mContentRatingSystems) { + if (TextUtils.equals(ratingSystem.getId(), contentRatingSystemId)) { + return ratingSystem; + } + } + return null; + } + /** * Returns a new list of all content rating systems defined. */ @@ -64,6 +80,9 @@ public class ContentRatingsManager { * displayed to the user. For example, "TV-PG (L, S)". */ public String getDisplayNameForRating(TvContentRating canonicalRating) { + if (TvContentRating.UNRATED.equals(canonicalRating)) { + return mContext.getResources().getString(R.string.unrated_rating_name); + } Rating rating = getRating(canonicalRating); if (rating == null) { return null; diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java index d7e1846e..2471c565 100644 --- a/src/com/android/tv/parental/ParentalControlSettings.java +++ b/src/com/android/tv/parental/ParentalControlSettings.java @@ -20,6 +20,7 @@ import android.content.Context; import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; +import com.android.tv.experiments.Experiments; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; import com.android.tv.util.TvSettings; @@ -109,6 +110,10 @@ public class ParentalControlSettings { @ContentRatingLevel int currentLevel = getContentRatingLevel(); if (currentLevel != TvSettings.CONTENT_RATING_LEVEL_CUSTOM) { mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, currentLevel); + if (currentLevel != TvSettings.CONTENT_RATING_LEVEL_NONE) { + // UNRATED contents should be blocked unless the rating level is none or custom + mRatings.add(TvContentRating.UNRATED); + } storeRatings(); } } @@ -129,6 +134,11 @@ public class ParentalControlSettings { } } else { mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, level); + if (level != TvSettings.CONTENT_RATING_LEVEL_NONE + && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { + // UNRATED contents should be blocked unless the rating level is none or custom + mRatings.add(TvContentRating.UNRATED); + } } storeRatings(); } @@ -138,6 +148,23 @@ public class ParentalControlSettings { return TvSettings.getContentRatingLevel(mContext); } + /** Sets the blocked status of a unrated contents. */ + public boolean setUnratedBlocked(boolean blocked) { + boolean changed; + if (blocked) { + changed = mRatings.add(TvContentRating.UNRATED); + mTvInputManager.addBlockedRating(TvContentRating.UNRATED); + } else { + changed = mRatings.remove(TvContentRating.UNRATED); + mTvInputManager.removeBlockedRating(TvContentRating.UNRATED); + } + if (changed) { + // change to custom level if the blocked status is changed + changeToCustomLevel(); + } + return changed; + } + /** * Sets the blocked status of a given content rating. *

@@ -172,8 +199,10 @@ public class ParentalControlSettings { * @return The {@link TvContentRating} that is blocked. */ public TvContentRating getBlockedRating(TvContentRating[] ratings) { - if (ratings == null) { - return null; + if (ratings == null || ratings.length <= 0) { + return mTvInputManager.isRatingBlocked(TvContentRating.UNRATED) + ? TvContentRating.UNRATED + : null; } for (TvContentRating rating : ratings) { if (mTvInputManager.isRatingBlocked(rating)) { diff --git a/src/com/android/tv/perf/EventNames.java b/src/com/android/tv/perf/EventNames.java new file mode 100644 index 00000000..6026897b --- /dev/null +++ b/src/com/android/tv/perf/EventNames.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.perf; + +import android.support.annotation.StringDef; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Constants for performance event names. + * + *

Only constants are used to insure no PII is sent. + * + */ +public final class EventNames { + + @Retention(SOURCE) + @StringDef({ + APPLICATION_ONCREATE, + FETCH_EPG_TASK, + MAIN_ACTIVITY_ONCREATE, + MAIN_ACTIVITY_ONSTART, + MAIN_ACTIVITY_ONRESUME, + ON_DEVICE_SEARCH + }) + public @interface EventName {} + + public static final String APPLICATION_ONCREATE = "Application.onCreate"; + public static final String FETCH_EPG_TASK = "FetchEpgTask"; + public static final String MAIN_ACTIVITY_ONCREATE = "MainActivity.onCreate"; + public static final String MAIN_ACTIVITY_ONSTART = "MainActivity.onStart"; + public static final String MAIN_ACTIVITY_ONRESUME = "MainActivity.onResume"; + /** + * Event name for query running time of on-device search in + * {@link com.android.tv.search.LocalSearchProvider}. + */ + public static final String ON_DEVICE_SEARCH = "OnDeviceSearch"; + + private EventNames() {} +} diff --git a/src/com/android/tv/perf/PerformanceMonitor.java b/src/com/android/tv/perf/PerformanceMonitor.java new file mode 100644 index 00000000..40368b41 --- /dev/null +++ b/src/com/android/tv/perf/PerformanceMonitor.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.perf; + +import android.content.Context; + +import static com.android.tv.perf.EventNames.EventName; + +/** Measures Performance. */ +public interface PerformanceMonitor { + + /** + * Starts monitoring application's lifecylce for interesting memory events, captures and records + * memory usage data whenever these events are fired. + */ + void startMemoryMonitor(); + + /** + * Collects and records memory usage for a specific custom event + * + * @param eventName to record + */ + void recordMemory(@EventName String eventName); + + /** + * Starts a timer for a global event to allow measuring the event's latency across activities If + * multiple events with the same name are started, only the last event is retained. + * + * @param eventName for which the timer starts + */ + void startGlobalTimer(@EventName String eventName); + + /** + * Stops a cross activities timer for a specific eventName and records the timer duration. If no + * timer found for the event specified an error will be logged, and recording will be skipped. + * + * @param eventName for which the timer stops + */ + void stopGlobalTimer(@EventName String eventName); + + /** + * Starts a timer to record latency of a specific scenario or event. Use this method to track + * latency in the same method/class + * + * @return TimerEvent object to be used for stopping/recording the timer for a specific event. + * If PerformanceMonitor is not initialized for any reason, an empty TimerEvent will be + * returned. + */ + TimerEvent startTimer(); + + + /** + * Stops timer for a specific event and records the timer duration. passing a null TimerEvent + * will cause this operation to be skipped. + * + * @param event that needs to be stopped + * @param eventName for which the timer stops. This must be constant with no PII. + */ + void stopTimer(TimerEvent event, @EventName String eventName); + + /** + * Starts recording jank for a specific scenario or event. + * + *

If jank recording was started already for an event with the current name, but was never + * stopped, the previously recorded event will be skipped. + * + * @param eventName of the event for which tracking is started + */ + void startJankRecorder(@EventName String eventName); + + /** + * Stops recording jank for a specific event and records the jank event. + * + * @param eventName of the event that needs to be stopped + */ + void stopJankRecorder(@EventName String eventName); + + /** + * Starts activity to display PerformanceMonitor events recorded in local database for debug + * purpose. + * + * @return true if the activity is available to start + */ + boolean startPerformanceMonitorEventDebugActivity(Context context); +} diff --git a/src/com/android/tv/perf/StubPerformanceMonitor.java b/src/com/android/tv/perf/StubPerformanceMonitor.java new file mode 100644 index 00000000..3742a2a7 --- /dev/null +++ b/src/com/android/tv/perf/StubPerformanceMonitor.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.perf; + +import android.app.Application; +import android.content.Context; + +/** Do nothing implementation of {@link PerformanceMonitor}. */ +public final class StubPerformanceMonitor implements PerformanceMonitor { + + private static final TimerEvent TIMER_EVENT = new TimerEvent() {}; + + public static PerformanceMonitor initialize(Application app) { + return new StubPerformanceMonitor(); + } + + @Override + public void startMemoryMonitor() {} + + @Override + public void recordMemory(String customEventName) {} + + @Override + public void startGlobalTimer(String customEventName) {} + + @Override + public void stopGlobalTimer(String customEventName) {} + + @Override + public TimerEvent startTimer() { + return TIMER_EVENT; + } + + @Override + public void stopTimer(TimerEvent event, String name) {} + + @Override + public void startJankRecorder(String eventName) {} + + @Override + public void stopJankRecorder(String eventName) {} + + @Override + public boolean startPerformanceMonitorEventDebugActivity(Context context) { + return false; + } + + public static TimerEvent startBootstrapTimer() { + return new TimerEvent() {}; + } +} diff --git a/src/com/android/tv/perf/TimerEvent.java b/src/com/android/tv/perf/TimerEvent.java new file mode 100644 index 00000000..f8ac6b2d --- /dev/null +++ b/src/com/android/tv/perf/TimerEvent.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.perf; + +/** An event to time */ +public interface TimerEvent {} diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java index 8d6c5a14..369e7d54 100644 --- a/src/com/android/tv/receiver/BootCompletedReceiver.java +++ b/src/com/android/tv/receiver/BootCompletedReceiver.java @@ -21,13 +21,15 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Build; import android.util.Log; import com.android.tv.Features; import com.android.tv.TvActivity; import com.android.tv.TvApplication; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.dvr.DvrRecordingService; +import com.android.tv.dvr.recorder.DvrRecordingService; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.recommendation.ChannelPreviewUpdater; import com.android.tv.recommendation.NotificationService; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.SetupUtils; @@ -49,12 +51,20 @@ public class BootCompletedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + return; + } if (DEBUG) Log.d(TAG, "boot completed " + intent); TvApplication.setCurrentRunningProcess(context, true); - // Start {@link NotificationService}. - Intent notificationIntent = new Intent(context, NotificationService.class); - notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); - context.startService(notificationIntent); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ChannelPreviewUpdater.getInstance(context).updatePreviewDataForChannelsImmediately(); + } else { + Intent notificationIntent = new Intent(context, NotificationService.class); + notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); + context.startService(notificationIntent); + } // Grant permission to already set up packages after the system has finished booting. SetupUtils.grantEpgPermissionToSetUpPackages(context); @@ -74,8 +84,9 @@ public class BootCompletedReceiver extends BroadcastReceiver { } } - if (CommonFeatures.DVR.isEnabled(context)) { - DvrRecordingService.startService(context); + RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler(); + if (scheduler != null) { + scheduler.updateAndStartServiceIfNeeded(); } } } diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java index 8cd4fdf1..cc8e76c4 100644 --- a/src/com/android/tv/receiver/GlobalKeyReceiver.java +++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java @@ -19,7 +19,8 @@ package com.android.tv.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.media.tv.TvContract; +import android.os.AsyncTask; +import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; @@ -31,27 +32,64 @@ import com.android.tv.TvApplication; public class GlobalKeyReceiver extends BroadcastReceiver { private static final boolean DEBUG = false; private static final String TAG = "GlobalKeyReceiver"; + private static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + // Settings.Secure.USER_SETUP_COMPLETE is hidden. + private static final String SETTINGS_USER_SETUP_COMPLETE = "user_setup_complete"; + + private static long sLastEventTime; + private static boolean sUserSetupComplete; @Override public void onReceive(Context context, Intent intent) { + if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + return; + } TvApplication.setCurrentRunningProcess(context, true); + Context appContext = context.getApplicationContext(); + if (DEBUG) Log.d(TAG, "onReceive: " + intent); + if (sUserSetupComplete) { + handleIntent(appContext, intent); + } else { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + return Settings.Secure.getInt(appContext.getContentResolver(), + SETTINGS_USER_SETUP_COMPLETE, 0) != 0; + } + + @Override + protected void onPostExecute(Boolean setupComplete) { + if (DEBUG) Log.d(TAG, "Is setup complete: " + setupComplete); + sUserSetupComplete = setupComplete; + if (sUserSetupComplete) { + handleIntent(appContext, intent); + } + } + }.execute(); + } + } + + private void handleIntent(Context appContext, Intent intent) { if (ACTION_GLOBAL_BUTTON.equals(intent.getAction())) { KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); - if (DEBUG) Log.d(TAG, "onReceive: " + event); + if (DEBUG) Log.d(TAG, "handleIntent: " + event); int keyCode = event.getKeyCode(); int action = event.getAction(); - if (action == KeyEvent.ACTION_UP) { + long eventTime = event.getEventTime(); + if (action == KeyEvent.ACTION_UP && sLastEventTime != eventTime) { + // Workaround for b/23947504, the same key event may be sent twice, filter it. + sLastEventTime = eventTime; switch (keyCode) { case KeyEvent.KEYCODE_GUIDE: - context.startActivity( - new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI)); + ((TvApplication) appContext).handleGuideKey(); break; case KeyEvent.KEYCODE_TV: - ((TvApplication) context.getApplicationContext()).handleTvKey(); + ((TvApplication) appContext).handleTvKey(); break; case KeyEvent.KEYCODE_TV_INPUT: - ((TvApplication) context.getApplicationContext()).handleTvInputKey(); + ((TvApplication) appContext).handleTvInputKey(); break; default: // Do nothing diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java index 26d000e7..124172f0 100644 --- a/src/com/android/tv/receiver/PackageIntentsReceiver.java +++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java @@ -19,17 +19,29 @@ package com.android.tv.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.net.Uri; +import android.util.Log; import com.android.tv.TvApplication; +import com.android.tv.util.Partner; /** * A class for handling the broadcast intents from PackageManager. */ public class PackageIntentsReceiver extends BroadcastReceiver { + private static final String TAG = "PackageIntentsReceiver"; @Override public void onReceive(Context context, Intent intent) { + if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + return; + } TvApplication.setCurrentRunningProcess(context, true); ((TvApplication) context.getApplicationContext()).handleInputCountChanged(); + + Uri uri = intent.getData(); + final String packageName = (uri != null ? uri.getSchemeSpecificPart() : null); + Partner.reset(context, packageName); } } diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java new file mode 100644 index 00000000..2709ebe1 --- /dev/null +++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.recommendation; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.support.media.tv.TvContractCompat; +import android.text.TextUtils; +import android.util.Log; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.TvApplication; +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.parental.ParentalControlSettings; +import com.android.tv.util.Utils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** Class for updating the preview programs for {@link Channel}. */ +@RequiresApi(Build.VERSION_CODES.O) +public class ChannelPreviewUpdater { + private static final String TAG = "ChannelPreviewUpdater"; + // STOPSHIP: set it to false. + private static final boolean DEBUG = true; + + private static final int UPATE_PREVIEW_PROGRAMS_JOB_ID = 1000001; + private static final long ROUTINE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10); + // The left time of a program should meet the threshold so that it could be recommended. + private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = + TimeUnit.MINUTES.toMillis(10); + private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% + private static final int RECOMMENDATION_COUNT = 6; + private static final int MIN_COUNT_TO_ADD_ROW = 4; + + private static ChannelPreviewUpdater sChannelPreviewUpdater; + + /** + * Creates and returns the {@link ChannelPreviewUpdater}. + */ + public static ChannelPreviewUpdater getInstance(Context context) { + if (sChannelPreviewUpdater == null) { + sChannelPreviewUpdater = new ChannelPreviewUpdater(context.getApplicationContext()); + } + return sChannelPreviewUpdater; + } + + private final Context mContext; + private final Recommender mRecommender; + private final PreviewDataManager mPreviewDataManager; + private JobService mJobService; + private JobParameters mJobParams; + + private final ParentalControlSettings mParentalControlSettings; + + private boolean mNeedUpdateAfterRecommenderReady = false; + + private Recommender.Listener mRecommenderListener = new Recommender.Listener() { + @Override + public void onRecommenderReady() { + if (mNeedUpdateAfterRecommenderReady) { + if (DEBUG) Log.d(TAG, "Recommender is ready"); + updatePreviewDataForChannelsImmediately(); + mNeedUpdateAfterRecommenderReady = false; + } + } + + @Override + public void onRecommendationChanged() { + updatePreviewDataForChannelsImmediately(); + } + }; + + private ChannelPreviewUpdater(Context context) { + mContext = context; + mRecommender = new Recommender(context, mRecommenderListener, true); + mRecommender.registerEvaluator(new RandomEvaluator(), 0.1, 0.1); + mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); + mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); + ApplicationSingletons appSingleton = TvApplication.getSingletons(context); + mPreviewDataManager = appSingleton.getPreviewDataManager(); + mParentalControlSettings = appSingleton.getTvInputManagerHelper() + .getParentalControlSettings(); + } + + /** + * Starts the routine service for updating the preview programs. + */ + public void startRoutineService() { + JobScheduler jobScheduler = + (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); + if (jobScheduler.getPendingJob(UPATE_PREVIEW_PROGRAMS_JOB_ID) != null) { + if (DEBUG) Log.d(TAG, "UPDATE_PREVIEW_JOB already exists"); + return; + } + JobInfo job = new JobInfo.Builder(UPATE_PREVIEW_PROGRAMS_JOB_ID, + new ComponentName(mContext, ChannelPreviewUpdateService.class)) + .setPeriodic(ROUTINE_INTERVAL_MS) + .setPersisted(true) + .build(); + if (jobScheduler.schedule(job) < 0) { + Log.i(TAG, "JobScheduler failed to schedule the job"); + } + } + + /** Called when {@link ChannelPreviewUpdateService} is started. */ + void onStartJob(JobService service, JobParameters params) { + if (DEBUG) Log.d(TAG, "onStartJob"); + mJobService = service; + mJobParams = params; + updatePreviewDataForChannelsImmediately(); + } + + /** + * Updates the preview programs table. + */ + public void updatePreviewDataForChannelsImmediately() { + if (!mRecommender.isReady()) { + mNeedUpdateAfterRecommenderReady = true; + return; + } + + if (!mPreviewDataManager.isLoadFinished()) { + mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForChannels(); + } + + @Override + public void onPreviewDataUpdateFinished() { } + }); + return; + } + updatePreviewDataForChannels(); + } + + /** Called when {@link ChannelPreviewUpdateService} is stopped. */ + void onStopJob() { + if (DEBUG) Log.d(TAG, "onStopJob"); + mJobService = null; + mJobParams = null; + } + + private void updatePreviewDataForChannels() { + new AsyncTask>() { + @Override + protected Set doInBackground(Void... params) { + Set programs = new HashSet<>(); + List channels = new ArrayList<>(mRecommender.recommendChannels()); + for (Channel channel : channels) { + if (channel.isPhysicalTunerChannel()) { + final Program program = Utils.getCurrentProgram(mContext, channel.getId()); + if (program != null + && isChannelRecommendationApplicable(channel, program)) { + programs.add(program); + if (programs.size() >= RECOMMENDATION_COUNT) { + break; + } + } + } + } + return programs; + } + + private boolean isChannelRecommendationApplicable(Channel channel, Program program) { + final long programDurationMs = + program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis(); + if (programDurationMs <= 0) { + return false; + } + if (TextUtils.isEmpty(program.getPosterArtUri())) { + return false; + } + if (mParentalControlSettings.isParentalControlsEnabled() + && (channel.isLocked() + || mParentalControlSettings.isRatingBlocked( + program.getContentRatings()))) { + return false; + } + long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); + final int programProgress = + (programDurationMs <= 0) + ? -1 + : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); + + // We recommend those programs that meet the condition only. + return programProgress < RECOMMENDATION_THRESHOLD_PROGRESS + || programLeftTimsMs > RECOMMENDATION_THRESHOLD_LEFT_TIME_MS; + } + + @Override + protected void onPostExecute(Set programs) { + updatePreviewDataForChannelsInternal(programs); + } + }.execute(); + } + + private void updatePreviewDataForChannelsInternal(Set programs) { + long defaultPreviewChannelId = mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL); + if (defaultPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) { + // Only create if there is enough programs + if (programs.size() > MIN_COUNT_TO_ADD_ROW) { + mPreviewDataManager.createDefaultPreviewChannel( + new PreviewDataManager.OnPreviewChannelCreationResultListener() { + @Override + public void onPreviewChannelCreationResult( + long createdPreviewChannelId) { + if (createdPreviewChannelId + != PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) { + TvContractCompat.requestChannelBrowsable( + mContext, createdPreviewChannelId); + updatePreviewProgramsForPreviewChannel( + createdPreviewChannelId, + generatePreviewProgramContentsFromPrograms( + createdPreviewChannelId, programs)); + } + } + }); + } + } else { + updatePreviewProgramsForPreviewChannel(defaultPreviewChannelId, + generatePreviewProgramContentsFromPrograms(defaultPreviewChannelId, programs)); + } + } + + private Set generatePreviewProgramContentsFromPrograms( + long previewChannelId, Set programs) { + Set result = new HashSet<>(); + for (Program program : programs) { + PreviewProgramContent previewProgramContent = + PreviewProgramContent.createFromProgram(mContext, previewChannelId, program); + if (previewProgramContent != null) { + result.add(previewProgramContent); + } + } + return result; + } + + private void updatePreviewProgramsForPreviewChannel(long previewChannelId, + Set previewProgramContents) { + PreviewDataManager.PreviewDataListener previewDataListener + = new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { } + + @Override + public void onPreviewDataUpdateFinished() { + mPreviewDataManager.removeListener(this); + if (mJobService != null && mJobParams != null) { + if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService"); + mJobService.jobFinished(mJobParams, false); + mJobService = null; + mJobParams = null; + } else { + if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService"); + } + } + }; + mPreviewDataManager.updatePreviewProgramsForChannel( + previewChannelId, previewProgramContents, previewDataListener); + } + + /** + * Job to execute the update of preview programs. + */ + public static class ChannelPreviewUpdateService extends JobService { + private ChannelPreviewUpdater mChannelPreviewUpdater; + + @Override + public void onCreate() { + TvApplication.setCurrentRunningProcess(this, true); + if (DEBUG) Log.d(TAG, "ChannelPreviewUpdateService.onCreate"); + mChannelPreviewUpdater = ChannelPreviewUpdater.getInstance(this); + } + + @Override + public boolean onStartJob(JobParameters params) { + mChannelPreviewUpdater.onStartJob(this, params); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + mChannelPreviewUpdater.onStopJob(); + return false; + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "ChannelPreviewUpdateService.onDestroy"); + } + } +} diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index 30ec73e3..a44eca41 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -426,6 +426,7 @@ public class NotificationService extends Service implements Recommender.Listener : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri()); intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0); // This callback will run on the main thread. @@ -455,11 +456,17 @@ public class NotificationService extends Service implements Recommender.Listener } private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) { - Bitmap result = BitmapUtils.scaleBitmap( + Bitmap result = BitmapUtils.getScaledMutableBitmap( background, Integer.MAX_VALUE, mCardImageHeight); Bitmap scaledLogo = BitmapUtils.scaleBitmap( logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); - Canvas canvas = new Canvas(result); + Canvas canvas; + try { + canvas = new Canvas(result); + } catch (Exception e) { + Log.w(TAG, "Failed to create Canvas", e); + return background; + } canvas.drawBitmap(result, new Matrix(), null); Rect rect = new Rect(); int startPadding; diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index 62ccd578..dc148ec8 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -41,7 +41,7 @@ import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.data.WatchedHistoryManager; import com.android.tv.util.PermissionUtils; -import com.android.tv.util.TvProviderUriMatcher; +import com.android.tv.util.TvUriMatcher; import java.util.ArrayList; import java.util.Collection; @@ -505,8 +505,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener @SuppressLint("SwitchIntDef") @Override public void onChange(final boolean selfChange, final Uri uri) { - switch (TvProviderUriMatcher.match(uri)) { - case TvProviderUriMatcher.MATCH_WATCHED_PROGRAM_ID: + switch (TvUriMatcher.match(uri)) { + case TvUriMatcher.MATCH_WATCHED_PROGRAM_ID: if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)) { mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, uri).sendToTarget(); diff --git a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java new file mode 100644 index 00000000..ad55afb7 --- /dev/null +++ b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.recommendation; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.text.TextUtils; +import android.util.Log; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.TvApplication; +import com.android.tv.data.PreviewDataManager; +import com.android.tv.data.PreviewProgramContent; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.data.RecordedProgram; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Class to update the preview data for {@link RecordedProgram} + */ +@RequiresApi(Build.VERSION_CODES.O) +public class RecordedProgramPreviewUpdater { + private static final String TAG = "RecordedProgramPreviewUpdater"; + // STOPSHIP: set it to false. + private static final boolean DEBUG = true; + + private static final int RECOMMENDATION_COUNT = 6; + + private static RecordedProgramPreviewUpdater sRecordedProgramPreviewUpdater; + + /** + * Creates and returns the {@link RecordedProgramPreviewUpdater}. + */ + public static RecordedProgramPreviewUpdater getInstance(Context context) { + if (sRecordedProgramPreviewUpdater == null) { + sRecordedProgramPreviewUpdater + = new RecordedProgramPreviewUpdater(context.getApplicationContext()); + } + return sRecordedProgramPreviewUpdater; + } + + private final Context mContext; + private final PreviewDataManager mPreviewDataManager; + private final DvrDataManager mDvrDataManager; + + private RecordedProgramPreviewUpdater(Context context) { + mContext = context.getApplicationContext(); + ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); + mPreviewDataManager = applicationSingletons.getPreviewDataManager(); + mDvrDataManager = applicationSingletons.getDvrDataManager(); + mDvrDataManager.addRecordedProgramListener(new DvrDataManager.RecordedProgramListener() { + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Add new preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Update preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Delete preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } + }); + } + + /** + * Updates the preview data for recorded programs. + */ + public void updatePreviewDataForRecordedPrograms() { + if (!mPreviewDataManager.isLoadFinished()) { + mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForRecordedPrograms(); + } + + @Override + public void onPreviewDataUpdateFinished() { } + }); + return; + } + if (!mDvrDataManager.isRecordedProgramLoadFinished()) { + mDvrDataManager.addRecordedProgramLoadFinishedListener( + new DvrDataManager.OnRecordedProgramLoadFinishedListener() { + @Override + public void onRecordedProgramLoadFinished() { + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + updatePreviewDataForRecordedPrograms(); + } + }); + return; + } + updatePreviewDataForRecordedProgramsInternal(); + } + + private void updatePreviewDataForRecordedProgramsInternal() { + Set recordedPrograms = generateRecommendationRecordedPrograms(); + Long recordedPreviewChannelId = mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL); + if (recordedPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID + && !recordedPrograms.isEmpty()) { + createPreviewChannelForRecordedPrograms(); + } else { + mPreviewDataManager.updatePreviewProgramsForChannel(recordedPreviewChannelId, + generatePreviewProgramContentsFromRecordedPrograms( + recordedPreviewChannelId, recordedPrograms), null); + } + } + + private void createPreviewChannelForRecordedPrograms() { + mPreviewDataManager.createPreviewChannel( + PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL, + new PreviewDataManager.OnPreviewChannelCreationResultListener() { + @Override + public void onPreviewChannelCreationResult(long createdPreviewChannelId) { + if (createdPreviewChannelId + != PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) { + updatePreviewDataForRecordedProgramsInternal(); + } + } + }); + } + + private Set generateRecommendationRecordedPrograms() { + Set programs = new HashSet<>(); + ArrayList sortedRecordedPrograms = + new ArrayList<>(mDvrDataManager.getRecordedPrograms()); + Collections.sort( + sortedRecordedPrograms, RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed()); + for (RecordedProgram recordedProgram : sortedRecordedPrograms) { + if (!TextUtils.isEmpty(recordedProgram.getPosterArtUri())) { + programs.add(recordedProgram); + if (programs.size() >= RECOMMENDATION_COUNT) { + break; + } + } + } + return programs; + } + + private Set generatePreviewProgramContentsFromRecordedPrograms( + long previewChannelId, Set recordedPrograms) { + Set result = new HashSet<>(); + for (RecordedProgram recordedProgram : recordedPrograms) { + result.add(PreviewProgramContent.createFromRecordedProgram(mContext, previewChannelId, + recordedProgram)); + } + return result; + } +} diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java index 5f89a21a..d90908f1 100644 --- a/src/com/android/tv/search/DataManagerSearch.java +++ b/src/com/android/tv/search/DataManagerSearch.java @@ -265,9 +265,7 @@ public class DataManagerSearch implements SearchInterface { } private String buildIntentData(long channelId) { - return TvContract.buildChannelUri(channelId).buildUpon() - .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH) - .build().toString(); + return TvContract.buildChannelUri(channelId).toString(); } private boolean isRatingBlocked(TvContentRating[] ratings) { diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java index 9255a43d..ef9336d7 100644 --- a/src/com/android/tv/search/LocalSearchProvider.java +++ b/src/com/android/tv/search/LocalSearchProvider.java @@ -23,10 +23,19 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; +import com.android.tv.TvApplication; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.TvCommonUtils; +import com.android.tv.perf.EventNames; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.TimerEvent; import com.android.tv.util.PermissionUtils; +import com.android.tv.util.TvUriMatcher; import java.util.ArrayList; import java.util.Arrays; @@ -36,6 +45,9 @@ public class LocalSearchProvider extends ContentProvider { private static final String TAG = "LocalSearchProvider"; private static final boolean DEBUG = false; + /** The authority for LocalSearchProvider. */ + public static final String AUTHORITY = "com.android.tv.search"; + public static final int PROGRESS_PERCENTAGE_HIDE = -1; // TODO: Remove this once added to the SearchManager. @@ -56,58 +68,93 @@ public class LocalSearchProvider extends ContentProvider { }; private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY; + static final String SUGGEST_PARAMETER_ACTION = "action"; // The launcher passes 10 as a 'limit' parameter by default. - private static final int DEFAULT_SEARCH_LIMIT = 10; + @VisibleForTesting + static final int DEFAULT_SEARCH_LIMIT = 10; + @VisibleForTesting + static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS; private static final String NO_LIVE_CONTENTS = "0"; private static final String LIVE_CONTENTS = "1"; - static final String SUGGEST_PARAMETER_ACTION = "action"; - static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS; + private PerformanceMonitor mPerformanceMonitor; + + /** Used only for testing */ + private SearchInterface mSearchInterface; @Override public boolean onCreate() { + mPerformanceMonitor = TvApplication.getSingletons(getContext()).getPerformanceMonitor(); return true; } + @VisibleForTesting + void setSearchInterface(SearchInterface searchInterface) { + SoftPreconditions.checkState(TvCommonUtils.isRunningInTest()); + mSearchInterface = searchInterface; + } + @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + public Cursor query(@NonNull Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) { + throw new IllegalArgumentException("Unknown URI: " + uri); + } + TimerEvent queryTimer = mPerformanceMonitor.startTimer(); if (DEBUG) { Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", " + Arrays.toString(selectionArgs) + ", " + sortOrder + ")"); } long time = SystemClock.elapsedRealtime(); - SearchInterface search; - if (PermissionUtils.hasAccessAllEpg(getContext())) { - if (DEBUG) Log.d(TAG, "Performing TV Provider search."); - search = new TvProviderSearch(getContext()); - } else { - if (DEBUG) Log.d(TAG, "Performing Data Manager search."); - search = new DataManagerSearch(getContext()); + SearchInterface search = mSearchInterface; + if (search == null) { + if (PermissionUtils.hasAccessAllEpg(getContext())) { + if (DEBUG) Log.d(TAG, "Performing TV Provider search."); + search = new TvProviderSearch(getContext()); + } else { + if (DEBUG) Log.d(TAG, "Performing Data Manager search."); + search = new DataManagerSearch(getContext()); + } } String query = uri.getLastPathSegment(); - int limit = DEFAULT_SEARCH_LIMIT; - int action = DEFAULT_SEARCH_ACTION; - try { - limit = Integer.parseInt(uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT)); - action = Integer.parseInt(uri.getQueryParameter(SUGGEST_PARAMETER_ACTION)); - } catch (NumberFormatException | UnsupportedOperationException e) { - // Ignore the exceptions + int limit = getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, + DEFAULT_SEARCH_LIMIT); + if (limit <= 0) { + limit = DEFAULT_SEARCH_LIMIT; + } + int action = getQueryParamater(uri, SUGGEST_PARAMETER_ACTION, DEFAULT_SEARCH_ACTION); + if (action < SearchInterface.ACTION_TYPE_START + || action > SearchInterface.ACTION_TYPE_END) { + action = DEFAULT_SEARCH_ACTION; } List results = new ArrayList<>(); if (!TextUtils.isEmpty(query)) { results.addAll(search.search(query, limit, action)); } Cursor c = createSuggestionsCursor(results); - if (DEBUG) Log.d(TAG, "Elapsed time: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + if (DEBUG) { + Log.d(TAG, "Elapsed time(count=" + c.getCount() + "): " + + (SystemClock.elapsedRealtime() - time) + "(msec)"); + } + mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH); return c; } + private int getQueryParamater(Uri uri, String key, int defaultValue) { + try { + return Integer.parseInt(uri.getQueryParameter(key)); + } catch (NumberFormatException | UnsupportedOperationException e) { + // Ignore the exceptions + } + return defaultValue; + } + private Cursor createSuggestionsCursor(List results) { MatrixCursor cursor = new MatrixCursor(SEARCHABLE_COLUMNS, results.size()); List row = new ArrayList<>(SEARCHABLE_COLUMNS.length); + int index = 0; for (SearchResult result : results) { row.clear(); row.add(result.title); @@ -122,6 +169,7 @@ public class LocalSearchProvider extends ContentProvider { row.add(result.duration == 0 ? null : String.valueOf(result.duration)); row.add(String.valueOf(result.progressPercentage)); cursor.addRow(row); + if (DEBUG) Log.d(TAG, "Result[" + (++index) + "]: " + result); } return cursor; } @@ -171,9 +219,20 @@ public class LocalSearchProvider extends ContentProvider { @Override public String toString() { - return "channelId: " + channelId + - ", channelNumber: " + channelNumber + - ", title: " + title; + return "SearchResult{channelId=" + channelId + + ", channelNumber=" + channelNumber + + ", title=" + title + + ", description=" + description + + ", imageUri=" + imageUri + + ", intentAction=" + intentAction + + ", intentData=" + intentData + + ", contentType=" + contentType + + ", isLive=" + isLive + + ", videoWidth=" + videoWidth + + ", videoHeight=" + videoHeight + + ", duration=" + duration + + ", progressPercentage=" + progressPercentage + + "}"; } } } \ No newline at end of file diff --git a/src/com/android/tv/search/SearchInterface.java b/src/com/android/tv/search/SearchInterface.java index caa45812..d631972a 100644 --- a/src/com/android/tv/search/SearchInterface.java +++ b/src/com/android/tv/search/SearchInterface.java @@ -24,11 +24,11 @@ import java.util.List; * Interface for channel and program search. */ public interface SearchInterface { - String SOURCE_TV_SEARCH = "TvSearch"; - + int ACTION_TYPE_START = 1; int ACTION_TYPE_AMBIGUOUS = 1; int ACTION_TYPE_SWITCH_CHANNEL = 2; int ACTION_TYPE_SWITCH_INPUT = 3; + int ACTION_TYPE_END = 3; /** * Search channels, inputs, or programs. diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java index 2ceec19a..e7d8a02d 100644 --- a/src/com/android/tv/search/TvProviderSearch.java +++ b/src/com/android/tv/search/TvProviderSearch.java @@ -402,9 +402,7 @@ public class TvProviderSearch implements SearchInterface { } private String buildIntentData(long channelId) { - return TvContract.buildChannelUri(channelId).buildUpon() - .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH) - .build().toString(); + return TvContract.buildChannelUri(channelId).toString(); } private boolean isRatingBlocked(String ratings) { @@ -432,6 +430,9 @@ public class TvProviderSearch implements SearchInterface { // Find exact matches first. for (TvInputInfo input : inputList) { + if (input.getType() == TvInputInfo.TYPE_TUNER) { + continue; + } String label = canonicalizeLabel(input.loadLabel(mContext)); String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext)); if (TextUtils.equals(query, label) || TextUtils.equals(query, customLabel)) { @@ -449,6 +450,9 @@ public class TvProviderSearch implements SearchInterface { // Then look for partial matches. for (TvInputInfo input : inputList) { + if (input.getType() == TvInputInfo.TYPE_TUNER) { + continue; + } String label = canonicalizeLabel(input.loadLabel(mContext)); String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext)); if ((label != null && label.contains(query)) || diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java index 2dd36074..a255de3e 100644 --- a/src/com/android/tv/tuner/ChannelScanFileParser.java +++ b/src/com/android/tv/tuner/ChannelScanFileParser.java @@ -90,10 +90,6 @@ public class ChannelScanFileParser { if (tokens.length != 3 && tokens.length != 4) { continue; } - if (!tokens[0].equals("A")) { - // Only support ATSC - continue; - } scanChannelList.add(ScanChannel.forTuner(Integer.parseInt(tokens[1]), tokens[2], tokens.length == 4 ? Integer.parseInt(tokens[3]) : null)); } diff --git a/src/com/android/tv/tuner/DvbTunerHal.java b/src/com/android/tv/tuner/DvbTunerHal.java new file mode 100644 index 00000000..ea977230 --- /dev/null +++ b/src/com/android/tv/tuner/DvbTunerHal.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner; + +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Log; +import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper; + +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * A class to handle a hardware Linux DVB API supported tuner device. + */ +public class DvbTunerHal extends TunerHal { + + private static final Object sLock = new Object(); + // @GuardedBy("sLock") + private static final SortedSet sUsedDvbDevices = new TreeSet<>(); + + private final DvbDeviceAccessor mDvbDeviceAccessor; + private DvbDeviceInfoWrapper mDvbDeviceInfo; + + protected DvbTunerHal(Context context) { + super(context); + mDvbDeviceAccessor = new DvbDeviceAccessor(context); + } + + @Override + protected boolean openFirstAvailable() { + List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); + if (deviceInfoList == null || deviceInfoList.isEmpty()) { + Log.e(TAG, "There's no dvb device attached"); + return false; + } + synchronized (sLock) { + for (DvbDeviceInfoWrapper deviceInfo : deviceInfoList) { + if (!sUsedDvbDevices.contains(deviceInfo)) { + if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); + mDvbDeviceInfo = deviceInfo; + sUsedDvbDevices.add(deviceInfo); + getDeliverySystemTypeFromDevice(); + return true; + } + } + } + Log.e(TAG, "There's no available dvb devices"); + return false; + } + + /** + * Acquires the tuner device. The requested device will be locked to the current instance if + * it's not acquired by others. + * + * @param deviceInfo a tuner device to open + * @return {@code true} if the operation was successful, {@code false} otherwise + */ + protected boolean open(DvbDeviceInfoWrapper deviceInfo) { + if (deviceInfo == null) { + Log.e(TAG, "Device info should not be null"); + return false; + } + if (mDvbDeviceInfo != null) { + Log.e(TAG, "Already acquired"); + return false; + } + List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); + if (deviceInfoList == null || deviceInfoList.isEmpty()) { + Log.e(TAG, "There's no dvb device attached"); + return false; + } + for (DvbDeviceInfoWrapper deviceInfoWrapper : deviceInfoList) { + if (deviceInfoWrapper.compareTo(deviceInfo) == 0) { + synchronized (sLock) { + if (sUsedDvbDevices.contains(deviceInfo)) { + Log.e(TAG, deviceInfo + " is already taken"); + return false; + } + sUsedDvbDevices.add(deviceInfo); + } + if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); + mDvbDeviceInfo = deviceInfo; + return true; + } + } + Log.e(TAG, "There's no such dvb device attached"); + return false; + } + + @Override + public void close() { + if (mDvbDeviceInfo != null) { + if (isStreaming()) { + stopTune(); + } + nativeFinalize(mDvbDeviceInfo.getId()); + synchronized (sLock) { + sUsedDvbDevices.remove(mDvbDeviceInfo); + } + mDvbDeviceInfo = null; + } + } + + @Override + protected boolean isDeviceOpen() { + return (mDvbDeviceInfo != null); + } + + @Override + protected long getDeviceId() { + if (mDvbDeviceInfo != null) { + return mDvbDeviceInfo.getId(); + } + return -1; + } + + @Override + protected int openDvbFrontEndFd() { + if (mDvbDeviceInfo != null) { + ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND); + if (descriptor != null) { + return descriptor.detachFd(); + } + } + return -1; + } + + @Override + protected int openDvbDemuxFd() { + if (mDvbDeviceInfo != null) { + ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX); + if (descriptor != null) { + return descriptor.detachFd(); + } + } + return -1; + } + + @Override + protected int openDvbDvrFd() { + if (mDvbDeviceInfo != null) { + ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR); + if (descriptor != null) { + return descriptor.detachFd(); + } + } + return -1; + } + + /** + * Gets the number of USB tuner devices currently present. + */ + public static int getNumberOfDevices(Context context) { + try { + return (new DvbDeviceAccessor(context)).getNumOfDvbDevices(); + } catch (Exception e) { + return 0; + } + } +} diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java index de19766e..1176cdf0 100644 --- a/src/com/android/tv/tuner/TunerHal.java +++ b/src/com/android/tv/tuner/TunerHal.java @@ -19,7 +19,12 @@ package com.android.tv.tuner; import android.content.Context; import android.support.annotation.IntDef; import android.support.annotation.StringDef; +import android.support.annotation.WorkerThread; import android.util.Log; +import android.util.Pair; + +import com.android.tv.Features; +import com.android.tv.customization.TvCustomizationManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -46,14 +51,43 @@ public abstract class TunerHal implements AutoCloseable { public static final String MODULATION_8VSB = "8VSB"; public static final String MODULATION_QAM256 = "QAM256"; + @IntDef({ DELIVERY_SYSTEM_UNDEFINED, DELIVERY_SYSTEM_ATSC, DELIVERY_SYSTEM_DVBC, + DELIVERY_SYSTEM_DVBS, DELIVERY_SYSTEM_DVBS2, DELIVERY_SYSTEM_DVBT, + DELIVERY_SYSTEM_DVBT2 }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeliverySystemType {} + public static final int DELIVERY_SYSTEM_UNDEFINED = 0; + public static final int DELIVERY_SYSTEM_ATSC = 1; + public static final int DELIVERY_SYSTEM_DVBC = 2; + public static final int DELIVERY_SYSTEM_DVBS = 3; + public static final int DELIVERY_SYSTEM_DVBS2 = 4; + public static final int DELIVERY_SYSTEM_DVBT = 5; + public static final int DELIVERY_SYSTEM_DVBT2 = 6; + + @IntDef({ TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK }) + @Retention(RetentionPolicy.SOURCE) + public @interface TunerType {} public static final int TUNER_TYPE_BUILT_IN = 1; public static final int TUNER_TYPE_USB = 2; + public static final int TUNER_TYPE_NETWORK = 3; protected static final int PID_PAT = 0; protected static final int PID_ATSC_SI_BASE = 0x1ffb; + protected static final int PID_DVB_SDT = 0x0011; + protected static final int PID_DVB_EIT = 0x0012; protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000; protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for // QAM256 tuning. + @IntDef({ + BUILT_IN_TUNER_TYPE_LINUX_DVB + }) + @Retention(RetentionPolicy.SOURCE) + private @interface BuiltInTunerType {} + private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1; + + private static Integer sBuiltInTunerType; + + protected @DeliverySystemType int mDeliverySystemType; private boolean mIsStreaming; private int mFrequency; private String mModulation; @@ -67,33 +101,62 @@ public abstract class TunerHal implements AutoCloseable { * @param context context for creating the TunerHal instance * @return the TunerHal instance */ + @WorkerThread public synchronized static TunerHal createInstance(Context context) { TunerHal tunerHal = null; - if (getTunerType(context) == TUNER_TYPE_BUILT_IN) { - } - if (tunerHal == null) { - tunerHal = new UsbTunerHal(context); - } - if (tunerHal.openFirstAvailable()) { - return tunerHal; + if (DvbTunerHal.getNumberOfDevices(context) > 0) { + if (DEBUG) Log.d(TAG, "Use DvbTunerHal"); + tunerHal = new DvbTunerHal(context); } - return null; + return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null; } /** * Gets the number of tuner devices currently present. */ - public static int getTunerCount(Context context) { - if (getTunerType(context) == TUNER_TYPE_BUILT_IN) { + @WorkerThread + public static Pair getTunerTypeAndCount(Context context) { + if (useBuiltInTuner(context)) { + if (getBuiltInTunerType(context) == BUILT_IN_TUNER_TYPE_LINUX_DVB) { + return new Pair<>(TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context)); + } + } else { + int usbTunerCount = DvbTunerHal.getNumberOfDevices(context); + if (usbTunerCount > 0) { + return new Pair<>(TUNER_TYPE_USB, usbTunerCount); + } } - return UsbTunerHal.getNumberOfDevices(context); + return new Pair<>(null, 0); + } + + /** + * Check a delivery system is for DVB or not. + */ + public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) { + return deliverySystemType == DELIVERY_SYSTEM_DVBC + || deliverySystemType == DELIVERY_SYSTEM_DVBS + || deliverySystemType == DELIVERY_SYSTEM_DVBS2 + || deliverySystemType == DELIVERY_SYSTEM_DVBT + || deliverySystemType == DELIVERY_SYSTEM_DVBT2; } /** - * Gets the type of tuner devices currently used. + * Returns if tuner input service would use built-in tuners instead of USB tuners or network + * tuners. */ - public static int getTunerType(Context context) { - return TUNER_TYPE_USB; + static boolean useBuiltInTuner(Context context) { + return getBuiltInTunerType(context) != 0; + } + + private static @BuiltInTunerType int getBuiltInTunerType(Context context) { + if (sBuiltInTunerType == null) { + sBuiltInTunerType = 0; + if (TvCustomizationManager.hasLinuxDvbBuiltInTuner(context) + && DvbTunerHal.getNumberOfDevices(context) > 0) { + sBuiltInTunerType = BUILT_IN_TUNER_TYPE_LINUX_DVB; + } + } + return sBuiltInTunerType; } protected TunerHal(Context context) { @@ -106,6 +169,20 @@ public abstract class TunerHal implements AutoCloseable { return mIsStreaming; } + protected void getDeliverySystemTypeFromDevice() { + if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) { + mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId()); + } + } + + /** + * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels + * of the same frequency. + */ + public boolean isReusable() { + return true; + } + @Override protected void finalize() throws Throwable { super.finalize(); @@ -131,9 +208,12 @@ public abstract class TunerHal implements AutoCloseable { * * @param frequency a frequency of the channel to tune to * @param modulation a modulation method of the channel to tune to + * @param channelNumber channel number when channel number is already known. Some tuner HAL + * may use channelNumber instead of frequency for tune. * @return {@code true} if the operation was successful, {@code false} otherwise */ - public synchronized boolean tune(int frequency, @ModulationType String modulation) { + public synchronized boolean tune(int frequency, @ModulationType String modulation, + String channelNumber) { if (!isDeviceOpen()) { Log.e(TAG, "There's no available device"); return false; @@ -148,6 +228,10 @@ public abstract class TunerHal implements AutoCloseable { if (mFrequency == frequency && Objects.equals(mModulation, modulation)) { addPidFilter(PID_PAT, FILTER_TYPE_OTHER); addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER); + if (isDvbDeliverySystem(mDeliverySystemType)) { + addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER); + addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER); + } mIsStreaming = true; return true; } @@ -156,6 +240,10 @@ public abstract class TunerHal implements AutoCloseable { if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) { addPidFilter(PID_PAT, FILTER_TYPE_OTHER); addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER); + if (isDvbDeliverySystem(mDeliverySystemType)) { + addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER); + addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER); + } mFrequency = frequency; mModulation = modulation; mIsStreaming = true; @@ -189,6 +277,7 @@ public abstract class TunerHal implements AutoCloseable { protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType); protected native void nativeCloseAllPidFilters(long deviceId); protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune); + protected native int nativeGetDeliverySystemType(long deviceId); /** * Stops current tuning. The tuner device and pid filters will be reset by this call and make @@ -210,6 +299,10 @@ public abstract class TunerHal implements AutoCloseable { nativeSetHasPendingTune(getDeviceId(), hasPendingTune); } + public int getDeliverySystemType() { + return mDeliverySystemType; + } + protected native void nativeStopTune(long deviceId); /** @@ -235,7 +328,7 @@ public abstract class TunerHal implements AutoCloseable { /** * Opens Linux DVB frontend device. This method is called from native JNI and used only for - * UsbTunerHal. + * DvbTunerHal. */ protected int openDvbFrontEndFd() { return -1; @@ -243,7 +336,7 @@ public abstract class TunerHal implements AutoCloseable { /** * Opens Linux DVB demux device. This method is called from native JNI and used only for - * UsbTunerHal. + * DvbTunerHal. */ protected int openDvbDemuxFd() { return -1; @@ -251,7 +344,7 @@ public abstract class TunerHal implements AutoCloseable { /** * Opens Linux DVB dvr device. This method is called from native JNI and used only for - * UsbTunerHal. + * DvbTunerHal. */ protected int openDvbDvrFd() { return -1; diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java index d89b6a0c..e06b9b4a 100644 --- a/src/com/android/tv/tuner/TunerInputController.java +++ b/src/com/android/tv/tuner/TunerInputController.java @@ -16,30 +16,43 @@ package com.android.tv.tuner; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.support.v4.os.BuildCompat; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.android.tv.Features; +import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.tuner.R; +import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.setup.TunerSetupActivity; import com.android.tv.tuner.tvinput.TunerTvInputService; +import com.android.tv.tuner.util.SystemPropertiesProxy; import com.android.tv.tuner.util.TunerInputInfoUtils; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Controls the package visibility of {@link TunerTvInputService}. @@ -48,84 +61,94 @@ import java.util.Map; * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} * to update the connection status of the supported USB TV tuners. */ -public class TunerInputController extends BroadcastReceiver { +public class TunerInputController { private static final boolean DEBUG = true; private static final String TAG = "TunerInputController"; + private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner"; + private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch"; + private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd"; + + /** + * Action of {@link Intent} to check network connection repeatedly when it is necessary. + */ + private static final String CHECKING_NETWORK_CONNECTION = + "com.android.tv.action.CHECKING_NETWORK_CONNECTION"; + + private static final String EXTRA_CHECKING_DURATION = + "com.android.tv.action.extra.CHECKING_DURATION"; + + private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); + private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10); private static final TunerDevice[] TUNER_DEVICES = { - new TunerDevice(0x2040, 0xb123), // WinTV-HVR-955Q - new TunerDevice(0x07ca, 0x0837) // AverTV Volar Hybrid Q + new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q + new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q + // WinTV-dualHD (bulk) will be supported after 2017 April security patch. + new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk) + // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete. + new TunerDevice(0x2040, 0x0264, null), }; private static final int MSG_ENABLE_INPUT_SERVICE = 1000; private static final long DVB_DRIVER_CHECK_DELAY_MS = 300; - private DvbDeviceAccessor mDvbDeviceAccessor; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ENABLE_INPUT_SERVICE: - Context context = (Context) msg.obj; - if (mDvbDeviceAccessor == null) { - mDvbDeviceAccessor = new DvbDeviceAccessor(context); - } - enableTunerTvInputService(context, mDvbDeviceAccessor.isDvbDeviceAvailable()); - break; - } - } - }; - /** - * Simple data holder for a USB device. Used to represent a tuner model, and compare - * against {@link UsbDevice}. + * Checks status of USB devices to see if there are available USB tuners connected. */ - private static class TunerDevice { - private final int vendorId; - private final int productId; - - private TunerDevice(int vendorId, int productId) { - this.vendorId = vendorId; - this.productId = productId; - } - - private boolean equals(UsbDevice device) { - return device.getVendorId() == vendorId && device.getProductId() == productId; - } + public static void onCheckingUsbTunerStatus(Context context, String action) { + onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler()); } - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); - TvApplication.setCurrentRunningProcess(context, true); - if (!Features.TUNER.isEnabled(context)) { - enableTunerTvInputService(context, false); + private static void onCheckingUsbTunerStatus(Context context, String action, + @NonNull CheckDvbDeviceHandler handler) { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); + if (TunerHal.useBuiltInTuner(context)) { + enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN); return; } + // Falls back to the below to check USB tuner devices. + boolean enabled = isUsbTunerConnected(context); + handler.removeMessages(MSG_ENABLE_INPUT_SERVICE); + if (enabled) { + // Need to check if DVB driver is accessible. Since the driver creation + // could be happen after the USB event, delay the checking by + // DVB_DRIVER_CHECK_DELAY_MS. + handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), + DVB_DRIVER_CHECK_DELAY_MS); + } else { + if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) { + // Since network tuner is attached, do not disable TunerTvInput, + // just updates the TvInputInfo. + TunerInputInfoUtils.updateTunerInputInfo(context); + return; + } + enableTunerTvInputService(context, false, false, TextUtils + .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ? + TunerHal.TUNER_TYPE_USB : null); + } + } - switch (intent.getAction()) { - case Intent.ACTION_BOOT_COMPLETED: - case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED: - case UsbManager.ACTION_USB_DEVICE_ATTACHED: - case UsbManager.ACTION_USB_DEVICE_DETACHED: - if (TunerInputInfoUtils.isBuiltInTuner(context)) { - enableTunerTvInputService(context, true); - break; - } - // Falls back to the below to check USB tuner devices. - boolean enabled = isUsbTunerConnected(context); - mHandler.removeMessages(MSG_ENABLE_INPUT_SERVICE); - if (enabled) { - // Need to check if DVB driver is accessible. Since the driver creation - // could be happen after the USB event, delay the checking by - // DVB_DRIVER_CHECK_DELAY_MS. - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), - DVB_DRIVER_CHECK_DELAY_MS); - } else { - enableTunerTvInputService(context, false); - } - break; + private static void onNetworkTunerChanged(Context context, boolean enabled) { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); + if (enabled) { + // Network tuner detection is initiated by UI. So the app should not + // be killed. + sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply(); + enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK); + } else { + sharedPreferences.edit() + .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply(); + if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) { + // Network tuner detection is initiated by UI. So the app should not + // be killed. + enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK); + } else { + // Since USB tuner is attached, do not disable TunerTvInput, + // just updates the TvInputInfo. + TunerInputInfoUtils.updateTunerInputInfo(context); + } } } @@ -135,15 +158,18 @@ public class TunerInputController extends BroadcastReceiver { * @param context {@link Context} instance * @return {@code true} if any tuner device we support is plugged in */ - private boolean isUsbTunerConnected(Context context) { + private static boolean isUsbTunerConnected(Context context) { UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); Map deviceList = manager.getDeviceList(); + String currentSecurityLevel = + SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null); + for (UsbDevice device : deviceList.values()) { if (DEBUG) { Log.d(TAG, "Device: " + device); } for (TunerDevice tuner : TUNER_DEVICES) { - if (tuner.equals(device)) { + if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) { Log.i(TAG, "Tuner found"); return true; } @@ -158,7 +184,8 @@ public class TunerInputController extends BroadcastReceiver { * @param context {@link Context} instance * @param enabled {@code true} to enable the service; otherwise {@code false} */ - private void enableTunerTvInputService(Context context, boolean enabled) { + private static void enableTunerTvInputService(Context context, boolean enabled, + boolean forceDontKillApp, Integer tunerType) { if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled); PackageManager pm = context.getPackageManager(); ComponentName componentName = new ComponentName(context, TunerTvInputService.class); @@ -170,23 +197,182 @@ public class TunerInputController extends BroadcastReceiver { // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only // when the LiveChannels app is active since we don't want to kill the running app. - int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated() + int flags = forceDontKillApp + || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated() ? PackageManager.DONT_KILL_APP : 0; int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; if (newState != pm.getComponentEnabledSetting(componentName)) { - // Send/cancel the USB tuner TV input setup recommendation card. - TunerSetupActivity.onTvInputEnabled(context, enabled); + // Send/cancel the USB tuner TV input setup notification. + TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType); // Enable/disable the USB tuner TV input. pm.setComponentEnabledSetting(componentName, newState, flags); - if (!enabled) { - Toast.makeText( - context, R.string.msg_usb_device_detached, Toast.LENGTH_SHORT).show(); + if (!enabled && tunerType != null) { + if (tunerType == TunerHal.TUNER_TYPE_USB) { + Toast.makeText(context, R.string.msg_usb_tuner_disconnected, + Toast.LENGTH_SHORT).show(); + } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) { + Toast.makeText(context, R.string.msg_network_tuner_disconnected, + Toast.LENGTH_SHORT).show(); + } } if (DEBUG) Log.d(TAG, "Status updated:" + enabled); } else if (enabled) { - // When # of USB tuners is changed or the device just boots. + // When # of tuners is changed or the tuner input service is switching from/to using + // network tuners or the device just boots. TunerInputInfoUtils.updateTunerInputInfo(context); } } + + /** + * Discovers a network tuner. If the network connection is down, it won't repeatedly checking. + */ + public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) { + boolean runningInMainProcess = + TvApplication.getSingletons(context).isRunningInMainProcess(); + SoftPreconditions.checkState(runningInMainProcess); + if (!runningInMainProcess) { + return; + } + executeNetworkTunerDiscoveryAsyncTask(context, 0); + } + + /** + * Discovers a network tuner. + * @param context {@link Context} + * @param repeatedDurationMs the time length to wait to repeatedly check network status to start + * finding network tuner when the network connection is not available. + * {@code 0} to disable repeatedly checking. + */ + private static void executeNetworkTunerDiscoveryAsyncTask(final Context context, + final long repeatedDurationMs) { + if (!Features.NETWORK_TUNER.isEnabled(context)) { + return; + } + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + if (isNetworkConnected(context)) { + // Implement and execute network tuner discovery AsyncTask here. + } else if (repeatedDurationMs > 0) { + AlarmManager alarmManager = + (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent networkCheckingIntent = new Intent(context, IntentReceiver.class); + networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION); + networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs); + PendingIntent alarmIntent = PendingIntent.getBroadcast( + context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + + repeatedDurationMs, alarmIntent); + } + return null; + } + + @Override + protected void onPostExecute(Boolean result) { + if (result == null) { + return; + } + onNetworkTunerChanged(context, result); + } + }.execute(); + } + + private static boolean isNetworkConnected(Context context) { + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } + + public static class IntentReceiver extends BroadcastReceiver { + private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(); + + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); + TvApplication.setCurrentRunningProcess(context, true); + if (!Features.TUNER.isEnabled(context)) { + enableTunerTvInputService(context, false, false, null); + return; + } + switch (intent.getAction()) { + case Intent.ACTION_BOOT_COMPLETED: + executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS); + case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED: + case UsbManager.ACTION_USB_DEVICE_ATTACHED: + case UsbManager.ACTION_USB_DEVICE_DETACHED: + onCheckingUsbTunerStatus(context, intent.getAction(), mHandler); + break; + case CHECKING_NETWORK_CONNECTION: + long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION, + INITIAL_CHECKING_DURATION_MS); + executeNetworkTunerDiscoveryAsyncTask(context, + Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS)); + break; + } + } + } + + /** + * Simple data holder for a USB device. Used to represent a tuner model, and compare + * against {@link UsbDevice}. + */ + private static class TunerDevice { + private final int vendorId; + private final int productId; + + // security patch level from which the specific tuner type is supported. + private final String minSecurityLevel; + + private TunerDevice(int vendorId, int productId, String minSecurityLevel) { + this.vendorId = vendorId; + this.productId = productId; + this.minSecurityLevel = minSecurityLevel; + } + + private boolean equals(UsbDevice device) { + return device.getVendorId() == vendorId && device.getProductId() == productId; + } + + private boolean isSupported(String currentSecurityLevel) { + if (minSecurityLevel == null) { + return true; + } + + long supportSecurityLevelTimeStamp = 0; + long currentSecurityLevelTimestamp = 0; + try { + SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT); + supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime(); + currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime(); + } catch (ParseException e) { + } + return supportSecurityLevelTimeStamp != 0 + && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp; + } + } + + private static class CheckDvbDeviceHandler extends Handler { + private DvbDeviceAccessor mDvbDeviceAccessor; + + CheckDvbDeviceHandler() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ENABLE_INPUT_SERVICE: + Context context = (Context) msg.obj; + if (mDvbDeviceAccessor == null) { + mDvbDeviceAccessor = new DvbDeviceAccessor(context); + } + boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable(); + enableTunerTvInputService( + context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null); + break; + } + } + } } diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java index 1547e3ae..11a6a969 100644 --- a/src/com/android/tv/tuner/TunerPreferences.java +++ b/src/com/android/tv/tuner/TunerPreferences.java @@ -25,11 +25,15 @@ import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.GuardedBy; +import android.support.annotation.IntDef; import android.support.annotation.MainThread; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.TunerPreferenceProvider.Preferences; import com.android.tv.tuner.util.TisConfiguration; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A helper class for the USB tuner preferences. @@ -39,20 +43,52 @@ public class TunerPreferences { private static final String PREFS_KEY_CHANNEL_DATA_VERSION = "channel_data_version"; private static final String PREFS_KEY_SCANNED_CHANNEL_COUNT = "scanned_channel_count"; + private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code"; private static final String PREFS_KEY_SCAN_DONE = "scan_done"; private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup"; private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream"; + private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting"; + private static final String PREFS_KEY_TRICKPLAY_EXPIRED_MS = "trickplay_expired_ms"; private static final String SHARED_PREFS_NAME = "com.android.tv.tuner.preferences"; public static final int CHANNEL_DATA_VERSION_NOT_SET = -1; + @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED}) + @Retention(RetentionPolicy.SOURCE) + public @interface TrickplaySetting { + } + + /** + * Trickplay setting is not changed by a user. Trickplay will be enabled in this case. + */ + public static final int TRICKPLAY_SETTING_NOT_SET = -1; + + /** + * Trickplay setting is disabled. + */ + public static final int TRICKPLAY_SETTING_DISABLED = 0; + + /** + * Trickplay setting is enabled. + */ + public static final int TRICKPLAY_SETTING_ENABLED = 1; + + @GuardedBy("TunerPreferences.class") private static final Bundle sPreferenceValues = new Bundle(); private static LoadPreferencesTask sLoadPreferencesTask; private static ContentObserver sContentObserver; + private static TunerPreferencesChangedListener sPreferencesChangedListener = null; private static boolean sInitialized; + /** + * Listeners for TunerPreferences change. + */ + public interface TunerPreferencesChangedListener { + void onTunerPreferencesChanged(); + } + /** * Initializes the USB tuner preferences. */ @@ -86,11 +122,19 @@ public class TunerPreferences { /** * Releases the resources. */ - @MainThread - public static void release(Context context) { + public static synchronized void release(Context context) { if (useContentProvider(context) && sContentObserver != null) { context.getContentResolver().unregisterContentObserver(sContentObserver); } + setTunerPreferencesChangedListener(null); + } + + /** + * Sets the listener for TunerPreferences change. + */ + public static void setTunerPreferencesChangedListener( + TunerPreferencesChangedListener listener) { + sPreferencesChangedListener = listener; } /** @@ -99,7 +143,8 @@ public class TunerPreferences { * This preferences is used across processes, so the preferences should be loaded again when the * databases changes. */ - public static synchronized void loadPreferences(Context context) { + @MainThread + public static void loadPreferences(Context context) { if (sLoadPreferencesTask != null && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) { sLoadPreferencesTask.cancel(true); @@ -113,8 +158,7 @@ public class TunerPreferences { return TisConfiguration.isPackagedWithLiveChannels(context); } - @MainThread - public static int getChannelDataVersion(Context context) { + public static synchronized int getChannelDataVersion(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION, @@ -126,8 +170,7 @@ public class TunerPreferences { } } - @MainThread - public static void setChannelDataVersion(Context context, int version) { + public static synchronized void setChannelDataVersion(Context context, int version) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version); } else { @@ -137,8 +180,7 @@ public class TunerPreferences { } } - @MainThread - public static int getScannedChannelCount(Context context) { + public static synchronized int getScannedChannelCount(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT); @@ -148,8 +190,7 @@ public class TunerPreferences { } } - @MainThread - public static void setScannedChannelCount(Context context, int channelCount) { + public static synchronized void setScannedChannelCount(Context context, int channelCount) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount); } else { @@ -159,8 +200,25 @@ public class TunerPreferences { } } - @MainThread - public static boolean isScanDone(Context context) { + public static synchronized String getLastPostalCode(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE); + } else { + return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null); + } + } + + public static synchronized void setLastPostalCode(Context context, String postalCode) { + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); + } else { + getSharedPreferences(context).edit() + .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode).apply(); + } + } + + public static synchronized boolean isScanDone(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE); @@ -170,8 +228,7 @@ public class TunerPreferences { } } - @MainThread - public static void setScanDone(Context context) { + public static synchronized void setScanDone(Context context) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCAN_DONE, true); } else { @@ -181,8 +238,7 @@ public class TunerPreferences { } } - @MainThread - public static boolean shouldShowSetupActivity(Context context) { + public static synchronized boolean shouldShowSetupActivity(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP); @@ -192,8 +248,7 @@ public class TunerPreferences { } } - @MainThread - public static void setShouldShowSetupActivity(Context context, boolean need) { + public static synchronized void setShouldShowSetupActivity(Context context, boolean need) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); } else { @@ -203,8 +258,50 @@ public class TunerPreferences { } } - @MainThread - public static boolean getStoreTsStream(Context context) { + public static synchronized long getTrickplayExpiredMs(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getLong(PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); + } else { + return getSharedPreferences(context) + .getLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); + } + } + + public static synchronized void setTrickplayExpiredMs(Context context, long timeMs) { + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs); + } else { + getSharedPreferences(context).edit() + .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs) + .apply(); + } + } + + public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); + } else { + return getSharedPreferences(context) + .getInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); + } + } + + public static synchronized void setTrickplaySetting(Context context, + @TrickplaySetting int trickplaySetting) { + SoftPreconditions.checkState(sInitialized); + SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); + } else { + getSharedPreferences(context).edit() + .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) + .apply(); + } + } + + public static synchronized boolean getStoreTsStream(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); @@ -214,8 +311,7 @@ public class TunerPreferences { } } - @MainThread - public static void setStoreTsStream(Context context, boolean shouldStore) { + public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); } else { @@ -229,8 +325,28 @@ public class TunerPreferences { return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); } - @MainThread - private static void setPreference(final Context context, final String key, final String value) { + private static synchronized void setPreference(Context context, String key, String value) { + sPreferenceValues.putString(key, value); + savePreference(context, key, value); + } + + private static synchronized void setPreference(Context context, String key, int value) { + sPreferenceValues.putInt(key, value); + savePreference(context, key, Integer.toString(value)); + } + + private static synchronized void setPreference(Context context, String key, long value) { + sPreferenceValues.putLong(key, value); + savePreference(context, key, Long.toString(value)); + } + + private static synchronized void setPreference(Context context, String key, boolean value) { + sPreferenceValues.putBoolean(key, value); + savePreference(context, key, Boolean.toString(value)); + } + + private static void savePreference(final Context context, final String key, + final String value) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -249,18 +365,6 @@ public class TunerPreferences { }.execute(); } - @MainThread - private static void setPreference(Context context, String key, int value) { - sPreferenceValues.putInt(key, value); - setPreference(context, key, Integer.toString(value)); - } - - @MainThread - private static void setPreference(Context context, String key, boolean value) { - sPreferenceValues.putBoolean(key, value); - setPreference(context, key, Boolean.toString(value)); - } - private static class LoadPreferencesTask extends AsyncTask { private final Context mContext; private LoadPreferencesTask(Context context) { @@ -279,8 +383,12 @@ public class TunerPreferences { String key = cursor.getString(0); String value = cursor.getString(1); switch (key) { + case PREFS_KEY_TRICKPLAY_EXPIRED_MS: + bundle.putLong(key, Long.parseLong(value)); + break; case PREFS_KEY_CHANNEL_DATA_VERSION: case PREFS_KEY_SCANNED_CHANNEL_COUNT: + case PREFS_KEY_TRICKPLAY_SETTING: try { bundle.putInt(key, Integer.parseInt(value)); } catch (NumberFormatException e) { @@ -292,6 +400,9 @@ public class TunerPreferences { case PREFS_KEY_STORE_TS_STREAM: bundle.putBoolean(key, Boolean.parseBoolean(value)); break; + case PREFS_KEY_LAST_POSTAL_CODE: + bundle.putString(key, value); + break; } } } @@ -304,7 +415,14 @@ public class TunerPreferences { @Override protected void onPostExecute(Bundle bundle) { - sPreferenceValues.putAll(bundle); + synchronized (TunerPreferences.class) { + if (bundle != null) { + sPreferenceValues.putAll(bundle); + } + } + if (sPreferencesChangedListener != null) { + sPreferencesChangedListener.onTunerPreferencesChanged(); + } } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/UsbTunerHal.java b/src/com/android/tv/tuner/UsbTunerHal.java deleted file mode 100644 index 22e35ea1..00000000 --- a/src/com/android/tv/tuner/UsbTunerHal.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.Context; -import android.os.ParcelFileDescriptor; -import android.util.Log; -import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper; - -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; - -/** - * A class to handle a hardware USB tuner device. - */ -public class UsbTunerHal extends TunerHal { - - private static final Object sLock = new Object(); - // @GuardedBy("sLock") - private static final SortedSet sUsedDvbDevices = new TreeSet<>(); - - private final DvbDeviceAccessor mDvbDeviceAccessor; - private DvbDeviceInfoWrapper mDvbDeviceInfo; - - protected UsbTunerHal(Context context) { - super(context); - mDvbDeviceAccessor = new DvbDeviceAccessor(context); - } - - @Override - protected boolean openFirstAvailable() { - List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); - if (deviceInfoList == null || deviceInfoList.isEmpty()) { - Log.e(TAG, "There's no dvb device attached"); - return false; - } - synchronized (sLock) { - for (DvbDeviceInfoWrapper deviceInfo : deviceInfoList) { - if (!sUsedDvbDevices.contains(deviceInfo)) { - if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); - mDvbDeviceInfo = deviceInfo; - sUsedDvbDevices.add(deviceInfo); - return true; - } - } - } - Log.e(TAG, "There's no available dvb devices"); - return false; - } - - /** - * Acquires the tuner device. The requested device will be locked to the current instance if - * it's not acquired by others. - * - * @param deviceInfo a tuner device to open - * @return {@code true} if the operation was successful, {@code false} otherwise - */ - protected boolean open(DvbDeviceInfoWrapper deviceInfo) { - if (deviceInfo == null) { - Log.e(TAG, "Device info should not be null"); - return false; - } - if (mDvbDeviceInfo != null) { - Log.e(TAG, "Already acquired"); - return false; - } - List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); - if (deviceInfoList == null || deviceInfoList.isEmpty()) { - Log.e(TAG, "There's no dvb device attached"); - return false; - } - for (DvbDeviceInfoWrapper deviceInfoWrapper : deviceInfoList) { - if (deviceInfoWrapper.compareTo(deviceInfo) == 0) { - synchronized (sLock) { - if (sUsedDvbDevices.contains(deviceInfo)) { - Log.e(TAG, deviceInfo + " is already taken"); - return false; - } - sUsedDvbDevices.add(deviceInfo); - } - if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); - mDvbDeviceInfo = deviceInfo; - return true; - } - } - Log.e(TAG, "There's no such dvb device attached"); - return false; - } - - @Override - public void close() { - if (mDvbDeviceInfo != null) { - if (isStreaming()) { - stopTune(); - } - nativeFinalize(mDvbDeviceInfo.getId()); - synchronized (sLock) { - sUsedDvbDevices.remove(mDvbDeviceInfo); - } - mDvbDeviceInfo = null; - } - } - - @Override - protected boolean isDeviceOpen() { - return (mDvbDeviceInfo != null); - } - - @Override - protected long getDeviceId() { - if (mDvbDeviceInfo != null) { - return mDvbDeviceInfo.getId(); - } - return -1; - } - - @Override - protected int openDvbFrontEndFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - @Override - protected int openDvbDemuxFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - @Override - protected int openDvbDvrFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - /** - * Gets the number of USB tuner devices currently present. - */ - public static int getNumberOfDevices(Context context) { - return (new DvbDeviceAccessor(context)).getNumOfDvbDevices(); - } -} diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java index 3c75caa9..24a0f354 100644 --- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java +++ b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java @@ -245,6 +245,10 @@ public class CaptionTrackRenderer implements Handler.Callback { } } + public void clear() { + mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR); + } + public void reset() { mCurrentWindowLayout = null; mIsDelayed = false; diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java index 92ab0620..d0f6cf11 100644 --- a/src/com/android/tv/tuner/cc/Cea708Parser.java +++ b/src/com/android/tv/tuner/cc/Cea708Parser.java @@ -140,6 +140,7 @@ public class Cea708Parser { private int mCommand = 0; private int mListenServiceNumber = 0; private boolean mDtvCcPacking = false; + private boolean mFirstServiceNumberDiscovered; // Assign a dummy listener in order to avoid null checks. private OnCea708ParserListener mListener = new OnCea708ParserListener() { @@ -208,6 +209,15 @@ public class Cea708Parser { } } + public void clear() { + mDtvCcPacket.clear(); + mCcPackets.clear(); + mBuffer.setLength(0); + mDiscoveredNumBytes.clear(); + mCommand = 0; + mDtvCcPacking = false; + } + public void setListenServiceNumber(int serviceNumber) { mListenServiceNumber = serviceNumber; } @@ -332,12 +342,14 @@ public class Cea708Parser { mDiscoveredNumBytes.put( serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0)); } - if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()) { + if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime() + || !mFirstServiceNumberDiscovered) { for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) { int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i); if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) { int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i); mListener.discoverServiceNumber(discoveredServiceNumber); + mFirstServiceNumberDiscovered = true; } } mDiscoveredNumBytes.clear(); diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java index aead4be8..8f98e67c 100644 --- a/src/com/android/tv/tuner/data/PsipData.java +++ b/src/com/android/tv/tuner/data/PsipData.java @@ -24,9 +24,10 @@ import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.ts.SectionParser; import com.android.tv.tuner.util.ConvertUtils; -import com.android.tv.tuner.util.StringUtils; +import com.android.tv.util.StringUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -226,6 +227,50 @@ public class PsipData { } } + public static class SdtItem { + private final String mServiceName; + private final String mServiceProviderName; + private final int mServiceType; + private final int mServiceId; + private final int mOriginalNetWorkId; + + public SdtItem(String serviceName, String serviceProviderName, int serviceType, + int serviceId, int originalNetWorkId) { + mServiceName = serviceName; + mServiceProviderName = serviceProviderName; + mServiceType = serviceType; + mServiceId = serviceId; + mOriginalNetWorkId = originalNetWorkId; + } + + public String getServiceName() { + return mServiceName; + } + + public String getServiceProviderName() { + return mServiceProviderName; + } + + public int getServiceType() { + return mServiceType; + } + + public int getServiceId() { + return mServiceId; + } + + public int getOriginalNetworkId() { + return mOriginalNetWorkId; + } + + @Override + public String toString() { + return String.format("ServiceName: %s ServiceProviderName:%s ServiceType:%d " + + "OriginalNetworkId:%d", + mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId); + } + } + /** * A base class for descriptors of Ts packets. */ @@ -462,6 +507,92 @@ public class PsipData { } } + public static class ServiceDescriptor extends TsDescriptor { + private final int mServiceType; + private final String mServiceProviderName; + private final String mServiceName; + + public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) { + mServiceType = serviceType; + mServiceProviderName = serviceProviderName; + mServiceName = serviceName; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE; + } + + public int getServiceType() { + return mServiceType; + } + + public String getServiceProviderName() { + return mServiceProviderName; + } + + public String getServiceName() { + return mServiceName; + } + + @Override + public String toString() { + return String.format( + "Service descriptor, service type: %d, " + + "service provider name: %s, " + + "service name: %s", mServiceType, mServiceProviderName, mServiceName); + } + } + + public static class ShortEventDescriptor extends TsDescriptor { + private final String mLanguage; + private final String mEventName; + private final String mText; + + public ShortEventDescriptor(String language, String eventName, String text) { + mLanguage = language; + mEventName = eventName; + mText = text; + } + + public String getEventName() { + return mEventName; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT; + } + + @Override + public String toString() { + return String.format("ShortEvent Descriptor, language:%s, event name: %s, " + + "text:%s", mLanguage, mEventName, mText); + } + } + + public static class ParentalRatingDescriptor extends TsDescriptor { + private final HashMap mRatings; + + public ParentalRatingDescriptor(HashMap ratings) { + mRatings = ratings; + } + + @Override + public int getTag() { + return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING; + } + + public HashMap getRatings() { + return mRatings; + } + + @Override + public String toString() { + return String.format("Parental rating descriptor, ratings:" + mRatings); + } + } + public static class RatingRegion { private final int mName; private final String mDescription; diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java index 89079d77..1cf514c1 100644 --- a/src/com/android/tv/tuner/data/TunerChannel.java +++ b/src/com/android/tv/tuner/data/TunerChannel.java @@ -24,7 +24,7 @@ import com.android.tv.tuner.data.nano.Channel.TunerChannelProto; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.Ints; -import com.android.tv.tuner.util.StringUtils; +import com.android.tv.util.StringUtils; import com.google.protobuf.nano.MessageNano; import java.io.IOException; @@ -40,6 +40,11 @@ import java.util.Objects; public class TunerChannel implements Comparable, PsipData.TvTracksInterface { private static final String TAG = "TunerChannel"; + /** + * Channel number separator between major number and minor number. + */ + public static final char CHANNEL_NUMBER_SEPARATOR = '-'; + // See ATSC Code Points Registry. private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] { "ATSC Reserved", @@ -63,6 +68,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff. public static final int INVALID_STREAMTYPE = -1; + // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766 private final TunerChannelProto mProto; private TunerChannel(PsipData.VctItem channel, int programNumber, @@ -88,6 +94,10 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } mProto.serviceType = channel.getServiceType(); } + initProto(pmtItems, type); + } + + private void initProto(List pmtItems, int type) { mProto.type = type; mProto.channelId = -1L; mProto.frequency = INVALID_FREQUENCY; @@ -129,14 +139,44 @@ public class TunerChannel implements Comparable, PsipData.TvTracks mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1; } + private TunerChannel(int programNumber, int type, PsipData.SdtItem channel, + List pmtItems) { + mProto = new TunerChannelProto(); + mProto.tsid = 0; + mProto.virtualMajor = 0; + mProto.virtualMinor = 0; + if (channel == null) { + mProto.shortName = ""; + mProto.programNumber = programNumber; + } else { + mProto.shortName = channel.getServiceName(); + mProto.programNumber = channel.getServiceId(); + mProto.serviceType = channel.getServiceType(); + } + initProto(pmtItems, type); + } + + /** + * Initialize tuner channel with VCT items and PMT items. + */ public TunerChannel(PsipData.VctItem channel, List pmtItems) { this(channel, 0, pmtItems, Channel.TYPE_TUNER); } + /** + * Initialize tuner channel with program number and PMT items. + */ public TunerChannel(int programNumber, List pmtItems) { this(null, programNumber, pmtItems, Channel.TYPE_TUNER); } + /** + * Initialize tuner channel with SDT items and PMT items. + */ + public TunerChannel(PsipData.SdtItem channel, List pmtItems) { + this(0, Channel.TYPE_TUNER, channel, pmtItems); + } + private TunerChannel(TunerChannelProto tunerChannelProto) { mProto = tunerChannelProto; } @@ -145,6 +185,50 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE); } + public static TunerChannel forDvbFile( + PsipData.SdtItem channel, List pmtItems) { + return new TunerChannel(0, Channel.TYPE_FILE, channel, pmtItems); + } + + /** + * Create a TunerChannel object suitable for network tuners + * @param major Channel number major + * @param minor Channel number minor + * @param programNumber Program number + * @param shortName Short name + * @param recordingProhibited Recording prohibition info + * @param videoFormat Video format. Should be {@code null} or one of the followings: + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, + * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P} + * @return a TunerChannel object + */ + public static TunerChannel forNetwork(int major, int minor, int programNumber, + String shortName, boolean recordingProhibited, String videoFormat) { + TunerChannel tunerChannel = new TunerChannel( + null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK); + tunerChannel.setVirtualMajor(major); + tunerChannel.setVirtualMinor(minor); + tunerChannel.setShortName(shortName); + // Set audio and video pids in order to work around the audio-only channel check. + tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0))); + tunerChannel.selectAudioTrack(0); + tunerChannel.setVideoPid(0); + tunerChannel.setRecordingProhibited(recordingProhibited); + if (videoFormat != null) { + tunerChannel.setVideoFormat(videoFormat); + } + return tunerChannel; + } + public String getName() { return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName; } @@ -193,7 +277,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.videoPid; } - public void setVideoPid(int videoPid) { + synchronized public void setVideoPid(int videoPid) { mProto.videoPid = videoPid; } @@ -219,7 +303,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Ints.asList(mProto.audioPids); } - public void setAudioPids(List audioPids) { + synchronized public void setAudioPids(List audioPids) { mProto.audioPids = Ints.toArray(audioPids); } @@ -227,7 +311,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Ints.asList(mProto.audioStreamTypes); } - public void setAudioStreamTypes(List audioStreamTypes) { + synchronized public void setAudioStreamTypes(List audioStreamTypes) { mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); } @@ -239,32 +323,32 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.type; } - public void setFilepath(String filepath) { - mProto.filepath = filepath; + synchronized public void setFilepath(String filepath) { + mProto.filepath = filepath == null ? "" : filepath; } public String getFilepath() { return mProto.filepath; } - public void setVirtualMajor(int virtualMajor) { + synchronized public void setVirtualMajor(int virtualMajor) { mProto.virtualMajor = virtualMajor; } - public void setVirtualMinor(int virtualMinor) { + synchronized public void setVirtualMinor(int virtualMinor) { mProto.virtualMinor = virtualMinor; } - public void setShortName(String shortName) { - mProto.shortName = shortName; + synchronized public void setShortName(String shortName) { + mProto.shortName = shortName == null ? "" : shortName; } - public void setFrequency(int frequency) { + synchronized public void setFrequency(int frequency) { mProto.frequency = frequency; } - public void setModulation(String modulation) { - mProto.modulation = modulation; + synchronized public void setModulation(String modulation) { + mProto.modulation = modulation == null ? "" : modulation; } public boolean hasVideo() { @@ -279,13 +363,18 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.channelId; } - public void setChannelId(long channelId) { + synchronized public void setChannelId(long channelId) { mProto.channelId = channelId; } public String getDisplayNumber() { - if (mProto.virtualMajor != 0 && mProto.virtualMinor != 0) { - return String.format("%d-%d", mProto.virtualMajor, mProto.virtualMinor); + return getDisplayNumber(true); + } + + public String getDisplayNumber(boolean ignoreZeroMinorNumber) { + if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) { + return String.format("%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, + mProto.virtualMinor); } else if (mProto.virtualMajor != 0) { return Integer.toString(mProto.virtualMajor); } else { @@ -298,7 +387,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } @Override - public void setHasCaptionTrack() { + synchronized public void setHasCaptionTrack() { mProto.hasCaptionTrack = true; } @@ -312,7 +401,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks)); } - public void setAudioTracks(List audioTracks) { + synchronized public void setAudioTracks(List audioTracks) { mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]); } @@ -321,11 +410,11 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks)); } - public void setCaptionTracks(List captionTracks) { + synchronized public void setCaptionTracks(List captionTracks) { mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]); } - public void selectAudioTrack(int index) { + synchronized public void selectAudioTrack(int index) { if (0 <= index && index < mProto.audioPids.length) { mProto.audioTrackIndex = index; } else { @@ -333,6 +422,22 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } } + synchronized public void setRecordingProhibited(boolean recordingProhibited) { + mProto.recordingProhibited = recordingProhibited; + } + + public boolean isRecordingProhibited() { + return mProto.recordingProhibited; + } + + synchronized public void setVideoFormat(String videoFormat) { + mProto.videoFormat = videoFormat == null ? "" : videoFormat; + } + + public String getVideoFormat() { + return mProto.videoFormat; + } + @Override public String toString() { switch (mProto.type) { @@ -359,7 +464,10 @@ public class TunerChannel implements Comparable, PsipData.TvTracks if (ret != 0) { return ret; } - + ret = StringUtils.compare(getName(), channel.getName()); + if (ret != 0) { + return ret; + } // For FileTsStreamer, file paths should be compared. return StringUtils.compare(getFilepath(), channel.getFilepath()); } @@ -374,12 +482,19 @@ public class TunerChannel implements Comparable, PsipData.TvTracks @Override public int hashCode() { - return Objects.hash(getFrequency(), getProgramNumber(), getFilepath()); + return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath()); } // Serialization - public byte[] toByteArray() { - return MessageNano.toByteArray(mProto); + synchronized public byte[] toByteArray() { + try { + return MessageNano.toByteArray(mProto); + } catch (Exception e) { + // Retry toByteArray. b/34197766 + Log.w(TAG, "TunerChannel or its variables are modified in multiple thread without lock", + e); + return MessageNano.toByteArray(mProto); + } } public static TunerChannel parseFrom(byte[] data) { diff --git a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java index 5e839223..5f536708 100644 --- a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java @@ -40,6 +40,7 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements private static final boolean DEBUG = false; public static final int MSG_SERVICE_NUMBER = 1; + public static final int MSG_ENABLE_CLOSED_CAPTION = 2; // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps. private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8; @@ -52,11 +53,13 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements private long mCurrentPositionUs; private long mPresentationTimeUs; private int mTrackIndex; + private boolean mRenderingDisabled; private Cea708Parser mCea708Parser; private CcListener mCcListener; public interface CcListener { void emitEvent(CaptionEvent captionEvent); + void clearCaption(); void discoverServiceNumber(int serviceNumber); } @@ -204,7 +207,7 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements } case SampleSource.SAMPLE_READ: { mSampleHolder.data.flip(); - if (mCea708Parser != null) { + if (mCea708Parser != null && !mRenderingDisabled) { mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs); } return true; @@ -274,10 +277,26 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements @Override public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - if (messageType == MSG_SERVICE_NUMBER) { - setServiceNumber((int) message); - } else { - super.handleMessage(messageType, message); + switch (messageType) { + case MSG_SERVICE_NUMBER: + setServiceNumber((int) message); + break; + case MSG_ENABLE_CLOSED_CAPTION: + boolean renderingDisabled = (Boolean) message == false; + if (mRenderingDisabled != renderingDisabled) { + mRenderingDisabled = renderingDisabled; + if (mRenderingDisabled) { + if (mCea708Parser != null) { + mCea708Parser.clear(); + } + if (mCcListener != null) { + mCcListener.clearCaption(); + } + } + } + break; + default: + super.handleMessage(messageType, message); } } } diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java new file mode 100644 index 00000000..0ab6d8c4 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.tuner.exoplayer; + +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; +import com.google.android.exoplayer2.extractor.Extractor; +import com.google.android.exoplayer2.extractor.ExtractorsFactory; +import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import com.google.android.exoplayer2.extractor.ts.TsExtractor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for + * H.264 stream + */ +public final class ExoPlayerExtractorsFactory implements ExtractorsFactory { + @Override + public Extractor[] createExtractors() { + // Only create TsExtractor since we only target MPEG2TS stream. + Extractor[] extractors = { + new TsExtractor(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory( + DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), false) }; + return extractors; + } +} diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java index c105e222..0b648400 100644 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java @@ -23,17 +23,28 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.util.Pair; -import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.extractor.ExtractorSampleSource; -import com.google.android.exoplayer.extractor.ExtractorSampleSource.EventListener; -import com.google.android.exoplayer.upstream.Allocator; import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.upstream.DefaultAllocator; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.ExtractorMediaSource.EventListener; +import com.google.android.exoplayer2.source.MediaPeriod; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.FixedTrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultAllocator; +import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer; @@ -42,10 +53,11 @@ import com.android.tv.tuner.tvinput.PlaybackBufferListener; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; /** * A class that extracts samples from a live broadcast stream while storing the sample on the disk. @@ -54,11 +66,7 @@ import java.util.concurrent.atomic.AtomicLong; public class ExoPlayerSampleExtractor implements SampleExtractor { private static final String TAG = "ExoPlayerSampleExtracto"; - // Buffer segment size for memory allocator. Copied from demo implementation of ExoPlayer. - private static final int BUFFER_SEGMENT_SIZE_IN_BYTES = 64 * 1024; - // Buffer segment count for sample source. Copied from demo implementation of ExoPlayer. - private static final int BUFFER_SEGMENT_COUNT = 256; - + private static final int INVALID_TRACK_INDEX = -1; private final HandlerThread mSourceReaderThread; private final long mId; @@ -70,36 +78,69 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { private AtomicBoolean mOnCompletionCalled = new AtomicBoolean(); private IOException mExceptionOnPrepare; private List mTrackFormats; + private int mVideoTrackIndex = INVALID_TRACK_INDEX; + private boolean mVideoTrackMet; + private long mBaseSamplePts = Long.MIN_VALUE; private HashMap mLastExtractedPositionUsMap = new HashMap<>(); + private final List> mPendingSamples = new LinkedList<>(); private OnCompletionListener mOnCompletionListener; private Handler mOnCompletionListenerHandler; private IOException mError; - public ExoPlayerSampleExtractor(Uri uri, DataSource source, BufferManager bufferManager, + public ExoPlayerSampleExtractor(Uri uri, final DataSource source, BufferManager bufferManager, PlaybackBufferListener bufferListener, boolean isRecording) { // It'll be used as a timeshift file chunk name's prefix. mId = System.currentTimeMillis(); - Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE_IN_BYTES); EventListener eventListener = new EventListener() { - @Override - public void onLoadError(int sourceId, IOException e) { - mError = e; + public void onLoadError(IOException error) { + mError = error; } }; mSourceReaderThread = new HandlerThread("SourceReaderThread"); - mSourceReaderWorker = new SourceReaderWorker(new ExtractorSampleSource(uri, source, - allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE_IN_BYTES, + mSourceReaderWorker = new SourceReaderWorker(new ExtractorMediaSource(uri, + new com.google.android.exoplayer2.upstream.DataSource.Factory() { + @Override + public com.google.android.exoplayer2.upstream.DataSource createDataSource() { + // Returns an adapter implementation for ExoPlayer V2 DataSource interface. + return new com.google.android.exoplayer2.upstream.DataSource() { + @Override + public long open(DataSpec dataSpec) throws IOException { + return source.open( + new com.google.android.exoplayer.upstream.DataSpec( + dataSpec.uri, dataSpec.postBody, + dataSpec.absoluteStreamPosition, dataSpec.position, + dataSpec.length, dataSpec.key, dataSpec.flags)); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) + throws IOException { + return source.read(buffer, offset, readLength); + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public void close() throws IOException { + source.close(); + } + }; + } + }, + new ExoPlayerExtractorsFactory(), // Do not create a handler if we not on a looper. e.g. test. - Looper.myLooper() != null ? new Handler() : null, - eventListener, 0)); + Looper.myLooper() != null ? new Handler() : null, eventListener)); if (isRecording) { mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false, RecordingSampleBuffer.BUFFER_REASON_RECORDING); } else { - if (bufferManager == null || bufferManager.isDisabled()) { + if (bufferManager == null) { mSampleBuffer = new SimpleSampleBuffer(bufferListener); } else { mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true, @@ -114,43 +155,141 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { mOnCompletionListenerHandler = handler; } - private class SourceReaderWorker implements Handler.Callback { + private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback { public static final int MSG_PREPARE = 1; public static final int MSG_FETCH_SAMPLES = 2; public static final int MSG_RELEASE = 3; private static final int RETRY_INTERVAL_MS = 50; - private final SampleSource mSampleSource; - private SampleSource.SampleSourceReader mSampleSourceReader; + private final MediaSource mSampleSource; + private MediaPeriod mMediaPeriod; + private SampleStream[] mStreams; private boolean[] mTrackMetEos; private boolean mMetEos = false; private long mCurrentPosition; + private DecoderInputBuffer mDecoderInputBuffer; + private SampleHolder mSampleHolder; + private boolean mPrepareRequested; - public SourceReaderWorker(SampleSource sampleSource) { + public SourceReaderWorker(MediaSource sampleSource) { mSampleSource = sampleSource; + mSampleSource.prepareSource(null, false, new MediaSource.Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + // Dynamic stream change is not supported yet. b/28169263 + // For now, this will cause EOS and playback reset. + } + }); + mDecoderInputBuffer = new DecoderInputBuffer( + DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); + } + + MediaFormat convertFormat(Format format) { + if (format.sampleMimeType.startsWith("audio/")) { + return MediaFormat.createAudioFormat(format.id, format.sampleMimeType, + format.bitrate, format.maxInputSize, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.channelCount, + format.sampleRate, format.initializationData, format.language, + format.pcmEncoding); + } else if (format.sampleMimeType.startsWith("video/")) { + return MediaFormat.createVideoFormat( + format.id, format.sampleMimeType, format.bitrate, format.maxInputSize, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.width, format.height, + format.initializationData, format.rotationDegrees, + format.pixelWidthHeightRatio, format.projectionData, format.stereoMode); + } else if (format.sampleMimeType.endsWith("/cea-608") + || format.sampleMimeType.startsWith("text/")) { + return MediaFormat.createTextFormat( + format.id, format.sampleMimeType, format.bitrate, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.language); + } else { + return MediaFormat.createFormatForMimeType( + format.id, format.sampleMimeType, format.bitrate, + com.google.android.exoplayer.C.UNKNOWN_TIME_US); + } + } + + @Override + public void onPrepared(MediaPeriod mediaPeriod) { + if (mMediaPeriod == null) { + // This instance is already released while the extractor is preparing. + return; + } + TrackSelection.Factory selectionFactory = new FixedTrackSelection.Factory(); + TrackGroupArray trackGroupArray = mMediaPeriod.getTrackGroups(); + TrackSelection[] selections = new TrackSelection[trackGroupArray.length]; + for (int i = 0; i < selections.length; ++i) { + selections[i] = selectionFactory.createTrackSelection(trackGroupArray.get(i), 0); + } + boolean retain[] = new boolean[trackGroupArray.length]; + boolean reset[] = new boolean[trackGroupArray.length]; + mStreams = new SampleStream[trackGroupArray.length]; + mMediaPeriod.selectTracks(selections, retain, mStreams, reset, 0); + if (mTrackFormats == null) { + int trackCount = trackGroupArray.length; + mTrackMetEos = new boolean[trackCount]; + List trackFormats = new ArrayList<>(); + int videoTrackCount = 0; + for (int i = 0; i < trackCount; i++) { + Format format = trackGroupArray.get(i).getFormat(0); + if (format.sampleMimeType.startsWith("video/")) { + videoTrackCount++; + mVideoTrackIndex = i; + } + trackFormats.add(convertFormat(format)); + } + if (videoTrackCount > 1) { + // Disable dropping samples when there are multiple video tracks. + mVideoTrackIndex = INVALID_TRACK_INDEX; + } + mTrackFormats = trackFormats; + List ids = new ArrayList<>(); + for (int i = 0; i < mTrackFormats.size(); i++) { + ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i)); + } + try { + mSampleBuffer.init(ids, mTrackFormats); + } catch (IOException e) { + // In this case, we will not schedule any further operation. + // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will + // call release() eventually. + mExceptionOnPrepare = e; + return; + } + mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); + mPrepared = true; + } + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + source.continueLoading(mCurrentPosition); } @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_PREPARE: - mPrepared = prepare(); - if (!mPrepared && mExceptionOnPrepare == null) { - mSourceReaderHandler - .sendEmptyMessageDelayed(MSG_PREPARE, RETRY_INTERVAL_MS); - } else{ - mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); + if (!mPrepareRequested) { + mPrepareRequested = true; + mMediaPeriod = mSampleSource.createPeriod(0, + new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), 0); + mMediaPeriod.prepare(this); + try { + mMediaPeriod.maybeThrowPrepareError(); + } catch (IOException e) { + mError = e; + } } return true; case MSG_FETCH_SAMPLES: boolean didSomething = false; - SampleHolder sample = new SampleHolder( - SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); ConditionVariable conditionVariable = new ConditionVariable(); - int trackCount = mSampleSourceReader.getTrackCount(); + int trackCount = mStreams.length; for (int i = 0; i < trackCount; ++i) { - if (!mTrackMetEos[i] && SampleSource.NOTHING_READ - != fetchSample(i, sample, conditionVariable)) { + if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ + != fetchSample(i, mSampleHolder, conditionVariable)) { if (mMetEos) { // If mMetEos was on during fetchSample() due to an error, // fetching from other tracks is not necessary. @@ -159,6 +298,7 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { didSomething = true; } } + mMediaPeriod.continueLoading(mCurrentPosition); if (!mMetEos) { if (didSomething) { mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); @@ -171,17 +311,10 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } return true; case MSG_RELEASE: - if (mSampleSourceReader != null) { - if (mPrepared) { - // ExtractorSampleSource expects all the tracks should be disabled - // before releasing. - int count = mSampleSourceReader.getTrackCount(); - for (int i = 0; i < count; ++i) { - mSampleSourceReader.disable(i); - } - } - mSampleSourceReader.release(); - mSampleSourceReader = null; + if (mMediaPeriod != null) { + mSampleSource.releasePeriod(mMediaPeriod); + mSampleSource.releaseSource(); + mMediaPeriod = null; } cleanUp(); mSourceReaderHandler.removeCallbacksAndMessages(null); @@ -190,91 +323,110 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { return false; } - private boolean prepare() { - if (mSampleSourceReader == null) { - mSampleSourceReader = mSampleSource.register(); - } - if(!mSampleSourceReader.prepare(0)) { - return false; - } - if (mTrackFormats == null) { - int trackCount = mSampleSourceReader.getTrackCount(); - mTrackMetEos = new boolean[trackCount]; - List trackFormats = new ArrayList<>(); - for (int i = 0; i < trackCount; i++) { - trackFormats.add(mSampleSourceReader.getFormat(i)); - mSampleSourceReader.enable(i, 0); - - } - mTrackFormats = trackFormats; - List ids = new ArrayList<>(); - for (int i = 0; i < mTrackFormats.size(); i++) { - ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i)); - } - try { - mSampleBuffer.init(ids, mTrackFormats); - } catch (IOException e) { - // In this case, we will not schedule any further operation. - // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will - // call release() eventually. - mExceptionOnPrepare = e; - return false; - } - } - return true; - } - private int fetchSample(int track, SampleHolder sample, ConditionVariable conditionVariable) { - mSampleSourceReader.continueBuffering(track, mCurrentPosition); - - MediaFormatHolder formatHolder = new MediaFormatHolder(); - sample.clearData(); - int ret = mSampleSourceReader.readData(track, mCurrentPosition, formatHolder, sample); - if (ret == SampleSource.SAMPLE_READ) { - if (mCurrentPosition < sample.timeUs) { - mCurrentPosition = sample.timeUs; + FormatHolder dummyFormatHolder = new FormatHolder(); + mDecoderInputBuffer.clear(); + int ret = mStreams[track].readData(dummyFormatHolder, mDecoderInputBuffer); + if (ret == C.RESULT_BUFFER_READ + // Double-check if the extractor provided the data to prevent NPE. b/33758354 + && mDecoderInputBuffer.data != null) { + if (mCurrentPosition < mDecoderInputBuffer.timeUs) { + mCurrentPosition = mDecoderInputBuffer.timeUs; } try { Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track); if (lastExtractedPositionUs == null) { - mLastExtractedPositionUsMap.put(track, sample.timeUs); + mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs); } else { mLastExtractedPositionUsMap.put(track, - Math.max(lastExtractedPositionUs, sample.timeUs)); + Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs)); } - queueSample(track, sample, conditionVariable); + queueSample(track, conditionVariable); } catch (IOException e) { mLastExtractedPositionUsMap.clear(); mMetEos = true; mSampleBuffer.setEos(); } - } else if (ret == SampleSource.END_OF_STREAM) { + } else if (ret == C.RESULT_END_OF_INPUT) { mTrackMetEos[track] = true; for (int i = 0; i < mTrackMetEos.length; ++i) { if (!mTrackMetEos[i]) { break; } - if (i == mTrackMetEos.length -1) { + if (i == mTrackMetEos.length - 1) { mMetEos = true; mSampleBuffer.setEos(); } } } - // TODO: Handle SampleSource.FORMAT_READ for dynamic resolution change. b/28169263 + // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263 return ret; } - } - - private void queueSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { - long writeStartTimeNs = SystemClock.elapsedRealtimeNanos(); - mSampleBuffer.writeSample(index, sample, conditionVariable); - // Checks whether the storage has enough bandwidth for recording samples. - if (mSampleBuffer.isWriteSpeedSlow(sample.size, - SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { - mSampleBuffer.handleWriteSpeedSlow(); + private void queueSample(int index, ConditionVariable conditionVariable) + throws IOException { + if (mVideoTrackIndex != INVALID_TRACK_INDEX) { + if (!mVideoTrackMet) { + if (index != mVideoTrackIndex) { + SampleHolder sample = + new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); + mSampleHolder.flags = + (mDecoderInputBuffer.isKeyFrame() + ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0) + | (mDecoderInputBuffer.isDecodeOnly() + ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY + : 0); + sample.timeUs = mDecoderInputBuffer.timeUs; + sample.size = mDecoderInputBuffer.data.position(); + sample.ensureSpaceForWrite(sample.size); + mDecoderInputBuffer.flip(); + sample.data.position(0); + sample.data.put(mDecoderInputBuffer.data); + sample.data.flip(); + mPendingSamples.add(new Pair<>(index, sample)); + return; + } + mVideoTrackMet = true; + mBaseSamplePts = + mDecoderInputBuffer.timeUs + - MpegTsDefaultAudioTrackRenderer + .INITIAL_AUDIO_BUFFERING_TIME_US; + for (Pair pair : mPendingSamples) { + if (pair.second.timeUs >= mBaseSamplePts) { + mSampleBuffer.writeSample(pair.first, pair.second, conditionVariable); + } + } + mPendingSamples.clear(); + } else { + if (mDecoderInputBuffer.timeUs < mBaseSamplePts + && mVideoTrackIndex != index) { + return; + } + } + } + // Copy the decoder input to the sample holder. + mSampleHolder.clearData(); + mSampleHolder.flags = + (mDecoderInputBuffer.isKeyFrame() + ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0) + | (mDecoderInputBuffer.isDecodeOnly() + ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY : 0); + mSampleHolder.timeUs = mDecoderInputBuffer.timeUs; + mSampleHolder.size = mDecoderInputBuffer.data.position(); + mSampleHolder.ensureSpaceForWrite(mSampleHolder.size); + mDecoderInputBuffer.flip(); + mSampleHolder.data.position(0); + mSampleHolder.data.put(mDecoderInputBuffer.data); + mSampleHolder.data.flip(); + long writeStartTimeNs = SystemClock.elapsedRealtimeNanos(); + mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable); + + // Checks whether the storage has enough bandwidth for recording samples. + if (mSampleBuffer.isWriteSpeedSlow(mSampleHolder.size, + SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { + mSampleBuffer.handleWriteSpeedSlow(); + } } } @@ -328,7 +480,7 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } @Override - public boolean continueBuffering(long positionUs) { + public boolean continueBuffering(long positionUs) { return mSampleBuffer.continueBuffering(positionUs); } @@ -386,12 +538,14 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } private long getLastExtractedPositionUs() { - long lastExtractedPositionUs = Long.MAX_VALUE; - for (long value : mLastExtractedPositionUsMap.values()) { - lastExtractedPositionUs = Math.min(lastExtractedPositionUs, value); + long lastExtractedPositionUs = Long.MIN_VALUE; + for (Map.Entry entry : mLastExtractedPositionUsMap.entrySet()) { + if (mVideoTrackIndex != entry.getKey()) { + lastExtractedPositionUs = Math.max(lastExtractedPositionUs, entry.getValue()); + } } - if (lastExtractedPositionUs == Long.MAX_VALUE) { - lastExtractedPositionUs = C.UNKNOWN_TIME_US; + if (lastExtractedPositionUs == Long.MIN_VALUE) { + lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US; } return lastExtractedPositionUs; } diff --git a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java index ec7b4b16..b7e42a7c 100644 --- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java @@ -25,7 +25,6 @@ import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; import com.android.tv.tuner.tvinput.PlaybackBufferListener; import android.os.Handler; -import android.util.Pair; import java.io.IOException; import java.util.ArrayList; @@ -61,18 +60,17 @@ public class FileSampleExtractor implements SampleExtractor{ @Override public boolean prepare() throws IOException { - ArrayList> trackInfos = - mBufferManager.readTrackInfoFiles(); - if (trackInfos == null || trackInfos.isEmpty()) { + List trackFormatList = mBufferManager.readTrackInfoFiles(); + if (trackFormatList == null || trackFormatList.isEmpty()) { throw new IOException("Cannot find meta files for the recording."); } - mTrackCount = trackInfos.size(); + mTrackCount = trackFormatList.size(); List ids = new ArrayList<>(); mTrackFormats.clear(); for (int i = 0; i < mTrackCount; ++i) { - Pair pair = trackInfos.get(i); - ids.add(pair.first); - mTrackFormats.add(MediaFormatUtil.createMediaFormat(pair.second)); + BufferManager.TrackFormat trackFormat = trackFormatList.get(i); + ids.add(trackFormat.trackId); + mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format)); } mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true, RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK); diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java index 381b22e9..2694298a 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java @@ -39,20 +39,22 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.data.Cea708Data; import com.android.tv.tuner.data.Cea708Data.CaptionEvent; import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer; -import com.android.tv.tuner.exoplayer.ac3.Ac3TrackRenderer; +import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; +import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsDataSourceManager; import com.android.tv.tuner.tvinput.EventDetector; +import com.android.tv.tuner.tvinput.TunerDebug; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * MPEG-2 TS stream player implementation using ExoPlayer. - */ -public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener, - Ac3PassthroughTrackRenderer.EventListener, Ac3TrackRenderer.Ac3EventListener { +/** MPEG-2 TS stream player implementation using ExoPlayer. */ +public class MpegTsPlayer + implements ExoPlayer.Listener, + MediaCodecVideoTrackRenderer.EventListener, + MpegTsDefaultAudioTrackRenderer.EventListener, + MpegTsMediaCodecAudioTrackRenderer.Ac3EventListener { private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER; /** @@ -60,7 +62,7 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen */ public interface RendererBuilder { void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource, - RendererBuilderCallback callback); + boolean hasSoftwareAudioDecoder, RendererBuilderCallback callback); } /** @@ -93,6 +95,11 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen */ void onEmitCaptionEvent(CaptionEvent event); + /** + * Notifies clearing up whole closed caption event. + */ + void onClearCaptionEvent(); + /** * Notifies the discovered caption service number. */ @@ -215,10 +222,11 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen * Creates renderers and {@link DataSource} and initializes player. * @param context a {@link Context} instance * @param channel to play + * @param hasSoftwareAudioDecoder {@code true} if there is connected software decoder * @param eventListener for program information which will be scanned from MPEG2-TS stream * @return true when everything is created and initialized well, false otherwise */ - public boolean prepare(Context context, TunerChannel channel, + public boolean prepare(Context context, TunerChannel channel, boolean hasSoftwareAudioDecoder, EventDetector.EventListener eventListener) { TsDataSource source = null; if (channel != null) { @@ -236,7 +244,7 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen } mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; mBuilderCallback = new InternalRendererBuilderCallback(); - mRendererBuilder.buildRenderers(this, source, mBuilderCallback); + mRendererBuilder.buildRenderers(this, source, hasSoftwareAudioDecoder, mBuilderCallback); return true; } @@ -304,8 +312,10 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed())); mPlayer.setPlayWhenReady(true); mTrickplayRunning = true; - if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED, + if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { + mPlayer.sendMessage( + mAudioRenderer, + MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, playbackParams.getSpeed()); } else { mPlayer.sendMessage(mAudioRenderer, @@ -317,9 +327,9 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen private void stopSmoothTrickplay(boolean calledBySeek) { if (mTrickplayRunning) { mTrickplayRunning = false; - if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, - Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED, + if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { + mPlayer.sendMessage( + mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, 1.0f); } else { mPlayer.sendMessage(mAudioRenderer, @@ -423,8 +433,9 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen */ public void setVolume(float volume) { mVolume = volume; - if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_VOLUME, volume); + if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME, + volume); } else { mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume); @@ -432,18 +443,20 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen } /** - * Enables or disables audio. + * Enables or disables audio and closed caption. * - * @param enable enables the audio when {@code true}, disables otherwise. + * @param enable enables the audio and closed caption when {@code true}, disables otherwise. */ - public void setAudioTrack(boolean enable) { - if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_AUDIO_TRACK, + public void setAudioTrackAndClosedCaption(boolean enable) { + if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0); } else { mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, enable ? mVolume : 0.0f); } + mPlayer.sendMessage(mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION, + enable); } /** @@ -494,6 +507,28 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen mPlayer.setSelectedTrack(rendererIndex, trackIndex); } + /** + * Returns the index of the currently selected track for the specified renderer. + * + * @param rendererIndex The index of the renderer. + * @return The selected track. A negative value or a value greater than or equal to the renderer's + * track count indicates that the renderer is disabled. + */ + public int getSelectedTrack(int rendererIndex) { + return mPlayer.getSelectedTrack(rendererIndex); + } + + /** + * Returns the format of a track. + * + * @param rendererIndex The index of the renderer. + * @param trackIndex The index of the track. + * @return The format of the track. + */ + public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) { + return mPlayer.getTrackFormat(rendererIndex, trackIndex); + } + /** * Gets the main handler of the player. */ @@ -579,6 +614,7 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen @Override public void onDroppedFrames(int count, long elapsed) { + TunerDebug.notifyVideoFrameDrop(count, elapsed); if (mTrickplayRunning && mListener != null) { mListener.onSmoothTrickplayForceStopped(); } @@ -621,6 +657,13 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen } } + @Override + public void clearCaption() { + if (mVideoEventListener != null) { + mVideoEventListener.onClearCaptionEvent(); + } + } + @Override public void discoverServiceNumber(int serviceNumber) { if (mVideoEventListener != null) { @@ -650,4 +693,4 @@ public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRen } } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java index 0e46c9cf..006ccac2 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java @@ -18,12 +18,14 @@ package com.android.tv.tuner.exoplayer; import android.content.Context; +import com.google.android.exoplayer.MediaCodecSelector; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.upstream.DataSource; +import com.android.tv.Features; import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder; import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback; -import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer; +import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.tvinput.PlaybackBufferListener; @@ -44,7 +46,7 @@ public class MpegTsRendererBuilder implements RendererBuilder { @Override public void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource, - RendererBuilderCallback callback) { + boolean mHasSoftwareAudioDecoder, RendererBuilderCallback callback) { // Build the video and audio renderers. SampleExtractor extractor = dataSource == null ? new MpegTsSampleExtractor(mBufferManager, mBufferListener) : @@ -52,10 +54,16 @@ public class MpegTsRendererBuilder implements RendererBuilder { SampleSource sampleSource = new MpegTsSampleSource(extractor); MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext, sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer); - // TODO: Only using Ac3PassthroughTrackRenderer for A/V sync issue. We will use - // {@link Ac3TrackRenderer} when we use ExoPlayer's extractor. - TrackRenderer audioRenderer = new Ac3PassthroughTrackRenderer(sampleSource, - mpegTsPlayer.getMainHandler(), mpegTsPlayer); + // TODO: Only using MpegTsDefaultAudioTrackRenderer for A/V sync issue. We will use + // {@link MpegTsMediaCodecAudioTrackRenderer} when we use ExoPlayer's extractor. + TrackRenderer audioRenderer = + new MpegTsDefaultAudioTrackRenderer( + sampleSource, + MediaCodecSelector.DEFAULT, + mpegTsPlayer.getMainHandler(), + mpegTsPlayer, + mHasSoftwareAudioDecoder, + !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext)); Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource); TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT]; diff --git a/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java deleted file mode 100644 index 9dae2e34..00000000 --- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ac3; - -import android.os.Handler; -import android.os.SystemClock; -import android.util.Log; - -import com.google.android.exoplayer.CodecCounters; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaClock; -import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.MediaFormatUtil; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.audio.AudioTrack; -import com.google.android.exoplayer.util.Assertions; -import com.google.android.exoplayer.util.MimeTypes; -import com.android.tv.tuner.tvinput.TunerDebug; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/** - * Decodes and renders AC3 audio. - */ -public class Ac3PassthroughTrackRenderer extends TrackRenderer implements MediaClock { - public static final int MSG_SET_VOLUME = 10000; - public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1; - public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2; - - // ATSC/53 allows sample rate to be only 48Khz. - // One AC3 sample has 1536 frames, and its duration is 32ms. - public static final long AC3_SAMPLE_DURATION_US = 32000; - - private static final String TAG = "Ac3PassthroughTrackRenderer"; - private static final boolean DEBUG = false; - - /** - * Interface definition for a callback to be notified of - * {@link com.google.android.exoplayer.audio.AudioTrack} error. - */ - public interface EventListener { - void onAudioTrackInitializationError(AudioTrack.InitializationException e); - void onAudioTrackWriteError(AudioTrack.WriteException e); - } - - private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2; - private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024; - private static final int MONITOR_DURATION_MS = 1000; - private static final int AC3_HEADER_BITRATE_OFFSET = 4; - - // Keep this as static in order to prevent new framework AudioTrack creation - // while old AudioTrack is being released. - private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper(); - private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000; - - // Ignore AudioTrack backward movement if duration of movement is below the threshold. - private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000; - - // AudioTrack position cannot go ahead beyond this limit. - private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000; - - // Since MediaCodec processing and AudioTrack playing add delay, - // PTS interpolated time should be delayed reasonably when AudioTrack is not used. - private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000; - - private final CodecCounters mCodecCounters; - private final SampleSource.SampleSourceReader mSource; - private final SampleHolder mSampleHolder; - private final MediaFormatHolder mFormatHolder; - private final EventListener mEventListener; - private final Handler mEventHandler; - private final AudioTrackMonitor mMonitor; - private final AudioClock mAudioClock; - - private MediaFormat mFormat; - private final ByteBuffer mOutputBuffer; - private boolean mOutputReady; - private int mTrackIndex; - private boolean mSourceStateReady; - private boolean mInputStreamEnded; - private boolean mOutputStreamEnded; - private long mEndOfStreamMs; - private long mCurrentPositionUs; - private int mPresentationCount; - private long mPresentationTimeUs; - private long mInterpolatedTimeUs; - private long mPreviousPositionUs; - private boolean mIsStopped; - private ArrayList mTracksIndex; - - public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler, - EventListener listener) { - mSource = source.register(); - mEventHandler = eventHandler; - mEventListener = listener; - mTrackIndex = -1; - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); - mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); - mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE); - mFormatHolder = new MediaFormatHolder(); - AUDIO_TRACK.restart(); - mCodecCounters = new CodecCounters(); - mMonitor = new AudioTrackMonitor(); - mAudioClock = new AudioClock(); - mTracksIndex = new ArrayList<>(); - } - - @Override - protected MediaClock getMediaClock() { - return this; - } - - private static boolean handlesMimeType(String mimeType) { - return mimeType.equals(MimeTypes.AUDIO_AC3) || mimeType.equals(MimeTypes.AUDIO_E_AC3); - } - - @Override - protected boolean doPrepare(long positionUs) throws ExoPlaybackException { - boolean sourcePrepared = mSource.prepare(positionUs); - if (!sourcePrepared) { - return false; - } - for (int i = 0; i < mSource.getTrackCount(); i++) { - if (handlesMimeType(mSource.getFormat(i).mimeType)) { - if (mTrackIndex < 0) { - mTrackIndex = i; - } - mTracksIndex.add(i); - } - } - - // TODO: Check this case. Source does not have the proper mime type. - return true; - } - - @Override - protected int getTrackCount() { - return mTracksIndex.size(); - } - - @Override - protected MediaFormat getFormat(int track) { - Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); - return mSource.getFormat(mTracksIndex.get(track)); - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) { - Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); - mTrackIndex = mTracksIndex.get(track); - mSource.enable(mTrackIndex, positionUs); - seekToInternal(positionUs); - } - - @Override - protected void onDisabled() { - AUDIO_TRACK.resetSessionId(); - clearDecodeState(); - mFormat = null; - mSource.disable(mTrackIndex); - } - - @Override - protected void onReleased() { - AUDIO_TRACK.release(); - mSource.release(); - } - - @Override - protected boolean isEnded() { - return mOutputStreamEnded && AUDIO_TRACK.isEnded(); - } - - @Override - protected boolean isReady() { - return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady)); - } - - private void seekToInternal(long positionUs) { - mMonitor.reset(MONITOR_DURATION_MS); - mSourceStateReady = false; - mInputStreamEnded = false; - mOutputStreamEnded = false; - mPresentationTimeUs = positionUs; - mPresentationCount = 0; - mPreviousPositionUs = 0; - mCurrentPositionUs = Long.MIN_VALUE; - mInterpolatedTimeUs = Long.MIN_VALUE; - mAudioClock.setPositionUs(positionUs); - } - - @Override - protected void seekTo(long positionUs) { - mSource.seekToUs(positionUs); - AUDIO_TRACK.reset(); - // resetSessionId() will create a new framework AudioTrack instead of reusing old one. - AUDIO_TRACK.resetSessionId(); - seekToInternal(positionUs); - } - - @Override - protected void onStarted() { - AUDIO_TRACK.play(); - mAudioClock.start(); - mIsStopped = false; - } - - @Override - protected void onStopped() { - AUDIO_TRACK.pause(); - mAudioClock.stop(); - mIsStopped = true; - } - - @Override - protected void maybeThrowError() throws ExoPlaybackException { - try { - mSource.maybeThrowError(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - mMonitor.maybeLog(); - try { - if (mEndOfStreamMs != 0) { - // Ensure playback stops, after EoS was notified. - // Sometimes MediaCodecTrackRenderer does not fetch EoS timely - // after EoS was notified here long before. - long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs; - if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) { - throw new ExoPlaybackException("Much time has elapsed after EoS"); - } - } - boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs); - if (mSourceStateReady != continueBuffering) { - mSourceStateReady = continueBuffering; - if (DEBUG) { - Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady)); - } - } - long discontinuity = mSource.readDiscontinuity(mTrackIndex); - if (discontinuity != SampleSource.NO_DISCONTINUITY) { - AUDIO_TRACK.handleDiscontinuity(); - mPresentationTimeUs = discontinuity; - mPresentationCount = 0; - clearDecodeState(); - return; - } - if (mFormat == null) { - readFormat(); - return; - } - - // Process only one sample at a time for doSomeWork() - if (processOutput()) { - if (!mOutputReady) { - while (feedInputBuffer()) { - if (mOutputReady) break; - } - } - } - mCodecCounters.ensureUpdated(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - private void ensureAudioTrackInitialized() { - if (!AUDIO_TRACK.isInitialized()) { - try { - if (DEBUG) { - Log.d(TAG, "AudioTrack initialized"); - } - AUDIO_TRACK.initialize(); - } catch (AudioTrack.InitializationException e) { - Log.e(TAG, "Error on AudioTrack initialization", e); - notifyAudioTrackInitializationError(e); - - // Do not throw exception here but just disabling audioTrack to keep playing - // video without audio. - AUDIO_TRACK.setStatus(false); - } - if (getState() == TrackRenderer.STATE_STARTED) { - if (DEBUG) { - Log.d(TAG, "AudioTrack played"); - } - AUDIO_TRACK.play(); - } - } - } - - private void clearDecodeState() { - mOutputReady = false; - AUDIO_TRACK.reset(); - } - - private void readFormat() throws IOException, ExoPlaybackException { - int result = mSource.readData(mTrackIndex, mCurrentPositionUs, - mFormatHolder, mSampleHolder); - if (result == SampleSource.FORMAT_READ) { - onInputFormatChanged(mFormatHolder); - } - } - - private void onInputFormatChanged(MediaFormatHolder formatHolder) - throws ExoPlaybackException { - mFormat = formatHolder.format; - if (DEBUG) { - Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString()); - } - clearDecodeState(); - AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16()); - } - - private boolean feedInputBuffer() throws IOException, ExoPlaybackException { - if (mInputStreamEnded) { - return false; - } - - mSampleHolder.data.clear(); - mSampleHolder.size = 0; - int result = mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, - mSampleHolder); - switch (result) { - case SampleSource.NOTHING_READ: { - return false; - } - case SampleSource.FORMAT_READ: { - Log.i(TAG, "Format was read again"); - onInputFormatChanged(mFormatHolder); - return true; - } - case SampleSource.END_OF_STREAM: { - Log.i(TAG, "End of stream from SampleSource"); - mInputStreamEnded = true; - return false; - } - default: { - mSampleHolder.data.flip(); - decodeDone(mSampleHolder.data, mSampleHolder.timeUs); - return true; - } - } - } - - private boolean processOutput() throws ExoPlaybackException { - if (mOutputStreamEnded) { - return false; - } - if (!mOutputReady) { - if (mInputStreamEnded) { - mOutputStreamEnded = true; - mEndOfStreamMs = SystemClock.elapsedRealtime(); - return false; - } - return true; - } - - ensureAudioTrackInitialized(); - int handleBufferResult; - try { - // To reduce discontinuity, interpolate presentation time. - mInterpolatedTimeUs = mPresentationTimeUs - + mPresentationCount * AC3_SAMPLE_DURATION_US; - handleBufferResult = AUDIO_TRACK.handleBuffer(mOutputBuffer, - 0, mOutputBuffer.limit(), mInterpolatedTimeUs); - } catch (AudioTrack.WriteException e) { - notifyAudioTrackWriteError(e); - throw new ExoPlaybackException(e); - } - - if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { - Log.i(TAG, "Play discontinuity happened"); - mCurrentPositionUs = Long.MIN_VALUE; - } - if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { - mCodecCounters.renderedOutputBufferCount++; - mOutputReady = false; - return true; - } - return false; - } - - @Override - protected long getDurationUs() { - return mSource.getFormat(mTrackIndex).durationUs; - } - - @Override - protected long getBufferedPositionUs() { - long pos = mSource.getBufferedPositionUs(); - return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US - ? pos : Math.max(pos, getPositionUs()); - } - - @Override - public long getPositionUs() { - if (!AUDIO_TRACK.isInitialized()) { - return mAudioClock.getPositionUs(); - } else if (!AUDIO_TRACK.isEnabled()) { - if (mInterpolatedTimeUs > 0) { - return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US; - } - return mPresentationTimeUs; - } - long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded()); - if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) { - mPreviousPositionUs = 0L; - if (DEBUG) { - long oldPositionUs = Math.max(mCurrentPositionUs, 0); - long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); - Log.d(TAG, "Audio position is not set, diff in us: " - + String.valueOf(currentPositionUs - oldPositionUs)); - } - mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); - } else { - if (mPreviousPositionUs - > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) { - Log.e(TAG, "audio_position BACK JUMP: " - + (mPreviousPositionUs - audioTrackCurrentPositionUs)); - mCurrentPositionUs = audioTrackCurrentPositionUs; - } else { - mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs); - } - mPreviousPositionUs = audioTrackCurrentPositionUs; - } - long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US; - if (mCurrentPositionUs > upperBound) { - mCurrentPositionUs = upperBound; - } - return mCurrentPositionUs; - } - - private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) { - if (outputBuffer == null || mOutputBuffer == null) { - return; - } - if (presentationTimeUs < 0) { - Log.e(TAG, "decodeDone - invalid presentationTimeUs"); - return; - } - - if (TunerDebug.ENABLED) { - TunerDebug.setAudioPtsUs(presentationTimeUs); - } - - mOutputBuffer.clear(); - Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit()); - - mOutputBuffer.put(outputBuffer); - mMonitor.addPts(presentationTimeUs, mOutputBuffer.position(), - mOutputBuffer.get(AC3_HEADER_BITRATE_OFFSET)); - if (presentationTimeUs == mPresentationTimeUs) { - mPresentationCount++; - } else { - mPresentationCount = 0; - mPresentationTimeUs = presentationTimeUs; - } - mOutputBuffer.flip(); - mOutputReady = true; - } - - private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { - if (mEventHandler == null || mEventListener == null) { - return; - } - mEventHandler.post(new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackInitializationError(e); - } - }); - } - - private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { - if (mEventHandler == null || mEventListener == null) { - return; - } - mEventHandler.post(new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackWriteError(e); - } - }); - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - switch (messageType) { - case MSG_SET_VOLUME: - AUDIO_TRACK.setVolume((Float) message); - break; - case MSG_SET_AUDIO_TRACK: - boolean enabled = (Integer) message == 1; - if (enabled == AUDIO_TRACK.isEnabled()) { - return; - } - if (!enabled) { - // mAudioClock can be different from getPositionUs. In order to sync them, - // we set mAudioClock. - mAudioClock.setPositionUs(getPositionUs()); - } - AUDIO_TRACK.setStatus(enabled); - if (enabled) { - // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to - // the current position. If not, AUDIO_TRACK has the obsolete data. - seekTo(mAudioClock.getPositionUs()); - } - break; - case MSG_SET_PLAYBACK_SPEED: - mAudioClock.setPlaybackSpeed((Float) message); - break; - default: - super.handleMessage(messageType, message); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java deleted file mode 100644 index 2bf86b5a..00000000 --- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ac3; - -import android.os.Handler; - -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.SampleSource; - -/** - * MPEG-2 TS audio track renderer. - *

Since the audio output from {@link android.media.MediaExtractor} contains extra samples at - * the beginning, using original {@link MediaCodecAudioTrackRenderer} as audio renderer causes - * asynchronous Audio/Video outputs. - * This class calculates the offset of audio data and adjust the presentation times to avoid the - * asynchronous Audio/Video problem. - */ -public class Ac3TrackRenderer extends MediaCodecAudioTrackRenderer { - private final String TAG = "Ac3TrackRenderer"; - private final boolean DEBUG = false; - - private final Ac3EventListener mListener; - - public interface Ac3EventListener extends EventListener { - /** - * Invoked when a {@link android.media.PlaybackParams} set to an - * {@link android.media.AudioTrack} is not valid. - * - * @param e The corresponding exception. - */ - void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e); - } - - public Ac3TrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - Handler eventHandler, EventListener eventListener) { - super(source, mediaCodecSelector, eventHandler, eventListener); - mListener = (Ac3EventListener) eventListener; - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - if (messageType == MSG_SET_PLAYBACK_PARAMS) { - try { - super.handleMessage(messageType, message); - } catch (IllegalArgumentException e) { - if (isAudioTrackSetPlaybackParamsError(e)) { - notifyAudioTrackSetPlaybackParamsError(e); - } - } - return; - } - super.handleMessage(messageType, message); - } - - private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) { - if (eventHandler != null && mListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - mListener.onAudioTrackSetPlaybackParamsError(e); - } - }); - } - } - - static private boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { - if (e.getStackTrace() == null || e.getStackTrace().length < 1) { - return false; - } - for (StackTraceElement element : e.getStackTrace()) { - String elementString = element.toString(); - if (elementString.startsWith("android.media.AudioTrack.setPlaybackParams")) { - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioClock.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioClock.java deleted file mode 100644 index 600c2c88..00000000 --- a/src/com/android/tv/tuner/exoplayer/ac3/AudioClock.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ac3; - -import com.android.tv.common.SoftPreconditions; - -import android.os.SystemClock; - -/** - * Copy of {@link com.google.android.exoplayer.MediaClock}. - *

- * A simple clock for tracking the progression of media time. The clock can be started, stopped and - * its time can be set and retrieved. When started, this clock is based on - * {@link SystemClock#elapsedRealtime()}. - */ -/* package */ class AudioClock { - private boolean mStarted; - - /** - * The media time when the clock was last set or stopped. - */ - private long mPositionUs; - - /** - * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs} - * when the clock was last set or mStarted. - */ - private long mDeltaUs; - - private float mPlaybackSpeed = 1.0f; - private long mDeltaUpdatedTimeUs; - - /** - * Starts the clock. Does nothing if the clock is already started. - */ - public void start() { - if (!mStarted) { - mStarted = true; - mDeltaUs = elapsedRealtimeMinus(mPositionUs); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - } - } - - /** - * Stops the clock. Does nothing if the clock is already stopped. - */ - public void stop() { - if (mStarted) { - mPositionUs = elapsedRealtimeMinus(mDeltaUs); - mStarted = false; - } - } - - /** - * @param timeUs The position to set in microseconds. - */ - public void setPositionUs(long timeUs) { - this.mPositionUs = timeUs; - mDeltaUs = elapsedRealtimeMinus(timeUs); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - } - - /** - * @return The current position in microseconds. - */ - public long getPositionUs() { - if (!mStarted) { - return mPositionUs; - } - if (mPlaybackSpeed != 1.0f) { - long elapsedTimeFromPlaybackSpeedChanged = SystemClock.elapsedRealtime() * 1000 - - mDeltaUpdatedTimeUs; - return elapsedRealtimeMinus(mDeltaUs) - + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged); - } else { - return elapsedRealtimeMinus(mDeltaUs); - } - } - - /** - * Sets playback speed. {@code speed} should be positive. - */ - public void setPlaybackSpeed(float speed) { - SoftPreconditions.checkState(speed > 0); - mDeltaUs = elapsedRealtimeMinus(getPositionUs()); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - mPlaybackSpeed = speed; - } - - private long elapsedRealtimeMinus(long toSubtractUs) { - return SystemClock.elapsedRealtime() * 1000 - toSubtractUs; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java deleted file mode 100644 index bfdf08ac..00000000 --- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ac3; - -import android.os.SystemClock; -import android.util.Log; -import android.util.Pair; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * Monitors the rendering position of {@link AudioTrack}. - */ -public class AudioTrackMonitor { - private static final String TAG = "AudioTrackMonitor"; - private static final boolean DEBUG = false; - - // For fetched audio samples - private final ArrayList> mPtsList = new ArrayList<>(); - private final Set mSampleSize = new HashSet<>(); - private final Set mCurSampleSize = new HashSet<>(); - private final Set mAc3Header = new HashSet<>(); - - private long mExpireMs; - private long mDuration; - private long mSampleCount; - private long mTotalCount; - private long mStartMs; - - private void flush() { - mExpireMs += mDuration; - mSampleCount = 0; - mCurSampleSize.clear(); - mPtsList.clear(); - } - - /** - * Resets and initializes {@link AudioTrackMonitor}. - * - * @param duration the frequency of monitoring in milliseconds - */ - public void reset(long duration) { - mExpireMs = SystemClock.elapsedRealtime(); - mDuration = duration; - mTotalCount = 0; - mStartMs = 0; - mSampleSize.clear(); - mAc3Header.clear(); - flush(); - } - - /** - * Adds an audio sample information for monitoring. - * - * @param pts the presentation timestamp of the sample - * @param sampleSize the size in bytes of the sample - * @param header the bitrate & sampling information header of the sample - */ - public void addPts(long pts, int sampleSize, int header) { - mTotalCount++; - mSampleCount++; - mSampleSize.add(sampleSize); - mAc3Header.add(header); - mCurSampleSize.add(sampleSize); - if (mTotalCount == 1) { - mStartMs = SystemClock.elapsedRealtime(); - } - if (mPtsList.isEmpty() || mPtsList.get(mPtsList.size() - 1).first != pts) { - mPtsList.add(Pair.create(pts, 1)); - return; - } - Pair pair = mPtsList.get(mPtsList.size() - 1); - mPtsList.set(mPtsList.size() - 1, Pair.create(pair.first, pair.second + 1)); - } - - /** - * Logs if interested events are present. - *

- * Periodic logging is not enabled in release mode in order to avoid verbose logging. - */ - public void maybeLog() { - long now = SystemClock.elapsedRealtime(); - if (mExpireMs != 0 && now >= mExpireMs) { - if (DEBUG) { - long sampleDuration = (mTotalCount - 1) * - Ac3PassthroughTrackRenderer.AC3_SAMPLE_DURATION_US / 1000; - long totalDuration = now - mStartMs; - StringBuilder ptsBuilder = new StringBuilder(); - ptsBuilder.append("PTS received ").append(mSampleCount).append(", ") - .append(totalDuration - sampleDuration).append(' '); - - for (Pair pair : mPtsList) { - ptsBuilder.append('[').append(pair.first).append(':').append(pair.second) - .append("], "); - } - Log.d(TAG, ptsBuilder.toString()); - } - if (DEBUG || mCurSampleSize.size() > 1) { - Log.d(TAG, "PTS received sample size: " - + String.valueOf(mSampleSize) + mCurSampleSize + mAc3Header); - } - flush(); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java deleted file mode 100644 index bc3c5d00..00000000 --- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ac3; - -import android.media.MediaFormat; - -import com.google.android.exoplayer.audio.AudioTrack; - -import java.nio.ByteBuffer; - -/** - * {@link AudioTrack} wrapper class for trickplay operations including FF/RW. - * FF/RW trickplay operations do not need framework {@link AudioTrack}. - * This wrapper class will do nothing in disabled status for those operations. - */ -public class AudioTrackWrapper { - private final AudioTrack mAudioTrack = new AudioTrack(); - private int mAudioSessionID; - private boolean mIsEnabled; - - AudioTrackWrapper() { - mIsEnabled = true; - } - - public void resetSessionId() { - mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET; - } - - public boolean isInitialized() { - return mIsEnabled && mAudioTrack.isInitialized(); - } - - public void restart() { - if (mAudioTrack.isInitialized()) { - mAudioTrack.release(); - } - mIsEnabled = true; - resetSessionId(); - } - - public void release() { - if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { - mAudioTrack.release(); - } - } - - public void initialize() throws AudioTrack.InitializationException { - if (!mIsEnabled) { - return; - } - if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { - mAudioTrack.initialize(mAudioSessionID); - } else { - mAudioSessionID = mAudioTrack.initialize(); - } - } - - public void reset() { - if (!mIsEnabled) { - return; - } - mAudioTrack.reset(); - } - - public boolean isEnded() { - return !mIsEnabled || !mAudioTrack.hasPendingData(); - } - - public boolean isReady() { - // In the case of not playing actual audio data, Audio track is always ready. - return !mIsEnabled || mAudioTrack.hasPendingData(); - } - - public void play() { - if (!mIsEnabled) { - return; - } - mAudioTrack.play(); - } - - public void pause() { - if (!mIsEnabled) { - return; - } - mAudioTrack.pause(); - } - - public void setVolume(float volume) { - if (!mIsEnabled) { - return; - } - mAudioTrack.setVolume(volume); - } - - public void reconfigure(MediaFormat format) { - if (!mIsEnabled || format == null) { - return; - } - String mimeType = format.getString(MediaFormat.KEY_MIME); - int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); - int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); - int pcmEncoding; - try { - pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING); - } catch (Exception e) { - pcmEncoding = com.google.android.exoplayer.MediaFormat.NO_VALUE; - } - // TODO: Handle non-AC3 or non-passthrough audio. - if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) { - // Workarounds b/25955476. - // Since all devices and platforms does not support passthrough for non-stereo AC3, - // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode. - // In other words, the channel count should be always 2. - channelCount = 2; - } - mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding); - } - - public void handleDiscontinuity() { - if (!mIsEnabled) { - return; - } - mAudioTrack.handleDiscontinuity(); - } - - public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs) - throws AudioTrack.WriteException { - if (!mIsEnabled) { - return AudioTrack.RESULT_BUFFER_CONSUMED; - } - return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs); - } - - public void setStatus(boolean enable) { - if (enable == mIsEnabled) { - return; - } - mAudioTrack.reset(); - mIsEnabled = enable; - } - - public boolean isEnabled() { - return mIsEnabled; - } - - // This should be used only in case of being enabled. - public long getCurrentPositionUs(boolean isEnded) { - return mAudioTrack.getCurrentPositionUs(isEnded); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java new file mode 100644 index 00000000..5666c5b9 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import com.android.tv.common.SoftPreconditions; + +import android.os.SystemClock; + +/** + * Copy of {@link com.google.android.exoplayer.MediaClock}. + *

+ * A simple clock for tracking the progression of media time. The clock can be started, stopped and + * its time can be set and retrieved. When started, this clock is based on + * {@link SystemClock#elapsedRealtime()}. + */ +/* package */ class AudioClock { + private boolean mStarted; + + /** + * The media time when the clock was last set or stopped. + */ + private long mPositionUs; + + /** + * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs} + * when the clock was last set or mStarted. + */ + private long mDeltaUs; + + private float mPlaybackSpeed = 1.0f; + private long mDeltaUpdatedTimeUs; + + /** + * Starts the clock. Does nothing if the clock is already started. + */ + public void start() { + if (!mStarted) { + mStarted = true; + mDeltaUs = elapsedRealtimeMinus(mPositionUs); + mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; + } + } + + /** + * Stops the clock. Does nothing if the clock is already stopped. + */ + public void stop() { + if (mStarted) { + mPositionUs = elapsedRealtimeMinus(mDeltaUs); + mStarted = false; + } + } + + /** + * @param timeUs The position to set in microseconds. + */ + public void setPositionUs(long timeUs) { + this.mPositionUs = timeUs; + mDeltaUs = elapsedRealtimeMinus(timeUs); + mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; + } + + /** + * @return The current position in microseconds. + */ + public long getPositionUs() { + if (!mStarted) { + return mPositionUs; + } + if (mPlaybackSpeed != 1.0f) { + long elapsedTimeFromPlaybackSpeedChanged = SystemClock.elapsedRealtime() * 1000 + - mDeltaUpdatedTimeUs; + return elapsedRealtimeMinus(mDeltaUs) + + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged); + } else { + return elapsedRealtimeMinus(mDeltaUs); + } + } + + /** + * Sets playback speed. {@code speed} should be positive. + */ + public void setPlaybackSpeed(float speed) { + SoftPreconditions.checkState(speed > 0); + mDeltaUs = elapsedRealtimeMinus(getPositionUs()); + mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; + mPlaybackSpeed = speed; + } + + private long elapsedRealtimeMinus(long toSubtractUs) { + return SystemClock.elapsedRealtime() * 1000 - toSubtractUs; + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java new file mode 100644 index 00000000..e581092a --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import com.google.android.exoplayer.ExoPlaybackException; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.SampleHolder; + +import java.nio.ByteBuffer; + +/** A base class for audio decoders. */ +public abstract class AudioDecoder { + + /** + * Decodes an audio sample. + * + * @param sampleHolder a holder that contains the sample data and corresponding metadata + */ + public abstract void decode(SampleHolder sampleHolder); + + /** Returns a decoded sample from decoder. */ + public abstract ByteBuffer getDecodedSample(); + + /** Returns the presentation time for the decoded sample. */ + public abstract long getDecodedTimeUs(); + + /** + * Clear previous decode state if any. Prepares to decode samples of the specified encoding. + * This method should be called before using decode. + * + * @param mime audio encoding + */ + public abstract void resetDecoderState(String mimeType); + + /** Releases all the resource. */ + public abstract void release(); + + /** + * Init decoder if needed. + * + * @param format the format used to initialize decoder + */ + public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException { + // Do nothing. + } + + /** Returns input buffer that will be used in decoder. */ + public ByteBuffer getInputBuffer() { + return null; + } + + /** Returns the output format. */ + public android.media.MediaFormat getOutputFormat() { + return null; + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java new file mode 100644 index 00000000..ec616b13 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import android.os.SystemClock; +import android.util.Log; +import android.util.Pair; + +import com.google.android.exoplayer.util.MimeTypes; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Monitors the rendering position of {@link AudioTrack}. + */ +public class AudioTrackMonitor { + private static final String TAG = "AudioTrackMonitor"; + private static final boolean DEBUG = false; + + // For fetched audio samples + private final ArrayList> mPtsList = new ArrayList<>(); + private final Set mSampleSize = new HashSet<>(); + private final Set mCurSampleSize = new HashSet<>(); + private final Set mHeader = new HashSet<>(); + + private long mExpireMs; + private long mDuration; + private long mSampleCount; + private long mTotalCount; + private long mStartMs; + + private boolean mIsMp2; + + private void flush() { + mExpireMs += mDuration; + mSampleCount = 0; + mCurSampleSize.clear(); + mPtsList.clear(); + } + + /** + * Resets and initializes {@link AudioTrackMonitor}. + * + * @param duration the frequency of monitoring in milliseconds + */ + public void reset(long duration) { + mExpireMs = SystemClock.elapsedRealtime(); + mDuration = duration; + mTotalCount = 0; + mStartMs = 0; + mSampleSize.clear(); + mHeader.clear(); + flush(); + } + + public void setEncoding(String mime) { + mIsMp2 = MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mime); + } + + /** + * Adds an audio sample information for monitoring. + * + * @param pts the presentation timestamp of the sample + * @param sampleSize the size in bytes of the sample + * @param header the bitrate & sampling information header of the sample + */ + public void addPts(long pts, int sampleSize, int header) { + mTotalCount++; + mSampleCount++; + mSampleSize.add(sampleSize); + mHeader.add(header); + mCurSampleSize.add(sampleSize); + if (mTotalCount == 1) { + mStartMs = SystemClock.elapsedRealtime(); + } + if (mPtsList.isEmpty() || mPtsList.get(mPtsList.size() - 1).first != pts) { + mPtsList.add(Pair.create(pts, 1)); + return; + } + Pair pair = mPtsList.get(mPtsList.size() - 1); + mPtsList.set(mPtsList.size() - 1, Pair.create(pair.first, pair.second + 1)); + } + + /** + * Logs if interested events are present. + *

+ * Periodic logging is not enabled in release mode in order to avoid verbose logging. + */ + public void maybeLog() { + long now = SystemClock.elapsedRealtime(); + if (mExpireMs != 0 && now >= mExpireMs) { + if (DEBUG) { + long unitDuration = mIsMp2 ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US + : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US; + long sampleDuration = (mTotalCount - 1) * unitDuration / 1000; + long totalDuration = now - mStartMs; + StringBuilder ptsBuilder = new StringBuilder(); + ptsBuilder.append("PTS received ").append(mSampleCount).append(", ") + .append(totalDuration - sampleDuration).append(' '); + + for (Pair pair : mPtsList) { + ptsBuilder.append('[').append(pair.first).append(':').append(pair.second) + .append("], "); + } + Log.d(TAG, ptsBuilder.toString()); + } + if (DEBUG || mCurSampleSize.size() > 1) { + Log.d(TAG, "PTS received sample size: " + + String.valueOf(mSampleSize) + mCurSampleSize + mHeader); + } + flush(); + } + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java new file mode 100644 index 00000000..953c9fc4 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import android.media.MediaFormat; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.audio.AudioTrack; + +import java.nio.ByteBuffer; + +/** + * {@link AudioTrack} wrapper class for trickplay operations including FF/RW. + * FF/RW trickplay operations do not need framework {@link AudioTrack}. + * This wrapper class will do nothing in disabled status for those operations. + */ +public class AudioTrackWrapper { + private static final int PCM16_FRAME_BYTES = 2; + private static final int AC3_FRAMES_IN_ONE_SAMPLE = 1536; + private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK = + MpegTsDefaultAudioTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK; + private final AudioTrack mAudioTrack = new AudioTrack(); + private int mAudioSessionID; + private boolean mIsEnabled; + + AudioTrackWrapper() { + mIsEnabled = true; + } + + public void resetSessionId() { + mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET; + } + + public boolean isInitialized() { + return mIsEnabled && mAudioTrack.isInitialized(); + } + + public void restart() { + if (mAudioTrack.isInitialized()) { + mAudioTrack.release(); + } + mIsEnabled = true; + resetSessionId(); + } + + public void release() { + if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { + mAudioTrack.release(); + } + } + + public void initialize() throws AudioTrack.InitializationException { + if (!mIsEnabled) { + return; + } + if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { + mAudioTrack.initialize(mAudioSessionID); + } else { + mAudioSessionID = mAudioTrack.initialize(); + } + } + + public void reset() { + if (!mIsEnabled) { + return; + } + mAudioTrack.reset(); + } + + public boolean isEnded() { + return !mIsEnabled || !mAudioTrack.hasPendingData(); + } + + public boolean isReady() { + // In the case of not playing actual audio data, Audio track is always ready. + return !mIsEnabled || mAudioTrack.hasPendingData(); + } + + public void play() { + if (!mIsEnabled) { + return; + } + mAudioTrack.play(); + } + + public void pause() { + if (!mIsEnabled) { + return; + } + mAudioTrack.pause(); + } + + public void setVolume(float volume) { + if (!mIsEnabled) { + return; + } + mAudioTrack.setVolume(volume); + } + + public void reconfigure(MediaFormat format, int audioBufferSize) { + if (!mIsEnabled || format == null) { + return; + } + String mimeType = format.getString(MediaFormat.KEY_MIME); + int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); + int pcmEncoding; + try { + pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING); + } catch (Exception e) { + pcmEncoding = C.ENCODING_PCM_16BIT; + } + // TODO: Handle non-AC3. + if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) { + // Workarounds b/25955476. + // Since all devices and platforms does not support passthrough for non-stereo AC3, + // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode. + // In other words, the channel count should be always 2. + channelCount = 2; + } + if (MediaFormat.MIMETYPE_AUDIO_RAW.equalsIgnoreCase(mimeType)) { + audioBufferSize = + channelCount + * PCM16_FRAME_BYTES + * AC3_FRAMES_IN_ONE_SAMPLE + * BUFFERED_SAMPLES_IN_AUDIOTRACK; + } + mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, audioBufferSize); + } + + public void handleDiscontinuity() { + if (!mIsEnabled) { + return; + } + mAudioTrack.handleDiscontinuity(); + } + + public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs) + throws AudioTrack.WriteException { + if (!mIsEnabled) { + return AudioTrack.RESULT_BUFFER_CONSUMED; + } + return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs); + } + + public void setStatus(boolean enable) { + if (enable == mIsEnabled) { + return; + } + mAudioTrack.reset(); + mIsEnabled = enable; + } + + public boolean isEnabled() { + return mIsEnabled; + } + + // This should be used only in case of being enabled. + public long getCurrentPositionUs(boolean isEnded) { + return mAudioTrack.getCurrentPositionUs(isEnded); + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java new file mode 100644 index 00000000..72bc68b6 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import android.media.MediaCodec; +import android.util.Log; + +import com.google.android.exoplayer.CodecCounters; +import com.google.android.exoplayer.DecoderInfo; +import com.google.android.exoplayer.ExoPlaybackException; +import com.google.android.exoplayer.MediaCodecSelector; +import com.google.android.exoplayer.MediaCodecUtil; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.SampleHolder; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** A decoder to use MediaCodec for decoding audio stream. */ +public class MediaCodecAudioDecoder extends AudioDecoder { + private static final String TAG = "MediaCodecAudioDecoder"; + + public static final int INDEX_INVALID = -1; + + private final CodecCounters mCodecCounters; + private final MediaCodecSelector mSelector; + + private MediaCodec mCodec; + private MediaCodec.BufferInfo mOutputBufferInfo; + private ByteBuffer mMediaCodecOutputBuffer; + private ArrayList mDecodeOnlyPresentationTimestamps; + private boolean mWaitingForFirstSyncFrame; + private boolean mIsNewIndex; + private int mInputIndex; + private int mOutputIndex; + + /** Creates a MediaCodec based audio decoder. */ + public MediaCodecAudioDecoder(MediaCodecSelector selector) { + mSelector = selector; + mOutputBufferInfo = new MediaCodec.BufferInfo(); + mCodecCounters = new CodecCounters(); + mDecodeOnlyPresentationTimestamps = new ArrayList<>(); + } + + /** Returns {@code true} if there is decoder for {@code mimeType}. */ + public static boolean supportMimeType(MediaCodecSelector selector, String mimeType) { + if (selector == null) { + return false; + } + return getDecoderInfo(selector, mimeType) != null; + } + + private static DecoderInfo getDecoderInfo(MediaCodecSelector selector, String mimeType) { + try { + return selector.getDecoderInfo(mimeType, false); + } catch (MediaCodecUtil.DecoderQueryException e) { + Log.e(TAG, "Select decoder error:" + e); + return null; + } + } + + private boolean shouldInitCodec(MediaFormat format) { + return format != null && mCodec == null; + } + + @Override + public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException { + if (!shouldInitCodec(format)) { + return; + } + + String mimeType = format.mimeType; + DecoderInfo decoderInfo = getDecoderInfo(mSelector, mimeType); + if (decoderInfo == null) { + Log.i(TAG, "There is not decoder found for " + mimeType); + return; + } + + String codecName = decoderInfo.name; + try { + mCodec = MediaCodec.createByCodecName(codecName); + mCodec.configure(format.getFrameworkMediaFormatV16(), null, null, 0); + mCodec.start(); + } catch (Exception e) { + Log.e(TAG, "Failed when configure or start codec:" + e); + throw new ExoPlaybackException(e); + } + mInputIndex = INDEX_INVALID; + mOutputIndex = INDEX_INVALID; + mWaitingForFirstSyncFrame = true; + mCodecCounters.codecInitCount++; + } + + @Override + public void resetDecoderState(String mimeType) { + if (mCodec == null) { + return; + } + mInputIndex = INDEX_INVALID; + mOutputIndex = INDEX_INVALID; + mDecodeOnlyPresentationTimestamps.clear(); + mCodec.flush(); + mWaitingForFirstSyncFrame = true; + } + + @Override + public void release() { + if (mCodec != null) { + mDecodeOnlyPresentationTimestamps.clear(); + mInputIndex = INDEX_INVALID; + mOutputIndex = INDEX_INVALID; + mCodecCounters.codecReleaseCount++; + try { + mCodec.stop(); + } finally { + try { + mCodec.release(); + } finally { + mCodec = null; + } + } + } + } + + /** Returns the index of input buffer which is ready for using. */ + public int getInputIndex() { + return mInputIndex; + } + + @Override + public ByteBuffer getInputBuffer() { + if (mInputIndex < 0) { + mInputIndex = mCodec.dequeueInputBuffer(0); + if (mInputIndex < 0) { + return null; + } + return mCodec.getInputBuffer(mInputIndex); + } + return mCodec.getInputBuffer(mInputIndex); + } + + @Override + public void decode(SampleHolder sampleHolder) { + if (mWaitingForFirstSyncFrame) { + if (!sampleHolder.isSyncFrame()) { + sampleHolder.clearData(); + return; + } + mWaitingForFirstSyncFrame = false; + } + long presentationTimeUs = sampleHolder.timeUs; + if (sampleHolder.isDecodeOnly()) { + mDecodeOnlyPresentationTimestamps.add(presentationTimeUs); + } + mCodec.queueInputBuffer(mInputIndex, 0, sampleHolder.data.limit(), presentationTimeUs, 0); + mInputIndex = INDEX_INVALID; + mCodecCounters.inputBufferCount++; + } + + private int getDecodeOnlyIndex(long presentationTimeUs) { + final int size = mDecodeOnlyPresentationTimestamps.size(); + for (int i = 0; i < size; i++) { + if (mDecodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) { + return i; + } + } + return INDEX_INVALID; + } + + /** Returns the index of output buffer which is ready for using. */ + public int getOutputIndex() { + if (mOutputIndex < 0) { + mOutputIndex = mCodec.dequeueOutputBuffer(mOutputBufferInfo, 0); + mIsNewIndex = true; + } else { + mIsNewIndex = false; + } + return mOutputIndex; + } + + @Override + public android.media.MediaFormat getOutputFormat() { + return mCodec.getOutputFormat(); + } + + /** Returns {@code true} if the output is only for decoding but not for rendering. */ + public boolean maybeDecodeOnlyIndex() { + int decodeOnlyIndex = getDecodeOnlyIndex(mOutputBufferInfo.presentationTimeUs); + if (decodeOnlyIndex != INDEX_INVALID) { + mCodec.releaseOutputBuffer(mOutputIndex, false); + mCodecCounters.skippedOutputBufferCount++; + mDecodeOnlyPresentationTimestamps.remove(decodeOnlyIndex); + mOutputIndex = INDEX_INVALID; + return true; + } + return false; + } + + @Override + public ByteBuffer getDecodedSample() { + if (maybeDecodeOnlyIndex() || mOutputIndex < 0) { + return null; + } + if (mIsNewIndex) { + mMediaCodecOutputBuffer = mCodec.getOutputBuffer(mOutputIndex); + } + return mMediaCodecOutputBuffer; + } + + @Override + public long getDecodedTimeUs() { + return mOutputBufferInfo.presentationTimeUs; + } + + /** Releases the output buffer after rendering. */ + public void releaseOutputBuffer() { + mCodecCounters.renderedOutputBufferCount++; + mCodec.releaseOutputBuffer(mOutputIndex, false); + mOutputIndex = INDEX_INVALID; + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java new file mode 100644 index 00000000..77170419 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import android.media.MediaCodec; +import android.os.Build; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; + +import com.google.android.exoplayer.CodecCounters; +import com.google.android.exoplayer.ExoPlaybackException; +import com.google.android.exoplayer.MediaClock; +import com.google.android.exoplayer.MediaCodecSelector; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.MediaFormatHolder; +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.TrackRenderer; +import com.google.android.exoplayer.audio.AudioTrack; +import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.MimeTypes; +import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; +import com.android.tv.tuner.tvinput.TunerDebug; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Decodes and renders DTV audio. Supports MediaCodec based decoding, passthrough playback and + * ffmpeg based software decoding (AC3, MP2). + */ +public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements MediaClock { + public static final int MSG_SET_VOLUME = 10000; + public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1; + public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2; + + // ATSC/53 allows sample rate to be only 48Khz. + // One AC3 sample has 1536 frames, and its duration is 32ms. + public static final long AC3_SAMPLE_DURATION_US = 32000; + + // TODO: Check whether DVB broadcasting uses sample rate other than 48Khz. + // MPEG-1 audio Layer II and III has 1152 frames per sample. + // 1152 frames duration is 24ms when sample rate is 48Khz. + static final long MP2_SAMPLE_DURATION_US = 24000; + + // This is around 150ms, 150ms is big enough not to under-run AudioTrack, + // and 150ms is also small enough to fill the buffer rapidly. + static int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5; + public static final long INITIAL_AUDIO_BUFFERING_TIME_US = + BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US; + + + private static final String TAG = "MpegTsDefaultAudioTrac"; + private static final boolean DEBUG = false; + + /** + * Interface definition for a callback to be notified of + * {@link com.google.android.exoplayer.audio.AudioTrack} error. + */ + public interface EventListener { + void onAudioTrackInitializationError(AudioTrack.InitializationException e); + void onAudioTrackWriteError(AudioTrack.WriteException e); + } + + private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2; + private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024; + private static final int MONITOR_DURATION_MS = 1000; + private static final int AC3_HEADER_BITRATE_OFFSET = 4; + private static final int MP2_HEADER_BITRATE_OFFSET = 2; + private static final int MP2_HEADER_BITRATE_MASK = 0xfc; + + // Keep this as static in order to prevent new framework AudioTrack creation + // while old AudioTrack is being released. + private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper(); + private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000; + + // Ignore AudioTrack backward movement if duration of movement is below the threshold. + private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000; + + // AudioTrack position cannot go ahead beyond this limit. + private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000; + + // Since MediaCodec processing and AudioTrack playing add delay, + // PTS interpolated time should be delayed reasonably when AudioTrack is not used. + private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000; + + private final MediaCodecSelector mSelector; + + private final CodecCounters mCodecCounters; + private final SampleSource.SampleSourceReader mSource; + private final MediaFormatHolder mFormatHolder; + private final EventListener mEventListener; + private final Handler mEventHandler; + private final AudioTrackMonitor mMonitor; + private final AudioClock mAudioClock; + private final boolean mAc3Passthrough; + private final boolean mSoftwareDecoderAvailable; + + private MediaFormat mFormat; + private SampleHolder mSampleHolder; + private String mDecodingMime; + private boolean mFormatConfigured; + private int mSampleSize; + private final ByteBuffer mOutputBuffer; + private AudioDecoder mAudioDecoder; + private boolean mOutputReady; + private int mTrackIndex; + private boolean mSourceStateReady; + private boolean mInputStreamEnded; + private boolean mOutputStreamEnded; + private long mEndOfStreamMs; + private long mCurrentPositionUs; + private int mPresentationCount; + private long mPresentationTimeUs; + private long mInterpolatedTimeUs; + private long mPreviousPositionUs; + private boolean mIsStopped; + private boolean mEnabled = true; + private boolean mIsMuted; + private ArrayList mTracksIndex; + private boolean mUseFrameworkDecoder; + + public MpegTsDefaultAudioTrackRenderer( + SampleSource source, + MediaCodecSelector selector, + Handler eventHandler, + EventListener listener, + boolean hasSoftwareAudioDecoder, + boolean usePassthrough) { + mSource = source.register(); + mSelector = selector; + mEventHandler = eventHandler; + mEventListener = listener; + mTrackIndex = -1; + mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE); + mFormatHolder = new MediaFormatHolder(); + AUDIO_TRACK.restart(); + mCodecCounters = new CodecCounters(); + mMonitor = new AudioTrackMonitor(); + mAudioClock = new AudioClock(); + mTracksIndex = new ArrayList<>(); + mAc3Passthrough = usePassthrough; + mSoftwareDecoderAvailable = hasSoftwareAudioDecoder && FfmpegDecoderClient.isAvailable(); + } + + @Override + protected MediaClock getMediaClock() { + return this; + } + + private boolean handlesMimeType(String mimeType) { + return mimeType.equals(MimeTypes.AUDIO_AC3) + || mimeType.equals(MimeTypes.AUDIO_E_AC3) + || mimeType.equals(MimeTypes.AUDIO_MPEG_L2) + || MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType); + } + + @Override + protected boolean doPrepare(long positionUs) throws ExoPlaybackException { + boolean sourcePrepared = mSource.prepare(positionUs); + if (!sourcePrepared) { + return false; + } + for (int i = 0; i < mSource.getTrackCount(); i++) { + String mimeType = mSource.getFormat(i).mimeType; + if (MimeTypes.isAudio(mimeType) && handlesMimeType(mimeType)) { + if (mTrackIndex < 0) { + mTrackIndex = i; + } + mTracksIndex.add(i); + } + } + + // TODO: Check this case. Source does not have the proper mime type. + return true; + } + + @Override + protected int getTrackCount() { + return mTracksIndex.size(); + } + + @Override + protected MediaFormat getFormat(int track) { + Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); + return mSource.getFormat(mTracksIndex.get(track)); + } + + @Override + protected void onEnabled(int track, long positionUs, boolean joining) { + Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); + mTrackIndex = mTracksIndex.get(track); + mSource.enable(mTrackIndex, positionUs); + seekToInternal(positionUs); + } + + @Override + protected void onDisabled() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + AUDIO_TRACK.resetSessionId(); + } + clearDecodeState(); + mFormat = null; + mSource.disable(mTrackIndex); + } + + @Override + protected void onReleased() { + releaseDecoder(); + AUDIO_TRACK.release(); + mSource.release(); + } + + @Override + protected boolean isEnded() { + return mOutputStreamEnded && AUDIO_TRACK.isEnded(); + } + + @Override + protected boolean isReady() { + return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady)); + } + + private void seekToInternal(long positionUs) { + mMonitor.reset(MONITOR_DURATION_MS); + mSourceStateReady = false; + mInputStreamEnded = false; + mOutputStreamEnded = false; + mPresentationTimeUs = positionUs; + mPresentationCount = 0; + mPreviousPositionUs = 0; + mCurrentPositionUs = Long.MIN_VALUE; + mInterpolatedTimeUs = Long.MIN_VALUE; + mAudioClock.setPositionUs(positionUs); + } + + @Override + protected void seekTo(long positionUs) { + mSource.seekToUs(positionUs); + AUDIO_TRACK.reset(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // resetSessionId() will create a new framework AudioTrack instead of reusing old one. + AUDIO_TRACK.resetSessionId(); + } + seekToInternal(positionUs); + clearDecodeState(); + } + + @Override + protected void onStarted() { + AUDIO_TRACK.play(); + mAudioClock.start(); + mIsStopped = false; + } + + @Override + protected void onStopped() { + AUDIO_TRACK.pause(); + mAudioClock.stop(); + mIsStopped = true; + } + + @Override + protected void maybeThrowError() throws ExoPlaybackException { + try { + mSource.maybeThrowError(); + } catch (IOException e) { + throw new ExoPlaybackException(e); + } + } + + @Override + protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + mMonitor.maybeLog(); + try { + if (mEndOfStreamMs != 0) { + // Ensure playback stops, after EoS was notified. + // Sometimes MediaCodecTrackRenderer does not fetch EoS timely + // after EoS was notified here long before. + long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs; + if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) { + throw new ExoPlaybackException("Much time has elapsed after EoS"); + } + } + boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs); + if (mSourceStateReady != continueBuffering) { + mSourceStateReady = continueBuffering; + if (DEBUG) { + Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady)); + } + } + long discontinuity = mSource.readDiscontinuity(mTrackIndex); + if (discontinuity != SampleSource.NO_DISCONTINUITY) { + AUDIO_TRACK.handleDiscontinuity(); + mPresentationTimeUs = discontinuity; + mPresentationCount = 0; + clearDecodeState(); + return; + } + if (mFormat == null) { + readFormat(); + return; + } + + if (mAudioDecoder != null) { + mAudioDecoder.maybeInitDecoder(mFormat); + } + // Process only one sample at a time for doSomeWork() when using FFmpeg decoder. + if (processOutput()) { + if (!mOutputReady) { + while (feedInputBuffer()) { + if (mOutputReady) break; + } + } + } + mCodecCounters.ensureUpdated(); + } catch (IOException e) { + throw new ExoPlaybackException(e); + } + } + + private void ensureAudioTrackInitialized() { + if (!AUDIO_TRACK.isInitialized()) { + try { + if (DEBUG) { + Log.d(TAG, "AudioTrack initialized"); + } + AUDIO_TRACK.initialize(); + } catch (AudioTrack.InitializationException e) { + Log.e(TAG, "Error on AudioTrack initialization", e); + notifyAudioTrackInitializationError(e); + + // Do not throw exception here but just disabling audioTrack to keep playing + // video without audio. + AUDIO_TRACK.setStatus(false); + } + if (getState() == TrackRenderer.STATE_STARTED) { + if (DEBUG) { + Log.d(TAG, "AudioTrack played"); + } + AUDIO_TRACK.play(); + } + } + } + + private void clearDecodeState() { + mOutputReady = false; + if (mAudioDecoder != null) { + mAudioDecoder.resetDecoderState(mDecodingMime); + } + AUDIO_TRACK.reset(); + } + + private void releaseDecoder() { + if (mAudioDecoder != null) { + mAudioDecoder.release(); + } + } + + private void readFormat() throws IOException, ExoPlaybackException { + int result = mSource.readData(mTrackIndex, mCurrentPositionUs, + mFormatHolder, mSampleHolder); + if (result == SampleSource.FORMAT_READ) { + onInputFormatChanged(mFormatHolder); + } + } + + private MediaFormat convertMediaFormatToRaw(MediaFormat format) { + return MediaFormat.createAudioFormat( + format.trackId, + MimeTypes.AUDIO_RAW, + format.bitrate, + format.maxInputSize, + format.durationUs, + format.channelCount, + format.sampleRate, + format.initializationData, + format.language); + } + + private void onInputFormatChanged(MediaFormatHolder formatHolder) + throws ExoPlaybackException { + String mimeType = formatHolder.format.mimeType; + mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType); + if (mUseFrameworkDecoder) { + mAudioDecoder = new MediaCodecAudioDecoder(mSelector); + mFormat = formatHolder.format; + mAudioDecoder.maybeInitDecoder(mFormat); + mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); + } else if (mSoftwareDecoderAvailable + && (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mimeType) + || MimeTypes.AUDIO_AC3.equalsIgnoreCase(mimeType) && !mAc3Passthrough)) { + releaseDecoder(); + mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); + mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); + mAudioDecoder = FfmpegDecoderClient.getInstance(); + mDecodingMime = mimeType; + mFormat = convertMediaFormatToRaw(formatHolder.format); + } else { + mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); + mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); + mFormat = formatHolder.format; + releaseDecoder(); + } + mFormatConfigured = true; + mMonitor.setEncoding(mimeType); + if (DEBUG && !mUseFrameworkDecoder) { + Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString()); + } + clearDecodeState(); + if (!mUseFrameworkDecoder) { + AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), 0); + } + } + + private void onSampleSizeChanged(int sampleSize) { + if (DEBUG) { + Log.d(TAG, "Sample size was changed to : " + sampleSize); + } + clearDecodeState(); + int audioBufferSize = sampleSize * BUFFERED_SAMPLES_IN_AUDIOTRACK; + mSampleSize = sampleSize; + AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), audioBufferSize); + } + + private void onOutputFormatChanged(android.media.MediaFormat format) { + if (DEBUG) { + Log.d(TAG, "AudioTrack was configured to FORMAT: " + format.toString()); + } + AUDIO_TRACK.reconfigure(format, 0); + } + + private boolean feedInputBuffer() throws IOException, ExoPlaybackException { + if (mInputStreamEnded) { + return false; + } + + if (mUseFrameworkDecoder) { + boolean indexChanged = + ((MediaCodecAudioDecoder) mAudioDecoder).getInputIndex() + == MediaCodecAudioDecoder.INDEX_INVALID; + if (indexChanged) { + mSampleHolder.data = mAudioDecoder.getInputBuffer(); + if (mSampleHolder.data != null) { + mSampleHolder.clearData(); + } else { + return false; + } + } + } else { + mSampleHolder.data.clear(); + mSampleHolder.size = 0; + } + int result = + mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder); + switch (result) { + case SampleSource.NOTHING_READ: { + return false; + } + case SampleSource.FORMAT_READ: { + Log.i(TAG, "Format was read again"); + onInputFormatChanged(mFormatHolder); + return true; + } + case SampleSource.END_OF_STREAM: { + Log.i(TAG, "End of stream from SampleSource"); + mInputStreamEnded = true; + return false; + } + default: { + if (mSampleHolder.size != mSampleSize + && mFormatConfigured + && !mUseFrameworkDecoder) { + onSampleSizeChanged(mSampleHolder.size); + } + mSampleHolder.data.flip(); + if (!mUseFrameworkDecoder) { + if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { + mMonitor.addPts( + mSampleHolder.timeUs, + mOutputBuffer.position(), + mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET) + & MP2_HEADER_BITRATE_MASK); + } else { + mMonitor.addPts( + mSampleHolder.timeUs, + mOutputBuffer.position(), + mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff); + } + } + if (mAudioDecoder != null) { + mAudioDecoder.decode(mSampleHolder); + if (mUseFrameworkDecoder) { + int outputIndex = + ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex(); + if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + onOutputFormatChanged(mAudioDecoder.getOutputFormat()); + return true; + } else if (outputIndex < 0) { + return true; + } + if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) { + AUDIO_TRACK.handleDiscontinuity(); + return true; + } + } + ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample(); + long presentationTimeUs = mAudioDecoder.getDecodedTimeUs(); + decodeDone(outputBuffer, presentationTimeUs); + } else { + decodeDone(mSampleHolder.data, mSampleHolder.timeUs); + } + return true; + } + } + } + + private boolean processOutput() throws ExoPlaybackException { + if (mOutputStreamEnded) { + return false; + } + if (!mOutputReady) { + if (mInputStreamEnded) { + mOutputStreamEnded = true; + mEndOfStreamMs = SystemClock.elapsedRealtime(); + return false; + } + return true; + } + + ensureAudioTrackInitialized(); + int handleBufferResult; + try { + // To reduce discontinuity, interpolate presentation time. + if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { + mInterpolatedTimeUs = mPresentationTimeUs + + mPresentationCount * MP2_SAMPLE_DURATION_US; + } else if (!mUseFrameworkDecoder) { + mInterpolatedTimeUs = mPresentationTimeUs + + mPresentationCount * AC3_SAMPLE_DURATION_US; + } else { + mInterpolatedTimeUs = mPresentationTimeUs; + } + handleBufferResult = + AUDIO_TRACK.handleBuffer( + mOutputBuffer, 0, mOutputBuffer.limit(), mInterpolatedTimeUs); + } catch (AudioTrack.WriteException e) { + notifyAudioTrackWriteError(e); + throw new ExoPlaybackException(e); + } + if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { + Log.i(TAG, "Play discontinuity happened"); + mCurrentPositionUs = Long.MIN_VALUE; + } + if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { + mCodecCounters.renderedOutputBufferCount++; + mOutputReady = false; + if (mUseFrameworkDecoder) { + ((MediaCodecAudioDecoder) mAudioDecoder).releaseOutputBuffer(); + } + return true; + } + return false; + } + + @Override + protected long getDurationUs() { + return mSource.getFormat(mTrackIndex).durationUs; + } + + @Override + protected long getBufferedPositionUs() { + long pos = mSource.getBufferedPositionUs(); + return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US + ? pos : Math.max(pos, getPositionUs()); + } + + @Override + public long getPositionUs() { + if (!AUDIO_TRACK.isInitialized()) { + return mAudioClock.getPositionUs(); + } else if (!AUDIO_TRACK.isEnabled()) { + if (mInterpolatedTimeUs > 0 && !mUseFrameworkDecoder) { + return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US; + } + return mPresentationTimeUs; + } + long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded()); + if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) { + mPreviousPositionUs = 0L; + if (DEBUG) { + long oldPositionUs = Math.max(mCurrentPositionUs, 0); + long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); + Log.d(TAG, "Audio position is not set, diff in us: " + + String.valueOf(currentPositionUs - oldPositionUs)); + } + mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); + } else { + if (mPreviousPositionUs + > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) { + Log.e(TAG, "audio_position BACK JUMP: " + + (mPreviousPositionUs - audioTrackCurrentPositionUs)); + mCurrentPositionUs = audioTrackCurrentPositionUs; + } else { + mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs); + } + mPreviousPositionUs = audioTrackCurrentPositionUs; + } + long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US; + if (mCurrentPositionUs > upperBound) { + mCurrentPositionUs = upperBound; + } + return mCurrentPositionUs; + } + + private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) { + if (outputBuffer == null || mOutputBuffer == null) { + return; + } + if (presentationTimeUs < 0) { + Log.e(TAG, "decodeDone - invalid presentationTimeUs"); + return; + } + + if (TunerDebug.ENABLED) { + TunerDebug.setAudioPtsUs(presentationTimeUs); + } + + mOutputBuffer.clear(); + Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit()); + + mOutputBuffer.put(outputBuffer); + if (presentationTimeUs == mPresentationTimeUs) { + mPresentationCount++; + } else { + mPresentationCount = 0; + mPresentationTimeUs = presentationTimeUs; + } + mOutputBuffer.flip(); + mOutputReady = true; + } + + private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { + if (mEventHandler == null || mEventListener == null) { + return; + } + mEventHandler.post(new Runnable() { + @Override + public void run() { + mEventListener.onAudioTrackInitializationError(e); + } + }); + } + + private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { + if (mEventHandler == null || mEventListener == null) { + return; + } + mEventHandler.post(new Runnable() { + @Override + public void run() { + mEventListener.onAudioTrackWriteError(e); + } + }); + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + switch (messageType) { + case MSG_SET_VOLUME: + float volume = (Float) message; + // Workaround: we cannot mute the audio track by setting the volume to 0, we need to + // disable the AUDIO_TRACK for this intent. However, enabling/disabling audio track + // whenever volume is being set might cause side effects, therefore we only handle + // "explicit mute operations", i.e., only after certain non-zero volume has been + // set, the subsequent volume setting operations will be consider as mute/un-mute + // operations and thus enable/disable the audio track. + if (mIsMuted && volume > 0) { + mIsMuted = false; + if (mEnabled) { + setStatus(true); + } + } else if (!mIsMuted && volume == 0) { + mIsMuted = true; + if (mEnabled) { + setStatus(false); + } + } + AUDIO_TRACK.setVolume(volume); + break; + case MSG_SET_AUDIO_TRACK: + mEnabled = (Integer) message == 1; + setStatus(mEnabled); + break; + case MSG_SET_PLAYBACK_SPEED: + mAudioClock.setPlaybackSpeed((Float) message); + break; + default: + super.handleMessage(messageType, message); + } + } + + private void setStatus(boolean enabled) { + if (enabled == AUDIO_TRACK.isEnabled()) { + return; + } + if (!enabled) { + // mAudioClock can be different from getPositionUs. In order to sync them, + // we set mAudioClock. + mAudioClock.setPositionUs(getPositionUs()); + } + AUDIO_TRACK.setStatus(enabled); + if (enabled) { + // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to + // the current position. If not, AUDIO_TRACK has the obsolete data. + seekTo(mAudioClock.getPositionUs()); + } + } +} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java new file mode 100644 index 00000000..142aa9b2 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.audio; + +import android.os.Handler; + +import com.google.android.exoplayer.ExoPlaybackException; +import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; +import com.google.android.exoplayer.MediaCodecSelector; +import com.google.android.exoplayer.SampleSource; + +/** + * MPEG-2 TS audio track renderer. + * + *

Since the audio output from {@link android.media.MediaExtractor} contains extra samples at the + * beginning, using original {@link MediaCodecAudioTrackRenderer} as audio renderer causes + * asynchronous Audio/Video outputs. This class calculates the offset of audio data and adjust the + * presentation times to avoid the asynchronous Audio/Video problem. + */ +public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRenderer { + private final Ac3EventListener mListener; + + public interface Ac3EventListener extends EventListener { + /** + * Invoked when a {@link android.media.PlaybackParams} set to an + * {@link android.media.AudioTrack} is not valid. + * + * @param e The corresponding exception. + */ + void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e); + } + + public MpegTsMediaCodecAudioTrackRenderer( + SampleSource source, + MediaCodecSelector mediaCodecSelector, + Handler eventHandler, + EventListener eventListener) { + super(source, mediaCodecSelector, eventHandler, eventListener); + mListener = (Ac3EventListener) eventListener; + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + if (messageType == MSG_SET_PLAYBACK_PARAMS) { + try { + super.handleMessage(messageType, message); + } catch (IllegalArgumentException e) { + if (isAudioTrackSetPlaybackParamsError(e)) { + notifyAudioTrackSetPlaybackParamsError(e); + } + } + return; + } + super.handleMessage(messageType, message); + } + + private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) { + if (eventHandler != null && mListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + mListener.onAudioTrackSetPlaybackParamsError(e); + } + }); + } + } + + static private boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { + if (e.getStackTrace() == null || e.getStackTrace().length < 1) { + return false; + } + for (StackTraceElement element : e.getStackTrace()) { + String elementString = element.toString(); + if (elementString.startsWith("android.media.AudioTrack.setPlaybackParams")) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java index eb596e93..112e9dc4 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java @@ -25,13 +25,14 @@ import android.util.Log; import android.util.Pair; import com.google.android.exoplayer.SampleHolder; +import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.exoplayer.SampleExtractor; import com.android.tv.util.Utils; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.ConcurrentModificationException; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -59,7 +60,8 @@ public class BufferManager { private final SampleChunk.SampleChunkCreator mSampleChunkCreator; // Maps from track name to a map which maps from starting position to {@link SampleChunk}. - private final Map> mChunkMap = new ArrayMap<>(); + private final Map>> mChunkMap = + new ArrayMap<>(); private final Map mStartPositionMap = new ArrayMap<>(); private final Map mEvictListeners = new ArrayMap<>(); private final StorageManager mStorageManager; @@ -77,13 +79,11 @@ public class BufferManager { } }; - private volatile boolean mClosed = false; private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK; private long mTotalWriteSize; private long mTotalWriteTimeNs; private float mWriteBandwidth = 0.0f; private volatile int mSpeedCheckCount; - private boolean mDisabled = false; public interface ChunkEvictedListener { void onChunkEvicted(String id, long createdTimeMs); @@ -173,6 +173,66 @@ public class BufferManager { void release() throws IOException; } + /** + * A Track format which will be loaded and saved from the permanent storage for recordings. + */ + public static class TrackFormat { + + /** + * The track id for the specified track. The track id will be used as a track identifier + * for recordings. + */ + public final String trackId; + + /** + * The {@link MediaFormat} for the specified track. + */ + public final MediaFormat format; + + /** + * Creates TrackFormat. + * @param trackId + * @param format + */ + public TrackFormat(String trackId, MediaFormat format) { + this.trackId = trackId; + this.format = format; + } + } + + /** + * A Holder for a sample position which will be loaded from the index file for recordings. + */ + public static class PositionHolder { + + /** + * The current sample position in microseconds. + * The position is identical to the PTS(presentation time stamp) of the sample. + */ + public final long positionUs; + + /** + * Base sample position for the current {@link SampleChunk}. + */ + public final long basePositionUs; + + /** + * The file offset for the current sample in the current {@link SampleChunk}. + */ + public final int offset; + + /** + * Creates a holder for a specific position in the recording. + * @param positionUs + * @param offset + */ + public PositionHolder(long positionUs, long basePositionUs, int offset) { + this.positionUs = positionUs; + this.basePositionUs = basePositionUs; + this.offset = offset; + } + } + /** * Storage configuration and policy manager for {@link BufferManager} */ @@ -185,11 +245,6 @@ public class BufferManager { */ File getBufferDir(); - /** - * Cleans up storage. - */ - void clearStorage(); - /** * Informs whether the storage is used for persistent use. (eg. dvr recording/play) * @@ -220,29 +275,27 @@ public class BufferManager { * Reads track name & {@link MediaFormat} from storage. * * @param isAudio {@code true} if it is for audio track - * @return {@link Pair} of track name & {@link MediaFormat} - * @throws IOException + * @return {@link List} of TrackFormat */ - Pair readTrackInfoFile(boolean isAudio) throws IOException; + List readTrackInfoFiles(boolean isAudio); /** - * Reads sample indexes for each written sample from storage. + * Reads key sample positions for each written sample from storage. * * @param trackId track name * @return indexes of the specified track * @throws IOException */ - ArrayList readIndexFile(String trackId) throws IOException; + ArrayList readIndexFile(String trackId) throws IOException; /** * Writes track information to storage. * - * @param trackId track name - * @param format {@link android.media.MediaFormat} of the track + * @param formatList {@list List} of TrackFormat * @param isAudio {@code true} if it is for audio track * @throws IOException */ - void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) + void writeTrackInfoFiles(List formatList, boolean isAudio) throws IOException; /** @@ -252,7 +305,7 @@ public class BufferManager { * @param index {@link SampleChunk} container * @throws IOException */ - void writeIndexFile(String trackName, SortedMap index) + void writeIndexFile(String trackName, SortedMap> index) throws IOException; } @@ -307,7 +360,6 @@ public class BufferManager { SampleChunk.SampleChunkCreator sampleChunkCreator) { mStorageManager = storageManager; mSampleChunkCreator = sampleChunkCreator; - clearBuffer(true); } public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) { @@ -318,44 +370,44 @@ public class BufferManager { mEvictListeners.remove(id); } - private void clearBuffer(boolean deleteFiles) { - mChunkMap.clear(); - if (deleteFiles) { - mStorageManager.clearStorage(); - } - mBufferSize = 0; - } - private static String getFileName(String id, long positionUs) { return String.format(Locale.ENGLISH, "%s_%016x.chunk", id, positionUs); } /** - * Creates a new {@link SampleChunk} for caching samples. + * Creates a new {@link SampleChunk} for caching samples if it is needed. * * @param id the name of the track - * @param positionUs starting position of the {@link SampleChunk} in micro seconds. + * @param positionUs current position to write a sample in micro seconds. * @param samplePool {@link SamplePool} for the fast creation of samples. + * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create + * a new {@link SampleChunk}. + * @param currentOffset the current offset to write. * @return returns the created {@link SampleChunk}. * @throws IOException */ - public SampleChunk createNewWriteFile(String id, long positionUs, - SamplePool samplePool) throws IOException { + public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool, + SampleChunk currentChunk, int currentOffset) throws IOException { if (!maybeEvictChunk()) { throw new IOException("Not enough storage space"); } - SortedMap map = mChunkMap.get(id); + SortedMap> map = mChunkMap.get(id); if (map == null) { map = new TreeMap<>(); mChunkMap.put(id, map); mStartPositionMap.put(id, positionUs); mPendingDelete.init(id); } - File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); - SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk(samplePool, file, - positionUs, mChunkCallback); - map.put(positionUs, sampleChunk); - return sampleChunk; + if (currentChunk == null) { + File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); + SampleChunk sampleChunk = mSampleChunkCreator + .createSampleChunk(samplePool, file, positionUs, mChunkCallback); + map.put(positionUs, new Pair(sampleChunk, 0)); + return sampleChunk; + } else { + map.put(positionUs, new Pair(currentChunk, currentOffset)); + return null; + } } /** @@ -366,10 +418,10 @@ public class BufferManager { * @throws IOException */ public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException { - ArrayList keyPositions = mStorageManager.readIndexFile(trackId); - long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0) : 0; + ArrayList keyPositions = mStorageManager.readIndexFile(trackId); + long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0; - SortedMap map = mChunkMap.get(trackId); + SortedMap> map = mChunkMap.get(trackId); if (map == null) { map = new TreeMap<>(); mChunkMap.put(trackId, map); @@ -377,11 +429,15 @@ public class BufferManager { mPendingDelete.init(trackId); } SampleChunk chunk = null; - for (long positionUs: keyPositions) { - chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool, - mStorageManager.getBufferDir(), getFileName(trackId, positionUs), positionUs, - mChunkCallback, chunk); - map.put(positionUs, chunk); + long basePositionUs = -1; + for (PositionHolder position: keyPositions) { + if (position.basePositionUs != basePositionUs) { + chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool, + mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs), + position.positionUs, mChunkCallback, chunk); + basePositionUs = position.basePositionUs; + } + map.put(position.positionUs, new Pair(chunk, position.offset)); } } @@ -392,19 +448,19 @@ public class BufferManager { * @param positionUs the position. * @return returns the found {@link SampleChunk}. */ - public SampleChunk getReadFile(String id, long positionUs) { - SortedMap map = mChunkMap.get(id); + public Pair getReadFile(String id, long positionUs) { + SortedMap> map = mChunkMap.get(id); if (map == null) { return null; } - SampleChunk sampleChunk; - SortedMap headMap = map.headMap(positionUs + 1); + Pair ret; + SortedMap> headMap = map.headMap(positionUs + 1); if (!headMap.isEmpty()) { - sampleChunk = headMap.get(headMap.lastKey()); + ret = headMap.get(headMap.lastKey()); } else { - sampleChunk = map.get(map.firstKey()); + ret = map.get(map.firstKey()); } - return sampleChunk; + return ret; } /** @@ -439,15 +495,16 @@ public class BufferManager { // Since chunks are persistent, we cannot evict chunks. return false; } - SortedMap earliestChunkMap = null; + SortedMap> earliestChunkMap = null; SampleChunk earliestChunk = null; String earliestChunkId = null; - for (Map.Entry> entry : mChunkMap.entrySet()) { - SortedMap map = entry.getValue(); + for (Map.Entry>> entry : + mChunkMap.entrySet()) { + SortedMap> map = entry.getValue(); if (map.isEmpty()) { continue; } - SampleChunk chunk = map.get(map.firstKey()); + SampleChunk chunk = map.get(map.firstKey()).first; if (earliestChunk == null || chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) { earliestChunkMap = map; @@ -473,8 +530,9 @@ public class BufferManager { } pendingDelete = mPendingDelete.getSize(); } - for (Map.Entry> entry : mChunkMap.entrySet()) { - SortedMap map = entry.getValue(); + for (Map.Entry>> entry : + mChunkMap.entrySet()) { + SortedMap> map = entry.getValue(); if (map.isEmpty()) { continue; } @@ -489,70 +547,74 @@ public class BufferManager { * @return returns all track information which is found by {@link BufferManager.StorageManager}. * @throws IOException */ - public ArrayList> readTrackInfoFiles() throws IOException { - ArrayList> trackInfos = new ArrayList<>(); - try { - trackInfos.add(mStorageManager.readTrackInfoFile(false)); - } catch (FileNotFoundException e) { - // There can be a single track only recording. (eg. audio-only, video-only) - // So the exception should not stop the read. + public List readTrackInfoFiles() throws IOException { + List trackFormatList = new ArrayList<>(); + trackFormatList.addAll(mStorageManager.readTrackInfoFiles(false)); + trackFormatList.addAll(mStorageManager.readTrackInfoFiles(true)); + if (trackFormatList.isEmpty()) { + throw new IOException("No track information to load"); } - try { - trackInfos.add(mStorageManager.readTrackInfoFile(true)); - } catch (FileNotFoundException e) { - // See above catch block. - } - return trackInfos; + return trackFormatList; } /** * Writes track information and index information for all tracks. * - * @param audio audio information. - * @param video video information. + * @param audios list of audio track information + * @param videos list of audio track information * @throws IOException */ - public void writeMetaFiles(Pair audio, Pair video) + public void writeMetaFiles(List audios, List videos) throws IOException { - if (audio != null) { - mStorageManager.writeTrackInfoFile(audio.first, audio.second, true); - SortedMap map = mChunkMap.get(audio.first); - if (map == null) { - throw new IOException("Audio track index missing"); + if (audios.isEmpty() && videos.isEmpty()) { + throw new IOException("No track information to save"); + } + if (!audios.isEmpty()) { + mStorageManager.writeTrackInfoFiles(audios, true); + for (TrackFormat trackFormat : audios) { + SortedMap> map = + mChunkMap.get(trackFormat.trackId); + if (map == null) { + throw new IOException("Audio track index missing"); + } + mStorageManager.writeIndexFile(trackFormat.trackId, map); } - mStorageManager.writeIndexFile(audio.first, map); } - if (video != null) { - mStorageManager.writeTrackInfoFile(video.first, video.second, false); - SortedMap map = mChunkMap.get(video.first); - if (map == null) { - throw new IOException("Video track index missing"); + if (!videos.isEmpty()) { + mStorageManager.writeTrackInfoFiles(videos, false); + for (TrackFormat trackFormat : videos) { + SortedMap> map = + mChunkMap.get(trackFormat.trackId); + if (map == null) { + throw new IOException("Video track index missing"); + } + mStorageManager.writeIndexFile(trackFormat.trackId, map); } - mStorageManager.writeIndexFile(video.first, map); } } - /** - * Marks it is closed and it is not used anymore. - */ - public void close() { - // Clean-up may happen after this is called. - mClosed = true; - } - /** * Releases all the resources. */ public void release() { - mPendingDelete.release(); - for (Map.Entry> entry : mChunkMap.entrySet()) { - for (SampleChunk chunk : entry.getValue().values()) { - SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()); + try { + mPendingDelete.release(); + for (Map.Entry>> entry : + mChunkMap.entrySet()) { + SampleChunk toRelease = null; + for (Pair positions : entry.getValue().values()) { + if (toRelease != positions.first) { + toRelease = positions.first; + SampleChunk.IoState.release(toRelease, !mStorageManager.isPersistent()); + } + } } - } - mChunkMap.clear(); - if (mClosed) { - clearBuffer(!mStorageManager.isPersistent()); + mChunkMap.clear(); + } catch (ConcurrentModificationException | NullPointerException e) { + // TODO: remove this after it it confirmed that race condition issues are resolved. + // b/32492258, b/32373376 + SoftPreconditions.checkState(false, "Exception on BufferManager#release: ", + e.toString()); } } @@ -610,20 +672,6 @@ public class BufferManager { return ((float) mTotalWriteSize * 1000 / mTotalWriteTimeNs); } - /** - * Marks {@link BufferManager} object disabled to prevent it from the future use. - */ - public void disable() { - mDisabled = true; - } - - /** - * Returns if {@link BufferManager} object is disabled. - */ - public boolean isDisabled() { - return mDisabled; - } - /** * Returns if {@link BufferManager} has checked the write speed, * which is suitable for Trickplay. diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java index 6a0502a7..6a09016c 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java @@ -17,8 +17,12 @@ package com.android.tv.tuner.exoplayer.buffer; import android.media.MediaFormat; +import android.util.Log; import android.util.Pair; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; +import com.google.protobuf.nano.MessageNano; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -28,18 +32,25 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.SortedMap; /** * Manages DVR storage. */ public class DvrStorageManager implements BufferManager.StorageManager { + private static final String TAG = "DvrStorageManager"; // TODO: make serializable classes and use protobuf after internal data structure is finalized. private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO = "com.google.android.videos.pixelWidthHeightRatio"; + private static final String META_FILE_TYPE_AUDIO = "audio"; + private static final String META_FILE_TYPE_VIDEO = "video"; + private static final String META_FILE_TYPE_CAPTION = "caption"; private static final String META_FILE_SUFFIX = ".meta"; private static final String IDX_FILE_SUFFIX = ".idx"; + private static final String IDX_FILE_SUFFIX_V2 = IDX_FILE_SUFFIX + "2"; // Size of minimum reserved storage buffer which will be used to save meta files // and index files after actual recording finished. @@ -58,18 +69,6 @@ public class DvrStorageManager implements BufferManager.StorageManager { mIsRecording = isRecording; } - @Override - public void clearStorage() { - if (mIsRecording) { - File[] files = mBufferDir.listFiles(); - if (files != null && files.length > 0) { - for (File file : files) { - file.delete(); - } - } - } - } - @Override public File getBufferDir() { return mBufferDir; @@ -132,6 +131,17 @@ public class DvrStorageManager implements BufferManager.StorageManager { } } + private void readFormatStringOptional(DataInputStream in, MediaFormat format, String key) { + try { + String str = readString(in); + if (str != null) { + format.setString(key, str); + } + } catch (IOException e) { + // Since we are reading optional field, ignore the exception. + } + } + private ByteBuffer readByteBuffer(DataInputStream in) throws IOException { int len = in.readInt(); if (len <= 0) { @@ -155,39 +165,104 @@ public class DvrStorageManager implements BufferManager.StorageManager { } @Override - public Pair readTrackInfoFile(boolean isAudio) throws IOException { - File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - String name = readString(in); - MediaFormat format = new MediaFormat(); - readFormatString(in, format, MediaFormat.KEY_MIME); - readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE); - readFormatInt(in, format, MediaFormat.KEY_WIDTH); - readFormatInt(in, format, MediaFormat.KEY_HEIGHT); - readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT); - readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE); - readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); - for (int i = 0; i < 3; ++i) { - readFormatByteBuffer(in, format, "csd-" + i); + public List readTrackInfoFiles(boolean isAudio) { + List trackFormatList = new ArrayList<>(); + int index = 0; + boolean trackNotFound = false; + do { + String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) + + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); + File file = new File(getBufferDir(), fileName); + try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { + String name = readString(in); + MediaFormat format = new MediaFormat(); + readFormatString(in, format, MediaFormat.KEY_MIME); + readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE); + readFormatInt(in, format, MediaFormat.KEY_WIDTH); + readFormatInt(in, format, MediaFormat.KEY_HEIGHT); + readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT); + readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE); + readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); + for (int i = 0; i < 3; ++i) { + readFormatByteBuffer(in, format, "csd-" + i); + } + readFormatLong(in, format, MediaFormat.KEY_DURATION); + + // This is optional since language field is added later. + readFormatStringOptional(in, format, MediaFormat.KEY_LANGUAGE); + trackFormatList.add(new BufferManager.TrackFormat(name, format)); + } catch (IOException e) { + trackNotFound = true; } - readFormatLong(in, format, MediaFormat.KEY_DURATION); - return new Pair<>(name, format); + index++; + } while(!trackNotFound); + return trackFormatList; + } + + /** + * Reads caption information from files. + * + * @return a list of {@link AtscCaptionTrack} objects which store caption information. + */ + public List readCaptionInfoFiles() { + List tracks = new ArrayList<>(); + int index = 0; + boolean trackNotFound = false; + do { + String fileName = META_FILE_TYPE_CAPTION + + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); + File file = new File(getBufferDir(), fileName); + try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { + byte[] data = new byte[(int) file.length()]; + in.read(data); + tracks.add(AtscCaptionTrack.parseFrom(data)); + } catch (IOException e) { + trackNotFound = true; + } + index++; + } while(!trackNotFound); + return tracks; + } + + private ArrayList readOldIndexFile(File indexFile) + throws IOException { + ArrayList indices = new ArrayList<>(); + try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) { + long count = in.readLong(); + for (long i = 0; i < count; ++i) { + long positionUs = in.readLong(); + indices.add(new BufferManager.PositionHolder(positionUs, positionUs, 0)); + } + return indices; } } - @Override - public ArrayList readIndexFile(String trackId) throws IOException { - ArrayList indices = new ArrayList<>(); - File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX); - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { + private ArrayList readNewIndexFile(File indexFile) + throws IOException { + ArrayList indices = new ArrayList<>(); + try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) { long count = in.readLong(); for (long i = 0; i < count; ++i) { - indices.add(in.readLong()); + long positionUs = in.readLong(); + long basePositionUs = in.readLong(); + int offset = in.readInt(); + indices.add(new BufferManager.PositionHolder(positionUs, basePositionUs, offset)); } return indices; } } + @Override + public ArrayList readIndexFile(String trackId) + throws IOException { + File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX_V2); + if (file.exists()) { + return readNewIndexFile(file); + } else { + return readOldIndexFile(new File(getBufferDir(),trackId + IDX_FILE_SUFFIX)); + } + } + private void writeFormatInt(DataOutputStream out, MediaFormat format, String key) throws IOException { if (format.containsKey(key)) { @@ -254,33 +329,63 @@ public class DvrStorageManager implements BufferManager.StorageManager { } @Override - public void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) + public void writeTrackInfoFiles(List formatList, boolean isAudio) throws IOException { - File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); - try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { - writeString(out, trackId); - writeFormatString(out, format, MediaFormat.KEY_MIME); - writeFormatInt(out, format, MediaFormat.KEY_MAX_INPUT_SIZE); - writeFormatInt(out, format, MediaFormat.KEY_WIDTH); - writeFormatInt(out, format, MediaFormat.KEY_HEIGHT); - writeFormatInt(out, format, MediaFormat.KEY_CHANNEL_COUNT); - writeFormatInt(out, format, MediaFormat.KEY_SAMPLE_RATE); - writeFormatFloat(out, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); - for (int i = 0; i < 3; ++i) { - writeFormatByteBuffer(out, format, "csd-" + i); + for (int i = 0; i < formatList.size() ; ++i) { + BufferManager.TrackFormat trackFormat = formatList.get(i); + String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) + + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); + File file = new File(getBufferDir(), fileName); + try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { + writeString(out, trackFormat.trackId); + writeFormatString(out, trackFormat.format, MediaFormat.KEY_MIME); + writeFormatInt(out, trackFormat.format, MediaFormat.KEY_MAX_INPUT_SIZE); + writeFormatInt(out, trackFormat.format, MediaFormat.KEY_WIDTH); + writeFormatInt(out, trackFormat.format, MediaFormat.KEY_HEIGHT); + writeFormatInt(out, trackFormat.format, MediaFormat.KEY_CHANNEL_COUNT); + writeFormatInt(out, trackFormat.format, MediaFormat.KEY_SAMPLE_RATE); + writeFormatFloat(out, trackFormat.format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); + for (int j = 0; j < 3; ++j) { + writeFormatByteBuffer(out, trackFormat.format, "csd-" + j); + } + writeFormatLong(out, trackFormat.format, MediaFormat.KEY_DURATION); + writeFormatString(out, trackFormat.format, MediaFormat.KEY_LANGUAGE); + } + } + } + + /** + * Writes caption information to files. + * + * @param tracks a list of {@link AtscCaptionTrack} objects which store caption information. + */ + public void writeCaptionInfoFiles(List tracks) { + if (tracks == null || tracks.isEmpty()) { + return; + } + for (int i = 0; i < tracks.size(); i++) { + AtscCaptionTrack track = tracks.get(i); + String fileName = META_FILE_TYPE_CAPTION + + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); + File file = new File(getBufferDir(), fileName); + try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { + out.write(MessageNano.toByteArray(track)); + } catch (Exception e) { + Log.e(TAG, "Fail to write caption info to files", e); } - writeFormatLong(out, format, MediaFormat.KEY_DURATION); } } @Override - public void writeIndexFile(String trackName, SortedMap index) + public void writeIndexFile(String trackName, SortedMap> index) throws IOException { - File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX); + File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) { out.writeLong(index.size()); - for (Long key : index.keySet()) { - out.writeLong(key); + for (Map.Entry> entry : index.entrySet()) { + out.writeLong(entry.getKey()); + out.writeLong(entry.getValue().first.getStartPositionUs()); + out.writeInt(entry.getValue().second); } } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java index 4869b49f..af0c3f0d 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java @@ -66,9 +66,14 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, public static final int BUFFER_REASON_RECORDING = 2; /** - * The duration of a chunk of samples, {@link SampleChunk}. + * The minimum duration to support seek in Trickplay. */ - static final long CHUNK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500); + static final long MIN_SEEK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500); + + /** + * The duration of a {@link SampleChunk} for recordings. + */ + static final long RECORDING_CHUNK_DURATION_US = MIN_SEEK_DURATION_US * 1200; // 10 minutes private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds private static final long BUFFER_NEEDED_US = 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS); @@ -79,7 +84,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, private int mTrackCount; private boolean[] mTrackSelected; - private List mIds; private List mReadSampleQueues; private final SamplePool mSamplePool = new SamplePool(); private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; @@ -130,7 +134,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (mTrackCount <= 0) { throw new IOException("No tracks to initialize"); } - mIds = ids; mTrackSelected = new boolean[mTrackCount]; mReadSampleQueues = new ArrayList<>(); mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason, @@ -139,6 +142,9 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); } mSampleChunkIoHelper.init(); + for (int i = 0; i < mTrackCount; ++i) { + mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this); + } } @Override @@ -146,8 +152,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (!mTrackSelected[index]) { mTrackSelected[index] = true; mReadSampleQueues.get(index).clear(); - mBufferManager.registerChunkEvictedListener(mIds.get(index), - RecordingSampleBuffer.this); mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs); } } @@ -157,7 +161,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (mTrackSelected[index]) { mTrackSelected[index] = false; mReadSampleQueues.get(index).clear(); - mBufferManager.unregisterChunkEvictedListener(mIds.get(index)); + mSampleChunkIoHelper.closeRead(index); } } @@ -193,7 +197,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, } // Disables buffering samples afterwards, and notifies the disk speed is slow. Log.w(TAG, "Disk is too slow for trickplay"); - mBufferManager.disable(); mBufferListener.onDiskTooSlow(); } @@ -205,7 +208,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, private boolean maybeReadSample(SampleQueue queue, int index) { if (queue.getLastQueuedPositionUs() != null && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US - && queue.isDurationGreaterThan(CHUNK_DURATION_US)) { + && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) { // The speed of queuing samples can be higher than the playback speed. // If the duration of the samples in the queue is not limited, // samples can be accumulated and there can be out-of-memory issues. @@ -300,7 +303,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, public void onChunkEvicted(String id, long createdTimeMs) { if (mBufferListener != null) { mBufferListener.onBufferStartTimeChanged( - createdTimeMs + TimeUnit.MICROSECONDS.toMillis(CHUNK_DURATION_US)); + createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US)); } } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java index 552caaef..04b5a071 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java @@ -78,7 +78,6 @@ public class SampleChunk { /** * A class for SampleChunk creation. */ - @VisibleForTesting public static class SampleChunkCreator { /** @@ -151,18 +150,23 @@ public class SampleChunk { mCurrentOffset = 0; } + private void reset(SampleChunk chunk, long offset) { + mChunk = chunk; + mCurrentOffset = offset; + } + /** * Prepares for read I/O operation from a new SampleChunk. * * @param chunk the new SampleChunk to read from * @throws IOException */ - void openRead(SampleChunk chunk) throws IOException { + void openRead(SampleChunk chunk, long offset) throws IOException { if (mChunk != null) { mChunk.closeRead(); } chunk.openRead(); - reset(chunk); + reset(chunk, offset); } /** @@ -240,6 +244,20 @@ public class SampleChunk { } } + /** + * Returns the current SampleChunk for subsequent I/O operation. + */ + SampleChunk getChunk() { + return mChunk; + } + + /** + * Returns the current offset of the current SampleChunk for subsequent I/O operation. + */ + long getOffset() { + return mCurrentOffset; + } + /** * Releases SampleChunk. the SampleChunk will not be used anymore. * diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java index 37ae4022..ca97a91a 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java @@ -21,6 +21,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -31,7 +32,9 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason; import java.io.IOException; +import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; /** @@ -46,11 +49,13 @@ public class SampleChunkIoHelper implements Handler.Callback { private static final int MSG_OPEN_READ = 1; private static final int MSG_OPEN_WRITE = 2; - private static final int MSG_CLOSE_WRITE = 3; - private static final int MSG_READ = 4; - private static final int MSG_WRITE = 5; - private static final int MSG_RELEASE = 6; + private static final int MSG_CLOSE_READ = 3; + private static final int MSG_CLOSE_WRITE = 4; + private static final int MSG_READ = 5; + private static final int MSG_WRITE = 6; + private static final int MSG_RELEASE = 7; + private final long mSampleChunkDurationUs; private final int mTrackCount; private final List mIds; private final List mMediaFormats; @@ -62,9 +67,11 @@ public class SampleChunkIoHelper implements Handler.Callback { private Handler mIoHandler; private final ConcurrentLinkedQueue mReadSampleBuffers[]; private final ConcurrentLinkedQueue mHandlerReadSampleBuffers[]; - private final long[] mWriteEndPositionUs; + private final long[] mWriteIndexEndPositionUs; + private final long[] mWriteChunkEndPositionUs; private final SampleChunk.IoState[] mReadIoStates; private final SampleChunk.IoState[] mWriteIoStates; + private final Set mSelectedTracks = new ArraySet<>(); private long mBufferDurationUs = 0; private boolean mWriteEnded; private boolean mErrorNotified; @@ -129,11 +136,20 @@ public class SampleChunkIoHelper implements Handler.Callback { mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; - mWriteEndPositionUs = new long[mTrackCount]; + mWriteIndexEndPositionUs = new long[mTrackCount]; + mWriteChunkEndPositionUs = new long[mTrackCount]; mReadIoStates = new SampleChunk.IoState[mTrackCount]; mWriteIoStates = new SampleChunk.IoState[mTrackCount]; + + // Small chunk duration for live playback will give more fine grained storage usage + // and eviction handling for trickplay. + mSampleChunkDurationUs = + bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK ? + RecordingSampleBuffer.MIN_SEEK_DURATION_US : + RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US; for (int i = 0; i < mTrackCount; ++i) { - mWriteEndPositionUs[i] = RecordingSampleBuffer.CHUNK_DURATION_US; + mWriteIndexEndPositionUs[i] = RecordingSampleBuffer.MIN_SEEK_DURATION_US; + mWriteChunkEndPositionUs[i] = mSampleChunkDurationUs; mReadIoStates[i] = new SampleChunk.IoState(); mWriteIoStates[i] = new SampleChunk.IoState(); } @@ -203,6 +219,15 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_READ, params)); } + /** + * Closes read from the specified track. + * + * @param index track index + */ + public void closeRead(int index) { + mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_CLOSE_READ, index)); + } + /** * Notifies writes are finished. */ @@ -229,21 +254,19 @@ public class SampleChunkIoHelper implements Handler.Callback { try { if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) { // Saves meta information for recording. - Pair audio = null, video = null; + List audios = new LinkedList<>(); + List videos = new LinkedList<>(); for (int i = 0; i < mTrackCount; ++i) { android.media.MediaFormat format = mMediaFormats.get(i).getFrameworkMediaFormatV16(); format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs); - if (audio == null && MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { - audio = new Pair<>(mIds.get(i), format); - } else if (video == null && MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { - video = new Pair<>(mIds.get(i), format); - } - if (audio != null && video != null) { - break; + if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { + audios.add(new BufferManager.TrackFormat(mIds.get(i), format)); + } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { + videos.add(new BufferManager.TrackFormat(mIds.get(i), format)); } } - mBufferManager.writeMetaFiles(audio, video); + mBufferManager.writeMetaFiles(audios, videos); } } finally { mBufferManager.release(); @@ -265,6 +288,9 @@ public class SampleChunkIoHelper implements Handler.Callback { case MSG_OPEN_WRITE: doOpenWrite((int) message.obj); return true; + case MSG_CLOSE_READ: + doCloseRead((int) message.obj); + return true; case MSG_CLOSE_WRITE: doCloseWrite(); return true; @@ -291,14 +317,16 @@ public class SampleChunkIoHelper implements Handler.Callback { private void doOpenRead(IoParams params) throws IOException { int index = params.index; mIoHandler.removeMessages(MSG_READ, index); - SampleChunk chunk = mBufferManager.getReadFile(mIds.get(index), params.positionUs); - if (chunk == null) { + Pair readPosition = + mBufferManager.getReadFile(mIds.get(index), params.positionUs); + if (readPosition == null) { String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs + "is not found"; - SoftPreconditions.checkNotNull(chunk, TAG, errorMessage); + SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage); throw new IOException(errorMessage); } - mReadIoStates[index].openRead(chunk); + mSelectedTracks.add(index); + mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second); if (mHandlerReadSampleBuffers[index] != null) { SampleHolder sample; while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { @@ -310,10 +338,22 @@ public class SampleChunkIoHelper implements Handler.Callback { } private void doOpenWrite(int index) throws IOException { - SampleChunk chunk = mBufferManager.createNewWriteFile(mIds.get(index), 0, mSamplePool); + SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0, + mSamplePool, null, 0); mWriteIoStates[index].openWrite(chunk); } + private void doCloseRead(int index) { + mSelectedTracks.remove(index); + if (mHandlerReadSampleBuffers[index] != null) { + SampleHolder sample; + while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { + mSamplePool.releaseSample(sample); + } + } + mIoHandler.removeMessages(MSG_READ, index); + } + private void doRead(int index) throws IOException { mIoHandler.removeMessages(MSG_READ, index); if (mHandlerReadSampleBuffers[index].size() >= MAX_READ_BUFFER_SAMPLES) { @@ -357,13 +397,21 @@ public class SampleChunkIoHelper implements Handler.Callback { if (sample.timeUs > mBufferDurationUs) { mBufferDurationUs = sample.timeUs; } - - if (sample.timeUs >= mWriteEndPositionUs[index]) { - nextChunk = mBufferManager.createNewWriteFile(mIds.get(index), - mWriteEndPositionUs[index], mSamplePool); - mWriteEndPositionUs[index] = - ((sample.timeUs / RecordingSampleBuffer.CHUNK_DURATION_US) + 1) * - RecordingSampleBuffer.CHUNK_DURATION_US; + if (sample.timeUs >= mWriteIndexEndPositionUs[index]) { + SampleChunk currentChunk = sample.timeUs >= mWriteChunkEndPositionUs[index] ? + null : mWriteIoStates[params.index].getChunk(); + int currentOffset = (int) mWriteIoStates[params.index].getOffset(); + nextChunk = mBufferManager.createNewWriteFileIfNeeded( + mIds.get(index), mWriteIndexEndPositionUs[index], mSamplePool, + currentChunk, currentOffset); + mWriteIndexEndPositionUs[index] = + ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) * + RecordingSampleBuffer.MIN_SEEK_DURATION_US; + if (nextChunk != null) { + mWriteChunkEndPositionUs[index] = + ((sample.timeUs / mSampleChunkDurationUs) + 1) + * mSampleChunkDurationUs; + } } } mWriteIoStates[params.index].write(params.sample, nextChunk); @@ -391,15 +439,22 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoHandler.removeCallbacksAndMessages(null); mFinished = true; conditionVariable.open(); + mSelectedTracks.clear(); } private void releaseEvictedChunks() { - if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK) { + if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK + || mSelectedTracks.isEmpty()) { return; } + long currentStartPositionUs = Long.MAX_VALUE; + for (int trackIndex : mSelectedTracks) { + currentStartPositionUs = Math.min(currentStartPositionUs, + mReadIoStates[trackIndex].getStartPositionUs()); + } for (int i = 0; i < mTrackCount; ++i) { long evictEndPositionUs = Math.min(mBufferManager.getStartPositionUs(mIds.get(i)), - mReadIoStates[i].getStartPositionUs()); + currentStartPositionUs); mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs); } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java index 7b098f40..75eac5a2 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java @@ -43,6 +43,7 @@ public class SampleQueue { if (sampleFromQueue == null) { return SampleSource.NOTHING_READ; } + sample.ensureSpaceForWrite(sampleFromQueue.size); sample.size = sampleFromQueue.size; sample.flags = sampleFromQueue.flags; sample.timeUs = sampleFromQueue.timeUs; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java index 40c4ef95..159fde18 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java @@ -19,18 +19,18 @@ package com.android.tv.tuner.exoplayer.buffer; import android.os.ConditionVariable; import android.support.annotation.NonNull; + import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; +import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.android.tv.tuner.exoplayer.SampleExtractor; import java.io.IOException; import java.util.List; -import junit.framework.Assert; - /** * Handles I/O for {@link SampleExtractor} when * physical storage based buffer is not used. Trickplay is disabled. @@ -115,8 +115,8 @@ public class SimpleSampleBuffer implements BufferManager.SampleBuffer { @Override public synchronized int readSample(int track, SampleHolder sampleHolder) { SampleQueue queue = mPlayingSampleQueues[track]; - Assert.assertNotNull(queue); - int result = queue.dequeueSample(sampleHolder); + SoftPreconditions.checkNotNull(queue); + int result = queue == null ? SampleSource.NOTHING_READ : queue.dequeueSample(sampleHolder); if (result != SampleSource.SAMPLE_READ && reachedEos()) { return SampleSource.END_OF_STREAM; } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java index 258a5cd0..9fe921b8 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java @@ -17,20 +17,23 @@ package com.android.tv.tuner.exoplayer.buffer; import android.content.Context; -import android.media.MediaFormat; import android.os.AsyncTask; -import android.os.Looper; import android.provider.Settings; +import android.support.annotation.NonNull; import android.util.Pair; +import com.android.tv.common.SoftPreconditions; + import java.io.File; import java.util.ArrayList; +import java.util.List; import java.util.SortedMap; /** * Manages Trickplay storage. */ public class TrickplayStorageManager implements BufferManager.StorageManager { + // TODO: Support multi-sessions. private static final String BUFFER_DIR = "timeshift"; // Copied from android.provider.Settings.Global (hidden fields) @@ -43,53 +46,68 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500L * 1024 * 1024; - private final File mBufferDir; + private static AsyncTask sLastCacheCleanUpTask; + private static File sBufferDir; + private static long sStorageBufferBytes; + private final long mMaxBufferSize; - private final long mStorageBufferBytes; - private static long getStorageBufferBytes(Context context, File path) { + private static void initParamsIfNeeded(Context context, @NonNull File path) { + // TODO: Support multi-sessions. + SoftPreconditions.checkState( + sBufferDir == null || sBufferDir.equals(path)); + if (path.equals(sBufferDir)) { + return; + } + sBufferDir = path; long lowPercentage = Settings.Global.getInt(context.getContentResolver(), SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); - long lowBytes = path.getTotalSpace() * lowPercentage / 100; + long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100; long maxLowBytes = Settings.Global.getLong(context.getContentResolver(), SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES); - return Math.min(lowBytes, maxLowBytes); + sStorageBufferBytes = Math.min(lowPercentageToBytes, maxLowBytes); } - public TrickplayStorageManager(Context context, File baseDir, long maxBufferSize) { - mBufferDir = new File(baseDir, BUFFER_DIR); - mBufferDir.mkdirs(); + public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) { + initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR)); + sBufferDir.mkdirs(); mMaxBufferSize = maxBufferSize; clearStorage(); - mStorageBufferBytes = getStorageBufferBytes(context, mBufferDir); } - @Override - public void clearStorage() { - File files[] = mBufferDir.listFiles(); - if (files == null || files.length == 0) { - return; + private void clearStorage() { + long now = System.currentTimeMillis(); + if (sLastCacheCleanUpTask != null) { + sLastCacheCleanUpTask.cancel(true); } - if (Looper.myLooper() == Looper.getMainLooper()) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - for (File file : files) { + sLastCacheCleanUpTask = new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + if (isCancelled()) { + return null; + } + File files[] = sBufferDir.listFiles(); + if (files == null || files.length == 0) { + return null; + } + for (File file : files) { + if (isCancelled()) { + break; + } + long lastModified = file.lastModified(); + if (lastModified != 0 && lastModified < now) { file.delete(); } - return null; } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - for (File file : files) { - file.delete(); + return null; } - } + }; + sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @Override public File getBufferDir() { - return mBufferDir; + return sBufferDir; } @Override @@ -104,25 +122,26 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { @Override public boolean hasEnoughBuffer(long pendingDelete) { - return mBufferDir.getUsableSpace() + pendingDelete >= mStorageBufferBytes; + return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes; } @Override - public Pair readTrackInfoFile(boolean isAudio) { + public List readTrackInfoFiles(boolean isAudio) { return null; } @Override - public ArrayList readIndexFile(String trackId) { + public ArrayList readIndexFile(String trackId) { return null; } @Override - public void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) { + public void writeTrackInfoFiles(List formatList, boolean isAudio) { } @Override - public void writeIndexFile(String trackName, SortedMap index) { + public void writeIndexFile(String trackName, + SortedMap> index) { } } diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java new file mode 100644 index 00000000..356636cc --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.ffmpeg; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; + +import android.support.annotation.MainThread; +import android.support.annotation.WorkerThread; +import android.support.annotation.VisibleForTesting; +import com.google.android.exoplayer.SampleHolder; +import com.android.tv.Features; +import com.android.tv.tuner.exoplayer.audio.AudioDecoder; + +import java.nio.ByteBuffer; + +/** + * The class connects {@link FfmpegDecoderService} to decode audio samples. + * In order to sandbox ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process + * without any permission and connected by binder. + */ +public class FfmpegDecoderClient extends AudioDecoder { + private static FfmpegDecoderClient sInstance; + + private IFfmpegDecoder mService; + private Boolean mIsAvailable; + + private static final String FFMPEG_DECODER_SERVICE_FILTER = + "com.android.tv.tuner.exoplayer.ffmpeg.IFfmpegDecoder"; + private static final long FFMPEG_SERVICE_CONNECT_TIMEOUT_MS = 500; + + private final ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mService = IFfmpegDecoder.Stub.asInterface(service); + synchronized (FfmpegDecoderClient.this) { + try { + mIsAvailable = mService.isAvailable(); + } catch (RemoteException e) { + } + FfmpegDecoderClient.this.notify(); + } + } + + @Override + public void onServiceDisconnected(ComponentName className) { + synchronized (FfmpegDecoderClient.this) { + sInstance.releaseLocked(); + mIsAvailable = false; + mService = null; + } + } + }; + + /** + * Connects to the decoder service for future uses. + * @param context + * @return {@code true} when decoder service is connected. + */ + @MainThread + public synchronized static boolean connect(Context context) { + if (Features.AC3_SOFTWARE_DECODE.isEnabled(context)) { + if (sInstance == null) { + sInstance = new FfmpegDecoderClient(); + Intent intent = + new Intent(FFMPEG_DECODER_SERVICE_FILTER) + .setComponent( + new ComponentName(context, FfmpegDecoderService.class)); + if (context.bindService(intent, sInstance.mConnection, Context.BIND_AUTO_CREATE)) { + return true; + } else { + sInstance = null; + } + } + } + return false; + } + + /** + * Disconnects from the decoder service and release resources. + * @param context + */ + @MainThread + public synchronized static void disconnect(Context context) { + if (sInstance != null) { + synchronized (sInstance) { + sInstance.releaseLocked(); + if (sInstance.mIsAvailable != null && sInstance.mIsAvailable) { + context.unbindService(sInstance.mConnection); + } + sInstance.mIsAvailable = false; + sInstance.mService = null; + } + sInstance = null; + } + } + + /** + * Returns whether service is available or not. + * Before using client, this should be used to check availability. + */ + @WorkerThread + public synchronized static boolean isAvailable() { + if (sInstance != null) { + return sInstance.available(); + } + return false; + } + + /** + * Returns an client instance. + */ + public synchronized static FfmpegDecoderClient getInstance() { + if (sInstance != null) { + sInstance.createDecoder(); + } + return sInstance; + } + + private FfmpegDecoderClient() { + } + + private synchronized boolean available() { + if (mIsAvailable == null) { + try { + this.wait(FFMPEG_SERVICE_CONNECT_TIMEOUT_MS); + } catch (InterruptedException e) { + } + } + return mIsAvailable != null && mIsAvailable == true; + } + + private synchronized void createDecoder() { + if (mIsAvailable == null || mIsAvailable == false) { + return; + } + try { + mService.create(); + } catch (RemoteException e) { + } + } + + private void releaseLocked() { + if (mIsAvailable == null || mIsAvailable == false) { + return; + } + try { + mService.release(); + } catch (RemoteException e) { + } + } + + @Override + public synchronized void release() { + releaseLocked(); + } + + @Override + public synchronized void decode(SampleHolder sampleHolder) { + if (mIsAvailable == null || mIsAvailable == false) { + return; + } + byte[] sampleBytes = new byte [sampleHolder.data.limit()]; + sampleHolder.data.get(sampleBytes, 0, sampleBytes.length); + try { + mService.decode(sampleHolder.timeUs, sampleBytes); + } catch (RemoteException e) { + } + } + + @Override + public synchronized void resetDecoderState(String mimeType) { + if (mIsAvailable == null || mIsAvailable == false) { + return; + } + try { + mService.resetDecoderState(mimeType); + } catch (RemoteException e) { + } + } + + @Override + public synchronized ByteBuffer getDecodedSample() { + if (mIsAvailable == null || mIsAvailable == false) { + return null; + } + try { + byte[] outputBytes = mService.getDecodedSample(); + if (outputBytes != null && outputBytes.length > 0) { + return ByteBuffer.wrap(outputBytes); + } + } catch (RemoteException e) { + } + return null; + } + + @Override + public synchronized long getDecodedTimeUs() { + if (mIsAvailable == null || mIsAvailable == false) { + return 0; + } + try { + return mService.getDecodedTimeUs(); + } catch (RemoteException e) { + } + return 0; + } + + @VisibleForTesting + public boolean testSandboxIsolatedProcess() { + // When testing isolated process, we will check the permission in FfmpegDecoderService. + // If the service have any permission, an exception will be thrown. + try { + mService.testSandboxIsolatedProcess(); + } catch (RemoteException e) { + return false; + } + return true; + } + + @VisibleForTesting + public void testSandboxMinijail() { + // When testing minijail, we will call a system call which is blocked by minijail. In that + // case, the FfmpegDecoderService will be disconnected, we can check the connection status + // to make sure if the minijail works or not. + try { + mService.testSandboxMinijail(); + } catch (RemoteException e) { + } + } +} diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java new file mode 100644 index 00000000..3ebdd381 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.ffmpeg; + +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioDecoder; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Ffmpeg based audio decoder service. + * It should be isolatedProcess due to security reason. + */ +public class FfmpegDecoderService extends Service { + private static final String TAG = "FfmpegDecoderService"; + private static final boolean DEBUG = false; + + private static final String POLICY_FILE = "whitelist.policy"; + + private static final long MINIJAIL_SETUP_WAIT_TIMEOUT_MS = 5000; + + private static boolean sLibraryLoaded = true; + + static { + try { + System.loadLibrary("minijail_jni"); + } catch (Exception | Error e) { + Log.e(TAG, "Load minijail failed:", e); + sLibraryLoaded = false; + } + } + + private FfmpegDecoder mBinder = new FfmpegDecoder(); + private volatile Object mMinijailSetupMonitor = new Object(); + //@GuardedBy("mMinijailSetupMonitor") + private volatile Boolean mMinijailSetup; + + @Override + public void onCreate() { + if (sLibraryLoaded) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + synchronized (mMinijailSetupMonitor) { + int pipeFd = getPolicyPipeFd(); + if (pipeFd <= 0) { + Log.e(TAG, "fail to open policy file"); + mMinijailSetup = false; + } else { + nativeSetupMinijail(pipeFd); + mMinijailSetup = true; + if (DEBUG) Log.d(TAG, "Minijail setup successfully"); + } + mMinijailSetupMonitor.notify(); + } + return null; + } + }.execute(); + } else { + synchronized (mMinijailSetupMonitor) { + mMinijailSetup = false; + mMinijailSetupMonitor.notify(); + } + } + super.onCreate(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private int getPolicyPipeFd() { + try { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + final ParcelFileDescriptor.AutoCloseOutputStream outputStream = + new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]); + final AssetFileDescriptor policyFile = getAssets().openFd("whitelist.policy"); + final byte[] buffer = new byte[2048]; + final FileInputStream policyStream = policyFile.createInputStream(); + while (true) { + int bytesRead = policyStream.read(buffer); + if (bytesRead == -1) break; + outputStream.write(buffer, 0, bytesRead); + } + policyStream.close(); + outputStream.close(); + return pipe[0].detachFd(); + } catch (IOException e) { + Log.e(TAG, "Policy file not found:" + e); + } + return -1; + } + + private final class FfmpegDecoder extends IFfmpegDecoder.Stub { + FfmpegAudioDecoder mDecoder; + @Override + public boolean isAvailable() { + return isMinijailSetupDone() && FfmpegAudioDecoder.isAvailable(); + } + + @Override + public void create() { + mDecoder = new FfmpegAudioDecoder(FfmpegDecoderService.this); + } + + @Override + public void release() { + if (mDecoder != null) { + mDecoder.release(); + mDecoder = null; + } + } + + @Override + public void decode(long timeUs, byte[] sample) { + if (!isMinijailSetupDone()) { + // If minijail is not setup, we don't run decode for better security. + return; + } + mDecoder.decode(timeUs, sample); + } + + @Override + public void resetDecoderState(String mimetype) { + mDecoder.resetDecoderState(mimetype); + } + + @Override + public byte[] getDecodedSample() { + ByteBuffer decodedBuffer = mDecoder.getDecodedSample(); + byte[] ret = new byte[decodedBuffer.limit()]; + decodedBuffer.get(ret, 0, ret.length); + return ret; + } + + @Override + public long getDecodedTimeUs() { + return mDecoder.getDecodedTimeUs(); + } + + private boolean isMinijailSetupDone() { + synchronized (mMinijailSetupMonitor) { + if (DEBUG) Log.d(TAG, "mMinijailSetup in isAvailable(): " + mMinijailSetup); + if (mMinijailSetup == null) { + try { + if (DEBUG) Log.d(TAG, "Wait till Minijail setup is done"); + mMinijailSetupMonitor.wait(MINIJAIL_SETUP_WAIT_TIMEOUT_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + return mMinijailSetup != null && mMinijailSetup; + } + } + + @Override + public void testSandboxIsolatedProcess() { + if (!isMinijailSetupDone()) { + // If minijail is not setup, we return directly to make the test fail. + return; + } + if (FfmpegDecoderService.this.checkSelfPermission("android.permission.INTERNET") + == PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Shouldn't have the permission of internet"); + } + } + + @Override + public void testSandboxMinijail() { + if (!isMinijailSetupDone()) { + // If minijail is not setup, we return directly to make the test fail. + return; + } + nativeTestMinijail(); + } + } + + private native void nativeSetupMinijail(int policyFd); + private native void nativeTestMinijail(); +} diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl b/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl new file mode 100644 index 00000000..ed053790 --- /dev/null +++ b/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.exoplayer.ffmpeg; + +interface IFfmpegDecoder { + boolean isAvailable(); + void create(); + void release(); + void resetDecoderState(String mimetype); + void decode(long timeUs, in byte[] sample); + byte[] getDecodedSample(); + long getDecodedTimeUs(); + void testSandboxIsolatedProcess(); + void testSandboxMinijail(); +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java index 97d9ece3..e0e21a20 100644 --- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java +++ b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; +import com.android.tv.common.BuildConfig; import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.tuner.R; @@ -35,6 +36,24 @@ public class ConnectionTypeFragment extends SetupMultiPaneFragment { public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ConnectionTypeFragment"; + @Override + public void onCreate(Bundle savedInstanceState) { + ((TunerSetupActivity) getActivity()).generateTunerHal(); + super.onCreate(savedInstanceState); + } + + @Override + public void onResume() { + ((TunerSetupActivity) getActivity()).generateTunerHal(); + super.onResume(); + } + + @Override + public void onDestroy() { + ((TunerSetupActivity) getActivity()).clearTunerHal(); + super.onDestroy(); + } + @Override protected SetupGuidedStepFragment onCreateContentFragment() { return new ContentFragment(); diff --git a/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/src/com/android/tv/tuner/setup/PostalCodeFragment.java new file mode 100644 index 00000000..025b9193 --- /dev/null +++ b/src/com/android/tv/tuner/setup/PostalCodeFragment.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.setup; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v17.leanback.widget.GuidanceStylist.Guidance; +import android.support.v17.leanback.widget.GuidedAction; +import android.support.v17.leanback.widget.GuidedActionsStylist; +import android.text.InputFilter; +import android.text.InputFilter.AllCaps; +import android.view.View; +import android.widget.TextView; +import com.android.tv.R; +import com.android.tv.common.ui.setup.SetupGuidedStepFragment; +import com.android.tv.common.ui.setup.SetupMultiPaneFragment; +import com.android.tv.tuner.util.PostalCodeUtils; +import com.android.tv.util.LocationUtils; +import java.util.List; + +/** + * A fragment for initial screen. + */ +public class PostalCodeFragment extends SetupMultiPaneFragment { + public static final String ACTION_CATEGORY = + "com.android.tv.tuner.setup.PostalCodeFragment"; + private static final int VIEW_TYPE_EDITABLE = 1; + + @Override + protected SetupGuidedStepFragment onCreateContentFragment() { + ContentFragment fragment = new ContentFragment(); + Bundle arguments = new Bundle(); + arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true); + fragment.setArguments(arguments); + return fragment; + } + + @Override + protected String getActionCategory() { + return ACTION_CATEGORY; + } + + @Override + protected boolean needsDoneButton() { + return true; + } + + @Override + protected boolean needsSkipButton() { + return true; + } + + @Override + protected void setOnClickAction(View view, final String category, final int actionId) { + if (actionId == ACTION_DONE) { + view.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + CharSequence postalCode = + ((ContentFragment) getContentFragment()).mEditAction.getTitle(); + String region = LocationUtils.getCurrentCountry(getContext()); + if (postalCode != null && PostalCodeUtils.matches(postalCode, region)) { + PostalCodeUtils.setLastPostalCode( + getContext(), postalCode.toString()); + onActionClick(category, actionId); + } else { + ContentFragment contentFragment = + (ContentFragment) getContentFragment(); + contentFragment.mEditAction.setDescription( + getString(R.string.postal_code_invalid_warning)); + contentFragment.notifyActionChanged(0); + contentFragment.mEditedActionView.performClick(); + } + } + }); + } else if (actionId == ACTION_SKIP) { + super.setOnClickAction(view, category, ACTION_SKIP); + } + } + + public static class ContentFragment extends SetupGuidedStepFragment { + private GuidedAction mEditAction; + private View mEditedActionView; + private View mDoneActionView; + private boolean mProceed; + + @Override + public void onGuidedActionFocused(GuidedAction action) { + if (action.equals(mEditAction)) { + if (mProceed) { + // "NEXT" in IME was just clicked, moves focus to Done button. + if (mDoneActionView == null) { + mDoneActionView = getActivity().findViewById(R.id.button_done); + } + mDoneActionView.requestFocus(); + mProceed = false; + } else { + // Directly opens IME to input postal/zip code. + if (mEditedActionView == null) { + int maxLength = PostalCodeUtils.getRegionMaxLength(getContext()); + mEditedActionView = getView().findViewById(R.id.guidedactions_editable); + ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title)) + .setFilters( + new InputFilter[] { + new InputFilter.LengthFilter(maxLength), new AllCaps() + }); + } + mEditedActionView.performClick(); + } + } + } + + @Override + public long onGuidedActionEditedAndProceed(GuidedAction action) { + mProceed = true; + return 0; + } + + @NonNull + @Override + public Guidance onCreateGuidance(Bundle savedInstanceState) { + String title = getString(R.string.postal_code_guidance_title); + String description = getString(R.string.postal_code_guidance_description); + String breadcrumb = getString(R.string.ut_setup_breadcrumb); + return new Guidance(title, description, breadcrumb, null); + } + + @Override + public void onCreateActions(@NonNull List actions, + Bundle savedInstanceState) { + String description = getString(R.string.postal_code_action_description); + mEditAction = new GuidedAction.Builder(getActivity()).id(0).editable(true) + .description(description).build(); + actions.add(mEditAction); + } + + @Override + protected String getActionCategory() { + return ACTION_CATEGORY; + } + + @Override + public GuidedActionsStylist onCreateActionsStylist() { + return new GuidedActionsStylist() { + @Override + public int getItemViewType(GuidedAction action) { + if (action.isEditable()) { + return VIEW_TYPE_EDITABLE; + } + return super.getItemViewType(action); + } + + @Override + public int onProvideItemLayoutId(int viewType) { + if (viewType == VIEW_TYPE_EDITABLE) { + return R.layout.guided_action_editable; + } + return super.onProvideItemLayoutId(viewType); + } + }; + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/setup/ScanFragment.java b/src/com/android/tv/tuner/setup/ScanFragment.java index 3b61debb..b6936e38 100644 --- a/src/com/android/tv/tuner/setup/ScanFragment.java +++ b/src/com/android/tv/tuner/setup/ScanFragment.java @@ -21,6 +21,7 @@ import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; @@ -35,25 +36,21 @@ import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.tv.common.AutoCloseableUtils; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.tuner.ChannelScanFileParser; -import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.R; +import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.PsipData; import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.source.FileTsStreamer; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsStreamer; import com.android.tv.tuner.source.TunerTsStreamer; import com.android.tv.tuner.tvinput.ChannelDataManager; import com.android.tv.tuner.tvinput.EventDetector; -import com.android.tv.tuner.util.TunerInputInfoUtils; - -import junit.framework.Assert; import java.util.ArrayList; import java.util.List; @@ -67,6 +64,7 @@ import java.util.concurrent.TimeUnit; public class ScanFragment extends SetupFragment { private static final String TAG = "ScanFragment"; private static final boolean DEBUG = false; + // In the fake mode, the connection to antenna or cable is not necessary. // Instead dummy channels are added. private static final boolean FAKE_MODE = false; @@ -98,6 +96,7 @@ public class ScanFragment extends SetupFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreateView"); View view = super.onCreateView(inflater, container, savedInstanceState); mChannelDataManager = new ChannelDataManager(getActivity()); mChannelDataManager.checkDataVersion(getActivity()); @@ -120,13 +119,19 @@ public class ScanFragment extends SetupFragment { } }); Bundle args = getArguments(); + int tunerType = (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0)); // TODO: Handle the case when the fragment is restored. startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0)); TextView scanTitleView = (TextView) view.findViewById(R.id.tune_title); - if (TunerInputInfoUtils.isBuiltInTuner(getActivity())){ - scanTitleView.setText(R.string.bt_channel_scan); - } else { - scanTitleView.setText(R.string.ut_channel_scan); + switch (tunerType) { + case TunerHal.TUNER_TYPE_USB: + scanTitleView.setText(R.string.ut_channel_scan); + break; + case TunerHal.TUNER_TYPE_NETWORK: + scanTitleView.setText(R.string.nt_channel_scan); + break; + default: + scanTitleView.setText(R.string.bt_channel_scan); } return view; } @@ -147,12 +152,14 @@ public class ScanFragment extends SetupFragment { } @Override - public void onDetach() { + public void onPause() { + Log.d(TAG, "onPause"); if (mChannelScanTask != null) { // Ensure scan task will stop. + Log.w(TAG, "The activity went to the background. Stopping channel scan."); mChannelScanTask.stopScan(); } - super.onDetach(); + super.onPause(); } /** @@ -168,7 +175,9 @@ public class ScanFragment extends SetupFragment { new Handler().postDelayed(new Runnable() { @Override public void run() { - mChannelScanTask.showFinishingProgressDialog(); + if (mChannelScanTask != null) { + mChannelScanTask.showFinishingProgressDialog(); + } } }, SHOW_PROGRESS_DIALOG_DELAY_MS); @@ -255,13 +264,13 @@ public class ScanFragment extends SetupFragment { if (FAKE_MODE) { mScanTsStreamer = new FakeTsStreamer(this); } else { - TunerHal hal = TunerHal.createInstance(mActivity.getApplicationContext()); + TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal(); if (hal == null) { throw new RuntimeException("Failed to open a DVB device"); } mScanTsStreamer = new TunerTsStreamer(hal, this); } - mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this) : null; + mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this, mActivity) : null; mConditionStopped = new ConditionVariable(); mChannelDataManager.setChannelScanListener(this, new Handler()); } @@ -316,10 +325,17 @@ public class ScanFragment extends SetupFragment { @Override protected void onProgressUpdate(Integer... values) { - mProgressBar.setProgress(values[0]); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mProgressBar.setProgress(values[0], true); + } else { + mProgressBar.setProgress(values[0]); + } } private void stopScan() { + if (mLatch != null) { + mLatch.countDown(); + } mConditionStopped.open(); } @@ -340,8 +356,8 @@ public class ScanFragment extends SetupFragment { Log.i(TAG, "Tuning to " + frequency + " " + modulation); TsStreamer streamer = getStreamer(scanChannel.type); - Assert.assertNotNull(streamer); - if (streamer.startStream(scanChannel)) { + SoftPreconditions.checkNotNull(streamer); + if (streamer != null && streamer.startStream(scanChannel)) { mLatch = new CountDownLatch(1); try { mLatch.await(CHANNEL_SCAN_PERIOD_MS, TimeUnit.MILLISECONDS); @@ -360,11 +376,7 @@ public class ScanFragment extends SetupFragment { if (mConditionStopped.block(-1)) { break; } - onProgressUpdate(MAX_PROGRESS * i++ / mScanChannelList.size()); - } - if (mScanTsStreamer instanceof TunerTsStreamer) { - AutoCloseableUtils.closeQuietly( - ((TunerTsStreamer) mScanTsStreamer).getTunerHal()); + publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size()); } mChannelDataManager.notifyScanCompleted(); if (!mConditionStopped.block(-1)) { @@ -427,8 +439,8 @@ public class ScanFragment extends SetupFragment { // Playbacks with video-only stream have not been tested yet. // No video-only channel has been found. addChannel(channel); + mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); } - mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); } public void showFinishingProgressDialog() { @@ -446,15 +458,21 @@ public class ScanFragment extends SetupFragment { mIsFinished = true; TunerPreferences.setScannedChannelCount(mActivity.getApplicationContext(), mChannelDataManager.getScannedChannelCount()); - // Cancel a previously shown recommendation card. - TunerSetupActivity.cancelRecommendationCard(mActivity.getApplicationContext()); + // Cancel a previously shown notification. + TunerSetupActivity.cancelNotification(mActivity.getApplicationContext()); // Mark scan as done TunerPreferences.setScanDone(mActivity.getApplicationContext()); // finishing will be done manually. if (mFinishingProgressDialog != null) { mFinishingProgressDialog.dismiss(); } - onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH); + // If the fragment is not resumed, the next fragment (scan result page) can't be + // displayed. In that case, just close the activity. + if (isResumed()) { + onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH); + } else if (getActivity() != null) { + getActivity().finish(); + } mChannelScanTask = null; } } diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java index 068543cd..3b8cd823 100644 --- a/src/com/android/tv/tuner/setup/ScanResultFragment.java +++ b/src/com/android/tv/tuner/setup/ScanResultFragment.java @@ -26,6 +26,7 @@ import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.tuner.R; +import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.util.TunerInputInfoUtils; @@ -76,11 +77,19 @@ public class ScanResultFragment extends SetupMultiPaneFragment { mChannelCountOnPreference, mChannelCountOnPreference); breadcrumb = null; } else { + Bundle args = getArguments(); + int tunerType = + (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0)); title = getString(R.string.ut_result_not_found_title); - if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { - description = getString(R.string.bt_result_not_found_description); - } else { - description = getString(R.string.ut_result_not_found_description); + switch (tunerType) { + case TunerHal.TUNER_TYPE_USB: + description = getString(R.string.ut_result_not_found_description); + break; + case TunerHal.TUNER_TYPE_NETWORK: + description = getString(R.string.nt_result_not_found_description); + break; + default: + description = getString(R.string.bt_result_not_found_description); } breadcrumb = getString(R.string.ut_setup_breadcrumb); } diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java index 78121bc5..e9f3baa7 100644 --- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java +++ b/src/com/android/tv/tuner/setup/TunerSetupActivity.java @@ -19,6 +19,7 @@ package com.android.tv.tuner.setup; import android.app.Fragment; import android.app.FragmentManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; @@ -29,49 +30,98 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.tv.TvContract; +import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; import android.support.v4.app.NotificationCompat; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; +import com.android.tv.Features; import com.android.tv.TvApplication; +import com.android.tv.common.AutoCloseableUtils; +import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonConstants; import com.android.tv.common.TvCommonUtils; import com.android.tv.common.ui.setup.SetupActivity; import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; +import com.android.tv.experiments.Experiments; import com.android.tv.tuner.R; import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.tvinput.TunerTvInputService; -import com.android.tv.tuner.util.TunerInputInfoUtils; +import com.android.tv.tuner.util.PostalCodeUtils; + +import java.util.concurrent.Executor; /** * An activity that serves tuner setup process. */ public class TunerSetupActivity extends SetupActivity { - private final String TAG = "TunerSetupActivity"; - // For the recommendation card + private static final String TAG = "TunerSetupActivity"; + private static final boolean DEBUG = false; + + /** + * Key for passing tuner type to sub-fragments. + */ + public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType"; + + // For the notification. private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity"; + private static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel"; private static final String NOTIFY_TAG = "TunerSetup"; private static final int NOTIFY_ID = 1000; private static final String TAG_DRAWABLE = "drawable"; private static final String TAG_ICON = "ic_launcher_s"; + private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1; private static final int CHANNEL_MAP_SCAN_FILE[] = { - R.raw.ut_us_atsc_center_frequencies_8vsb, - R.raw.ut_us_cable_standard_center_frequencies_qam256, - R.raw.ut_us_all, - R.raw.ut_kr_atsc_center_frequencies_8vsb, - R.raw.ut_kr_cable_standard_center_frequencies_qam256, - R.raw.ut_kr_all, - R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256}; + R.raw.ut_us_atsc_center_frequencies_8vsb, + R.raw.ut_us_cable_standard_center_frequencies_qam256, + R.raw.ut_us_all, + R.raw.ut_kr_atsc_center_frequencies_8vsb, + R.raw.ut_kr_cable_standard_center_frequencies_qam256, + R.raw.ut_kr_all, + R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256, + R.raw.ut_euro_dvbt_all, + R.raw.ut_euro_dvbt_all, + R.raw.ut_euro_dvbt_all + }; private ScanFragment mLastScanFragment; + private Integer mTunerType; + private TunerHalFactory mTunerHalFactory; + private boolean mNeedToShowPostalCodeFragment; + private String mPreviousPostalCode; @Override protected void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate"); + new AsyncTask() { + @Override + protected Integer doInBackground(Void... arg0) { + return TunerHal.getTunerTypeAndCount(TunerSetupActivity.this).first; + } + + @Override + protected void onPostExecute(Integer result) { + if (!TunerSetupActivity.this.isDestroyed()) { + mTunerType = result; + if (result == null) { + finish(); + } else { + showInitialFragment(); + } + } + } + }.execute(); TvApplication.setCurrentRunningProcess(this, false); super.onCreate(savedInstanceState); // TODO: check {@link shouldShowRequestPermissionRationale}. @@ -79,16 +129,52 @@ public class TunerSetupActivity extends SetupActivity { != PackageManager.PERMISSION_GRANTED) { // No need to check the request result. requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, - 0); + PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); + } + mTunerHalFactory = new TunerHalFactory(getApplicationContext()); + try { + // Updating postal code takes time, therefore we called it here for "warm-up". + mPreviousPostalCode = PostalCodeUtils.getLastPostalCode(this); + PostalCodeUtils.setLastPostalCode(this, null); + PostalCodeUtils.updatePostalCode(this); + } catch (Exception e) { + // Do nothing. If the last known postal code is null, we'll show guided fragment to + // prompt users to input postal code before ConnectionTypeFragment is shown. + Log.i(TAG, "Can't get postal code:" + e); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED + && Experiments.CLOUD_EPG.get()) { + try { + // Updating postal code takes time, therefore we should update postal code + // right after the permission is granted, so that the subsequent operations, + // especially EPG fetcher, could get the newly updated postal code. + PostalCodeUtils.updatePostalCode(this); + } catch (Exception e) { + // Do nothing + } + } } } @Override protected Fragment onCreateInitialFragment() { - SetupFragment fragment = new WelcomeFragment(); - fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); - return fragment; + if (mTunerType != null) { + SetupFragment fragment = new WelcomeFragment(); + Bundle args = new Bundle(); + args.putInt(KEY_TUNER_TYPE, mTunerType); + fragment.setArguments(args); + fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION + | SetupFragment.FRAGMENT_REENTER_TRANSITION); + return fragment; + } else { + return null; + } } @Override @@ -101,34 +187,42 @@ public class TunerSetupActivity extends SetupActivity { setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK); finish(); break; - default: { - SetupFragment fragment = new ConnectionTypeFragment(); - fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_RETURN_TRANSITION); - showFragment(fragment, true); + default: + if (mNeedToShowPostalCodeFragment + || Features.ENABLE_CLOUD_EPG_REGION.isEnabled( + getApplicationContext()) + && TextUtils.isEmpty( + PostalCodeUtils.getLastPostalCode(this))) { + // We cannot get postal code automatically. Postal code input fragment + // should always be shown even if users have input some valid postal + // code in this activity before. + mNeedToShowPostalCodeFragment = true; + showPostalCodeFragment(); + } else { + showConnectionTypeFragment(); + } break; - } + } + return true; + case PostalCodeFragment.ACTION_CATEGORY: + if (actionId == SetupMultiPaneFragment.ACTION_DONE + || actionId == SetupMultiPaneFragment.ACTION_SKIP) { + showConnectionTypeFragment(); } return true; case ConnectionTypeFragment.ACTION_CATEGORY: - TunerHal hal = TunerHal.createInstance(getApplicationContext()); - if (hal == null) { + if (mTunerHalFactory.getOrCreate() == null) { finish(); Toast.makeText(getApplicationContext(), R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show(); return true; } - try { - hal.close(); - } catch (Exception e) { - Log.e(TAG, "Tuner hal close failed", e); - return true; - } mLastScanFragment = new ScanFragment(); - Bundle args = new Bundle(); - args.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, + Bundle args1 = new Bundle(); + args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]); - mLastScanFragment.setArguments(args); + args1.putInt(KEY_TUNER_TYPE, mTunerType); + mLastScanFragment.setArguments(args1); showFragment(mLastScanFragment, true); return true; case ScanFragment.ACTION_CATEGORY: @@ -137,7 +231,11 @@ public class TunerSetupActivity extends SetupActivity { getFragmentManager().popBackStack(); return true; case ScanFragment.ACTION_FINISH: + mTunerHalFactory.clear(); SetupFragment fragment = new ScanResultFragment(); + Bundle args2 = new Bundle(); + args2.putInt(KEY_TUNER_TYPE, mTunerType); + fragment.setArguments(args2); fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_REENTER_TRANSITION); showFragment(fragment, true); @@ -183,6 +281,14 @@ public class TunerSetupActivity extends SetupActivity { return super.onKeyUp(keyCode, event); } + @Override + public void onDestroy() { + if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) { + PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode); + } + super.onDestroy(); + } + /** * A callback to be invoked when the TvInputService is enabled or disabled. * @@ -190,17 +296,17 @@ public class TunerSetupActivity extends SetupActivity { * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled; * otherwise {@code false} */ - public static void onTvInputEnabled(Context context, boolean enabled) { - // Send a recommendation card for tuner setup if there's no channels and the tuner TV input + public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) { + // Send a notification for tuner setup if there's no channels and the tuner TV input // setup has been not done. boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context); int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context); if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) { TunerPreferences.setShouldShowSetupActivity(context, true); - sendRecommendationCard(context); + sendNotification(context, tunerType); } else { TunerPreferences.setShouldShowSetupActivity(context, false); - cancelRecommendationCard(context); + cancelNotification(context); } } @@ -213,7 +319,7 @@ public class TunerSetupActivity extends SetupActivity { String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(), TunerTvInputService.class.getName())); - // Make an intent to launch the setup activity of USB tuner TV input. + // Make an intent to launch the setup activity of TV tuner input. Intent intent = TvCommonUtils.createSetupIntent( new Intent(context, TunerSetupActivity.class), inputId); intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId); @@ -223,6 +329,27 @@ public class TunerSetupActivity extends SetupActivity { return intent; } + /** + * Gets the currently used tuner HAL. + */ + TunerHal getTunerHal() { + return mTunerHalFactory.getOrCreate(); + } + + /** + * Generates tuner HAL. + */ + void generateTunerHal() { + mTunerHalFactory.generate(); + } + + /** + * Clears the currently used tuner HAL. + */ + void clearTunerHal() { + mTunerHalFactory.clear(); + } + /** * Returns a {@link PendingIntent} to launch the tuner TV input service. * @@ -233,34 +360,53 @@ public class TunerSetupActivity extends SetupActivity { PendingIntent.FLAG_UPDATE_CURRENT); } + private static void sendNotification(Context context, Integer tunerType) { + SoftPreconditions.checkState(tunerType != null, TAG, + "tunerType is null when send notification"); + if (tunerType == null) { + return; + } + Resources resources = context.getResources(); + String contentTitle = resources.getString(R.string.ut_setup_notification_content_title); + int contentTextId = 0; + switch (tunerType) { + case TunerHal.TUNER_TYPE_BUILT_IN: + contentTextId = R.string.bt_setup_notification_content_text; + break; + case TunerHal.TUNER_TYPE_USB: + contentTextId = R.string.ut_setup_notification_content_text; + break; + case TunerHal.TUNER_TYPE_NETWORK: + contentTextId = R.string.nt_setup_notification_content_text; + break; + } + String contentText = resources.getString(contentTextId); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + sendNotificationInternal(context, contentTitle, contentText); + } else { + Bitmap largeIcon = BitmapFactory.decodeResource(resources, + R.drawable.recommendation_antenna); + sendRecommendationCard(context, contentTitle, contentText, largeIcon); + } + } + /** * Sends the recommendation card to start the tuner TV input setup activity. * * @param context a {@link Context} instance */ - private static void sendRecommendationCard(Context context) { - Resources resources = context.getResources(); - String focusedTitle = resources.getString( - R.string.ut_setup_recommendation_card_focused_title); - String title; - if (TunerInputInfoUtils.isBuiltInTuner(context)) { - title = resources.getString(R.string.bt_setup_recommendation_card_title); - } else { - title = resources.getString(R.string.ut_setup_recommendation_card_title); - } - Bitmap largeIcon = BitmapFactory.decodeResource(resources, - R.drawable.recommendation_antenna); - + private static void sendRecommendationCard(Context context, String contentTitle, + String contentText, Bitmap largeIcon) { // Build and send the notification. Notification notification = new NotificationCompat.BigPictureStyle( new NotificationCompat.Builder(context) .setAutoCancel(false) - .setContentTitle(focusedTitle) - .setContentText(title) - .setContentInfo(title) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setContentInfo(contentText) .setCategory(Notification.CATEGORY_RECOMMENDATION) .setLargeIcon(largeIcon) - .setSmallIcon(resources.getIdentifier( + .setSmallIcon(context.getResources().getIdentifier( TAG_ICON, TAG_DRAWABLE, context.getPackageName())) .setContentIntent(createPendingIntentForSetupActivity(context))) .build(); @@ -269,14 +415,129 @@ public class TunerSetupActivity extends SetupActivity { notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); } + private static void sendNotificationInternal(Context context, String contentTitle, + String contentText) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(new NotificationChannel( + TUNER_SET_UP_NOTIFICATION_CHANNEL_ID, + context.getResources().getString(R.string.ut_setup_notification_channel_name), + NotificationManager.IMPORTANCE_HIGH)); + Notification notification = new Notification.Builder( + context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setSmallIcon(context.getResources().getIdentifier( + TAG_ICON, TAG_DRAWABLE, context.getPackageName())) + .setContentIntent(createPendingIntentForSetupActivity(context)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .extend(new Notification.TvExtender()) + .build(); + notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); + } + + private void showPostalCodeFragment() { + SetupFragment fragment = new PostalCodeFragment(); + fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION + | SetupFragment.FRAGMENT_RETURN_TRANSITION); + showFragment(fragment, true); + } + + private void showConnectionTypeFragment() { + SetupFragment fragment = new ConnectionTypeFragment(); + fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION + | SetupFragment.FRAGMENT_RETURN_TRANSITION); + showFragment(fragment, true); + } + /** - * Cancels the previously shown recommendation card. + * Cancels the previously shown notification. * * @param context a {@link Context} instance */ - public static void cancelRecommendationCard(Context context) { + public static void cancelNotification(Context context) { NotificationManager notificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID); } -} + + @VisibleForTesting + static class TunerHalFactory { + private Context mContext; + @VisibleForTesting + TunerHal mTunerHal; + private GenerateTunerHalTask mGenerateTunerHalTask; + private final Executor mExecutor; + + TunerHalFactory(Context context) { + this(context, AsyncTask.SERIAL_EXECUTOR); + } + + TunerHalFactory(Context context, Executor executor) { + mContext = context; + mExecutor = executor; + } + + /** + * Returns tuner HAL currently used. If it's {@code null} and tuner HAL is not generated + * before, tries to generate it synchronously. + */ + @WorkerThread + TunerHal getOrCreate() { + if (mGenerateTunerHalTask != null + && mGenerateTunerHalTask.getStatus() != AsyncTask.Status.FINISHED) { + try { + return mGenerateTunerHalTask.get(); + } catch (Exception e) { + Log.e(TAG, "Cannot get Tuner HAL: " + e); + } + } else if (mGenerateTunerHalTask == null && mTunerHal == null) { + mTunerHal = createInstance(); + } + return mTunerHal; + } + + /** + * Generates tuner hal for scanning with asynchronous tasks. + */ + @MainThread + void generate() { + if (mGenerateTunerHalTask == null && mTunerHal == null) { + mGenerateTunerHalTask = new GenerateTunerHalTask(); + mGenerateTunerHalTask.executeOnExecutor(mExecutor); + } + } + + /** + * Clears the currently used tuner hal. + */ + @MainThread + void clear() { + if (mGenerateTunerHalTask != null) { + mGenerateTunerHalTask.cancel(true); + mGenerateTunerHalTask = null; + } + if (mTunerHal != null) { + AutoCloseableUtils.closeQuietly(mTunerHal); + mTunerHal = null; + } + } + + @WorkerThread + protected TunerHal createInstance() { + return TunerHal.createInstance(mContext); + } + + class GenerateTunerHalTask extends AsyncTask { + @Override + protected TunerHal doInBackground(Void... args) { + return createInstance(); + } + + @Override + protected void onPostExecute(TunerHal tunerHal) { + mTunerHal = tunerHal; + } + } + } +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/setup/WelcomeFragment.java b/src/com/android/tv/tuner/setup/WelcomeFragment.java index 7e809411..feae1ec9 100644 --- a/src/com/android/tv/tuner/setup/WelcomeFragment.java +++ b/src/com/android/tv/tuner/setup/WelcomeFragment.java @@ -18,18 +18,14 @@ package com.android.tv.tuner.setup; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.tuner.R; +import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.util.TunerInputInfoUtils; - import java.util.List; /** @@ -41,7 +37,9 @@ public class WelcomeFragment extends SetupMultiPaneFragment { @Override protected SetupGuidedStepFragment onCreateContentFragment() { - return new ContentFragment(); + ContentFragment fragment = new ContentFragment(); + fragment.setArguments(getArguments()); + return fragment; } @Override @@ -58,11 +56,10 @@ public class WelcomeFragment extends SetupMultiPaneFragment { private int mChannelCountOnPreference; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mChannelCountOnPreference = TunerPreferences - .getScannedChannelCount(getActivity().getApplicationContext()); - return super.onCreateView(inflater, container, savedInstanceState); + public void onCreate(@Nullable Bundle savedInstanceState) { + mChannelCountOnPreference = + TunerPreferences.getScannedChannelCount(getActivity().getApplicationContext()); + super.onCreate(savedInstanceState); } @NonNull @@ -70,20 +67,33 @@ public class WelcomeFragment extends SetupMultiPaneFragment { public Guidance onCreateGuidance(Bundle savedInstanceState) { String title; String description; + int tunerType = getArguments().getInt(TunerSetupActivity.KEY_TUNER_TYPE, + TunerHal.TUNER_TYPE_BUILT_IN); if (mChannelCountOnPreference == 0) { - if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { - title = getString(R.string.bt_setup_new_title); - description = getString(R.string.bt_setup_new_description); - } else { - title = getString(R.string.ut_setup_new_title); - description = getString(R.string.ut_setup_new_description); + switch (tunerType) { + case TunerHal.TUNER_TYPE_USB: + title = getString(R.string.ut_setup_new_title); + description = getString(R.string.ut_setup_new_description); + break; + case TunerHal.TUNER_TYPE_NETWORK: + title = getString(R.string.nt_setup_new_title); + description = getString(R.string.nt_setup_new_description); + break; + default: + title = getString(R.string.bt_setup_new_title); + description = getString(R.string.bt_setup_new_description); } } else { title = getString(R.string.bt_setup_again_title); - if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { - description = getString(R.string.bt_setup_again_description); - } else { - description = getString(R.string.ut_setup_again_description); + switch (tunerType) { + case TunerHal.TUNER_TYPE_USB: + description = getString(R.string.ut_setup_again_description); + break; + case TunerHal.TUNER_TYPE_NETWORK: + description = getString(R.string.nt_setup_again_description); + break; + default: + description = getString(R.string.bt_setup_again_description); } } return new Guidance(title, description, null, null); diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java index 14997ee4..f17dd46b 100644 --- a/src/com/android/tv/tuner/source/FileTsStreamer.java +++ b/src/com/android/tv/tuner/source/FileTsStreamer.java @@ -16,12 +16,14 @@ package com.android.tv.tuner.source; +import android.content.Context; import android.os.Environment; import android.util.Log; import android.util.SparseBooleanArray; import com.google.android.exoplayer.C; import com.google.android.exoplayer.upstream.DataSpec; +import com.android.tv.Features; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.ChannelScanFileParser.ScanChannel; import com.android.tv.tuner.data.TunerChannel; @@ -60,6 +62,7 @@ public class FileTsStreamer implements TsStreamer { private final Object mCircularBufferMonitor = new Object(); private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE]; private final FileSourceEventDetector mEventDetector; + private final Context mContext; private long mBytesFetched; private long mLastReadPosition; @@ -120,8 +123,11 @@ public class FileTsStreamer implements TsStreamer { * Creates {@link TsStreamer} for scanning & playing MPEG-2 TS file. * @param eventListener the listener for channel & program information */ - public FileTsStreamer(EventDetector.EventListener eventListener) { - mEventDetector = new FileSourceEventDetector(eventListener); + public FileTsStreamer(EventDetector.EventListener eventListener, Context context) { + mEventDetector = + new FileSourceEventDetector( + eventListener, Features.ENABLE_FILE_DVB.isEnabled(context)); + mContext = context; } @Override @@ -132,8 +138,12 @@ public class FileTsStreamer implements TsStreamer { return false; } mEventDetector.start(mSource, FileSourceEventDetector.ALL_PROGRAM_NUMBERS); - mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); mSource.addPidFilter(TsParser.PAT_PID); + mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); + if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) { + mSource.addPidFilter(TsParser.DVB_EIT_PID); + mSource.addPidFilter(TsParser.DVB_SDT_PID); + } synchronized (mCircularBufferMonitor) { if (mStreaming) { return true; @@ -160,8 +170,12 @@ public class FileTsStreamer implements TsStreamer { mSource.addPidFilter(i); } mSource.addPidFilter(channel.getPcrPid()); - mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); mSource.addPidFilter(TsParser.PAT_PID); + mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); + if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) { + mSource.addPidFilter(TsParser.DVB_EIT_PID); + mSource.addPidFilter(TsParser.DVB_SDT_PID); + } synchronized (mCircularBufferMonitor) { if (mStreaming) { return true; @@ -256,7 +270,7 @@ public class FileTsStreamer implements TsStreamer { * Returns whether the current pid filter is empty or not. */ public boolean isFilterEmpty() { - return mPids.size() > 0; + return mPids.size() == 0; } /** diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java index ccbb75ba..16be7582 100644 --- a/src/com/android/tv/tuner/source/TsDataSourceManager.java +++ b/src/com/android/tv/tuner/source/TsDataSourceManager.java @@ -17,9 +17,11 @@ package com.android.tv.tuner.source; import android.content.Context; +import android.support.annotation.VisibleForTesting; -import com.android.tv.tuner.data.nano.Channel; +import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.tvinput.EventDetector; import java.util.Map; @@ -31,8 +33,6 @@ import java.util.concurrent.ConcurrentHashMap; * One TsDataSourceManager should be created for per session. */ public class TsDataSourceManager { - private static String TAG = "TsDataSourceManager"; - private static final Object sLock = new Object(); private static final Map sTsStreamers = new ConcurrentHashMap<>(); @@ -80,7 +80,7 @@ public class TsDataSourceManager { if (mIsRecording) { return null; } - FileTsStreamer streamer = new FileTsStreamer(eventListener); + FileTsStreamer streamer = new FileTsStreamer(eventListener, context); if (streamer.startStream(channel)) { TsDataSource source = streamer.createDataSource(); sTsStreamers.put(source, streamer); @@ -126,6 +126,14 @@ public class TsDataSourceManager { mKeepTuneStatus = keepTuneStatus; } + /** + * Add tuner hal into TunerTsStreamerManager for test. + */ + @VisibleForTesting + public void addTunerHalForTest(TunerHal tunerHal) { + mTunerStreamerManager.addTunerHal(tunerHal, mId); + } + /** * Releases persistent resources. */ diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java index b24048e6..843cbdb7 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamer.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamer.java @@ -18,6 +18,7 @@ package com.android.tv.tuner.source; import android.content.Context; import android.util.Log; +import android.util.Pair; import com.google.android.exoplayer.C; import com.google.android.exoplayer.upstream.DataSpec; @@ -30,6 +31,7 @@ import com.android.tv.tuner.tvinput.EventDetector; import com.android.tv.tuner.tvinput.EventDetector.EventListener; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -42,23 +44,27 @@ public class TunerTsStreamer implements TsStreamer { private static final int MIN_READ_UNIT = 1500; private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB + private static final int TS_PACKET_SIZE = 188; private static final int READ_TIMEOUT_MS = 5000; // 5 secs. private static final int BUFFER_UNDERRUN_SLEEP_MS = 10; + private static final int READ_ERROR_STREAMING_ENDED = -1; + private static final int READ_ERROR_BUFFER_OVERWRITTEN = -2; private final Object mCircularBufferMonitor = new Object(); private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE]; private long mBytesFetched; private final AtomicLong mLastReadPosition = new AtomicLong(); - private boolean mEndOfStreamSent; private boolean mStreaming; private final TunerHal mTunerHal; private TunerChannel mChannel; private Thread mStreamingThread; private final EventDetector mEventDetector; + private final List> mEventListenerActions = new ArrayList<>(); private final TsStreamWriter mTsStreamWriter; + private String mChannelNumber; public static class TunerDataSource extends TsDataSource { private final TunerTsStreamer mTsStreamer; @@ -103,6 +109,15 @@ public class TunerTsStreamer implements TsStreamer { offset, readLength); if (ret > 0) { mLastReadPosition.addAndGet(ret); + } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) { + long currentPosition = mStartBufferedPosition + mLastReadPosition.get(); + long endPosition = mTsStreamer.getBufferedPosition(); + long diff = ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE) + * TS_PACKET_SIZE; + Log.w(TAG, "Demux position jump by overwritten buffer: " + diff); + mStartBufferedPosition = currentPosition + diff; + mLastReadPosition.set(0); + return 0; } return ret; } @@ -114,7 +129,10 @@ public class TunerTsStreamer implements TsStreamer { */ public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) { mTunerHal = tunerHal; - mEventDetector = new EventDetector(mTunerHal, eventListener); + mEventDetector = new EventDetector(mTunerHal); + if (eventListener != null) { + mEventDetector.registerListener(eventListener); + } mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ? new TsStreamWriter(context) : null; } @@ -125,7 +143,8 @@ public class TunerTsStreamer implements TsStreamer { @Override public boolean startStream(TunerChannel channel) { - if (mTunerHal.tune(channel.getFrequency(), channel.getModulation())) { + if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(), + channel.getDisplayNumber(false))) { if (channel.hasVideo()) { mTunerHal.addPidFilter(channel.getVideoPid(), TunerHal.FILTER_TYPE_VIDEO); @@ -148,6 +167,7 @@ public class TunerTsStreamer implements TsStreamer { channel.getProgramNumber()); } mChannel = channel; + mChannelNumber = channel.getDisplayNumber(); synchronized (mCircularBufferMonitor) { if (mStreaming) { Log.w(TAG, "Streaming should be stopped before start streaming"); @@ -156,7 +176,6 @@ public class TunerTsStreamer implements TsStreamer { mStreaming = true; mBytesFetched = 0; mLastReadPosition.set(0L); - mEndOfStreamSent = false; } if (mTsStreamWriter != null) { mTsStreamWriter.setChannel(mChannel); @@ -172,7 +191,7 @@ public class TunerTsStreamer implements TsStreamer { @Override public boolean startStream(ChannelScanFileParser.ScanChannel channel) { - if (mTunerHal.tune(channel.frequency, channel.modulation)) { + if (mTunerHal.tune(channel.frequency, channel.modulation, null)) { mEventDetector.startDetecting( channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS); synchronized (mCircularBufferMonitor) { @@ -183,7 +202,6 @@ public class TunerTsStreamer implements TsStreamer { mStreaming = true; mBytesFetched = 0; mLastReadPosition.set(0L); - mEndOfStreamSent = false; } mStreamingThread = new StreamingThread(); mStreamingThread.start(); @@ -258,6 +276,26 @@ public class TunerTsStreamer implements TsStreamer { } } + public String getStreamerInfo() { + return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming; + } + + public void registerListener(EventListener listener) { + if (mEventDetector != null && listener != null) { + synchronized (mEventListenerActions) { + mEventListenerActions.add(new Pair<>(listener, true)); + } + } + } + + public void unregisterListener(EventListener listener) { + if (mEventDetector != null) { + synchronized (mEventListenerActions) { + mEventListenerActions.add(new Pair(listener, false)); + } + } + } + private class StreamingThread extends Thread { @Override public void run() { @@ -271,6 +309,20 @@ public class TunerTsStreamer implements TsStreamer { } } + if (mEventDetector != null) { + synchronized (mEventListenerActions) { + for (Pair listenerAction : mEventListenerActions) { + EventListener listener = (EventListener) listenerAction.first; + if ((boolean) listenerAction.second) { + mEventDetector.registerListener(listener); + } else { + mEventDetector.unregisterListener(listener); + } + } + mEventListenerActions.clear(); + } + } + int bytesWritten = mTunerHal.readTsStream(dataBuffer, dataBuffer.length); if (bytesWritten <= 0) { try { @@ -321,21 +373,14 @@ public class TunerTsStreamer implements TsStreamer { * @throws IOException */ public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException { - long readStartTime = System.currentTimeMillis(); while (true) { synchronized (mCircularBufferMonitor) { - if (mEndOfStreamSent || !mStreaming) { - return -1; + if (!mStreaming) { + return READ_ERROR_STREAMING_ENDED; } if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) { - Log.e(TAG, "Demux is requesting the data which is already overwritten."); - return -1; - } - if (System.currentTimeMillis() - readStartTime > READ_TIMEOUT_MS) { - // Nothing was received during READ_TIMEOUT_MS before. - mEndOfStreamSent = true; - mCircularBufferMonitor.notifyAll(); - return -1; + Log.w(TAG, "Demux is requesting the data which is already overwritten."); + return READ_ERROR_BUFFER_OVERWRITTEN; } if (mBytesFetched < pos + amount) { try { diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java index cf1f6dcf..258a4d86 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java @@ -42,6 +42,7 @@ class TunerTsStreamerManager { private final Object mCancelLock = new Object(); private final StreamerFinder mStreamerFinder = new StreamerFinder(); private final Map mCreators = new HashMap<>(); + private final Map mListeners = new HashMap<>(); private final Map mSourceToStreamerMap = new HashMap<>(); private final TunerHalManager mTunerHalManager = new TunerHalManager(); private static TunerTsStreamerManager sInstance; @@ -68,6 +69,8 @@ class TunerTsStreamerManager { mStreamerFinder.appendSessionLocked(channel, sessionId); TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); TsDataSource source = streamer.createDataSource(); + mListeners.put(sessionId, listener); + streamer.registerListener(listener); mSourceToStreamerMap.put(source, streamer); return source; } @@ -83,6 +86,7 @@ class TunerTsStreamerManager { if (!creator.isCancelledLocked()) { mStreamerFinder.putLocked(channel, sessionId, streamer); TsDataSource source = streamer.createDataSource(); + mListeners.put(sessionId, listener); mSourceToStreamerMap.put(source, streamer); return source; } @@ -104,6 +108,8 @@ class TunerTsStreamerManager { if (streamer == null) { return; } + EventDetector.EventListener listener = mListeners.remove(sessionId); + streamer.unregisterListener(listener); TunerChannel channel = streamer.getChannel(); SoftPreconditions.checkState(channel != null); mStreamerFinder.removeSessionLocked(channel, sessionId); @@ -125,6 +131,13 @@ class TunerTsStreamerManager { } } + /** + * Add tuner hal into TunerHalManager for test. + */ + void addTunerHal(TunerHal tunerHal, int sessionId) { + mTunerHalManager.addTunerHal(tunerHal, sessionId); + } + synchronized void release(int sessionId) { mTunerHalManager.releaseCachedHal(sessionId); } @@ -261,16 +274,16 @@ class TunerTsStreamerManager { } private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) { - if (!reuse) { + if (!reuse || !hal.isReusable()) { AutoCloseableUtils.closeQuietly(hal); return; } TunerHal cachedHal = mTunerHals.get(sessionId); if (cachedHal != hal) { mTunerHals.put(sessionId, hal); - } - if (cachedHal != null && cachedHal != hal) { - AutoCloseableUtils.closeQuietly(cachedHal); + if (cachedHal != null) { + AutoCloseableUtils.closeQuietly(cachedHal); + } } } @@ -283,5 +296,9 @@ class TunerTsStreamerManager { AutoCloseableUtils.closeQuietly(hal); } } + + private void addTunerHal(TunerHal tunerHal, int sessionId) { + mTunerHals.put(sessionId, tunerHal); + } } } \ No newline at end of file diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java index 8c1f6a1b..e1f890f3 100644 --- a/src/com/android/tv/tuner/ts/SectionParser.java +++ b/src/com/android/tv/tuner/ts/SectionParser.java @@ -18,11 +18,12 @@ package com.android.tv.tuner.ts; import android.media.tv.TvContentRating; import android.media.tv.TvContract.Programs.Genres; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor; @@ -34,24 +35,32 @@ import com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor; import com.android.tv.tuner.data.PsipData.GenreDescriptor; import com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor; import com.android.tv.tuner.data.PsipData.MgtItem; +import com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor; import com.android.tv.tuner.data.PsipData.PsipSection; import com.android.tv.tuner.data.PsipData.RatingRegion; import com.android.tv.tuner.data.PsipData.RegionalRating; +import com.android.tv.tuner.data.PsipData.SdtItem; +import com.android.tv.tuner.data.PsipData.ServiceDescriptor; +import com.android.tv.tuner.data.PsipData.ShortEventDescriptor; import com.android.tv.tuner.data.PsipData.TsDescriptor; import com.android.tv.tuner.data.PsipData.VctItem; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.ByteArrayBuffer; +import com.android.tv.tuner.util.ConvertUtils; import com.ibm.icu.text.UnicodeDecompressor; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.util.Calendar; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Parses ATSC PSIP sections. @@ -68,6 +77,13 @@ public class SectionParser { private static final byte TABLE_ID_EIT = (byte) 0xcb; private static final byte TABLE_ID_ETT = (byte) 0xcc; + // Table id for DVB + private static final byte TABLE_ID_SDT = (byte) 0x42; + private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e; + private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f; + private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50; + private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60; + // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25. public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a; public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; @@ -76,6 +92,12 @@ public class SectionParser { public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0; public static final int DESCRIPTOR_TAG_GENRE = 0xab; + // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12. + public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48; + public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d; + public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54; + public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55; + private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff private static final byte MODE_UTF16 = (byte) 0x3f; @@ -88,17 +110,57 @@ public class SectionParser { // The following values are defined in the live channels app. // See https://developer.android.com/reference/android/media/tv/TvContentRating.html. + private static final String RATING_DOMAIN = "com.android.tv"; private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV"; + private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV"; private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV"; private static final String[] RATING_REGION_TABLE_US_TV = { "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA" }; + private static final String[] RATING_REGION_TABLE_US_MV = { + "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17" + }; + private static final String[] RATING_REGION_TABLE_KR_TV = { "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19" }; + private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = { + "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV" + }; + + // According to ANSI-CEA-766-D + private static final int VALUE_US_TV_Y = 1; + private static final int VALUE_US_TV_Y7 = 2; + private static final int VALUE_US_TV_NONE = 1; + private static final int VALUE_US_TV_G = 2; + private static final int VALUE_US_TV_PG = 3; + private static final int VALUE_US_TV_14 = 4; + private static final int VALUE_US_TV_MA = 5; + + private static final int DIMENSION_US_TV_RATING = 0; + private static final int DIMENSION_US_TV_D = 1; + private static final int DIMENSION_US_TV_L = 2; + private static final int DIMENSION_US_TV_S = 3; + private static final int DIMENSION_US_TV_V = 4; + private static final int DIMENSION_US_TV_Y = 5; + private static final int DIMENSION_US_TV_FV = 6; + private static final int DIMENSION_US_MV_RATING = 7; + + private static final int VALUE_US_MV_G = 2; + private static final int VALUE_US_MV_PG = 3; + private static final int VALUE_US_MV_PG13 = 4; + private static final int VALUE_US_MV_R = 5; + private static final int VALUE_US_MV_NC17 = 6; + private static final int VALUE_US_MV_X = 7; + + private static final String STRING_US_TV_Y = "US_TV_Y"; + private static final String STRING_US_TV_Y7 = "US_TV_Y7"; + private static final String STRING_US_TV_FV = "US_TV_FV"; + + /* * The following CRC table is from the code generated by the following command. * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c @@ -330,6 +392,7 @@ public class SectionParser { void onVctParsed(List items, int sectionNumber, int lastSectionNumber); void onEitParsed(int sourceId, List items); void onEttParsed(int sourceId, List descriptions); + void onSdtParsed(List items); } private final OutputListener mListener; @@ -367,6 +430,10 @@ public class SectionParser { mParsedEttItems.clear(); } + public void resetVersionNumbers() { + mSectionVersionMap.clear(); + } + private void parseSection(byte[] data) { if (!checkSanity(data)) { Log.d(TAG, "Bad CRC!"); @@ -410,6 +477,13 @@ public class SectionParser { case TABLE_ID_ETT: result = parseETT(data); break; + case TABLE_ID_SDT: + result = parseSDT(data); + break; + case TABLE_ID_DVB_ACTUAL_P_F_EIT: + case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT: + result = parseDVBEIT(data); + break; default: break; } @@ -510,10 +584,8 @@ public class SectionParser { pos += 11 + descriptorsLength; results.add(new MgtItem(tableType, tableTypePid)); } - if ((data[pos] & 0xf0) != 0xf0) { - Log.e(TAG, "Broken MGT."); - return false; - } + // Skip the remaining descriptor part which we don't use. + if (mListener != null) { mListener.onMgtParsed(results); } @@ -704,6 +776,127 @@ public class SectionParser { return true; } + private boolean parseSDT(byte[] data) { + // For details of the structure for SDT, see DVB Document A038 Table 5. + if (DEBUG) { + Log.d(TAG, "SDT id discovered"); + } + if (data.length <= 11) { + Log.e(TAG, "Broken SDT."); + return false; + } + if ((data[1] & 0x80) >> 7 != 1) { + Log.e(TAG, "Broken SDT, section syntax indicator error."); + return false; + } + int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); + int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); + int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); + int pos = 11; + if (sectionLength + 3 > data.length) { + Log.e(TAG, "Broken SDT."); + } + List sdtItems = new ArrayList<>(); + while (pos + 9 < data.length) { + int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff); + int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff); + pos += 5; + List descriptors = parseDescriptors(data, pos, pos + descriptorsLength); + List serviceDescriptors = generateServiceDescriptors(descriptors); + String serviceName = ""; + String serviceProviderName = ""; + int serviceType = 0; + for (ServiceDescriptor serviceDescriptor : serviceDescriptors) { + serviceName = serviceDescriptor.getServiceName(); + serviceProviderName = serviceDescriptor.getServiceProviderName(); + serviceType = serviceDescriptor.getServiceType(); + } + if (serviceDescriptors.size() > 0) { + sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId, + originalNetworkId)); + } + pos += descriptorsLength; + } + if (mListener != null) { + mListener.onSdtParsed(sdtItems); + } + return true; + } + + private boolean parseDVBEIT(byte[] data) { + // For details of the structure for DVB ETT, see DVB Document A038 Table 7. + if (DEBUG) { + Log.d(TAG, "DVB EIT is discovered."); + } + if (data.length < 18) { + Log.e(TAG, "Broken DVB EIT."); + return false; + } + int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); + int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); + int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); + int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff); + + int pos = 14; + List results = new ArrayList<>(); + while (pos + 12 < data.length) { + int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff); + float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff); + int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f); + int mjdMonth = (int) ((modifiedJulianDate - 14956.1f + - (int) (startYear * 365.25f)) / 30.6001f); + int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f) + - (int) (mjdMonth * 30.6001f); + int startMonth = mjdMonth - 1; + if (mjdMonth == 14 || mjdMonth == 15) { + startYear += 1; + startMonth -= 12; + } + int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f); + int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f); + int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f); + Calendar calendar = Calendar.getInstance(); + startYear += 1900; + calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond); + long startTime = ConvertUtils.convertUnixEpochToGPSTime( + calendar.getTimeInMillis() / 1000); + int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10 + + (data[pos + 7] & 0x0f)) * 3600 + + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60 + + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f)); + int descriptorsLength = ((data[pos + 10] & 0x0f) << 8) + | (data[pos + 10 + 1] & 0xff); + int descriptorsPos = pos + 10 + 2; + if (data.length < descriptorsPos + descriptorsLength) { + Log.e(TAG, "Broken EIT."); + return false; + } + List descriptors = parseDescriptors( + data, descriptorsPos, descriptorsPos + descriptorsLength); + if (DEBUG) { + Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size())); + } + // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for + // details. Content rating here will be null + String contentRating = generateContentRating(descriptors); + // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details. + // Genre here will be null here. + String broadcastGenre = generateBroadcastGenre(descriptors); + String canonicalGenre = generateCanonicalGenre(descriptors); + String titleText = generateShortEventName(descriptors); + List audioTracks = generateAudioTracks(descriptors); + List captionTracks = generateCaptionTracks(descriptors); + pos += 12 + descriptorsLength; + results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText, + startTime, durationInSecond, contentRating, audioTracks, captionTracks, + broadcastGenre, canonicalGenre, null)); + } + if (mListener != null) { + mListener.onEitParsed(sourceId, results); + } + return true; + } + private static List generateAudioTracks(List descriptors) { // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639 // Language descriptor. @@ -717,6 +910,9 @@ public class SectionParser { if (audioDescriptor.getLanguage() != null) { audioTrack.language = audioDescriptor.getLanguage(); } + if (audioTrack.language == null) { + audioTrack.language = ""; + } audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED; audioTrack.channelCount = audioDescriptor.getNumChannels(); audioTrack.sampleRate = audioDescriptor.getSampleRate(); @@ -787,44 +983,179 @@ public class SectionParser { return services; } - private static String generateContentRating(List descriptors) { - List contentRatings = new ArrayList<>(); + @VisibleForTesting + static String generateContentRating(List descriptors) { + Set contentRatings = new ArraySet<>(); + List usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV); + List krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV); + for (RatingRegion region : usRatingRegions) { + String contentRating = getUsRating(region); + if (contentRating != null) { + contentRatings.add(contentRating); + } + } + for (RatingRegion region : krRatingRegions) { + String contentRating = getKrRating(region); + if (contentRating != null) { + contentRatings.add(contentRating); + } + } + return TextUtils.join(",", contentRatings); + } + + /** + * Gets a list of {@link RatingRegion} in the specific region. + * + * @param descriptors {@link TsDescriptor} list which may contains rating information + * @param region the specific region + * @return a list of {@link RatingRegion} in the specific region + */ + private static List getRatingRegions(List descriptors, int region) { + List ratingRegions = new ArrayList<>(); for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof ContentAdvisoryDescriptor) { - ContentAdvisoryDescriptor contentAdvisoryDescriptor = - (ContentAdvisoryDescriptor) descriptor; - for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) { - for (RegionalRating index : ratingRegion.getRegionalRatings()) { - String ratingSystem = null; - String rating = null; - switch (ratingRegion.getName()) { - case RATING_REGION_US_TV: - ratingSystem = RATING_REGION_RATING_SYSTEM_US_TV; - if (index.getDimension() == 0 && index.getRating() >= 0 - && index.getRating() < RATING_REGION_TABLE_US_TV.length) { - rating = RATING_REGION_TABLE_US_TV[index.getRating()]; - } - break; - case RATING_REGION_KR_TV: - ratingSystem = RATING_REGION_RATING_SYSTEM_KR_TV; - if (index.getDimension() == 0 && index.getRating() >= 0 - && index.getRating() < RATING_REGION_TABLE_KR_TV.length) { - rating = RATING_REGION_TABLE_KR_TV[index.getRating()]; - } - break; - default: - break; + if (!(descriptor instanceof ContentAdvisoryDescriptor)) { + continue; + } + ContentAdvisoryDescriptor contentAdvisoryDescriptor = + (ContentAdvisoryDescriptor) descriptor; + for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) { + if (ratingRegion.getName() == region) { + ratingRegions.add(ratingRegion); + } + } + } + return ratingRegions; + } + + /** + * Gets US content rating and subratings (if any). + * + * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. + * @return A string representing the US content rating and subratings. The format of the string + * is defined in {@link TvContentRating}. null, if no such a string exists. + */ + private static String getUsRating(RatingRegion ratingRegion) { + if (ratingRegion.getName() != RATING_REGION_US_TV) { + return null; + } + List regionalRatings = ratingRegion.getRegionalRatings(); + String rating = null; + int ratingIndex = VALUE_US_TV_NONE; + List subratings = new ArrayList<>(); + for (RegionalRating index : regionalRatings) { + // See Table 3 of ANSI-CEA-766-D + int dimension = index.getDimension(); + int value = index.getRating(); + switch (dimension) { + // According to Table 6.27 of ATSC A65, + // the dimensions shall be in increasing order. + // Therefore, rating and ratingIndex are assigned before any corresponding + // subrating. + case DIMENSION_US_TV_RATING: + if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) { + rating = RATING_REGION_TABLE_US_TV[value]; + ratingIndex = value; + } + break; + case DIMENSION_US_TV_D: + if (value == 1 + && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) { + // US_TV_D is applicable to US_TV_PG and US_TV_14 + subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); + } + break; + case DIMENSION_US_TV_L: + case DIMENSION_US_TV_S: + case DIMENSION_US_TV_V: + if (value == 1 + && ratingIndex >= VALUE_US_TV_PG + && ratingIndex <= VALUE_US_TV_MA) { + // US_TV_L, US_TV_S, and US_TV_V are applicable to + // US_TV_PG, US_TV_14 and US_TV_MA + subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); + } + break; + case DIMENSION_US_TV_Y: + if (rating == null) { + if (value == VALUE_US_TV_Y) { + rating = STRING_US_TV_Y; + } else if (value == VALUE_US_TV_Y7) { + rating = STRING_US_TV_Y7; } - if (ratingSystem != null && rating != null) { - contentRatings.add(TvContentRating - .createRating("com.android.tv", ratingSystem, rating) - .flattenToString()); + } + break; + case DIMENSION_US_TV_FV: + if (STRING_US_TV_Y7.equals(rating) && value == 1) { + // US_TV_FV is applicable to US_TV_Y7 + subratings.add(STRING_US_TV_FV); + } + break; + case DIMENSION_US_MV_RATING: + if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) { + if (value == VALUE_US_MV_X) { + // US_MV_X was replaced by US_MV_NC17 in 1990, + // and it's not supported by TvContentRating + value = VALUE_US_MV_NC17; + } + if (rating != null) { + // According to Table 3 of ANSI-CEA-766-D, + // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be + // present in the same descriptor. + Log.w( + TAG, + "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are " + + "present in the same descriptor"); + } else { + return TvContentRating.createRating( + RATING_DOMAIN, + RATING_REGION_RATING_SYSTEM_US_MV, + RATING_REGION_TABLE_US_MV[value - 2]) + .flattenToString(); } } - } + break; + + default: + break; } } - return TextUtils.join(",", contentRatings); + if (rating == null) { + return null; + } + + String[] subratingArray = subratings.toArray(new String[subratings.size()]); + return TvContentRating.createRating( + RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray) + .flattenToString(); + } + + /** + * Gets KR(South Korea) content rating. + * + * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. + * @return A string representing the KR content rating. The format of the string is defined in + * {@link TvContentRating}. null, if no such a string exists. + */ + private static String getKrRating(RatingRegion ratingRegion) { + if (ratingRegion.getName() != RATING_REGION_KR_TV) { + return null; + } + List regionalRatings = ratingRegion.getRegionalRatings(); + String rating = null; + for (RegionalRating index : regionalRatings) { + if (index.getDimension() == 0 + && index.getRating() >= 0 + && index.getRating() < RATING_REGION_TABLE_KR_TV.length) { + rating = RATING_REGION_TABLE_KR_TV[index.getRating()]; + break; + } + } + if (rating == null) { + return null; + } + return TvContentRating.createRating( + RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating) + .flattenToString(); } private static String generateBroadcastGenre(List descriptors) { @@ -849,6 +1180,28 @@ public class SectionParser { return null; } + private static List generateServiceDescriptors( + List descriptors) { + List serviceDescriptors = new ArrayList<>(); + for (TsDescriptor descriptor : descriptors) { + if (descriptor instanceof ServiceDescriptor) { + ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor; + serviceDescriptors.add(serviceDescriptor); + } + } + return serviceDescriptors; + } + + private static String generateShortEventName(List descriptors) { + for (TsDescriptor descriptor : descriptors) { + if (descriptor instanceof ShortEventDescriptor) { + ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor; + return shortEventDescriptor.getEventName(); + } + } + return ""; + } + private static List parseDescriptors(byte[] data, int offset, int limit) { // For details of the structure for descriptors, see ATSC A/65 Section 6.9. List descriptors = new ArrayList<>(); @@ -894,6 +1247,22 @@ public class SectionParser { descriptor = parseIso639Language(data, pos, pos + length + 2); break; + case DVB_DESCRIPTOR_TAG_SERVICE: + descriptor = parseDvbService(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_SHORT_EVENT: + descriptor = parseDvbShortEvent(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_CONTENT: + descriptor = parseDvbContent(data, pos, pos + length + 2); + break; + + case DVB_DESCRIPTOR_TAG_PARENTAL_RATING: + descriptor = parseDvbParentalRating(data, pos, pos + length + 2); + break; + default: } if (descriptor != null) { @@ -948,6 +1317,7 @@ public class SectionParser { pos += 3; boolean ccType = (data[pos] & 0x80) != 0; if (!ccType) { + pos +=3; continue; } int captionServiceNumber = data[pos] & 0x3f; @@ -987,6 +1357,7 @@ public class SectionParser { int ratingRegion = data[pos] & 0xff; int dimensionCount = data[pos + 1] & 0xff; pos += 2; + int previousDimension = -1; for (int j = 0; j < dimensionCount; ++j) { if (limit <= pos + 1) { Log.e(TAG, "Broken ContentAdvisory"); @@ -994,6 +1365,13 @@ public class SectionParser { } int dimensionIndex = data[pos] & 0xff; int ratingValue = data[pos + 1] & 0x0f; + if (dimensionIndex <= previousDimension) { + // According to Table 6.27 of ATSC A65, + // the indices shall be in increasing order. + Log.e(TAG, "Broken ContentAdvisory"); + return null; + } + previousDimension = dimensionIndex; pos += 2; indices.add(new RegionalRating(dimensionIndex, ratingValue)); } @@ -1189,6 +1567,74 @@ public class SectionParser { language, language2); } + private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 86. + if (limit < pos + 5) { + Log.e(TAG, "Broken service descriptor."); + return null; + } + pos += 2; + int serviceType = data[pos] & 0xff; + pos++; + int serviceProviderNameLength = data[pos] & 0xff; + pos++; + String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength); + pos += serviceProviderNameLength; + int serviceNameLength = data[pos] & 0xff; + pos++; + String serviceName = extractTextFromDvb(data, pos, serviceNameLength); + return new ServiceDescriptor(serviceType, serviceProviderName, serviceName); + } + + private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 91. + if (limit < pos + 7) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + pos += 2; + String language = new String(data, pos, 3); + int eventNameLength = data[pos + 3] & 0xff; + pos += 4; + if (pos + eventNameLength > limit) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + String eventName = new String(data, pos, eventNameLength); + pos += eventNameLength; + int textLength = data[pos] & 0xff; + if (pos + textLength > limit) { + Log.e(TAG, "Broken short event descriptor."); + return null; + } + pos++; + String text = new String(data, pos, textLength); + return new ShortEventDescriptor(language, eventName, text); + } + + private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) { + // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to + // get content genre. + return null; + } + + private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) { + // For details of DVB service descriptors, see DVB Document A038 Table 81. + HashMap ratings = new HashMap<>(); + pos += 2; + while (pos + 4 <= limit) { + String countryCode = new String(data, pos, 3); + int rating = data[pos + 3] & 0xff; + pos += 4; + if (rating > 15) { + // Rating > 15 means that the ratings is defined by broadcaster. + continue; + } + ratings.put(countryCode, rating + 3); + } + return new ParentalRatingDescriptor(ratings); + } + private static int getShortNameSize(byte[] data, int offset) { for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) { if (data[offset + i] == 0 && data[offset + i + 1] == 0) { @@ -1244,6 +1690,55 @@ public class SectionParser { return null; } + private static String extractTextFromDvb(byte[] data, int pos, int length) { + // For details of DVB character set selection, see DVB Document A038 Annex A. + if (data.length < pos + length) { + return null; + } + try { + String charsetPrefix = "ISO-8859-"; + switch (data[0]) { + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x09: + case 0x0A: + case 0x0B: + String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4); + return new String(data, pos, length, charset); + case 0x10: + if (length < 3) { + Log.e(TAG, "Broken DVB text"); + return null; + } + int codeTable = data[pos + 2] & 0xff; + if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) { + return new String( + data, pos, length, charsetPrefix + String.valueOf(codeTable)); + } else { + return new String(data, pos, length, "ISO-8859-1"); + } + case 0x11: + case 0x14: + case 0x15: + return new String(data, pos, length, "UTF-16BE"); + case 0x12: + return new String(data, pos, length, "EUC-KR"); + case 0x13: + return new String(data, pos, length, "GB2312"); + default: + return new String(data, pos, length, "ISO-8859-1"); + } + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Unsupported text format.", e); + } + return new String(data, pos, length); + } + private static boolean checkSanity(byte[] data) { if (data.length <= 1) { return false; diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java index c24c2a21..7cdb534e 100644 --- a/src/com/android/tv/tuner/ts/TsParser.java +++ b/src/com/android/tv/tuner/ts/TsParser.java @@ -25,6 +25,7 @@ import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.PsipData.EttItem; import com.android.tv.tuner.data.PsipData.MgtItem; +import com.android.tv.tuner.data.PsipData.SdtItem; import com.android.tv.tuner.data.PsipData.VctItem; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.ts.SectionParser.OutputListener; @@ -46,6 +47,8 @@ public class TsParser { public static final int ATSC_SI_BASE_PID = 0x1ffb; public static final int PAT_PID = 0x0000; + public static final int DVB_SDT_PID = 0x0011; + public static final int DVB_EIT_PID = 0x0012; private static final int TS_PACKET_START_CODE = 0x47; private static final int TS_PACKET_TEI_MASK = 0x80; private static final int TS_PACKET_SIZE = 188; @@ -64,6 +67,7 @@ public class TsParser { private final Map mProgramNumberToVctItemMap = new HashMap<>(); private final Map> mProgramNumberToPMTMap = new HashMap<>(); private final Map> mSourceIdToEitMap = new HashMap<>(); + private final Map mProgramNumberToSdtItemMap = new HashMap<>(); private final Map> mEitMap = new HashMap<>(); private final Map> mETTMap = new HashMap<>(); private final TreeSet mEITPids = new TreeSet<>(); @@ -71,6 +75,7 @@ public class TsParser { private final SparseBooleanArray mProgramNumberHandledStatus = new SparseBooleanArray(); private final SparseBooleanArray mVctItemHandledStatus = new SparseBooleanArray(); private final TsOutputListener mListener; + private final boolean mIsDvbSignal; private int mVctItemCount; private int mHandledVctItemCount; @@ -84,6 +89,7 @@ public class TsParser { void onEitItemParsed(VctItem channel, List items); void onEttPidDetected(int pid); void onAllVctItemsParsed(); + void onSdtItemParsed(SdtItem channel, List pmtItems); } private abstract class Stream { @@ -102,6 +108,7 @@ public class TsParser { } protected abstract void handleData(byte[] data, boolean startIndicator); + protected abstract void resetDataVersions(); } private class SectionStream extends Stream { @@ -138,6 +145,11 @@ public class TsParser { mSectionParser.parseSections(mPacket); } + @Override + protected void resetDataVersions() { + mSectionParser.resetVersionNumbers(); + } + private final OutputListener mSectionListener = new OutputListener() { @Override public void onPatParsed(List items) { @@ -173,6 +185,12 @@ public class TsParser { mListener.onAllVctItemsParsed(); } } + SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber); + if (sdtItem != null) { + // When PMT is parsed later than SDT. + mProgramNumberHandledStatus.put(programNumber, true); + handleSdtItem(sdtItem, items); + } } } @@ -276,6 +294,24 @@ public class TsParser { mETTMap.put(entry, descriptions); handleEvents(sourceId); } + + @Override + public void onSdtParsed(List sdtItems) { + for (SdtItem sdtItem : sdtItems) { + if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem); + int programNumber = sdtItem.getServiceId(); + mProgramNumberToSdtItemMap.put(programNumber, sdtItem); + List pmtList = mProgramNumberToPMTMap.get(programNumber); + if (pmtList != null) { + mProgramNumberHandledStatus.put(programNumber, true); + handleSdtItem(sdtItem, pmtList); + } else { + mProgramNumberHandledStatus.put(programNumber, false); + Log.i(TAG, "onSdtParsed, but PMT for programNo " + programNumber + + " is not found yet."); + } + } + } }; } @@ -335,6 +371,15 @@ public class TsParser { } } + private void handleSdtItem(SdtItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "handleSdtItem " + channel); + } + if (mListener != null) { + mListener.onSdtItemParsed(channel, pmtItems); + } + } + private void handleEvents(int sourceId) { Map itemSet = new HashMap<>(); for (int pid : mEITPids) { @@ -367,17 +412,26 @@ public class TsParser { handleEitItems(channel, items); } else { mVctItemHandledStatus.put(sourceId, false); - Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet."); + if (!mIsDvbSignal) { + // Log only when zapping to non-DVB channels, since there is not VCT in DVB signal. + Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet."); + } } } /** * Creates MPEG-2 TS parser. + * * @param listener TsOutputListener */ - public TsParser(TsOutputListener listener) { - startListening(ATSC_SI_BASE_PID); + public TsParser(TsOutputListener listener, boolean isDvbSignal) { startListening(PAT_PID); + startListening(ATSC_SI_BASE_PID); + mIsDvbSignal = isDvbSignal; + if (isDvbSignal) { + startListening(DVB_EIT_PID); + startListening(DVB_SDT_PID); + } mListener = listener; } @@ -412,7 +466,7 @@ public class TsParser { // We are not interested in this packet. return false; } - if (payloadPos > pos + TS_PACKET_SIZE) { + if (payloadPos >= pos + TS_PACKET_SIZE) { if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet."); return false; } @@ -451,4 +505,16 @@ public class TsParser { } return incompleteChannels; } + + /** + * Reset the versions so that data with old version number can be handled. + */ + public void resetDataVersions() { + for (int eitPid : mEITPids) { + Stream stream = mStreamMap.get(eitPid); + if (stream != null) { + stream.resetDataVersions(); + } + } + } } diff --git a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java index a16bc522..d2b4998a 100644 --- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java +++ b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java @@ -25,6 +25,7 @@ import android.content.OperationApplicationException; import android.database.Cursor; import android.media.tv.TvContract; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -37,6 +38,7 @@ import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.util.ConvertUtils; +import com.android.tv.util.PermissionUtils; import java.util.ArrayList; import java.util.Collections; @@ -192,11 +194,14 @@ public class ChannelDataManager implements Handler.Callback { public void release() { mHandler.removeCallbacksAndMessages(null); - mHandlerThread.quitSafely(); + releaseSafely(); } public void releaseSafely() { mHandlerThread.quitSafely(); + mListener = null; + mChannelScanListener = null; + mChannelScanHandler = null; } public TunerChannel getChannel(long channelId) { @@ -435,7 +440,7 @@ public class ChannelDataManager implements Handler.Callback { } } ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), newItem, channel.getChannelId())); + TvContract.Programs.CONTENT_URI), newItem, channel)); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -505,7 +510,7 @@ public class ChannelDataManager implements Handler.Callback { continue; } ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), item, channel.getChannelId())); + TvContract.Programs.CONTENT_URI), item, channel)); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -516,9 +521,13 @@ public class ChannelDataManager implements Handler.Callback { } private ContentProviderOperation buildContentProviderOperation( - ContentProviderOperation.Builder builder, EitItem item, Long channelId) { - if (channelId != null) { - builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channelId); + ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) { + if (channel != null) { + builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED, + channel.isRecordingProhibited() ? 1 : 0); + } } if (item != null) { builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText()) @@ -556,7 +565,10 @@ public class ChannelDataManager implements Handler.Callback { values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.getName()); values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, channel.toByteArray()); values.put(TvContract.Channels.COLUMN_DESCRIPTION, channel.getDescription()); + values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.getVideoFormat()); values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, VERSION); + values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, + channel.isRecordingProhibited() ? 1 : 0); if (channelId <= 0) { values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId); @@ -598,13 +610,29 @@ public class ChannelDataManager implements Handler.Callback { } private void checkVersion() { - String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?"; - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, selection, - new String[] {Integer.toString(VERSION)}, null)) { - if (cursor != null && cursor.moveToFirst()) { - // The stored channel data seem outdated. Delete them all. - clearChannels(); + if (PermissionUtils.hasAccessAllEpg(mContext)) { + String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?"; + try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, + CHANNEL_DATA_SELECTION_ARGS, selection, + new String[] {Integer.toString(VERSION)}, null)) { + if (cursor != null && cursor.moveToFirst()) { + // The stored channel data seem outdated. Delete them all. + clearChannels(); + } + } + } else { + try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, + new String[] { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 }, + null, null, null)) { + if (cursor != null) { + while (cursor.moveToNext()) { + int version = cursor.getInt(0); + if (version != VERSION) { + clearChannels(); + break; + } + } + } } } } diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java index a132398f..dc99118a 100644 --- a/src/com/android/tv/tuner/tvinput/EventDetector.java +++ b/src/com/android/tv/tuner/tvinput/EventDetector.java @@ -21,12 +21,12 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.data.PsiData; import com.android.tv.tuner.data.PsipData; +import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import java.util.ArrayList; import java.util.HashSet; @@ -48,10 +48,11 @@ public class EventDetector { // To prevent channel duplication private final Set mVctProgramNumberSet = new HashSet<>(); + private final Set mSdtProgramNumberSet = new HashSet<>(); private final SparseArray mChannelMap = new SparseArray<>(); private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); - private final EventListener mEventListener; + private final List mEventListeners = new ArrayList<>(); private int mFrequency; private String mModulation; private int mProgramNumber = ALL_PROGRAM_NUMBERS; @@ -105,8 +106,10 @@ public class EventDetector { item.setHasCaptionTrack(); } } - if (tunerChannel != null && mEventListener != null) { - mEventListener.onEventDetected(tunerChannel, items); + if (tunerChannel != null && !mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onEventDetected(tunerChannel, items); + } } } @@ -117,8 +120,10 @@ public class EventDetector { @Override public void onAllVctItemsParsed() { - if (mEventListener != null) { - mEventListener.onChannelScanDone(); + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelScanDone(); + } } } @@ -161,8 +166,47 @@ public class EventDetector { if (!found) { mVctProgramNumberSet.add(channelProgramNumber); } - if (mEventListener != null) { - mEventListener.onChannelDetected(tunerChannel, !found); + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelDetected(tunerChannel, !found); + } + } + } + + @Override + public void onSdtItemParsed(PsipData.SdtItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onSdtItemParsed SDT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } + + // Merges the audio and caption tracks located in PMT items into the tracks of the given + // tuner channel. + TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PsiData.PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getServiceId(); + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); + tunerChannel.setFrequency(mFrequency); + tunerChannel.setModulation(mModulation); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mSdtProgramNumberSet.add(channelProgramNumber); + } + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelDetected(tunerChannel, !found); + } } } }; @@ -196,18 +240,23 @@ public class EventDetector { /** * Creates a detector for ATSC TV channles and program information. + * * @param usbTunerInteface {@link TunerHal} - * @param listener for ATSC TV channels and program information */ - public EventDetector(TunerHal usbTunerInteface, EventListener listener) { + public EventDetector(TunerHal usbTunerInteface) { mTunerHal = usbTunerInteface; - mEventListener = listener; } private void reset() { - mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset() + // TODO: Use TsParser.reset() + int deliverySystemType = mTunerHal.getDeliverySystemType(); + mTsParser = + new TsParser( + mTsOutputListener, + TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType())); mPidSet.clear(); mVctProgramNumberSet.clear(); + mSdtProgramNumberSet.clear(); mVctCaptionTracksFound.clear(); mEitCaptionTracksFound.clear(); mChannelMap.clear(); @@ -258,4 +307,28 @@ public class EventDetector { public List getMalFormedChannels() { return mTsParser.getMalFormedChannels(); } + + /** + * Registers an EventListener. + * @param eventListener the listener to be registered + */ + public void registerListener(EventListener eventListener) { + if (mTsParser != null) { + // Resets the version numbers so that the new listener can receive the EIT items. + // Otherwise, each EIT session is handled only once unless there is a new version. + mTsParser.resetDataVersions(); + } + mEventListeners.add(eventListener); + } + + /** + * Unregisters an EventListener. + * @param eventListener the listener to be unregistered + */ + public void unregisterListener(EventListener eventListener) { + boolean removed = mEventListeners.remove(eventListener); + if (!removed && DEBUG) { + Log.d(TAG, "Cannot unregister a non-registered listener!"); + } + } } diff --git a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java index 61de24f4..99222bf8 100644 --- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java +++ b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java @@ -23,10 +23,11 @@ import android.util.SparseBooleanArray; import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.EitItem; +import com.android.tv.tuner.data.PsipData.SdtItem; import com.android.tv.tuner.data.PsipData.VctItem; +import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.source.FileTsStreamer; import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.tvinput.EventDetector.EventListener; @@ -49,15 +50,18 @@ public class FileSourceEventDetector { private TsParser mTsParser; private final Set mVctProgramNumberSet = new HashSet<>(); + private final Set mSdtProgramNumberSet = new HashSet<>(); private final SparseArray mChannelMap = new SparseArray<>(); private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); private final EventListener mEventListener; + private final boolean mEnableDvbSignal; private FileTsStreamer.StreamProvider mStreamProvider; private int mProgramNumber = ALL_PROGRAM_NUMBERS; - public FileSourceEventDetector(EventDetector.EventListener listener) { + public FileSourceEventDetector(EventDetector.EventListener listener, boolean enableDvbSignal) { mEventListener = listener; + mEnableDvbSignal = enableDvbSignal; } /** @@ -74,9 +78,10 @@ public class FileSourceEventDetector { } private void reset() { - mTsParser = new TsParser(mTsOutputListener); // TODO: Use TsParser.reset() + mTsParser = new TsParser(mTsOutputListener, mEnableDvbSignal); // TODO: Use TsParser.reset() mStreamProvider.clearPidFilter(); mVctProgramNumberSet.clear(); + mSdtProgramNumberSet.clear(); mVctCaptionTracksFound.clear(); mEitCaptionTracksFound.clear(); mChannelMap.clear(); @@ -206,5 +211,39 @@ public class FileSourceEventDetector { mEventListener.onChannelDetected(tunerChannel, !found); } } + + @Override + public void onSdtItemParsed(SdtItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onSdtItemParsed SDT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } + + // Merges the audio and caption tracks located in PMT items into the tracks of the given + // tuner channel. + TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getServiceId(); + tunerChannel.setFilepath(mStreamProvider.getFilepath()); + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mSdtProgramNumberSet.add(channelProgramNumber); + } + if (mEventListener != null) { + mEventListener.onChannelDetected(tunerChannel, !found); + } + } }; } diff --git a/src/com/android/tv/tuner/tvinput/TunerDebug.java b/src/com/android/tv/tuner/tvinput/TunerDebug.java index a7a41ea7..2ddc946a 100644 --- a/src/com/android/tv/tuner/tvinput/TunerDebug.java +++ b/src/com/android/tv/tuner/tvinput/TunerDebug.java @@ -55,10 +55,10 @@ public class TunerDebug { return LazyHolder.INSTANCE; } - public static void notifyVideoFrameDrop(long delta) { + public static void notifyVideoFrameDrop(int count, long delta) { // TODO: provide timestamp mismatch information using delta TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mVideoFrameDrop++; + sTunerDebug.mVideoFrameDrop += count; } public static int getVideoFrameDrop() { diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java index 6ec55e4f..34013bf1 100644 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java @@ -33,14 +33,18 @@ import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.util.Log; +import android.util.Pair; +import com.google.android.exoplayer.C; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.recording.RecordingCapability; import com.android.tv.dvr.DvrStorageStatusManager; -import com.android.tv.dvr.RecordedProgram; +import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.tuner.DvbDeviceAccessor; import com.android.tv.tuner.data.PsipData; +import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor; import com.android.tv.tuner.exoplayer.SampleExtractor; import com.android.tv.tuner.exoplayer.buffer.BufferManager; @@ -53,10 +57,10 @@ import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Random; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** @@ -71,6 +75,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private static final String SORT_BY_TIME = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + ", " + TvContract.Programs.COLUMN_CHANNEL_ID + ", " + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; + private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); private static final long PREPARE_RECORDER_POLL_MS = 50; @@ -80,20 +85,23 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private static final int MSG_STOP_RECORDING = 4; private static final int MSG_MONITOR_STORAGE_STATUS = 5; private static final int MSG_RELEASE = 6; + private static final int MSG_UPDATE_CC_INFO = 7; private final RecordingCapability mCapabilities; public RecordingCapability getCapabilities() { return mCapabilities; } - @IntDef({STATE_IDLE, STATE_TUNED, STATE_RECORDING}) + @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING}) @Retention(RetentionPolicy.SOURCE) public @interface DvrSessionState {} private static final int STATE_IDLE = 1; - private static final int STATE_TUNED = 2; - private static final int STATE_RECORDING = 3; + private static final int STATE_TUNING = 2; + private static final int STATE_TUNED = 3; + private static final int STATE_RECORDING = 4; private static final long CHANNEL_ID_NONE = -1; + private static final int MAX_TUNING_RETRY = 6; private final Context mContext; private final ChannelDataManager mChannelDataManager; @@ -108,13 +116,16 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private long mRecordStartTime; private long mRecordEndTime; private boolean mRecorderRunning; - private BufferManager mBufferManager; private SampleExtractor mRecorder; private final TunerRecordingSession mSession; @DvrSessionState private int mSessionState = STATE_IDLE; private final String mInputId; private Uri mProgramUri; + private PsipData.EitItem mCurrenProgram; + private List mCaptionTracks; + private DvrStorageManager mDvrStorageManager; + public TunerRecordingSessionWorker(Context context, String inputId, ChannelDataManager dataManager, TunerRecordingSession session) { mRandom.setSeed(System.nanoTime()); @@ -157,6 +168,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, if (mChannel == null || mChannel.compareTo(channel) != 0) { return; } + mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget(); mChannelDataManager.notifyEventDetected(channel, items); } @@ -178,7 +190,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, @MainThread public void tune(Uri channelUri) { mHandler.removeCallbacksAndMessages(null); - mHandler.obtainMessage(MSG_TUNE, channelUri).sendToTarget(); + mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); } /** @@ -211,11 +223,22 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, switch (msg.what) { case MSG_TUNE: { Uri channelUri = (Uri) msg.obj; + int retryCount = msg.arg1; if (DEBUG) Log.d(TAG, "Tune to " + channelUri); if (doTune(channelUri)) { - mSession.onTuned(channelUri); - } else { - reset(); + if (mSessionState == STATE_TUNED) { + mSession.onTuned(channelUri); + } else { + Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); + if (retryCount < MAX_TUNING_RETRY) { + Message tuneMsg = + mHandler.obtainMessage(MSG_TUNE, retryCount + 1, 0, channelUri); + mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); + } else { + mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); + reset(); + } + } } return true; } @@ -281,6 +304,12 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, mHandler.getLooper().quitSafely(); return true; } + case MSG_UPDATE_CC_INFO: { + Pair> pair = + (Pair>) msg.obj; + updateCaptionTracks(pair.first, pair.second); + return true; + } } return false; } @@ -310,20 +339,17 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, mRecorder.release(); mRecorder = null; } - if (mBufferManager != null) { - mBufferManager.close(); - mBufferManager = null; - } if (mTunerSource != null) { mSourceManager.releaseDataSource(mTunerSource); mTunerSource = null; } + mDvrStorageManager = null; mSessionState = STATE_IDLE; mRecorderRunning = false; } private boolean doTune(Uri channelUri) { - if (mSessionState != STATE_IDLE) { + if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) { mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); Log.e(TAG, "Tuning was requested from wrong status."); return false; @@ -333,6 +359,10 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel); return false; + } else if (mChannel.isRecordingProhibited()) { + mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); + Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel); + return false; } if (!mDvrStorageStatusManager.isStorageSufficient()) { mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); @@ -341,9 +371,9 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this); if (mTunerSource == null) { - mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); - Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); - return false; + // Retry tuning in this case. + mSessionState = STATE_TUNING; + return true; } mSessionState = STATE_TUNED; return true; @@ -365,10 +395,10 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } // Since tuning might be happened a while ago, shifts the start position of tuned source. mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); - mBufferManager = new BufferManager(new DvrStorageManager(mStorageDir, true)); mRecordStartTime = System.currentTimeMillis(); - mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, mBufferManager, this, - true); + mDvrStorageManager = new DvrStorageManager(mStorageDir, true); + mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, + new BufferManager(mDvrStorageManager), this, true); mRecorder.setOnCompletionListener(this, mHandler); mProgramUri = programUri; mSessionState = STATE_RECORDING; @@ -392,6 +422,34 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, Log.i(TAG, "Recording stopped"); } + private void updateCaptionTracks(TunerChannel channel, List items) { + if (mChannel == null || channel == null || mChannel.compareTo(channel) != 0 + || items == null || items.isEmpty()) { + return; + } + PsipData.EitItem currentProgram = getCurrentProgram(items); + if (currentProgram == null || !currentProgram.hasCaptionTrack() + || mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0) { + return; + } + mCurrenProgram = currentProgram; + mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks()); + if (DEBUG) { + Log.d(TAG, "updated " + mCaptionTracks.size() + " caption tracks for " + + currentProgram); + } + } + + private PsipData.EitItem getCurrentProgram(List items) { + for (PsipData.EitItem item : items) { + if (mRecordStartTime >= item.getStartTimeUtcMillis() + && mRecordStartTime < item.getEndTimeUtcMillis()) { + return item; + } + } + return null; + } + private static class Program { private final long mChannelId; private final String mTitle; @@ -566,15 +624,25 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, return; } Log.i(TAG, "recording finished " + (success ? "completely" : "partially")); - Uri uri = insertRecordedProgram(getRecordedProgram(), mChannel.getChannelId(), - Uri.fromFile(mStorageDir).toString(), 1024 * 1024, mRecordStartTime, - mRecordStartTime + TimeUnit.MICROSECONDS.toMillis(lastExtractedPositionUs)); + long recordEndTime = + (lastExtractedPositionUs == C.UNKNOWN_TIME_US) + ? System.currentTimeMillis() + : mRecordStartTime + lastExtractedPositionUs / 1000; + Uri uri = + insertRecordedProgram( + getRecordedProgram(), + mChannel.getChannelId(), + Uri.fromFile(mStorageDir).toString(), + 1024 * 1024, + mRecordStartTime, + recordEndTime); if (uri == null) { new DeleteRecordingTask().execute(mStorageDir); mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); Log.e(TAG, "Inserting a recording to DB failed"); return; } + mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks); mSession.onRecordFinished(uri); } diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java index 5c61402e..44bae908 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSession.java +++ b/src/com/android/tv/tuner/tvinput/TunerSession.java @@ -38,12 +38,12 @@ import android.widget.Toast; import com.google.android.exoplayer.audio.AudioCapabilities; import com.android.tv.tuner.R; +import com.android.tv.tuner.TunerPreferences; +import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener; import com.android.tv.tuner.cc.CaptionLayout; import com.android.tv.tuner.cc.CaptionTrackRenderer; import com.android.tv.tuner.data.Cea708Data.CaptionEvent; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.util.GlobalSettingsUtils; import com.android.tv.tuner.util.StatusTextUtils; import com.android.tv.tuner.util.SystemPropertiesProxy; @@ -52,7 +52,8 @@ import com.android.tv.tuner.util.SystemPropertiesProxy; * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions * are implemented in {@link TunerSessionWorker}. */ -public class TunerSession extends TvInputService.Session implements Handler.Callback { +public class TunerSession extends TvInputService.Session implements + Handler.Callback, TunerPreferencesChangedListener { private static final String TAG = "TunerSession"; private static final boolean DEBUG = false; private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug"; @@ -65,8 +66,9 @@ public class TunerSession extends TvInputService.Session implements Handler.Call public static final int MSG_UI_START_CAPTION_TRACK = 6; public static final int MSG_UI_STOP_CAPTION_TRACK = 7; public static final int MSG_UI_RESET_CAPTION_TRACK = 8; - public static final int MSG_UI_SET_STATUS_TEXT = 9; - public static final int MSG_UI_TOAST_RESCAN_NEEDED = 10; + public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9; + public static final int MSG_UI_SET_STATUS_TEXT = 10; + public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11; private final Context mContext; private final Handler mUiHandler; @@ -81,8 +83,7 @@ public class TunerSession extends TvInputService.Session implements Handler.Call private boolean mPlayPaused; private long mTuneStartTimestamp; - public TunerSession(Context context, ChannelDataManager channelDataManager, - BufferManager bufferManager) { + public TunerSession(Context context, ChannelDataManager channelDataManager) { super(context); mContext = context; mUiHandler = new Handler(this); @@ -97,12 +98,10 @@ public class TunerSession extends TvInputService.Session implements Handler.Call mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE); mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status); mAudioStatusView.setVisibility(View.INVISIBLE); - mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( - context.getString(R.string.ut_surround_sound_disabled)))); CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption); mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout); - mSessionWorker = new TunerSessionWorker(context, channelDataManager, - bufferManager, this); + mSessionWorker = new TunerSessionWorker(context, channelDataManager, this); + TunerPreferences.setTunerPreferencesChangedListener(this); } public boolean isReleased() { @@ -214,6 +213,7 @@ public class TunerSession extends TvInputService.Session implements Handler.Call mReleased = true; mSessionWorker.release(); mUiHandler.removeCallbacksAndMessages(null); + TunerPreferences.setTunerPreferencesChangedListener(null); } /** @@ -272,10 +272,13 @@ public class TunerSession extends TvInputService.Session implements Handler.Call // setting is "never". final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext); if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) { - mAudioStatusView.setVisibility(View.VISIBLE); + mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( + mContext.getString(R.string.ut_surround_sound_disabled)))); } else { - Log.e(TAG, "Audio is unavailable, surround sound setting is " + value); + mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( + mContext.getString(R.string.audio_passthrough_not_supported)))); } + mAudioStatusView.setVisibility(View.VISIBLE); return true; } case MSG_UI_HIDE_AUDIO_UNPLAYABLE: { @@ -298,6 +301,10 @@ public class TunerSession extends TvInputService.Session implements Handler.Call mCaptionTrackRenderer.reset(); return true; } + case MSG_UI_CLEAR_CAPTION_RENDERER: { + mCaptionTrackRenderer.clear(); + return true; + } case MSG_UI_SET_STATUS_TEXT: { mStatusView.setText((CharSequence) msg.obj); return true; @@ -309,4 +316,9 @@ public class TunerSession extends TvInputService.Session implements Handler.Call } return false; } + + @Override + public void onTunerPreferencesChanged() { + mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED); + } } diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java index 5230298e..e7eb017e 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java @@ -27,6 +27,7 @@ import android.media.tv.TvContract; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.net.Uri; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -35,6 +36,7 @@ import android.support.annotation.AnyThread; import android.support.annotation.MainThread; import android.support.annotation.WorkerThread; import android.text.Html; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -45,7 +47,10 @@ import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.ExoPlayer; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvContentRatingCache; +import com.android.tv.customization.TvCustomizationManager; +import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE; import com.android.tv.tuner.TunerPreferences; +import com.android.tv.tuner.TunerPreferences.TrickplaySetting; import com.android.tv.tuner.data.Cea708Data; import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.PsipData.TvTracksInterface; @@ -55,20 +60,23 @@ import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder; import com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager; import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; import com.android.tv.tuner.exoplayer.MpegTsPlayer; +import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; +import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsDataSourceManager; import com.android.tv.tuner.util.StatusTextUtils; +import com.android.tv.tuner.util.SystemPropertiesProxy; import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; /** * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs @@ -82,6 +90,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, private static final boolean DEBUG = false; private static final boolean ENABLE_PROFILER = true; private static final String PLAY_FROM_CHANNEL = "channel"; + private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes"; + private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB + private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB // Public messages public static final int MSG_SELECT_TRACK = 1; @@ -93,6 +104,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7; public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8; public static final int MSG_UNBLOCKED_RATING = 9; + public static final int MSG_TUNER_PREFERENCES_CHANGED = 10; // Private messages private static final int MSG_TUNE = 1000; @@ -147,10 +159,20 @@ public class TunerSessionWorker implements PlaybackBufferListener, private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500; private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20; private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250; + private static final int RELEASE_WAIT_INTERVAL_MS = 50; + private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14); + + // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker + // creation/release is required. + // This is used to guarantee that at most one active TunerSessionWorker exists at any give time. + private static Semaphore sActiveSessionSemaphore = new Semaphore(1); private final Context mContext; private final ChannelDataManager mChannelDataManager; private final TsDataSourceManager mSourceManager; + private final int mMaxTrickplayBufferSizeMb; + private final File mTrickplayBufferDir; + private final @TRICKPLAY_MODE int mTrickplayModeCustomization; private volatile Surface mSurface; private volatile float mVolume = 1.0f; private volatile boolean mCaptionEnabled; @@ -159,6 +181,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, private volatile Long mRecordingDuration; private volatile long mRecordStartTimeMs; private volatile long mBufferStartTimeMs; + private volatile boolean mTrickplayDisabledByStorageIssue; + private @TrickplaySetting int mTrickplaySetting; + private long mTrickplayExpiredMs; private String mRecordingId; private final Handler mHandler; private int mRetryCount; @@ -177,19 +202,20 @@ public class TunerSessionWorker implements PlaybackBufferListener, private TvContentRating mUnblockedContentRating; private long mLastPositionMs; private AudioCapabilities mAudioCapabilities; - private final CountDownLatch mReleaseLatch = new CountDownLatch(1); private long mLastLimitInBytes; - private long mLastPositionInBytes; - private final BufferManager mBufferManager; private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance(); private final TunerSession mSession; + private final boolean mHasSoftwareAudioDecoder; private int mPlayerState = ExoPlayer.STATE_IDLE; private long mPreparingStartTimeMs; private long mBufferingStartTimeMs; private long mReadyStartTimeMs; + private boolean mIsActiveSession; + private boolean mReleaseRequested; // Guarded by mReleaseLock + private final Object mReleaseLock = new Object(); public TunerSessionWorker(Context context, ChannelDataManager channelDataManager, - BufferManager bufferManager, TunerSession tunerSession) { + TunerSession tunerSession) { if (DEBUG) Log.d(TAG, "TunerSessionWorker created"); mContext = context; @@ -211,10 +237,39 @@ public class TunerSessionWorker implements PlaybackBufferListener, (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); mCaptionEnabled = captioningManager.isEnabled(); mPlaybackParams.setSpeed(1.0f); - mBufferManager = bufferManager; + mMaxTrickplayBufferSizeMb = + SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); + mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context); + if (mTrickplayModeCustomization == + TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { + boolean useExternalStorage = + Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && + Environment.isExternalStorageRemovable(); + mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null; + } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { + mTrickplayBufferDir = context.getCacheDir(); + } else { + mTrickplayBufferDir = null; + } + mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null; + mTrickplaySetting = TunerPreferences.getTrickplaySetting(context); + if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET + && mTrickplayModeCustomization + == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { + // Consider the case of Customization package updates the value of trickplay mode + // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install. + mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET; + TunerPreferences.setTrickplaySetting(context, mTrickplaySetting); + TunerPreferences.setTrickplayExpiredMs(context, 0); + } + mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context); mPreparingStartTimeMs = INVALID_TIME; mBufferingStartTimeMs = INVALID_TIME; mReadyStartTimeMs = INVALID_TIME; + // NOTE: We assume that TunerSessionWorker instance will be at most one. + // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time. + // connect() will return false, if there is a connected TunerSessionWorker already. + mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context); } // Public methods @@ -285,24 +340,21 @@ public class TunerSessionWorker implements PlaybackBufferListener, } private Long getDurationForRecording(String recordingId) { - try { - DvrStorageManager storageManager = + DvrStorageManager storageManager = new DvrStorageManager(new File(getRecordingPath()), false); - Pair trackInfo = null; - try { - trackInfo = storageManager.readTrackInfoFile(false); - } catch (FileNotFoundException e) { - } - if (trackInfo == null) { - trackInfo = storageManager.readTrackInfoFile(true); - } - Long durationUs = trackInfo.second.getLong(MediaFormat.KEY_DURATION); + List trackFormatList = + storageManager.readTrackInfoFiles(false); + if (trackFormatList.isEmpty()) { + trackFormatList = storageManager.readTrackInfoFiles(true); + } + if (!trackFormatList.isEmpty()) { + BufferManager.TrackFormat trackFormat = trackFormatList.get(0); + Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION); // we need duration by milli for trickplay notification. return durationUs != null ? durationUs / 1000 : null; - } catch (IOException e) { - Log.e(TAG, "meta file for recording was not found: " + recordingId); - return null; } + Log.e(TAG, "meta file for recording was not found: " + recordingId); + return null; } @MainThread @@ -341,16 +393,15 @@ public class TunerSessionWorker implements PlaybackBufferListener, @MainThread public void release() { if (DEBUG) Log.d(TAG, "release()"); + synchronized (mReleaseLock) { + mReleaseRequested = true; + } + if (mHasSoftwareAudioDecoder) { + FfmpegDecoderClient.disconnect(mContext); + } mChannelDataManager.setListener(null); mHandler.removeCallbacksAndMessages(null); mHandler.sendEmptyMessage(MSG_RELEASE); - try { - mReleaseLatch.await(); - } catch (InterruptedException e) { - Log.e(TAG, "Couldn't wait for finish of MSG_RELEASE", e); - } finally { - mHandler.getLooper().quitSafely(); - } } // MpegTsPlayer.Listener @@ -367,7 +418,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (playbackState == ExoPlayer.STATE_READY) { if (DEBUG) Log.d(TAG, "ExoPlayer ready"); if (!mPlayerStarted) { - sendMessage(MSG_START_PLAYBACK, mPlayer); + sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer)); } mReadyStartTimeMs = SystemClock.elapsedRealtime(); } else if (playbackState == ExoPlayer.STATE_PREPARING) { @@ -379,7 +430,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards. Log.i(TAG, "Player ended: end of stream"); if (mChannel != null) { - sendMessage(MSG_RETRY_PLAYBACK, mPlayer); + sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); } } mPlayerState = playbackState; @@ -397,7 +448,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, // If we are playing live stream, retrying playback maybe helpful. But for recorded stream, // retrying playback is not helpful. if (mChannel != null) { - mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer).sendToTarget(); + mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)) + .sendToTarget(); } } @@ -415,8 +467,12 @@ public class TunerSessionWorker implements PlaybackBufferListener, public void onDrawnToSurface(MpegTsPlayer player, Surface surface) { if (mSurface != null && mPlayerStarted) { if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE"); - mBufferStartTimeMs = mRecordStartTimeMs = - (mRecordingId != null) ? 0 : System.currentTimeMillis(); + if (mRecordingId != null) { + // Workaround of b/33298048: set it to 1 instead of 0. + mBufferStartTimeMs = mRecordStartTimeMs = 1; + } else { + mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); + } notifyVideoAvailable(); mReportedDrawnToSurface = true; @@ -460,6 +516,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event); } + @Override + public void onClearCaptionEvent() { + mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER); + } + @Override public void onDiscoverCaptionServiceNumber(int serviceNumber) { sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber); @@ -499,7 +560,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, @Override public void onDiskTooSlow() { - sendMessage(MSG_RETRY_PLAYBACK, mPlayer); + mTrickplayDisabledByStorageIssue = true; + sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); } // EventDetector.EventListener @@ -602,6 +664,28 @@ public class TunerSessionWorker implements PlaybackBufferListener, return true; } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + if (!mIsActiveSession) { + // Wait until release is finished if there is a pending release. + try { + while (!sActiveSessionSemaphore.tryAcquire( + RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) { + synchronized (mReleaseLock) { + if (mReleaseRequested) { + return true; + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + synchronized (mReleaseLock) { + if (mReleaseRequested) { + sActiveSessionSemaphore.release(); + return true; + } + } + mIsActiveSession = true; + } Uri channelUri = (Uri) msg.obj; String recording = null; long channelId = parseChannel(channelUri); @@ -616,7 +700,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); return true; } - mHandler.removeCallbacksAndMessages(null); + clearCallbacksAndMessagesSafely(); + mChannelDataManager.removeAllCallbacksAndMessages(); if (channel != null) { mChannelDataManager.requestProgramsData(channel); } @@ -624,8 +709,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, // TODO: Need to refactor. notifyContentAllowed() should not be called if parental // control is turned on. mSession.notifyContentAllowed(); - resetPlayback(); resetTvTracks(); + resetPlayback(); mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); return true; @@ -633,7 +718,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, case MSG_STOP_TUNE: { if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); mChannel = null; - stopPlayback(); + stopPlayback(true); stopCaptionTrack(); resetTvTracks(); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); @@ -642,14 +727,17 @@ public class TunerSessionWorker implements PlaybackBufferListener, case MSG_RELEASE: { if (DEBUG) Log.d(TAG, "MSG_RELEASE"); mHandler.removeCallbacksAndMessages(null); - stopPlayback(); + stopPlayback(true); stopCaptionTrack(); mSourceManager.release(); - mReleaseLatch.countDown(); + mHandler.getLooper().quitSafely(); + if (mIsActiveSession) { + sActiveSessionSemaphore.release(); + } return true; } case MSG_RETRY_PLAYBACK: { - if (mPlayer == msg.obj) { + if (System.identityHashCode(mPlayer) == (int) msg.obj) { Log.i(TAG, "Retrying the playback for channel: " + mChannel); mHandler.removeMessages(MSG_RETRY_PLAYBACK); // When there is a request of retrying playback, don't reuse TunerHal. @@ -658,16 +746,18 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (DEBUG) { Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount); } + mChannelDataManager.removeAllCallbacksAndMessages(); if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) { resetPlayback(); } else { // When it reaches this point, it may be due to an error that occurred in // the tuner device. Calling stopPlayback() resets the tuner device // to recover from the error. - stopPlayback(); + stopPlayback(false); stopCaptionTrack(); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); + Log.i(TAG, "Notify weak signal since fail to retry playback"); // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen // value before recovering the playback. @@ -679,13 +769,14 @@ public class TunerSessionWorker implements PlaybackBufferListener, } case MSG_RESET_PLAYBACK: { if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK"); + mChannelDataManager.removeAllCallbacksAndMessages(); resetPlayback(); return true; } case MSG_START_PLAYBACK: { if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK"); if (mChannel != null || mRecordingId != null) { - startPlayback(msg.obj); + startPlayback((int) msg.obj); } return true; } @@ -790,7 +881,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, return true; } case MSG_RESCHEDULE_PROGRAMS: { - doReschedulePrograms(); + if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { + mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); + } else { + doReschedulePrograms(); + } return true; } case MSG_PARENTAL_CONTROLS: { @@ -814,11 +909,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, return true; } case MSG_SELECT_TRACK: { - if (mChannel != null) { + if (mChannel != null || mRecordingId != null) { doSelectTrack(msg.arg1, (String) msg.obj); - } else if (mRecordingId != null) { - // TODO : mChannel == null && mRecordingId != null - Log.d(TAG, "track selected for recording"); } return true; } @@ -835,6 +927,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mPlayer == null) { return true; } + setTrickplayEnabledIfNeeded(); doTimeShiftPause(); return true; } @@ -843,6 +936,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mPlayer == null) { return true; } + setTrickplayEnabledIfNeeded(); doTimeShiftResume(); return true; } @@ -852,6 +946,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mPlayer == null) { return true; } + setTrickplayEnabledIfNeeded(); doTimeShiftSeekTo(position); return true; } @@ -859,6 +954,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mPlayer == null) { return true; } + setTrickplayEnabledIfNeeded(); doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj); return true; } @@ -883,6 +979,22 @@ public class TunerSessionWorker implements PlaybackBufferListener, } return true; } + case MSG_TUNER_PREFERENCES_CHANGED: { + mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED); + @TrickplaySetting int trickplaySetting = + TunerPreferences.getTrickplaySetting(mContext); + if (trickplaySetting != mTrickplaySetting) { + boolean wasTrcikplayEnabled = + mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + boolean isTrickplayEnabled = + trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + mTrickplaySetting = trickplaySetting; + if (isTrickplayEnabled != wasTrcikplayEnabled) { + sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer)); + } + } + return true; + } case MSG_BUFFER_START_TIME_CHANGED: { if (mPlayer == null) { return true; @@ -891,7 +1003,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (!hasEnoughBackwardBuffer() && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) { mPlayer.setPlayWhenReady(true); - mPlayer.setAudioTrack(true); + mPlayer.setAudioTrackAndClosedCaption(true); mPlaybackParams.setSpeed(1.0f); } return true; @@ -909,7 +1021,6 @@ public class TunerSessionWorker implements PlaybackBufferListener, } TsDataSource source = mPlayer.getDataSource(); long limitInBytes = source != null ? source.getBufferedPosition() : 0L; - long positionInBytes = source != null ? source.getLastReadPosition() : 0L; if (TunerDebug.ENABLED) { TunerDebug.calculateDiff(); mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT, @@ -927,32 +1038,36 @@ public class TunerSessionWorker implements PlaybackBufferListener, TunerDebug.getVideoPtsUsRate() ))); } - if (DEBUG) { - Log.d(TAG, String.format("MSG_CHECK_SIGNAL position: %d, limit: %d", - positionInBytes, limitInBytes)); - } mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); long currentTime = SystemClock.elapsedRealtime(); - boolean noBufferRead = positionInBytes == mLastPositionInBytes - && limitInBytes == mLastLimitInBytes; - boolean isBufferingTooLong = mBufferingStartTimeMs != INVALID_TIME - && currentTime - mBufferingStartTimeMs - > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - boolean isPreparingTooLong = mPreparingStartTimeMs != INVALID_TIME - && currentTime - mPreparingStartTimeMs - > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; + long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME + ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs; + long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME + ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs; + boolean isBufferingTooLong = + bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; + boolean isPreparingTooLong = + preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; boolean isWeakSignal = source != null - && mChannel.getType() == Channel.TYPE_TUNER - && (noBufferRead || isBufferingTooLong || isPreparingTooLong); + && mChannel.getType() != Channel.TYPE_FILE + && (isBufferingTooLong || isPreparingTooLong); if (isWeakSignal && !mReportedWeakSignal) { if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) { - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_RETRY_PLAYBACK, mPlayer), PLAYBACK_RETRY_DELAY_MS); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, + System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); } if (mPlayer != null) { - mPlayer.setAudioTrack(false); + mPlayer.setAudioTrackAndClosedCaption(false); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); + Log.i(TAG, "Notify weak signal due to signal check, " + String.format( + "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " + + "videoFrameDrop:%d", + (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, + bufferingTimeMs, + preparingTimeMs, + TunerDebug.getVideoFrameDrop() + )); } else if (!isWeakSignal && mReportedWeakSignal) { boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME && currentTime - mReadyStartTimeMs @@ -962,11 +1077,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, } else if (mReportedDrawnToSurface) { mHandler.removeMessages(MSG_RETRY_PLAYBACK); notifyVideoAvailable(); - mPlayer.setAudioTrack(true); + mPlayer.setAudioTrackAndClosedCaption(true); } } mLastLimitInBytes = limitInBytes; - mLastPositionInBytes = positionInBytes; mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); return true; } @@ -999,15 +1113,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (trackId == null) { return; } - AtscAudioTrack audioTrack = mAudioTrackMap.get(numTrackId); - if (audioTrack == null) { - return; - } - int oldAudioPid = mChannel.getAudioPid(); - mChannel.selectAudioTrack(audioTrack.index); - int newAudioPid = mChannel.getAudioPid(); - if (oldAudioPid != newAudioPid) { - mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, audioTrack.index); + if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) { + mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId); } mSession.notifyTrackSelected(type, trackId); } else if (type == TvTrackInfo.TYPE_SUBTITLE) { @@ -1030,11 +1137,49 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - private MpegTsPlayer createPlayer(AudioCapabilities capabilities, BufferManager bufferManager) { + private void setTrickplayEnabledIfNeeded() { + if (mChannel == null || + mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { + return; + } + if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { + mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED; + TunerPreferences.setTrickplaySetting( + mContext, mTrickplaySetting); + } + } + + private MpegTsPlayer createPlayer(AudioCapabilities capabilities) { if (capabilities == null) { Log.w(TAG, "No Audio Capabilities"); } - + long now = System.currentTimeMillis(); + if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED + && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { + if (mTrickplayExpiredMs == 0) { + mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS; + TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs); + } else { + if (mTrickplayExpiredMs < now) { + mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED; + TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting); + } + } + } + BufferManager bufferManager = null; + if (mRecordingId != null) { + StorageManager storageManager = + new DvrStorageManager(new File(getRecordingPath()), false); + bufferManager = new BufferManager(storageManager); + updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles()); + } else if (!mTrickplayDisabledByStorageIssue + && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED + && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { + bufferManager = new BufferManager(new TrickplayStorageManager(mContext, + mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb)); + } else { + Log.w(TAG, "Trickplay is disabled."); + } MpegTsPlayer player = new MpegTsPlayer( new MpegTsRendererBuilder(mContext, bufferManager, this), mHandler, mSourceManager, capabilities, this); @@ -1069,24 +1214,26 @@ public class TunerSessionWorker implements PlaybackBufferListener, } private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) { - if (DEBUG) { - Log.d(TAG, "UpdateTvTracks " + tvTracksInterface); - } - List audioTracks = tvTracksInterface.getAudioTracks(); - List captionTracks = tvTracksInterface.getCaptionTracks(); - // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio - // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio - // track info in PMT more and use info in EIT only when we have nothing. - if (audioTracks != null && !audioTracks.isEmpty() - && (mChannel.getAudioTracks() == null || fromPmt)) { - updateAudioTracks(audioTracks); - } - if (captionTracks == null || captionTracks.isEmpty()) { - if (tvTracksInterface.hasCaptionTrack()) { + synchronized (tvTracksInterface) { + if (DEBUG) { + Log.d(TAG, "UpdateTvTracks " + tvTracksInterface); + } + List audioTracks = tvTracksInterface.getAudioTracks(); + List captionTracks = tvTracksInterface.getCaptionTracks(); + // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio + // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio + // track info in PMT more and use info in EIT only when we have nothing. + if (audioTracks != null && !audioTracks.isEmpty() + && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) { + updateAudioTracks(audioTracks); + } + if (captionTracks == null || captionTracks.isEmpty()) { + if (tvTracksInterface.hasCaptionTrack()) { + updateCaptionTracks(captionTracks); + } + } else { updateCaptionTracks(captionTracks); } - } else { - updateCaptionTracks(captionTracks); } } @@ -1132,25 +1279,24 @@ public class TunerSessionWorker implements PlaybackBufferListener, int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO); removeTvTracks(TvTrackInfo.TYPE_AUDIO); for (int i = 0; i < audioTrackCount; i++) { - AtscAudioTrack audioTrack = mAudioTrackMap.get(i); - if (audioTrack == null) { - continue; - } - String language = audioTrack.language; - if (language == null && mChannel.getAudioTracks() != null - && mChannel.getAudioTracks().size() == mAudioTrackMap.size()) { - // If a language is not present, use a language field in PMT section parsed. - language = mChannel.getAudioTracks().get(i).language; - } - // Save the index to the audio track. - // Later, when an audio track is selected, both the audio pid and its audio stream - // type reside in the selected index position of the tuner channel's audio data. - audioTrack.index = i; + // We use language information from EIT/VCT only when the player does not provide + // languages. + com.google.android.exoplayer.MediaFormat infoFromPlayer = + mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i); + AtscAudioTrack infoFromEit = mAudioTrackMap.get(i); + AtscAudioTrack infoFromVct = (mChannel != null + && mChannel.getAudioTracks().size() == mAudioTrackMap.size() + && i < mChannel.getAudioTracks().size()) + ? mChannel.getAudioTracks().get(i) : null; + String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language + : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language + : (infoFromVct != null && infoFromVct.language != null) + ? infoFromVct.language : null; TvTrackInfo.Builder builder = new TvTrackInfo.Builder( TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); builder.setLanguage(language); - builder.setAudioChannelCount(audioTrack.channelCount); - builder.setAudioSampleRate(audioTrack.sampleRate); + builder.setAudioChannelCount(infoFromPlayer.channelCount); + builder.setAudioSampleRate(infoFromPlayer.sampleRate); TvTrackInfo track = builder.build(); mTvTracks.add(track); } @@ -1226,8 +1372,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - private void stopPlayback() { - mChannelDataManager.removeAllCallbacksAndMessages(); + private void stopPlayback(boolean removeChannelDataCallbacks) { + if (removeChannelDataCallbacks) { + mChannelDataManager.removeAllCallbacksAndMessages(); + } if (mPlayer != null) { mPlayer.setPlayWhenReady(false); mPlayer.release(); @@ -1239,14 +1387,15 @@ public class TunerSessionWorker implements PlaybackBufferListener, mPreparingStartTimeMs = INVALID_TIME; mBufferingStartTimeMs = INVALID_TIME; mReadyStartTimeMs = INVALID_TIME; + mLastLimitInBytes = 0L; mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE); mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); } } - private void startPlayback(Object playerObj) { + private void startPlayback(int playerHashCode) { // TODO: provide hasAudio()/hasVideo() for play recordings. - if (mPlayer == null || mPlayer != playerObj) { + if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) { return; } if (mChannel != null && !mChannel.hasAudio()) { @@ -1257,9 +1406,12 @@ public class TunerSessionWorker implements PlaybackBufferListener, return; } if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio()) - || (mChannel.hasVideo() && !mPlayer.hasVideo()))) { - // Tracks haven't been detected in the extractor. Try again. - sendMessage(MSG_RETRY_PLAYBACK, mPlayer); + || (mChannel.hasVideo() && !mPlayer.hasVideo())) + && mChannel.getType() != Channel.TYPE_NETWORK) { + // If the channel is from network, skip this part since the video and audio tracks + // information for channels from network are more reliable in the extractor. Otherwise, + // tracks haven't been detected in the extractor. Try again. + sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); return; } // Since mSurface is volatile, we define a local variable surface to keep the same value @@ -1269,7 +1421,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, mPlayer.setSurface(surface); mPlayer.setPlayWhenReady(true); mPlayer.setVolume(mVolume); - if (mChannel != null && !mChannel.hasVideo() && mChannel.hasAudio()) { + if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) { notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY); } else if (!mReportedWeakSignal) { // Doesn't show buffering during weak signal. @@ -1286,22 +1438,21 @@ public class TunerSessionWorker implements PlaybackBufferListener, return; } mSourceManager.setKeepTuneStatus(true); - BufferManager bufferManager = mChannel != null ? mBufferManager : new BufferManager( - new DvrStorageManager(new File(getRecordingPath()), false)); - MpegTsPlayer player = createPlayer(mAudioCapabilities, bufferManager); + MpegTsPlayer player = createPlayer(mAudioCapabilities); player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); player.setVideoEventListener(this); player.setCaptionServiceNumber(mCaptionTrack != null ? mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER); - if (!player.prepare(mContext, mChannel, this)) { + if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) { mSourceManager.setKeepTuneStatus(false); player.release(); if (!mHandler.hasMessages(MSG_TUNE)) { // When prepare failed, there may be some errors related to hardware. In that // case, retry playback immediately may not help. notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer), - PLAYBACK_RETRY_DELAY_MS); + Log.i(TAG, "Notify weak signal due to player preparation failure"); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, + System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); } } else { mPlayer = player; @@ -1314,7 +1465,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void resetPlayback() { long timestamp, oldTimestamp; timestamp = SystemClock.elapsedRealtime(); - stopPlayback(); + stopPlayback(false); stopCaptionTrack(); if (ENABLE_PROFILER) { oldTimestamp = timestamp; @@ -1336,8 +1487,12 @@ public class TunerSessionWorker implements PlaybackBufferListener, mRecordingDuration = recording != null ? getDurationForRecording(recording) : null; mProgram = null; mPrograms = null; - mBufferStartTimeMs = mRecordStartTimeMs = - (mRecordingId != null) ? 0 : System.currentTimeMillis(); + if (mRecordingId != null) { + // Workaround of b/33298048: set it to 1 instead of 0. + mBufferStartTimeMs = mRecordStartTimeMs = 1; + } else { + mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); + } mLastPositionMs = 0; mCaptionTrack = null; mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); @@ -1385,14 +1540,19 @@ public class TunerSessionWorker implements PlaybackBufferListener, } else { mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs); mPlaybackParams.setSpeed(1.0f); - mPlayer.setAudioTrack(true); + mPlayer.setAudioTrackAndClosedCaption(true); return; } } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) { - mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs); - mPlaybackParams.setSpeed(1.0f); - mPlayer.setAudioTrack(true); - return; + // Stops trickplay when FF requested the position later than current position. + // If RW trickplay requested the position later than current position, + // continue trickplay. + if (mPlaybackParams.getSpeed() > 0.0f) { + mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs); + mPlaybackParams.setSpeed(1.0f); + mPlayer.setAudioTrackAndClosedCaption(true); + return; + } } long delayForNextSeek = getTrickPlaySeekIntervalMs(); @@ -1414,7 +1574,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, } mPlaybackParams.setSpeed(1.0f); mPlayer.setPlayWhenReady(false); - mPlayer.setAudioTrack(true); + mPlayer.setAudioTrackAndClosedCaption(true); } private void doTimeShiftResume() { @@ -1422,7 +1582,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); mPlaybackParams.setSpeed(1.0f); mPlayer.setPlayWhenReady(true); - mPlayer.setAudioTrack(true); + mPlayer.setAudioTrackAndClosedCaption(true); } private void doTimeShiftSeekTo(long timeMs) { @@ -1443,14 +1603,14 @@ public class TunerSessionWorker implements PlaybackBufferListener, doTimeShiftResume(); } else if (mPlayer.supportSmoothTrickPlay(speed)) { mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - mPlayer.setAudioTrack(false); + mPlayer.setAudioTrackAndClosedCaption(false); mPlayer.startSmoothTrickplay(mPlaybackParams); mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS); } else { mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) { - mPlayer.setAudioTrack(false); + mPlayer.setAudioTrackAndClosedCaption(false); mPlayer.setPlayWhenReady(false); // Initiate trickplay mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK, @@ -1525,8 +1685,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, } TvContentRating[] ratings = mTvContentRatingCache .getRatings(currentProgram.getContentRating()); - if (ratings == null) { - return null; + if (ratings == null || ratings.length == 0) { + ratings = new TvContentRating[] {TvContentRating.UNRATED}; } for (TvContentRating rating : ratings) { if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager @@ -1544,15 +1704,15 @@ public class TunerSessionWorker implements PlaybackBufferListener, } mChannelBlocked = channelBlocked; if (mChannelBlocked) { - mHandler.removeCallbacksAndMessages(null); - stopPlayback(); + clearCallbacksAndMessagesSafely(); + stopPlayback(true); resetTvTracks(); if (contentRating != null) { mSession.notifyContentBlocked(contentRating); } mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); } else { - mHandler.removeCallbacksAndMessages(null); + clearCallbacksAndMessagesSafely(); resetPlayback(); mSession.notifyContentAllowed(); mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, @@ -1562,6 +1722,17 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } + @WorkerThread + private void clearCallbacksAndMessagesSafely() { + // If MSG_RELEASE is removed, TunerSessionWorker will hang forever. + // Do not remove messages, after release is requested from MainThread. + synchronized (mReleaseLock) { + if (!mReleaseRequested) { + mHandler.removeCallbacksAndMessages(null); + } + } + } + private boolean hasEnoughBackwardBuffer() { return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS >= mBufferStartTimeMs - mRecordStartTimeMs; diff --git a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java index e734b779..6ad00daa 100644 --- a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java +++ b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java @@ -24,6 +24,7 @@ import android.database.Cursor; import android.media.tv.TvContract; import android.net.Uri; import android.os.AsyncTask; +import android.util.Log; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrStorageStatusManager; @@ -40,10 +41,17 @@ import java.util.concurrent.TimeUnit; * from database. */ public class TunerStorageCleanUpService extends JobService { + private static final String TAG = "TunerStorageCleanUpService"; + private CleanUpStorageTask mTask; @Override public void onCreate() { + if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + this.stopSelf(); + return; + } TvApplication.setCurrentRunningProcess(this, false); super.onCreate(); mTask = new CleanUpStorageTask(this, this); diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java index 684ebdbd..2725ddfc 100644 --- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java +++ b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java @@ -28,9 +28,6 @@ import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; import com.android.tv.TvApplication; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; -import com.android.tv.tuner.util.SystemPropertiesProxy; import java.util.Collections; import java.util.Set; @@ -45,9 +42,6 @@ public class TunerTvInputService extends TvInputService private static final String TAG = "TunerTvInputService"; private static final boolean DEBUG = false; - private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes"; - private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB - private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100; // WeakContainer for {@link TvInputSessionImpl} @@ -55,17 +49,20 @@ public class TunerTvInputService extends TvInputService private ChannelDataManager mChannelDataManager; private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; private AudioCapabilities mAudioCapabilities; - private BufferManager mBufferManager; @Override public void onCreate() { + if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) { + Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); + this.stopSelf(); + return; + } TvApplication.setCurrentRunningProcess(this, false); super.onCreate(); if (DEBUG) Log.d(TAG, "onCreate"); mChannelDataManager = new ChannelDataManager(getApplicationContext()); mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this); mAudioCapabilitiesReceiver.register(); - mBufferManager = createBufferManager(); if (CommonFeatures.DVR.isEnabled(this)) { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -79,11 +76,6 @@ public class TunerTvInputService extends TvInputService jobScheduler.schedule(job); } } - if (mBufferManager == null) { - Log.i(TAG, "Trickplay is disabled"); - } else { - Log.i(TAG, "Trickplay is enabled"); - } } @Override @@ -92,9 +84,6 @@ public class TunerTvInputService extends TvInputService super.onDestroy(); mChannelDataManager.release(); mAudioCapabilitiesReceiver.unregister(); - if (mBufferManager != null) { - mBufferManager.close(); - } } @Override @@ -106,8 +95,7 @@ public class TunerTvInputService extends TvInputService public Session onCreateSession(String inputId) { if (DEBUG) Log.d(TAG, "onCreateSession"); try { - final TunerSession session = new TunerSession( - this, mChannelDataManager, mBufferManager); + final TunerSession session = new TunerSession(this, mChannelDataManager); mTunerSessions.add(session); session.setAudioCapabilities(mAudioCapabilities); session.setOverlayViewEnabled(true); @@ -129,17 +117,6 @@ public class TunerTvInputService extends TvInputService } } - private BufferManager createBufferManager() { - int maxBufferSizeMb = - SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); - if (maxBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { - return new BufferManager( - new TrickplayStorageManager(getApplicationContext(), getCacheDir(), - 1024L * 1024 * maxBufferSizeMb)); - } - return null; - } - public static String getInputId(Context context) { return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class)); } diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/src/com/android/tv/tuner/util/PostalCodeUtils.java new file mode 100644 index 00000000..9eb689a7 --- /dev/null +++ b/src/com/android/tv/tuner/util/PostalCodeUtils.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tuner.util; + +import android.content.Context; +import android.location.Address; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.tuner.TunerPreferences; +import com.android.tv.util.LocationUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * A utility class to update, get, and set the last known postal or zip code. + */ +public class PostalCodeUtils { + private static final String TAG = "PostalCodeUtils"; + + // Postcode formats, where A signifies a letter and 9 a digit: + // US zip code format: 99999 + private static final String POSTCODE_REGEX_US = "^(\\d{5})"; + // UK postcode district formats: A9, A99, AA9, AA99 + // Full UK postcode format: Postcode District + space + 9AA + // Should be able to handle both postcode district and full postcode + private static final String POSTCODE_REGEX_GB = + "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$"; + private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode + + private static final Map REGION_PATTERN = new HashMap<>(); + private static final Map REGION_MAX_LENGTH = new HashMap<>(); + + static { + REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US)); + REGION_PATTERN.put( + Locale.UK.getCountry(), + Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR)); + REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5); + REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8); + } + + // The longest postcode number is 10-character-long. + // Use a larger number to accommodate future changes. + private static final int DEFAULT_MAX_LENGTH = 16; + + /** Returns {@code true} if postal code has been changed */ + public static boolean updatePostalCode(Context context) + throws IOException, SecurityException, NoPostalCodeException { + String postalCode = getPostalCode(context); + String lastPostalCode = getLastPostalCode(context); + if (TextUtils.isEmpty(postalCode)) { + if (TextUtils.isEmpty(lastPostalCode)) { + throw new NoPostalCodeException(); + } + } else if (!TextUtils.equals(postalCode, lastPostalCode)) { + setLastPostalCode(context, postalCode); + return true; + } + return false; + } + + /** + * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or + * input by users. + */ + public static String getLastPostalCode(Context context) { + return TunerPreferences.getLastPostalCode(context); + } + + /** + * Sets the last stored postal or zip code. This method will overwrite the value written by + * calling {@link #updatePostalCode(Context)}. + */ + public static void setLastPostalCode(Context context, String postalCode) { + Log.i(TAG, "Set Postal Code:" + postalCode); + TunerPreferences.setLastPostalCode(context, postalCode); + } + + @Nullable + private static String getPostalCode(Context context) throws IOException, SecurityException { + Address address = LocationUtils.getCurrentAddress(context); + if (address != null) { + Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", " + + address.getPostalCode()); + return address.getPostalCode(); + } + return null; + } + + /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ + public static class NoPostalCodeException extends Exception { + public NoPostalCodeException() { + } + } + + /** + * Checks whether a postcode matches the format of the specific region. + * + * @return {@code false} if the region is supported and the postcode doesn't match; {@code true} + * otherwise + */ + public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) { + Pattern pattern = REGION_PATTERN.get(region.toUpperCase()); + return pattern == null || pattern.matcher(postcode).matches(); + } + + /** + * Gets the largest possible postcode length in the region. + * + * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH} + * otherwise + */ + public static int getRegionMaxLength(Context context) { + Integer maxLength = + REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); + return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; + } +} \ No newline at end of file diff --git a/src/com/android/tv/tuner/util/StringUtils.java b/src/com/android/tv/tuner/util/StringUtils.java deleted file mode 100644 index 15571e75..00000000 --- a/src/com/android/tv/tuner/util/StringUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -/** - * Utility class for handling {@link String}. - */ -public final class StringUtils { - - private StringUtils() { } - - /** - * Returns compares two strings lexicographically and handles null values quietly. - */ - public static int compare(String a, String b) { - if (a == null) { - return b == null ? 0 : -1; - } - if (b == null) { - return 1; - } - return a.compareTo(b); - } -} diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java index 62a64361..2817ccbf 100644 --- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java +++ b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java @@ -58,4 +58,20 @@ public class SystemPropertiesProxy { } return def; } + + public static String getString(String key, String def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getIntMethod = + SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class); + getIntMethod.setAccessible(true); + return (String) getIntMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.get()", e); + } + return def; + } } diff --git a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java index 5c411f64..f421bf1a 100644 --- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java +++ b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java @@ -21,10 +21,11 @@ import android.content.ComponentName; import android.content.Context; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; +import android.os.AsyncTask; import android.os.Build; import android.support.annotation.Nullable; -import android.support.v4.os.BuildCompat; import android.util.Log; +import android.util.Pair; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.tuner.R; @@ -43,23 +44,31 @@ public class TunerInputInfoUtils { */ @Nullable @TargetApi(Build.VERSION_CODES.N) - public static TvInputInfo buildTunerInputInfo(Context context, boolean fromBuiltInTuner) { - int numOfDevices = TunerHal.getTunerCount(context); - if (numOfDevices == 0) { + public static TvInputInfo buildTunerInputInfo(Context context) { + Pair tunerTypeAndCount = TunerHal.getTunerTypeAndCount(context); + if (tunerTypeAndCount.first == null || tunerTypeAndCount.second == 0) { return null; } - TvInputInfo.Builder builder = new TvInputInfo.Builder(context, new ComponentName(context, - TunerTvInputService.class)); - if (fromBuiltInTuner) { - builder.setLabel(R.string.bt_app_name); - } else { - builder.setLabel(R.string.ut_app_name); + int inputLabelId = 0; + switch (tunerTypeAndCount.first) { + case TunerHal.TUNER_TYPE_BUILT_IN: + inputLabelId = R.string.bt_app_name; + break; + case TunerHal.TUNER_TYPE_USB: + inputLabelId = R.string.ut_app_name; + break; + case TunerHal.TUNER_TYPE_NETWORK: + inputLabelId = R.string.nt_app_name; + break; } try { - return builder.setCanRecord(CommonFeatures.DVR.isEnabled(context)) - .setTunerCount(numOfDevices) + TvInputInfo.Builder builder = new TvInputInfo.Builder(context, + new ComponentName(context, TunerTvInputService.class)); + return builder.setLabel(inputLabelId) + .setCanRecord(CommonFeatures.DVR.isEnabled(context)) + .setTunerCount(tunerTypeAndCount.second) .build(); - } catch (NullPointerException e) { + } catch (IllegalArgumentException | NullPointerException e) { // TunerTvInputService is not enabled. return null; } @@ -71,30 +80,36 @@ public class TunerInputInfoUtils { * @param context {@link Context} instance */ public static void updateTunerInputInfo(Context context) { - if (BuildCompat.isAtLeastN()) { - if (DEBUG) Log.d(TAG, "updateTunerInputInfo()"); - TvInputInfo info = buildTunerInputInfo(context, isBuiltInTuner(context)); - if (info != null) { - ((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE)) - .updateTvInputInfo(info); - if (DEBUG) { - Log.d(TAG, "TvInputInfo [" + info.loadLabel(context) - + "] updated: " + info.toString()); + final Context appContext = context.getApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + new AsyncTask() { + @Override + protected TvInputInfo doInBackground(Void... params) { + if (DEBUG) Log.d(TAG, "updateTunerInputInfo()"); + return buildTunerInputInfo(appContext); } - } else { - if (DEBUG) { - Log.d(TAG, "Updating tuner input's info failed. Input is not ready yet."); + + @Override + @TargetApi(Build.VERSION_CODES.N) + protected void onPostExecute(TvInputInfo info) { + if (info != null) { + ((TvInputManager) appContext.getSystemService(Context.TV_INPUT_SERVICE)) + .updateTvInputInfo(info); + if (DEBUG) { + Log.d( + TAG, + "TvInputInfo [" + + info.loadLabel(appContext) + + "] updated: " + + info.toString()); + } + } else { + if (DEBUG) { + Log.d(TAG, "Updating tuner input info failed. Input is not ready yet."); + } + } } - } + }.execute(); } } - - /** - * Returns if the current tuner service is for a built-in tuner. - * - * @param context {@link Context} instance - */ - public static boolean isBuiltInTuner(Context context) { - return TunerHal.getTunerType(context) == TunerHal.TUNER_TYPE_BUILT_IN; - } -} +} \ No newline at end of file diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java index 09acb36b..625014ea 100644 --- a/src/com/android/tv/ui/AppLayerTvView.java +++ b/src/com/android/tv/ui/AppLayerTvView.java @@ -22,7 +22,8 @@ import android.util.AttributeSet; import android.view.SurfaceView; import android.view.View; -import com.android.tv.experiments.Experiments; +import com.android.tv.util.Debug; +import com.android.tv.util.Utils; /** * A TvView class for application layer when multiple windows are being used in the app. @@ -55,8 +56,17 @@ public class AppLayerTvView extends TvView { public void onViewAdded(View child) { if (child instanceof SurfaceView) { // Note: See b/29118070 for detail. - ((SurfaceView) child).setSecure(!Experiments.ENABLE_DEVELOPER_FEATURES.get()); + ((SurfaceView) child).setSecure(!Utils.isDeveloper()); } super.onViewAdded(child); } + + @Override + public void getLocationOnScreen(int[] outLocation) { + super.getLocationOnScreen(outLocation); + + // The TvView.MySessionCallback.onSessionCreated() will call this method indirectly. + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "AppLayerTvView.getLocationOnScreen, session created"); + } } diff --git a/src/com/android/tv/ui/BlockScreenView.java b/src/com/android/tv/ui/BlockScreenView.java index 52b9389d..09c167ca 100644 --- a/src/com/android/tv/ui/BlockScreenView.java +++ b/src/com/android/tv/ui/BlockScreenView.java @@ -21,32 +21,37 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.content.Context; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.tv.R; import com.android.tv.ui.TunableTvView.BlockScreenType; -public class BlockScreenView extends LinearLayout { +public class BlockScreenView extends FrameLayout { private View mContainerView; private View mImageContainer; - private ImageView mNormalImageView; - private ImageView mShrunkenImageView; + private ImageView mNormalLockIconView; + private ImageView mShrunkenLockIconView; private View mSpace; - private TextView mTextView; + private TextView mBlockingInfoTextView; + private ImageView mBackgroundImageView; private final int mSpacingNormal; private final int mSpacingShrunken; - // Animators used for fade in/out of block screen icon. - private Animator mFadeIn; + // Animator used to fade out the whole block screen. private Animator mFadeOut; + // Animators used to fade in/out the block screen icon and info text. + private Animator mInfoFadeIn; + private Animator mInfoFadeOut; + public BlockScreenView(Context context) { this(context, null, 0); } @@ -68,21 +73,32 @@ public class BlockScreenView extends LinearLayout { super.onFinishInflate(); mContainerView = findViewById(R.id.block_screen_container); mImageContainer = findViewById(R.id.image_container); - mNormalImageView = (ImageView) findViewById(R.id.block_screen_icon); - mShrunkenImageView = (ImageView) findViewById(R.id.block_screen_shrunken_icon); + mNormalLockIconView = (ImageView) findViewById(R.id.block_screen_icon); + mShrunkenLockIconView = (ImageView) findViewById(R.id.block_screen_shrunken_icon); mSpace = findViewById(R.id.space); - mTextView = (TextView) findViewById(R.id.block_screen_text); - mFadeIn = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_in); - mFadeIn.setTarget(mContainerView); + mBlockingInfoTextView = (TextView) findViewById(R.id.block_screen_text); + mBackgroundImageView = (ImageView) findViewById(R.id.background_image); mFadeOut = AnimatorInflater.loadAnimator(getContext(), R.animator.tvview_block_screen_fade_out); - mFadeOut.setTarget(mContainerView); + mFadeOut.setTarget(this); mFadeOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(GONE); + setBackgroundImage(null); + setAlpha(1.0f); + } + }); + mInfoFadeIn = AnimatorInflater.loadAnimator(getContext(), + R.animator.tvview_block_screen_fade_in); + mInfoFadeIn.setTarget(mContainerView); + mInfoFadeOut = AnimatorInflater.loadAnimator(getContext(), + R.animator.tvview_block_screen_fade_out); + mInfoFadeOut.setTarget(mContainerView); + mInfoFadeOut.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mContainerView.setVisibility(GONE); - mContainerView.setAlpha(1f); } }); } @@ -90,53 +106,54 @@ public class BlockScreenView extends LinearLayout { /** * Sets the normal image. */ - public void setImage(int resId) { - mNormalImageView.setImageResource(resId); + public void setIconImage(int resId) { + mNormalLockIconView.setImageResource(resId); updateSpaceVisibility(); } /** * Sets the scale type of the normal image. */ - public void setScaleType(ScaleType scaleType) { - mNormalImageView.setScaleType(scaleType); + public void setIconScaleType(ScaleType scaleType) { + mNormalLockIconView.setScaleType(scaleType); updateSpaceVisibility(); } /** - * Sets the shrunken image. + * Show or hide the image of this view. */ - public void setShrunkenImage(int resId) { - mShrunkenImageView.setImageResource(resId); + public void setIconVisibility(boolean visible) { + mImageContainer.setVisibility(visible ? VISIBLE : GONE); updateSpaceVisibility(); } /** - * Show or hide the image of this view. + * Sets the text message. */ - public void setImageVisibility(boolean visible) { - mImageContainer.setVisibility(visible ? VISIBLE : GONE); + public void setInfoText(int resId) { + mBlockingInfoTextView.setText(resId); updateSpaceVisibility(); } /** * Sets the text message. */ - public void setText(int resId) { - mTextView.setText(resId); + public void setInfoText(String text) { + mBlockingInfoTextView.setText(text); updateSpaceVisibility(); } /** - * Sets the text message. + * Sets the background image should be displayed in the block screen view. Passes {@code null} + * to remove the currently displayed background image. */ - public void setText(String text) { - mTextView.setText(text); - updateSpaceVisibility(); + public void setBackgroundImage(Drawable backgroundImage) { + mBackgroundImageView.setVisibility(backgroundImage == null ? GONE : VISIBLE); + mBackgroundImageView.setImageDrawable(backgroundImage); } private void updateSpaceVisibility() { - if (isImageViewVisible() && isTextViewVisible(mTextView)) { + if (isImageViewVisible() && isTextViewVisible(mBlockingInfoTextView)) { mSpace.setVisibility(VISIBLE); } else { mSpace.setVisibility(GONE); @@ -145,7 +162,8 @@ public class BlockScreenView extends LinearLayout { private boolean isImageViewVisible() { return mImageContainer.getVisibility() == VISIBLE - && (isImageViewVisible(mNormalImageView) || isImageViewVisible(mShrunkenImageView)); + && (isImageViewVisible(mNormalLockIconView) + || isImageViewVisible(mShrunkenLockIconView)); } private static boolean isImageViewVisible(ImageView imageView) { @@ -177,37 +195,39 @@ public class BlockScreenView extends LinearLayout { mContainerView.setVisibility(GONE); break; case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: - mNormalImageView.setVisibility(GONE); - mShrunkenImageView.setVisibility(VISIBLE); + mNormalLockIconView.setVisibility(GONE); + mShrunkenLockIconView.setVisibility(VISIBLE); mContainerView.setVisibility(VISIBLE); + mContainerView.setAlpha(1.0f); break; case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL: - mNormalImageView.setVisibility(VISIBLE); - mShrunkenImageView.setVisibility(GONE); + mNormalLockIconView.setVisibility(VISIBLE); + mShrunkenLockIconView.setVisibility(GONE); mContainerView.setVisibility(VISIBLE); + mContainerView.setAlpha(1.0f); break; } } else { switch (blockScreenType) { case TunableTvView.BLOCK_SCREEN_TYPE_NO_UI: if (mContainerView.getVisibility() == VISIBLE) { - mFadeOut.start(); + mInfoFadeOut.start(); } break; case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: - mNormalImageView.setVisibility(GONE); - mShrunkenImageView.setVisibility(VISIBLE); - mContainerView.setVisibility(VISIBLE); + mNormalLockIconView.setVisibility(GONE); + mShrunkenLockIconView.setVisibility(VISIBLE); if (mContainerView.getVisibility() == GONE) { - mFadeIn.start(); + mContainerView.setVisibility(VISIBLE); + mInfoFadeIn.start(); } break; case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL: - mNormalImageView.setVisibility(VISIBLE); - mShrunkenImageView.setVisibility(GONE); - mContainerView.setVisibility(VISIBLE); + mNormalLockIconView.setVisibility(VISIBLE); + mShrunkenLockIconView.setVisibility(GONE); if (mContainerView.getVisibility() == GONE) { - mFadeIn.start(); + mContainerView.setVisibility(VISIBLE); + mInfoFadeIn.start(); } break; } @@ -216,26 +236,33 @@ public class BlockScreenView extends LinearLayout { } /** - * Scales the contents view by the given {@code scale}. + * Adds a listener to the fade-in animation of info text and icons of the block screen. */ - public void scaleContainerView(float scale) { - mContainerView.setScaleX(scale); - mContainerView.setScaleY(scale); + public void addInfoFadeInAnimationListener(AnimatorListener listener) { + mInfoFadeIn.addListener(listener); } - public void addFadeOutAnimationListener(AnimatorListener listener) { - mFadeOut.addListener(listener); + /** + * Fades out the block screen. + */ + public void fadeOut() { + if (getVisibility() == VISIBLE && !mFadeOut.isStarted()) { + mFadeOut.start(); + } } /** * Ends the currently running animations. */ public void endAnimations() { - if (mFadeIn != null && mFadeIn.isRunning()) { - mFadeIn.end(); - } if (mFadeOut != null && mFadeOut.isRunning()) { mFadeOut.end(); } + if (mInfoFadeIn != null && mInfoFadeIn.isRunning()) { + mInfoFadeIn.end(); + } + if (mInfoFadeOut != null && mInfoFadeOut.isRunning()) { + mInfoFadeOut.end(); + } } } diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java index 3cf4de83..a5d897f2 100644 --- a/src/com/android/tv/ui/ChannelBannerView.java +++ b/src/com/android/tv/ui/ChannelBannerView.java @@ -23,12 +23,9 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.media.tv.TvContentRating; -import android.media.tv.TvContract; import android.media.tv.TvInputInfo; -import android.net.Uri; import android.os.Handler; import android.support.annotation.Nullable; import android.text.Spannable; @@ -52,12 +49,13 @@ import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; +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.dvr.DvrManager; -import com.android.tv.dvr.ScheduledRecording; +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; @@ -65,10 +63,6 @@ import com.android.tv.util.ImageLoader.ImageLoaderCallback; import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.Utils; -import junit.framework.Assert; - -import java.util.Objects; - /** * A view to render channel banner. */ @@ -98,8 +92,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private static final String EMPTY_STRING = ""; - private static Program sNoProgram; - private static Program sLockedChannelProgram; + private Program mNoProgram; + private Program mLockedChannelProgram; private static String sClosedCaptionMark; private final MainActivity mMainActivity; @@ -123,6 +117,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private String mProgramDescriptionText; private View mAnchorView; private Channel mCurrentChannel; + private boolean mCurrentChannelLogoExists; private Program mLastUpdatedProgram; private final Handler mHandler = new Handler(); private final DvrManager mDvrManager; @@ -130,6 +125,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private TvContentRating mBlockingContentRating; private int mLockType; + private boolean mUpdateOnTune; private Animator mResizeAnimator; private int mCurrentHeight; @@ -178,24 +174,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } }; - private final ContentObserver mProgramUpdateObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - // TODO: This {@code uri} argument may be a program which is not related to this - // channel. Consider adding channel id as a parameter of program URI to avoid - // unnecessary update. - mHandler.post(mProgramUpdateRunnable); - } - }; - - private final Runnable mProgramUpdateRunnable = new Runnable() { - @Override - public void run() { - removeCallbacks(this); - updateViews(null); - } - }; - public ChannelBannerView(Context context) { this(context, null); } @@ -243,38 +221,19 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mContentRatingsManager = TvApplication.getSingletons(getContext()) .getTvInputManagerHelper().getContentRatingsManager(); - if (sNoProgram == null) { - sNoProgram = new Program.Builder() - .setTitle(context.getString(R.string.channel_banner_no_title)) - .setDescription(EMPTY_STRING) - .build(); - } - if (sLockedChannelProgram == null){ - sLockedChannelProgram = new Program.Builder() - .setTitle(context.getString(R.string.channel_banner_locked_channel_title)) - .setDescription(EMPTY_STRING) - .build(); - } + mNoProgram = new Program.Builder() + .setTitle(context.getString(R.string.channel_banner_no_title)) + .setDescription(EMPTY_STRING) + .build(); + mLockedChannelProgram = new Program.Builder() + .setTitle(context.getString(R.string.channel_banner_locked_channel_title)) + .setDescription(EMPTY_STRING) + .build(); if (sClosedCaptionMark == null) { sClosedCaptionMark = context.getString(R.string.closed_caption); } } - @Override - protected void onAttachedToWindow() { - if (DEBUG) Log.d(TAG, "onAttachedToWindow"); - super.onAttachedToWindow(); - getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI, - true, mProgramUpdateObserver); - } - - @Override - protected void onDetachedFromWindow() { - if (DEBUG) Log.d(TAG, "onDetachedToWindow"); - getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver); - super.onDetachedFromWindow(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -345,19 +304,17 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage * Set new lock type. * * @param lockType Any of LOCK_NONE, LOCK_PROGRAM_DETAIL, or LOCK_CHANNEL_INFO. - * @return {@code true} only if lock type is changed + * @return the previous lock type of the channel banner. * @throws IllegalArgumentException if lockType is invalid. */ - public boolean setLockType(int lockType) { + public int setLockType(int lockType) { if (lockType != LOCK_NONE && lockType != LOCK_CHANNEL_INFO && lockType != LOCK_PROGRAM_DETAIL) { throw new IllegalArgumentException("No such lock type " + lockType); } - if (mLockType != lockType) { - mLockType = lockType; - return true; - } - return false; + int previousLockType = mLockType; + mLockType = lockType; + return previousLockType; } /** @@ -372,31 +329,34 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage /** * Update channel banner view. * - * @param info A StreamInfo that includes stream information. - * If it's {@code null}, only program information will be updated. + * @param updateOnTune {@false} denotes the channel banner is updated due to other reasons than + * tuning. The channel info will not be updated in this case. */ - public void updateViews(StreamInfo info) { + public void updateViews(boolean updateOnTune) { resetAnimationEffects(); - Channel channel = mMainActivity.getCurrentChannel(); - if (!Objects.equals(mCurrentChannel, channel)) { - mBlockingContentRating = null; + mChannelView.setVisibility(VISIBLE); + mUpdateOnTune = updateOnTune; + if (mUpdateOnTune) { if (isShown()) { scheduleHide(); } - } - mCurrentChannel = channel; - mChannelView.setVisibility(VISIBLE); - if (info != null) { - // If the current channels between ChannelTuner and TvView are different, - // the stream information should not be seen. - updateStreamInfo(channel != null && channel.equals(info.getCurrentChannel()) ? info - : null); + mBlockingContentRating = null; + mCurrentChannel = mMainActivity.getCurrentChannel(); + mCurrentChannelLogoExists = + mCurrentChannel != null && mCurrentChannel.channelLogoExists(); + updateStreamInfo(null); updateChannelInfo(); } updateProgramInfo(mMainActivity.getCurrentProgram()); + mUpdateOnTune = false; } - private void updateStreamInfo(StreamInfo info) { + /** + * Update channel banner view with stream info. + * + * @param info A StreamInfo that includes stream information. + */ + public void updateStreamInfo(StreamInfo info) { // Update stream information in a channel. if (mLockType != LOCK_CHANNEL_INFO && info != null) { updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark @@ -414,9 +374,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mAspectRatioTextView.setVisibility(View.GONE); mResolutionTextView.setVisibility(View.GONE); mAudioChannelTextView.setVisibility(View.GONE); - for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { - mContentRatingsTextViews[i].setVisibility(View.GONE); - } } } @@ -467,7 +424,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } mChannelLogoImageView.setImageBitmap(null); mChannelLogoImageView.setVisibility(View.GONE); - if (mCurrentChannel != null) { + if (mCurrentChannel != null && mCurrentChannelLogoExists) { mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoImageViewWidth, mChannelLogoImageViewHeight, createChannelLogoCallback(this, mCurrentChannel)); @@ -550,8 +507,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (mResizeAnimator == null) { String description = mProgramDescriptionTextView.getText().toString(); - boolean needFadeAnimation = !description.equals(mProgramDescriptionText); - updateBannerHeight(needFadeAnimation); + boolean programDescriptionNeedFadeAnimation = + !description.equals(mProgramDescriptionText) && !mUpdateOnTune; + updateBannerHeight(programDescriptionNeedFadeAnimation); } else { mProgramInfoUpdatePendingByResizing = true; } @@ -559,9 +517,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private void updateProgramInfo(Program program) { if (mLockType == LOCK_CHANNEL_INFO) { - program = sLockedChannelProgram; + program = mLockedChannelProgram; } else if (program == null || !program.isValid() || TextUtils.isEmpty(program.getTitle())) { - program = sNoProgram; + program = mNoProgram; } if (mLastUpdatedProgram == null @@ -590,9 +548,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mProgramDescriptionText = program.getDescription(); } String description = mProgramDescriptionTextView.getText().toString(); - boolean needFadeAnimation = isProgramChanged - || !description.equals(mProgramDescriptionText); - updateBannerHeight(needFadeAnimation); + boolean programDescriptionNeedFadeAnimation = (isProgramChanged + || !description.equals(mProgramDescriptionText)) && !mUpdateOnTune; + updateBannerHeight(programDescriptionNeedFadeAnimation); } else { mProgramInfoUpdatePendingByResizing = true; } @@ -603,7 +561,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (program == null) { return; } - updateProgramTextView(program == sLockedChannelProgram, program.getTitle(), + updateProgramTextView(program == mLockedChannelProgram, program.getTitle(), program.getEpisodeDisplayTitle(getContext())); } @@ -630,9 +588,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mProgramTextView.setText(text); } - int width = mProgramDescriptionTextViewWidth - - ((mChannelLogoImageView.getVisibility() != View.VISIBLE) - ? 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart); + int width = mProgramDescriptionTextViewWidth + (mCurrentChannelLogoExists ? + 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart); ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams(); lp.width = width; mProgramTextView.setLayoutParams(lp); @@ -655,23 +612,32 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } private void updateProgramRatings(Program program) { - if (mBlockingContentRating != null) { - mContentRatingsTextViews[0].setText( - mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating)); - mContentRatingsTextViews[0].setVisibility(View.VISIBLE); - for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { + if (mLockType == LOCK_CHANNEL_INFO) { + for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { mContentRatingsTextViews[i].setVisibility(View.GONE); } - return; - } - TvContentRating[] ratings = (program == null) ? null : program.getContentRatings(); - for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { - if (ratings == null || ratings.length <= i) { - mContentRatingsTextViews[i].setVisibility(View.GONE); + } else if (mBlockingContentRating != null) { + String displayNameForRating = + mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating); + if (!TextUtils.isEmpty(displayNameForRating)) { + mContentRatingsTextViews[0].setText(displayNameForRating); + mContentRatingsTextViews[0].setVisibility(View.VISIBLE); } else { - mContentRatingsTextViews[i].setText( - mContentRatingsManager.getDisplayNameForRating(ratings[i])); - mContentRatingsTextViews[i].setVisibility(View.VISIBLE); + mContentRatingsTextViews[0].setVisibility(View.GONE); + } + for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { + mContentRatingsTextViews[i].setVisibility(View.GONE); + } + } else { + TvContentRating[] ratings = (program == null) ? null : program.getContentRatings(); + for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { + if (ratings == null || ratings.length <= i) { + mContentRatingsTextViews[i].setVisibility(View.GONE); + } else { + mContentRatingsTextViews[i].setText( + mContentRatingsManager.getDisplayNameForRating(ratings[i])); + mContentRatingsTextViews[i].setVisibility(View.VISIBLE); + } } } } @@ -769,8 +735,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mLastUpdatedProgram = program; } - private void updateBannerHeight(boolean needFadeAnimation) { - Assert.assertNull(mResizeAnimator); + private void updateBannerHeight(boolean needProgramDescriptionFadeAnimation) { + SoftPreconditions.checkState(mResizeAnimator == null); // Need to measure the layout height with the new description text. CharSequence oldDescription = mProgramDescriptionTextView.getText(); mProgramDescriptionTextView.setText(mProgramDescriptionText); @@ -785,12 +751,13 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage layoutParams.height = targetHeight; setLayoutParams(layoutParams); } - } else if (mCurrentHeight != targetHeight || needFadeAnimation) { + } else if (mCurrentHeight != targetHeight || needProgramDescriptionFadeAnimation) { // Restore description text for fade in/out animation. - if (needFadeAnimation) { + if (needProgramDescriptionFadeAnimation) { mProgramDescriptionTextView.setText(oldDescription); } - mResizeAnimator = createResizeAnimator(targetHeight, needFadeAnimation); + mResizeAnimator = + createResizeAnimator(targetHeight, needProgramDescriptionFadeAnimation); mResizeAnimator.start(); } } diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java index abc05bad..ac5d841d 100644 --- a/src/com/android/tv/ui/KeypadChannelSwitchView.java +++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java @@ -39,7 +39,7 @@ import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java index 5e25ae43..dc92111c 100644 --- a/src/com/android/tv/ui/SelectInputView.java +++ b/src/com/android/tv/ui/SelectInputView.java @@ -18,7 +18,6 @@ package com.android.tv.ui; import android.content.Context; import android.content.res.Resources; -import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; @@ -37,14 +36,13 @@ import android.widget.TextView; import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.data.Channel; import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,7 +56,7 @@ public class SelectInputView extends VerticalGridView implements private final TvInputManagerHelper mTvInputManagerHelper; private final List mInputList = new ArrayList<>(); - private final InputsComparator mComparator = new InputsComparator(); + private final TvInputManagerHelper.HardwareInputComparator mComparator; private final Tracker mTracker; private final DurationTimer mViewDurationTimer = new DurationTimer(); private final TvInputCallback mTvInputCallback = new TvInputCallback() { @@ -149,6 +147,8 @@ public class SelectInputView extends VerticalGridView implements ApplicationSingletons appSingletons = TvApplication.getSingletons(context); mTracker = appSingletons.getTracker(); mTvInputManagerHelper = appSingletons.getTvInputManagerHelper(); + mComparator = + new TvInputManagerHelper.HardwareInputComparator(context, mTvInputManagerHelper); Resources resources = context.getResources(); mInputItemHeight = resources.getDimensionPixelSize(R.dimen.input_banner_item_height); @@ -385,72 +385,6 @@ public class SelectInputView extends VerticalGridView implements } } - private class InputsComparator implements Comparator { - @Override - public int compare(TvInputInfo lhs, TvInputInfo rhs) { - if (lhs == null) { - return (rhs == null) ? 0 : 1; - } - if (rhs == null) { - return -1; - } - - boolean enabledL = isInputEnabled(lhs); - boolean enabledR = isInputEnabled(rhs); - if (enabledL != enabledR) { - return enabledL ? -1 : 1; - } - - int priorityL = getPriority(lhs); - int priorityR = getPriority(rhs); - if (priorityL != priorityR) { - return priorityR - priorityL; - } - - String customLabelL = (String) lhs.loadCustomLabel(getContext()); - String customLabelR = (String) rhs.loadCustomLabel(getContext()); - if (!TextUtils.equals(customLabelL, customLabelR)) { - customLabelL = customLabelL == null ? "" : customLabelL; - customLabelR = customLabelR == null ? "" : customLabelR; - return customLabelL.compareToIgnoreCase(customLabelR); - } - - String labelL = (String) lhs.loadLabel(getContext()); - String labelR = (String) rhs.loadLabel(getContext()); - labelL = labelL == null ? "" : labelL; - labelR = labelR == null ? "" : labelR; - return labelL.compareToIgnoreCase(labelR); - } - - private int getPriority(TvInputInfo info) { - switch (info.getType()) { - case TvInputInfo.TYPE_TUNER: - return 9; - case TvInputInfo.TYPE_HDMI: - HdmiDeviceInfo hdmiInfo = info.getHdmiDeviceInfo(); - if (hdmiInfo != null && hdmiInfo.isCecDevice()) { - return 8; - } - return 7; - case TvInputInfo.TYPE_DVI: - return 6; - case TvInputInfo.TYPE_COMPONENT: - return 5; - case TvInputInfo.TYPE_SVIDEO: - return 4; - case TvInputInfo.TYPE_COMPOSITE: - return 3; - case TvInputInfo.TYPE_DISPLAY_PORT: - return 2; - case TvInputInfo.TYPE_VGA: - return 1; - case TvInputInfo.TYPE_SCART: - default: - return 0; - } - } - } - /** * A callback interface for the input selection. */ diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index cbe459fb..48386698 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -19,9 +19,18 @@ package com.android.tv.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; -import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ApplicationErrorReport; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.media.PlaybackParams; import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; @@ -33,12 +42,11 @@ import android.media.tv.TvView.TvInputCallback; import android.net.ConnectivityManager; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.UiThread; -import android.support.v4.os.BuildCompat; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.AttributeSet; @@ -47,23 +55,29 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import com.android.tv.ApplicationSingletons; +import com.android.tv.Features; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.TvViewSession; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.data.Program; +import com.android.tv.data.ProgramDataManager; +import com.android.tv.parental.ParentalControlSettings; +import com.android.tv.util.DurationTimer; +import com.android.tv.util.Debug; import com.android.tv.analytics.Tracker; +import com.android.tv.common.BuildConfig; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.recommendation.NotificationService; +import com.android.tv.util.ImageLoader; import com.android.tv.util.NetworkUtils; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; @@ -79,6 +93,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { public static final int VIDEO_UNAVAILABLE_REASON_NOT_TUNED = -1; public static final int VIDEO_UNAVAILABLE_REASON_NO_RESOURCE = -2; + public static final int VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED = -3; + public static final int VIDEO_UNAVAILABLE_REASON_NONE = -100; @Retention(RetentionPolicy.SOURCE) @IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL}) @@ -105,17 +121,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private static final int FADING_IN = 2; private static final int FADING_OUT = 3; - // It is too small to see the description text without PIP_BLOCK_SCREEN_SCALE_FACTOR. - private static final float PIP_BLOCK_SCREEN_SCALE_FACTOR = 1.2f; - private AppLayerTvView mTvView; private TvViewSession mTvViewSession; private Channel mCurrentChannel; private TvInputManagerHelper mInputManagerHelper; private ContentRatingsManager mContentRatingsManager; + private ParentalControlSettings mParentalControlSettings; + private ProgramDataManager mProgramDataManager; @Nullable private WatchedHistoryManager mWatchedHistoryManager; private boolean mStarted; + private String mTagetInputId; private TvInputInfo mInputInfo; private OnTuneListener mOnTuneListener; private int mVideoWidth; @@ -125,7 +141,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private float mVideoDisplayAspectRatio; private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; private boolean mHasClosedCaption = false; - private boolean mVideoAvailable; private boolean mScreenBlocked; private OnScreenBlockingChangedListener mOnScreenBlockedListener; private TvContentRating mBlockedContentRating; @@ -136,10 +151,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private boolean mParentControlEnabled; private int mFixedSurfaceWidth; private int mFixedSurfaceHeight; - private boolean mIsPip; - private int mScreenHeight; - private int mShrunkenTvViewHeight; private final boolean mCanModifyParentalControls; + private boolean mIsUnderShrunken; @TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE; private TimeShiftListener mTimeShiftListener; @@ -150,21 +163,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private final DurationTimer mChannelViewTimer = new DurationTimer(); private InternetCheckTask mInternetCheckTask; - // A block screen view which has lock icon with black background. - // This indicates that user's action is needed to play video. + // A block screen view to hide the real TV view underlying. It may be used to enforce parental + // control, or hide screen when there's no video available and show appropriate information. private final BlockScreenView mBlockScreenView; - - // A View to hide screen when there's problem in video playback. - private final BlockScreenView mHideScreenView; - - // A View to block screen until onContentAllowed is received if parental control is on. - private final View mBlockScreenForTuneView; + private final int mTuningImageColorFilter; // A spinner view to show buffering status. private final View mBufferingSpinnerView; - // A View for fade-in/out animation private final View mDimScreenView; + private int mFadeState = FADED_IN; private Runnable mActionAfterFade; @@ -286,21 +294,77 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override public void onVideoAvailable(String inputId) { - unhideScreenByVideoAvailability(); + if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}"); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start up of Live TV ends," + + " TunableTvView.onVideoAvailable resets timer"); + long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset(); + Debug.removeTimer(Debug.TAG_START_UP_TIMER); + if (BuildConfig.ENG && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) { + showAlertDialogForLongStartUp(); + } + mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE; + updateBlockScreenAndMuting(); if (mOnTuneListener != null) { mOnTuneListener.onStreamInfoChanged(TunableTvView.this); } } + private void showAlertDialogForLongStartUp() { + new AlertDialog.Builder(getContext()).setTitle( + getContext().getString(R.string.settings_send_feedback)) + .setMessage("Because the start up time of Live channels is too long," + + " please send feedback") + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + ApplicationErrorReport report = new ApplicationErrorReport(); + report.packageName = report.processName = getContext() + .getApplicationContext().getPackageName(); + report.time = System.currentTimeMillis(); + report.type = ApplicationErrorReport.TYPE_CRASH; + + // Add the crash info to add title of feedback automatically. + ApplicationErrorReport.CrashInfo crash = new + ApplicationErrorReport.CrashInfo(); + crash.exceptionClassName = + "Live TV start up takes long time"; + crash.exceptionMessage = + "The start up time of Live TV is too long"; + report.crashInfo = crash; + + intent.putExtra(Intent.EXTRA_BUG_REPORT, report); + getContext().startActivity(intent); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } + @Override public void onVideoUnavailable(String inputId, int reason) { - hideScreenByVideoAvailability(inputId, reason); + if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING + && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "TunableTvView.onVideoUnAvailable reason = (" + reason + + ") and removes timer"); + Debug.removeTimer(Debug.TAG_START_UP_TIMER); + } else { + Debug.getTimer(Debug.TAG_START_UP_TIMER).log( + "TunableTvView.onVideoUnAvailable reason = (" + reason + ")"); + } + mVideoUnavailableReason = reason; + if (closePipIfNeeded()) { + return; + } + updateBlockScreenAndMuting(); if (mOnTuneListener != null) { mOnTuneListener.onStreamInfoChanged(TunableTvView.this); } switch (reason) { - case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: + case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason); default: @@ -310,8 +374,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override public void onContentAllowed(String inputId) { - mBlockScreenForTuneView.setVisibility(View.GONE); - unblockScreenByContentRating(); + mBlockedContentRating = null; + updateBlockScreenAndMuting(); if (mOnTuneListener != null) { mOnTuneListener.onContentAllowed(); } @@ -319,7 +383,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override public void onContentBlocked(String inputId, TvContentRating rating) { - blockScreenByContentRating(rating); + if (rating != null && rating.equals(mBlockedContentRating)) { + return; + } + mBlockedContentRating = rating; + if (closePipIfNeeded()) { + return; + } + updateBlockScreenAndMuting(); if (mOnTuneListener != null) { mOnTuneListener.onContentBlocked(); } @@ -327,6 +398,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override public void onTimeShiftStatusChanged(String inputId, int status) { + if (DEBUG) { + Log.d(TAG, "onTimeShiftStatusChanged: {inputId=" + inputId + ", status=" + status + + "}"); + } boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE; setTimeShiftAvailable(available); } @@ -361,25 +436,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mTracker = appSingletons.getTracker(); mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL; mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen); - if (!mCanModifyParentalControls) { - mBlockScreenView.setImage(R.drawable.ic_message_lock_no_permission); - mBlockScreenView.setScaleType(ImageView.ScaleType.CENTER); - } else { - mBlockScreenView.setImage(R.drawable.ic_message_lock); - } - mBlockScreenView.setShrunkenImage(R.drawable.ic_message_lock_preview); - mBlockScreenView.addFadeOutAnimationListener(new AnimatorListenerAdapter() { + mBlockScreenView.addInfoFadeInAnimationListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationStart(Animator animation) { adjustBlockScreenSpacingAndText(); } }); - mHideScreenView = (BlockScreenView) findViewById(R.id.hide_screen); - mHideScreenView.setImageVisibility(false); mBufferingSpinnerView = findViewById(R.id.buffering_spinner); - mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune); - mDimScreenView = findViewById(R.id.dim); + mTuningImageColorFilter = getResources() + .getColor(R.color.tvview_block_image_color_filter, null); + mDimScreenView = findViewById(R.id.dim_screen); mDimScreenView.animate().setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -397,27 +464,21 @@ public class TunableTvView extends FrameLayout implements StreamInfo { }); } - public void initialize(AppLayerTvView tvView, boolean isPip, int screenHeight, - int shrunkenTvViewHeight) { - mTvView = tvView; + public void initialize(ProgramDataManager programDataManager, + TvInputManagerHelper tvInputManagerHelper) { + mTvView = (AppLayerTvView) findViewById(R.id.tv_view); + mProgramDataManager = programDataManager; + mInputManagerHelper = tvInputManagerHelper; + mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager(); + mParentalControlSettings = tvInputManagerHelper.getParentalControlSettings(); if (mInputSessionManager != null) { - mTvViewSession = mInputSessionManager.createTvViewSession(tvView, this, mCallback); + mTvViewSession = mInputSessionManager.createTvViewSession(mTvView, this, mCallback); } else { mTvView.setCallback(mCallback); } - mIsPip = isPip; - mScreenHeight = screenHeight; - mShrunkenTvViewHeight = shrunkenTvViewHeight; - mTvView.setZOrderOnTop(isPip); - copyLayoutParamsToTvView(); } - public void start(TvInputManagerHelper tvInputManagerHelper) { - mInputManagerHelper = tvInputManagerHelper; - mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager(); - if (mStarted) { - return; - } + public void start() { mStarted = true; } @@ -431,7 +492,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } else { mTvView.tune(inputId, channelUri); } - hideScreenByVideoAvailability(inputId, TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING; + updateBlockScreenAndMuting(); } } @@ -462,15 +524,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Reset TV view. + * Resets TV view. */ public void reset() { resetInternal(); - hideScreenByVideoAvailability(null, VIDEO_UNAVAILABLE_REASON_NOT_TUNED); + mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED; + updateBlockScreenAndMuting(); } /** - * Reset TV view to acquire the recording session. + * Resets TV view to acquire the recording session. */ public void resetByRecording() { resetInternal(); @@ -497,6 +560,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mWatchedHistoryManager = watchedHistoryManager; } + /** + * Sets if the TunableTvView is under shrunken. + */ + public void setIsUnderShrunken(boolean isUnderShrunken) { + mIsUnderShrunken = isUnderShrunken; + } + public boolean isPlaying() { return mStarted; } @@ -506,8 +576,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo { */ public void onParentalControlChanged(boolean enabled) { mParentControlEnabled = enabled; - if (!mParentControlEnabled) { - mBlockScreenForTuneView.setVisibility(View.GONE); + if (!enabled) { + // Unblock screen immediately if parental control is turned off + updateBlockScreenAndMuting(); } } @@ -519,6 +590,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * if the state is disconnected or channelId doesn't exist, it returns false. */ public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) { + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TunableTvView.tuneTo"); if (!mStarted) { throw new IllegalStateException("TvView isn't started"); } @@ -541,6 +613,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null; boolean needSurfaceSizeUpdate = false; if (!inputInfo.equals(mInputInfo)) { + mTagetInputId = inputInfo.getId(); mInputInfo = inputInfo; mCanReceiveInputEvent = getContext().getPackageManager().checkPermission( PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getServiceInfo().packageName) @@ -560,6 +633,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mVideoDisplayAspectRatio = 0f; mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; mHasClosedCaption = false; + mBlockedContentRating = null; mTimeShiftCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; // To reduce the IPCs, unregister the callback here and register it when necessary. mTvView.setTimeShiftPositionCallback(null); @@ -569,19 +643,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView. getSurfaceView().getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight); } - hideScreenByVideoAvailability(mInputInfo.getId(), - TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING; if (mTvViewSession != null) { mTvViewSession.tune(channel, params, listener); } else { mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params); } - unblockScreenByContentRating(); - if (channel.isPassthrough()) { - mBlockScreenForTuneView.setVisibility(View.GONE); - } else if (mParentControlEnabled) { - mBlockScreenForTuneView.setVisibility(View.VISIBLE); - } + updateBlockScreenAndMuting(); if (mOnTuneListener != null) { mOnTuneListener.onStreamInfoChanged(this); } @@ -711,7 +779,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override public boolean isVideoAvailable() { - return mVideoAvailable; + return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE; + } + + @Override + public boolean isVideoOrAudioAvailable() { + return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE + || mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY; } @Override @@ -747,12 +821,50 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Returns if the screen is blocked by {@link #blockScreen()}. + * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying + * {@link TvView}, which is the actual view to play live TV videos. + */ + public MarginLayoutParams getTvViewLayoutParams() { + return (MarginLayoutParams) mTvView.getLayoutParams(); + } + + /** + * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying + * {@link TvView}, which is the actual view to play live TV videos. + */ + public void setTvViewLayoutParams(MarginLayoutParams layoutParams) { + mTvView.setLayoutParams(layoutParams); + } + + /** + * Gets the underlying {@link AppLayerTvView}, which is the actual view to play live TV videos. + */ + public TvView getTvView() { + return mTvView; + } + + /** + * Returns if the screen is blocked, either by {@link #blockOrUnblockScreen(boolean)} or because + * the content is blocked. + */ + public boolean isBlocked() { + return isScreenBlocked() || isContentBlocked(); + } + + /** + * Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}. */ public boolean isScreenBlocked() { return mScreenBlocked; } + /** + * Returns {@code true} if the content is blocked, otherwise {@code false}. + */ + public boolean isContentBlocked() { + return mBlockedContentRating != null; + } + public void setOnScreenBlockedListener(OnScreenBlockingChangedListener listener) { mOnScreenBlockedListener = listener; } @@ -766,77 +878,23 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Locks current TV screen and mutes. + * Blocks/unblocks current TV screen and mutes. * There would be black screen with lock icon in order to show that * screen block is intended and not an error. - * TODO: Accept parameter to show lock icon or not. + * + * @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock. */ - public void blockScreen() { - mScreenBlocked = true; - checkBlockScreenAndMuteNeeded(); - if (mOnScreenBlockedListener != null) { - mOnScreenBlockedListener.onScreenBlockingChanged(true); - } - } - - private void blockScreenByContentRating(TvContentRating rating) { - mBlockedContentRating = rating; - checkBlockScreenAndMuteNeeded(); - } - - @Override - @SuppressLint("RtlHardcoded") - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mIsPip) { - int height = bottom - top; - float scale; - if (mBlockScreenType == BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW) { - scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mShrunkenTvViewHeight; - } else { - scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mScreenHeight; - } - // TODO: need to get UX confirmation. - mBlockScreenView.scaleContainerView(scale); + public void blockOrUnblockScreen(boolean blockOrUnblock) { + if (mScreenBlocked == blockOrUnblock) { + return; } - } - - @Override - public void setLayoutParams(ViewGroup.LayoutParams params) { - super.setLayoutParams(params); - if (mTvView != null) { - copyLayoutParamsToTvView(); + mScreenBlocked = blockOrUnblock; + if (closePipIfNeeded()) { + return; } - } - - private void copyLayoutParamsToTvView() { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); - FrameLayout.LayoutParams tvViewLp = (FrameLayout.LayoutParams) mTvView.getLayoutParams(); - if (tvViewLp.bottomMargin != lp.bottomMargin - || tvViewLp.topMargin != lp.topMargin - || tvViewLp.leftMargin != lp.leftMargin - || tvViewLp.rightMargin != lp.rightMargin - || tvViewLp.gravity != lp.gravity - || tvViewLp.height != lp.height - || tvViewLp.width != lp.width) { - if (lp.topMargin == tvViewLp.topMargin && lp.leftMargin == tvViewLp.leftMargin - && !BuildCompat.isAtLeastN()) { - // HACK: If top and left position aren't changed and SurfaceHolder.setFixedSize is - // used, SurfaceView doesn't catch the width and height change. It causes a bug that - // PIP size change isn't shown when PIP is located TOP|LEFT. So we adjust 1 px for - // small size PIP as a workaround. - // Note: This framework issue has been fixed from NYC. - tvViewLp.leftMargin = lp.leftMargin + 1; - } else { - tvViewLp.leftMargin = lp.leftMargin; - } - tvViewLp.topMargin = lp.topMargin; - tvViewLp.bottomMargin = lp.bottomMargin; - tvViewLp.rightMargin = lp.rightMargin; - tvViewLp.gravity = lp.gravity; - tvViewLp.height = lp.height; - tvViewLp.width = lp.width; - mTvView.setLayoutParams(tvViewLp); + updateBlockScreenAndMuting(); + if (mOnScreenBlockedListener != null) { + mOnScreenBlockedListener.onScreenBlockingChanged(blockOrUnblock); } } @@ -859,35 +917,67 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * @param type The type of block screen to set. */ public void setBlockScreenType(@BlockScreenType int type) { - // TODO: need to support the transition from NORMAL to SHRUNKEN and vice verse. if (mBlockScreenType != type) { mBlockScreenType = type; - updateBlockScreenUI(true); + updateBlockScreen(true); } } - private void updateBlockScreenUI(boolean animation) { + private void updateBlockScreen(boolean animation) { mBlockScreenView.endAnimations(); - - if (!mScreenBlocked && mBlockedContentRating == null) { - mBlockScreenView.setVisibility(GONE); - return; - } - - mBlockScreenView.setVisibility(VISIBLE); - if (!animation || mBlockScreenType != TunableTvView.BLOCK_SCREEN_TYPE_NO_UI) { - adjustBlockScreenSpacingAndText(); + int blockReason = (mScreenBlocked || mBlockedContentRating != null) + && mParentControlEnabled ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED + : mVideoUnavailableReason; + if (blockReason != VIDEO_UNAVAILABLE_REASON_NONE) { + mBufferingSpinnerView.setVisibility( + blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING + || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING ? + VISIBLE : GONE); + if (!animation) { + adjustBlockScreenSpacingAndText(); + } + if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { + return; + } + mBlockScreenView.setVisibility(VISIBLE); + mBlockScreenView.setBackgroundImage(null); + if (blockReason == VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED) { + mBlockScreenView.setIconVisibility(true); + if (!mCanModifyParentalControls) { + mBlockScreenView.setIconImage(R.drawable.ic_message_lock_no_permission); + mBlockScreenView.setIconScaleType(ImageView.ScaleType.CENTER); + } else { + mBlockScreenView.setIconImage(R.drawable.ic_message_lock); + mBlockScreenView.setIconScaleType(ImageView.ScaleType.FIT_CENTER); + } + } else { + if (mInternetCheckTask != null) { + mInternetCheckTask.cancel(true); + mInternetCheckTask = null; + } + mBlockScreenView.setIconVisibility(false); + if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) { + showImageForTuningIfNeeded(); + } else if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN + && mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) { + mInternetCheckTask = new InternetCheckTask(); + mInternetCheckTask.execute(); + } + } + mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation); + } else { + mBufferingSpinnerView.setVisibility(GONE); + if (mBlockScreenView.getVisibility() == VISIBLE) { + mBlockScreenView.fadeOut(); + } } - mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation); } private void adjustBlockScreenSpacingAndText() { - // TODO: need to add animation for padding change when the block screen type is changed - // NORMAL to SHRUNKEN and vice verse. mBlockScreenView.setSpacing(mBlockScreenType); String text = getBlockScreenText(); if (text != null) { - mBlockScreenView.setText(text); + mBlockScreenView.setInfoText(text); } } @@ -896,151 +986,121 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * Note that returning {@code null} value means that the current text should not be changed. */ private String getBlockScreenText() { - if (mScreenBlocked) { + // TODO: add a test for this method + Resources res = getResources(); + if (mScreenBlocked && mParentControlEnabled) { switch (mBlockScreenType) { case BLOCK_SCREEN_TYPE_NO_UI: case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: return ""; case BLOCK_SCREEN_TYPE_NORMAL: if (mCanModifyParentalControls) { - return getResources().getString(R.string.tvview_channel_locked); + return res.getString(R.string.tvview_channel_locked); } else { - return getResources().getString( - R.string.tvview_channel_locked_no_permission); + return res.getString(R.string.tvview_channel_locked_no_permission); } } - } else if (mBlockedContentRating != null) { + } else if (mBlockedContentRating != null && mParentControlEnabled) { String name = mContentRatingsManager.getDisplayNameForRating(mBlockedContentRating); switch (mBlockScreenType) { case BLOCK_SCREEN_TYPE_NO_UI: return ""; case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: if (TextUtils.isEmpty(name)) { - return getResources().getString(R.string.shrunken_tvview_content_locked); + return res.getString(R.string.shrunken_tvview_content_locked); + } else if (name.equals(res.getString(R.string.unrated_rating_name))) { + return res.getString(R.string.shrunken_tvview_content_locked_unrated); } else { - return getContext().getString( - R.string.shrunken_tvview_content_locked_format, name); + return res.getString(R.string.shrunken_tvview_content_locked_format, name); } case BLOCK_SCREEN_TYPE_NORMAL: if (TextUtils.isEmpty(name)) { if (mCanModifyParentalControls) { - return getResources().getString(R.string.tvview_content_locked); + return res.getString(R.string.tvview_content_locked); } else { - return getResources().getString( - R.string.tvview_content_locked_no_permission); + return res.getString(R.string.tvview_content_locked_no_permission); } } else { if (mCanModifyParentalControls) { - return getContext().getString( - R.string.tvview_content_locked_format, name); + return name.equals(res.getString(R.string.unrated_rating_name)) + ? res.getString(R.string.tvview_content_locked_unrated) + : res.getString(R.string.tvview_content_locked_format, name); } else { - return getContext().getString( - R.string.tvview_content_locked_format_no_permission, name); + return name.equals(res.getString(R.string.unrated_rating_name)) + ? res.getString( + R.string.tvview_content_locked_unrated_no_permission) + : res.getString( + R.string.tvview_content_locked_format_no_permission, + name); } } } + } else if (mVideoUnavailableReason != VIDEO_UNAVAILABLE_REASON_NONE) { + switch (mVideoUnavailableReason) { + case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: + return res.getString(R.string.tvview_msg_audio_only); + case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: + return res.getString(R.string.tvview_msg_weak_signal); + case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE: + return getTuneConflictMessage(); + default: + return ""; + } } return null; } - private void checkBlockScreenAndMuteNeeded() { - updateBlockScreenUI(false); - if (mScreenBlocked || mBlockedContentRating != null) { - mute(); - if (mIsPip) { - // If we don't make mTvView invisible, some frames are leaked when a user changes - // PIP layout in options. - // Note: When video is unavailable, we keep the mTvView's visibility, because - // TIS implementation may not send video available with no surface. - mTvView.setVisibility(View.INVISIBLE); - } - } else { - unmuteIfPossible(); - if (mIsPip) { - mTvView.setVisibility(View.VISIBLE); - } + private boolean closePipIfNeeded() { + if (Features.PICTURE_IN_PICTURE.isEnabled(getContext()) + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && ((Activity) getContext()).isInPictureInPictureMode() + && (mScreenBlocked + || mBlockedContentRating != null + || mVideoUnavailableReason + == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)) { + ((Activity) getContext()).finish(); + return true; } + return false; } - public void unblockScreen() { - mScreenBlocked = false; - checkBlockScreenAndMuteNeeded(); - if (mOnScreenBlockedListener != null) { - mOnScreenBlockedListener.onScreenBlockingChanged(false); - } + private void updateBlockScreenAndMuting() { + updateBlockScreen(false); + updateMuteStatus(); } - private void unblockScreenByContentRating() { - mBlockedContentRating = null; - checkBlockScreenAndMuteNeeded(); + private boolean shouldShowImageForTuning() { + if (mVideoUnavailableReason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING + || mScreenBlocked || mBlockedContentRating != null || mCurrentChannel == null + || mIsUnderShrunken || getWidth() == 0 || getWidth() == 0 || !isBundledInput()) { + return false; + } + Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); + if (currentProgram == null) { + return false; + } + TvContentRating rating = + mParentalControlSettings.getBlockedRating(currentProgram.getContentRatings()); + return !(mParentControlEnabled && rating != null); } - @UiThread - private void hideScreenByVideoAvailability(String inputId, int reason) { - mVideoAvailable = false; - mVideoUnavailableReason = reason; - if (mInternetCheckTask != null) { - mInternetCheckTask.cancel(true); - mInternetCheckTask = null; - } - switch (reason) { - case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(R.string.tvview_msg_audio_only); - mBufferingSpinnerView.setVisibility(GONE); - unmuteIfPossible(); - break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: - mBufferingSpinnerView.setVisibility(VISIBLE); - mute(); - break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setText(R.string.tvview_msg_weak_signal); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(null); - mBufferingSpinnerView.setVisibility(VISIBLE); - mute(); - break; - case VIDEO_UNAVAILABLE_REASON_NOT_TUNED: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(null); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - break; - case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(getTuneConflictMessage(inputId)); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - break; - case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: - default: - mHideScreenView.setVisibility(VISIBLE); - mHideScreenView.setImageVisibility(false); - mHideScreenView.setText(null); - mBufferingSpinnerView.setVisibility(GONE); - mute(); - if (mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) { - mInternetCheckTask = new InternetCheckTask(); - mInternetCheckTask.execute(); - } - break; + private void showImageForTuningIfNeeded() { + if (shouldShowImageForTuning()) { + if (mCurrentChannel == null) { + return; + } + Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); + if (currentProgram != null) { + currentProgram.loadPosterArt(getContext(), getWidth(), getHeight(), + createProgramPosterArtCallback(mCurrentChannel.getId())); + } } } - private String getTuneConflictMessage(String inputId) { - if (inputId != null) { - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(inputId); + private String getTuneConflictMessage() { + if (mTagetInputId != null) { + TvInputInfo input = mInputManager.getTvInputInfo(mTagetInputId); + Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(mTagetInputId); if (timeMs != null) { return getResources().getQuantityString(R.plurals.tvview_msg_input_no_resource, input.getTunerCount(), @@ -1050,27 +1110,36 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return null; } - private void unhideScreenByVideoAvailability() { - mVideoAvailable = true; - mHideScreenView.setVisibility(GONE); - mBufferingSpinnerView.setVisibility(GONE); - unmuteIfPossible(); - } - - private void unmuteIfPossible() { - if (mVideoAvailable && !mScreenBlocked && mBlockedContentRating == null) { - unmute(); + private void updateMuteStatus() { + // Workaround: TunerTvInputService uses AC3 pass-through implementation, which disables + // audio tracks to enforce the mute request. We don't want to send mute request if we are + // not going to block the screen to prevent the video jankiness resulted by disabling audio + // track before the playback is started. In other way, we should send unmute request before + // the playback is started, because TunerTvInput will remember the muted state and mute + // itself right way when the playback is going to be started, which results the initial + // jankiness, too. + boolean isBundledInput = isBundledInput(); + if ((isBundledInput || isVideoOrAudioAvailable()) && !mScreenBlocked + && mBlockedContentRating == null) { + if (mIsMuted) { + mIsMuted = false; + mTvView.setStreamVolume(mVolume); + } + } else { + if (!mIsMuted) { + if ((mInputInfo == null || isBundledInput) + && !mScreenBlocked && mBlockedContentRating == null) { + return; + } + mIsMuted = true; + mTvView.setStreamVolume(0); + } } } - private void mute() { - mIsMuted = true; - mTvView.setStreamVolume(0); - } - - private void unmute() { - mIsMuted = false; - mTvView.setStreamVolume(mVolume); + private boolean isBundledInput() { + return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER + && Utils.isBundledInput(mInputInfo.getId()); } /** Returns true if this view is faded out. */ @@ -1268,6 +1337,24 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return mTimeShiftCurrentPositionMs; } + private ImageLoader.ImageLoaderCallback createProgramPosterArtCallback( + final long channelId) { + return new ImageLoader.ImageLoaderCallback(mBlockScreenView) { + @Override + public void onBitmapLoaded(BlockScreenView view, @Nullable Bitmap posterArt) { + if (posterArt == null || getCurrentChannel() == null + || channelId != getCurrentChannel().getId() + || !shouldShowImageForTuning()) { + return; + } + Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt); + drawablePosterArt.mutate().setColorFilter( + mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER); + view.setBackgroundImage(drawablePosterArt); + } + }; + } + /** * Used to receive the time-shift events. */ @@ -1304,11 +1391,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override protected void onPostExecute(Boolean networkAvailable) { mInternetCheckTask = null; - if (!mVideoAvailable && !networkAvailable && isAttachedToWindow() + if (!networkAvailable && isAttachedToWindow() + && !mScreenBlocked && mBlockedContentRating == null && mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) { - mHideScreenView.setImageVisibility(true); - mHideScreenView.setImage(R.drawable.ic_sad_cloud); - mHideScreenView.setText(R.string.tvview_msg_no_internet_connection); + mBlockScreenView.setIconVisibility(true); + mBlockScreenView.setIconImage(R.drawable.ic_sad_cloud); + mBlockScreenView.setInfoText(R.string.tvview_msg_no_internet_connection); } } } diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java index e14b286b..9324742e 100644 --- a/src/com/android/tv/ui/TvOverlayManager.java +++ b/src/com/android/tv/ui/TvOverlayManager.java @@ -20,6 +20,7 @@ import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentManager.OnBackStackChangedListener; import android.content.Intent; +import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.os.Handler; @@ -39,6 +40,7 @@ import com.android.tv.MainActivity.KeyHandlerResultType; import com.android.tv.R; import com.android.tv.TimeShiftManager; import com.android.tv.TvApplication; +import com.android.tv.TvOptionsManager; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; import com.android.tv.common.feature.CommonFeatures; @@ -46,14 +48,16 @@ import com.android.tv.common.ui.setup.OnActionClickListener; import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.data.ChannelDataManager; +import com.android.tv.dialog.DvrHistoryDialogFragment; import com.android.tv.dialog.FullscreenDialogFragment; +import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dialog.RecentlyWatchedDialogFragment; import com.android.tv.dialog.SafeDismissDialogFragment; import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.ui.DvrActivity; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; +import com.android.tv.dvr.ui.browse.DvrBrowseActivity; import com.android.tv.guide.ProgramGuide; +import com.android.tv.license.LicenseDialogFragment; import com.android.tv.menu.Menu; import com.android.tv.menu.Menu.MenuShowReason; import com.android.tv.menu.MenuRowFactory; @@ -62,7 +66,6 @@ import com.android.tv.onboarding.NewSourcesFragment; import com.android.tv.onboarding.SetupSourcesFragment; import com.android.tv.search.ProgramGuideSearchFragment; import com.android.tv.ui.TvTransitionManager.SceneType; -import com.android.tv.ui.sidepanel.SettingsFragment; import com.android.tv.ui.sidepanel.SideFragmentManager; import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; import com.android.tv.util.TvInputManagerHelper; @@ -157,15 +160,47 @@ public class TvOverlayManager { // Used for the padded print of the overlay type. private static final int NUM_OVERLAY_TYPES = 9; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE, + UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, + UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK, + UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO}) + private @interface ChannelBannerUpdateReason {} + /** + * Updates channel banner because the channel banner is forced to show. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; + /** + * Updates channel banner because of tuning. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; + /** + * Updates channel banner because of fast tuning. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; + /** + * Updates channel banner because of info updating. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; + /** + * Updates channel banner because the current watched channel is locked or unlocked. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; + /** + * Updates channel banner because of stream info updating. + */ + public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6; + private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources"; private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources"; private static final Set AVAILABLE_DIALOG_TAGS = new HashSet<>(); static { AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); + AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG); - AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG); + AVAILABLE_DIALOG_TAGS.add(LicenseDialogFragment.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG); } @@ -176,8 +211,10 @@ public class TvOverlayManager { private final ChannelDataManager mChannelDataManager; private final TvInputManagerHelper mInputManager; private final Menu mMenu; + private final TunableTvView mTvView; private final SideFragmentManager mSideFragmentManager; private final ProgramGuide mProgramGuide; + private final ChannelBannerView mChannelBannerView; private final KeypadChannelSwitchView mKeypadChannelSwitchView; private final SelectInputView mSelectInputView; private final ProgramGuideSearchFragment mSearchFragment; @@ -185,6 +222,7 @@ public class TvOverlayManager { private SafeDismissDialogFragment mCurrentDialog; private boolean mSetupFragmentActive; private boolean mNewSourcesFragmentActive; + private boolean mChannelBannerHiddenBySideFragment; private final Handler mHandler = new TvOverlayHandler(this); private @TvOverlayType int mOpenedOverlays; @@ -195,15 +233,17 @@ public class TvOverlayManager { private OnBackStackChangedListener mOnBackStackChangedListener; public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, - TunableTvView tvView, KeypadChannelSwitchView keypadChannelSwitchView, - ChannelBannerView channelBannerView, InputBannerView inputBannerView, - SelectInputView selectInputView, ViewGroup sceneContainer, - ProgramGuideSearchFragment searchFragment) { + TunableTvView tvView, TvOptionsManager optionsManager, + KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView, + InputBannerView inputBannerView, SelectInputView selectInputView, + ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment) { mMainActivity = mainActivity; mChannelTuner = channelTuner; ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity); mChannelDataManager = singletons.getChannelDataManager(); mInputManager = singletons.getTvInputManagerHelper(); + mTvView = tvView; + mChannelBannerView = channelBannerView; mKeypadChannelSwitchView = keypadChannelSwitchView; mSelectInputView = selectInputView; mSearchFragment = searchFragment; @@ -225,7 +265,8 @@ public class TvOverlayManager { }); // Menu MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); - mMenu = new Menu(mainActivity, tvView, menuView, new MenuRowFactory(mainActivity, tvView), + mMenu = new Menu(mainActivity, tvView, optionsManager, menuView, + new MenuRowFactory(mainActivity, tvView), new Menu.OnMenuVisibilityChangeListener() { @Override public void onMenuVisibilityChange(boolean visible) { @@ -249,7 +290,7 @@ public class TvOverlayManager { new Runnable() { @Override public void run() { - mMainActivity.showChannelBannerIfHiddenBySideFragment(); + showChannelBannerIfHiddenBySideFragment(); onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); } }); @@ -320,6 +361,9 @@ public class TvOverlayManager { public void release() { mMenu.release(); mHandler.removeCallbacksAndMessages(null); + if (mKeypadChannelSwitchView != null) { + mKeypadChannelSwitchView.setChannels(null); + } } /** @@ -436,6 +480,13 @@ public class TvOverlayManager { onOverlayOpened(OVERLAY_TYPE_DIALOG); } + /** + * Should be called by {@link MainActivity} when the currently browsable channels are updated. + */ + public void onBrowsableChannelsUpdated() { + mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList()); + } + private void runAfterSideFragmentsAreClosed(final Runnable runnable) { if (mSideFragmentManager.isSidePanelVisible()) { // When the side panel is closing, it closes all the fragments, so the new fragment @@ -541,7 +592,7 @@ public class TvOverlayManager { * Shows DVR manager. */ public void showDvrManager() { - Intent intent = new Intent(mMainActivity, DvrActivity.class); + Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class); mMainActivity.startActivity(intent); } @@ -563,6 +614,14 @@ public class TvOverlayManager { new RecentlyWatchedDialogFragment(), false); } + /** + * Shows DVR history dialog. + */ + public void showDvrHistoryDialog() { + showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG, + new DvrHistoryDialogFragment(), false); + } + /** * Shows banner view. */ @@ -570,12 +629,20 @@ public class TvOverlayManager { mTransitionManager.goToChannelBannerScene(); } - public void showKeypadChannelSwitch() { - hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); - mTransitionManager.goToKeypadChannelSwitchScene(); + /** + * Pops up the KeypadChannelSwitchView with the given key input event. + * + * @param keyCode A key code of the key event. + */ + public void showKeypadChannelSwitch(int keyCode) { + if (mChannelTuner.areAllChannelsLoaded()) { + hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + mTransitionManager.goToKeypadChannelSwitchScene(); + mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); + } } /** @@ -618,6 +685,31 @@ public class TvOverlayManager { }); } + /** + * Shows/hides the program guide according to it's hidden or shown now. + * + * @return {@code true} if program guide is going to be shown, otherwise {@code false}. + */ + public boolean toggleProgramGuide() { + if (mProgramGuide.isActive()) { + mProgramGuide.onBackPressed(); + return false; + } else { + showProgramGuide(); + return true; + } + } + + /** + * Sets blocking content rating of the currently playing TV channel. + */ + public void setBlockingContentRating(TvContentRating rating) { + if (!mMainActivity.isChannelChangeKeyDownReceived()) { + mChannelBannerView.setBlockingContentRating(rating); + updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK); + } + } + /** * Hides all the opened overlays according to the flags. */ @@ -631,12 +723,12 @@ public class TvOverlayManager { } else { if (mCurrentDialog != null) { if (mCurrentDialog instanceof PinDialogFragment) { - // The result listener of PinDialogFragment could call MenuView when - // the dialog is dismissed. In order not to call it, set the result listener - // to null. - ((PinDialogFragment) mCurrentDialog).setResultListener(null); + // We don't want any OnPinCheckedListener is triggered to prevent any possible + // side effects. Dismisses the dialog silently. + ((PinDialogFragment) mCurrentDialog).dismissSilently(); + } else { + mCurrentDialog.dismiss(); } - mCurrentDialog.dismiss(); } mPendingDialogActionQueue.clear(); mCurrentDialog = null; @@ -674,7 +766,7 @@ public class TvOverlayManager { } if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) { // Keeps side panels. - } else if (mSideFragmentManager.isSidePanelVisible()) { + } else if (mSideFragmentManager.isActive()) { if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) { mSideFragmentManager.hideSidePanel(withAnimation); } else { @@ -701,6 +793,83 @@ public class TvOverlayManager { || mNewSourcesFragmentActive; } + /** + * Updates and shows channel banner if it's needed. + */ + public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { + if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); + if (mMainActivity.isChannelChangeKeyDownReceived() + && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE + && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { + // Tuning is still ongoing, no need to update banner for other reasons + return; + } + if (!mChannelTuner.isCurrentChannelPassthrough()) { + int lockType = ChannelBannerView.LOCK_NONE; + if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { + if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() + && mMainActivity.getCurrentChannel().isLocked()) { + lockType = ChannelBannerView.LOCK_CHANNEL_INFO; + } else { + // Do not show detailed program information while fast-tuning. + lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; + } + } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE) { + if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()) { + if (mMainActivity.getCurrentChannel().isLocked()) { + lockType = ChannelBannerView.LOCK_CHANNEL_INFO; + } else { + // If parental control is turned on, + // assumes that program is locked by default and waits for onContentAllowed. + lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; + } + } + } else if (mTvView.isScreenBlocked()) { + lockType = ChannelBannerView.LOCK_CHANNEL_INFO; + } else if (mTvView.isContentBlocked() || (mMainActivity.getParentalControlSettings() + .isParentalControlsEnabled() && !mTvView.isVideoOrAudioAvailable())) { + // If the parental control is enabled, do not show the program detail until the + // video becomes available. + lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; + } + // If lock type is not changed, we don't need to update channel banner by parental + // control. + int previousLockType = mChannelBannerView.setLockType(lockType); + if (previousLockType == lockType + && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) { + return; + } else if (reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO) { + mChannelBannerView.updateStreamInfo(mTvView); + // If parental control is enabled, we shows program description when the video is + // available, instead of tuning. Therefore we need to check it here if the program + // description is previously hidden by parental control. + if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL && + lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) { + mChannelBannerView.updateViews(false); + } + } else { + mChannelBannerView.updateViews(reason == UPDATE_CHANNEL_BANNER_REASON_TUNE + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); + } + } + boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); + if (needToShowBanner && !mMainActivity.willShowOverlayUiWhenResume() + && getCurrentDialog() == null + && !isSetupFragmentActive() + && !isNewSourcesFragmentActive()) { + if (mChannelTuner.getCurrentChannel() == null) { + mChannelBannerHiddenBySideFragment = false; + } else if (getSideFragmentManager().isActive()) { + mChannelBannerHiddenBySideFragment = true; + } else { + mChannelBannerHiddenBySideFragment = false; + showBanner(); + } + } + } + @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) { switch (sceneType) { case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER: @@ -749,6 +918,18 @@ public class TvOverlayManager { } } + /** + * Shows the channel banner if it was hidden from the side fragment. + * + *

When the side fragment is visible, showing the channel banner should be put off until the + * side fragment is closed even though the channel changes. + */ + private void showChannelBannerIfHiddenBySideFragment() { + if (mChannelBannerHiddenBySideFragment) { + updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); + } + } + private String toBinaryString(int value) { return String.format("0b%" + NUM_OVERLAY_TYPES + "s", Integer.toBinaryString(value)) .replace(' ', '0'); @@ -843,6 +1024,7 @@ public class TvOverlayManager { timeShiftManager.play(); showMenu(Menu.REASON_PLAY_CONTROLS_PLAY); break; + case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_PAUSE: timeShiftManager.pause(); showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); @@ -916,7 +1098,7 @@ public class TvOverlayManager { } if (mMenu.isActive()) { if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { - mMainActivity.showKeypadChannelSwitchView(keyCode); + showKeypadChannelSwitch(keyCode); return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; @@ -971,6 +1153,7 @@ public class TvOverlayManager { case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD: case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD: + case KeyEvent.KEYCODE_MEDIA_STOP: return true; } return false; diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java index 52e96cc0..628bbb72 100644 --- a/src/com/android/tv/ui/TvTransitionManager.java +++ b/src/com/android/tv/ui/TvTransitionManager.java @@ -129,8 +129,8 @@ public class TvTransitionManager extends TransitionManager { public void goToSelectInputScene() { initIfNeeded(); if (mCurrentScene != mSelectInputScene) { - transitionTo(mSelectInputScene); mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel()); + transitionTo(mSelectInputScene); } } diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java index bf874fc7..f042987a 100644 --- a/src/com/android/tv/ui/TvViewUiManager.java +++ b/src/com/android/tv/ui/TvViewUiManager.java @@ -24,21 +24,19 @@ import android.animation.TimeInterpolator; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Point; import android.hardware.display.DisplayManager; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; import android.util.Property; import android.view.Display; -import android.view.Gravity; -import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; @@ -52,9 +50,8 @@ import com.android.tv.data.DisplayMode; import com.android.tv.util.TvSettings; /** - * The TvViewUiManager is responsible for handling UI layouting and animation of main and PIP - * TvViews. It also control the settings regarding TvView UI such as display mode, PIP layout, - * and PIP size. + * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. + * It also control the settings regarding TvView UI such as display mode. */ public class TvViewUiManager { private static final String TAG = "TvViewManager"; @@ -69,42 +66,44 @@ public class TvViewUiManager { private final Resources mResources; private final FrameLayout mContentView; private final TunableTvView mTvView; - private final TunableTvView mPipView; private final TvOptionsManager mTvOptionsManager; - private final int mTvViewPapWidth; private final int mTvViewShrunkenStartMargin; private final int mTvViewShrunkenEndMargin; - private final int mTvViewPapStartMargin; - private final int mTvViewPapEndMargin; private int mWindowWidth; private int mWindowHeight; - private final int mPipViewHorizontalMargin; - private final int mPipViewTopMargin; - private final int mPipViewBottomMargin; private final SharedPreferences mSharedPreferences; private final TimeInterpolator mLinearOutSlowIn; private final TimeInterpolator mFastOutLinearIn; - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_SET_LAYOUT_PARAMS: - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) msg.obj; - if (DEBUG) { - Log.d(TAG, "setFixedSize: w=" + layoutParams.width + " h=" - + layoutParams.height); - } - mTvView.setLayoutParams(layoutParams); - // Smooth PIP size change, we don't change surface size when - // isInPictureInPictureMode is true. - if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext) - || !((Activity) mContext).isInPictureInPictureMode()) { - mTvView.setFixedSurfaceSize(layoutParams.width, layoutParams.height); + private final Handler mHandler = + new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_LAYOUT_PARAMS: + FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) msg.obj; + if (DEBUG) { + Log.d( + TAG, + "setFixedSize: w=" + + layoutParams.width + + " h=" + + layoutParams.height); + } + mTvView.setTvViewLayoutParams(layoutParams); + mTvView.setLayoutParams(mTvViewFrame); + // Smooth PIP size change, we don't change surface size when + // isInPictureInPictureMode is true. + if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext) + || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && !((Activity) mContext).isInPictureInPictureMode())) { + mTvView.setFixedSurfaceSize( + layoutParams.width, layoutParams.height); + } + break; } - break; - } - } - }; + } + }; private int mDisplayMode; // Used to restore the previous state from ShrunkenTvView state. private int mTvViewStartMarginBeforeShrunken; @@ -113,16 +112,13 @@ public class TvViewUiManager { private boolean mIsUnderShrunkenTvView; private int mTvViewStartMargin; private int mTvViewEndMargin; - private int mPipLayout; - private int mPipSize; - private boolean mPipStarted; private ObjectAnimator mTvViewAnimator; private FrameLayout.LayoutParams mTvViewLayoutParams; // TV view's position when the display mode is FULL. It is used to compute PIP location relative // to TV view's position. - private MarginLayoutParams mTvViewFrame; - private MarginLayoutParams mLastAnimatedTvViewFrame; - private MarginLayoutParams mOldTvViewFrame; + private FrameLayout.LayoutParams mTvViewFrame; + private FrameLayout.LayoutParams mLastAnimatedTvViewFrame; + private FrameLayout.LayoutParams mOldTvViewFrame; private ObjectAnimator mBackgroundAnimator; private int mBackgroundColor; private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED; @@ -130,12 +126,11 @@ public class TvViewUiManager { private int mAppliedTvViewEndMargin; private float mAppliedVideoDisplayAspectRatio; - public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView, + public TvViewUiManager(Context context, TunableTvView tvView, FrameLayout contentView, TvOptionsManager tvOptionManager) { mContext = context; mResources = mContext.getResources(); mTvView = tvView; - mPipView = pipView; mContentView = contentView; mTvOptionsManager = tvOptionManager; @@ -147,18 +142,12 @@ public class TvViewUiManager { mWindowWidth = size.x; mWindowHeight = size.y; - // Have an assumption that PIP and TvView Shrinking happens only in full screen. + // Have an assumption that TvView Shrinking happens only in full screen. mTvViewShrunkenStartMargin = mResources .getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start); mTvViewShrunkenEndMargin = mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end) + mResources.getDimensionPixelSize(R.dimen.side_panel_width); - int papMarginHorizontal = mResources - .getDimensionPixelOffset(R.dimen.papview_margin_horizontal); - int papSpacing = mResources.getDimensionPixelOffset(R.dimen.papview_spacing); - mTvViewPapWidth = (mWindowWidth - papSpacing) / 2 - papMarginHorizontal; - mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing; - mTvViewPapEndMargin = papMarginHorizontal; mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); @@ -167,11 +156,6 @@ public class TvViewUiManager { .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); mFastOutLinearIn = AnimationUtils .loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in); - - mPipViewHorizontalMargin = mResources - .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal); - mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top); - mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom); } public void onConfigurationChanged(final int windowWidth, final int windowHeight) { @@ -200,18 +184,11 @@ public class TvViewUiManager { */ public void startShrunkenTvView() { mIsUnderShrunkenTvView = true; + mTvView.setIsUnderShrunken(true); mTvViewStartMarginBeforeShrunken = mTvViewStartMargin; mTvViewEndMarginBeforeShrunken = mTvViewEndMargin; - if (mPipStarted && getPipLayout() == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { - float sidePanelWidth = mResources.getDimensionPixelOffset(R.dimen.side_panel_width); - float factor = 1.0f - sidePanelWidth / mWindowWidth; - int startMargin = (int) (mTvViewPapStartMargin * factor); - int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth); - setTvViewMargin(startMargin, endMargin); - } else { - setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin); - } + setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin); mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true); } @@ -221,6 +198,7 @@ public class TvViewUiManager { */ public void endShrunkenTvView() { mIsUnderShrunkenTvView = false; + mTvView.setIsUnderShrunken(false); setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken); setDisplayMode(mDisplayModeBeforeShrunken, false, true); } @@ -296,9 +274,9 @@ public class TvViewUiManager { } /** - * Updates TvView. It is called when video resolution is updated. + * Updates TvView's aspect ratio. It should be called when video resolution is changed. */ - public void updateTvView() { + public void updateTvAspectRatio() { applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false); if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) { mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration), @@ -326,120 +304,6 @@ public class TvViewUiManager { } } - /** - * Returns the current PIP layout. The layout should be one of - * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT}, - * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and - * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}. - */ - public int getPipLayout() { - return mPipLayout; - } - - /** - * Sets the PIP layout. The layout should be one of - * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT}, - * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and - * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}. - * - * @param storeInPreference if true, the stored value will be restored by - * {@link #restorePipLayout()}. - */ - public void setPipLayout(int pipLayout, boolean storeInPreference) { - mPipLayout = pipLayout; - if (storeInPreference) { - TvSettings.setPipLayout(mContext, pipLayout); - } - updatePipView(mTvViewFrame); - if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { - setTvViewMargin(mTvViewPapStartMargin, mTvViewPapEndMargin); - setDisplayMode(DisplayMode.MODE_NORMAL, false, false); - } else { - setTvViewMargin(0, 0); - restoreDisplayMode(false); - } - mTvOptionsManager.onPipLayoutChanged(pipLayout); - } - - /** - * Restores the PIP layout which {@link #setPipLayout} lastly stores. - */ - public void restorePipLayout() { - setPipLayout(TvSettings.getPipLayout(mContext), false); - } - - /** - * Called when PIP is started. - */ - public void onPipStart() { - mPipStarted = true; - updatePipView(); - mPipView.setVisibility(View.VISIBLE); - } - - /** - * Called when PIP is stopped. - */ - public void onPipStop() { - setTvViewMargin(0, 0); - mPipView.setVisibility(View.GONE); - mPipStarted = false; - } - - /** - * Called when PIP is resumed. - */ - public void showPipForResume() { - mPipView.setVisibility(View.VISIBLE); - } - - /** - * Called when PIP is paused. - */ - public void hidePipForPause() { - if (mPipLayout != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { - mPipView.setVisibility(View.GONE); - } - } - - /** - * Updates PIP view. It is usually called, when video resolution in PIP is updated. - */ - public void updatePipView() { - updatePipView(mTvViewFrame); - } - - /** - * Returns the size of the PIP view. - */ - public int getPipSize() { - return mPipSize; - } - - /** - * Sets PIP size and applies it immediately. - * - * @param pipSize PIP size. The value should be one of {@link TvSettings#PIP_SIZE_BIG} - * and {@link TvSettings#PIP_SIZE_SMALL}. - * @param storeInPreference if true, the stored value will be restored by - * {@link #restorePipSize()}. - */ - public void setPipSize(int pipSize, boolean storeInPreference) { - mPipSize = pipSize; - if (storeInPreference) { - TvSettings.setPipSize(mContext, pipSize); - } - updatePipView(mTvViewFrame); - mTvOptionsManager.onPipSizeChanged(pipSize); - } - - /** - * Restores the PIP size which {@link #setPipSize} lastly stores. - */ - public void restorePipSize() { - setPipSize(TvSettings.getPipSize(mContext), false); - } - /** * This margins will be applied when applyDisplayMode is called. */ @@ -488,14 +352,14 @@ public class TvViewUiManager { } private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams, - MarginLayoutParams tvViewFrame, boolean animate) { + FrameLayout.LayoutParams tvViewFrame, boolean animate) { if (DEBUG) { Log.d(TAG, "setTvViewPosition: w=" + layoutParams.width + " h=" + layoutParams.height + " s=" + layoutParams.getMarginStart() + " t=" + layoutParams.topMargin + " e=" + layoutParams.getMarginEnd() + " b=" + layoutParams.bottomMargin + " animate=" + animate); } - MarginLayoutParams oldTvViewFrame = mTvViewFrame; + FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame; mTvViewLayoutParams = layoutParams; mTvViewFrame = tvViewFrame; if (animate) { @@ -503,11 +367,11 @@ public class TvViewUiManager { if (mTvViewAnimator.isStarted()) { // Cancel the current animation and start new one. mTvViewAnimator.cancel(); - mOldTvViewFrame = mLastAnimatedTvViewFrame; + mOldTvViewFrame = new FrameLayout.LayoutParams(mLastAnimatedTvViewFrame); } else { - mOldTvViewFrame = oldTvViewFrame; + mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame); } - mTvViewAnimator.setObjectValues(mTvView.getLayoutParams(), layoutParams); + mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams); mTvViewAnimator.setEvaluator(new TypeEvaluator() { FrameLayout.LayoutParams lp; @Override @@ -517,7 +381,7 @@ public class TvViewUiManager { lp = new FrameLayout.LayoutParams(0, 0); lp.gravity = startValue.gravity; } - interpolateMarginsRelative(lp, startValue, endValue, fraction); + interpolateMargins(lp, startValue, endValue, fraction); return lp; } }); @@ -538,116 +402,10 @@ public class TvViewUiManager { mHandler.removeMessages(MSG_SET_LAYOUT_PARAMS); mHandler.obtainMessage(MSG_SET_LAYOUT_PARAMS, layoutParams).sendToTarget(); } else { - mTvView.setLayoutParams(layoutParams); - } - updatePipView(mTvViewFrame); - } - } - - /** - * The redlines assume that the ratio of the TV screen is 16:9. If the radio is not 16:9, the - * layout of PAP can be broken. - */ - @SuppressLint("RtlHardcoded") - private void updatePipView(MarginLayoutParams tvViewFrame) { - if (!mPipStarted) { - return; - } - int width; - int height; - int startMargin; - int endMargin; - int topMargin; - int bottomMargin; - int gravity; - - if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) { - gravity = Gravity.CENTER_VERTICAL | Gravity.START; - height = tvViewFrame.height; - float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio(); - if (videoDisplayAspectRatio <= 0f) { - width = tvViewFrame.width; - } else { - width = (int) (height * videoDisplayAspectRatio); - if (width > tvViewFrame.width) { - width = tvViewFrame.width; - } - } - startMargin = mResources.getDimensionPixelOffset(R.dimen.papview_margin_horizontal) - * tvViewFrame.width / mTvViewPapWidth + (tvViewFrame.width - width) / 2; - endMargin = 0; - topMargin = 0; - bottomMargin = 0; - } else { - int tvViewWidth = tvViewFrame.width; - int tvViewHeight = tvViewFrame.height; - int tvStartMargin = tvViewFrame.getMarginStart(); - int tvEndMargin = tvViewFrame.getMarginEnd(); - int tvTopMargin = tvViewFrame.topMargin; - int tvBottomMargin = tvViewFrame.bottomMargin; - float horizontalScaleFactor = (float) tvViewWidth / mWindowWidth; - float verticalScaleFactor = (float) tvViewHeight / mWindowHeight; - - int maxWidth; - if (mPipSize == TvSettings.PIP_SIZE_SMALL) { - maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_width) - * horizontalScaleFactor); - height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_height) - * verticalScaleFactor); - } else if (mPipSize == TvSettings.PIP_SIZE_BIG) { - maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_width) - * horizontalScaleFactor); - height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_height) - * verticalScaleFactor); - } else { - throw new IllegalArgumentException("Invalid PIP size: " + mPipSize); - } - float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio(); - if (videoDisplayAspectRatio <= 0f) { - width = maxWidth; - } else { - width = (int) (height * videoDisplayAspectRatio); - if (width > maxWidth) { - width = maxWidth; - } - } - - startMargin = tvStartMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor); - endMargin = tvEndMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor); - topMargin = tvTopMargin + (int) (mPipViewTopMargin * verticalScaleFactor); - bottomMargin = tvBottomMargin + (int) (mPipViewBottomMargin * verticalScaleFactor); - - switch (mPipLayout) { - case TvSettings.PIP_LAYOUT_TOP_LEFT: - gravity = Gravity.TOP | Gravity.LEFT; - break; - case TvSettings.PIP_LAYOUT_TOP_RIGHT: - gravity = Gravity.TOP | Gravity.RIGHT; - break; - case TvSettings.PIP_LAYOUT_BOTTOM_LEFT: - gravity = Gravity.BOTTOM | Gravity.LEFT; - break; - case TvSettings.PIP_LAYOUT_BOTTOM_RIGHT: - gravity = Gravity.BOTTOM | Gravity.RIGHT; - break; - default: - throw new IllegalArgumentException("Invalid PIP location: " + mPipLayout); + mTvView.setTvViewLayoutParams(layoutParams); + mTvView.setLayoutParams(mTvViewFrame); } } - - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams(); - if (lp.width != width || lp.height != height || lp.getMarginStart() != startMargin - || lp.getMarginEnd() != endMargin || lp.topMargin != topMargin - || lp.bottomMargin != bottomMargin || lp.gravity != gravity) { - lp.width = width; - lp.height = height; - lp.setMarginStart(startMargin); - lp.setMarginEnd(endMargin); - lp.topMargin = topMargin; - lp.bottomMargin = bottomMargin; - lp.gravity = gravity; - mPipView.setLayoutParams(lp); - } } private void initTvAnimatorIfNeeded() { @@ -663,7 +421,7 @@ public class TvViewUiManager { // because TvView may request layout itself during animation and layout SurfaceView with // its own parameters when TvInputService requests to do so. mTvViewAnimator = new ObjectAnimator(); - mTvViewAnimator.setTarget(mTvView); + mTvViewAnimator.setTarget(mTvView.getTvView()); mTvViewAnimator.setProperty( Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams")); mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration)); @@ -693,10 +451,10 @@ public class TvViewUiManager { @Override public void onAnimationUpdate(ValueAnimator animator) { float fraction = animator.getAnimatedFraction(); - mLastAnimatedTvViewFrame = new MarginLayoutParams(0, 0); - interpolateMarginsRelative(mLastAnimatedTvViewFrame, + mLastAnimatedTvViewFrame = (FrameLayout.LayoutParams) mTvView.getLayoutParams(); + interpolateMargins(mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction); - updatePipView(mLastAnimatedTvViewFrame); + mTvView.setLayoutParams(mLastAnimatedTvViewFrame); } }); } @@ -745,66 +503,58 @@ public class TvViewUiManager { } int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin; int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth; - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0, - ((FrameLayout.LayoutParams) mTvView.getLayoutParams()).gravity); int displayMode = mDisplayMode; - double availableAreaRatio = 0; - double videoRatio = 0; + float availableAreaRatio = 0; if (availableAreaWidth <= 0 || availableAreaHeight <= 0) { displayMode = DisplayMode.MODE_FULL; Log.w(TAG, "Some resolution info is missing during applyDisplayMode. (" + "availableAreaWidth=" + availableAreaWidth + ", availableAreaHeight=" + availableAreaHeight + ")"); } else { - availableAreaRatio = (double) availableAreaWidth / availableAreaHeight; - videoRatio = videoDisplayAspectRatio; + availableAreaRatio = (float) availableAreaWidth / availableAreaHeight; } - - int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2; - MarginLayoutParams tvViewFrame = createMarginLayoutParams( - mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); - layoutParams.width = availableAreaWidth; - layoutParams.height = availableAreaHeight; + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0, + ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity); switch (displayMode) { - case DisplayMode.MODE_FULL: - layoutParams.width = availableAreaWidth; - layoutParams.height = availableAreaHeight; - break; case DisplayMode.MODE_ZOOM: - if (videoRatio < availableAreaRatio) { + if (videoDisplayAspectRatio < availableAreaRatio) { // Y axis will be clipped. layoutParams.width = availableAreaWidth; - layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio); + layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio); } else { // X axis will be clipped. - layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio); + layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio); layoutParams.height = availableAreaHeight; } break; case DisplayMode.MODE_NORMAL: - if (videoRatio < availableAreaRatio) { + if (videoDisplayAspectRatio < availableAreaRatio) { // X axis has black area. - layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio); + layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio); layoutParams.height = availableAreaHeight; } else { // Y axis has black area. layoutParams.width = availableAreaWidth; - layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio); + layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio); } break; + case DisplayMode.MODE_FULL: + default: + layoutParams.width = availableAreaWidth; + layoutParams.height = availableAreaHeight; + break; } - // FrameLayout has an issue with centering when left and right margins differ. // So stick to Gravity.START | Gravity.CENTER_VERTICAL. - int marginStart = mTvViewStartMargin + (availableAreaWidth - layoutParams.width) / 2; + int marginStart = (availableAreaWidth - layoutParams.width) / 2; layoutParams.setMarginStart(marginStart); - // Set marginEnd as well because setTvViewPosition uses both start/end margin. - layoutParams.setMarginEnd(mWindowWidth - layoutParams.width - marginStart); - + int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2; + FrameLayout.LayoutParams tvViewFrame = createMarginLayoutParams( + mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); + setTvViewPosition(layoutParams, tvViewFrame, animate); setBackgroundColor(mResources.getColor(isTvViewFullScreen() ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview, - null), layoutParams, animate); - setTvViewPosition(layoutParams, tvViewFrame, animate); + null), layoutParams, animate); // Update the current display mode. mTvOptionsManager.onDisplayModeChanged(displayMode); @@ -814,7 +564,7 @@ public class TvViewUiManager { return (int) (start + (end - start) * fraction); } - private static void interpolateMarginsRelative(MarginLayoutParams out, + private static void interpolateMargins(MarginLayoutParams out, MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction) { out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction); out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction); @@ -825,9 +575,9 @@ public class TvViewUiManager { out.height = interpolate(startValue.height, endValue.height, fraction); } - private MarginLayoutParams createMarginLayoutParams( + private FrameLayout.LayoutParams createMarginLayoutParams( int startMargin, int endMargin, int topMargin, int bottomMargin) { - MarginLayoutParams lp = new MarginLayoutParams(0, 0); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(0, 0); lp.setMarginStart(startMargin); lp.setMarginEnd(endMargin); lp.topMargin = topMargin; @@ -836,4 +586,4 @@ public class TvViewUiManager { lp.height = mWindowHeight - topMargin - bottomMargin; return lp; } -} +} \ No newline at end of file diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java index 23aff91c..cd70a886 100644 --- a/src/com/android/tv/ui/sidepanel/ActionItem.java +++ b/src/com/android/tv/ui/sidepanel/ActionItem.java @@ -17,7 +17,6 @@ package com.android.tv.ui.sidepanel; import android.view.View; -import android.widget.ImageView; import android.widget.TextView; import com.android.tv.R; @@ -25,24 +24,14 @@ import com.android.tv.R; public abstract class ActionItem extends Item { private final String mTitle; private final String mDescription; - private final int mIconId; public ActionItem(String title) { - this(title, null, 0); + this(title, null); } public ActionItem(String title, String description) { - this(title, description, 0); - } - - public ActionItem(String title, int iconId) { - this(title, null, iconId); - } - - public ActionItem(String title, String description, int iconId) { mTitle = title; mDescription = description; - mIconId = iconId; } @Override @@ -62,12 +51,5 @@ public abstract class ActionItem extends Item { } else { descriptionView.setVisibility(View.GONE); } - ImageView iconView = (ImageView) view.findViewById(R.id.icon); - if (mIconId != 0) { - iconView.setVisibility(View.VISIBLE); - iconView.setImageResource(mIconId); - } else { - iconView.setVisibility(View.GONE); - } } } \ No newline at end of file diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java index d6ccdf6b..341e4350 100644 --- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java +++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java @@ -18,6 +18,7 @@ package com.android.tv.ui.sidepanel; import android.media.tv.TvTrackInfo; import android.os.Bundle; +import android.text.TextUtils; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -25,7 +26,6 @@ import android.view.ViewGroup; import com.android.tv.R; import com.android.tv.util.CaptionSettings; -import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -38,8 +38,6 @@ public class ClosedCaptionFragment extends SideFragment { private String mClosedCaptionLanguage; private String mClosedCaptionTrackId; private ClosedCaptionOptionItem mSelectedItem; - private List mItems; - private boolean mPaused; public ClosedCaptionFragment() { super(KeyEvent.KEYCODE_CAPTIONS, KeyEvent.KEYCODE_S); @@ -63,37 +61,32 @@ public class ClosedCaptionFragment extends SideFragment { mClosedCaptionLanguage = captionSettings.getLanguage(); mClosedCaptionTrackId = captionSettings.getTrackId(); - mItems = new ArrayList<>(); + List items = new ArrayList<>(); mSelectedItem = null; List tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_SUBTITLE); if (tracks != null && !tracks.isEmpty()) { - String trackId = captionSettings.isEnabled() ? + String selectedTrackId = captionSettings.isEnabled() ? getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE) : null; - boolean isEnabled = trackId != null; - - ClosedCaptionOptionItem item = new ClosedCaptionOptionItem( - getString(R.string.closed_caption_option_item_off), - CaptionSettings.OPTION_OFF, null, null); - // Pick 'Off' as default because we may fail to find the matching language. - mSelectedItem = item; - if (!isEnabled) { + ClosedCaptionOptionItem item = new ClosedCaptionOptionItem(null, null); + items.add(item); + if (selectedTrackId == null) { + mSelectedItem = item; item.setChecked(true); + setSelectedPosition(0); } - mItems.add(item); - - for (final TvTrackInfo track : tracks) { - item = new ClosedCaptionOptionItem(getLabel(track), - CaptionSettings.OPTION_ON, track.getId(), track.getLanguage()); - if (isEnabled && track.getId().equals(trackId)) { - item.setChecked(true); + for (int i = 0; i < tracks.size(); i++) { + item = new ClosedCaptionOptionItem(tracks.get(i), i); + if (TextUtils.equals(selectedTrackId, tracks.get(i).getId())) { mSelectedItem = item; + item.setChecked(true); + setSelectedPosition(i + 1); } - mItems.add(item); + items.add(item); } } if (getMainActivity().hasCaptioningSettingsActivity()) { - mItems.add(new ActionItem(getString(R.string.closed_caption_system_settings), + items.add(new ActionItem(getString(R.string.closed_caption_system_settings), getString(R.string.closed_caption_system_settings_description)) { @Override protected void onSelected() { @@ -103,14 +96,14 @@ public class ClosedCaptionFragment extends SideFragment { @Override protected void onFocused() { super.onFocused(); - if (!mPaused && mSelectedItem != null) { + if (mSelectedItem != null) { getMainActivity().selectSubtitleTrack( mSelectedItem.mOption, mSelectedItem.mTrackId); } } }); } - return mItems; + return items; } @Override @@ -119,50 +112,6 @@ public class ClosedCaptionFragment extends SideFragment { return super.onCreateView(inflater, container, savedInstanceState); } - @Override - public void onResume() { - super.onResume(); - if (mPaused) { - // Apply system's closed caption settings to the UI. - CaptionSettings captionSettings = getMainActivity().getCaptionSettings(); - mClosedCaptionOption = CaptionSettings.OPTION_SYSTEM; - mClosedCaptionLanguage = captionSettings.getSystemLanguage(); - ClosedCaptionOptionItem selectedItem = null; - if (captionSettings.isSystemSettingEnabled()) { - for (Item item : mItems) { - if (!(item instanceof ClosedCaptionOptionItem)) { - continue; - } - ClosedCaptionOptionItem captionItem = (ClosedCaptionOptionItem) item; - if (Utils.isEqualLanguage(captionItem.mLanguage, mClosedCaptionLanguage)) { - selectedItem = captionItem; - break; - } - } - } - if (mSelectedItem != null) { - mSelectedItem.setChecked(false); - } - if (selectedItem == null && mItems.get(0) instanceof ClosedCaptionOptionItem) { - selectedItem = (ClosedCaptionOptionItem) mItems.get(0); - } - if (selectedItem != null) { - selectedItem.setChecked(true); - } - // We shouldn't call MainActivity.selectSubtitleTrack() here because - // 1. Tracks are not available because video is just started at this moment. - // 2. MainActivity will apply system settings when video's tracks are available. - mSelectedItem = selectedItem; - } - mPaused = false; - } - - @Override - public void onPause() { - super.onPause(); - mPaused = true; - } - @Override public void onDestroyView() { if (mResetClosedCaption) { @@ -172,23 +121,28 @@ public class ClosedCaptionFragment extends SideFragment { super.onDestroyView(); } - private String getLabel(TvTrackInfo track) { - if (track.getLanguage() != null) { + private String getLabel(TvTrackInfo track, Integer trackIndex) { + if (track == null) { + return getString(R.string.closed_caption_option_item_off); + } else if (track.getLanguage() != null) { return new Locale(track.getLanguage()).getDisplayName(); } - return getString(R.string.default_language); + return getString(R.string.closed_caption_unknown_language, trackIndex + 1); } private class ClosedCaptionOptionItem extends RadioButtonItem { private final int mOption; private final String mTrackId; - private final String mLanguage; - private ClosedCaptionOptionItem(String title, int option, String trackId, String language) { - super(title); - mOption = option; - mTrackId = trackId; - mLanguage = language; + private ClosedCaptionOptionItem(TvTrackInfo track, Integer trackIndex) { + super(getLabel(track, trackIndex)); + if (track == null) { + mOption = CaptionSettings.OPTION_OFF; + mTrackId = null; + } else { + mOption = CaptionSettings.OPTION_ON; + mTrackId = track.getId(); + } } @Override diff --git a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java index 7613a9a2..c2746937 100644 --- a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java +++ b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java @@ -23,9 +23,12 @@ import android.widget.TextView; import com.android.tv.R; public abstract class CompoundButtonItem extends Item { + private static int sDefaultMaxLine = 0; + private final String mCheckedTitle; private final String mUncheckedTitle; private final String mDescription; + private final int mMaxLine; private boolean mChecked; private TextView mTextView; private CompoundButton mCompoundButton; @@ -38,6 +41,15 @@ public abstract class CompoundButtonItem extends Item { mCheckedTitle = checkedTitle; mUncheckedTitle = uncheckedTitle; mDescription = description; + mMaxLine = 0; + } + + public CompoundButtonItem(String checkedTitle, String uncheckedTitle, String description, + int maxLine) { + mCheckedTitle = checkedTitle; + mUncheckedTitle = uncheckedTitle; + mDescription = description; + mMaxLine = maxLine; } protected abstract int getCompoundButtonId(); @@ -57,6 +69,15 @@ public abstract class CompoundButtonItem extends Item { mTextView = (TextView) view.findViewById(getTitleViewId()); TextView descriptionView = (TextView) view.findViewById(getDescriptionViewId()); if (mDescription != null) { + if (mMaxLine != 0) { + descriptionView.setMaxLines(mMaxLine); + } else { + if (sDefaultMaxLine == 0) { + sDefaultMaxLine = view.getContext().getResources() + .getInteger(R.integer.option_item_description_max_lines); + } + descriptionView.setMaxLines(sDefaultMaxLine); + } descriptionView.setVisibility(View.VISIBLE); descriptionView.setText(mDescription); } else { diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java index 9cc54ed2..297e69d9 100644 --- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java +++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java @@ -16,6 +16,8 @@ package com.android.tv.ui.sidepanel; +import android.content.Context; +import android.content.SharedPreferences; import android.media.tv.TvContract.Channels; import android.os.Bundle; import android.support.v17.leanback.widget.VerticalGridView; @@ -27,6 +29,7 @@ import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; +import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; import com.android.tv.ui.OnRepeatedKeyInterceptListener; @@ -36,39 +39,38 @@ import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.List; import java.util.Iterator; +import java.util.List; public class CustomizeChannelListFragment extends SideFragment { private static final int GROUP_BY_SOURCE = 0; private static final int GROUP_BY_HD_SD = 1; private static final String TRACKER_LABEL = "customize channel list"; - private final List mChannels = new ArrayList<>(); - private final long mInitialChannelId; + private static final String PREF_KEY_GROUP_SETTINGS = "pref_key_group_settigns"; + private final List mChannels = new ArrayList<>(); + private long mInitialChannelId = Channel.INVALID_ID; private long mLastFocusedChannelId = Channel.INVALID_ID; - private int mGroupingType = GROUP_BY_SOURCE; + private static Integer sGroupingType; private TvInputManagerHelper mInputManager; private Channel.DefaultComparator mChannelComparator; private boolean mGroupByFragmentRunning; private final List mItems = new ArrayList<>(); - public CustomizeChannelListFragment() { - this(Channel.INVALID_ID); - } - - public CustomizeChannelListFragment(long initialChannelId) { - mInitialChannelId = initialChannelId; - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInputManager = getMainActivity().getTvInputManagerHelper(); + mInitialChannelId = getMainActivity().getCurrentChannelId(); mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager); + if (sGroupingType == null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + sGroupingType = sharedPreferences.getInt(PREF_KEY_GROUP_SETTINGS, GROUP_BY_SOURCE); + } } @Override @@ -128,12 +130,15 @@ public class CustomizeChannelListFragment extends SideFragment { @Override public void onDestroyView() { getChannelDataManager().applyUpdatedValuesToDb(); - if (!mGroupByFragmentRunning) { - getMainActivity().endShrunkenTvView(); - } super.onDestroyView(); } + @Override + public void onDestroy() { + super.onDestroy(); + getMainActivity().endShrunkenTvView(); + } + @Override protected String getTitle() { return getString(R.string.side_panel_title_edit_channels_for_an_input); @@ -149,7 +154,7 @@ public class CustomizeChannelListFragment extends SideFragment { mItems.clear(); mChannels.clear(); mChannels.addAll(getChannelDataManager().getChannelList()); - if (mGroupingType == GROUP_BY_SOURCE) { + if (sGroupingType == GROUP_BY_SOURCE) { addItemForGroupBySource(mItems); } else { // GROUP_BY_HD_SD @@ -321,6 +326,49 @@ public class CustomizeChannelListFragment extends SideFragment { } } + public static class GroupByFragment extends SideFragment { + @Override + protected String getTitle() { + return getString(R.string.side_panel_title_group_by); + } + @Override + public String getTrackerLabel() { + return GroupBySubMenu.TRACKER_LABEL; + } + + @Override + protected List getItemList() { + List items = new ArrayList<>(); + items.add(new RadioButtonItem( + getString(R.string.edit_channels_group_by_sources)) { + @Override + protected void onSelected() { + super.onSelected(); + setGroupingType(GROUP_BY_SOURCE); + closeFragment(); + } + }); + items.add(new RadioButtonItem( + getString(R.string.edit_channels_group_by_hd_sd)) { + @Override + protected void onSelected() { + super.onSelected(); + setGroupingType(GROUP_BY_HD_SD); + closeFragment(); + } + }); + ((RadioButtonItem) items.get(sGroupingType)).setChecked(true); + return items; + } + + private void setGroupingType(int groupingType) { + sGroupingType = groupingType; + SharedPreferences sharedPreferences = getContext().getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + sharedPreferences.edit().putInt(PREF_KEY_GROUP_SETTINGS, groupingType).apply(); + } + } + private class GroupBySubMenu extends SubMenuItem { private static final String TRACKER_LABEL = "Group by"; public GroupBySubMenu(String description) { @@ -330,41 +378,7 @@ public class CustomizeChannelListFragment extends SideFragment { @Override protected SideFragment getFragment() { - return new SideFragment() { - @Override - protected String getTitle() { - return getString(R.string.side_panel_title_group_by); - } - @Override - public String getTrackerLabel() { - return GroupBySubMenu.TRACKER_LABEL; - } - - @Override - protected List getItemList() { - List items = new ArrayList<>(); - items.add(new RadioButtonItem( - getString(R.string.edit_channels_group_by_sources)) { - @Override - protected void onSelected() { - super.onSelected(); - mGroupingType = GROUP_BY_SOURCE; - closeFragment(); - } - }); - items.add(new RadioButtonItem( - getString(R.string.edit_channels_group_by_hd_sd)) { - @Override - protected void onSelected() { - super.onSelected(); - mGroupingType = GROUP_BY_HD_SD; - closeFragment(); - } - }); - ((RadioButtonItem) items.get(mGroupingType)).setChecked(true); - return items; - } - }; + return new GroupByFragment(); } @Override diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java index 0d189cca..f633fa5a 100644 --- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java +++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java @@ -18,8 +18,6 @@ package com.android.tv.ui.sidepanel; import android.accounts.Account; import android.app.Activity; -import android.app.ApplicationErrorReport; -import android.content.Intent; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; @@ -27,9 +25,11 @@ import android.widget.Toast; import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.BuildConfig; +import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.epg.EpgFetcher; import com.android.tv.experiments.Experiments; import com.android.tv.tuner.TunerPreferences; +import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -54,7 +54,15 @@ public class DeveloperOptionFragment extends SideFragment { @Override protected List getItemList() { List items = new ArrayList<>(); - if (BuildConfig.ENG) { + if (CommonFeatures.DVR.isEnabled(getContext())) { + items.add(new ActionItem(getString(R.string.dev_item_dvr_history)) { + @Override + protected void onSelected() { + getMainActivity().getOverlayManager().showDvrHistoryDialog(); + } + }); + } + if (Utils.isDeveloper()) { items.add(new ActionItem(getString(R.string.dev_item_watch_history)) { @Override protected void onSelected() { @@ -62,18 +70,6 @@ public class DeveloperOptionFragment extends SideFragment { } }); } - items.add(new ActionItem(getString(R.string.dev_item_send_feedback)) { - @Override - protected void onSelected() { - Intent intent = new Intent(Intent.ACTION_APP_ERROR); - ApplicationErrorReport report = new ApplicationErrorReport(); - report.packageName = report.processName = getContext().getPackageName(); - report.time = System.currentTimeMillis(); - report.type = ApplicationErrorReport.TYPE_NONE; - intent.putExtra(Intent.EXTRA_BUG_REPORT, report); - startActivityForResult(intent, 0); - } - }); items.add(new SwitchItem(getString(R.string.dev_item_store_ts_on), getString(R.string.dev_item_store_ts_off), getString(R.string.dev_item_store_ts_description)) { @@ -89,13 +85,18 @@ public class DeveloperOptionFragment extends SideFragment { TunerPreferences.setStoreTsStream(getContext(), isChecked()); } }); + if (Utils.isDeveloper()) { + items.add( + new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) { + @Override + protected void onSelected() { + TvApplication.getSingletons(getContext()) + .getPerformanceMonitor() + .startPerformanceMonitorEventDebugActivity(getContext()); + } + }); + } return items; } - - /** True if there is the dev options menu */ - public static boolean shouldShow() { - return Experiments.ENABLE_DEVELOPER_FEATURES.get() || BuildConfig.ENG; - } - -} +} \ No newline at end of file diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java index 00f16427..4e47e75b 100644 --- a/src/com/android/tv/ui/sidepanel/Item.java +++ b/src/com/android/tv/ui/sidepanel/Item.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; public abstract class Item { private View mItemView; private boolean mEnabled = true; + private boolean mClickable = true; public void setEnabled(boolean enabled) { if (mEnabled != enabled) { @@ -34,6 +35,16 @@ public abstract class Item { } } + /** + * Sets the item to be clickable or not. + */ + public void setClickable(boolean clickable) { + mClickable = clickable; + if (mItemView != null) { + mItemView.setClickable(clickable); + } + } + /** * Returns whether this item is enabled. */ @@ -64,6 +75,7 @@ public abstract class Item { */ protected void onUpdate() { setEnabledInternal(mItemView, mEnabled); + mItemView.setClickable(mClickable); } protected abstract void onSelected(); diff --git a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java deleted file mode 100644 index dec017a8..00000000 --- a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.ui.sidepanel; - -import android.media.tv.TvInputInfo; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.tv.R; -import com.android.tv.util.PipInputManager; -import com.android.tv.util.PipInputManager.PipInput; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class PipInputSelectorFragment extends SideFragment { - private static final String TAG = "PipInputSelector"; - private static final String TRACKER_LABEL = "PIP input source"; - - private final List mInputItems = new ArrayList<>(); - private PipInputManager mPipInputManager; - private PipInput mInitialPipInput; - private boolean mSelected; - - private final PipInputManager.Listener mPipInputListener = new PipInputManager.Listener() { - @Override - public void onPipInputStateUpdated() { - notifyDataSetChanged(); - } - - @Override - public void onPipInputListUpdated() { - refreshInputList(); - setItems(mInputItems); - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mPipInputManager = getMainActivity().getPipInputManager(); - mPipInputManager.addListener(mPipInputListener); - getMainActivity().startShrunkenTvView(false, false); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onStart() { - super.onStart(); - mInitialPipInput = mPipInputManager.getPipInput(getMainActivity().getPipChannel()); - if (mInitialPipInput == null) { - Log.w(TAG, "PIP should be on"); - closeFragment(); - } - int count = 0; - for (Item item : mInputItems) { - InputItem inputItem = (InputItem) item; - if (Objects.equals(inputItem.mPipInput, mInitialPipInput)) { - setSelectedPosition(count); - break; - } - ++count; - } - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mPipInputManager.removeListener(mPipInputListener); - if (!mSelected) { - getMainActivity().tuneToChannelForPip(mInitialPipInput.getChannel()); - } - getMainActivity().endShrunkenTvView(); - } - - @Override - protected String getTitle() { - return getString(R.string.side_panel_title_pip_input_source); - } - - @Override - public String getTrackerLabel() { - return TRACKER_LABEL; - } - - @Override - protected List getItemList() { - refreshInputList(); - return mInputItems; - } - - private void refreshInputList() { - mInputItems.clear(); - for (PipInput input : mPipInputManager.getPipInputList(false)) { - mInputItems.add(new InputItem(input)); - } - } - - private class InputItem extends RadioButtonItem { - private final PipInput mPipInput; - - private InputItem(PipInput input) { - super(input.getLongLabel()); - mPipInput = input; - setEnabled(isAvailable()); - } - - @Override - protected void onUpdate() { - super.onUpdate(); - setEnabled(mPipInput.isAvailable()); - setChecked(mPipInput == mInitialPipInput); - } - - @Override - protected void onFocused() { - super.onFocused(); - if (isEnabled()) { - getMainActivity().tuneToChannelForPip(mPipInput.getChannel()); - } - } - - @Override - protected void onSelected() { - super.onSelected(); - if (isEnabled()) { - mSelected = true; - closeFragment(); - } - } - - private boolean isAvailable() { - if (!mPipInput.isAvailable()) { - return false; - } - - // If this input shares the same parent with the current main input, you cannot select - // it. (E.g. two HDMI CEC devices that are connected to HDMI port 1 through an A/V - // receiver.) - PipInput pipInput = mPipInputManager.getPipInput(getMainActivity().getCurrentChannel()); - if (pipInput == null) { - return false; - } - TvInputInfo mainInputInfo = pipInput.getInputInfo(); - TvInputInfo pipInputInfo = mPipInput.getInputInfo(); - return mainInputInfo == null || pipInputInfo == null - || !TextUtils.equals(mainInputInfo.getId(), pipInputInfo.getId()) - && !TextUtils.equals(mainInputInfo.getParentId(), pipInputInfo.getParentId()); - } - } -} diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java index e8033a22..6a5b510c 100644 --- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java @@ -16,18 +16,25 @@ package com.android.tv.ui.sidepanel; +import static com.android.tv.Features.TUNER; + +import android.app.ApplicationErrorReport; +import android.content.Intent; +import android.media.tv.TvInputInfo; import android.view.View; import android.widget.Toast; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; +import com.android.tv.customization.TvCustomizationManager; import com.android.tv.dialog.PinDialogFragment; -import com.android.tv.dialog.WebDialogFragment; -import com.android.tv.license.LicenseUtils; -import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment; +import com.android.tv.license.LicenseSideFragment; +import com.android.tv.license.Licenses; +import com.android.tv.tuner.TunerPreferences; import com.android.tv.util.PermissionUtils; import com.android.tv.util.SetupUtils; +import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -38,33 +45,6 @@ import java.util.List; public class SettingsFragment extends SideFragment { private static final String TRACKER_LABEL = "settings"; - private final long mCurrentChannelId; - - public SettingsFragment(long currentChannelId) { - mCurrentChannelId = currentChannelId; - } - - /** - * Opens a dialog showing open source licenses. - */ - public static final class LicenseActionItem extends ActionItem { - public final static String DIALOG_TAG = LicenseActionItem.class.getSimpleName(); - public static final String TRACKER_LABEL = "Open Source Licenses"; - private final MainActivity mMainActivity; - - public LicenseActionItem(MainActivity mainActivity) { - super(mainActivity.getString(R.string.settings_menu_licenses)); - mMainActivity = mainActivity; - } - - @Override - protected void onSelected() { - WebDialogFragment dialog = WebDialogFragment.newInstance(LicenseUtils.LICENSE_FILE, - mMainActivity.getString(R.string.dialog_title_licenses), TRACKER_LABEL); - mMainActivity.getOverlayManager().showDialogFragment(DIALOG_TAG, dialog, false); - } - } - @Override protected String getTitle() { return getResources().getString(R.string.side_panel_title_settings); @@ -80,11 +60,11 @@ public class SettingsFragment extends SideFragment { List items = new ArrayList<>(); final Item customizeChannelListItem = new SubMenuItem( getString(R.string.settings_channel_source_item_customize_channels), - getString(R.string.settings_channel_source_item_customize_channels_description), 0, + getString(R.string.settings_channel_source_item_customize_channels_description), getMainActivity().getOverlayManager().getSideFragmentManager()) { @Override protected SideFragment getFragment() { - return new CustomizeChannelListFragment(mCurrentChannelId); + return new CustomizeChannelListFragment(); } @Override @@ -122,25 +102,11 @@ public class SettingsFragment extends SideFragment { : R.string.option_toggle_parental_controls_off)) { @Override protected void onSelected() { - final MainActivity tvActivity = getMainActivity(); - final SideFragmentManager sideFragmentManager = tvActivity.getOverlayManager() - .getSideFragmentManager(); - sideFragmentManager.hideSidePanel(true); - PinDialogFragment fragment = new PinDialogFragment( - PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - if (success) { - sideFragmentManager - .show(new ParentalControlsFragment(), false); - sideFragmentManager.showSidePanel(true); - } else { - sideFragmentManager.hideAll(false); - } - } - }); - tvActivity.getOverlayManager() + getMainActivity().getOverlayManager() + .getSideFragmentManager().hideSidePanel(true); + PinDialogFragment fragment = PinDialogFragment + .create(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN); + getMainActivity().getOverlayManager() .showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true); } }); @@ -149,12 +115,73 @@ public class SettingsFragment extends SideFragment { // But, we may be able to turn on channel lock feature regardless of the permission. // It's TBD. } - if (LicenseUtils.hasLicenses(activity.getAssets())) { - items.add(new LicenseActionItem(activity)); + boolean showTrickplaySetting = false; + if (TUNER.isEnabled(getContext())) { + for (TvInputInfo inputInfo : TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper().getTvInputInfos(true, true)) { + if (Utils.isInternalTvInput(getContext(), inputInfo.getId())) { + showTrickplaySetting = true; + break; + } + } + if (showTrickplaySetting) { + showTrickplaySetting = + TvCustomizationManager.getTrickplayMode(getContext()) + == TvCustomizationManager.TRICKPLAY_MODE_ENABLED; + } + } + if (showTrickplaySetting) { + items.add( + new SwitchItem(getString(R.string.settings_trickplay), + getString(R.string.settings_trickplay), + getString(R.string.settings_trickplay_description), + getResources().getInteger(R.integer.trickplay_description_max_lines)) { + @Override + protected void onUpdate() { + super.onUpdate(); + boolean enabled = TunerPreferences.getTrickplaySetting(getContext()) + != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + setChecked(enabled); + } + + @Override + protected void onSelected() { + super.onSelected(); + @TunerPreferences.TrickplaySetting int setting = + isChecked() ? TunerPreferences.TRICKPLAY_SETTING_ENABLED + : TunerPreferences.TRICKPLAY_SETTING_DISABLED; + TunerPreferences.setTrickplaySetting(getContext(), setting); + } + }); + } + items.add(new ActionItem(getString(R.string.settings_send_feedback)) { + @Override + protected void onSelected() { + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + ApplicationErrorReport report = new ApplicationErrorReport(); + report.packageName = report.processName = getContext().getPackageName(); + report.time = System.currentTimeMillis(); + report.type = ApplicationErrorReport.TYPE_NONE; + intent.putExtra(Intent.EXTRA_BUG_REPORT, report); + startActivityForResult(intent, 0); + } + }); + if (Licenses.hasLicenses(getContext())) { + items.add( + new SubMenuItem( + getString(R.string.settings_menu_licenses), + getMainActivity().getOverlayManager().getSideFragmentManager()) { + @Override + protected SideFragment getFragment() { + return new LicenseSideFragment(); + } + }); } // Show version. - items.add(new SimpleItem(getString(R.string.settings_menu_version), - ((TvApplication) activity.getApplicationContext()).getVersionName())); + SimpleActionItem version = new SimpleActionItem(getString(R.string.settings_menu_version), + ((TvApplication) activity.getApplicationContext()).getVersionName()); + version.setClickable(false); + items.add(version); return items; } diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java index 8df56cd2..6bd921a2 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragment.java +++ b/src/com/android/tv/ui/sidepanel/SideFragment.java @@ -26,32 +26,36 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.util.DurationTimer; import com.android.tv.analytics.HasTrackerLabel; import com.android.tv.analytics.Tracker; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ProgramDataManager; import com.android.tv.util.SystemProperties; +import com.android.tv.util.ViewCache; import java.util.List; -public abstract class SideFragment extends Fragment implements HasTrackerLabel { +public abstract class SideFragment extends Fragment implements HasTrackerLabel { public static final int INVALID_POSITION = -1; - private static final int RECYCLED_VIEW_POOL_SIZE = 7; - private static final int[] PRELOADED_VIEW_IDS = { + private static final int PRELOAD_VIEW_SIZE = 7; + private static final int[] PRELOAD_VIEW_IDS = { R.layout.option_item_radio_button, R.layout.option_item_channel_lock, R.layout.option_item_check_box, - R.layout.option_item_channel_check + R.layout.option_item_channel_check, + R.layout.option_item_action }; - private static RecyclerView.RecycledViewPool sRecycledViewPool; + private static RecyclerView.RecycledViewPool sRecycledViewPool = + new RecyclerView.RecycledViewPool(); private VerticalGridView mListView; private ItemAdapter mAdapter; @@ -89,14 +93,8 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (sRecycledViewPool == null) { - // sRecycledViewPool should be initialized by calling preloadRecycledViews() - // before the entering animation of this fragment starts, - // because it takes long time and if it is called after the animation starts (e.g. here) - // it can affect the animation. - throw new IllegalStateException("The RecyclerView pool has not been initialized."); - } - View view = inflater.inflate(getFragmentLayoutResourceId(), container, false); + View view = ViewCache.getInstance().getOrCreateView( + inflater, getFragmentLayoutResourceId(), container); TextView textView = (TextView) view.findViewById(R.id.side_panel_title); textView.setText(getTitle()); @@ -158,7 +156,7 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel { return mListView.getSelectedPosition(); } - public void setItems(List items) { + public void setItems(List items) { mAdapter.reset(items); } @@ -229,56 +227,50 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel { protected abstract String getTitle(); @Override public abstract String getTrackerLabel(); - protected abstract List getItemList(); + protected abstract List getItemList(); public interface SideFragmentListener { void onSideFragmentViewDestroyed(); } /** - * Preloads the view holders. + * Preloads the item views. */ - public static void preloadRecycledViews(Context context) { - if (sRecycledViewPool != null) { - return; - } - sRecycledViewPool = new RecyclerView.RecycledViewPool(); - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - for (int id : PRELOADED_VIEW_IDS) { - sRecycledViewPool.setMaxRecycledViews(id, RECYCLED_VIEW_POOL_SIZE); - for (int j = 0; j < RECYCLED_VIEW_POOL_SIZE; ++j) { - ItemAdapter.ViewHolder viewHolder = new ItemAdapter.ViewHolder( - inflater.inflate(id, null, false)); - sRecycledViewPool.putRecycledView(viewHolder); - } + public static void preloadItemViews(Context context) { + ViewCache.getInstance().putView( + context, R.layout.option_fragment, new FrameLayout(context), 1); + VerticalGridView fakeParent = new VerticalGridView(context); + for (int id : PRELOAD_VIEW_IDS) { + sRecycledViewPool.setMaxRecycledViews(id, PRELOAD_VIEW_SIZE); + ViewCache.getInstance().putView(context, id, fakeParent, PRELOAD_VIEW_SIZE); } } /** - * Releases the pre-loaded view holders. + * Releases the recycled view pool. */ - public static void releasePreloadedRecycledViews() { - sRecycledViewPool = null; + public static void releaseRecycledViewPool() { + sRecycledViewPool.clear(); } - private static class ItemAdapter extends RecyclerView.Adapter { + private static class ItemAdapter extends RecyclerView.Adapter { private final LayoutInflater mLayoutInflater; - private List mItems; + private List mItems; - private ItemAdapter(LayoutInflater layoutInflater, List items) { + private ItemAdapter(LayoutInflater layoutInflater, List items) { mLayoutInflater = layoutInflater; mItems = items; } - private void reset(List items) { + private void reset(List items) { mItems = items; notifyDataSetChanged(); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new ViewHolder(mLayoutInflater.inflate(viewType, parent, false)); + View view = ViewCache.getInstance().getOrCreateView(mLayoutInflater, viewType, parent); + return new ViewHolder(view); } @Override @@ -301,11 +293,11 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel { return mItems == null ? 0 : mItems.size(); } - private Item getItem(int position) { + private T getItem(int position) { return mItems.get(position); } - private void clearRadioGroup(Item item) { + private void clearRadioGroup(T item) { int position = mItems.indexOf(item); for (int i = position - 1; i >= 0; --i) { if ((item = mItems.get(i)) instanceof RadioButtonItem) { @@ -322,55 +314,57 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel { } } } + } - private static class ViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, View.OnFocusChangeListener { - private ItemAdapter mAdapter; - public Item mItem; + private static class ViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, View.OnFocusChangeListener { + private ItemAdapter mAdapter; + public Item mItem; - private ViewHolder(View view) { - super(view); - itemView.setOnClickListener(this); - itemView.setOnFocusChangeListener(this); - } + private ViewHolder(View view) { + super(view); + itemView.setOnClickListener(this); + itemView.setOnFocusChangeListener(this); + } - public void onBind(ItemAdapter adapter, Item item) { - mAdapter = adapter; - mItem = item; - mItem.onBind(itemView); - mItem.onUpdate(); - } + public void onBind(ItemAdapter adapter, Item item) { + mAdapter = adapter; + mItem = item; + mItem.onBind(itemView); + mItem.onUpdate(); + } - public void onUnbind() { - mItem.onUnbind(); - mItem = null; - mAdapter = null; - } + public void onUnbind() { + mItem.onUnbind(); + mItem = null; + mAdapter = null; + } - @Override - public void onClick(View view) { - if (mItem instanceof RadioButtonItem) { - mAdapter.clearRadioGroup(mItem); - } - if (view.getBackground() instanceof RippleDrawable) { - view.postDelayed(new Runnable() { - @Override - public void run() { - if (mItem != null) { - mItem.onSelected(); + @Override + public void onClick(View view) { + if (mItem instanceof RadioButtonItem) { + mAdapter.clearRadioGroup(mItem); + } + if (view.getBackground() instanceof RippleDrawable) { + view.postDelayed( + new Runnable() { + @Override + public void run() { + if (mItem != null) { + mItem.onSelected(); + } } - } - }, view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration)); - } else { - mItem.onSelected(); - } + }, + view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration)); + } else { + mItem.onSelected(); } + } - @Override - public void onFocusChange(View view, boolean focusGained) { - if (focusGained) { - mItem.onFocused(); - } + @Override + public void onFocusChange(View view, boolean focusGained) { + if (focusGained) { + mItem.onFocused(); } } } diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java index 553cd9d7..d02d3fb7 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java +++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java @@ -24,6 +24,7 @@ import android.app.FragmentManager; import android.app.FragmentTransaction; import android.os.Handler; import android.view.View; +import android.view.ViewTreeObserver; import com.android.tv.R; @@ -34,6 +35,7 @@ public class SideFragmentManager { private final FragmentManager mFragmentManager; private final Runnable mPreShowRunnable; private final Runnable mPostHideRunnable; + private ViewTreeObserver.OnGlobalLayoutListener mShowOnGlobalLayoutListener; // To get the count reliably while using popBackStack(), // instead of using getBackStackEntryCount() with popBackStackImmediate(). @@ -99,17 +101,10 @@ public class SideFragmentManager { * Shows the given {@link SideFragment}. */ public void show(SideFragment sideFragment, boolean showEnterAnimation) { - SideFragment.preloadRecycledViews(mActivity); if (isHiding()) { mHideAnimator.end(); } boolean isFirst = (mFragmentCount == 0); - if (isFirst) { - if (mPreShowRunnable != null) { - mPreShowRunnable.run(); - } - } - FragmentTransaction ft = mFragmentManager.beginTransaction(); if (!isFirst) { ft.setCustomAnimations( @@ -123,8 +118,22 @@ public class SideFragmentManager { mFragmentCount++; if (isFirst) { + // We should wait for fragment transition and intital layouting finished to start the + // slide-in animation to prevent jankiness resulted by performing transition and + // layouting at the same time with animation. mPanel.setVisibility(View.VISIBLE); - mShowAnimator.start(); + mShowOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mShowOnGlobalLayoutListener = null; + if (mPreShowRunnable != null) { + mPreShowRunnable.run(); + } + mShowAnimator.start(); + } + }; + mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener); } scheduleHideAll(); } @@ -142,6 +151,18 @@ public class SideFragmentManager { } public void hideAll(boolean withAnimation) { + if (mShowAnimator.isStarted()) { + mShowAnimator.end(); + } + if (mShowOnGlobalLayoutListener != null) { + // The show operation maybe requested but the show animator is not started yet, in this + // case, we show still run mPreShowRunnable. + mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(mShowOnGlobalLayoutListener); + mShowOnGlobalLayoutListener = null; + if (mPreShowRunnable != null) { + mPreShowRunnable.run(); + } + } if (withAnimation) { if (!isHiding()) { mHideAnimator.start(); @@ -178,7 +199,6 @@ public class SideFragmentManager { * @param withAnimation specifies if animation should be shown. */ public void showSidePanel(boolean withAnimation) { - SideFragment.preloadRecycledViews(mActivity); if (mFragmentCount == 0) { return; } diff --git a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java new file mode 100644 index 00000000..42553b66 --- /dev/null +++ b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.ui.sidepanel; + +/** + * A simple item which shows title and description. + */ +public class SimpleActionItem extends ActionItem { + public SimpleActionItem(String title) { + super(title); + } + + public SimpleActionItem(String title, String description) { + super(title, description); + } + + @Override + protected void onSelected() { + } +} diff --git a/src/com/android/tv/ui/sidepanel/SimpleItem.java b/src/com/android/tv/ui/sidepanel/SimpleItem.java deleted file mode 100644 index 52a5f13f..00000000 --- a/src/com/android/tv/ui/sidepanel/SimpleItem.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.ui.sidepanel; - -/** - * A simple item which shows title and description. - */ -public class SimpleItem extends ActionItem { - public SimpleItem(String title) { - super(title); - } - - public SimpleItem(String title, String description) { - super(title, description); - } - - @Override - protected void onSelected() { - } -} diff --git a/src/com/android/tv/ui/sidepanel/SubMenuItem.java b/src/com/android/tv/ui/sidepanel/SubMenuItem.java index aa349dbd..4b0e8e2c 100644 --- a/src/com/android/tv/ui/sidepanel/SubMenuItem.java +++ b/src/com/android/tv/ui/sidepanel/SubMenuItem.java @@ -21,20 +21,11 @@ public abstract class SubMenuItem extends ActionItem { private final SideFragmentManager mSideFragmentManager; public SubMenuItem(String title, SideFragmentManager fragmentManager) { - this(title, null, 0, fragmentManager); + this(title, null, fragmentManager); } public SubMenuItem(String title, String description, SideFragmentManager fragmentManager) { - this(title, description, 0, fragmentManager); - } - - public SubMenuItem(String title, int iconId, SideFragmentManager fragmentManager) { - this(title, null, iconId, fragmentManager); - } - - public SubMenuItem(String title, String description, int iconId, - SideFragmentManager fragmentManager) { - super(title, description, iconId); + super(title, description); mSideFragmentManager = fragmentManager; } diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java index ef9966a5..06591b62 100644 --- a/src/com/android/tv/ui/sidepanel/SwitchItem.java +++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java @@ -31,6 +31,11 @@ public class SwitchItem extends CompoundButtonItem { super(checkedTitle, uncheckedTitle, description); } + public SwitchItem(String checkedTitle, String uncheckedTitle, String description, + int maxLines) { + super(checkedTitle, uncheckedTitle, description, maxLines); + } + @Override protected int getResourceId() { return R.layout.option_item_switch; diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java index da712924..9a4879fc 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java @@ -130,17 +130,8 @@ public class ParentalControlsFragment extends SideFragment { protected void onSelected() { final MainActivity tvActivity = getMainActivity(); tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true); - - PinDialogFragment fragment = - new PinDialogFragment( - PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN, - new PinDialogFragment.ResultListener() { - @Override - public void done(boolean success) { - tvActivity.getOverlayManager().getSideFragmentManager() - .showSidePanel(true); - } - }); + PinDialogFragment fragment = PinDialogFragment.create( + PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN); tvActivity.getOverlayManager().showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true); } diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java index 6bc47939..7c8cecbe 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java @@ -17,16 +17,17 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.graphics.drawable.Drawable; +import android.media.tv.TvContentRating; import android.os.Bundle; import android.util.ArrayMap; import android.util.SparseIntArray; import android.view.View; import android.widget.CompoundButton; import android.widget.ImageView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.dialog.WebDialogFragment; +import com.android.tv.experiments.Experiments; import com.android.tv.license.LicenseUtils; import com.android.tv.parental.ContentRatingSystem; import com.android.tv.parental.ContentRatingSystem.Rating; @@ -38,7 +39,6 @@ import com.android.tv.ui.sidepanel.RadioButtonItem; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.util.TvSettings; import com.android.tv.util.TvSettings.ContentRatingLevel; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -77,6 +77,7 @@ public class RatingsFragment extends SideFragment { private final List mRatingLevelItems = new ArrayList<>(); // A map from the rating system ID string to RatingItem objects. private final Map> mContentRatingSystemItemMap = new ArrayMap<>(); + private CheckBoxItem mBlockUnratedItem; private ParentalControlSettings mParentalControlSettings; public static String getDescription(MainActivity tvActivity) { @@ -102,6 +103,12 @@ public class RatingsFragment extends SideFragment { protected List getItemList() { List items = new ArrayList<>(); + if (mBlockUnratedItem != null + && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { + items.add(mBlockUnratedItem); + items.add(new DividerItem()); + } + mRatingLevelItems.clear(); for (int i = 0; i < sLevelResourceIdMap.size(); ++i) { mRatingLevelItems.add(new RatingLevelItem(sLevelResourceIdMap.keyAt(i))); @@ -152,6 +159,28 @@ public class RatingsFragment extends SideFragment { super.onCreate(savedInstanceState); mParentalControlSettings = getMainActivity().getParentalControlSettings(); mParentalControlSettings.loadRatings(); + if (Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { + mBlockUnratedItem = + new CheckBoxItem( + getResources().getString(R.string.option_block_unrated_programs)) { + + @Override + protected void onUpdate() { + super.onUpdate(); + setChecked( + mParentalControlSettings.isRatingBlocked( + new TvContentRating[] {TvContentRating.UNRATED})); + } + + @Override + protected void onSelected() { + super.onSelected(); + if (mParentalControlSettings.setUnratedBlocked(isChecked())) { + updateRatingLevels(); + } + } + }; + } } @Override @@ -202,6 +231,13 @@ public class RatingsFragment extends SideFragment { super.onSelected(); mParentalControlSettings.setContentRatingLevel( getMainActivity().getContentRatingsManager(), mRatingLevel); + if (mBlockUnratedItem != null + && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) { + // set checked if UNRATED is blocked, and set unchecked otherwise. + mBlockUnratedItem.setChecked( + mParentalControlSettings.isRatingBlocked( + new TvContentRating[] {TvContentRating.UNRATED})); + } notifyItemsChanged(mRatingLevelItems.size()); } } @@ -302,7 +338,7 @@ public class RatingsFragment extends SideFragment { @Override protected void onSelected() { getMainActivity().getOverlayManager().getSideFragmentManager() - .show(new SubRatingsFragment(mContentRatingSystem, mRating)); + .show(SubRatingsFragment.create(mContentRatingSystem, mRating.getName())); } @Override diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java index f6612fdb..4634b74c 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java @@ -17,6 +17,7 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.view.View; import android.widget.CompoundButton; import android.widget.ImageView; @@ -36,13 +37,34 @@ import java.util.List; public class SubRatingsFragment extends SideFragment { private static final String TRACKER_LABEL = "Sub ratings"; - private final ContentRatingSystem mContentRatingSystem; - private final Rating mRating; + private static final String ARGS_CONTENT_RATING_SYSTEM_ID = "args_content_rating_system_id"; + private static final String ARGS_RATING_NAME = "args_rating_name"; + + private ContentRatingSystem mContentRatingSystem; + private Rating mRating; private final List mSubRatingItems = new ArrayList<>(); - public SubRatingsFragment(ContentRatingSystem contentRatingSystem, Rating rating) { - mContentRatingSystem = contentRatingSystem; - mRating = rating; + public static SubRatingsFragment create(ContentRatingSystem contentRatingSystem, + String ratingName) { + SubRatingsFragment fragment = new SubRatingsFragment(); + Bundle args = new Bundle(); + args.putString(ARGS_CONTENT_RATING_SYSTEM_ID, contentRatingSystem.getId()); + args.putString(ARGS_RATING_NAME, ratingName); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContentRatingSystem = getMainActivity().getContentRatingsManager() + .getContentRatingSystem(getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID)); + if (mContentRatingSystem != null) { + mRating = mContentRatingSystem.getRating(getArguments().getString(ARGS_RATING_NAME)); + } + if (mRating == null) { + closeFragment(); + } } @Override diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java index 78243642..477412e4 100644 --- a/src/com/android/tv/util/AsyncDbTask.java +++ b/src/com/android/tv/util/AsyncDbTask.java @@ -31,7 +31,7 @@ import android.util.Range; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; -import com.android.tv.dvr.RecordedProgram; +import com.android.tv.dvr.data.RecordedProgram; import java.util.ArrayList; import java.util.List; @@ -76,7 +76,7 @@ public abstract class AsyncDbTask * accepted for execution * @throws NullPointerException if command is null */ - public static void execute(Runnable command) { + public static void executeOnDbThread(Runnable command) { DB_EXECUTOR.execute(command); } diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/BitmapUtils.java index d45a8dce..fbaab023 100644 --- a/src/com/android/tv/util/BitmapUtils.java +++ b/src/com/android/tv/util/BitmapUtils.java @@ -24,6 +24,7 @@ import android.graphics.BitmapFactory; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.net.TrafficStats; import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; @@ -56,6 +57,12 @@ public final class BitmapUtils { return Bitmap.createScaledBitmap(bm, rect.right, rect.bottom, false); } + public static Bitmap getScaledMutableBitmap(Bitmap bm, int maxWidth, int maxHeight) { + Bitmap scaledBitmap = scaleBitmap(bm, maxWidth, maxHeight); + return scaledBitmap.isMutable() ? scaledBitmap + : scaledBitmap.copy(Bitmap.Config.ARGB_8888, true); + } + private static Rect calculateNewSize(Bitmap bm, int maxWidth, int maxHeight) { final double ratio = maxHeight / (double) maxWidth; final double bmRatio = bm.getHeight() / (double) bm.getWidth(); @@ -89,6 +96,8 @@ public final class BitmapUtils { boolean isResourceUri = isContentResolverUri(uri); URLConnection urlConnection = null; InputStream inputStream = null; + final int oldTag = TrafficStats.getThreadStatsTag(); + TrafficStats.setThreadStatsTag(NetworkTrafficTags.LOGO_FETCHER); try { if (isResourceUri) { inputStream = context.getContentResolver().openInputStream(uri); @@ -142,6 +151,7 @@ public final class BitmapUtils { return null; } finally { close(inputStream, urlConnection); + TrafficStats.setThreadStatsTag(oldTag); } } diff --git a/src/com/android/tv/util/Debug.java b/src/com/android/tv/util/Debug.java new file mode 100644 index 00000000..67a2683d --- /dev/null +++ b/src/com/android/tv/util/Debug.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * A class only for help developers. + */ +public class Debug { + /** + * A threshold of start up time, when the start up time of Live TV is more than it, + * a warning will show to the developer. + */ + public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6); + /** + * Tag for measuring start up time of Live TV. + */ + public static final String TAG_START_UP_TIMER = "start_up_timer"; + + /** + * A global map for duration timers. + */ + private final static Map sTimerMap = new HashMap<>(); + + /** + * Returns the global duration timer by tag. + */ + public static DurationTimer getTimer(String tag) { + if (sTimerMap.get(tag) != null) { + return sTimerMap.get(tag); + } + DurationTimer timer = new DurationTimer(tag, true); + sTimerMap.put(tag, timer); + return timer; + } + + /** + * Removes the global duration timer by tag. + */ + public static DurationTimer removeTimer(String tag) { + return sTimerMap.remove(tag); + } +} diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/util/DurationTimer.java new file mode 100644 index 00000000..1f057bf6 --- /dev/null +++ b/src/com/android/tv/util/DurationTimer.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +import android.os.SystemClock; +import android.util.Log; + +import com.android.tv.common.BuildConfig; + +/** + * Times a duration. + */ +public final class DurationTimer { + private static final String TAG = "DurationTimer"; + public static final long TIME_NOT_SET = -1; + + private long mStartTimeMs = TIME_NOT_SET; + private String mTag = TAG; + private boolean mLogEngOnly; + + public DurationTimer() { } + + public DurationTimer(String tag, boolean logEngOnly) { + mTag = tag; + mLogEngOnly = logEngOnly; + } + + /** + * Returns true if the timer is running. + */ + public boolean isRunning() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** + * Start the timer. + */ + public void start() { + mStartTimeMs = SystemClock.elapsedRealtime(); + } + + /** + * Returns true if timer is started. + */ + public boolean isStarted() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** + * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long getDuration() { + return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET; + } + + /** + * Stops the timer and resets its value to {@link #TIME_NOT_SET}. + * + * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long reset() { + long duration = getDuration(); + mStartTimeMs = TIME_NOT_SET; + return duration; + } + + /** + * Adds information and duration time to the log. + */ + public void log(String message) { + if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) { + Log.i(mTag, message + " : " + getDuration() + "ms"); + } + } +} diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java index 04bb478a..86bb94c1 100644 --- a/src/com/android/tv/util/ImageLoader.java +++ b/src/com/android/tv/util/ImageLoader.java @@ -292,7 +292,8 @@ public final class ImageLoader { * Checks if a reload would be needed if the results of other was available. */ private boolean isReloadNeeded(LoadBitmapTask other) { - return mMaxHeight >= other.mMaxHeight * 2 || mMaxWidth >= other.mMaxWidth * 2; + return (other.mMaxHeight != Integer.MAX_VALUE && mMaxHeight >= other.mMaxHeight * 2) + || (other.mMaxWidth != Integer.MAX_VALUE && mMaxWidth >= other.mMaxWidth * 2); } @Nullable diff --git a/src/com/android/tv/util/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java index 8e3b59e9..d5d7bee3 100644 --- a/src/com/android/tv/util/LocationUtils.java +++ b/src/com/android/tv/util/LocationUtils.java @@ -16,15 +16,20 @@ package com.android.tv.util; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; +import com.android.tv.tuner.util.PostalCodeUtils; import java.io.IOException; import java.util.List; @@ -39,6 +44,7 @@ public class LocationUtils { private static Context sApplicationContext; private static Address sAddress; + private static String sCountry; private static IOException sError; /** @@ -59,6 +65,18 @@ public class LocationUtils { return null; } + /** Returns the current country. */ + @NonNull + public static synchronized String getCurrentCountry(Context context) { + if (sCountry != null) { + return sCountry; + } + if (TextUtils.isEmpty(sCountry)) { + sCountry = context.getResources().getConfiguration().locale.getCountry(); + } + return sCountry; + } + private static void updateAddress(Location location) { if (DEBUG) Log.d(TAG, "Updating address with " + location); if (location == null) { @@ -68,9 +86,14 @@ public class LocationUtils { try { List

addresses = geocoder.getFromLocation( location.getLatitude(), location.getLongitude(), 1); - if (addresses != null) { + if (addresses != null && !addresses.isEmpty()) { sAddress = addresses.get(0); if (DEBUG) Log.d(TAG, "Got " + sAddress); + try { + PostalCodeUtils.updatePostalCode(sApplicationContext); + } catch (Exception e) { + // Do nothing + } } else { if (DEBUG) Log.d(TAG, "No address returned"); } diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/src/com/android/tv/util/NetworkTrafficTags.java new file mode 100644 index 00000000..2dca613c --- /dev/null +++ b/src/com/android/tv/util/NetworkTrafficTags.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.util; + +import android.net.TrafficStats; +import android.support.annotation.NonNull; + +import java.util.concurrent.Executor; + +/** Constants for tagging network traffic in the Live channels app. */ +public final class NetworkTrafficTags { + + public static final int DEFAULT_LIVE_CHANNELS = 1; + public static final int LOGO_FETCHER = 2; + public static final int HDHOMERUN = 3; + public static final int EPG_FETCH = 4; + + /** + * An executor which simply wraps a provided delegate executor, but calls {@link + * TrafficStats#setThreadStatsTag(int)} before executing any task. + */ + public static class TrafficStatsTaggingExecutor implements Executor { + private final Executor delegateExecutor; + private final int tag; + + public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) { + this.delegateExecutor = delegateExecutor; + this.tag = tag; + } + + @Override + public void execute(final @NonNull Runnable command) { + // TODO(b/62038127): robolectric does not support lamdas in unbundled apps + delegateExecutor.execute( + new Runnable() { + @Override + public void run() { + TrafficStats.setThreadStatsTag(tag); + try { + command.run(); + } finally { + TrafficStats.clearThreadStatsTag(); + } + } + }); + } + } + + private NetworkTrafficTags() {} +} diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java index 3040020e..49b02b82 100644 --- a/src/com/android/tv/util/OnboardingUtils.java +++ b/src/com/android/tv/util/OnboardingUtils.java @@ -16,17 +16,10 @@ package com.android.tv.util; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.media.tv.TvContract.Channels; import android.net.Uri; import android.preference.PreferenceManager; -import android.support.annotation.UiThread; - -import com.android.tv.TvApplication; -import com.android.tv.data.ChannelDataManager; /** * A utility class related to onboarding experience. @@ -81,42 +74,12 @@ public final class OnboardingUtils { .putInt(PREF_KEY_ONBOARDING_VERSION_CODE, ONBOARDING_VERSION).apply(); } - /** - * Checks whether the onboarding screen should be shown or not. - */ - public static boolean needToShowOnboarding(Context context) { - return isFirstRunWithCurrentVersion(context) || !areChannelsAvailable(context); - } - - /** - * Checks if there are any available tuner channels. - */ - @UiThread - public static boolean areChannelsAvailable(Context context) { - ChannelDataManager manager = TvApplication.getSingletons(context).getChannelDataManager(); - if (manager.isDbLoadFinished()) { - return manager.getChannelCount() != 0; - } - // This method should block the UI thread. - ContentResolver resolver = context.getContentResolver(); - try (Cursor c = resolver.query(Channels.CONTENT_URI, new String[] {Channels._ID}, null, - null, null)) { - return c != null && c.getCount() != 0; - } - } - - /** - * Checks if there are any available TV inputs. - */ - public static boolean areInputsAvailable(Context context) { - return TvApplication.getSingletons(context).getTvInputManagerHelper() - .getTvInputInfos(true, false).size() > 0; - } - /** * Returns merchant collection URL. */ private static String getMerchantCollectionUrl() { return "TODO: add a merchant collection url"; } + + private OnboardingUtils() {} } diff --git a/src/com/android/tv/util/Partner.java b/src/com/android/tv/util/Partner.java new file mode 100644 index 00000000..e3688392 --- /dev/null +++ b/src/com/android/tv/util/Partner.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.media.tv.TvInputInfo; +import android.text.TextUtils; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file refers to Partner.java in LeanbackLauncher. Interact with partner customizations. There + * can only be one set of customizations on a device, and it must be bundled with the system. + */ +public class Partner { + private static final String TAG = "Partner"; + /** Marker action used to discover partner */ + private static final String ACTION_PARTNER_CUSTOMIZATION = + "com.google.android.leanbacklauncher.action.PARTNER_CUSTOMIZATION"; + + /** ID tags for device input types */ + public static final String INPUT_TYPE_BUNDLED_TUNER = "input_type_combined_tuners"; + public static final String INPUT_TYPE_TUNER = "input_type_tuner"; + public static final String INPUT_TYPE_CEC_LOGICAL = "input_type_cec_logical"; + public static final String INPUT_TYPE_CEC_RECORDER = "input_type_cec_recorder"; + public static final String INPUT_TYPE_CEC_PLAYBACK = "input_type_cec_playback"; + public static final String INPUT_TYPE_MHL_MOBILE = "input_type_mhl_mobile"; + public static final String INPUT_TYPE_HDMI = "input_type_hdmi"; + public static final String INPUT_TYPE_DVI = "input_type_dvi"; + public static final String INPUT_TYPE_COMPONENT = "input_type_component"; + public static final String INPUT_TYPE_SVIDEO = "input_type_svideo"; + public static final String INPUT_TYPE_COMPOSITE = "input_type_composite"; + public static final String INPUT_TYPE_DISPLAY_PORT = "input_type_displayport"; + public static final String INPUT_TYPE_VGA = "input_type_vga"; + public static final String INPUT_TYPE_SCART = "input_type_scart"; + public static final String INPUT_TYPE_OTHER = "input_type_other"; + + private static final String INPUTS_ORDER = "home_screen_inputs_ordering"; + private static final String TYPE_ARRAY = "array"; + + private static Partner sPartner; + private static final Object sLock = new Object(); + + private final String mPackageName; + private final String mReceiverName; + private final Resources mResources; + + private static final Map INPUT_TYPE_MAP = new HashMap<>(); + static { + INPUT_TYPE_MAP.put(INPUT_TYPE_BUNDLED_TUNER, TvInputManagerHelper.TYPE_BUNDLED_TUNER); + INPUT_TYPE_MAP.put(INPUT_TYPE_TUNER, TvInputInfo.TYPE_TUNER); + INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_LOGICAL, TvInputManagerHelper.TYPE_CEC_DEVICE); + INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_RECORDER, TvInputManagerHelper.TYPE_CEC_DEVICE_RECORDER); + INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_PLAYBACK, TvInputManagerHelper.TYPE_CEC_DEVICE_PLAYBACK); + INPUT_TYPE_MAP.put(INPUT_TYPE_MHL_MOBILE, TvInputManagerHelper.TYPE_MHL_MOBILE); + INPUT_TYPE_MAP.put(INPUT_TYPE_HDMI, TvInputInfo.TYPE_HDMI); + INPUT_TYPE_MAP.put(INPUT_TYPE_DVI, TvInputInfo.TYPE_DVI); + INPUT_TYPE_MAP.put(INPUT_TYPE_COMPONENT, TvInputInfo.TYPE_COMPONENT); + INPUT_TYPE_MAP.put(INPUT_TYPE_SVIDEO, TvInputInfo.TYPE_SVIDEO); + INPUT_TYPE_MAP.put(INPUT_TYPE_COMPOSITE, TvInputInfo.TYPE_COMPOSITE); + INPUT_TYPE_MAP.put(INPUT_TYPE_DISPLAY_PORT, TvInputInfo.TYPE_DISPLAY_PORT); + INPUT_TYPE_MAP.put(INPUT_TYPE_VGA, TvInputInfo.TYPE_VGA); + INPUT_TYPE_MAP.put(INPUT_TYPE_SCART, TvInputInfo.TYPE_SCART); + INPUT_TYPE_MAP.put(INPUT_TYPE_OTHER, TvInputInfo.TYPE_OTHER); + } + + private Partner(String packageName, String receiverName, Resources res) { + mPackageName = packageName; + mReceiverName = receiverName; + mResources = res; + } + + /** Returns partner instance. */ + public static Partner getInstance(Context context) { + PackageManager pm = context.getPackageManager(); + synchronized (sLock) { + ResolveInfo info = getPartnerResolveInfo(pm); + if (info != null) { + final String packageName = info.activityInfo.packageName; + final String receiverName = info.activityInfo.name; + try { + final Resources res = pm.getResourcesForApplication(packageName); + sPartner = new Partner(packageName, receiverName, res); + sPartner.sendInitBroadcast(context); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + if (sPartner == null) { + sPartner = new Partner(null, null, null); + } + } + return sPartner; + } + + /** Resets the Partner instance to handle the partner package has changed. */ + public static void reset(Context context, String packageName) { + synchronized (sLock) { + if (sPartner != null && !TextUtils.isEmpty(packageName)) { + if (packageName.equals(sPartner.mPackageName)) { + // Force a refresh, so we send an Init to the updated package + sPartner = null; + getInstance(context); + } + } + } + } + + /** This method is used to send init broadcast to the new/changed partner package. */ + private void sendInitBroadcast(Context context) { + if (!TextUtils.isEmpty(mPackageName) && !TextUtils.isEmpty(mReceiverName)) { + Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); + final ComponentName componentName = new ComponentName(mPackageName, mReceiverName); + intent.setComponent(componentName); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + context.sendBroadcast(intent); + } + } + + /** Returns the order of inputs. */ + public Map getInputsOrderMap() { + HashMap map = new HashMap<>(); + if (mResources != null && !TextUtils.isEmpty(mPackageName)) { + String[] inputsArray = null; + final int resId = mResources.getIdentifier(INPUTS_ORDER, TYPE_ARRAY, mPackageName); + if (resId != 0) { + inputsArray = mResources.getStringArray(resId); + } + if (inputsArray != null) { + int priority = 0; + for (String input : inputsArray) { + Integer type = INPUT_TYPE_MAP.get(input); + if (type != null) { + map.put(type, priority++); + } + } + } + } + return map; + } + + private static ResolveInfo getPartnerResolveInfo(PackageManager pm) { + final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); + ResolveInfo partnerInfo = null; + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { + if (isSystemApp(info)) { + partnerInfo = info; + break; + } + } + return partnerInfo; + } + + protected static boolean isSystemApp(ResolveInfo info) { + return (info.activityInfo != null + && info.activityInfo.applicationInfo != null + && (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } +} diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java index 453885a4..a355be99 100644 --- a/src/com/android/tv/util/PermissionUtils.java +++ b/src/com/android/tv/util/PermissionUtils.java @@ -47,4 +47,9 @@ public class PermissionUtils { return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS) == PackageManager.PERMISSION_GRANTED; } + + public static boolean hasInternet(Context context) { + return context.checkSelfPermission("android.permission.INTERNET") + == PackageManager.PERMISSION_GRANTED; + } } diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java deleted file mode 100644 index 2c51d5a0..00000000 --- a/src/com/android/tv/util/PipInputManager.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.content.Context; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; -import android.media.tv.TvInputManager.TvInputCallback; -import android.util.ArraySet; -import android.util.Log; - -import com.android.tv.ChannelTuner; -import com.android.tv.R; -import com.android.tv.data.Channel; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A class that manages inputs for PIP. All tuner inputs are represented to one tuner input for PIP. - * Hidden inputs should not be visible to the users. - */ -public class PipInputManager { - private static final String TAG = "PipInputManager"; - - // Tuner inputs aren't distinguished each other in PipInput. They are handled as one input. - // Therefore, we define a fake input id for the unified input. - private static final String TUNER_INPUT_ID = "tuner_input_id"; - - private final Context mContext; - private final TvInputManagerHelper mInputManager; - private final ChannelTuner mChannelTuner; - private boolean mStarted; - private final Map mPipInputMap = new HashMap<>(); // inputId -> PipInput - private final Set mListeners = new ArraySet<>(); - - private final TvInputCallback mTvInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - if (input.isPassthroughInput()) { - boolean available = mInputManager.getInputState(input) - == TvInputManager.INPUT_STATE_CONNECTED; - mPipInputMap.put(inputId, new PipInput(inputId, available)); - } else if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) { - boolean available = mChannelTuner.getBrowsableChannelCount() != 0; - mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available)); - } else { - return; - } - for (Listener l : mListeners) { - l.onPipInputListUpdated(); - } - } - - @Override - public void onInputRemoved(String inputId) { - PipInput pipInput = mPipInputMap.remove(inputId); - if (pipInput == null) { - if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) { - Log.w(TAG, "A TV input (" + inputId + ") isn't tracked in PipInputManager"); - return; - } - if (mInputManager.getTunerTvInputSize() > 0) { - return; - } - mPipInputMap.remove(TUNER_INPUT_ID); - } - for (Listener l : mListeners) { - l.onPipInputListUpdated(); - } - } - - @Override - public void onInputStateChanged(String inputId, int state) { - PipInput pipInput = mPipInputMap.get(inputId); - if (pipInput == null) { - // For tuner input, state change is handled in mChannelTunerListener. - return; - } - pipInput.updateAvailability(); - } - }; - - private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() { - @Override - public void onLoadFinished() { } - - @Override - public void onCurrentChannelUnavailable(Channel channel) { } - - @Override - public void onBrowsableChannelListChanged() { - PipInput tunerInput = mPipInputMap.get(TUNER_INPUT_ID); - if (tunerInput == null) { - return; - } - tunerInput.updateAvailability(); - } - - @Override - public void onChannelChanged(Channel previousChannel, Channel currentChannel) { - if (previousChannel != null && currentChannel != null - && !previousChannel.isPassthrough() && !currentChannel.isPassthrough()) { - // Channel change between channels for tuner inputs. - return; - } - PipInput previousMainInput = getPipInput(previousChannel); - if (previousMainInput != null) { - previousMainInput.updateAvailability(); - } - PipInput currentMainInput = getPipInput(currentChannel); - if (currentMainInput != null) { - currentMainInput.updateAvailability(); - } - } - }; - - public PipInputManager(Context context, TvInputManagerHelper inputManager, - ChannelTuner channelTuner) { - mContext = context; - mInputManager = inputManager; - mChannelTuner = channelTuner; - } - - /** - * Starts {@link PipInputManager}. - */ - public void start() { - if (mStarted) { - return; - } - mStarted = true; - mInputManager.addCallback(mTvInputCallback); - mChannelTuner.addListener(mChannelTunerListener); - initializePipInputList(); - } - - /** - * Stops {@link PipInputManager}. - */ - public void stop() { - if (!mStarted) { - return; - } - mStarted = false; - mInputManager.removeCallback(mTvInputCallback); - mChannelTuner.removeListener(mChannelTunerListener); - mPipInputMap.clear(); - } - - /** - * Adds a {@link PipInputManager.Listener}. - */ - public void addListener(Listener listener) { - mListeners.add(listener); - } - - /** - * Removes a {@link PipInputManager.Listener}. - */ - public void removeListener(Listener listener) { - mListeners.remove(listener); - } - - /** - * Gets the size of inputs for PIP. - * - *

The hidden inputs are not counted. - * - * @param availableOnly If {@code true}, it counts only available PIP inputs. Please see {@link - * PipInput#isAvailable()} for the details of availability. - */ - public int getPipInputSize(boolean availableOnly) { - int count = 0; - for (PipInput pipInput : mPipInputMap.values()) { - if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) { - ++count; - } - if (pipInput.isPassthrough()) { - TvInputInfo info = pipInput.getInputInfo(); - // Do not count HDMI ports if a CEC device is directly connected to the port. - if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) { - --count; - } - } - } - return count; - } - - /** - * Gets the list of inputs for PIP.. - * - *

The hidden inputs are excluded. - * - * @param availableOnly If true, it returns only available PIP inputs. Please see {@link - * PipInput#isAvailable()} for the details of availability. - */ - public List getPipInputList(boolean availableOnly) { - List pipInputs = new ArrayList<>(); - List removeInputs = new ArrayList<>(); - for (PipInput pipInput : mPipInputMap.values()) { - if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) { - pipInputs.add(pipInput); - } - if (pipInput.isPassthrough()) { - TvInputInfo info = pipInput.getInputInfo(); - // Do not show HDMI ports if a CEC device is directly connected to the port. - if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) { - removeInputs.add(mPipInputMap.get(info.getParentId())); - } - } - } - if (!removeInputs.isEmpty()) { - pipInputs.removeAll(removeInputs); - } - Collections.sort(pipInputs, new Comparator() { - @Override - public int compare(PipInput lhs, PipInput rhs) { - if (!lhs.mIsPassthrough) { - return -1; - } - if (!rhs.mIsPassthrough) { - return 1; - } - String a = lhs.getLabel(); - String b = rhs.getLabel(); - return a.compareTo(b); - } - }); - return pipInputs; - } - - /** - * Returns an PIP input corresponding to {@code channel}. - */ - public PipInput getPipInput(Channel channel) { - if (channel == null) { - return null; - } - if (channel.isPassthrough()) { - return mPipInputMap.get(channel.getInputId()); - } else { - return mPipInputMap.get(TUNER_INPUT_ID); - } - } - - /** - * Returns true, if {@code channel1} and {@code channel2} belong to the same input. For example, - * two channels from different tuner inputs are also in the same input "Tuner" from PIP - * point of view. - */ - public boolean areInSamePipInput(Channel channel1, Channel channel2) { - PipInput input1 = getPipInput(channel1); - PipInput input2 = getPipInput(channel2); - return input1 != null && input2 != null - && getPipInput(channel1).equals(getPipInput(channel2)); - } - - private void initializePipInputList() { - boolean hasTunerInput = false; - for (TvInputInfo input : mInputManager.getTvInputInfos(false, false)) { - if (input.isPassthroughInput()) { - boolean available = mInputManager.getInputState(input) - == TvInputManager.INPUT_STATE_CONNECTED; - mPipInputMap.put(input.getId(), new PipInput(input.getId(), available)); - } else if (!hasTunerInput) { - hasTunerInput = true; - boolean available = mChannelTuner.getBrowsableChannelCount() != 0; - mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available)); - } - } - PipInput input = getPipInput(mChannelTuner.getCurrentChannel()); - if (input != null) { - input.updateAvailability(); - } - for (Listener l : mListeners) { - l.onPipInputListUpdated(); - } - } - - /** - * Listeners to notify PIP input state changes. - */ - public interface Listener { - /** - * Called when the state (availability) of PIP inputs is changed. - */ - void onPipInputStateUpdated(); - - /** - * Called when the list of PIP inputs is changed. - */ - void onPipInputListUpdated(); - } - - /** - * Input class for PIP. It has useful methods for PIP handling. - */ - public class PipInput { - private final String mInputId; - private final boolean mIsPassthrough; - private final TvInputInfo mInputInfo; - private boolean mAvailable; - - private PipInput(String inputId, boolean available) { - mInputId = inputId; - mIsPassthrough = !mInputId.equals(TUNER_INPUT_ID); - if (mIsPassthrough) { - mInputInfo = mInputManager.getTvInputInfo(mInputId); - } else { - mInputInfo = null; - } - mAvailable = available; - } - - /** - * Returns the {@link TvInputInfo} object that matches to this PIP input. - */ - public TvInputInfo getInputInfo() { - return mInputInfo; - } - - /** - * Returns {@code true}, if the input is available for PIP. If a channel of an input is - * already played or an input is not connected state or there is no browsable channel, the - * input is unavailable. - */ - public boolean isAvailable() { - return mAvailable; - } - - /** - * Returns true, if the input is a passthrough TV input. - */ - public boolean isPassthrough() { - return mIsPassthrough; - } - - /** - * Gets a channel to play in a PIP view. - */ - public Channel getChannel() { - if (mIsPassthrough) { - return Channel.createPassthroughChannel(mInputId); - } else { - return mChannelTuner.findNearestBrowsableChannel( - Utils.getLastWatchedChannelId(mContext)); - } - } - - /** - * Gets a label of the input. - */ - public String getLabel() { - if (mIsPassthrough) { - return mInputInfo.loadLabel(mContext).toString(); - } else { - return mContext.getString(R.string.input_selector_tuner_label); - } - } - - /** - * Gets a long label including a customized label. - */ - public String getLongLabel() { - if (mIsPassthrough) { - String customizedLabel = Utils.loadLabel(mContext, mInputInfo); - String label = getLabel(); - if (label.equals(customizedLabel)) { - return customizedLabel; - } - return customizedLabel + " (" + label + ")"; - } else { - return mContext.getString(R.string.input_long_label_for_tuner); - } - } - - /** - * Updates availability. It returns true, if availability is changed. - */ - private void updateAvailability() { - boolean available; - // current playing input cannot be available for PIP. - Channel currentChannel = mChannelTuner.getCurrentChannel(); - if (mIsPassthrough) { - if (currentChannel != null && currentChannel.getInputId().equals(mInputId)) { - available = false; - } else { - available = mInputManager.getInputState(mInputId) - == TvInputManager.INPUT_STATE_CONNECTED; - } - } else { - if (currentChannel != null && !currentChannel.isPassthrough()) { - available = false; - } else { - available = mChannelTuner.getBrowsableChannelCount() > 0; - } - } - if (mAvailable != available) { - mAvailable = available; - for (Listener l : mListeners) { - l.onPipInputStateUpdated(); - } - } - } - - private boolean isHidden() { - // mInputInfo is null for the tuner input and it's always visible. - return mInputInfo != null && mInputInfo.isHidden(mContext); - } - } -} diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java index 4135bd4e..8b45131b 100644 --- a/src/com/android/tv/util/RecurringRunner.java +++ b/src/com/android/tv/util/RecurringRunner.java @@ -57,12 +57,15 @@ public final class RecurringRunner { mHandler = new Handler(mContext.getMainLooper()); } - public void start() { + public void start(boolean resetNextRunTime) { SoftPreconditions.checkState(!mRunning, TAG, mName + " start is called twice."); if (mRunning) { return; } mRunning = true; + if (resetNextRunTime) { + resetNextRunTime(); + } new AsyncTask() { @Override protected Long doInBackground(Void... params) { @@ -76,6 +79,10 @@ public final class RecurringRunner { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void start() { + start(false); + } + public void stop() { mRunning = false; mHandler.removeCallbacksAndMessages(null); diff --git a/src/com/android/tv/util/SearchManagerHelper.java b/src/com/android/tv/util/SearchManagerHelper.java deleted file mode 100644 index b6e34d7a..00000000 --- a/src/com/android/tv/util/SearchManagerHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.app.SearchManager; -import android.content.Context; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.Log; - -import java.lang.reflect.InvocationTargetException; - -/** - * A convenience class for calling methods in android.app.SearchManager. - */ -public final class SearchManagerHelper { - private static final String TAG = "SearchManagerHelper"; - - private static final Object sLock = new Object(); - private static SearchManagerHelper sInstance; - - private final SearchManager mSearchManager; - - private SearchManagerHelper(Context context) { - mSearchManager = ((android.app.SearchManager) context.getSystemService( - Context.SEARCH_SERVICE)); - } - - public static SearchManagerHelper getInstance(Context context) { - synchronized (sLock) { - if (sInstance == null) { - sInstance = new SearchManagerHelper(context.getApplicationContext()); - } - return sInstance; - } - } - - public void launchAssistAction() { - try { - SearchManager.class.getDeclaredMethod("launchLegacyAssist", String.class, Integer.TYPE, - Bundle.class).invoke(mSearchManager, null, UserHandle.myUserId(), null); - } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException - | InvocationTargetException e) { - Log.e(TAG, "Fail to call SearchManager.launchAssistAction", e); - } - } -} diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java index 8223a81c..32e3a81f 100644 --- a/src/com/android/tv/util/SetupUtils.java +++ b/src/com/android/tv/util/SetupUtils.java @@ -37,8 +37,6 @@ import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.experiments.Experiments; import com.android.tv.tuner.tvinput.TunerTvInputService; import java.util.Collections; @@ -114,7 +112,7 @@ public class SetupUtils { @Override public void onLoadFinished() { manager.removeListener(this); - updateChannelBrowsable(mTvApplication, inputId, postRunnable); + updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); } @Override @@ -124,17 +122,18 @@ public class SetupUtils { public void onChannelBrowsableChanged() { } }); } else { - updateChannelBrowsable(mTvApplication, inputId, postRunnable); + updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); } } - private static void updateChannelBrowsable(Context context, final String inputId, + private static void updateChannelsAfterSetup(Context context, final String inputId, final Runnable postRunnable) { ApplicationSingletons appSingletons = TvApplication.getSingletons(context); final ChannelDataManager manager = appSingletons.getChannelDataManager(); manager.updateChannels(new Runnable() { @Override public void run() { + Channel firstChannelForInput = null; boolean browsableChanged = false; for (Channel channel : manager.getChannelList()) { if (channel.getInputId().equals(inputId)) { @@ -142,8 +141,14 @@ public class SetupUtils { manager.updateBrowsable(channel.getId(), true, true); browsableChanged = true; } + if (firstChannelForInput == null) { + firstChannelForInput = channel; + } } } + if (firstChannelForInput != null) { + Utils.setLastWatchedChannel(context, firstChannelForInput); + } if (browsableChanged) { manager.notifyChannelBrowsableChanged(); manager.applyUpdatedValuesToDb(); @@ -382,13 +387,5 @@ public class SetupUtils { mSetUpInputs.add(inputId); mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply(); } - // Start fetching program guide data for internal tuners. - Context context = mTvApplication.getApplicationContext(); - if (Utils.isInternalTvInput(context, inputId)) { - if (context.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED && Experiments.CLOUD_EPG.get()) { - EpgFetcher.getInstance(context).startImmediately(); - } - } } -} +} \ No newline at end of file diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/util/StringUtils.java new file mode 100644 index 00000000..659807e2 --- /dev/null +++ b/src/com/android/tv/util/StringUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +/** + * Utility class for handling {@link String}. + */ +public final class StringUtils { + + private StringUtils() { } + + /** + * Returns compares two strings lexicographically and handles null values quietly. + */ + public static int compare(String a, String b) { + if (a == null) { + return b == null ? 0 : -1; + } + if (b == null) { + return 1; + } + return a.compareTo(b); + } +} diff --git a/src/com/android/tv/util/TimeShiftUtils.java b/src/com/android/tv/util/TimeShiftUtils.java index 238d0e74..8038a78f 100644 --- a/src/com/android/tv/util/TimeShiftUtils.java +++ b/src/com/android/tv/util/TimeShiftUtils.java @@ -18,7 +18,6 @@ package com.android.tv.util; import java.util.concurrent.TimeUnit; -// TODO: move related functions in TimeShiftManger here. /** * A class that includes convenience methods for time shift plays. */ @@ -40,7 +39,7 @@ public class TimeShiftUtils { * Returns real speeds used in time shift play. This method is only for fast-forwarding and * rewinding. The normal play speed is not addressed here. * - * @param speedLevel the valid value is ranged from 0 to {@link MAX_SPPED_LEVEL}. + * @param speedLevel the valid value is ranged from 0 to {@link #MAX_SPEED_LEVEL}. * @param programDurationMillis the length of program under playing. * @throws IndexOutOfBoundsException if speed level is out of its range. */ @@ -60,4 +59,3 @@ public class TimeShiftUtils { : SHORT_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL]; } } - diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index 121f56ed..730a985b 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -18,20 +18,26 @@ package com.android.tv.util; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; import android.os.Handler; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import com.android.tv.Features; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.TvCommonUtils; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -42,14 +48,64 @@ import java.util.Map; public class TvInputManagerHelper { private static final String TAG = "TvInputManagerHelper"; private static final boolean DEBUG = false; + + /** + * Types of HDMI device and bundled tuner. + */ + public static final int TYPE_CEC_DEVICE = -2; + public static final int TYPE_BUNDLED_TUNER = -3; + public static final int TYPE_CEC_DEVICE_RECORDER = -4; + public static final int TYPE_CEC_DEVICE_PLAYBACK = -5; + public static final int TYPE_MHL_MOBILE = -6; + + private static final String PERMISSION_ACCESS_ALL_EPG_DATA = + "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; + private static final String [] mPhysicalTunerBlackList = { + }; + private static final String META_LABEL_SORT_KEY = "input_sort_key"; + + /** + * The default tv input priority to show. + */ + private static final ArrayList DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>(); + static { + DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER); + DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE); + DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER); + DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK); + DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART); + DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER); + } + private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = { }; + private static final String[] TESTABLE_INPUTS = { + "com.android.tv.testinput/.TestTvInputService" + }; + private final Context mContext; + private final PackageManager mPackageManager; private final TvInputManager mTvInputManager; private final Map mInputStateMap = new HashMap<>(); private final Map mInputMap = new HashMap<>(); + private final Map mTvInputLabels = new ArrayMap<>(); + private final Map mTvInputCustomLabels = new ArrayMap<>(); private final Map mInputIdToPartnerInputMap = new HashMap<>(); + + private final Map mTvInputApplicationLabels = new ArrayMap<>(); + private final Map mTvInputApplicationIcons = new ArrayMap<>(); + private final Map mTvInputAppliactionBanners = new ArrayMap<>(); + private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override public void onInputStateChanged(String inputId, int state) { @@ -72,6 +128,11 @@ public class TvInputManagerHelper { TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); if (info != null) { mInputMap.put(inputId, info); + mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = info.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + } mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId)); mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info)); } @@ -85,6 +146,11 @@ public class TvInputManagerHelper { public void onInputRemoved(String inputId) { if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); mInputMap.remove(inputId); + mTvInputLabels.remove(inputId); + mTvInputCustomLabels.remove(inputId); + mTvInputApplicationLabels.remove(inputId); + mTvInputApplicationIcons.remove(inputId); + mTvInputAppliactionBanners.remove(inputId); mInputStateMap.remove(inputId); mInputIdToPartnerInputMap.remove(inputId); mContentRatingsManager.update(); @@ -103,6 +169,14 @@ public class TvInputManagerHelper { } TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); mInputMap.put(inputId, info); + mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = info.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + } + mTvInputApplicationLabels.remove(inputId); + mTvInputApplicationIcons.remove(inputId); + mTvInputAppliactionBanners.remove(inputId); for (TvInputCallback callback : mCallbacks) { callback.onInputUpdated(inputId); } @@ -114,6 +188,11 @@ public class TvInputManagerHelper { public void onTvInputInfoUpdated(TvInputInfo inputInfo) { if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo); mInputMap.put(inputInfo.getId(), inputInfo); + mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString()); + } for (TvInputCallback callback : mCallbacks) { callback.onTvInputInfoUpdated(inputInfo); } @@ -131,13 +210,18 @@ public class TvInputManagerHelper { public TvInputManagerHelper(Context context) { mContext = context.getApplicationContext(); + mPackageManager = context.getPackageManager(); mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); mContentRatingsManager = new ContentRatingsManager(context); mParentalControlSettings = new ParentalControlSettings(context); - mTvInputInfoComparator = new TvInputInfoComparator(this); + mTvInputInfoComparator = new InputComparatorInternal(this); } public void start() { + if (!hasTvInputManager()) { + // Not a TV device + return; + } if (mStarted) { return; } @@ -145,6 +229,11 @@ public class TvInputManagerHelper { mStarted = true; mTvInputManager.registerCallback(mInternalCallback, mHandler); mInputMap.clear(); + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + mTvInputApplicationIcons.clear(); + mTvInputAppliactionBanners.clear(); mInputStateMap.clear(); mInputIdToPartnerInputMap.clear(); for (TvInputInfo input : mTvInputManager.getTvInputList()) { @@ -171,9 +260,23 @@ public class TvInputManagerHelper { mStarted = false; mInputStateMap.clear(); mInputMap.clear(); + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + mTvInputApplicationIcons.clear(); + mTvInputAppliactionBanners.clear();; mInputIdToPartnerInputMap.clear(); } + /** + * Clears the TvInput labels map. + */ + public void clearTvInputLabels() { + mTvInputLabels.clear(); + mTvInputCustomLabels.clear(); + mTvInputApplicationLabels.clear(); + } + public List getTvInputInfos(boolean availableOnly, boolean tunerOnly) { ArrayList list = new ArrayList<>(); for (Map.Entry pair : mInputStateMap.entrySet()) { @@ -192,7 +295,7 @@ public class TvInputManagerHelper { /** * Returns the default comparator for {@link TvInputInfo}. - * See {@link TvInputInfoComparator} for detail. + * See {@link InputComparatorInternal} for detail. */ public Comparator getDefaultTvInputInfoComparator() { return mTvInputInfoComparator; @@ -237,15 +340,81 @@ public class TvInputManagerHelper { } /** - * Loads label of {@code info}. + * Is (Context.TV_INPUT_SERVICE) available. * - * It's visible for comparator test to mock TvInputInfo. - * Package private is enough for this method, but public is necessary to workaround mockito - * bug. + *

This is only available on TV devices. + */ + public boolean hasTvInputManager() { + return mTvInputManager != null; + } + + /** + * Loads label of {@code info}. */ - @VisibleForTesting public String loadLabel(TvInputInfo info) { - return info.loadLabel(mContext).toString(); + String label = mTvInputLabels.get(info.getId()); + if (label == null) { + label = info.loadLabel(mContext).toString(); + mTvInputLabels.put(info.getId(), label); + } + return label; + } + + /** + * Loads custom label of {@code info} + */ + public String loadCustomLabel(TvInputInfo info) { + String customLabel = mTvInputCustomLabels.get(info.getId()); + if (customLabel == null) { + CharSequence customLabelCharSequence = info.loadCustomLabel(mContext); + if (customLabelCharSequence != null) { + customLabel = customLabelCharSequence.toString(); + mTvInputCustomLabels.put(info.getId(), customLabel); + } + } + return customLabel; + } + + /** + * Gets the tv input application's label. + */ + public CharSequence getTvInputApplicationLabel(CharSequence inputId) { + return mTvInputApplicationLabels.get(inputId); + } + + /** + * Stores the tv input application's label. + */ + public void setTvInputApplicationLabel(String inputId, CharSequence label) { + mTvInputApplicationLabels.put(inputId, label); + } + + /** + * Gets the tv input application's icon. + */ + public Drawable getTvInputApplicationIcon(String inputId) { + return mTvInputApplicationIcons.get(inputId); + } + + /** + * Stores the tv input application's icon. + */ + public void setTvInputApplicationIcon(String inputId, Drawable icon) { + mTvInputApplicationIcons.put(inputId, icon); + } + + /** + * Gets the tv input application's banner. + */ + public Drawable getTvInputApplicationBanner(String inputId) { + return mTvInputAppliactionBanners.get(inputId); + } + + /** + * Stores the tv input application's banner. + */ + public void setTvInputApplicationBanner(String inputId, Drawable banner) { + mTvInputAppliactionBanners.put(inputId, banner); } /** @@ -321,14 +490,54 @@ public class TvInputManagerHelper { return mContentRatingsManager; } - private boolean isInBlackList(String inputId) { - if (!Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { + private int getInputSortKey(TvInputInfo input) { + return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, + Integer.MAX_VALUE); + } + + private boolean isInputPhysicalTuner(TvInputInfo input) { + String packageName = input.getServiceInfo().packageName; + if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) { return false; } - for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) { - if (inputId.contains(disabledTunerInputPrefix)) { - return true; + + if (input.createSetupIntent() == null) { + return false; + } else { + boolean mayBeTunerInput = mPackageManager.checkPermission( + PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName) + == PackageManager.PERMISSION_GRANTED; + if (!mayBeTunerInput) { + try { + ApplicationInfo ai = mPackageManager.getApplicationInfo( + input.getServiceInfo().packageName, 0); + if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) { + return false; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + } + return true; + } + + private boolean isInBlackList(String inputId) { + if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { + for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) { + if (inputId.contains(disabledTunerInputPrefix)) { + return true; + } + } + } + if (TvCommonUtils.isRunningInTest()) { + for (String testableInput : TESTABLE_INPUTS) { + if (testableInput.equals(inputId)) { + return false; + } } + return true; } return false; } @@ -342,10 +551,10 @@ public class TvInputManagerHelper { * (i.e. Mockito's spy doesn't work) */ @VisibleForTesting - static class TvInputInfoComparator implements Comparator { + static class InputComparatorInternal implements Comparator { private final TvInputManagerHelper mInputManager; - public TvInputInfoComparator(TvInputManagerHelper inputManager) { + public InputComparatorInternal(TvInputManagerHelper inputManager) { mInputManager = inputManager; } @@ -357,4 +566,123 @@ public class TvInputManagerHelper { return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs)); } } + + /** + * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of + * TV inputs. + */ + public static class HardwareInputComparator implements Comparator { + private Map mTypePriorities = new HashMap<>(); + private final TvInputManagerHelper mTvInputManagerHelper; + private final Context mContext; + + public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) { + mContext = context; + mTvInputManagerHelper = tvInputManagerHelper; + setupDeviceTypePriorities(); + } + + @Override + public int compare(TvInputInfo lhs, TvInputInfo rhs) { + if (lhs == null) { + return (rhs == null) ? 0 : 1; + } + if (rhs == null) { + return -1; + } + + boolean enabledL = (mTvInputManagerHelper.getInputState(lhs) + != TvInputManager.INPUT_STATE_DISCONNECTED); + boolean enabledR = (mTvInputManagerHelper.getInputState(rhs) + != TvInputManager.INPUT_STATE_DISCONNECTED); + if (enabledL != enabledR) { + return enabledL ? -1 : 1; + } + + int priorityL = getPriority(lhs); + int priorityR = getPriority(rhs); + if (priorityL != priorityR) { + return priorityL - priorityR; + } + + if (lhs.getType() == TvInputInfo.TYPE_TUNER + && rhs.getType() == TvInputInfo.TYPE_TUNER) { + boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs); + boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs); + if (isPhysicalL != isPhysicalR) { + return isPhysicalL ? -1 : 1; + } + } + + int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs); + int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs); + if (sortKeyL != sortKeyR) { + return sortKeyR - sortKeyL; + } + + String parentLabelL = lhs.getParentId() != null + ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId())) + : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId())); + String parentLabelR = rhs.getParentId() != null + ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId())) + : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId())); + + if (!TextUtils.equals(parentLabelL, parentLabelR)) { + return parentLabelL.compareToIgnoreCase(parentLabelR); + } + return getLabel(lhs).compareToIgnoreCase(getLabel(rhs)); + } + + private String getLabel(TvInputInfo input) { + if (input == null) { + return ""; + } + String label = mTvInputManagerHelper.loadCustomLabel(input); + if (TextUtils.isEmpty(label)) { + label = mTvInputManagerHelper.loadLabel(input); + } + return label; + } + + private int getPriority(TvInputInfo info) { + Integer priority = null; + if (mTypePriorities != null) { + priority = mTypePriorities.get(getTvInputTypeForPriority(info)); + } + if (priority != null) { + return priority; + } + return Integer.MAX_VALUE; + } + + private void setupDeviceTypePriorities() { + mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap(); + + // Fill in any missing priorities in the map we got from the OEM + int priority = mTypePriorities.size(); + for (int type : DEFAULT_TV_INPUT_PRIORITY) { + if (!mTypePriorities.containsKey(type)) { + mTypePriorities.put(type, priority++); + } + } + } + + private int getTvInputTypeForPriority(TvInputInfo info) { + if (info.getHdmiDeviceInfo() != null) { + if (info.getHdmiDeviceInfo().isCecDevice()) { + switch (info.getHdmiDeviceInfo().getDeviceType()) { + case HdmiDeviceInfo.DEVICE_RECORDER: + return TYPE_CEC_DEVICE_RECORDER; + case HdmiDeviceInfo.DEVICE_PLAYBACK: + return TYPE_CEC_DEVICE_PLAYBACK; + default: + return TYPE_CEC_DEVICE; + } + } else if (info.getHdmiDeviceInfo().isMhlDevice()) { + return TYPE_MHL_MOBILE; + } + } + return info.getType(); + } + } } diff --git a/src/com/android/tv/util/TvProviderUriMatcher.java b/src/com/android/tv/util/TvProviderUriMatcher.java deleted file mode 100644 index 749e4aa3..00000000 --- a/src/com/android/tv/util/TvProviderUriMatcher.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.content.UriMatcher; -import android.media.tv.TvContract; -import android.net.Uri; -import android.support.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Utility class to aid in matching URIs in TvProvider. - */ -public class TvProviderUriMatcher { - private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); - - @Retention(RetentionPolicy.SOURCE) - @IntDef({MATCH_CHANNEL, MATCH_CHANNEL_ID, MATCH_PROGRAM, MATCH_PROGRAM_ID, - MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID}) - private @interface TvProviderUriMatchCode {} - /** The code for the channels URI. */ - public static final int MATCH_CHANNEL = 1; - /** The code for the channel URI. */ - public static final int MATCH_CHANNEL_ID = 2; - /** The code for the programs URI. */ - public static final int MATCH_PROGRAM = 3; - /** The code for the program URI. */ - public static final int MATCH_PROGRAM_ID = 4; - /** The code for the recorded programs URI. */ - public static final int MATCH_RECORDED_PROGRAM = 5; - /** The code for the recorded program URI. */ - public static final int MATCH_RECORDED_PROGRAM_ID = 6; - /** The code for the watched program URI. */ - public static final int MATCH_WATCHED_PROGRAM_ID = 7; - static { - URI_MATCHER.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL); - URI_MATCHER.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID); - URI_MATCHER.addURI(TvContract.AUTHORITY, "program", MATCH_PROGRAM); - URI_MATCHER.addURI(TvContract.AUTHORITY, "program/#", MATCH_PROGRAM_ID); - URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM); - URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID); - URI_MATCHER.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID); - } - - private TvProviderUriMatcher() { } - - /** - * Try to match against the path in a url. - * - * @see UriMatcher#match - */ - @SuppressWarnings("WrongConstant") - @TvProviderUriMatchCode public static int match(Uri uri) { - return URI_MATCHER.match(uri); - } -} diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java index 97ff59d6..c5fde317 100644 --- a/src/com/android/tv/util/TvSettings.java +++ b/src/com/android/tv/util/TvSettings.java @@ -17,6 +17,8 @@ package com.android.tv.util; import android.content.Context; +import android.content.SharedPreferences; +import android.media.tv.TvTrackInfo; import android.preference.PreferenceManager; import android.support.annotation.IntDef; @@ -26,53 +28,27 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; - /** * A class about the constants for TV settings. * Objects that are returned from the various {@code get} methods must be treated as immutable. */ public final class TvSettings { - private TvSettings() {} - public static final String PREF_DISPLAY_MODE = "display_mode"; // int value - public static final String PREF_PIP_LAYOUT = "pip_layout"; // int value - public static final String PREF_PIP_SIZE = "pip_size"; // int value public static final String PREF_PIN = "pin"; // 4-digit string value. Otherwise, it's not set. - // PIP sounds - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - PIP_SOUND_MAIN, PIP_SOUND_PIP_WINDOW }) - public @interface PipSound {} - public static final int PIP_SOUND_MAIN = 0; - public static final int PIP_SOUND_PIP_WINDOW = PIP_SOUND_MAIN + 1; - - // PIP layouts - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - PIP_LAYOUT_BOTTOM_RIGHT, PIP_LAYOUT_TOP_RIGHT, PIP_LAYOUT_TOP_LEFT, - PIP_LAYOUT_BOTTOM_LEFT, PIP_LAYOUT_SIDE_BY_SIDE }) - public @interface PipLayout {} - public static final int PIP_LAYOUT_BOTTOM_RIGHT = 0; - public static final int PIP_LAYOUT_TOP_RIGHT = PIP_LAYOUT_BOTTOM_RIGHT + 1; - public static final int PIP_LAYOUT_TOP_LEFT = PIP_LAYOUT_TOP_RIGHT + 1; - public static final int PIP_LAYOUT_BOTTOM_LEFT = PIP_LAYOUT_TOP_LEFT + 1; - public static final int PIP_LAYOUT_SIDE_BY_SIDE = PIP_LAYOUT_BOTTOM_LEFT + 1; - public static final int PIP_LAYOUT_LAST = PIP_LAYOUT_SIDE_BY_SIDE; - - // PIP sizes - @Retention(RetentionPolicy.SOURCE) - @IntDef({ PIP_SIZE_SMALL, PIP_SIZE_BIG }) - public @interface PipSize {} - public static final int PIP_SIZE_SMALL = 0; - public static final int PIP_SIZE_BIG = PIP_SIZE_SMALL + 1; - public static final int PIP_SIZE_LAST = PIP_SIZE_BIG; - // Multi-track audio settings private static final String PREF_MULTI_AUDIO_ID = "pref.multi_audio_id"; private static final String PREF_MULTI_AUDIO_LANGUAGE = "pref.multi_audio_language"; private static final String PREF_MULTI_AUDIO_CHANNEL_COUNT = "pref.multi_audio_channel_count"; + // DVR Multi-audio and subtitle settings + private static final String PREF_DVR_MULTI_AUDIO_ID = "pref.dvr_multi_audio_id"; + private static final String PREF_DVR_MULTI_AUDIO_LANGUAGE = "pref.dvr_multi_audio_language"; + private static final String PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT = + "pref.dvr_multi_audio_channel_count"; + private static final String PREF_DVR_SUBTITLE_ID = "pref.dvr_subtitle_id"; + private static final String PREF_DVR_SUBTITLE_LANGUAGE = "pref.dvr_subtitle_language"; + // Parental Control settings private static final String PREF_CONTENT_RATING_SYSTEMS = "pref.content_rating_systems"; private static final String PREF_CONTENT_RATING_LEVEL = "pref.content_rating_level"; @@ -89,58 +65,7 @@ public final class TvSettings { public static final int CONTENT_RATING_LEVEL_LOW = 3; public static final int CONTENT_RATING_LEVEL_CUSTOM = 4; - // PIP settings - /** - * Returns the layout of the PIP window stored in the shared preferences. - * - * @return the saved layout of the PIP window. This value is one of - * {@link #PIP_LAYOUT_TOP_LEFT}, {@link #PIP_LAYOUT_TOP_RIGHT}, - * {@link #PIP_LAYOUT_BOTTOM_LEFT}, {@link #PIP_LAYOUT_BOTTOM_RIGHT} and - * {@link #PIP_LAYOUT_SIDE_BY_SIDE}. If the preference value does not exist, - * {@link #PIP_LAYOUT_BOTTOM_RIGHT} is returned. - */ - @SuppressWarnings("ResourceType") - @PipLayout - public static int getPipLayout(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getInt( - PREF_PIP_LAYOUT, PIP_LAYOUT_BOTTOM_RIGHT); - } - - /** - * Stores the layout of PIP window to the shared preferences. - * - * @param pipLayout This value should be one of {@link #PIP_LAYOUT_TOP_LEFT}, - * {@link #PIP_LAYOUT_TOP_RIGHT}, {@link #PIP_LAYOUT_BOTTOM_LEFT}, - * {@link #PIP_LAYOUT_BOTTOM_RIGHT} and {@link #PIP_LAYOUT_SIDE_BY_SIDE}. - */ - public static void setPipLayout(Context context, @PipLayout int pipLayout) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putInt( - PREF_PIP_LAYOUT, pipLayout).apply(); - } - - /** - * Returns the size of the PIP view stored in the shared preferences. - * - * @return the saved size of the PIP view. This value is one of - * {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}. If the preference value does not - * exist, {@link #PIP_SIZE_SMALL} is returned. - */ - @SuppressWarnings("ResourceType") - @PipSize - public static int getPipSize(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getInt( - PREF_PIP_SIZE, PIP_SIZE_SMALL); - } - - /** - * Stores the size of PIP view to the shared preferences. - * - * @param pipSize This value should be one of {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}. - */ - public static void setPipSize(Context context, @PipSize int pipSize) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putInt( - PREF_PIP_SIZE, pipSize).apply(); - } + private TvSettings() {} // Multi-track audio settings public static String getMultiAudioId(Context context) { @@ -173,26 +98,61 @@ public final class TvSettings { PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount).apply(); } - // Parental Control settings - public static void addContentRatingSystems(Context context, Set ids) { - Set contentRatingSystemSet = getContentRatingSystemSet(context); - if (contentRatingSystemSet.addAll(ids)) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply(); + public static void setDvrPlaybackTrackSettings(Context context, int trackType, + TvTrackInfo info) { + if (trackType == TvTrackInfo.TYPE_AUDIO) { + if (info == null) { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .remove(PREF_DVR_MULTI_AUDIO_ID).apply(); + } else { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(PREF_DVR_MULTI_AUDIO_LANGUAGE, info.getLanguage()) + .putInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, info.getAudioChannelCount()) + .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId()).apply(); + } + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + if (info == null) { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .remove(PREF_DVR_SUBTITLE_ID).apply(); + } else { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(PREF_DVR_SUBTITLE_LANGUAGE, info.getLanguage()) + .putString(PREF_DVR_SUBTITLE_ID, info.getId()).apply(); + } } } - public static void addContentRatingSystem(Context context, String id) { - Set contentRatingSystemSet = getContentRatingSystemSet(context); - if (contentRatingSystemSet.add(id)) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply(); + public static TvTrackInfo getDvrPlaybackTrackSettings(Context context, + int trackType) { + String language; + String trackId; + int channelCount; + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); + if (trackType == TvTrackInfo.TYPE_AUDIO) { + trackId = pref.getString(PREF_DVR_MULTI_AUDIO_ID, null); + if (trackId == null) { + return null; + } + language = pref.getString(PREF_DVR_MULTI_AUDIO_LANGUAGE, null); + channelCount = pref.getInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, 0); + return new TvTrackInfo.Builder(trackType, trackId) + .setLanguage(language).setAudioChannelCount(channelCount).build(); + } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { + trackId = pref.getString(PREF_DVR_SUBTITLE_ID, null); + if (trackId == null) { + return null; + } + language = pref.getString(PREF_DVR_SUBTITLE_LANGUAGE, null); + return new TvTrackInfo.Builder(trackType, trackId).setLanguage(language).build(); + } else { + return null; } } - public static void removeContentRatingSystems(Context context, Set ids) { + // Parental Control settings + public static void addContentRatingSystem(Context context, String id) { Set contentRatingSystemSet = getContentRatingSystemSet(context); - if (contentRatingSystemSet.removeAll(ids)) { + if (contentRatingSystemSet.add(id)) { PreferenceManager.getDefaultSharedPreferences(context).edit() .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply(); } @@ -254,4 +214,4 @@ public final class TvSettings { PreferenceManager.getDefaultSharedPreferences(context).edit().putLong( PREF_DISABLE_PIN_UNTIL, timeMillis).apply(); } -} +} \ No newline at end of file diff --git a/src/com/android/tv/util/TvTrackInfoUtils.java b/src/com/android/tv/util/TvTrackInfoUtils.java index c004f001..667cc9bf 100644 --- a/src/com/android/tv/util/TvTrackInfoUtils.java +++ b/src/com/android/tv/util/TvTrackInfoUtils.java @@ -52,35 +52,22 @@ public class TvTrackInfoUtils { } // Assumes {@code null} language matches to any language since it means user hasn't // selected any track before or selected a track without language information. - boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(), - language); boolean lhsLangMatch = language == null || Utils.isEqualLanguage(lhs.getLanguage(), language); - if (rhsLangMatch) { - if (lhsLangMatch) { - boolean rhsCountMatch = rhs.getAudioChannelCount() == channelCount; - boolean lhsCountMatch = lhs.getAudioChannelCount() == channelCount; - if (rhsCountMatch) { - if (lhsCountMatch) { - boolean rhsIdMatch = rhs.getId().equals(id); - boolean lhsIdMatch = lhs.getId().equals(id); - if (rhsIdMatch) { - return lhsIdMatch ? 0 : -1; - } else { - return lhsIdMatch ? 1 : 0; - } - - } else { - return -1; - } - } else { - return lhsCountMatch ? 1 : 0; - } + boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(), + language); + if (lhsLangMatch && rhsLangMatch) { + boolean lhsCountMatch = lhs.getType() != TvTrackInfo.TYPE_AUDIO + || lhs.getAudioChannelCount() == channelCount; + boolean rhsCountMatch = rhs.getType() != TvTrackInfo.TYPE_AUDIO + || rhs.getAudioChannelCount() == channelCount; + if (lhsCountMatch && rhsCountMatch) { + return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id)); } else { - return -1; + return Boolean.compare(lhsCountMatch, rhsCountMatch); } } else { - return lhsLangMatch ? 1 : 0; + return Boolean.compare(lhsLangMatch, rhsLangMatch); } } }; @@ -112,4 +99,4 @@ public class TvTrackInfoUtils { private TvTrackInfoUtils() { } -} +} \ No newline at end of file diff --git a/src/com/android/tv/util/TvUriMatcher.java b/src/com/android/tv/util/TvUriMatcher.java new file mode 100644 index 00000000..3d91cdad --- /dev/null +++ b/src/com/android/tv/util/TvUriMatcher.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +import android.app.SearchManager; +import android.content.UriMatcher; +import android.media.tv.TvContract; +import android.net.Uri; +import android.support.annotation.IntDef; + +import com.android.tv.search.LocalSearchProvider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Utility class to aid in matching URIs in TvProvider. + */ +public class TvUriMatcher { + private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + + @Retention(RetentionPolicy.SOURCE) + @IntDef({MATCH_CHANNEL, MATCH_CHANNEL_ID, MATCH_PROGRAM, MATCH_PROGRAM_ID, + MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID, + MATCH_ON_DEVICE_SEARCH}) + private @interface TvProviderUriMatchCode {} + /** The code for the channels URI. */ + public static final int MATCH_CHANNEL = 1; + /** The code for the channel URI. */ + public static final int MATCH_CHANNEL_ID = 2; + /** The code for the programs URI. */ + public static final int MATCH_PROGRAM = 3; + /** The code for the program URI. */ + public static final int MATCH_PROGRAM_ID = 4; + /** The code for the recorded programs URI. */ + public static final int MATCH_RECORDED_PROGRAM = 5; + /** The code for the recorded program URI. */ + public static final int MATCH_RECORDED_PROGRAM_ID = 6; + /** The code for the watched program URI. */ + public static final int MATCH_WATCHED_PROGRAM_ID = 7; + /** The code for the on-device search URI. */ + public static final int MATCH_ON_DEVICE_SEARCH = 8; + static { + URI_MATCHER.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL); + URI_MATCHER.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID); + URI_MATCHER.addURI(TvContract.AUTHORITY, "program", MATCH_PROGRAM); + URI_MATCHER.addURI(TvContract.AUTHORITY, "program/#", MATCH_PROGRAM_ID); + URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM); + URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID); + URI_MATCHER.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID); + URI_MATCHER.addURI(LocalSearchProvider.AUTHORITY, + SearchManager.SUGGEST_URI_PATH_QUERY + "/*", MATCH_ON_DEVICE_SEARCH); + } + + private TvUriMatcher() { } + + /** + * Try to match against the path in a url. + * + * @see UriMatcher#match + */ + @SuppressWarnings("WrongConstant") + @TvProviderUriMatchCode public static int match(Uri uri) { + return URI_MATCHER.match(uri); + } +} diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java index 99d34431..d11bab3c 100644 --- a/src/com/android/tv/util/Utils.java +++ b/src/com/android/tv/util/Utils.java @@ -31,6 +31,7 @@ import android.media.tv.TvContract.Programs.Genres; import android.media.tv.TvInputInfo; import android.media.tv.TvTrackInfo; import android.net.Uri; +import android.os.Looper; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -44,11 +45,13 @@ import android.view.View; import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; +import com.android.tv.common.BuildConfig; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; +import com.android.tv.experiments.Experiments; import java.io.File; import java.text.SimpleDateFormat; @@ -62,6 +65,8 @@ import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** @@ -74,7 +79,6 @@ public class Utils { private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); - public static final String EXTRA_KEY_KEYCODE = "keycode"; public static final String EXTRA_KEY_ACTION = "action"; public static final String EXTRA_ACTION_SHOW_TV_INPUT ="show_tv_input"; public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher"; @@ -83,11 +87,9 @@ public class Utils { public static final String EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED = "recorded_program_pin_checked"; - // Query parameter in the intent of starting MainActivity. - public static final String PARAM_SOURCE = "source"; - private static final String PATH_CHANNEL = "channel"; private static final String PATH_PROGRAM = "program"; + private static final String PATH_RECORDED_PROGRAM = "recorded_program"; private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id"; private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT = @@ -97,6 +99,8 @@ public class Utils { "last_watched_tuner_input_id"; private static final String PREF_KEY_RECORDING_FAILED_REASONS = "recording_failed_reasons"; + private static final String PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET = + "failed_scheduled_recording_info_set"; private static final int VIDEO_SD_WIDTH = 704; private static final int VIDEO_SD_HEIGHT = 480; @@ -114,6 +118,7 @@ public class Utils { private static final int AUDIO_CHANNEL_SURROUND_8 = 8; private static final long RECORDING_FAILED_REASON_NONE = 0; + private static final long HALF_MINUTE_MS = TimeUnit.SECONDS.toMillis(30); private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); // Hardcoded list for known bundled inputs not written by OEM/SOCs. @@ -206,6 +211,28 @@ public class Utils { .apply(); } + /** + * Adds the info of failed scheduled recording. + */ + public static void addFailedScheduledRecordingInfo(Context context, + String scheduledRecordingInfo) { + Set failedScheduledRecordingInfoSet = getFailedScheduledRecordingInfoSet(context); + failedScheduledRecordingInfoSet.add(scheduledRecordingInfo); + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, + failedScheduledRecordingInfoSet) + .apply(); + } + + /** + * Clears the failed scheduled recording info set. + */ + public static void clearFailedScheduledRecordingInfoSet(Context context) { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .remove(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET) + .apply(); + } + /** * Clears recording failed reason. */ @@ -245,6 +272,14 @@ public class Utils { RECORDING_FAILED_REASON_NONE); } + /** + * Returns the failed scheduled recordings info set. + */ + public static Set getFailedScheduledRecordingInfoSet(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, new HashSet<>()); + } + /** * Checks do recording failed reason exist. */ @@ -295,6 +330,13 @@ public class Utils { return isTvUri(uri) && PATH_PROGRAM.equals(uri.getPathSegments().get(0)); } + /** + * Returns {@code true}, if {@code uri} is a programs URI. + */ + public static boolean isRecordedProgramsUri(Uri uri) { + return isTvUri(uri) && PATH_RECORDED_PROGRAM.equals(uri.getPathSegments().get(0)); + } + /** * Gets the info of the program on particular time. */ @@ -332,6 +374,14 @@ public class Utils { return getProgramAt(context, channelId, System.currentTimeMillis()); } + /** + * Returns the round off minutes when convert milliseconds to minutes. + */ + public static int getRoundOffMinsFromMs(long millis) { + // Round off the result by adding half minute to the original ms. + return (int) TimeUnit.MILLISECONDS.toMinutes(millis + HALF_MINUTE_MS); + } + /** * Returns duration string according to the date & time format. * If {@code startUtcMillis} and {@code endUtcMills} are equal, @@ -392,16 +442,18 @@ public class Utils { : DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis + 1, flag); } - @VisibleForTesting + /** + * Checks if two given time (in milliseconds) are in the same day with regard to the + * locale timezone. + */ public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) { - final long DAY_IN_MS = TimeUnit.DAYS.toMillis(1); TimeZone timeZone = Calendar.getInstance().getTimeZone(); long offset = timeZone.getRawOffset(); if (timeZone.inDaylightTime(new Date(dayToMatchInMillis))) { offset += timeZone.getDSTSavings(); } - return Utils.floorTime(dayToMatchInMillis + offset, DAY_IN_MS) - == Utils.floorTime(subjectTimeInMillis + offset, DAY_IN_MS); + return Utils.floorTime(dayToMatchInMillis + offset, ONE_DAY_MS) + == Utils.floorTime(subjectTimeInMillis + offset, ONE_DAY_MS); } /** @@ -523,7 +575,7 @@ public class Utils { if (track.getType() != TvTrackInfo.TYPE_AUDIO) { throw new IllegalArgumentException("Not an audio track: " + track); } - String language = context.getString(R.string.default_language); + String language = context.getString(R.string.multi_audio_unknown_language); if (!TextUtils.isEmpty(track.getLanguage())) { language = new Locale(track.getLanguage()).getDisplayName(); } else { @@ -606,10 +658,12 @@ public class Utils { if (input == null) { return null; } - CharSequence customLabel = input.loadCustomLabel(context); + TvInputManagerHelper inputManager = + TvApplication.getSingletons(context).getTvInputManagerHelper(); + CharSequence customLabel = inputManager.loadCustomLabel(input); String label = (customLabel == null) ? null : customLabel.toString(); if (TextUtils.isEmpty(label)) { - label = input.loadLabel(context).toString(); + label = inputManager.loadLabel(input).toString(); } return label; } @@ -860,4 +914,28 @@ public class Utils { } return Genres.encode(genres); } + + /** + * Returns true if the current user is a developer. + */ + public static boolean isDeveloper() { + return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get(); + } + + /** + * Runs the method in main thread. If the current thread is not main thread, block it util + * the method is finished. + */ + public static void runInMainThreadAndWait(Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + Future temp = MainThreadExecutor.getInstance().submit(runnable); + try { + temp.get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "failed to finish the execution", e); + } + } + } } diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java new file mode 100644 index 00000000..ed9a8ff6 --- /dev/null +++ b/src/com/android/tv/util/ViewCache.java @@ -0,0 +1,100 @@ +package com.android.tv.util; + +import android.content.Context; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * A cache for the views. + */ +public class ViewCache { + private final static SparseArray> mViews = new SparseArray(); + + private static ViewCache sViewCache; + + private ViewCache() { } + + /** + * Returns an instance of the view cache. + */ + public static ViewCache getInstance() { + if (sViewCache == null) { + sViewCache = new ViewCache(); + } + return sViewCache; + } + + /** + * Returns if the view cache is empty. + */ + public boolean isEmpty() { + return mViews.size() == 0; + } + + /** + * Stores a view into this view cache. + */ + public void putView(int resId, View view) { + ArrayList views = mViews.get(resId); + if (views == null) { + views = new ArrayList(); + mViews.put(resId, views); + } + views.add(view); + } + + /** + * Stores multi specific views into the view cache. + */ + public void putView(Context context, int resId, ViewGroup fakeParent, int num) { + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + ArrayList views = mViews.get(resId); + if (views == null) { + views = new ArrayList<>(); + mViews.put(resId, views); + } + for (int i = 0; i < num; i++) { + View view = inflater.inflate(resId, fakeParent, false); + views.add(view); + } + } + + /** + * Returns the view for specific resource id. + */ + public View getView(int resId) { + ArrayList views = mViews.get(resId); + if (views != null && !views.isEmpty()) { + View view = views.remove(views.size() - 1); + if (views.isEmpty()) { + mViews.remove(resId); + } + return view; + } else { + return null; + } + } + + /** + * Returns the view if exists, or create a new view for the specific resource id. + */ + public View getOrCreateView(LayoutInflater inflater, int resId, ViewGroup container) { + View view = getView(resId); + if (view == null) { + view = inflater.inflate(resId, container, false); + } + return view; + } + + /** + * Clears the view cache. + */ + public void clear() { + mViews.clear(); + } +} diff --git a/tests/OWNERS b/tests/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/tests/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/tests/common/Android.mk b/tests/common/Android.mk index aecd15bc..27c9f031 100644 --- a/tests/common/Android.mk +++ b/tests/common/Android.mk @@ -17,7 +17,7 @@ LOCAL_JAVA_LIBRARIES := tv-common LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_MODULE := tv-test-common LOCAL_MODULE_TAGS := optional -LOCAL_SDK_VERSION := system_current +LOCAL_SDK_VERSION := current LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/src diff --git a/tests/common/OWNERS b/tests/common/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/tests/common/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/tests/common/res/drawable-xhdpi/ch_1000_logo.png b/tests/common/res/drawable-xhdpi/ch_1000_logo.png new file mode 100644 index 00000000..eec6d373 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_1000_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_100_logo.png b/tests/common/res/drawable-xhdpi/ch_100_logo.png new file mode 100644 index 00000000..3a8b6731 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_100_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_101_logo.png b/tests/common/res/drawable-xhdpi/ch_101_logo.png new file mode 100644 index 00000000..2977ef1d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_101_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_102_logo.png b/tests/common/res/drawable-xhdpi/ch_102_logo.png new file mode 100644 index 00000000..978112e1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_102_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_103_logo.png b/tests/common/res/drawable-xhdpi/ch_103_logo.png new file mode 100644 index 00000000..ceb1fd6a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_103_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_104_logo.png b/tests/common/res/drawable-xhdpi/ch_104_logo.png new file mode 100644 index 00000000..c927568d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_104_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_105_logo.png b/tests/common/res/drawable-xhdpi/ch_105_logo.png new file mode 100644 index 00000000..8e1be19d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_105_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_106_logo.png b/tests/common/res/drawable-xhdpi/ch_106_logo.png new file mode 100644 index 00000000..a19c9ef8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_106_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_107_logo.png b/tests/common/res/drawable-xhdpi/ch_107_logo.png new file mode 100644 index 00000000..9d36a488 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_107_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_108_logo.png b/tests/common/res/drawable-xhdpi/ch_108_logo.png new file mode 100644 index 00000000..700ae189 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_108_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_109_logo.png b/tests/common/res/drawable-xhdpi/ch_109_logo.png new file mode 100644 index 00000000..209e3b47 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_109_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_10_logo.png b/tests/common/res/drawable-xhdpi/ch_10_logo.png new file mode 100644 index 00000000..76b3a9b2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_10_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_110_logo.png b/tests/common/res/drawable-xhdpi/ch_110_logo.png new file mode 100644 index 00000000..0c0c1920 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_110_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_111_logo.png b/tests/common/res/drawable-xhdpi/ch_111_logo.png new file mode 100644 index 00000000..07c7ee83 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_111_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_112_logo.png b/tests/common/res/drawable-xhdpi/ch_112_logo.png new file mode 100644 index 00000000..1675e54d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_112_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_113_logo.png b/tests/common/res/drawable-xhdpi/ch_113_logo.png new file mode 100644 index 00000000..57cc81ce Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_113_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_114_logo.png b/tests/common/res/drawable-xhdpi/ch_114_logo.png new file mode 100644 index 00000000..3d57f201 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_114_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_115_logo.png b/tests/common/res/drawable-xhdpi/ch_115_logo.png new file mode 100644 index 00000000..3897f5c9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_115_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_116_logo.png b/tests/common/res/drawable-xhdpi/ch_116_logo.png new file mode 100644 index 00000000..83a55a67 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_116_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_117_logo.png b/tests/common/res/drawable-xhdpi/ch_117_logo.png new file mode 100644 index 00000000..4b4eccf0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_117_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_118_logo.png b/tests/common/res/drawable-xhdpi/ch_118_logo.png new file mode 100644 index 00000000..4a682f67 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_118_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_119_logo.png b/tests/common/res/drawable-xhdpi/ch_119_logo.png new file mode 100644 index 00000000..2a2aed5e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_119_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_11_logo.png b/tests/common/res/drawable-xhdpi/ch_11_logo.png new file mode 100644 index 00000000..62268929 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_11_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_120_logo.png b/tests/common/res/drawable-xhdpi/ch_120_logo.png new file mode 100644 index 00000000..46c5f97a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_120_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_121_logo.png b/tests/common/res/drawable-xhdpi/ch_121_logo.png new file mode 100644 index 00000000..650bd3e4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_121_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_122_logo.png b/tests/common/res/drawable-xhdpi/ch_122_logo.png new file mode 100644 index 00000000..5a3c5d7b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_122_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_123_logo.png b/tests/common/res/drawable-xhdpi/ch_123_logo.png new file mode 100644 index 00000000..ade9ab29 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_123_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_124_logo.png b/tests/common/res/drawable-xhdpi/ch_124_logo.png new file mode 100644 index 00000000..62d15c06 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_124_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_125_logo.png b/tests/common/res/drawable-xhdpi/ch_125_logo.png new file mode 100644 index 00000000..34af08a9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_125_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_126_logo.png b/tests/common/res/drawable-xhdpi/ch_126_logo.png new file mode 100644 index 00000000..8d10d163 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_126_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_127_logo.png b/tests/common/res/drawable-xhdpi/ch_127_logo.png new file mode 100644 index 00000000..428f8e0d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_127_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_128_logo.png b/tests/common/res/drawable-xhdpi/ch_128_logo.png new file mode 100644 index 00000000..536e04fa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_128_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_129_logo.png b/tests/common/res/drawable-xhdpi/ch_129_logo.png new file mode 100644 index 00000000..79fc8dc8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_129_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_12_logo.png b/tests/common/res/drawable-xhdpi/ch_12_logo.png new file mode 100644 index 00000000..5f155f41 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_12_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_130_logo.png b/tests/common/res/drawable-xhdpi/ch_130_logo.png new file mode 100644 index 00000000..b2bc041f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_130_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_131_logo.png b/tests/common/res/drawable-xhdpi/ch_131_logo.png new file mode 100644 index 00000000..06081906 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_131_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_132_logo.png b/tests/common/res/drawable-xhdpi/ch_132_logo.png new file mode 100644 index 00000000..18a0bdef Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_132_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_133_logo.png b/tests/common/res/drawable-xhdpi/ch_133_logo.png new file mode 100644 index 00000000..312027b2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_133_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_134_logo.png b/tests/common/res/drawable-xhdpi/ch_134_logo.png new file mode 100644 index 00000000..c551922e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_134_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_135_logo.png b/tests/common/res/drawable-xhdpi/ch_135_logo.png new file mode 100644 index 00000000..64d7b889 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_135_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_136_logo.png b/tests/common/res/drawable-xhdpi/ch_136_logo.png new file mode 100644 index 00000000..31021239 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_136_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_137_logo.png b/tests/common/res/drawable-xhdpi/ch_137_logo.png new file mode 100644 index 00000000..a7f8cfb8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_137_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_138_logo.png b/tests/common/res/drawable-xhdpi/ch_138_logo.png new file mode 100644 index 00000000..981425f0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_138_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_139_logo.png b/tests/common/res/drawable-xhdpi/ch_139_logo.png new file mode 100644 index 00000000..03170e5e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_139_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_13_logo.png b/tests/common/res/drawable-xhdpi/ch_13_logo.png new file mode 100644 index 00000000..817922f8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_13_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_140_logo.png b/tests/common/res/drawable-xhdpi/ch_140_logo.png new file mode 100644 index 00000000..f26cf917 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_140_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_141_logo.png b/tests/common/res/drawable-xhdpi/ch_141_logo.png new file mode 100644 index 00000000..0064d436 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_141_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_142_logo.png b/tests/common/res/drawable-xhdpi/ch_142_logo.png new file mode 100644 index 00000000..1d28785e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_142_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_143_logo.png b/tests/common/res/drawable-xhdpi/ch_143_logo.png new file mode 100644 index 00000000..6f6bb7ea Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_143_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_144_logo.png b/tests/common/res/drawable-xhdpi/ch_144_logo.png new file mode 100644 index 00000000..afa678cc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_144_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_145_logo.png b/tests/common/res/drawable-xhdpi/ch_145_logo.png new file mode 100644 index 00000000..0e989ba5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_145_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_146_logo.png b/tests/common/res/drawable-xhdpi/ch_146_logo.png new file mode 100644 index 00000000..4ee0082c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_146_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_147_logo.png b/tests/common/res/drawable-xhdpi/ch_147_logo.png new file mode 100644 index 00000000..ddcc91dd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_147_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_148_logo.png b/tests/common/res/drawable-xhdpi/ch_148_logo.png new file mode 100644 index 00000000..c7f0c42a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_148_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_149_logo.png b/tests/common/res/drawable-xhdpi/ch_149_logo.png new file mode 100644 index 00000000..f2d38ace Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_149_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_14_logo.png b/tests/common/res/drawable-xhdpi/ch_14_logo.png new file mode 100644 index 00000000..f66db228 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_14_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_150_logo.png b/tests/common/res/drawable-xhdpi/ch_150_logo.png new file mode 100644 index 00000000..6efad527 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_150_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_151_logo.png b/tests/common/res/drawable-xhdpi/ch_151_logo.png new file mode 100644 index 00000000..b37e11ea Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_151_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_152_logo.png b/tests/common/res/drawable-xhdpi/ch_152_logo.png new file mode 100644 index 00000000..81f872a5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_152_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_153_logo.png b/tests/common/res/drawable-xhdpi/ch_153_logo.png new file mode 100644 index 00000000..e564739d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_153_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_154_logo.png b/tests/common/res/drawable-xhdpi/ch_154_logo.png new file mode 100644 index 00000000..331498e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_154_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_155_logo.png b/tests/common/res/drawable-xhdpi/ch_155_logo.png new file mode 100644 index 00000000..da8c34d4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_155_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_156_logo.png b/tests/common/res/drawable-xhdpi/ch_156_logo.png new file mode 100644 index 00000000..5ca6d550 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_156_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_157_logo.png b/tests/common/res/drawable-xhdpi/ch_157_logo.png new file mode 100644 index 00000000..460ece79 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_157_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_158_logo.png b/tests/common/res/drawable-xhdpi/ch_158_logo.png new file mode 100644 index 00000000..8d11e42e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_158_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_159_logo.png b/tests/common/res/drawable-xhdpi/ch_159_logo.png new file mode 100644 index 00000000..a10cf881 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_159_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_15_logo.png b/tests/common/res/drawable-xhdpi/ch_15_logo.png new file mode 100644 index 00000000..ae4fc936 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_15_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_160_logo.png b/tests/common/res/drawable-xhdpi/ch_160_logo.png new file mode 100644 index 00000000..c219ea72 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_160_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_161_logo.png b/tests/common/res/drawable-xhdpi/ch_161_logo.png new file mode 100644 index 00000000..2b13ad83 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_161_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_162_logo.png b/tests/common/res/drawable-xhdpi/ch_162_logo.png new file mode 100644 index 00000000..11bfadca Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_162_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_163_logo.png b/tests/common/res/drawable-xhdpi/ch_163_logo.png new file mode 100644 index 00000000..9c41b03a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_163_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_164_logo.png b/tests/common/res/drawable-xhdpi/ch_164_logo.png new file mode 100644 index 00000000..ec4a101e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_164_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_165_logo.png b/tests/common/res/drawable-xhdpi/ch_165_logo.png new file mode 100644 index 00000000..1aceac38 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_165_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_166_logo.png b/tests/common/res/drawable-xhdpi/ch_166_logo.png new file mode 100644 index 00000000..f731014f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_166_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_167_logo.png b/tests/common/res/drawable-xhdpi/ch_167_logo.png new file mode 100644 index 00000000..08c82fbf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_167_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_168_logo.png b/tests/common/res/drawable-xhdpi/ch_168_logo.png new file mode 100644 index 00000000..0c5707bc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_168_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_169_logo.png b/tests/common/res/drawable-xhdpi/ch_169_logo.png new file mode 100644 index 00000000..a9710cec Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_169_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_16_logo.png b/tests/common/res/drawable-xhdpi/ch_16_logo.png new file mode 100644 index 00000000..76aee2de Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_16_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_170_logo.png b/tests/common/res/drawable-xhdpi/ch_170_logo.png new file mode 100644 index 00000000..1bf43fa7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_170_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_171_logo.png b/tests/common/res/drawable-xhdpi/ch_171_logo.png new file mode 100644 index 00000000..8c6d6fde Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_171_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_172_logo.png b/tests/common/res/drawable-xhdpi/ch_172_logo.png new file mode 100644 index 00000000..13d73ec7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_172_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_173_logo.png b/tests/common/res/drawable-xhdpi/ch_173_logo.png new file mode 100644 index 00000000..2423b0ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_173_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_174_logo.png b/tests/common/res/drawable-xhdpi/ch_174_logo.png new file mode 100644 index 00000000..2f752dce Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_174_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_175_logo.png b/tests/common/res/drawable-xhdpi/ch_175_logo.png new file mode 100644 index 00000000..ffe3b45e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_175_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_176_logo.png b/tests/common/res/drawable-xhdpi/ch_176_logo.png new file mode 100644 index 00000000..d35592de Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_176_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_177_logo.png b/tests/common/res/drawable-xhdpi/ch_177_logo.png new file mode 100644 index 00000000..c50df44f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_177_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_178_logo.png b/tests/common/res/drawable-xhdpi/ch_178_logo.png new file mode 100644 index 00000000..22539784 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_178_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_179_logo.png b/tests/common/res/drawable-xhdpi/ch_179_logo.png new file mode 100644 index 00000000..a2c1946b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_179_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_17_logo.png b/tests/common/res/drawable-xhdpi/ch_17_logo.png new file mode 100644 index 00000000..0189498f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_17_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_180_logo.png b/tests/common/res/drawable-xhdpi/ch_180_logo.png new file mode 100644 index 00000000..9c72f2ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_180_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_181_logo.png b/tests/common/res/drawable-xhdpi/ch_181_logo.png new file mode 100644 index 00000000..23610936 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_181_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_182_logo.png b/tests/common/res/drawable-xhdpi/ch_182_logo.png new file mode 100644 index 00000000..c36bc811 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_182_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_183_logo.png b/tests/common/res/drawable-xhdpi/ch_183_logo.png new file mode 100644 index 00000000..e0e75a41 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_183_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_184_logo.png b/tests/common/res/drawable-xhdpi/ch_184_logo.png new file mode 100644 index 00000000..334598f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_184_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_185_logo.png b/tests/common/res/drawable-xhdpi/ch_185_logo.png new file mode 100644 index 00000000..6891720d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_185_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_186_logo.png b/tests/common/res/drawable-xhdpi/ch_186_logo.png new file mode 100644 index 00000000..58fc146a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_186_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_187_logo.png b/tests/common/res/drawable-xhdpi/ch_187_logo.png new file mode 100644 index 00000000..6d4f46fc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_187_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_188_logo.png b/tests/common/res/drawable-xhdpi/ch_188_logo.png new file mode 100644 index 00000000..96fc401c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_188_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_189_logo.png b/tests/common/res/drawable-xhdpi/ch_189_logo.png new file mode 100644 index 00000000..93dd4050 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_189_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_18_logo.png b/tests/common/res/drawable-xhdpi/ch_18_logo.png new file mode 100644 index 00000000..2025821b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_18_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_190_logo.png b/tests/common/res/drawable-xhdpi/ch_190_logo.png new file mode 100644 index 00000000..ea257681 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_190_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_191_logo.png b/tests/common/res/drawable-xhdpi/ch_191_logo.png new file mode 100644 index 00000000..2ac4c189 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_191_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_192_logo.png b/tests/common/res/drawable-xhdpi/ch_192_logo.png new file mode 100644 index 00000000..14728cd3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_192_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_193_logo.png b/tests/common/res/drawable-xhdpi/ch_193_logo.png new file mode 100644 index 00000000..dbbf2a1f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_193_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_194_logo.png b/tests/common/res/drawable-xhdpi/ch_194_logo.png new file mode 100644 index 00000000..aabcefda Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_194_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_195_logo.png b/tests/common/res/drawable-xhdpi/ch_195_logo.png new file mode 100644 index 00000000..e0158d09 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_195_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_196_logo.png b/tests/common/res/drawable-xhdpi/ch_196_logo.png new file mode 100644 index 00000000..a1087785 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_196_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_197_logo.png b/tests/common/res/drawable-xhdpi/ch_197_logo.png new file mode 100644 index 00000000..5644e83c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_197_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_198_logo.png b/tests/common/res/drawable-xhdpi/ch_198_logo.png new file mode 100644 index 00000000..c06acf55 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_198_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_199_logo.png b/tests/common/res/drawable-xhdpi/ch_199_logo.png new file mode 100644 index 00000000..6d5ec3ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_199_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_19_logo.png b/tests/common/res/drawable-xhdpi/ch_19_logo.png new file mode 100644 index 00000000..5e729625 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_19_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_1_logo.png b/tests/common/res/drawable-xhdpi/ch_1_logo.png new file mode 100644 index 00000000..0a39d154 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_1_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_200_logo.png b/tests/common/res/drawable-xhdpi/ch_200_logo.png new file mode 100644 index 00000000..7999b2f6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_200_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_201_logo.png b/tests/common/res/drawable-xhdpi/ch_201_logo.png new file mode 100644 index 00000000..68c868e9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_201_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_202_logo.png b/tests/common/res/drawable-xhdpi/ch_202_logo.png new file mode 100644 index 00000000..abd838fa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_202_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_203_logo.png b/tests/common/res/drawable-xhdpi/ch_203_logo.png new file mode 100644 index 00000000..f91c34cc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_203_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_204_logo.png b/tests/common/res/drawable-xhdpi/ch_204_logo.png new file mode 100644 index 00000000..8e8582c6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_204_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_205_logo.png b/tests/common/res/drawable-xhdpi/ch_205_logo.png new file mode 100644 index 00000000..4e3f4d94 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_205_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_206_logo.png b/tests/common/res/drawable-xhdpi/ch_206_logo.png new file mode 100644 index 00000000..584bb98c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_206_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_207_logo.png b/tests/common/res/drawable-xhdpi/ch_207_logo.png new file mode 100644 index 00000000..e6b2748e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_207_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_208_logo.png b/tests/common/res/drawable-xhdpi/ch_208_logo.png new file mode 100644 index 00000000..5a18dcad Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_208_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_209_logo.png b/tests/common/res/drawable-xhdpi/ch_209_logo.png new file mode 100644 index 00000000..c4de3050 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_209_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_20_logo.png b/tests/common/res/drawable-xhdpi/ch_20_logo.png new file mode 100644 index 00000000..6b4d6104 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_20_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_210_logo.png b/tests/common/res/drawable-xhdpi/ch_210_logo.png new file mode 100644 index 00000000..e4eed085 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_210_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_211_logo.png b/tests/common/res/drawable-xhdpi/ch_211_logo.png new file mode 100644 index 00000000..d5881047 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_211_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_212_logo.png b/tests/common/res/drawable-xhdpi/ch_212_logo.png new file mode 100644 index 00000000..c849f6f2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_212_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_213_logo.png b/tests/common/res/drawable-xhdpi/ch_213_logo.png new file mode 100644 index 00000000..92def1c5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_213_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_214_logo.png b/tests/common/res/drawable-xhdpi/ch_214_logo.png new file mode 100644 index 00000000..51f749fd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_214_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_215_logo.png b/tests/common/res/drawable-xhdpi/ch_215_logo.png new file mode 100644 index 00000000..5425aaad Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_215_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_216_logo.png b/tests/common/res/drawable-xhdpi/ch_216_logo.png new file mode 100644 index 00000000..53fed3c2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_216_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_217_logo.png b/tests/common/res/drawable-xhdpi/ch_217_logo.png new file mode 100644 index 00000000..d253f9cd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_217_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_218_logo.png b/tests/common/res/drawable-xhdpi/ch_218_logo.png new file mode 100644 index 00000000..3c7b0698 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_218_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_219_logo.png b/tests/common/res/drawable-xhdpi/ch_219_logo.png new file mode 100644 index 00000000..7b96dd6e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_219_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_21_logo.png b/tests/common/res/drawable-xhdpi/ch_21_logo.png new file mode 100644 index 00000000..f2848346 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_21_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_220_logo.png b/tests/common/res/drawable-xhdpi/ch_220_logo.png new file mode 100644 index 00000000..f9d20884 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_220_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_221_logo.png b/tests/common/res/drawable-xhdpi/ch_221_logo.png new file mode 100644 index 00000000..9213142d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_221_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_222_logo.png b/tests/common/res/drawable-xhdpi/ch_222_logo.png new file mode 100644 index 00000000..66cb9a8b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_222_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_223_logo.png b/tests/common/res/drawable-xhdpi/ch_223_logo.png new file mode 100644 index 00000000..11a44724 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_223_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_224_logo.png b/tests/common/res/drawable-xhdpi/ch_224_logo.png new file mode 100644 index 00000000..cdba6bf5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_224_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_225_logo.png b/tests/common/res/drawable-xhdpi/ch_225_logo.png new file mode 100644 index 00000000..e9416bfd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_225_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_226_logo.png b/tests/common/res/drawable-xhdpi/ch_226_logo.png new file mode 100644 index 00000000..88792532 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_226_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_227_logo.png b/tests/common/res/drawable-xhdpi/ch_227_logo.png new file mode 100644 index 00000000..7ae8205b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_227_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_228_logo.png b/tests/common/res/drawable-xhdpi/ch_228_logo.png new file mode 100644 index 00000000..718d93e2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_228_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_229_logo.png b/tests/common/res/drawable-xhdpi/ch_229_logo.png new file mode 100644 index 00000000..eb4f9129 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_229_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_22_logo.png b/tests/common/res/drawable-xhdpi/ch_22_logo.png new file mode 100644 index 00000000..9aa987d1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_22_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_230_logo.png b/tests/common/res/drawable-xhdpi/ch_230_logo.png new file mode 100644 index 00000000..a1bb57d3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_230_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_231_logo.png b/tests/common/res/drawable-xhdpi/ch_231_logo.png new file mode 100644 index 00000000..e748e868 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_231_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_232_logo.png b/tests/common/res/drawable-xhdpi/ch_232_logo.png new file mode 100644 index 00000000..5d4f1d69 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_232_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_233_logo.png b/tests/common/res/drawable-xhdpi/ch_233_logo.png new file mode 100644 index 00000000..7ff780ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_233_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_234_logo.png b/tests/common/res/drawable-xhdpi/ch_234_logo.png new file mode 100644 index 00000000..a4e10dfb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_234_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_235_logo.png b/tests/common/res/drawable-xhdpi/ch_235_logo.png new file mode 100644 index 00000000..c318ac0b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_235_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_236_logo.png b/tests/common/res/drawable-xhdpi/ch_236_logo.png new file mode 100644 index 00000000..2ab86fd6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_236_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_237_logo.png b/tests/common/res/drawable-xhdpi/ch_237_logo.png new file mode 100644 index 00000000..04fa9d61 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_237_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_238_logo.png b/tests/common/res/drawable-xhdpi/ch_238_logo.png new file mode 100644 index 00000000..6649b9db Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_238_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_239_logo.png b/tests/common/res/drawable-xhdpi/ch_239_logo.png new file mode 100644 index 00000000..6eaf887d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_239_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_23_logo.png b/tests/common/res/drawable-xhdpi/ch_23_logo.png new file mode 100644 index 00000000..c3dcf172 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_23_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_240_logo.png b/tests/common/res/drawable-xhdpi/ch_240_logo.png new file mode 100644 index 00000000..50d0d0df Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_240_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_241_logo.png b/tests/common/res/drawable-xhdpi/ch_241_logo.png new file mode 100644 index 00000000..779d53b5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_241_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_242_logo.png b/tests/common/res/drawable-xhdpi/ch_242_logo.png new file mode 100644 index 00000000..717aaae5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_242_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_243_logo.png b/tests/common/res/drawable-xhdpi/ch_243_logo.png new file mode 100644 index 00000000..fd04b2a2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_243_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_244_logo.png b/tests/common/res/drawable-xhdpi/ch_244_logo.png new file mode 100644 index 00000000..d8b1b710 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_244_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_245_logo.png b/tests/common/res/drawable-xhdpi/ch_245_logo.png new file mode 100644 index 00000000..3a08f595 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_245_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_246_logo.png b/tests/common/res/drawable-xhdpi/ch_246_logo.png new file mode 100644 index 00000000..404bd8f0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_246_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_247_logo.png b/tests/common/res/drawable-xhdpi/ch_247_logo.png new file mode 100644 index 00000000..46ee0163 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_247_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_248_logo.png b/tests/common/res/drawable-xhdpi/ch_248_logo.png new file mode 100644 index 00000000..ebfeb6d8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_248_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_249_logo.png b/tests/common/res/drawable-xhdpi/ch_249_logo.png new file mode 100644 index 00000000..f49dc8c4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_249_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_24_logo.png b/tests/common/res/drawable-xhdpi/ch_24_logo.png new file mode 100644 index 00000000..8fda8dcc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_24_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_250_logo.png b/tests/common/res/drawable-xhdpi/ch_250_logo.png new file mode 100644 index 00000000..3c464624 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_250_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_251_logo.png b/tests/common/res/drawable-xhdpi/ch_251_logo.png new file mode 100644 index 00000000..58f3f7d2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_251_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_252_logo.png b/tests/common/res/drawable-xhdpi/ch_252_logo.png new file mode 100644 index 00000000..f0284549 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_252_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_253_logo.png b/tests/common/res/drawable-xhdpi/ch_253_logo.png new file mode 100644 index 00000000..47a8f91d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_253_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_254_logo.png b/tests/common/res/drawable-xhdpi/ch_254_logo.png new file mode 100644 index 00000000..e2505fc0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_254_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_255_logo.png b/tests/common/res/drawable-xhdpi/ch_255_logo.png new file mode 100644 index 00000000..55e7116b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_255_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_256_logo.png b/tests/common/res/drawable-xhdpi/ch_256_logo.png new file mode 100644 index 00000000..d31964ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_256_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_257_logo.png b/tests/common/res/drawable-xhdpi/ch_257_logo.png new file mode 100644 index 00000000..cf850f90 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_257_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_258_logo.png b/tests/common/res/drawable-xhdpi/ch_258_logo.png new file mode 100644 index 00000000..148d0ee4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_258_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_259_logo.png b/tests/common/res/drawable-xhdpi/ch_259_logo.png new file mode 100644 index 00000000..aa84697d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_259_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_25_logo.png b/tests/common/res/drawable-xhdpi/ch_25_logo.png new file mode 100644 index 00000000..401e785e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_25_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_260_logo.png b/tests/common/res/drawable-xhdpi/ch_260_logo.png new file mode 100644 index 00000000..dc4f67f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_260_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_261_logo.png b/tests/common/res/drawable-xhdpi/ch_261_logo.png new file mode 100644 index 00000000..dfbecb37 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_261_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_262_logo.png b/tests/common/res/drawable-xhdpi/ch_262_logo.png new file mode 100644 index 00000000..2fa3185b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_262_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_263_logo.png b/tests/common/res/drawable-xhdpi/ch_263_logo.png new file mode 100644 index 00000000..d1e84b38 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_263_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_264_logo.png b/tests/common/res/drawable-xhdpi/ch_264_logo.png new file mode 100644 index 00000000..3f52decb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_264_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_265_logo.png b/tests/common/res/drawable-xhdpi/ch_265_logo.png new file mode 100644 index 00000000..f60c362a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_265_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_266_logo.png b/tests/common/res/drawable-xhdpi/ch_266_logo.png new file mode 100644 index 00000000..94dc4463 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_266_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_267_logo.png b/tests/common/res/drawable-xhdpi/ch_267_logo.png new file mode 100644 index 00000000..48ac79ed Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_267_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_268_logo.png b/tests/common/res/drawable-xhdpi/ch_268_logo.png new file mode 100644 index 00000000..b6f1ad64 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_268_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_269_logo.png b/tests/common/res/drawable-xhdpi/ch_269_logo.png new file mode 100644 index 00000000..0d98d378 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_269_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_26_logo.png b/tests/common/res/drawable-xhdpi/ch_26_logo.png new file mode 100644 index 00000000..a8835e4a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_26_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_270_logo.png b/tests/common/res/drawable-xhdpi/ch_270_logo.png new file mode 100644 index 00000000..27a0aead Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_270_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_271_logo.png b/tests/common/res/drawable-xhdpi/ch_271_logo.png new file mode 100644 index 00000000..7fa6feb3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_271_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_272_logo.png b/tests/common/res/drawable-xhdpi/ch_272_logo.png new file mode 100644 index 00000000..af7e34e6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_272_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_273_logo.png b/tests/common/res/drawable-xhdpi/ch_273_logo.png new file mode 100644 index 00000000..a8a041af Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_273_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_274_logo.png b/tests/common/res/drawable-xhdpi/ch_274_logo.png new file mode 100644 index 00000000..0f498f23 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_274_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_275_logo.png b/tests/common/res/drawable-xhdpi/ch_275_logo.png new file mode 100644 index 00000000..d598fb35 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_275_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_276_logo.png b/tests/common/res/drawable-xhdpi/ch_276_logo.png new file mode 100644 index 00000000..7cbac69a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_276_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_277_logo.png b/tests/common/res/drawable-xhdpi/ch_277_logo.png new file mode 100644 index 00000000..70a7f3ea Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_277_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_278_logo.png b/tests/common/res/drawable-xhdpi/ch_278_logo.png new file mode 100644 index 00000000..fcb332a5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_278_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_279_logo.png b/tests/common/res/drawable-xhdpi/ch_279_logo.png new file mode 100644 index 00000000..a95b4a96 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_279_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_27_logo.png b/tests/common/res/drawable-xhdpi/ch_27_logo.png new file mode 100644 index 00000000..138b7dc8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_27_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_280_logo.png b/tests/common/res/drawable-xhdpi/ch_280_logo.png new file mode 100644 index 00000000..2f862b39 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_280_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_281_logo.png b/tests/common/res/drawable-xhdpi/ch_281_logo.png new file mode 100644 index 00000000..6d888d16 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_281_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_282_logo.png b/tests/common/res/drawable-xhdpi/ch_282_logo.png new file mode 100644 index 00000000..81db1656 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_282_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_283_logo.png b/tests/common/res/drawable-xhdpi/ch_283_logo.png new file mode 100644 index 00000000..4933bc3c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_283_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_284_logo.png b/tests/common/res/drawable-xhdpi/ch_284_logo.png new file mode 100644 index 00000000..b4880b8b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_284_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_285_logo.png b/tests/common/res/drawable-xhdpi/ch_285_logo.png new file mode 100644 index 00000000..eea6175e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_285_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_286_logo.png b/tests/common/res/drawable-xhdpi/ch_286_logo.png new file mode 100644 index 00000000..d2b5b5bd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_286_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_287_logo.png b/tests/common/res/drawable-xhdpi/ch_287_logo.png new file mode 100644 index 00000000..f374d9d0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_287_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_288_logo.png b/tests/common/res/drawable-xhdpi/ch_288_logo.png new file mode 100644 index 00000000..16072cfa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_288_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_289_logo.png b/tests/common/res/drawable-xhdpi/ch_289_logo.png new file mode 100644 index 00000000..b76c2f48 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_289_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_28_logo.png b/tests/common/res/drawable-xhdpi/ch_28_logo.png new file mode 100644 index 00000000..284301b0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_28_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_290_logo.png b/tests/common/res/drawable-xhdpi/ch_290_logo.png new file mode 100644 index 00000000..2778664f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_290_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_291_logo.png b/tests/common/res/drawable-xhdpi/ch_291_logo.png new file mode 100644 index 00000000..52da29f6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_291_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_292_logo.png b/tests/common/res/drawable-xhdpi/ch_292_logo.png new file mode 100644 index 00000000..a1d8e6f8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_292_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_293_logo.png b/tests/common/res/drawable-xhdpi/ch_293_logo.png new file mode 100644 index 00000000..74020c30 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_293_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_294_logo.png b/tests/common/res/drawable-xhdpi/ch_294_logo.png new file mode 100644 index 00000000..49d72b5d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_294_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_295_logo.png b/tests/common/res/drawable-xhdpi/ch_295_logo.png new file mode 100644 index 00000000..cae47aeb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_295_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_296_logo.png b/tests/common/res/drawable-xhdpi/ch_296_logo.png new file mode 100644 index 00000000..4b38d480 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_296_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_297_logo.png b/tests/common/res/drawable-xhdpi/ch_297_logo.png new file mode 100644 index 00000000..3edd15d0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_297_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_298_logo.png b/tests/common/res/drawable-xhdpi/ch_298_logo.png new file mode 100644 index 00000000..cb472f31 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_298_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_299_logo.png b/tests/common/res/drawable-xhdpi/ch_299_logo.png new file mode 100644 index 00000000..b672881e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_299_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_29_logo.png b/tests/common/res/drawable-xhdpi/ch_29_logo.png new file mode 100644 index 00000000..9f1c523f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_29_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_2_logo.png b/tests/common/res/drawable-xhdpi/ch_2_logo.png new file mode 100644 index 00000000..d6887b05 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_2_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_300_logo.png b/tests/common/res/drawable-xhdpi/ch_300_logo.png new file mode 100644 index 00000000..9b842d77 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_300_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_301_logo.png b/tests/common/res/drawable-xhdpi/ch_301_logo.png new file mode 100644 index 00000000..4c7fda85 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_301_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_302_logo.png b/tests/common/res/drawable-xhdpi/ch_302_logo.png new file mode 100644 index 00000000..e39f5acb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_302_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_303_logo.png b/tests/common/res/drawable-xhdpi/ch_303_logo.png new file mode 100644 index 00000000..78f057f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_303_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_304_logo.png b/tests/common/res/drawable-xhdpi/ch_304_logo.png new file mode 100644 index 00000000..b8a68630 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_304_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_305_logo.png b/tests/common/res/drawable-xhdpi/ch_305_logo.png new file mode 100644 index 00000000..7f698610 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_305_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_306_logo.png b/tests/common/res/drawable-xhdpi/ch_306_logo.png new file mode 100644 index 00000000..0458b383 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_306_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_307_logo.png b/tests/common/res/drawable-xhdpi/ch_307_logo.png new file mode 100644 index 00000000..f2a35cd5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_307_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_308_logo.png b/tests/common/res/drawable-xhdpi/ch_308_logo.png new file mode 100644 index 00000000..49d2b4b3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_308_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_309_logo.png b/tests/common/res/drawable-xhdpi/ch_309_logo.png new file mode 100644 index 00000000..81da237b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_309_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_30_logo.png b/tests/common/res/drawable-xhdpi/ch_30_logo.png new file mode 100644 index 00000000..72db9c52 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_30_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_310_logo.png b/tests/common/res/drawable-xhdpi/ch_310_logo.png new file mode 100644 index 00000000..901a9113 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_310_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_311_logo.png b/tests/common/res/drawable-xhdpi/ch_311_logo.png new file mode 100644 index 00000000..0aa3b284 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_311_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_312_logo.png b/tests/common/res/drawable-xhdpi/ch_312_logo.png new file mode 100644 index 00000000..0cfead7e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_312_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_313_logo.png b/tests/common/res/drawable-xhdpi/ch_313_logo.png new file mode 100644 index 00000000..b552c871 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_313_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_314_logo.png b/tests/common/res/drawable-xhdpi/ch_314_logo.png new file mode 100644 index 00000000..354ab843 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_314_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_315_logo.png b/tests/common/res/drawable-xhdpi/ch_315_logo.png new file mode 100644 index 00000000..2db60cca Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_315_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_316_logo.png b/tests/common/res/drawable-xhdpi/ch_316_logo.png new file mode 100644 index 00000000..da4d32a3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_316_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_317_logo.png b/tests/common/res/drawable-xhdpi/ch_317_logo.png new file mode 100644 index 00000000..d344ef36 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_317_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_318_logo.png b/tests/common/res/drawable-xhdpi/ch_318_logo.png new file mode 100644 index 00000000..9150c2b1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_318_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_319_logo.png b/tests/common/res/drawable-xhdpi/ch_319_logo.png new file mode 100644 index 00000000..4b1b7c9d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_319_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_31_logo.png b/tests/common/res/drawable-xhdpi/ch_31_logo.png new file mode 100644 index 00000000..5386601d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_31_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_320_logo.png b/tests/common/res/drawable-xhdpi/ch_320_logo.png new file mode 100644 index 00000000..4efe21d4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_320_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_321_logo.png b/tests/common/res/drawable-xhdpi/ch_321_logo.png new file mode 100644 index 00000000..d523277d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_321_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_322_logo.png b/tests/common/res/drawable-xhdpi/ch_322_logo.png new file mode 100644 index 00000000..cf2500dc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_322_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_323_logo.png b/tests/common/res/drawable-xhdpi/ch_323_logo.png new file mode 100644 index 00000000..e838c0c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_323_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_324_logo.png b/tests/common/res/drawable-xhdpi/ch_324_logo.png new file mode 100644 index 00000000..cc0cf6d2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_324_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_325_logo.png b/tests/common/res/drawable-xhdpi/ch_325_logo.png new file mode 100644 index 00000000..adfda884 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_325_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_326_logo.png b/tests/common/res/drawable-xhdpi/ch_326_logo.png new file mode 100644 index 00000000..434d2cd6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_326_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_327_logo.png b/tests/common/res/drawable-xhdpi/ch_327_logo.png new file mode 100644 index 00000000..0a7f0b95 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_327_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_328_logo.png b/tests/common/res/drawable-xhdpi/ch_328_logo.png new file mode 100644 index 00000000..77129370 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_328_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_329_logo.png b/tests/common/res/drawable-xhdpi/ch_329_logo.png new file mode 100644 index 00000000..9ff0a89f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_329_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_32_logo.png b/tests/common/res/drawable-xhdpi/ch_32_logo.png new file mode 100644 index 00000000..39351ac1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_32_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_330_logo.png b/tests/common/res/drawable-xhdpi/ch_330_logo.png new file mode 100644 index 00000000..54adc2e7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_330_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_331_logo.png b/tests/common/res/drawable-xhdpi/ch_331_logo.png new file mode 100644 index 00000000..6c29286f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_331_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_332_logo.png b/tests/common/res/drawable-xhdpi/ch_332_logo.png new file mode 100644 index 00000000..ccab9144 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_332_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_333_logo.png b/tests/common/res/drawable-xhdpi/ch_333_logo.png new file mode 100644 index 00000000..a35ce116 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_333_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_334_logo.png b/tests/common/res/drawable-xhdpi/ch_334_logo.png new file mode 100644 index 00000000..a191d804 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_334_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_335_logo.png b/tests/common/res/drawable-xhdpi/ch_335_logo.png new file mode 100644 index 00000000..a5680b24 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_335_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_336_logo.png b/tests/common/res/drawable-xhdpi/ch_336_logo.png new file mode 100644 index 00000000..42292bf8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_336_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_337_logo.png b/tests/common/res/drawable-xhdpi/ch_337_logo.png new file mode 100644 index 00000000..d231fca3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_337_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_338_logo.png b/tests/common/res/drawable-xhdpi/ch_338_logo.png new file mode 100644 index 00000000..000988ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_338_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_339_logo.png b/tests/common/res/drawable-xhdpi/ch_339_logo.png new file mode 100644 index 00000000..3150c92c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_339_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_33_logo.png b/tests/common/res/drawable-xhdpi/ch_33_logo.png new file mode 100644 index 00000000..4931e205 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_33_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_340_logo.png b/tests/common/res/drawable-xhdpi/ch_340_logo.png new file mode 100644 index 00000000..d35d772e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_340_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_341_logo.png b/tests/common/res/drawable-xhdpi/ch_341_logo.png new file mode 100644 index 00000000..3ad19a0b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_341_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_342_logo.png b/tests/common/res/drawable-xhdpi/ch_342_logo.png new file mode 100644 index 00000000..8d6cad2c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_342_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_343_logo.png b/tests/common/res/drawable-xhdpi/ch_343_logo.png new file mode 100644 index 00000000..6e16dc96 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_343_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_344_logo.png b/tests/common/res/drawable-xhdpi/ch_344_logo.png new file mode 100644 index 00000000..e66c5a19 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_344_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_345_logo.png b/tests/common/res/drawable-xhdpi/ch_345_logo.png new file mode 100644 index 00000000..ed451fde Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_345_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_346_logo.png b/tests/common/res/drawable-xhdpi/ch_346_logo.png new file mode 100644 index 00000000..31059671 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_346_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_347_logo.png b/tests/common/res/drawable-xhdpi/ch_347_logo.png new file mode 100644 index 00000000..65ee51eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_347_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_348_logo.png b/tests/common/res/drawable-xhdpi/ch_348_logo.png new file mode 100644 index 00000000..54e9cca7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_348_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_349_logo.png b/tests/common/res/drawable-xhdpi/ch_349_logo.png new file mode 100644 index 00000000..7a11ae34 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_349_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_34_logo.png b/tests/common/res/drawable-xhdpi/ch_34_logo.png new file mode 100644 index 00000000..27a217e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_34_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_350_logo.png b/tests/common/res/drawable-xhdpi/ch_350_logo.png new file mode 100644 index 00000000..5c486ab7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_350_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_351_logo.png b/tests/common/res/drawable-xhdpi/ch_351_logo.png new file mode 100644 index 00000000..17fff276 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_351_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_352_logo.png b/tests/common/res/drawable-xhdpi/ch_352_logo.png new file mode 100644 index 00000000..2d9c412c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_352_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_353_logo.png b/tests/common/res/drawable-xhdpi/ch_353_logo.png new file mode 100644 index 00000000..38c76b38 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_353_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_354_logo.png b/tests/common/res/drawable-xhdpi/ch_354_logo.png new file mode 100644 index 00000000..8ea7d467 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_354_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_355_logo.png b/tests/common/res/drawable-xhdpi/ch_355_logo.png new file mode 100644 index 00000000..0c94dcf6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_355_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_356_logo.png b/tests/common/res/drawable-xhdpi/ch_356_logo.png new file mode 100644 index 00000000..3f2b288a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_356_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_357_logo.png b/tests/common/res/drawable-xhdpi/ch_357_logo.png new file mode 100644 index 00000000..63d43c31 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_357_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_358_logo.png b/tests/common/res/drawable-xhdpi/ch_358_logo.png new file mode 100644 index 00000000..1f5771fe Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_358_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_359_logo.png b/tests/common/res/drawable-xhdpi/ch_359_logo.png new file mode 100644 index 00000000..7a4114bd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_359_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_35_logo.png b/tests/common/res/drawable-xhdpi/ch_35_logo.png new file mode 100644 index 00000000..af2cae5d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_35_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_360_logo.png b/tests/common/res/drawable-xhdpi/ch_360_logo.png new file mode 100644 index 00000000..4e49a1f9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_360_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_361_logo.png b/tests/common/res/drawable-xhdpi/ch_361_logo.png new file mode 100644 index 00000000..43b16ac6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_361_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_362_logo.png b/tests/common/res/drawable-xhdpi/ch_362_logo.png new file mode 100644 index 00000000..efb32dab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_362_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_363_logo.png b/tests/common/res/drawable-xhdpi/ch_363_logo.png new file mode 100644 index 00000000..c59eb0b3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_363_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_364_logo.png b/tests/common/res/drawable-xhdpi/ch_364_logo.png new file mode 100644 index 00000000..9d649d47 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_364_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_365_logo.png b/tests/common/res/drawable-xhdpi/ch_365_logo.png new file mode 100644 index 00000000..96cae282 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_365_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_366_logo.png b/tests/common/res/drawable-xhdpi/ch_366_logo.png new file mode 100644 index 00000000..3c3a5cf6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_366_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_367_logo.png b/tests/common/res/drawable-xhdpi/ch_367_logo.png new file mode 100644 index 00000000..7467625a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_367_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_368_logo.png b/tests/common/res/drawable-xhdpi/ch_368_logo.png new file mode 100644 index 00000000..adb62ffc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_368_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_369_logo.png b/tests/common/res/drawable-xhdpi/ch_369_logo.png new file mode 100644 index 00000000..773f6c5c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_369_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_36_logo.png b/tests/common/res/drawable-xhdpi/ch_36_logo.png new file mode 100644 index 00000000..4580833f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_36_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_370_logo.png b/tests/common/res/drawable-xhdpi/ch_370_logo.png new file mode 100644 index 00000000..d60583bc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_370_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_371_logo.png b/tests/common/res/drawable-xhdpi/ch_371_logo.png new file mode 100644 index 00000000..91958a07 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_371_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_372_logo.png b/tests/common/res/drawable-xhdpi/ch_372_logo.png new file mode 100644 index 00000000..4b2d757c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_372_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_373_logo.png b/tests/common/res/drawable-xhdpi/ch_373_logo.png new file mode 100644 index 00000000..f8aae527 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_373_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_374_logo.png b/tests/common/res/drawable-xhdpi/ch_374_logo.png new file mode 100644 index 00000000..3549da94 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_374_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_375_logo.png b/tests/common/res/drawable-xhdpi/ch_375_logo.png new file mode 100644 index 00000000..c19806e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_375_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_376_logo.png b/tests/common/res/drawable-xhdpi/ch_376_logo.png new file mode 100644 index 00000000..86144b73 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_376_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_377_logo.png b/tests/common/res/drawable-xhdpi/ch_377_logo.png new file mode 100644 index 00000000..74f693c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_377_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_378_logo.png b/tests/common/res/drawable-xhdpi/ch_378_logo.png new file mode 100644 index 00000000..40253dab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_378_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_379_logo.png b/tests/common/res/drawable-xhdpi/ch_379_logo.png new file mode 100644 index 00000000..5459058f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_379_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_37_logo.png b/tests/common/res/drawable-xhdpi/ch_37_logo.png new file mode 100644 index 00000000..7fce3b4d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_37_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_380_logo.png b/tests/common/res/drawable-xhdpi/ch_380_logo.png new file mode 100644 index 00000000..a47a40d8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_380_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_381_logo.png b/tests/common/res/drawable-xhdpi/ch_381_logo.png new file mode 100644 index 00000000..7059114c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_381_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_382_logo.png b/tests/common/res/drawable-xhdpi/ch_382_logo.png new file mode 100644 index 00000000..a61201a5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_382_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_383_logo.png b/tests/common/res/drawable-xhdpi/ch_383_logo.png new file mode 100644 index 00000000..c126d6e3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_383_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_384_logo.png b/tests/common/res/drawable-xhdpi/ch_384_logo.png new file mode 100644 index 00000000..0bd5f454 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_384_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_385_logo.png b/tests/common/res/drawable-xhdpi/ch_385_logo.png new file mode 100644 index 00000000..864ff5c6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_385_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_386_logo.png b/tests/common/res/drawable-xhdpi/ch_386_logo.png new file mode 100644 index 00000000..643db67a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_386_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_387_logo.png b/tests/common/res/drawable-xhdpi/ch_387_logo.png new file mode 100644 index 00000000..206ec14f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_387_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_388_logo.png b/tests/common/res/drawable-xhdpi/ch_388_logo.png new file mode 100644 index 00000000..37c9dbae Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_388_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_389_logo.png b/tests/common/res/drawable-xhdpi/ch_389_logo.png new file mode 100644 index 00000000..958ffb63 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_389_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_38_logo.png b/tests/common/res/drawable-xhdpi/ch_38_logo.png new file mode 100644 index 00000000..6e864bf8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_38_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_390_logo.png b/tests/common/res/drawable-xhdpi/ch_390_logo.png new file mode 100644 index 00000000..a5a6547e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_390_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_391_logo.png b/tests/common/res/drawable-xhdpi/ch_391_logo.png new file mode 100644 index 00000000..0d1c076b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_391_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_392_logo.png b/tests/common/res/drawable-xhdpi/ch_392_logo.png new file mode 100644 index 00000000..25e3a87a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_392_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_393_logo.png b/tests/common/res/drawable-xhdpi/ch_393_logo.png new file mode 100644 index 00000000..92ebefe2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_393_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_394_logo.png b/tests/common/res/drawable-xhdpi/ch_394_logo.png new file mode 100644 index 00000000..b220319a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_394_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_395_logo.png b/tests/common/res/drawable-xhdpi/ch_395_logo.png new file mode 100644 index 00000000..29df3f8f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_395_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_396_logo.png b/tests/common/res/drawable-xhdpi/ch_396_logo.png new file mode 100644 index 00000000..f1e6f983 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_396_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_397_logo.png b/tests/common/res/drawable-xhdpi/ch_397_logo.png new file mode 100644 index 00000000..7f44d63c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_397_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_398_logo.png b/tests/common/res/drawable-xhdpi/ch_398_logo.png new file mode 100644 index 00000000..03b14271 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_398_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_399_logo.png b/tests/common/res/drawable-xhdpi/ch_399_logo.png new file mode 100644 index 00000000..a15aaa4e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_399_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_39_logo.png b/tests/common/res/drawable-xhdpi/ch_39_logo.png new file mode 100644 index 00000000..633a1172 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_39_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_3_logo.png b/tests/common/res/drawable-xhdpi/ch_3_logo.png new file mode 100644 index 00000000..9c921385 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_3_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_400_logo.png b/tests/common/res/drawable-xhdpi/ch_400_logo.png new file mode 100644 index 00000000..15442662 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_400_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_401_logo.png b/tests/common/res/drawable-xhdpi/ch_401_logo.png new file mode 100644 index 00000000..c4d06916 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_401_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_402_logo.png b/tests/common/res/drawable-xhdpi/ch_402_logo.png new file mode 100644 index 00000000..b8719f32 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_402_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_403_logo.png b/tests/common/res/drawable-xhdpi/ch_403_logo.png new file mode 100644 index 00000000..054b98b7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_403_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_404_logo.png b/tests/common/res/drawable-xhdpi/ch_404_logo.png new file mode 100644 index 00000000..64566990 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_404_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_405_logo.png b/tests/common/res/drawable-xhdpi/ch_405_logo.png new file mode 100644 index 00000000..2ceab3fb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_405_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_406_logo.png b/tests/common/res/drawable-xhdpi/ch_406_logo.png new file mode 100644 index 00000000..f7557109 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_406_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_407_logo.png b/tests/common/res/drawable-xhdpi/ch_407_logo.png new file mode 100644 index 00000000..8228e1c5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_407_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_408_logo.png b/tests/common/res/drawable-xhdpi/ch_408_logo.png new file mode 100644 index 00000000..d9c09b85 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_408_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_409_logo.png b/tests/common/res/drawable-xhdpi/ch_409_logo.png new file mode 100644 index 00000000..fd2b76e5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_409_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_40_logo.png b/tests/common/res/drawable-xhdpi/ch_40_logo.png new file mode 100644 index 00000000..63600168 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_40_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_410_logo.png b/tests/common/res/drawable-xhdpi/ch_410_logo.png new file mode 100644 index 00000000..8b05de2e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_410_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_411_logo.png b/tests/common/res/drawable-xhdpi/ch_411_logo.png new file mode 100644 index 00000000..7306991e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_411_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_412_logo.png b/tests/common/res/drawable-xhdpi/ch_412_logo.png new file mode 100644 index 00000000..55964214 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_412_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_413_logo.png b/tests/common/res/drawable-xhdpi/ch_413_logo.png new file mode 100644 index 00000000..c744901d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_413_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_414_logo.png b/tests/common/res/drawable-xhdpi/ch_414_logo.png new file mode 100644 index 00000000..304dc7da Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_414_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_415_logo.png b/tests/common/res/drawable-xhdpi/ch_415_logo.png new file mode 100644 index 00000000..1a9b1f19 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_415_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_416_logo.png b/tests/common/res/drawable-xhdpi/ch_416_logo.png new file mode 100644 index 00000000..53318edf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_416_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_417_logo.png b/tests/common/res/drawable-xhdpi/ch_417_logo.png new file mode 100644 index 00000000..763bad1b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_417_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_418_logo.png b/tests/common/res/drawable-xhdpi/ch_418_logo.png new file mode 100644 index 00000000..afa06409 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_418_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_419_logo.png b/tests/common/res/drawable-xhdpi/ch_419_logo.png new file mode 100644 index 00000000..4741b659 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_419_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_41_logo.png b/tests/common/res/drawable-xhdpi/ch_41_logo.png new file mode 100644 index 00000000..6002ae31 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_41_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_420_logo.png b/tests/common/res/drawable-xhdpi/ch_420_logo.png new file mode 100644 index 00000000..22a72aec Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_420_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_421_logo.png b/tests/common/res/drawable-xhdpi/ch_421_logo.png new file mode 100644 index 00000000..f4c301dd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_421_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_422_logo.png b/tests/common/res/drawable-xhdpi/ch_422_logo.png new file mode 100644 index 00000000..e70d59ac Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_422_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_423_logo.png b/tests/common/res/drawable-xhdpi/ch_423_logo.png new file mode 100644 index 00000000..a4c96fbd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_423_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_424_logo.png b/tests/common/res/drawable-xhdpi/ch_424_logo.png new file mode 100644 index 00000000..9dc1713e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_424_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_425_logo.png b/tests/common/res/drawable-xhdpi/ch_425_logo.png new file mode 100644 index 00000000..a79e8f5c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_425_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_426_logo.png b/tests/common/res/drawable-xhdpi/ch_426_logo.png new file mode 100644 index 00000000..34bcbef0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_426_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_427_logo.png b/tests/common/res/drawable-xhdpi/ch_427_logo.png new file mode 100644 index 00000000..6133494d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_427_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_428_logo.png b/tests/common/res/drawable-xhdpi/ch_428_logo.png new file mode 100644 index 00000000..d91391a4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_428_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_429_logo.png b/tests/common/res/drawable-xhdpi/ch_429_logo.png new file mode 100644 index 00000000..f24a64f9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_429_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_42_logo.png b/tests/common/res/drawable-xhdpi/ch_42_logo.png new file mode 100644 index 00000000..c2f641c0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_42_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_430_logo.png b/tests/common/res/drawable-xhdpi/ch_430_logo.png new file mode 100644 index 00000000..e656d6da Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_430_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_431_logo.png b/tests/common/res/drawable-xhdpi/ch_431_logo.png new file mode 100644 index 00000000..4827d71d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_431_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_432_logo.png b/tests/common/res/drawable-xhdpi/ch_432_logo.png new file mode 100644 index 00000000..bb5493e2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_432_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_433_logo.png b/tests/common/res/drawable-xhdpi/ch_433_logo.png new file mode 100644 index 00000000..f777f36a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_433_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_434_logo.png b/tests/common/res/drawable-xhdpi/ch_434_logo.png new file mode 100644 index 00000000..f342df85 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_434_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_435_logo.png b/tests/common/res/drawable-xhdpi/ch_435_logo.png new file mode 100644 index 00000000..70667e7a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_435_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_436_logo.png b/tests/common/res/drawable-xhdpi/ch_436_logo.png new file mode 100644 index 00000000..1895a238 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_436_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_437_logo.png b/tests/common/res/drawable-xhdpi/ch_437_logo.png new file mode 100644 index 00000000..9b6b335b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_437_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_438_logo.png b/tests/common/res/drawable-xhdpi/ch_438_logo.png new file mode 100644 index 00000000..5070cdb1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_438_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_439_logo.png b/tests/common/res/drawable-xhdpi/ch_439_logo.png new file mode 100644 index 00000000..adcad27b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_439_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_43_logo.png b/tests/common/res/drawable-xhdpi/ch_43_logo.png new file mode 100644 index 00000000..5ea7d814 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_43_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_440_logo.png b/tests/common/res/drawable-xhdpi/ch_440_logo.png new file mode 100644 index 00000000..0b1f76c6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_440_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_441_logo.png b/tests/common/res/drawable-xhdpi/ch_441_logo.png new file mode 100644 index 00000000..65870b6e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_441_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_442_logo.png b/tests/common/res/drawable-xhdpi/ch_442_logo.png new file mode 100644 index 00000000..9812a1cb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_442_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_443_logo.png b/tests/common/res/drawable-xhdpi/ch_443_logo.png new file mode 100644 index 00000000..d539f5c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_443_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_444_logo.png b/tests/common/res/drawable-xhdpi/ch_444_logo.png new file mode 100644 index 00000000..fbf615d9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_444_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_445_logo.png b/tests/common/res/drawable-xhdpi/ch_445_logo.png new file mode 100644 index 00000000..440085aa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_445_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_446_logo.png b/tests/common/res/drawable-xhdpi/ch_446_logo.png new file mode 100644 index 00000000..d26f1c37 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_446_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_447_logo.png b/tests/common/res/drawable-xhdpi/ch_447_logo.png new file mode 100644 index 00000000..0967878d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_447_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_448_logo.png b/tests/common/res/drawable-xhdpi/ch_448_logo.png new file mode 100644 index 00000000..b979c062 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_448_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_449_logo.png b/tests/common/res/drawable-xhdpi/ch_449_logo.png new file mode 100644 index 00000000..2232b90e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_449_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_44_logo.png b/tests/common/res/drawable-xhdpi/ch_44_logo.png new file mode 100644 index 00000000..be6cbd3b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_44_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_450_logo.png b/tests/common/res/drawable-xhdpi/ch_450_logo.png new file mode 100644 index 00000000..8e25df9a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_450_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_451_logo.png b/tests/common/res/drawable-xhdpi/ch_451_logo.png new file mode 100644 index 00000000..c744e56e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_451_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_452_logo.png b/tests/common/res/drawable-xhdpi/ch_452_logo.png new file mode 100644 index 00000000..050b0196 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_452_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_453_logo.png b/tests/common/res/drawable-xhdpi/ch_453_logo.png new file mode 100644 index 00000000..4ccdbf14 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_453_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_454_logo.png b/tests/common/res/drawable-xhdpi/ch_454_logo.png new file mode 100644 index 00000000..10aa3779 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_454_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_455_logo.png b/tests/common/res/drawable-xhdpi/ch_455_logo.png new file mode 100644 index 00000000..7b607a6c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_455_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_456_logo.png b/tests/common/res/drawable-xhdpi/ch_456_logo.png new file mode 100644 index 00000000..f2d57066 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_456_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_457_logo.png b/tests/common/res/drawable-xhdpi/ch_457_logo.png new file mode 100644 index 00000000..483e5912 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_457_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_458_logo.png b/tests/common/res/drawable-xhdpi/ch_458_logo.png new file mode 100644 index 00000000..447d5942 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_458_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_459_logo.png b/tests/common/res/drawable-xhdpi/ch_459_logo.png new file mode 100644 index 00000000..80d6c5b6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_459_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_45_logo.png b/tests/common/res/drawable-xhdpi/ch_45_logo.png new file mode 100644 index 00000000..0db467c8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_45_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_460_logo.png b/tests/common/res/drawable-xhdpi/ch_460_logo.png new file mode 100644 index 00000000..22d785cb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_460_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_461_logo.png b/tests/common/res/drawable-xhdpi/ch_461_logo.png new file mode 100644 index 00000000..658a1046 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_461_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_462_logo.png b/tests/common/res/drawable-xhdpi/ch_462_logo.png new file mode 100644 index 00000000..dd0c5d14 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_462_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_463_logo.png b/tests/common/res/drawable-xhdpi/ch_463_logo.png new file mode 100644 index 00000000..7b72b915 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_463_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_464_logo.png b/tests/common/res/drawable-xhdpi/ch_464_logo.png new file mode 100644 index 00000000..cc15444c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_464_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_465_logo.png b/tests/common/res/drawable-xhdpi/ch_465_logo.png new file mode 100644 index 00000000..a17cb719 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_465_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_466_logo.png b/tests/common/res/drawable-xhdpi/ch_466_logo.png new file mode 100644 index 00000000..604644f4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_466_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_467_logo.png b/tests/common/res/drawable-xhdpi/ch_467_logo.png new file mode 100644 index 00000000..4c74b50d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_467_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_468_logo.png b/tests/common/res/drawable-xhdpi/ch_468_logo.png new file mode 100644 index 00000000..8e329206 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_468_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_469_logo.png b/tests/common/res/drawable-xhdpi/ch_469_logo.png new file mode 100644 index 00000000..14a1d99e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_469_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_46_logo.png b/tests/common/res/drawable-xhdpi/ch_46_logo.png new file mode 100644 index 00000000..f9d83ea9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_46_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_470_logo.png b/tests/common/res/drawable-xhdpi/ch_470_logo.png new file mode 100644 index 00000000..567e879a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_470_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_471_logo.png b/tests/common/res/drawable-xhdpi/ch_471_logo.png new file mode 100644 index 00000000..4a128a0e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_471_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_472_logo.png b/tests/common/res/drawable-xhdpi/ch_472_logo.png new file mode 100644 index 00000000..f00d1cb0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_472_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_473_logo.png b/tests/common/res/drawable-xhdpi/ch_473_logo.png new file mode 100644 index 00000000..ee7bbfc9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_473_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_474_logo.png b/tests/common/res/drawable-xhdpi/ch_474_logo.png new file mode 100644 index 00000000..a1b7e0eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_474_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_475_logo.png b/tests/common/res/drawable-xhdpi/ch_475_logo.png new file mode 100644 index 00000000..14db7abe Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_475_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_476_logo.png b/tests/common/res/drawable-xhdpi/ch_476_logo.png new file mode 100644 index 00000000..89c71e80 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_476_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_477_logo.png b/tests/common/res/drawable-xhdpi/ch_477_logo.png new file mode 100644 index 00000000..60509130 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_477_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_478_logo.png b/tests/common/res/drawable-xhdpi/ch_478_logo.png new file mode 100644 index 00000000..a2c3069b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_478_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_479_logo.png b/tests/common/res/drawable-xhdpi/ch_479_logo.png new file mode 100644 index 00000000..2be6ae4f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_479_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_47_logo.png b/tests/common/res/drawable-xhdpi/ch_47_logo.png new file mode 100644 index 00000000..ed4b464a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_47_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_480_logo.png b/tests/common/res/drawable-xhdpi/ch_480_logo.png new file mode 100644 index 00000000..f2a2d6fa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_480_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_481_logo.png b/tests/common/res/drawable-xhdpi/ch_481_logo.png new file mode 100644 index 00000000..b71fcfa8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_481_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_482_logo.png b/tests/common/res/drawable-xhdpi/ch_482_logo.png new file mode 100644 index 00000000..8d317e47 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_482_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_483_logo.png b/tests/common/res/drawable-xhdpi/ch_483_logo.png new file mode 100644 index 00000000..ea417f9e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_483_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_484_logo.png b/tests/common/res/drawable-xhdpi/ch_484_logo.png new file mode 100644 index 00000000..b46584a4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_484_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_485_logo.png b/tests/common/res/drawable-xhdpi/ch_485_logo.png new file mode 100644 index 00000000..3564dcd5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_485_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_486_logo.png b/tests/common/res/drawable-xhdpi/ch_486_logo.png new file mode 100644 index 00000000..8227575f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_486_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_487_logo.png b/tests/common/res/drawable-xhdpi/ch_487_logo.png new file mode 100644 index 00000000..edb15bee Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_487_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_488_logo.png b/tests/common/res/drawable-xhdpi/ch_488_logo.png new file mode 100644 index 00000000..f358d335 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_488_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_489_logo.png b/tests/common/res/drawable-xhdpi/ch_489_logo.png new file mode 100644 index 00000000..5122c67e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_489_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_48_logo.png b/tests/common/res/drawable-xhdpi/ch_48_logo.png new file mode 100644 index 00000000..e1be7313 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_48_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_490_logo.png b/tests/common/res/drawable-xhdpi/ch_490_logo.png new file mode 100644 index 00000000..1901d995 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_490_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_491_logo.png b/tests/common/res/drawable-xhdpi/ch_491_logo.png new file mode 100644 index 00000000..04b0a021 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_491_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_492_logo.png b/tests/common/res/drawable-xhdpi/ch_492_logo.png new file mode 100644 index 00000000..b18dea9b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_492_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_493_logo.png b/tests/common/res/drawable-xhdpi/ch_493_logo.png new file mode 100644 index 00000000..19044655 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_493_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_494_logo.png b/tests/common/res/drawable-xhdpi/ch_494_logo.png new file mode 100644 index 00000000..b5b0f76c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_494_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_495_logo.png b/tests/common/res/drawable-xhdpi/ch_495_logo.png new file mode 100644 index 00000000..6124dc83 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_495_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_496_logo.png b/tests/common/res/drawable-xhdpi/ch_496_logo.png new file mode 100644 index 00000000..6b0e472b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_496_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_497_logo.png b/tests/common/res/drawable-xhdpi/ch_497_logo.png new file mode 100644 index 00000000..31fcd048 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_497_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_498_logo.png b/tests/common/res/drawable-xhdpi/ch_498_logo.png new file mode 100644 index 00000000..472c09b6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_498_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_499_logo.png b/tests/common/res/drawable-xhdpi/ch_499_logo.png new file mode 100644 index 00000000..2d653f61 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_499_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_49_logo.png b/tests/common/res/drawable-xhdpi/ch_49_logo.png new file mode 100644 index 00000000..46bc774d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_49_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_4_logo.png b/tests/common/res/drawable-xhdpi/ch_4_logo.png new file mode 100644 index 00000000..d5e6517c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_4_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_500_logo.png b/tests/common/res/drawable-xhdpi/ch_500_logo.png new file mode 100644 index 00000000..ecfa17ec Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_500_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_501_logo.png b/tests/common/res/drawable-xhdpi/ch_501_logo.png new file mode 100644 index 00000000..0c81a787 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_501_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_502_logo.png b/tests/common/res/drawable-xhdpi/ch_502_logo.png new file mode 100644 index 00000000..5dd20644 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_502_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_503_logo.png b/tests/common/res/drawable-xhdpi/ch_503_logo.png new file mode 100644 index 00000000..a48ec52d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_503_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_504_logo.png b/tests/common/res/drawable-xhdpi/ch_504_logo.png new file mode 100644 index 00000000..999a641e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_504_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_505_logo.png b/tests/common/res/drawable-xhdpi/ch_505_logo.png new file mode 100644 index 00000000..d7c600e4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_505_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_506_logo.png b/tests/common/res/drawable-xhdpi/ch_506_logo.png new file mode 100644 index 00000000..ac80e6dd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_506_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_507_logo.png b/tests/common/res/drawable-xhdpi/ch_507_logo.png new file mode 100644 index 00000000..c4d434f8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_507_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_508_logo.png b/tests/common/res/drawable-xhdpi/ch_508_logo.png new file mode 100644 index 00000000..35330059 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_508_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_509_logo.png b/tests/common/res/drawable-xhdpi/ch_509_logo.png new file mode 100644 index 00000000..2077eac5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_509_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_50_logo.png b/tests/common/res/drawable-xhdpi/ch_50_logo.png new file mode 100644 index 00000000..901373e6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_50_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_510_logo.png b/tests/common/res/drawable-xhdpi/ch_510_logo.png new file mode 100644 index 00000000..469bf77d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_510_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_511_logo.png b/tests/common/res/drawable-xhdpi/ch_511_logo.png new file mode 100644 index 00000000..80d727bb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_511_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_512_logo.png b/tests/common/res/drawable-xhdpi/ch_512_logo.png new file mode 100644 index 00000000..37ba7e2b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_512_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_513_logo.png b/tests/common/res/drawable-xhdpi/ch_513_logo.png new file mode 100644 index 00000000..ef7c4f2a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_513_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_514_logo.png b/tests/common/res/drawable-xhdpi/ch_514_logo.png new file mode 100644 index 00000000..450f5963 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_514_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_515_logo.png b/tests/common/res/drawable-xhdpi/ch_515_logo.png new file mode 100644 index 00000000..6533b2fd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_515_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_516_logo.png b/tests/common/res/drawable-xhdpi/ch_516_logo.png new file mode 100644 index 00000000..08cd2f0d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_516_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_517_logo.png b/tests/common/res/drawable-xhdpi/ch_517_logo.png new file mode 100644 index 00000000..1935d709 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_517_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_518_logo.png b/tests/common/res/drawable-xhdpi/ch_518_logo.png new file mode 100644 index 00000000..806fbbbf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_518_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_519_logo.png b/tests/common/res/drawable-xhdpi/ch_519_logo.png new file mode 100644 index 00000000..938bb559 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_519_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_51_logo.png b/tests/common/res/drawable-xhdpi/ch_51_logo.png new file mode 100644 index 00000000..07a5f4ad Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_51_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_520_logo.png b/tests/common/res/drawable-xhdpi/ch_520_logo.png new file mode 100644 index 00000000..7f9f68b2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_520_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_521_logo.png b/tests/common/res/drawable-xhdpi/ch_521_logo.png new file mode 100644 index 00000000..e8d0432c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_521_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_522_logo.png b/tests/common/res/drawable-xhdpi/ch_522_logo.png new file mode 100644 index 00000000..d7345b8c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_522_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_523_logo.png b/tests/common/res/drawable-xhdpi/ch_523_logo.png new file mode 100644 index 00000000..61573120 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_523_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_524_logo.png b/tests/common/res/drawable-xhdpi/ch_524_logo.png new file mode 100644 index 00000000..9a8b2fdd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_524_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_525_logo.png b/tests/common/res/drawable-xhdpi/ch_525_logo.png new file mode 100644 index 00000000..9ed318fa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_525_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_526_logo.png b/tests/common/res/drawable-xhdpi/ch_526_logo.png new file mode 100644 index 00000000..5000af2a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_526_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_527_logo.png b/tests/common/res/drawable-xhdpi/ch_527_logo.png new file mode 100644 index 00000000..38fbd9eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_527_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_528_logo.png b/tests/common/res/drawable-xhdpi/ch_528_logo.png new file mode 100644 index 00000000..565e8b2a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_528_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_529_logo.png b/tests/common/res/drawable-xhdpi/ch_529_logo.png new file mode 100644 index 00000000..b0924df7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_529_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_52_logo.png b/tests/common/res/drawable-xhdpi/ch_52_logo.png new file mode 100644 index 00000000..6faa9702 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_52_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_530_logo.png b/tests/common/res/drawable-xhdpi/ch_530_logo.png new file mode 100644 index 00000000..0eec0234 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_530_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_531_logo.png b/tests/common/res/drawable-xhdpi/ch_531_logo.png new file mode 100644 index 00000000..2f0243d1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_531_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_532_logo.png b/tests/common/res/drawable-xhdpi/ch_532_logo.png new file mode 100644 index 00000000..a6ece89e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_532_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_533_logo.png b/tests/common/res/drawable-xhdpi/ch_533_logo.png new file mode 100644 index 00000000..a1695158 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_533_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_534_logo.png b/tests/common/res/drawable-xhdpi/ch_534_logo.png new file mode 100644 index 00000000..00ad0b07 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_534_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_535_logo.png b/tests/common/res/drawable-xhdpi/ch_535_logo.png new file mode 100644 index 00000000..a536ff32 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_535_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_536_logo.png b/tests/common/res/drawable-xhdpi/ch_536_logo.png new file mode 100644 index 00000000..60fcdf33 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_536_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_537_logo.png b/tests/common/res/drawable-xhdpi/ch_537_logo.png new file mode 100644 index 00000000..0446d9bc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_537_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_538_logo.png b/tests/common/res/drawable-xhdpi/ch_538_logo.png new file mode 100644 index 00000000..6f98e08d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_538_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_539_logo.png b/tests/common/res/drawable-xhdpi/ch_539_logo.png new file mode 100644 index 00000000..b6f17bbb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_539_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_53_logo.png b/tests/common/res/drawable-xhdpi/ch_53_logo.png new file mode 100644 index 00000000..441e1d97 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_53_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_540_logo.png b/tests/common/res/drawable-xhdpi/ch_540_logo.png new file mode 100644 index 00000000..61950bff Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_540_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_541_logo.png b/tests/common/res/drawable-xhdpi/ch_541_logo.png new file mode 100644 index 00000000..eea9a988 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_541_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_542_logo.png b/tests/common/res/drawable-xhdpi/ch_542_logo.png new file mode 100644 index 00000000..684cf6d1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_542_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_543_logo.png b/tests/common/res/drawable-xhdpi/ch_543_logo.png new file mode 100644 index 00000000..44e4e6e0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_543_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_544_logo.png b/tests/common/res/drawable-xhdpi/ch_544_logo.png new file mode 100644 index 00000000..adf6c97a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_544_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_545_logo.png b/tests/common/res/drawable-xhdpi/ch_545_logo.png new file mode 100644 index 00000000..57f8edc6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_545_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_546_logo.png b/tests/common/res/drawable-xhdpi/ch_546_logo.png new file mode 100644 index 00000000..4ac92311 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_546_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_547_logo.png b/tests/common/res/drawable-xhdpi/ch_547_logo.png new file mode 100644 index 00000000..ca857974 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_547_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_548_logo.png b/tests/common/res/drawable-xhdpi/ch_548_logo.png new file mode 100644 index 00000000..1037011c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_548_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_549_logo.png b/tests/common/res/drawable-xhdpi/ch_549_logo.png new file mode 100644 index 00000000..fd10a2ee Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_549_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_54_logo.png b/tests/common/res/drawable-xhdpi/ch_54_logo.png new file mode 100644 index 00000000..50f22f48 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_54_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_550_logo.png b/tests/common/res/drawable-xhdpi/ch_550_logo.png new file mode 100644 index 00000000..992666b6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_550_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_551_logo.png b/tests/common/res/drawable-xhdpi/ch_551_logo.png new file mode 100644 index 00000000..41369f0b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_551_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_552_logo.png b/tests/common/res/drawable-xhdpi/ch_552_logo.png new file mode 100644 index 00000000..56be3e73 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_552_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_553_logo.png b/tests/common/res/drawable-xhdpi/ch_553_logo.png new file mode 100644 index 00000000..04812119 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_553_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_554_logo.png b/tests/common/res/drawable-xhdpi/ch_554_logo.png new file mode 100644 index 00000000..b7e943a3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_554_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_555_logo.png b/tests/common/res/drawable-xhdpi/ch_555_logo.png new file mode 100644 index 00000000..fb74741b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_555_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_556_logo.png b/tests/common/res/drawable-xhdpi/ch_556_logo.png new file mode 100644 index 00000000..8c6d4046 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_556_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_557_logo.png b/tests/common/res/drawable-xhdpi/ch_557_logo.png new file mode 100644 index 00000000..fe65c25c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_557_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_558_logo.png b/tests/common/res/drawable-xhdpi/ch_558_logo.png new file mode 100644 index 00000000..598e6424 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_558_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_559_logo.png b/tests/common/res/drawable-xhdpi/ch_559_logo.png new file mode 100644 index 00000000..68a46085 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_559_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_55_logo.png b/tests/common/res/drawable-xhdpi/ch_55_logo.png new file mode 100644 index 00000000..59f21e84 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_55_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_560_logo.png b/tests/common/res/drawable-xhdpi/ch_560_logo.png new file mode 100644 index 00000000..db6d6693 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_560_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_561_logo.png b/tests/common/res/drawable-xhdpi/ch_561_logo.png new file mode 100644 index 00000000..b53f08f0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_561_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_562_logo.png b/tests/common/res/drawable-xhdpi/ch_562_logo.png new file mode 100644 index 00000000..970f0174 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_562_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_563_logo.png b/tests/common/res/drawable-xhdpi/ch_563_logo.png new file mode 100644 index 00000000..1c426378 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_563_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_564_logo.png b/tests/common/res/drawable-xhdpi/ch_564_logo.png new file mode 100644 index 00000000..61a89f16 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_564_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_565_logo.png b/tests/common/res/drawable-xhdpi/ch_565_logo.png new file mode 100644 index 00000000..956342b2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_565_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_566_logo.png b/tests/common/res/drawable-xhdpi/ch_566_logo.png new file mode 100644 index 00000000..a935ec4a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_566_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_567_logo.png b/tests/common/res/drawable-xhdpi/ch_567_logo.png new file mode 100644 index 00000000..4d6e407e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_567_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_568_logo.png b/tests/common/res/drawable-xhdpi/ch_568_logo.png new file mode 100644 index 00000000..f4578ed0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_568_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_569_logo.png b/tests/common/res/drawable-xhdpi/ch_569_logo.png new file mode 100644 index 00000000..807c7323 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_569_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_56_logo.png b/tests/common/res/drawable-xhdpi/ch_56_logo.png new file mode 100644 index 00000000..dc296510 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_56_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_570_logo.png b/tests/common/res/drawable-xhdpi/ch_570_logo.png new file mode 100644 index 00000000..ef03c654 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_570_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_571_logo.png b/tests/common/res/drawable-xhdpi/ch_571_logo.png new file mode 100644 index 00000000..422f9aa4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_571_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_572_logo.png b/tests/common/res/drawable-xhdpi/ch_572_logo.png new file mode 100644 index 00000000..68d08376 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_572_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_573_logo.png b/tests/common/res/drawable-xhdpi/ch_573_logo.png new file mode 100644 index 00000000..1d8ca402 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_573_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_574_logo.png b/tests/common/res/drawable-xhdpi/ch_574_logo.png new file mode 100644 index 00000000..f582224a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_574_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_575_logo.png b/tests/common/res/drawable-xhdpi/ch_575_logo.png new file mode 100644 index 00000000..44fc827b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_575_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_576_logo.png b/tests/common/res/drawable-xhdpi/ch_576_logo.png new file mode 100644 index 00000000..a8c9b36d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_576_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_577_logo.png b/tests/common/res/drawable-xhdpi/ch_577_logo.png new file mode 100644 index 00000000..328c67a9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_577_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_578_logo.png b/tests/common/res/drawable-xhdpi/ch_578_logo.png new file mode 100644 index 00000000..d856dac5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_578_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_579_logo.png b/tests/common/res/drawable-xhdpi/ch_579_logo.png new file mode 100644 index 00000000..2a989c37 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_579_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_57_logo.png b/tests/common/res/drawable-xhdpi/ch_57_logo.png new file mode 100644 index 00000000..72981424 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_57_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_580_logo.png b/tests/common/res/drawable-xhdpi/ch_580_logo.png new file mode 100644 index 00000000..4c8034c8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_580_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_581_logo.png b/tests/common/res/drawable-xhdpi/ch_581_logo.png new file mode 100644 index 00000000..7423ef65 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_581_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_582_logo.png b/tests/common/res/drawable-xhdpi/ch_582_logo.png new file mode 100644 index 00000000..ffbc803d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_582_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_583_logo.png b/tests/common/res/drawable-xhdpi/ch_583_logo.png new file mode 100644 index 00000000..d6b92dce Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_583_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_584_logo.png b/tests/common/res/drawable-xhdpi/ch_584_logo.png new file mode 100644 index 00000000..cecbe6c7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_584_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_585_logo.png b/tests/common/res/drawable-xhdpi/ch_585_logo.png new file mode 100644 index 00000000..99b994f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_585_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_586_logo.png b/tests/common/res/drawable-xhdpi/ch_586_logo.png new file mode 100644 index 00000000..a54afdd4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_586_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_587_logo.png b/tests/common/res/drawable-xhdpi/ch_587_logo.png new file mode 100644 index 00000000..e09f40ff Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_587_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_588_logo.png b/tests/common/res/drawable-xhdpi/ch_588_logo.png new file mode 100644 index 00000000..e4b3c192 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_588_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_589_logo.png b/tests/common/res/drawable-xhdpi/ch_589_logo.png new file mode 100644 index 00000000..2c451a54 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_589_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_58_logo.png b/tests/common/res/drawable-xhdpi/ch_58_logo.png new file mode 100644 index 00000000..07f71a01 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_58_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_590_logo.png b/tests/common/res/drawable-xhdpi/ch_590_logo.png new file mode 100644 index 00000000..56e5e60c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_590_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_591_logo.png b/tests/common/res/drawable-xhdpi/ch_591_logo.png new file mode 100644 index 00000000..09e1a554 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_591_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_592_logo.png b/tests/common/res/drawable-xhdpi/ch_592_logo.png new file mode 100644 index 00000000..82635ce4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_592_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_593_logo.png b/tests/common/res/drawable-xhdpi/ch_593_logo.png new file mode 100644 index 00000000..21f8e54c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_593_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_594_logo.png b/tests/common/res/drawable-xhdpi/ch_594_logo.png new file mode 100644 index 00000000..8de16c08 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_594_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_595_logo.png b/tests/common/res/drawable-xhdpi/ch_595_logo.png new file mode 100644 index 00000000..7fd01896 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_595_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_596_logo.png b/tests/common/res/drawable-xhdpi/ch_596_logo.png new file mode 100644 index 00000000..35452d61 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_596_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_597_logo.png b/tests/common/res/drawable-xhdpi/ch_597_logo.png new file mode 100644 index 00000000..c812338a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_597_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_598_logo.png b/tests/common/res/drawable-xhdpi/ch_598_logo.png new file mode 100644 index 00000000..6a7a4da0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_598_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_599_logo.png b/tests/common/res/drawable-xhdpi/ch_599_logo.png new file mode 100644 index 00000000..9bb000ac Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_599_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_59_logo.png b/tests/common/res/drawable-xhdpi/ch_59_logo.png new file mode 100644 index 00000000..a7a0155f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_59_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_5_logo.png b/tests/common/res/drawable-xhdpi/ch_5_logo.png new file mode 100644 index 00000000..2de0c9cc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_5_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_600_logo.png b/tests/common/res/drawable-xhdpi/ch_600_logo.png new file mode 100644 index 00000000..e3b247e9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_600_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_601_logo.png b/tests/common/res/drawable-xhdpi/ch_601_logo.png new file mode 100644 index 00000000..ff572b8c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_601_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_602_logo.png b/tests/common/res/drawable-xhdpi/ch_602_logo.png new file mode 100644 index 00000000..56d0505a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_602_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_603_logo.png b/tests/common/res/drawable-xhdpi/ch_603_logo.png new file mode 100644 index 00000000..e0d30043 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_603_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_604_logo.png b/tests/common/res/drawable-xhdpi/ch_604_logo.png new file mode 100644 index 00000000..4a592ff9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_604_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_605_logo.png b/tests/common/res/drawable-xhdpi/ch_605_logo.png new file mode 100644 index 00000000..e415b276 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_605_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_606_logo.png b/tests/common/res/drawable-xhdpi/ch_606_logo.png new file mode 100644 index 00000000..2c7b490b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_606_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_607_logo.png b/tests/common/res/drawable-xhdpi/ch_607_logo.png new file mode 100644 index 00000000..649d0327 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_607_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_608_logo.png b/tests/common/res/drawable-xhdpi/ch_608_logo.png new file mode 100644 index 00000000..5581b4d9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_608_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_609_logo.png b/tests/common/res/drawable-xhdpi/ch_609_logo.png new file mode 100644 index 00000000..da8f4bb3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_609_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_60_logo.png b/tests/common/res/drawable-xhdpi/ch_60_logo.png new file mode 100644 index 00000000..0ee771f8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_60_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_610_logo.png b/tests/common/res/drawable-xhdpi/ch_610_logo.png new file mode 100644 index 00000000..7cdbcbd6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_610_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_611_logo.png b/tests/common/res/drawable-xhdpi/ch_611_logo.png new file mode 100644 index 00000000..b14d3f54 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_611_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_612_logo.png b/tests/common/res/drawable-xhdpi/ch_612_logo.png new file mode 100644 index 00000000..d56de9ff Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_612_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_613_logo.png b/tests/common/res/drawable-xhdpi/ch_613_logo.png new file mode 100644 index 00000000..7cfec8b5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_613_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_614_logo.png b/tests/common/res/drawable-xhdpi/ch_614_logo.png new file mode 100644 index 00000000..cc7841ce Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_614_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_615_logo.png b/tests/common/res/drawable-xhdpi/ch_615_logo.png new file mode 100644 index 00000000..6c38d269 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_615_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_616_logo.png b/tests/common/res/drawable-xhdpi/ch_616_logo.png new file mode 100644 index 00000000..e11b777d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_616_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_617_logo.png b/tests/common/res/drawable-xhdpi/ch_617_logo.png new file mode 100644 index 00000000..78aeb608 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_617_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_618_logo.png b/tests/common/res/drawable-xhdpi/ch_618_logo.png new file mode 100644 index 00000000..e1f883fb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_618_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_619_logo.png b/tests/common/res/drawable-xhdpi/ch_619_logo.png new file mode 100644 index 00000000..75f4eeba Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_619_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_61_logo.png b/tests/common/res/drawable-xhdpi/ch_61_logo.png new file mode 100644 index 00000000..801cb923 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_61_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_620_logo.png b/tests/common/res/drawable-xhdpi/ch_620_logo.png new file mode 100644 index 00000000..b6380b73 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_620_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_621_logo.png b/tests/common/res/drawable-xhdpi/ch_621_logo.png new file mode 100644 index 00000000..e6a53185 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_621_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_622_logo.png b/tests/common/res/drawable-xhdpi/ch_622_logo.png new file mode 100644 index 00000000..1cde4b8f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_622_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_623_logo.png b/tests/common/res/drawable-xhdpi/ch_623_logo.png new file mode 100644 index 00000000..f271e516 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_623_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_624_logo.png b/tests/common/res/drawable-xhdpi/ch_624_logo.png new file mode 100644 index 00000000..21343a8c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_624_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_625_logo.png b/tests/common/res/drawable-xhdpi/ch_625_logo.png new file mode 100644 index 00000000..e15b9853 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_625_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_626_logo.png b/tests/common/res/drawable-xhdpi/ch_626_logo.png new file mode 100644 index 00000000..c1f8e273 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_626_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_627_logo.png b/tests/common/res/drawable-xhdpi/ch_627_logo.png new file mode 100644 index 00000000..c07973b5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_627_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_628_logo.png b/tests/common/res/drawable-xhdpi/ch_628_logo.png new file mode 100644 index 00000000..369240ca Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_628_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_629_logo.png b/tests/common/res/drawable-xhdpi/ch_629_logo.png new file mode 100644 index 00000000..ac9f7bd7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_629_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_62_logo.png b/tests/common/res/drawable-xhdpi/ch_62_logo.png new file mode 100644 index 00000000..c4b48ea7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_62_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_630_logo.png b/tests/common/res/drawable-xhdpi/ch_630_logo.png new file mode 100644 index 00000000..4959855a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_630_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_631_logo.png b/tests/common/res/drawable-xhdpi/ch_631_logo.png new file mode 100644 index 00000000..e03e2706 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_631_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_632_logo.png b/tests/common/res/drawable-xhdpi/ch_632_logo.png new file mode 100644 index 00000000..5be9d478 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_632_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_633_logo.png b/tests/common/res/drawable-xhdpi/ch_633_logo.png new file mode 100644 index 00000000..9c51c4a7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_633_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_634_logo.png b/tests/common/res/drawable-xhdpi/ch_634_logo.png new file mode 100644 index 00000000..72d30bc6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_634_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_635_logo.png b/tests/common/res/drawable-xhdpi/ch_635_logo.png new file mode 100644 index 00000000..6c03f3c5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_635_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_636_logo.png b/tests/common/res/drawable-xhdpi/ch_636_logo.png new file mode 100644 index 00000000..928a67c2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_636_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_637_logo.png b/tests/common/res/drawable-xhdpi/ch_637_logo.png new file mode 100644 index 00000000..4a07f186 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_637_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_638_logo.png b/tests/common/res/drawable-xhdpi/ch_638_logo.png new file mode 100644 index 00000000..33813958 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_638_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_639_logo.png b/tests/common/res/drawable-xhdpi/ch_639_logo.png new file mode 100644 index 00000000..be6ba122 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_639_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_63_logo.png b/tests/common/res/drawable-xhdpi/ch_63_logo.png new file mode 100644 index 00000000..dd84e2e6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_63_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_640_logo.png b/tests/common/res/drawable-xhdpi/ch_640_logo.png new file mode 100644 index 00000000..f6e1750c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_640_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_641_logo.png b/tests/common/res/drawable-xhdpi/ch_641_logo.png new file mode 100644 index 00000000..421019c8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_641_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_642_logo.png b/tests/common/res/drawable-xhdpi/ch_642_logo.png new file mode 100644 index 00000000..e441876c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_642_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_643_logo.png b/tests/common/res/drawable-xhdpi/ch_643_logo.png new file mode 100644 index 00000000..29745014 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_643_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_644_logo.png b/tests/common/res/drawable-xhdpi/ch_644_logo.png new file mode 100644 index 00000000..f5e4f2a0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_644_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_645_logo.png b/tests/common/res/drawable-xhdpi/ch_645_logo.png new file mode 100644 index 00000000..bfa0d2cd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_645_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_646_logo.png b/tests/common/res/drawable-xhdpi/ch_646_logo.png new file mode 100644 index 00000000..d148f910 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_646_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_647_logo.png b/tests/common/res/drawable-xhdpi/ch_647_logo.png new file mode 100644 index 00000000..cccc2c66 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_647_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_648_logo.png b/tests/common/res/drawable-xhdpi/ch_648_logo.png new file mode 100644 index 00000000..1fa3cad7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_648_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_649_logo.png b/tests/common/res/drawable-xhdpi/ch_649_logo.png new file mode 100644 index 00000000..0958fd36 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_649_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_64_logo.png b/tests/common/res/drawable-xhdpi/ch_64_logo.png new file mode 100644 index 00000000..9a6a84ec Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_64_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_650_logo.png b/tests/common/res/drawable-xhdpi/ch_650_logo.png new file mode 100644 index 00000000..bd3ab1f0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_650_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_651_logo.png b/tests/common/res/drawable-xhdpi/ch_651_logo.png new file mode 100644 index 00000000..03e859a0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_651_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_652_logo.png b/tests/common/res/drawable-xhdpi/ch_652_logo.png new file mode 100644 index 00000000..81b61689 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_652_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_653_logo.png b/tests/common/res/drawable-xhdpi/ch_653_logo.png new file mode 100644 index 00000000..f6665e5f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_653_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_654_logo.png b/tests/common/res/drawable-xhdpi/ch_654_logo.png new file mode 100644 index 00000000..6ca0e57c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_654_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_655_logo.png b/tests/common/res/drawable-xhdpi/ch_655_logo.png new file mode 100644 index 00000000..51e7b392 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_655_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_656_logo.png b/tests/common/res/drawable-xhdpi/ch_656_logo.png new file mode 100644 index 00000000..bae25480 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_656_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_657_logo.png b/tests/common/res/drawable-xhdpi/ch_657_logo.png new file mode 100644 index 00000000..029e91c9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_657_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_658_logo.png b/tests/common/res/drawable-xhdpi/ch_658_logo.png new file mode 100644 index 00000000..61a77eac Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_658_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_659_logo.png b/tests/common/res/drawable-xhdpi/ch_659_logo.png new file mode 100644 index 00000000..f12e97ee Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_659_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_65_logo.png b/tests/common/res/drawable-xhdpi/ch_65_logo.png new file mode 100644 index 00000000..b770749e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_65_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_660_logo.png b/tests/common/res/drawable-xhdpi/ch_660_logo.png new file mode 100644 index 00000000..1408fb45 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_660_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_661_logo.png b/tests/common/res/drawable-xhdpi/ch_661_logo.png new file mode 100644 index 00000000..f8224b76 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_661_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_662_logo.png b/tests/common/res/drawable-xhdpi/ch_662_logo.png new file mode 100644 index 00000000..08d36a1b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_662_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_663_logo.png b/tests/common/res/drawable-xhdpi/ch_663_logo.png new file mode 100644 index 00000000..b95c596c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_663_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_664_logo.png b/tests/common/res/drawable-xhdpi/ch_664_logo.png new file mode 100644 index 00000000..4c42427a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_664_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_665_logo.png b/tests/common/res/drawable-xhdpi/ch_665_logo.png new file mode 100644 index 00000000..c8918dce Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_665_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_666_logo.png b/tests/common/res/drawable-xhdpi/ch_666_logo.png new file mode 100644 index 00000000..6839f828 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_666_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_667_logo.png b/tests/common/res/drawable-xhdpi/ch_667_logo.png new file mode 100644 index 00000000..28702195 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_667_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_668_logo.png b/tests/common/res/drawable-xhdpi/ch_668_logo.png new file mode 100644 index 00000000..944a8d46 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_668_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_669_logo.png b/tests/common/res/drawable-xhdpi/ch_669_logo.png new file mode 100644 index 00000000..2677744f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_669_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_66_logo.png b/tests/common/res/drawable-xhdpi/ch_66_logo.png new file mode 100644 index 00000000..e298eb86 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_66_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_670_logo.png b/tests/common/res/drawable-xhdpi/ch_670_logo.png new file mode 100644 index 00000000..bcb1d377 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_670_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_671_logo.png b/tests/common/res/drawable-xhdpi/ch_671_logo.png new file mode 100644 index 00000000..3e01dba8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_671_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_672_logo.png b/tests/common/res/drawable-xhdpi/ch_672_logo.png new file mode 100644 index 00000000..478d3f22 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_672_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_673_logo.png b/tests/common/res/drawable-xhdpi/ch_673_logo.png new file mode 100644 index 00000000..123522db Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_673_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_674_logo.png b/tests/common/res/drawable-xhdpi/ch_674_logo.png new file mode 100644 index 00000000..25c44806 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_674_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_675_logo.png b/tests/common/res/drawable-xhdpi/ch_675_logo.png new file mode 100644 index 00000000..223ba448 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_675_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_676_logo.png b/tests/common/res/drawable-xhdpi/ch_676_logo.png new file mode 100644 index 00000000..12cca720 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_676_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_677_logo.png b/tests/common/res/drawable-xhdpi/ch_677_logo.png new file mode 100644 index 00000000..ec735de1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_677_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_678_logo.png b/tests/common/res/drawable-xhdpi/ch_678_logo.png new file mode 100644 index 00000000..1eee9496 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_678_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_679_logo.png b/tests/common/res/drawable-xhdpi/ch_679_logo.png new file mode 100644 index 00000000..231b68eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_679_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_67_logo.png b/tests/common/res/drawable-xhdpi/ch_67_logo.png new file mode 100644 index 00000000..5fcf7946 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_67_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_680_logo.png b/tests/common/res/drawable-xhdpi/ch_680_logo.png new file mode 100644 index 00000000..24c09b87 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_680_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_681_logo.png b/tests/common/res/drawable-xhdpi/ch_681_logo.png new file mode 100644 index 00000000..2bae5da6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_681_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_682_logo.png b/tests/common/res/drawable-xhdpi/ch_682_logo.png new file mode 100644 index 00000000..6bb1047f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_682_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_683_logo.png b/tests/common/res/drawable-xhdpi/ch_683_logo.png new file mode 100644 index 00000000..04be69f4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_683_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_684_logo.png b/tests/common/res/drawable-xhdpi/ch_684_logo.png new file mode 100644 index 00000000..02e0666d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_684_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_685_logo.png b/tests/common/res/drawable-xhdpi/ch_685_logo.png new file mode 100644 index 00000000..ff410e54 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_685_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_686_logo.png b/tests/common/res/drawable-xhdpi/ch_686_logo.png new file mode 100644 index 00000000..f93e7d7f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_686_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_687_logo.png b/tests/common/res/drawable-xhdpi/ch_687_logo.png new file mode 100644 index 00000000..a97a96f4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_687_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_688_logo.png b/tests/common/res/drawable-xhdpi/ch_688_logo.png new file mode 100644 index 00000000..f094f64c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_688_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_689_logo.png b/tests/common/res/drawable-xhdpi/ch_689_logo.png new file mode 100644 index 00000000..c557b3f1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_689_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_68_logo.png b/tests/common/res/drawable-xhdpi/ch_68_logo.png new file mode 100644 index 00000000..957bcb55 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_68_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_690_logo.png b/tests/common/res/drawable-xhdpi/ch_690_logo.png new file mode 100644 index 00000000..ee692d35 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_690_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_691_logo.png b/tests/common/res/drawable-xhdpi/ch_691_logo.png new file mode 100644 index 00000000..7087605e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_691_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_692_logo.png b/tests/common/res/drawable-xhdpi/ch_692_logo.png new file mode 100644 index 00000000..902b536f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_692_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_693_logo.png b/tests/common/res/drawable-xhdpi/ch_693_logo.png new file mode 100644 index 00000000..3fb1634e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_693_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_694_logo.png b/tests/common/res/drawable-xhdpi/ch_694_logo.png new file mode 100644 index 00000000..baaa7b69 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_694_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_695_logo.png b/tests/common/res/drawable-xhdpi/ch_695_logo.png new file mode 100644 index 00000000..1d957bfd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_695_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_696_logo.png b/tests/common/res/drawable-xhdpi/ch_696_logo.png new file mode 100644 index 00000000..ca8ed9e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_696_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_697_logo.png b/tests/common/res/drawable-xhdpi/ch_697_logo.png new file mode 100644 index 00000000..e01c4825 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_697_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_698_logo.png b/tests/common/res/drawable-xhdpi/ch_698_logo.png new file mode 100644 index 00000000..c6540359 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_698_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_699_logo.png b/tests/common/res/drawable-xhdpi/ch_699_logo.png new file mode 100644 index 00000000..70a22946 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_699_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_69_logo.png b/tests/common/res/drawable-xhdpi/ch_69_logo.png new file mode 100644 index 00000000..6ccbe33b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_69_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_6_logo.png b/tests/common/res/drawable-xhdpi/ch_6_logo.png new file mode 100644 index 00000000..22887a6c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_6_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_700_logo.png b/tests/common/res/drawable-xhdpi/ch_700_logo.png new file mode 100644 index 00000000..1cbf5379 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_700_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_701_logo.png b/tests/common/res/drawable-xhdpi/ch_701_logo.png new file mode 100644 index 00000000..9b438c00 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_701_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_702_logo.png b/tests/common/res/drawable-xhdpi/ch_702_logo.png new file mode 100644 index 00000000..92bef76d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_702_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_703_logo.png b/tests/common/res/drawable-xhdpi/ch_703_logo.png new file mode 100644 index 00000000..78d680b0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_703_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_704_logo.png b/tests/common/res/drawable-xhdpi/ch_704_logo.png new file mode 100644 index 00000000..fcb61adb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_704_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_705_logo.png b/tests/common/res/drawable-xhdpi/ch_705_logo.png new file mode 100644 index 00000000..87566617 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_705_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_706_logo.png b/tests/common/res/drawable-xhdpi/ch_706_logo.png new file mode 100644 index 00000000..fe9d775b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_706_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_707_logo.png b/tests/common/res/drawable-xhdpi/ch_707_logo.png new file mode 100644 index 00000000..b3b0451c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_707_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_708_logo.png b/tests/common/res/drawable-xhdpi/ch_708_logo.png new file mode 100644 index 00000000..2a95b3a4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_708_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_709_logo.png b/tests/common/res/drawable-xhdpi/ch_709_logo.png new file mode 100644 index 00000000..55111fbe Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_709_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_70_logo.png b/tests/common/res/drawable-xhdpi/ch_70_logo.png new file mode 100644 index 00000000..1860ec9c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_70_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_710_logo.png b/tests/common/res/drawable-xhdpi/ch_710_logo.png new file mode 100644 index 00000000..6284e6ee Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_710_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_711_logo.png b/tests/common/res/drawable-xhdpi/ch_711_logo.png new file mode 100644 index 00000000..d55d32ca Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_711_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_712_logo.png b/tests/common/res/drawable-xhdpi/ch_712_logo.png new file mode 100644 index 00000000..7268941c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_712_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_713_logo.png b/tests/common/res/drawable-xhdpi/ch_713_logo.png new file mode 100644 index 00000000..781301e4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_713_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_714_logo.png b/tests/common/res/drawable-xhdpi/ch_714_logo.png new file mode 100644 index 00000000..8145eaac Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_714_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_715_logo.png b/tests/common/res/drawable-xhdpi/ch_715_logo.png new file mode 100644 index 00000000..b7715dc5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_715_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_716_logo.png b/tests/common/res/drawable-xhdpi/ch_716_logo.png new file mode 100644 index 00000000..0c1f26e4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_716_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_717_logo.png b/tests/common/res/drawable-xhdpi/ch_717_logo.png new file mode 100644 index 00000000..de20828f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_717_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_718_logo.png b/tests/common/res/drawable-xhdpi/ch_718_logo.png new file mode 100644 index 00000000..8d915ecc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_718_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_719_logo.png b/tests/common/res/drawable-xhdpi/ch_719_logo.png new file mode 100644 index 00000000..bd1c668d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_719_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_71_logo.png b/tests/common/res/drawable-xhdpi/ch_71_logo.png new file mode 100644 index 00000000..e3f8b550 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_71_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_720_logo.png b/tests/common/res/drawable-xhdpi/ch_720_logo.png new file mode 100644 index 00000000..66a60923 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_720_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_721_logo.png b/tests/common/res/drawable-xhdpi/ch_721_logo.png new file mode 100644 index 00000000..c877d396 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_721_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_722_logo.png b/tests/common/res/drawable-xhdpi/ch_722_logo.png new file mode 100644 index 00000000..9fe37161 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_722_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_723_logo.png b/tests/common/res/drawable-xhdpi/ch_723_logo.png new file mode 100644 index 00000000..94563ccd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_723_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_724_logo.png b/tests/common/res/drawable-xhdpi/ch_724_logo.png new file mode 100644 index 00000000..52e5fcd9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_724_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_725_logo.png b/tests/common/res/drawable-xhdpi/ch_725_logo.png new file mode 100644 index 00000000..518fefaa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_725_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_726_logo.png b/tests/common/res/drawable-xhdpi/ch_726_logo.png new file mode 100644 index 00000000..1f28222e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_726_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_727_logo.png b/tests/common/res/drawable-xhdpi/ch_727_logo.png new file mode 100644 index 00000000..bffbc57f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_727_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_728_logo.png b/tests/common/res/drawable-xhdpi/ch_728_logo.png new file mode 100644 index 00000000..f036778b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_728_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_729_logo.png b/tests/common/res/drawable-xhdpi/ch_729_logo.png new file mode 100644 index 00000000..a2838d56 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_729_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_72_logo.png b/tests/common/res/drawable-xhdpi/ch_72_logo.png new file mode 100644 index 00000000..5b2f971c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_72_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_730_logo.png b/tests/common/res/drawable-xhdpi/ch_730_logo.png new file mode 100644 index 00000000..443d08b8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_730_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_731_logo.png b/tests/common/res/drawable-xhdpi/ch_731_logo.png new file mode 100644 index 00000000..d07e52a1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_731_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_732_logo.png b/tests/common/res/drawable-xhdpi/ch_732_logo.png new file mode 100644 index 00000000..56f51b3f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_732_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_733_logo.png b/tests/common/res/drawable-xhdpi/ch_733_logo.png new file mode 100644 index 00000000..18248b0a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_733_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_734_logo.png b/tests/common/res/drawable-xhdpi/ch_734_logo.png new file mode 100644 index 00000000..fb740e33 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_734_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_735_logo.png b/tests/common/res/drawable-xhdpi/ch_735_logo.png new file mode 100644 index 00000000..9f83309f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_735_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_736_logo.png b/tests/common/res/drawable-xhdpi/ch_736_logo.png new file mode 100644 index 00000000..768fc5ef Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_736_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_737_logo.png b/tests/common/res/drawable-xhdpi/ch_737_logo.png new file mode 100644 index 00000000..6af65c67 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_737_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_738_logo.png b/tests/common/res/drawable-xhdpi/ch_738_logo.png new file mode 100644 index 00000000..3b908312 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_738_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_739_logo.png b/tests/common/res/drawable-xhdpi/ch_739_logo.png new file mode 100644 index 00000000..fea61e37 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_739_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_73_logo.png b/tests/common/res/drawable-xhdpi/ch_73_logo.png new file mode 100644 index 00000000..13556e6d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_73_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_740_logo.png b/tests/common/res/drawable-xhdpi/ch_740_logo.png new file mode 100644 index 00000000..7fe9aa19 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_740_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_741_logo.png b/tests/common/res/drawable-xhdpi/ch_741_logo.png new file mode 100644 index 00000000..a437fd22 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_741_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_742_logo.png b/tests/common/res/drawable-xhdpi/ch_742_logo.png new file mode 100644 index 00000000..5674dccb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_742_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_743_logo.png b/tests/common/res/drawable-xhdpi/ch_743_logo.png new file mode 100644 index 00000000..dbfcaa51 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_743_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_744_logo.png b/tests/common/res/drawable-xhdpi/ch_744_logo.png new file mode 100644 index 00000000..fe6dd633 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_744_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_745_logo.png b/tests/common/res/drawable-xhdpi/ch_745_logo.png new file mode 100644 index 00000000..703bc014 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_745_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_746_logo.png b/tests/common/res/drawable-xhdpi/ch_746_logo.png new file mode 100644 index 00000000..15775a4c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_746_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_747_logo.png b/tests/common/res/drawable-xhdpi/ch_747_logo.png new file mode 100644 index 00000000..d5666758 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_747_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_748_logo.png b/tests/common/res/drawable-xhdpi/ch_748_logo.png new file mode 100644 index 00000000..3bb80444 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_748_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_749_logo.png b/tests/common/res/drawable-xhdpi/ch_749_logo.png new file mode 100644 index 00000000..92a6954a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_749_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_74_logo.png b/tests/common/res/drawable-xhdpi/ch_74_logo.png new file mode 100644 index 00000000..484f4c0d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_74_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_750_logo.png b/tests/common/res/drawable-xhdpi/ch_750_logo.png new file mode 100644 index 00000000..96af0fa6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_750_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_751_logo.png b/tests/common/res/drawable-xhdpi/ch_751_logo.png new file mode 100644 index 00000000..bddc6d97 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_751_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_752_logo.png b/tests/common/res/drawable-xhdpi/ch_752_logo.png new file mode 100644 index 00000000..38d693f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_752_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_753_logo.png b/tests/common/res/drawable-xhdpi/ch_753_logo.png new file mode 100644 index 00000000..3c383de7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_753_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_754_logo.png b/tests/common/res/drawable-xhdpi/ch_754_logo.png new file mode 100644 index 00000000..04d7b62b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_754_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_755_logo.png b/tests/common/res/drawable-xhdpi/ch_755_logo.png new file mode 100644 index 00000000..d7846b43 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_755_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_756_logo.png b/tests/common/res/drawable-xhdpi/ch_756_logo.png new file mode 100644 index 00000000..6b51e7d1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_756_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_757_logo.png b/tests/common/res/drawable-xhdpi/ch_757_logo.png new file mode 100644 index 00000000..b396d7c3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_757_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_758_logo.png b/tests/common/res/drawable-xhdpi/ch_758_logo.png new file mode 100644 index 00000000..d77783b7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_758_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_759_logo.png b/tests/common/res/drawable-xhdpi/ch_759_logo.png new file mode 100644 index 00000000..ca26792b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_759_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_75_logo.png b/tests/common/res/drawable-xhdpi/ch_75_logo.png new file mode 100644 index 00000000..aa78479e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_75_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_760_logo.png b/tests/common/res/drawable-xhdpi/ch_760_logo.png new file mode 100644 index 00000000..5d56db66 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_760_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_761_logo.png b/tests/common/res/drawable-xhdpi/ch_761_logo.png new file mode 100644 index 00000000..e505365d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_761_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_762_logo.png b/tests/common/res/drawable-xhdpi/ch_762_logo.png new file mode 100644 index 00000000..202b7572 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_762_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_763_logo.png b/tests/common/res/drawable-xhdpi/ch_763_logo.png new file mode 100644 index 00000000..14af6ab4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_763_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_764_logo.png b/tests/common/res/drawable-xhdpi/ch_764_logo.png new file mode 100644 index 00000000..c4e1ea66 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_764_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_765_logo.png b/tests/common/res/drawable-xhdpi/ch_765_logo.png new file mode 100644 index 00000000..3b5ded8e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_765_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_766_logo.png b/tests/common/res/drawable-xhdpi/ch_766_logo.png new file mode 100644 index 00000000..61391524 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_766_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_767_logo.png b/tests/common/res/drawable-xhdpi/ch_767_logo.png new file mode 100644 index 00000000..4fa0441e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_767_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_768_logo.png b/tests/common/res/drawable-xhdpi/ch_768_logo.png new file mode 100644 index 00000000..80e8293b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_768_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_769_logo.png b/tests/common/res/drawable-xhdpi/ch_769_logo.png new file mode 100644 index 00000000..63c2e6cc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_769_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_76_logo.png b/tests/common/res/drawable-xhdpi/ch_76_logo.png new file mode 100644 index 00000000..3ebf6807 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_76_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_770_logo.png b/tests/common/res/drawable-xhdpi/ch_770_logo.png new file mode 100644 index 00000000..b2ee3584 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_770_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_771_logo.png b/tests/common/res/drawable-xhdpi/ch_771_logo.png new file mode 100644 index 00000000..9a53a498 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_771_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_772_logo.png b/tests/common/res/drawable-xhdpi/ch_772_logo.png new file mode 100644 index 00000000..1da9e70d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_772_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_773_logo.png b/tests/common/res/drawable-xhdpi/ch_773_logo.png new file mode 100644 index 00000000..0f35e46a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_773_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_774_logo.png b/tests/common/res/drawable-xhdpi/ch_774_logo.png new file mode 100644 index 00000000..acaa8857 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_774_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_775_logo.png b/tests/common/res/drawable-xhdpi/ch_775_logo.png new file mode 100644 index 00000000..70ca99e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_775_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_776_logo.png b/tests/common/res/drawable-xhdpi/ch_776_logo.png new file mode 100644 index 00000000..ab54947d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_776_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_777_logo.png b/tests/common/res/drawable-xhdpi/ch_777_logo.png new file mode 100644 index 00000000..ed378329 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_777_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_778_logo.png b/tests/common/res/drawable-xhdpi/ch_778_logo.png new file mode 100644 index 00000000..01cdc4e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_778_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_779_logo.png b/tests/common/res/drawable-xhdpi/ch_779_logo.png new file mode 100644 index 00000000..ca5175f7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_779_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_77_logo.png b/tests/common/res/drawable-xhdpi/ch_77_logo.png new file mode 100644 index 00000000..c22abf24 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_77_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_780_logo.png b/tests/common/res/drawable-xhdpi/ch_780_logo.png new file mode 100644 index 00000000..2e5f24e2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_780_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_781_logo.png b/tests/common/res/drawable-xhdpi/ch_781_logo.png new file mode 100644 index 00000000..81e5ed66 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_781_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_782_logo.png b/tests/common/res/drawable-xhdpi/ch_782_logo.png new file mode 100644 index 00000000..3236afa9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_782_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_783_logo.png b/tests/common/res/drawable-xhdpi/ch_783_logo.png new file mode 100644 index 00000000..a600800e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_783_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_784_logo.png b/tests/common/res/drawable-xhdpi/ch_784_logo.png new file mode 100644 index 00000000..cd97c5eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_784_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_785_logo.png b/tests/common/res/drawable-xhdpi/ch_785_logo.png new file mode 100644 index 00000000..d50fd82a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_785_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_786_logo.png b/tests/common/res/drawable-xhdpi/ch_786_logo.png new file mode 100644 index 00000000..7a93ef71 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_786_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_787_logo.png b/tests/common/res/drawable-xhdpi/ch_787_logo.png new file mode 100644 index 00000000..62b5cf6b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_787_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_788_logo.png b/tests/common/res/drawable-xhdpi/ch_788_logo.png new file mode 100644 index 00000000..c82602d3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_788_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_789_logo.png b/tests/common/res/drawable-xhdpi/ch_789_logo.png new file mode 100644 index 00000000..7dfbeeb3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_789_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_78_logo.png b/tests/common/res/drawable-xhdpi/ch_78_logo.png new file mode 100644 index 00000000..03fb415f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_78_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_790_logo.png b/tests/common/res/drawable-xhdpi/ch_790_logo.png new file mode 100644 index 00000000..d44c2181 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_790_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_791_logo.png b/tests/common/res/drawable-xhdpi/ch_791_logo.png new file mode 100644 index 00000000..ee81b961 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_791_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_792_logo.png b/tests/common/res/drawable-xhdpi/ch_792_logo.png new file mode 100644 index 00000000..37715025 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_792_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_793_logo.png b/tests/common/res/drawable-xhdpi/ch_793_logo.png new file mode 100644 index 00000000..eca3a477 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_793_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_794_logo.png b/tests/common/res/drawable-xhdpi/ch_794_logo.png new file mode 100644 index 00000000..fbbfc372 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_794_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_795_logo.png b/tests/common/res/drawable-xhdpi/ch_795_logo.png new file mode 100644 index 00000000..0366a2bd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_795_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_796_logo.png b/tests/common/res/drawable-xhdpi/ch_796_logo.png new file mode 100644 index 00000000..e13e6bc1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_796_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_797_logo.png b/tests/common/res/drawable-xhdpi/ch_797_logo.png new file mode 100644 index 00000000..e45decc0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_797_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_798_logo.png b/tests/common/res/drawable-xhdpi/ch_798_logo.png new file mode 100644 index 00000000..6b9659e2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_798_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_799_logo.png b/tests/common/res/drawable-xhdpi/ch_799_logo.png new file mode 100644 index 00000000..e8cb2ef8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_799_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_79_logo.png b/tests/common/res/drawable-xhdpi/ch_79_logo.png new file mode 100644 index 00000000..237fa7b2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_79_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_7_logo.png b/tests/common/res/drawable-xhdpi/ch_7_logo.png new file mode 100644 index 00000000..df78066a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_7_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_800_logo.png b/tests/common/res/drawable-xhdpi/ch_800_logo.png new file mode 100644 index 00000000..6c775941 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_800_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_801_logo.png b/tests/common/res/drawable-xhdpi/ch_801_logo.png new file mode 100644 index 00000000..f0006515 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_801_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_802_logo.png b/tests/common/res/drawable-xhdpi/ch_802_logo.png new file mode 100644 index 00000000..4c504b58 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_802_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_803_logo.png b/tests/common/res/drawable-xhdpi/ch_803_logo.png new file mode 100644 index 00000000..edfa6869 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_803_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_804_logo.png b/tests/common/res/drawable-xhdpi/ch_804_logo.png new file mode 100644 index 00000000..c7b0dba0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_804_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_805_logo.png b/tests/common/res/drawable-xhdpi/ch_805_logo.png new file mode 100644 index 00000000..bcd6ee28 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_805_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_806_logo.png b/tests/common/res/drawable-xhdpi/ch_806_logo.png new file mode 100644 index 00000000..e926e9f4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_806_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_807_logo.png b/tests/common/res/drawable-xhdpi/ch_807_logo.png new file mode 100644 index 00000000..25334551 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_807_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_808_logo.png b/tests/common/res/drawable-xhdpi/ch_808_logo.png new file mode 100644 index 00000000..5d7b51a9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_808_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_809_logo.png b/tests/common/res/drawable-xhdpi/ch_809_logo.png new file mode 100644 index 00000000..2f6a8e31 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_809_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_80_logo.png b/tests/common/res/drawable-xhdpi/ch_80_logo.png new file mode 100644 index 00000000..ff7a55c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_80_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_810_logo.png b/tests/common/res/drawable-xhdpi/ch_810_logo.png new file mode 100644 index 00000000..1a083992 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_810_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_811_logo.png b/tests/common/res/drawable-xhdpi/ch_811_logo.png new file mode 100644 index 00000000..070df630 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_811_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_812_logo.png b/tests/common/res/drawable-xhdpi/ch_812_logo.png new file mode 100644 index 00000000..376e68cb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_812_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_813_logo.png b/tests/common/res/drawable-xhdpi/ch_813_logo.png new file mode 100644 index 00000000..d72f0a8e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_813_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_814_logo.png b/tests/common/res/drawable-xhdpi/ch_814_logo.png new file mode 100644 index 00000000..b50d9ad1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_814_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_815_logo.png b/tests/common/res/drawable-xhdpi/ch_815_logo.png new file mode 100644 index 00000000..fdbc4cdd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_815_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_816_logo.png b/tests/common/res/drawable-xhdpi/ch_816_logo.png new file mode 100644 index 00000000..f1212200 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_816_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_817_logo.png b/tests/common/res/drawable-xhdpi/ch_817_logo.png new file mode 100644 index 00000000..be4fa008 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_817_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_818_logo.png b/tests/common/res/drawable-xhdpi/ch_818_logo.png new file mode 100644 index 00000000..c9d087d7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_818_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_819_logo.png b/tests/common/res/drawable-xhdpi/ch_819_logo.png new file mode 100644 index 00000000..b8cab83d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_819_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_81_logo.png b/tests/common/res/drawable-xhdpi/ch_81_logo.png new file mode 100644 index 00000000..58f6fc00 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_81_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_820_logo.png b/tests/common/res/drawable-xhdpi/ch_820_logo.png new file mode 100644 index 00000000..3bf591fb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_820_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_821_logo.png b/tests/common/res/drawable-xhdpi/ch_821_logo.png new file mode 100644 index 00000000..16046f46 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_821_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_822_logo.png b/tests/common/res/drawable-xhdpi/ch_822_logo.png new file mode 100644 index 00000000..58d688c9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_822_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_823_logo.png b/tests/common/res/drawable-xhdpi/ch_823_logo.png new file mode 100644 index 00000000..6b70ecc1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_823_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_824_logo.png b/tests/common/res/drawable-xhdpi/ch_824_logo.png new file mode 100644 index 00000000..4c90c96e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_824_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_825_logo.png b/tests/common/res/drawable-xhdpi/ch_825_logo.png new file mode 100644 index 00000000..5111136c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_825_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_826_logo.png b/tests/common/res/drawable-xhdpi/ch_826_logo.png new file mode 100644 index 00000000..f35941e8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_826_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_827_logo.png b/tests/common/res/drawable-xhdpi/ch_827_logo.png new file mode 100644 index 00000000..49add1ab Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_827_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_828_logo.png b/tests/common/res/drawable-xhdpi/ch_828_logo.png new file mode 100644 index 00000000..67936aa4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_828_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_829_logo.png b/tests/common/res/drawable-xhdpi/ch_829_logo.png new file mode 100644 index 00000000..7bc97945 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_829_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_82_logo.png b/tests/common/res/drawable-xhdpi/ch_82_logo.png new file mode 100644 index 00000000..ddd21127 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_82_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_830_logo.png b/tests/common/res/drawable-xhdpi/ch_830_logo.png new file mode 100644 index 00000000..3aa669d8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_830_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_831_logo.png b/tests/common/res/drawable-xhdpi/ch_831_logo.png new file mode 100644 index 00000000..f7c2cf1c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_831_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_832_logo.png b/tests/common/res/drawable-xhdpi/ch_832_logo.png new file mode 100644 index 00000000..19096734 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_832_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_833_logo.png b/tests/common/res/drawable-xhdpi/ch_833_logo.png new file mode 100644 index 00000000..d0a1af2f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_833_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_834_logo.png b/tests/common/res/drawable-xhdpi/ch_834_logo.png new file mode 100644 index 00000000..52e2ef58 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_834_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_835_logo.png b/tests/common/res/drawable-xhdpi/ch_835_logo.png new file mode 100644 index 00000000..7ac421e1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_835_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_836_logo.png b/tests/common/res/drawable-xhdpi/ch_836_logo.png new file mode 100644 index 00000000..7f4577b4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_836_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_837_logo.png b/tests/common/res/drawable-xhdpi/ch_837_logo.png new file mode 100644 index 00000000..df5e1c86 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_837_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_838_logo.png b/tests/common/res/drawable-xhdpi/ch_838_logo.png new file mode 100644 index 00000000..579557ed Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_838_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_839_logo.png b/tests/common/res/drawable-xhdpi/ch_839_logo.png new file mode 100644 index 00000000..3988cc5b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_839_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_83_logo.png b/tests/common/res/drawable-xhdpi/ch_83_logo.png new file mode 100644 index 00000000..dd97e15c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_83_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_840_logo.png b/tests/common/res/drawable-xhdpi/ch_840_logo.png new file mode 100644 index 00000000..29e88d2a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_840_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_841_logo.png b/tests/common/res/drawable-xhdpi/ch_841_logo.png new file mode 100644 index 00000000..9ee069a4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_841_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_842_logo.png b/tests/common/res/drawable-xhdpi/ch_842_logo.png new file mode 100644 index 00000000..edbd8c57 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_842_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_843_logo.png b/tests/common/res/drawable-xhdpi/ch_843_logo.png new file mode 100644 index 00000000..7ee70de2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_843_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_844_logo.png b/tests/common/res/drawable-xhdpi/ch_844_logo.png new file mode 100644 index 00000000..6a0a95a8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_844_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_845_logo.png b/tests/common/res/drawable-xhdpi/ch_845_logo.png new file mode 100644 index 00000000..ae4aa7eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_845_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_846_logo.png b/tests/common/res/drawable-xhdpi/ch_846_logo.png new file mode 100644 index 00000000..79eb61d9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_846_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_847_logo.png b/tests/common/res/drawable-xhdpi/ch_847_logo.png new file mode 100644 index 00000000..2a8fd743 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_847_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_848_logo.png b/tests/common/res/drawable-xhdpi/ch_848_logo.png new file mode 100644 index 00000000..c2e82237 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_848_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_849_logo.png b/tests/common/res/drawable-xhdpi/ch_849_logo.png new file mode 100644 index 00000000..44d58b8d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_849_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_84_logo.png b/tests/common/res/drawable-xhdpi/ch_84_logo.png new file mode 100644 index 00000000..5ad6a473 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_84_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_850_logo.png b/tests/common/res/drawable-xhdpi/ch_850_logo.png new file mode 100644 index 00000000..358dc326 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_850_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_851_logo.png b/tests/common/res/drawable-xhdpi/ch_851_logo.png new file mode 100644 index 00000000..7c5ef9cd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_851_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_852_logo.png b/tests/common/res/drawable-xhdpi/ch_852_logo.png new file mode 100644 index 00000000..0ab1e98b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_852_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_853_logo.png b/tests/common/res/drawable-xhdpi/ch_853_logo.png new file mode 100644 index 00000000..c5b6a11b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_853_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_854_logo.png b/tests/common/res/drawable-xhdpi/ch_854_logo.png new file mode 100644 index 00000000..5c34d4e2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_854_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_855_logo.png b/tests/common/res/drawable-xhdpi/ch_855_logo.png new file mode 100644 index 00000000..b8f10da8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_855_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_856_logo.png b/tests/common/res/drawable-xhdpi/ch_856_logo.png new file mode 100644 index 00000000..1b790da7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_856_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_857_logo.png b/tests/common/res/drawable-xhdpi/ch_857_logo.png new file mode 100644 index 00000000..a7f3b55d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_857_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_858_logo.png b/tests/common/res/drawable-xhdpi/ch_858_logo.png new file mode 100644 index 00000000..e3555be6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_858_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_859_logo.png b/tests/common/res/drawable-xhdpi/ch_859_logo.png new file mode 100644 index 00000000..970ee63e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_859_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_85_logo.png b/tests/common/res/drawable-xhdpi/ch_85_logo.png new file mode 100644 index 00000000..35c5c992 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_85_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_860_logo.png b/tests/common/res/drawable-xhdpi/ch_860_logo.png new file mode 100644 index 00000000..72720501 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_860_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_861_logo.png b/tests/common/res/drawable-xhdpi/ch_861_logo.png new file mode 100644 index 00000000..bf6d0be9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_861_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_862_logo.png b/tests/common/res/drawable-xhdpi/ch_862_logo.png new file mode 100644 index 00000000..ee6c88f2 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_862_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_863_logo.png b/tests/common/res/drawable-xhdpi/ch_863_logo.png new file mode 100644 index 00000000..9b92ef30 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_863_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_864_logo.png b/tests/common/res/drawable-xhdpi/ch_864_logo.png new file mode 100644 index 00000000..2024e828 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_864_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_865_logo.png b/tests/common/res/drawable-xhdpi/ch_865_logo.png new file mode 100644 index 00000000..80b87668 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_865_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_866_logo.png b/tests/common/res/drawable-xhdpi/ch_866_logo.png new file mode 100644 index 00000000..6dfe8538 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_866_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_867_logo.png b/tests/common/res/drawable-xhdpi/ch_867_logo.png new file mode 100644 index 00000000..6cb51f22 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_867_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_868_logo.png b/tests/common/res/drawable-xhdpi/ch_868_logo.png new file mode 100644 index 00000000..e179e5cf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_868_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_869_logo.png b/tests/common/res/drawable-xhdpi/ch_869_logo.png new file mode 100644 index 00000000..940780eb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_869_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_86_logo.png b/tests/common/res/drawable-xhdpi/ch_86_logo.png new file mode 100644 index 00000000..93bbad73 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_86_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_870_logo.png b/tests/common/res/drawable-xhdpi/ch_870_logo.png new file mode 100644 index 00000000..caa05fe8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_870_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_871_logo.png b/tests/common/res/drawable-xhdpi/ch_871_logo.png new file mode 100644 index 00000000..9085b8a8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_871_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_872_logo.png b/tests/common/res/drawable-xhdpi/ch_872_logo.png new file mode 100644 index 00000000..695dabaf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_872_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_873_logo.png b/tests/common/res/drawable-xhdpi/ch_873_logo.png new file mode 100644 index 00000000..21871509 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_873_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_874_logo.png b/tests/common/res/drawable-xhdpi/ch_874_logo.png new file mode 100644 index 00000000..2bbcf9cc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_874_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_875_logo.png b/tests/common/res/drawable-xhdpi/ch_875_logo.png new file mode 100644 index 00000000..494b8ebc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_875_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_876_logo.png b/tests/common/res/drawable-xhdpi/ch_876_logo.png new file mode 100644 index 00000000..eded05e4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_876_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_877_logo.png b/tests/common/res/drawable-xhdpi/ch_877_logo.png new file mode 100644 index 00000000..cf22732d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_877_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_878_logo.png b/tests/common/res/drawable-xhdpi/ch_878_logo.png new file mode 100644 index 00000000..3ca881cf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_878_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_879_logo.png b/tests/common/res/drawable-xhdpi/ch_879_logo.png new file mode 100644 index 00000000..8f629b25 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_879_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_87_logo.png b/tests/common/res/drawable-xhdpi/ch_87_logo.png new file mode 100644 index 00000000..1a661da8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_87_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_880_logo.png b/tests/common/res/drawable-xhdpi/ch_880_logo.png new file mode 100644 index 00000000..f23459d8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_880_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_881_logo.png b/tests/common/res/drawable-xhdpi/ch_881_logo.png new file mode 100644 index 00000000..810320be Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_881_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_882_logo.png b/tests/common/res/drawable-xhdpi/ch_882_logo.png new file mode 100644 index 00000000..cee19fc9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_882_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_883_logo.png b/tests/common/res/drawable-xhdpi/ch_883_logo.png new file mode 100644 index 00000000..b7cd2a11 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_883_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_884_logo.png b/tests/common/res/drawable-xhdpi/ch_884_logo.png new file mode 100644 index 00000000..ee24315c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_884_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_885_logo.png b/tests/common/res/drawable-xhdpi/ch_885_logo.png new file mode 100644 index 00000000..aac4f31f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_885_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_886_logo.png b/tests/common/res/drawable-xhdpi/ch_886_logo.png new file mode 100644 index 00000000..d929436e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_886_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_887_logo.png b/tests/common/res/drawable-xhdpi/ch_887_logo.png new file mode 100644 index 00000000..4d38b4c9 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_887_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_888_logo.png b/tests/common/res/drawable-xhdpi/ch_888_logo.png new file mode 100644 index 00000000..b8bf4191 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_888_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_889_logo.png b/tests/common/res/drawable-xhdpi/ch_889_logo.png new file mode 100644 index 00000000..e38f60dd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_889_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_88_logo.png b/tests/common/res/drawable-xhdpi/ch_88_logo.png new file mode 100644 index 00000000..9cf19a3b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_88_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_890_logo.png b/tests/common/res/drawable-xhdpi/ch_890_logo.png new file mode 100644 index 00000000..46e8755c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_890_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_891_logo.png b/tests/common/res/drawable-xhdpi/ch_891_logo.png new file mode 100644 index 00000000..8fcae156 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_891_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_892_logo.png b/tests/common/res/drawable-xhdpi/ch_892_logo.png new file mode 100644 index 00000000..4672b7e1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_892_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_893_logo.png b/tests/common/res/drawable-xhdpi/ch_893_logo.png new file mode 100644 index 00000000..170586f5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_893_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_894_logo.png b/tests/common/res/drawable-xhdpi/ch_894_logo.png new file mode 100644 index 00000000..4b366898 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_894_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_895_logo.png b/tests/common/res/drawable-xhdpi/ch_895_logo.png new file mode 100644 index 00000000..1855d24c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_895_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_896_logo.png b/tests/common/res/drawable-xhdpi/ch_896_logo.png new file mode 100644 index 00000000..dc47d447 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_896_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_897_logo.png b/tests/common/res/drawable-xhdpi/ch_897_logo.png new file mode 100644 index 00000000..909a60e0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_897_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_898_logo.png b/tests/common/res/drawable-xhdpi/ch_898_logo.png new file mode 100644 index 00000000..a57a6344 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_898_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_899_logo.png b/tests/common/res/drawable-xhdpi/ch_899_logo.png new file mode 100644 index 00000000..fa99418a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_899_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_89_logo.png b/tests/common/res/drawable-xhdpi/ch_89_logo.png new file mode 100644 index 00000000..0c7edc8c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_89_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_8_logo.png b/tests/common/res/drawable-xhdpi/ch_8_logo.png new file mode 100644 index 00000000..0886b851 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_8_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_900_logo.png b/tests/common/res/drawable-xhdpi/ch_900_logo.png new file mode 100644 index 00000000..cf6327bb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_900_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_901_logo.png b/tests/common/res/drawable-xhdpi/ch_901_logo.png new file mode 100644 index 00000000..17e1c9ef Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_901_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_902_logo.png b/tests/common/res/drawable-xhdpi/ch_902_logo.png new file mode 100644 index 00000000..f98b40cb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_902_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_903_logo.png b/tests/common/res/drawable-xhdpi/ch_903_logo.png new file mode 100644 index 00000000..d3d331d8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_903_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_904_logo.png b/tests/common/res/drawable-xhdpi/ch_904_logo.png new file mode 100644 index 00000000..d3e29be6 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_904_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_905_logo.png b/tests/common/res/drawable-xhdpi/ch_905_logo.png new file mode 100644 index 00000000..65ab0adc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_905_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_906_logo.png b/tests/common/res/drawable-xhdpi/ch_906_logo.png new file mode 100644 index 00000000..1cc924d7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_906_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_907_logo.png b/tests/common/res/drawable-xhdpi/ch_907_logo.png new file mode 100644 index 00000000..9fd37e6e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_907_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_908_logo.png b/tests/common/res/drawable-xhdpi/ch_908_logo.png new file mode 100644 index 00000000..15597dba Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_908_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_909_logo.png b/tests/common/res/drawable-xhdpi/ch_909_logo.png new file mode 100644 index 00000000..db80fbef Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_909_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_90_logo.png b/tests/common/res/drawable-xhdpi/ch_90_logo.png new file mode 100644 index 00000000..2f2960da Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_90_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_910_logo.png b/tests/common/res/drawable-xhdpi/ch_910_logo.png new file mode 100644 index 00000000..dd6f8a31 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_910_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_911_logo.png b/tests/common/res/drawable-xhdpi/ch_911_logo.png new file mode 100644 index 00000000..23266b64 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_911_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_912_logo.png b/tests/common/res/drawable-xhdpi/ch_912_logo.png new file mode 100644 index 00000000..677197d5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_912_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_913_logo.png b/tests/common/res/drawable-xhdpi/ch_913_logo.png new file mode 100644 index 00000000..41c4ae03 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_913_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_914_logo.png b/tests/common/res/drawable-xhdpi/ch_914_logo.png new file mode 100644 index 00000000..2a2b4a2d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_914_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_915_logo.png b/tests/common/res/drawable-xhdpi/ch_915_logo.png new file mode 100644 index 00000000..85941edf Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_915_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_916_logo.png b/tests/common/res/drawable-xhdpi/ch_916_logo.png new file mode 100644 index 00000000..fbf4a41e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_916_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_917_logo.png b/tests/common/res/drawable-xhdpi/ch_917_logo.png new file mode 100644 index 00000000..018dff5e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_917_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_918_logo.png b/tests/common/res/drawable-xhdpi/ch_918_logo.png new file mode 100644 index 00000000..2c8b0b80 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_918_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_919_logo.png b/tests/common/res/drawable-xhdpi/ch_919_logo.png new file mode 100644 index 00000000..de2e6073 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_919_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_91_logo.png b/tests/common/res/drawable-xhdpi/ch_91_logo.png new file mode 100644 index 00000000..3992165b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_91_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_920_logo.png b/tests/common/res/drawable-xhdpi/ch_920_logo.png new file mode 100644 index 00000000..33839469 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_920_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_921_logo.png b/tests/common/res/drawable-xhdpi/ch_921_logo.png new file mode 100644 index 00000000..019c36f5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_921_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_922_logo.png b/tests/common/res/drawable-xhdpi/ch_922_logo.png new file mode 100644 index 00000000..79e4e07d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_922_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_923_logo.png b/tests/common/res/drawable-xhdpi/ch_923_logo.png new file mode 100644 index 00000000..6738d938 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_923_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_924_logo.png b/tests/common/res/drawable-xhdpi/ch_924_logo.png new file mode 100644 index 00000000..3cbc4e30 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_924_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_925_logo.png b/tests/common/res/drawable-xhdpi/ch_925_logo.png new file mode 100644 index 00000000..500111c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_925_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_926_logo.png b/tests/common/res/drawable-xhdpi/ch_926_logo.png new file mode 100644 index 00000000..bd3f94ad Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_926_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_927_logo.png b/tests/common/res/drawable-xhdpi/ch_927_logo.png new file mode 100644 index 00000000..51ad2eaa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_927_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_928_logo.png b/tests/common/res/drawable-xhdpi/ch_928_logo.png new file mode 100644 index 00000000..6f579bb3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_928_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_929_logo.png b/tests/common/res/drawable-xhdpi/ch_929_logo.png new file mode 100644 index 00000000..79f11bb1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_929_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_92_logo.png b/tests/common/res/drawable-xhdpi/ch_92_logo.png new file mode 100644 index 00000000..25508036 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_92_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_930_logo.png b/tests/common/res/drawable-xhdpi/ch_930_logo.png new file mode 100644 index 00000000..4c06ad09 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_930_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_931_logo.png b/tests/common/res/drawable-xhdpi/ch_931_logo.png new file mode 100644 index 00000000..b7ac61ff Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_931_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_932_logo.png b/tests/common/res/drawable-xhdpi/ch_932_logo.png new file mode 100644 index 00000000..cac5a477 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_932_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_933_logo.png b/tests/common/res/drawable-xhdpi/ch_933_logo.png new file mode 100644 index 00000000..959f957f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_933_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_934_logo.png b/tests/common/res/drawable-xhdpi/ch_934_logo.png new file mode 100644 index 00000000..e9e5b263 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_934_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_935_logo.png b/tests/common/res/drawable-xhdpi/ch_935_logo.png new file mode 100644 index 00000000..9741307e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_935_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_936_logo.png b/tests/common/res/drawable-xhdpi/ch_936_logo.png new file mode 100644 index 00000000..2c5964de Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_936_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_937_logo.png b/tests/common/res/drawable-xhdpi/ch_937_logo.png new file mode 100644 index 00000000..5392a73c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_937_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_938_logo.png b/tests/common/res/drawable-xhdpi/ch_938_logo.png new file mode 100644 index 00000000..8350910c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_938_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_939_logo.png b/tests/common/res/drawable-xhdpi/ch_939_logo.png new file mode 100644 index 00000000..f0635df0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_939_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_93_logo.png b/tests/common/res/drawable-xhdpi/ch_93_logo.png new file mode 100644 index 00000000..976c07cb Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_93_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_940_logo.png b/tests/common/res/drawable-xhdpi/ch_940_logo.png new file mode 100644 index 00000000..b9c76dbc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_940_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_941_logo.png b/tests/common/res/drawable-xhdpi/ch_941_logo.png new file mode 100644 index 00000000..d9e3361f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_941_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_942_logo.png b/tests/common/res/drawable-xhdpi/ch_942_logo.png new file mode 100644 index 00000000..997643c1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_942_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_943_logo.png b/tests/common/res/drawable-xhdpi/ch_943_logo.png new file mode 100644 index 00000000..4c6f1752 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_943_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_944_logo.png b/tests/common/res/drawable-xhdpi/ch_944_logo.png new file mode 100644 index 00000000..40a11f40 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_944_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_945_logo.png b/tests/common/res/drawable-xhdpi/ch_945_logo.png new file mode 100644 index 00000000..9cc401a7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_945_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_946_logo.png b/tests/common/res/drawable-xhdpi/ch_946_logo.png new file mode 100644 index 00000000..bdb5bab4 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_946_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_947_logo.png b/tests/common/res/drawable-xhdpi/ch_947_logo.png new file mode 100644 index 00000000..541b632d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_947_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_948_logo.png b/tests/common/res/drawable-xhdpi/ch_948_logo.png new file mode 100644 index 00000000..4db04738 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_948_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_949_logo.png b/tests/common/res/drawable-xhdpi/ch_949_logo.png new file mode 100644 index 00000000..399a4e51 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_949_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_94_logo.png b/tests/common/res/drawable-xhdpi/ch_94_logo.png new file mode 100644 index 00000000..e22800b8 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_94_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_950_logo.png b/tests/common/res/drawable-xhdpi/ch_950_logo.png new file mode 100644 index 00000000..5db6be41 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_950_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_951_logo.png b/tests/common/res/drawable-xhdpi/ch_951_logo.png new file mode 100644 index 00000000..6fbf5951 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_951_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_952_logo.png b/tests/common/res/drawable-xhdpi/ch_952_logo.png new file mode 100644 index 00000000..0e5e39aa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_952_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_953_logo.png b/tests/common/res/drawable-xhdpi/ch_953_logo.png new file mode 100644 index 00000000..430a5e4d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_953_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_954_logo.png b/tests/common/res/drawable-xhdpi/ch_954_logo.png new file mode 100644 index 00000000..8ddc6cee Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_954_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_955_logo.png b/tests/common/res/drawable-xhdpi/ch_955_logo.png new file mode 100644 index 00000000..69ec92b5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_955_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_956_logo.png b/tests/common/res/drawable-xhdpi/ch_956_logo.png new file mode 100644 index 00000000..bc1edbd7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_956_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_957_logo.png b/tests/common/res/drawable-xhdpi/ch_957_logo.png new file mode 100644 index 00000000..20db4202 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_957_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_958_logo.png b/tests/common/res/drawable-xhdpi/ch_958_logo.png new file mode 100644 index 00000000..8e9a7a68 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_958_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_959_logo.png b/tests/common/res/drawable-xhdpi/ch_959_logo.png new file mode 100644 index 00000000..4d61c092 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_959_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_95_logo.png b/tests/common/res/drawable-xhdpi/ch_95_logo.png new file mode 100644 index 00000000..50cc5990 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_95_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_960_logo.png b/tests/common/res/drawable-xhdpi/ch_960_logo.png new file mode 100644 index 00000000..b2a54413 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_960_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_961_logo.png b/tests/common/res/drawable-xhdpi/ch_961_logo.png new file mode 100644 index 00000000..5dc31323 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_961_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_962_logo.png b/tests/common/res/drawable-xhdpi/ch_962_logo.png new file mode 100644 index 00000000..4ef2a219 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_962_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_963_logo.png b/tests/common/res/drawable-xhdpi/ch_963_logo.png new file mode 100644 index 00000000..22633d33 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_963_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_964_logo.png b/tests/common/res/drawable-xhdpi/ch_964_logo.png new file mode 100644 index 00000000..ba8ad46c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_964_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_965_logo.png b/tests/common/res/drawable-xhdpi/ch_965_logo.png new file mode 100644 index 00000000..2c935852 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_965_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_966_logo.png b/tests/common/res/drawable-xhdpi/ch_966_logo.png new file mode 100644 index 00000000..8b751988 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_966_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_967_logo.png b/tests/common/res/drawable-xhdpi/ch_967_logo.png new file mode 100644 index 00000000..974712fd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_967_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_968_logo.png b/tests/common/res/drawable-xhdpi/ch_968_logo.png new file mode 100644 index 00000000..d1edc191 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_968_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_969_logo.png b/tests/common/res/drawable-xhdpi/ch_969_logo.png new file mode 100644 index 00000000..7774ed92 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_969_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_96_logo.png b/tests/common/res/drawable-xhdpi/ch_96_logo.png new file mode 100644 index 00000000..e37da468 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_96_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_970_logo.png b/tests/common/res/drawable-xhdpi/ch_970_logo.png new file mode 100644 index 00000000..5dd52457 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_970_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_971_logo.png b/tests/common/res/drawable-xhdpi/ch_971_logo.png new file mode 100644 index 00000000..0aca871c Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_971_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_972_logo.png b/tests/common/res/drawable-xhdpi/ch_972_logo.png new file mode 100644 index 00000000..2f8803b5 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_972_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_973_logo.png b/tests/common/res/drawable-xhdpi/ch_973_logo.png new file mode 100644 index 00000000..35ec5c90 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_973_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_974_logo.png b/tests/common/res/drawable-xhdpi/ch_974_logo.png new file mode 100644 index 00000000..98f9a7aa Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_974_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_975_logo.png b/tests/common/res/drawable-xhdpi/ch_975_logo.png new file mode 100644 index 00000000..bdc31316 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_975_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_976_logo.png b/tests/common/res/drawable-xhdpi/ch_976_logo.png new file mode 100644 index 00000000..078d35b7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_976_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_977_logo.png b/tests/common/res/drawable-xhdpi/ch_977_logo.png new file mode 100644 index 00000000..c6fbb7be Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_977_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_978_logo.png b/tests/common/res/drawable-xhdpi/ch_978_logo.png new file mode 100644 index 00000000..00a6e5e1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_978_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_979_logo.png b/tests/common/res/drawable-xhdpi/ch_979_logo.png new file mode 100644 index 00000000..fd1f56d1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_979_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_97_logo.png b/tests/common/res/drawable-xhdpi/ch_97_logo.png new file mode 100644 index 00000000..f852548a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_97_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_980_logo.png b/tests/common/res/drawable-xhdpi/ch_980_logo.png new file mode 100644 index 00000000..362a1988 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_980_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_981_logo.png b/tests/common/res/drawable-xhdpi/ch_981_logo.png new file mode 100644 index 00000000..9f879c4b Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_981_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_982_logo.png b/tests/common/res/drawable-xhdpi/ch_982_logo.png new file mode 100644 index 00000000..0d945a11 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_982_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_983_logo.png b/tests/common/res/drawable-xhdpi/ch_983_logo.png new file mode 100644 index 00000000..ffeaddfc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_983_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_984_logo.png b/tests/common/res/drawable-xhdpi/ch_984_logo.png new file mode 100644 index 00000000..d8f92211 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_984_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_985_logo.png b/tests/common/res/drawable-xhdpi/ch_985_logo.png new file mode 100644 index 00000000..b6c6f0dc Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_985_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_986_logo.png b/tests/common/res/drawable-xhdpi/ch_986_logo.png new file mode 100644 index 00000000..8e2c0b50 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_986_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_987_logo.png b/tests/common/res/drawable-xhdpi/ch_987_logo.png new file mode 100644 index 00000000..ce8c107a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_987_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_988_logo.png b/tests/common/res/drawable-xhdpi/ch_988_logo.png new file mode 100644 index 00000000..ffa8ded7 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_988_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_989_logo.png b/tests/common/res/drawable-xhdpi/ch_989_logo.png new file mode 100644 index 00000000..8522a03a Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_989_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_98_logo.png b/tests/common/res/drawable-xhdpi/ch_98_logo.png new file mode 100644 index 00000000..13fe760f Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_98_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_990_logo.png b/tests/common/res/drawable-xhdpi/ch_990_logo.png new file mode 100644 index 00000000..bd6df061 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_990_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_991_logo.png b/tests/common/res/drawable-xhdpi/ch_991_logo.png new file mode 100644 index 00000000..8611d57d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_991_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_992_logo.png b/tests/common/res/drawable-xhdpi/ch_992_logo.png new file mode 100644 index 00000000..36d26bc0 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_992_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_993_logo.png b/tests/common/res/drawable-xhdpi/ch_993_logo.png new file mode 100644 index 00000000..f67e0eec Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_993_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_994_logo.png b/tests/common/res/drawable-xhdpi/ch_994_logo.png new file mode 100644 index 00000000..a63d5ee1 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_994_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_995_logo.png b/tests/common/res/drawable-xhdpi/ch_995_logo.png new file mode 100644 index 00000000..b7b45167 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_995_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_996_logo.png b/tests/common/res/drawable-xhdpi/ch_996_logo.png new file mode 100644 index 00000000..82c042c3 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_996_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_997_logo.png b/tests/common/res/drawable-xhdpi/ch_997_logo.png new file mode 100644 index 00000000..d70e4793 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_997_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_998_logo.png b/tests/common/res/drawable-xhdpi/ch_998_logo.png new file mode 100644 index 00000000..f6c69d2e Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_998_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_999_logo.png b/tests/common/res/drawable-xhdpi/ch_999_logo.png new file mode 100644 index 00000000..844c06fd Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_999_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_99_logo.png b/tests/common/res/drawable-xhdpi/ch_99_logo.png new file mode 100644 index 00000000..d8be447d Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_99_logo.png differ diff --git a/tests/common/res/drawable-xhdpi/ch_9_logo.png b/tests/common/res/drawable-xhdpi/ch_9_logo.png new file mode 100644 index 00000000..8ba6bc55 Binary files /dev/null and b/tests/common/res/drawable-xhdpi/ch_9_logo.png differ diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/ChannelInfo.java index af1c9891..946c0b55 100644 --- a/tests/common/src/com/android/tv/testing/ChannelInfo.java +++ b/tests/common/src/com/android/tv/testing/ChannelInfo.java @@ -16,9 +16,11 @@ package com.android.tv.testing; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.media.tv.TvContract; +import android.net.Uri; import android.support.annotation.Nullable; import android.util.SparseArray; @@ -38,11 +40,6 @@ public final class ChannelInfo { VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P); } - /** - * If this is specify for logo, it will be selected randomly including null. - */ - public static final String GENERATE_LOGO = "GEN"; - public static final String[] PROJECTION = { TvContract.Channels.COLUMN_DISPLAY_NUMBER, TvContract.Channels.COLUMN_DISPLAY_NAME, @@ -80,15 +77,20 @@ public final class ChannelInfo { .setOriginalNetworkId(channelNumber); if (context != null) { // tests/input/tools/get_test_logos.sh only stores 1000 logos. - int logo_num = (channelNumber % 1000); - builder.setLogoUrl( - "android.resource://com.android.tv.testinput/drawable/ch_" + logo_num - + "_logo" - ); + builder.setLogoUrl(getUriStringForChannelLogo(context, channelNumber)); } return builder.build(); } + public static String getUriStringForChannelLogo(Context context, int logoIndex) { + int index = (logoIndex % 1000) + 1; + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(context.getPackageName()) + .path("drawable") + .appendPath("ch_" + index + "_logo").build().toString(); + } + public static ChannelInfo fromCursor(Cursor c) { // TODO: Fill other fields. Builder builder = new Builder(); diff --git a/tests/common/src/com/android/tv/testing/Utils.java b/tests/common/src/com/android/tv/testing/Utils.java index 66a13466..b2b4036e 100644 --- a/tests/common/src/com/android/tv/testing/Utils.java +++ b/tests/common/src/com/android/tv/testing/Utils.java @@ -19,16 +19,15 @@ package com.android.tv.testing; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.net.Uri; -import android.os.Looper; import android.util.Log; import com.android.tv.common.TvCommonUtils; -import com.android.tv.util.MainThreadExecutor; import java.io.IOException; import java.io.InputStream; @@ -37,8 +36,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; /** * An utility class for testing. @@ -101,27 +98,6 @@ public final class Utils { return new Random(DEFAULT_RANDOM_SEED); } - /** - * Executes a call on the main thread, blocking until it is completed. - * - *

Useful for doing things that are not thread-safe, such as looking at or modifying the view - * hierarchy. - * - * @param runnable The code to run on the main thread. - */ - public static void runOnMainSync(Runnable runnable) { - if (Looper.myLooper() == Looper.getMainLooper()) { - runnable.run(); - } else { - Future temp = MainThreadExecutor.getInstance().submit(runnable); - try { - temp.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - } - private static long getSeed() { // Set random seed as the date to track failed test data easily. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); @@ -131,4 +107,16 @@ public final class Utils { } private Utils() {} + + /** + * Checks whether TvActivity is enabled or not. + */ + public static boolean isTvActivityEnabled(Context context) { + PackageManager pm = context.getPackageManager(); + ComponentName name = new ComponentName("com.android.tv", + "com.android.tv.TvActivity"); + int enabled = pm.getComponentEnabledSetting(name); + return enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } } diff --git a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java index b9def95e..a9bfa97a 100644 --- a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java +++ b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java @@ -16,7 +16,7 @@ package com.android.tv.testing.dvr; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import junit.framework.Assert; diff --git a/tests/common/src/com/android/tv/testing/uihelper/Constants.java b/tests/common/src/com/android/tv/testing/uihelper/Constants.java index 8f607fbf..8dd8e14a 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/Constants.java +++ b/tests/common/src/com/android/tv/testing/uihelper/Constants.java @@ -23,6 +23,7 @@ public final class Constants { public static final double EXTRA_TIMEOUT_PERCENT = .05; public static final int MIN_EXTRA_TIMEOUT = 10; public static final long MAX_SHOW_DELAY_MILLIS = 200; + public static final long MAX_FOCUSED_DELAY_MILLIS = 1000; public static final String TV_APP_PACKAGE = "com.android.tv"; public static final BySelector TV_VIEW = By.res(TV_APP_PACKAGE, "main_tunable_tv_view"); public static final BySelector CHANNEL_BANNER = By.res(TV_APP_PACKAGE, "channel_banner_view"); @@ -30,6 +31,8 @@ public final class Constants { public static final BySelector MENU = By.res(TV_APP_PACKAGE, "menu"); public static final BySelector SIDE_PANEL = By.res(TV_APP_PACKAGE, "side_panel"); public static final BySelector PROGRAM_GUIDE = By.res(TV_APP_PACKAGE, "program_guide"); + public static final BySelector DVR_LIBRARY = By.res(TV_APP_PACKAGE, "dvr_frame"); + public static final BySelector DVR_SCHEDULES = By.res(TV_APP_PACKAGE, "dvr_schedules"); public static final BySelector FOCUSED_VIEW = By.focused(true); private Constants() { diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java index 6757cf01..1dc0f020 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java @@ -1,6 +1,7 @@ package com.android.tv.testing.uihelper; import static com.android.tv.testing.uihelper.UiDeviceAsserts.waitForCondition; +import static junit.framework.TestCase.assertTrue; import android.content.Context; import android.content.Intent; @@ -11,6 +12,8 @@ import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.util.Log; +import com.android.tv.testing.Utils; + import junit.framework.Assert; /** @@ -29,6 +32,7 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper { } public void assertAppStarted() { + assertTrue("TvActivity should be enabled.", Utils.isTvActivityEnabled(mContext)); Intent intent = mContext.getPackageManager() .getLaunchIntentForPackage(Constants.TV_APP_PACKAGE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances @@ -48,4 +52,11 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper { mUiDevice.pressBack(); } } + + public void assertAppStopped() { + while(mUiDevice.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0))) { + mUiDevice.pressBack(); + mUiDevice.waitForIdle(); + } + } } \ No newline at end of file diff --git a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java index ea5360a3..80d53242 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java @@ -139,6 +139,11 @@ public class MenuHelper extends BaseUiDeviceHelper { R.string.channels_item_program_guide); } + public UiObject2 assertPressDvrLibrary() { + return assertPressMenuItem(R.string.menu_title_channels, + R.string.channels_item_dvr); + } + /** * Navigate to the menu item with the text {@code itemTextResId} in the row with text * {@code rowTitleResId}. @@ -171,7 +176,11 @@ public class MenuHelper extends BaseUiDeviceHelper { public void showMenu() { if (!mUiDevice.hasObject(MENU)) { mUiDevice.pressMenu(); - UiDeviceAsserts.assertWaitForCondition(mUiDevice, Until.hasObject(MENU)); + if (!UiDeviceAsserts.waitForCondition(mUiDevice, Until.hasObject(MENU))) { + // Sometimes animations might block menu key, try again to make sure it's received. + mUiDevice.pressMenu(); + UiDeviceAsserts.assertWaitForCondition(mUiDevice, Until.hasObject(MENU)); + } } } } diff --git a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java index 2d4f9b2f..98a19a41 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java @@ -47,17 +47,23 @@ public class SidePanelHelper extends BaseUiDeviceHelper { } public UiObject2 assertNavigateToItem(int resId) { - String title = mTargetResources.getString(resId); - return assertNavigateToItem(title); + return assertNavigateToItem(resId, Direction.DOWN); } + public UiObject2 assertNavigateToItem(int resId, Direction direction) { + String title = mTargetResources.getString(resId); + return assertNavigateToItem(title, direction); + } public UiObject2 assertNavigateToItem(String title) { + return assertNavigateToItem(title, Direction.DOWN); + } + + public UiObject2 assertNavigateToItem(String title, Direction direction) { BySelector sidePanelSelector = ByResource.id(mTargetResources, R.id.side_panel_list); UiObject2 sidePanelList = mUiDevice.findObject(sidePanelSelector); Assert.assertNotNull(sidePanelSelector + " not found", sidePanelList); - return UiDeviceAsserts - .assertNavigateTo(mUiDevice, sidePanelList, By.hasDescendant(By.text(title)), - Direction.DOWN); + return UiDeviceAsserts.assertNavigateTo(mUiDevice, sidePanelList, + By.hasDescendant(By.text(title)), direction); } } diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java index ea9b5460..c096d7d2 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java @@ -17,6 +17,7 @@ package com.android.tv.testing.uihelper; import static com.android.tv.testing.uihelper.Constants.FOCUSED_VIEW; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import android.support.test.uiautomator.By; @@ -38,6 +39,12 @@ public final class UiDeviceAsserts { assertEquals("Has " + bySelector, expected, uiDevice.hasObject(bySelector)); } + public static void assertWaitUntilFocused(UiDevice uiDevice, BySelector bySelector) { + UiObject2 uiObject = uiDevice.findObject(bySelector); + assertNotNull(uiObject); + assertTrue(uiObject.wait(Until.focused(true), Constants.MAX_FOCUSED_DELAY_MILLIS)); + } + /** * Assert that {@code searchCondition} becomes true within * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds. diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java index 577559c2..98eff906 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java @@ -15,8 +15,16 @@ */ package com.android.tv.testing.uihelper; +import static junit.framework.Assert.assertTrue; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.Build; +import android.os.SystemClock; +import android.support.test.uiautomator.Configurator; import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiDevice; +import android.view.InputDevice; import android.view.KeyEvent; /** @@ -51,7 +59,7 @@ public final class UiDeviceUtils { } /** - * Parses the string and sends the corresponding individual key preses. + * Parses the string and sends the corresponding individual key presses. *

* Note: only handles 0-9, '.', and '-'. */ @@ -69,6 +77,59 @@ public final class UiDeviceUtils { } } + /** + * Sends the DPAD Center key presses with the {@code repeat} count. + * TODO: Remove instrumentation argument once migrated to JUnit4. + */ + public static void pressDPadCenter(Instrumentation instrumentation, int repeat) { + pressKey(instrumentation, KeyEvent.KEYCODE_DPAD_CENTER, repeat); + } + + private static void pressKey(Instrumentation instrumentation, int keyCode, int repeat) { + UiDevice.getInstance(instrumentation).waitForIdle(); + for (int i = 0; i < repeat; ++i) { + assertPressKeyDown(instrumentation, keyCode, false); + if (i < repeat - 1) { + assertPressKeyUp(instrumentation, keyCode, false); + } + } + // Send last key event synchronously. + assertPressKeyUp(instrumentation, keyCode, true); + } + + private static void assertPressKeyDown(Instrumentation instrumentation, int keyCode, + boolean sync) { + assertPressKey(instrumentation, KeyEvent.ACTION_DOWN, keyCode, sync); + } + + private static void assertPressKeyUp(Instrumentation instrumentation, int keyCode, + boolean sync) { + assertPressKey(instrumentation, KeyEvent.ACTION_UP, keyCode, sync); + } + + private static void assertPressKey(Instrumentation instrumentation, int action, int keyCode, + boolean sync) { + long eventTime = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(eventTime, eventTime, action, keyCode, 0, 0, -1, 0, 0, + InputDevice.SOURCE_KEYBOARD); + assertTrue("Failed to inject key up event:" + event, + injectEvent(instrumentation, event, sync)); + } + + private static boolean injectEvent(Instrumentation instrumentation, KeyEvent event, + boolean sync) { + return getUiAutomation(instrumentation).injectInputEvent(event, sync); + } + + private static UiAutomation getUiAutomation(Instrumentation instrumentation) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + int flags = Configurator.getInstance().getUiAutomationFlags(); + return instrumentation.getUiAutomation(flags); + } else { + return instrumentation.getUiAutomation(); + } + } + private UiDeviceUtils() { } } diff --git a/tests/func/Android.mk b/tests/func/Android.mk index b0c5ad1c..e89ba25b 100644 --- a/tests/func/Android.mk +++ b/tests/func/Android.mk @@ -14,8 +14,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ tv-test-common \ ub-uiautomator \ -LOCAL_JAVA_LIBRARIES := legacy-android-test - LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_SDK_VERSION := current diff --git a/tests/func/OWNERS b/tests/func/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/tests/func/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java index e4f612e3..cfa5eda7 100644 --- a/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java +++ b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java @@ -23,6 +23,7 @@ import android.support.test.uiautomator.Until; import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; +import com.android.tv.testing.uihelper.UiDeviceUtils; /** * Tests for channel sources. @@ -60,8 +61,7 @@ public class ChannelSourcesTest extends LiveChannelsTestCase { assertWaitForCondition(mDevice, Until.hasObject(mBySettingsSidePanel)); mSidePanelHelper.assertNavigateToItem(R.string.settings_channel_source_item_setup); - mDevice.pressDPadCenter(); - mDevice.pressDPadCenter(); + UiDeviceUtils.pressDPadCenter(getInstrumentation(), 2); assertWaitForCondition(mDevice, Until.hasObject(ByResource.text(mTargetResources, R.string.setup_sources_text))); diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java index 25c7909b..e306e6c6 100644 --- a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java +++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java @@ -73,6 +73,8 @@ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { .hasObject(Constants.PROGRAM_GUIDE)) { mDevice.pressBack(); } + // To destroy the activity to make sure next test case's activity launch check works well. + mDevice.pressBack(); super.tearDown(); } diff --git a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java index bbc7aa81..82c6a810 100644 --- a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java +++ b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java @@ -16,11 +16,11 @@ package com.android.tv.tests.ui; +import static com.android.tv.testing.uihelper.Constants.CHANNEL_BANNER; import static com.android.tv.testing.uihelper.Constants.FOCUSED_VIEW; import static com.android.tv.testing.uihelper.Constants.MENU; import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition; -import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiObject2; @@ -32,9 +32,8 @@ import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.testing.uihelper.DialogHelper; @SmallTest -@SdkSuppress(minSdkVersion = 23) public class PlayControlsRowViewTest extends LiveChannelsTestCase { - private static final int BUTTON_INDEX_PLAY_PAUSE = 2; + private static final String BUTTON_ID_PLAY_PAUSE = "com.android.tv:id/play_pause"; private BySelector mBySettingsSidePanel; @@ -42,7 +41,9 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { protected void setUp() throws Exception { super.setUp(); mLiveChannelsHelper.assertAppStarted(); - pressKeysForChannel(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY); + pressKeysForChannel(TvTestInputConstants.CH_2); + // Wait until KeypadChannelSwitchView closes. + assertWaitForCondition(mDevice, Until.hasObject(CHANNEL_BANNER)); // Tune to a new channel to ensure that the channel is changed. mDevice.pressDPadUp(); getInstrumentation().waitForIdleSync(); @@ -56,7 +57,7 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { public void testFocusedViewInNormalCase() { mMenuHelper.showMenu(); mMenuHelper.assertNavigateToPlayControlsRow(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); mDevice.pressBack(); } @@ -69,49 +70,30 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { // Fast forward button mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); mDevice.pressBack(); // Next button mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT); mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); - mDevice.pressBack(); - } - - /** - * Tests the case when the rewinding action is disabled. - * In this case, the button corresponding to the action is disabled, so play/pause button should - * have the focus. - */ - public void testFocusedViewWithDisabledActionBackward() { - // Previous button - mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS); - mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); - mDevice.pressBack(); - - // Rewind button - mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_REWIND); - mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); mDevice.pressBack(); } public void testFocusedViewInMenu() { mMenuHelper.showMenu(); mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); mMenuHelper.assertNavigateToRow(R.string.menu_title_channels); mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); } public void testKeepPausedWhileParentalControlChange() { // Pause the playback. mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE); mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); // Show parental controls fragment. mMenuHelper.assertPressOptionsSettings(); assertWaitForCondition(mDevice, Until.hasObject(mBySettingsSidePanel)); @@ -130,14 +112,14 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { mDevice.pressBack(); // Return to the main menu. mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); } public void testKeepPausedAfterVisitingHome() { // Pause the playback. mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE); mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); // Press HOME twice to visit the home screen and return to Live TV. mDevice.pressHome(); // Wait until home screen is shown. @@ -147,19 +129,15 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { mDevice.waitForIdle(); // Return to the main menu. mMenuHelper.assertWaitForMenu(); - assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE); + assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); } - private void assertButtonHasFocus(int expectedButtonIndex) { + private void assertButtonHasFocus(String buttonId) { UiObject2 menu = mDevice.findObject(MENU); UiObject2 focusedView = menu.findObject(FOCUSED_VIEW); assertNotNull("Play controls row doesn't have a focused child.", focusedView); UiObject2 focusedButtonGroup = focusedView.getParent(); assertNotNull("The focused item should have parent", focusedButtonGroup); - UiObject2 controlBar = focusedButtonGroup.getParent(); - assertNotNull("The focused item should have grandparent", controlBar); - assertTrue("The grandparent should have more than five children", - controlBar.getChildCount() >= 5); - assertEquals(controlBar.getChildren().get(expectedButtonIndex), focusedButtonGroup); + assertEquals(buttonId, focusedButtonGroup.getResourceName()); } } diff --git a/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java b/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java deleted file mode 100644 index 95921df9..00000000 --- a/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.tests.ui; - -import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition; - -import android.os.SystemClock; -import android.support.test.filters.LargeTest; -import android.support.test.uiautomator.Until; -import android.util.Log; - -import com.android.tv.R; -import com.android.tv.testing.uihelper.Constants; - -/** - * Tests for {@link com.android.tv.MainActivity}. - */ -@LargeTest -public class ProgramGuidePerformanceTest extends LiveChannelsTestCase { - private static final String TAG = "ProgramGuidePerformance"; - - public static final int SHOW_MENU_MAX_DURATION_MS = 1500; - public void testShowMenu() { - mLiveChannelsHelper.assertAppStarted(); - mMenuHelper.showMenu(); - mMenuHelper.assertNavigateToMenuItem(R.string.menu_title_channels, - R.string.channels_item_program_guide); - //TODO: build a simple performance framework like JankTest - long start = SystemClock.elapsedRealtime(); - Log.v(TAG, "start " + start + " milliSeconds"); - mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE)); - long end = SystemClock.elapsedRealtime(); - Log.v(TAG, "end " + end + " milliSeconds"); - long duration = end - start; - assertDuration("ShowMenu", SHOW_MENU_MAX_DURATION_MS, duration); - mDevice.pressBack(); - } - - private void assertDuration(String msg, long expectedMaxMilliSeconds, long actualMilliSeconds) { - Log.d(TAG, msg + " duration " + actualMilliSeconds + " milliSeconds"); - assertTrue(msg + " duration expected to be <= " + expectedMaxMilliSeconds - + " milliSeconds but was " + actualMilliSeconds + " milliSeconds.", - actualMilliSeconds <= expectedMaxMilliSeconds); - } -} diff --git a/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java b/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java new file mode 100644 index 00000000..d88e67ad --- /dev/null +++ b/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tests.ui.dvr; + +import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertHas; +import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition; +import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitUntilFocused; + +import android.os.Build; +import android.support.test.filters.MediumTest; +import android.support.test.filters.SdkSuppress; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; + +import com.android.tv.R; +import com.android.tv.testing.uihelper.ByResource; +import com.android.tv.testing.uihelper.Constants; +import com.android.tv.tests.ui.LiveChannelsTestCase; + +import java.util.regex.Pattern; + +@MediumTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class DvrLibraryTest extends LiveChannelsTestCase { + private static final String PROGRAM_NAME_PREFIX = "Title("; + + private BySelector mRecentRow; + private BySelector mScheduledRow; + private BySelector mSeriesRow; + private BySelector mFullScheduleCard; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mRecentRow = By.hasDescendant(ByResource.text(mTargetResources, R.string.dvr_main_recent)); + mScheduledRow = By.hasDescendant( + ByResource.text(mTargetResources, R.string.dvr_main_scheduled)); + mSeriesRow = By.hasDescendant(ByResource.text(mTargetResources, R.string.dvr_main_series)); + mFullScheduleCard = By.focusable(true).hasDescendant( + ByResource.text(mTargetResources, R.string.dvr_full_schedule_card_view_title)); + mLiveChannelsHelper.assertAppStarted(); + } + + public void testCancel() { + mMenuHelper.assertPressDvrLibrary(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, Constants.MENU, false); + } + + public void testEmptyLibrary() { + mMenuHelper.assertPressDvrLibrary(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + + // DVR Library is empty, only Scheduled row and Full schedule card should be displayed. + assertHas(mDevice, mRecentRow, false); + assertHas(mDevice, mScheduledRow, true); + assertHas(mDevice, mSeriesRow, false); + + mDevice.pressDPadCenter(); + assertWaitUntilFocused(mDevice, mFullScheduleCard); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + + // Empty schedules screen should be shown. + assertHas(mDevice, Constants.DVR_SCHEDULES, true); + assertHas(mDevice, ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), + true); + + // Close the DVR library. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + } + + public void testScheduleRecordings() { + BySelector newScheduleCard = By.focusable(true).hasDescendant( + By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.textEndsWith("today")); + BySelector seriesCardWithOneSchedule = By.focusable(true).hasDescendant( + By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.text(mTargetResources + .getQuantityString(R.plurals.dvr_count_scheduled_recordings, 1, 1))); + BySelector seriesCardWithOneRecordedProgram = By.focusable(true).hasDescendant( + By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.text(mTargetResources + .getQuantityString(R.plurals.dvr_count_new_recordings, 1, 1))); + Pattern watchButton = Pattern.compile("^" + mTargetResources + .getString(R.string.dvr_detail_watch).toUpperCase() + "\n.*$"); + + mMenuHelper.showMenu(); + mMenuHelper.assertNavigateToPlayControlsRow(); + mDevice.pressDPadRight(); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.hasObject( + ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone( + ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); + + mMenuHelper.assertPressDvrLibrary(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + + // Schedule should be automatically added to the series. + assertHas(mDevice, mRecentRow, false); + assertHas(mDevice, mScheduledRow, true); + assertHas(mDevice, mSeriesRow, true); + String programName = mDevice.findObject(By.textStartsWith(PROGRAM_NAME_PREFIX)).getText(); + + // Move to scheduled row, there should be one new schedule and one full schedule card. + mDevice.pressDPadRight(); + assertWaitUntilFocused(mDevice, newScheduleCard); + mDevice.pressDPadRight(); + assertWaitUntilFocused(mDevice, mFullScheduleCard); + + // Enters the full schedule, there should be one schedule in the full schedule. + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, Constants.DVR_SCHEDULES, true); + assertHas(mDevice, ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), + false); + assertHas(mDevice, By.textStartsWith(programName), true); + + // Moves to the series card, clicks it, the detail page should be shown with "View schedule" + // button. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + mDevice.pressDPadLeft(); + assertWaitUntilFocused(mDevice, newScheduleCard); + mDevice.pressDPadDown(); + assertWaitUntilFocused(mDevice, seriesCardWithOneSchedule); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_view_schedule).toUpperCase()), true); + assertHas(mDevice, By.text(watchButton), false); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_series_delete).toUpperCase()), false); + + // Clicks the new schedule, the detail page should be shown with "Stop recording" button. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + assertWaitUntilFocused(mDevice, seriesCardWithOneSchedule); + mDevice.pressDPadUp(); + assertWaitUntilFocused(mDevice, newScheduleCard); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_stop_recording).toUpperCase()), true); + + // Stops the recording + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.hasObject( + ByResource.text(mTargetResources, R.string.dvr_action_stop))); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone( + ByResource.text(mTargetResources, R.string.dvr_action_stop))); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + assertWaitUntilFocused(mDevice, mFullScheduleCard); + + // Moves to series' detail page again, now it should have two more buttons + mDevice.pressDPadDown(); + assertWaitUntilFocused(mDevice, seriesCardWithOneRecordedProgram); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, By.text(watchButton), true); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_view_schedule).toUpperCase()), true); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_series_delete).toUpperCase()), true); + + // Moves to the recent row and clicks the recent recorded program. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + assertWaitUntilFocused(mDevice, seriesCardWithOneRecordedProgram); + mDevice.pressDPadUp(); + assertWaitUntilFocused(mDevice, mFullScheduleCard); + mDevice.pressDPadUp(); + assertWaitUntilFocused(mDevice, By.focusable(true).hasDescendant(By.text(programName))); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_watch).toUpperCase()), true); + assertHas(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_delete).toUpperCase()), true); + + // Moves to the delete button and clicks to remove the recorded program. + mDevice.pressDPadRight(); + assertWaitUntilFocused(mDevice, By.text(mTargetResources + .getString(R.string.dvr_detail_delete).toUpperCase())); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); + assertWaitUntilFocused(mDevice, mFullScheduleCard); + + // DVR Library should be empty now. + assertHas(mDevice, mRecentRow, false); + assertHas(mDevice, mScheduledRow, true); + assertHas(mDevice, mSeriesRow, false); + + // Close the DVR library. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); + } +} diff --git a/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java b/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java new file mode 100644 index 00000000..deeb9bfd --- /dev/null +++ b/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.tests.ui.sidepanel; + +import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition; + +import android.graphics.Point; +import android.support.test.filters.LargeTest; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.Direction; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; + +import com.android.tv.R; +import com.android.tv.testing.uihelper.Constants; +import com.android.tv.tests.ui.LiveChannelsTestCase; + +@LargeTest +public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { + private BySelector mBySettingsSidePanel; + private UiObject2 mTvView; + private Point mNormalTvViewCenter; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mLiveChannelsHelper.assertAppStarted(); + mTvView = mDevice.findObject(Constants.TV_VIEW); + mNormalTvViewCenter = mTvView.getVisibleCenter(); + assertNotNull(mNormalTvViewCenter); + pressKeysForChannel(com.android.tv.testing.testinput.TvTestInputConstants.CH_2); + // Wait until KeypadChannelSwitchView closes. + assertWaitForCondition(mDevice, Until.hasObject(Constants.CHANNEL_BANNER)); + mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_settings); + } + + private void assertShrunkenTvView(boolean shrunkenExpected) { + Point currentTvViewCenter = mTvView.getVisibleCenter(); + if (shrunkenExpected) { + assertFalse(mNormalTvViewCenter.equals(currentTvViewCenter)); + } else { + assertTrue(mNormalTvViewCenter.equals(currentTvViewCenter)); + } + } + + public void testCustomizeChannelList_noraml() { + // Show customize channel list fragment + mMenuHelper.assertPressOptionsSettings(); + assertWaitForCondition(mDevice, Until.hasObject(mBySettingsSidePanel)); + mSidePanelHelper.assertNavigateToItem( + R.string.settings_channel_source_item_customize_channels); + mDevice.pressDPadCenter(); + BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); + assertShrunkenTvView(true); + + // Show group by fragment + mSidePanelHelper.assertNavigateToItem(R.string.edit_channels_item_group_by, Direction.UP); + mDevice.pressDPadCenter(); + bySidePanel = mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_group_by); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); + assertShrunkenTvView(true); + + // Back to customize channel list fragment + mDevice.pressBack(); + bySidePanel = mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); + assertShrunkenTvView(true); + + // Return to the main menu. + mDevice.pressBack(); + assertWaitForCondition(mDevice, Until.hasObject(mBySettingsSidePanel)); + assertShrunkenTvView(false); + } + + public void testCustomizeChannelList_timeout() { + // Show customize channel list fragment + mMenuHelper.assertPressOptionsSettings(); + assertWaitForCondition(mDevice, Until.hasObject(mBySettingsSidePanel)); + mSidePanelHelper.assertNavigateToItem( + R.string.settings_channel_source_item_customize_channels); + mDevice.pressDPadCenter(); + BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); + assertShrunkenTvView(true); + + // Show group by fragment + mSidePanelHelper.assertNavigateToItem(R.string.edit_channels_item_group_by, Direction.UP); + mDevice.pressDPadCenter(); + bySidePanel = mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_group_by); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); + assertShrunkenTvView(true); + + // Wait for time-out to return to the main menu. + assertWaitForCondition(mDevice, Until.gone(bySidePanel), + mTargetResources.getInteger(R.integer.side_panel_show_duration)); + assertShrunkenTvView(false); + } +} diff --git a/tests/input/OWNERS b/tests/input/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/tests/input/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/tests/input/res/drawable-xhdpi/ch_1000_logo.png b/tests/input/res/drawable-xhdpi/ch_1000_logo.png deleted file mode 100644 index eec6d373..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_1000_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_100_logo.png b/tests/input/res/drawable-xhdpi/ch_100_logo.png deleted file mode 100644 index 3a8b6731..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_100_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_101_logo.png b/tests/input/res/drawable-xhdpi/ch_101_logo.png deleted file mode 100644 index 2977ef1d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_101_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_102_logo.png b/tests/input/res/drawable-xhdpi/ch_102_logo.png deleted file mode 100644 index 978112e1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_102_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_103_logo.png b/tests/input/res/drawable-xhdpi/ch_103_logo.png deleted file mode 100644 index ceb1fd6a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_103_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_104_logo.png b/tests/input/res/drawable-xhdpi/ch_104_logo.png deleted file mode 100644 index c927568d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_104_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_105_logo.png b/tests/input/res/drawable-xhdpi/ch_105_logo.png deleted file mode 100644 index 8e1be19d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_105_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_106_logo.png b/tests/input/res/drawable-xhdpi/ch_106_logo.png deleted file mode 100644 index a19c9ef8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_106_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_107_logo.png b/tests/input/res/drawable-xhdpi/ch_107_logo.png deleted file mode 100644 index 9d36a488..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_107_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_108_logo.png b/tests/input/res/drawable-xhdpi/ch_108_logo.png deleted file mode 100644 index 700ae189..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_108_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_109_logo.png b/tests/input/res/drawable-xhdpi/ch_109_logo.png deleted file mode 100644 index 209e3b47..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_109_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_10_logo.png b/tests/input/res/drawable-xhdpi/ch_10_logo.png deleted file mode 100644 index 76b3a9b2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_10_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_110_logo.png b/tests/input/res/drawable-xhdpi/ch_110_logo.png deleted file mode 100644 index 0c0c1920..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_110_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_111_logo.png b/tests/input/res/drawable-xhdpi/ch_111_logo.png deleted file mode 100644 index 07c7ee83..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_111_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_112_logo.png b/tests/input/res/drawable-xhdpi/ch_112_logo.png deleted file mode 100644 index 1675e54d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_112_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_113_logo.png b/tests/input/res/drawable-xhdpi/ch_113_logo.png deleted file mode 100644 index 57cc81ce..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_113_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_114_logo.png b/tests/input/res/drawable-xhdpi/ch_114_logo.png deleted file mode 100644 index 3d57f201..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_114_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_115_logo.png b/tests/input/res/drawable-xhdpi/ch_115_logo.png deleted file mode 100644 index 3897f5c9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_115_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_116_logo.png b/tests/input/res/drawable-xhdpi/ch_116_logo.png deleted file mode 100644 index 83a55a67..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_116_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_117_logo.png b/tests/input/res/drawable-xhdpi/ch_117_logo.png deleted file mode 100644 index 4b4eccf0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_117_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_118_logo.png b/tests/input/res/drawable-xhdpi/ch_118_logo.png deleted file mode 100644 index 4a682f67..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_118_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_119_logo.png b/tests/input/res/drawable-xhdpi/ch_119_logo.png deleted file mode 100644 index 2a2aed5e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_119_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_11_logo.png b/tests/input/res/drawable-xhdpi/ch_11_logo.png deleted file mode 100644 index 62268929..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_11_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_120_logo.png b/tests/input/res/drawable-xhdpi/ch_120_logo.png deleted file mode 100644 index 46c5f97a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_120_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_121_logo.png b/tests/input/res/drawable-xhdpi/ch_121_logo.png deleted file mode 100644 index 650bd3e4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_121_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_122_logo.png b/tests/input/res/drawable-xhdpi/ch_122_logo.png deleted file mode 100644 index 5a3c5d7b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_122_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_123_logo.png b/tests/input/res/drawable-xhdpi/ch_123_logo.png deleted file mode 100644 index ade9ab29..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_123_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_124_logo.png b/tests/input/res/drawable-xhdpi/ch_124_logo.png deleted file mode 100644 index 62d15c06..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_124_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_125_logo.png b/tests/input/res/drawable-xhdpi/ch_125_logo.png deleted file mode 100644 index 34af08a9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_125_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_126_logo.png b/tests/input/res/drawable-xhdpi/ch_126_logo.png deleted file mode 100644 index 8d10d163..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_126_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_127_logo.png b/tests/input/res/drawable-xhdpi/ch_127_logo.png deleted file mode 100644 index 428f8e0d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_127_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_128_logo.png b/tests/input/res/drawable-xhdpi/ch_128_logo.png deleted file mode 100644 index 536e04fa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_128_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_129_logo.png b/tests/input/res/drawable-xhdpi/ch_129_logo.png deleted file mode 100644 index 79fc8dc8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_129_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_12_logo.png b/tests/input/res/drawable-xhdpi/ch_12_logo.png deleted file mode 100644 index 5f155f41..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_12_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_130_logo.png b/tests/input/res/drawable-xhdpi/ch_130_logo.png deleted file mode 100644 index b2bc041f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_130_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_131_logo.png b/tests/input/res/drawable-xhdpi/ch_131_logo.png deleted file mode 100644 index 06081906..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_131_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_132_logo.png b/tests/input/res/drawable-xhdpi/ch_132_logo.png deleted file mode 100644 index 18a0bdef..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_132_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_133_logo.png b/tests/input/res/drawable-xhdpi/ch_133_logo.png deleted file mode 100644 index 312027b2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_133_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_134_logo.png b/tests/input/res/drawable-xhdpi/ch_134_logo.png deleted file mode 100644 index c551922e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_134_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_135_logo.png b/tests/input/res/drawable-xhdpi/ch_135_logo.png deleted file mode 100644 index 64d7b889..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_135_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_136_logo.png b/tests/input/res/drawable-xhdpi/ch_136_logo.png deleted file mode 100644 index 31021239..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_136_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_137_logo.png b/tests/input/res/drawable-xhdpi/ch_137_logo.png deleted file mode 100644 index a7f8cfb8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_137_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_138_logo.png b/tests/input/res/drawable-xhdpi/ch_138_logo.png deleted file mode 100644 index 981425f0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_138_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_139_logo.png b/tests/input/res/drawable-xhdpi/ch_139_logo.png deleted file mode 100644 index 03170e5e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_139_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_13_logo.png b/tests/input/res/drawable-xhdpi/ch_13_logo.png deleted file mode 100644 index 817922f8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_13_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_140_logo.png b/tests/input/res/drawable-xhdpi/ch_140_logo.png deleted file mode 100644 index f26cf917..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_140_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_141_logo.png b/tests/input/res/drawable-xhdpi/ch_141_logo.png deleted file mode 100644 index 0064d436..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_141_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_142_logo.png b/tests/input/res/drawable-xhdpi/ch_142_logo.png deleted file mode 100644 index 1d28785e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_142_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_143_logo.png b/tests/input/res/drawable-xhdpi/ch_143_logo.png deleted file mode 100644 index 6f6bb7ea..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_143_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_144_logo.png b/tests/input/res/drawable-xhdpi/ch_144_logo.png deleted file mode 100644 index afa678cc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_144_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_145_logo.png b/tests/input/res/drawable-xhdpi/ch_145_logo.png deleted file mode 100644 index 0e989ba5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_145_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_146_logo.png b/tests/input/res/drawable-xhdpi/ch_146_logo.png deleted file mode 100644 index 4ee0082c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_146_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_147_logo.png b/tests/input/res/drawable-xhdpi/ch_147_logo.png deleted file mode 100644 index ddcc91dd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_147_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_148_logo.png b/tests/input/res/drawable-xhdpi/ch_148_logo.png deleted file mode 100644 index c7f0c42a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_148_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_149_logo.png b/tests/input/res/drawable-xhdpi/ch_149_logo.png deleted file mode 100644 index f2d38ace..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_149_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_14_logo.png b/tests/input/res/drawable-xhdpi/ch_14_logo.png deleted file mode 100644 index f66db228..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_14_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_150_logo.png b/tests/input/res/drawable-xhdpi/ch_150_logo.png deleted file mode 100644 index 6efad527..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_150_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_151_logo.png b/tests/input/res/drawable-xhdpi/ch_151_logo.png deleted file mode 100644 index b37e11ea..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_151_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_152_logo.png b/tests/input/res/drawable-xhdpi/ch_152_logo.png deleted file mode 100644 index 81f872a5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_152_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_153_logo.png b/tests/input/res/drawable-xhdpi/ch_153_logo.png deleted file mode 100644 index e564739d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_153_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_154_logo.png b/tests/input/res/drawable-xhdpi/ch_154_logo.png deleted file mode 100644 index 331498e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_154_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_155_logo.png b/tests/input/res/drawable-xhdpi/ch_155_logo.png deleted file mode 100644 index da8c34d4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_155_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_156_logo.png b/tests/input/res/drawable-xhdpi/ch_156_logo.png deleted file mode 100644 index 5ca6d550..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_156_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_157_logo.png b/tests/input/res/drawable-xhdpi/ch_157_logo.png deleted file mode 100644 index 460ece79..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_157_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_158_logo.png b/tests/input/res/drawable-xhdpi/ch_158_logo.png deleted file mode 100644 index 8d11e42e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_158_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_159_logo.png b/tests/input/res/drawable-xhdpi/ch_159_logo.png deleted file mode 100644 index a10cf881..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_159_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_15_logo.png b/tests/input/res/drawable-xhdpi/ch_15_logo.png deleted file mode 100644 index ae4fc936..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_15_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_160_logo.png b/tests/input/res/drawable-xhdpi/ch_160_logo.png deleted file mode 100644 index c219ea72..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_160_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_161_logo.png b/tests/input/res/drawable-xhdpi/ch_161_logo.png deleted file mode 100644 index 2b13ad83..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_161_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_162_logo.png b/tests/input/res/drawable-xhdpi/ch_162_logo.png deleted file mode 100644 index 11bfadca..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_162_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_163_logo.png b/tests/input/res/drawable-xhdpi/ch_163_logo.png deleted file mode 100644 index 9c41b03a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_163_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_164_logo.png b/tests/input/res/drawable-xhdpi/ch_164_logo.png deleted file mode 100644 index ec4a101e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_164_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_165_logo.png b/tests/input/res/drawable-xhdpi/ch_165_logo.png deleted file mode 100644 index 1aceac38..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_165_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_166_logo.png b/tests/input/res/drawable-xhdpi/ch_166_logo.png deleted file mode 100644 index f731014f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_166_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_167_logo.png b/tests/input/res/drawable-xhdpi/ch_167_logo.png deleted file mode 100644 index 08c82fbf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_167_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_168_logo.png b/tests/input/res/drawable-xhdpi/ch_168_logo.png deleted file mode 100644 index 0c5707bc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_168_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_169_logo.png b/tests/input/res/drawable-xhdpi/ch_169_logo.png deleted file mode 100644 index a9710cec..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_169_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_16_logo.png b/tests/input/res/drawable-xhdpi/ch_16_logo.png deleted file mode 100644 index 76aee2de..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_16_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_170_logo.png b/tests/input/res/drawable-xhdpi/ch_170_logo.png deleted file mode 100644 index 1bf43fa7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_170_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_171_logo.png b/tests/input/res/drawable-xhdpi/ch_171_logo.png deleted file mode 100644 index 8c6d6fde..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_171_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_172_logo.png b/tests/input/res/drawable-xhdpi/ch_172_logo.png deleted file mode 100644 index 13d73ec7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_172_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_173_logo.png b/tests/input/res/drawable-xhdpi/ch_173_logo.png deleted file mode 100644 index 2423b0ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_173_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_174_logo.png b/tests/input/res/drawable-xhdpi/ch_174_logo.png deleted file mode 100644 index 2f752dce..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_174_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_175_logo.png b/tests/input/res/drawable-xhdpi/ch_175_logo.png deleted file mode 100644 index ffe3b45e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_175_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_176_logo.png b/tests/input/res/drawable-xhdpi/ch_176_logo.png deleted file mode 100644 index d35592de..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_176_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_177_logo.png b/tests/input/res/drawable-xhdpi/ch_177_logo.png deleted file mode 100644 index c50df44f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_177_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_178_logo.png b/tests/input/res/drawable-xhdpi/ch_178_logo.png deleted file mode 100644 index 22539784..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_178_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_179_logo.png b/tests/input/res/drawable-xhdpi/ch_179_logo.png deleted file mode 100644 index a2c1946b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_179_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_17_logo.png b/tests/input/res/drawable-xhdpi/ch_17_logo.png deleted file mode 100644 index 0189498f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_17_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_180_logo.png b/tests/input/res/drawable-xhdpi/ch_180_logo.png deleted file mode 100644 index 9c72f2ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_180_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_181_logo.png b/tests/input/res/drawable-xhdpi/ch_181_logo.png deleted file mode 100644 index 23610936..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_181_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_182_logo.png b/tests/input/res/drawable-xhdpi/ch_182_logo.png deleted file mode 100644 index c36bc811..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_182_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_183_logo.png b/tests/input/res/drawable-xhdpi/ch_183_logo.png deleted file mode 100644 index e0e75a41..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_183_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_184_logo.png b/tests/input/res/drawable-xhdpi/ch_184_logo.png deleted file mode 100644 index 334598f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_184_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_185_logo.png b/tests/input/res/drawable-xhdpi/ch_185_logo.png deleted file mode 100644 index 6891720d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_185_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_186_logo.png b/tests/input/res/drawable-xhdpi/ch_186_logo.png deleted file mode 100644 index 58fc146a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_186_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_187_logo.png b/tests/input/res/drawable-xhdpi/ch_187_logo.png deleted file mode 100644 index 6d4f46fc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_187_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_188_logo.png b/tests/input/res/drawable-xhdpi/ch_188_logo.png deleted file mode 100644 index 96fc401c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_188_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_189_logo.png b/tests/input/res/drawable-xhdpi/ch_189_logo.png deleted file mode 100644 index 93dd4050..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_189_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_18_logo.png b/tests/input/res/drawable-xhdpi/ch_18_logo.png deleted file mode 100644 index 2025821b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_18_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_190_logo.png b/tests/input/res/drawable-xhdpi/ch_190_logo.png deleted file mode 100644 index ea257681..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_190_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_191_logo.png b/tests/input/res/drawable-xhdpi/ch_191_logo.png deleted file mode 100644 index 2ac4c189..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_191_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_192_logo.png b/tests/input/res/drawable-xhdpi/ch_192_logo.png deleted file mode 100644 index 14728cd3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_192_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_193_logo.png b/tests/input/res/drawable-xhdpi/ch_193_logo.png deleted file mode 100644 index dbbf2a1f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_193_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_194_logo.png b/tests/input/res/drawable-xhdpi/ch_194_logo.png deleted file mode 100644 index aabcefda..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_194_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_195_logo.png b/tests/input/res/drawable-xhdpi/ch_195_logo.png deleted file mode 100644 index e0158d09..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_195_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_196_logo.png b/tests/input/res/drawable-xhdpi/ch_196_logo.png deleted file mode 100644 index a1087785..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_196_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_197_logo.png b/tests/input/res/drawable-xhdpi/ch_197_logo.png deleted file mode 100644 index 5644e83c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_197_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_198_logo.png b/tests/input/res/drawable-xhdpi/ch_198_logo.png deleted file mode 100644 index c06acf55..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_198_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_199_logo.png b/tests/input/res/drawable-xhdpi/ch_199_logo.png deleted file mode 100644 index 6d5ec3ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_199_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_19_logo.png b/tests/input/res/drawable-xhdpi/ch_19_logo.png deleted file mode 100644 index 5e729625..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_19_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_1_logo.png b/tests/input/res/drawable-xhdpi/ch_1_logo.png deleted file mode 100644 index 0a39d154..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_1_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_200_logo.png b/tests/input/res/drawable-xhdpi/ch_200_logo.png deleted file mode 100644 index 7999b2f6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_200_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_201_logo.png b/tests/input/res/drawable-xhdpi/ch_201_logo.png deleted file mode 100644 index 68c868e9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_201_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_202_logo.png b/tests/input/res/drawable-xhdpi/ch_202_logo.png deleted file mode 100644 index abd838fa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_202_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_203_logo.png b/tests/input/res/drawable-xhdpi/ch_203_logo.png deleted file mode 100644 index f91c34cc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_203_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_204_logo.png b/tests/input/res/drawable-xhdpi/ch_204_logo.png deleted file mode 100644 index 8e8582c6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_204_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_205_logo.png b/tests/input/res/drawable-xhdpi/ch_205_logo.png deleted file mode 100644 index 4e3f4d94..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_205_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_206_logo.png b/tests/input/res/drawable-xhdpi/ch_206_logo.png deleted file mode 100644 index 584bb98c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_206_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_207_logo.png b/tests/input/res/drawable-xhdpi/ch_207_logo.png deleted file mode 100644 index e6b2748e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_207_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_208_logo.png b/tests/input/res/drawable-xhdpi/ch_208_logo.png deleted file mode 100644 index 5a18dcad..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_208_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_209_logo.png b/tests/input/res/drawable-xhdpi/ch_209_logo.png deleted file mode 100644 index c4de3050..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_209_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_20_logo.png b/tests/input/res/drawable-xhdpi/ch_20_logo.png deleted file mode 100644 index 6b4d6104..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_20_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_210_logo.png b/tests/input/res/drawable-xhdpi/ch_210_logo.png deleted file mode 100644 index e4eed085..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_210_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_211_logo.png b/tests/input/res/drawable-xhdpi/ch_211_logo.png deleted file mode 100644 index d5881047..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_211_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_212_logo.png b/tests/input/res/drawable-xhdpi/ch_212_logo.png deleted file mode 100644 index c849f6f2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_212_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_213_logo.png b/tests/input/res/drawable-xhdpi/ch_213_logo.png deleted file mode 100644 index 92def1c5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_213_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_214_logo.png b/tests/input/res/drawable-xhdpi/ch_214_logo.png deleted file mode 100644 index 51f749fd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_214_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_215_logo.png b/tests/input/res/drawable-xhdpi/ch_215_logo.png deleted file mode 100644 index 5425aaad..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_215_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_216_logo.png b/tests/input/res/drawable-xhdpi/ch_216_logo.png deleted file mode 100644 index 53fed3c2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_216_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_217_logo.png b/tests/input/res/drawable-xhdpi/ch_217_logo.png deleted file mode 100644 index d253f9cd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_217_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_218_logo.png b/tests/input/res/drawable-xhdpi/ch_218_logo.png deleted file mode 100644 index 3c7b0698..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_218_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_219_logo.png b/tests/input/res/drawable-xhdpi/ch_219_logo.png deleted file mode 100644 index 7b96dd6e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_219_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_21_logo.png b/tests/input/res/drawable-xhdpi/ch_21_logo.png deleted file mode 100644 index f2848346..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_21_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_220_logo.png b/tests/input/res/drawable-xhdpi/ch_220_logo.png deleted file mode 100644 index f9d20884..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_220_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_221_logo.png b/tests/input/res/drawable-xhdpi/ch_221_logo.png deleted file mode 100644 index 9213142d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_221_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_222_logo.png b/tests/input/res/drawable-xhdpi/ch_222_logo.png deleted file mode 100644 index 66cb9a8b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_222_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_223_logo.png b/tests/input/res/drawable-xhdpi/ch_223_logo.png deleted file mode 100644 index 11a44724..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_223_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_224_logo.png b/tests/input/res/drawable-xhdpi/ch_224_logo.png deleted file mode 100644 index cdba6bf5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_224_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_225_logo.png b/tests/input/res/drawable-xhdpi/ch_225_logo.png deleted file mode 100644 index e9416bfd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_225_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_226_logo.png b/tests/input/res/drawable-xhdpi/ch_226_logo.png deleted file mode 100644 index 88792532..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_226_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_227_logo.png b/tests/input/res/drawable-xhdpi/ch_227_logo.png deleted file mode 100644 index 7ae8205b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_227_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_228_logo.png b/tests/input/res/drawable-xhdpi/ch_228_logo.png deleted file mode 100644 index 718d93e2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_228_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_229_logo.png b/tests/input/res/drawable-xhdpi/ch_229_logo.png deleted file mode 100644 index eb4f9129..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_229_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_22_logo.png b/tests/input/res/drawable-xhdpi/ch_22_logo.png deleted file mode 100644 index 9aa987d1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_22_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_230_logo.png b/tests/input/res/drawable-xhdpi/ch_230_logo.png deleted file mode 100644 index a1bb57d3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_230_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_231_logo.png b/tests/input/res/drawable-xhdpi/ch_231_logo.png deleted file mode 100644 index e748e868..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_231_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_232_logo.png b/tests/input/res/drawable-xhdpi/ch_232_logo.png deleted file mode 100644 index 5d4f1d69..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_232_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_233_logo.png b/tests/input/res/drawable-xhdpi/ch_233_logo.png deleted file mode 100644 index 7ff780ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_233_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_234_logo.png b/tests/input/res/drawable-xhdpi/ch_234_logo.png deleted file mode 100644 index a4e10dfb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_234_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_235_logo.png b/tests/input/res/drawable-xhdpi/ch_235_logo.png deleted file mode 100644 index c318ac0b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_235_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_236_logo.png b/tests/input/res/drawable-xhdpi/ch_236_logo.png deleted file mode 100644 index 2ab86fd6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_236_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_237_logo.png b/tests/input/res/drawable-xhdpi/ch_237_logo.png deleted file mode 100644 index 04fa9d61..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_237_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_238_logo.png b/tests/input/res/drawable-xhdpi/ch_238_logo.png deleted file mode 100644 index 6649b9db..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_238_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_239_logo.png b/tests/input/res/drawable-xhdpi/ch_239_logo.png deleted file mode 100644 index 6eaf887d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_239_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_23_logo.png b/tests/input/res/drawable-xhdpi/ch_23_logo.png deleted file mode 100644 index c3dcf172..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_23_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_240_logo.png b/tests/input/res/drawable-xhdpi/ch_240_logo.png deleted file mode 100644 index 50d0d0df..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_240_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_241_logo.png b/tests/input/res/drawable-xhdpi/ch_241_logo.png deleted file mode 100644 index 779d53b5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_241_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_242_logo.png b/tests/input/res/drawable-xhdpi/ch_242_logo.png deleted file mode 100644 index 717aaae5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_242_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_243_logo.png b/tests/input/res/drawable-xhdpi/ch_243_logo.png deleted file mode 100644 index fd04b2a2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_243_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_244_logo.png b/tests/input/res/drawable-xhdpi/ch_244_logo.png deleted file mode 100644 index d8b1b710..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_244_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_245_logo.png b/tests/input/res/drawable-xhdpi/ch_245_logo.png deleted file mode 100644 index 3a08f595..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_245_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_246_logo.png b/tests/input/res/drawable-xhdpi/ch_246_logo.png deleted file mode 100644 index 404bd8f0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_246_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_247_logo.png b/tests/input/res/drawable-xhdpi/ch_247_logo.png deleted file mode 100644 index 46ee0163..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_247_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_248_logo.png b/tests/input/res/drawable-xhdpi/ch_248_logo.png deleted file mode 100644 index ebfeb6d8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_248_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_249_logo.png b/tests/input/res/drawable-xhdpi/ch_249_logo.png deleted file mode 100644 index f49dc8c4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_249_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_24_logo.png b/tests/input/res/drawable-xhdpi/ch_24_logo.png deleted file mode 100644 index 8fda8dcc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_24_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_250_logo.png b/tests/input/res/drawable-xhdpi/ch_250_logo.png deleted file mode 100644 index 3c464624..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_250_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_251_logo.png b/tests/input/res/drawable-xhdpi/ch_251_logo.png deleted file mode 100644 index 58f3f7d2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_251_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_252_logo.png b/tests/input/res/drawable-xhdpi/ch_252_logo.png deleted file mode 100644 index f0284549..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_252_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_253_logo.png b/tests/input/res/drawable-xhdpi/ch_253_logo.png deleted file mode 100644 index 47a8f91d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_253_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_254_logo.png b/tests/input/res/drawable-xhdpi/ch_254_logo.png deleted file mode 100644 index e2505fc0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_254_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_255_logo.png b/tests/input/res/drawable-xhdpi/ch_255_logo.png deleted file mode 100644 index 55e7116b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_255_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_256_logo.png b/tests/input/res/drawable-xhdpi/ch_256_logo.png deleted file mode 100644 index d31964ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_256_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_257_logo.png b/tests/input/res/drawable-xhdpi/ch_257_logo.png deleted file mode 100644 index cf850f90..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_257_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_258_logo.png b/tests/input/res/drawable-xhdpi/ch_258_logo.png deleted file mode 100644 index 148d0ee4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_258_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_259_logo.png b/tests/input/res/drawable-xhdpi/ch_259_logo.png deleted file mode 100644 index aa84697d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_259_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_25_logo.png b/tests/input/res/drawable-xhdpi/ch_25_logo.png deleted file mode 100644 index 401e785e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_25_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_260_logo.png b/tests/input/res/drawable-xhdpi/ch_260_logo.png deleted file mode 100644 index dc4f67f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_260_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_261_logo.png b/tests/input/res/drawable-xhdpi/ch_261_logo.png deleted file mode 100644 index dfbecb37..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_261_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_262_logo.png b/tests/input/res/drawable-xhdpi/ch_262_logo.png deleted file mode 100644 index 2fa3185b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_262_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_263_logo.png b/tests/input/res/drawable-xhdpi/ch_263_logo.png deleted file mode 100644 index d1e84b38..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_263_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_264_logo.png b/tests/input/res/drawable-xhdpi/ch_264_logo.png deleted file mode 100644 index 3f52decb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_264_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_265_logo.png b/tests/input/res/drawable-xhdpi/ch_265_logo.png deleted file mode 100644 index f60c362a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_265_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_266_logo.png b/tests/input/res/drawable-xhdpi/ch_266_logo.png deleted file mode 100644 index 94dc4463..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_266_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_267_logo.png b/tests/input/res/drawable-xhdpi/ch_267_logo.png deleted file mode 100644 index 48ac79ed..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_267_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_268_logo.png b/tests/input/res/drawable-xhdpi/ch_268_logo.png deleted file mode 100644 index b6f1ad64..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_268_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_269_logo.png b/tests/input/res/drawable-xhdpi/ch_269_logo.png deleted file mode 100644 index 0d98d378..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_269_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_26_logo.png b/tests/input/res/drawable-xhdpi/ch_26_logo.png deleted file mode 100644 index a8835e4a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_26_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_270_logo.png b/tests/input/res/drawable-xhdpi/ch_270_logo.png deleted file mode 100644 index 27a0aead..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_270_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_271_logo.png b/tests/input/res/drawable-xhdpi/ch_271_logo.png deleted file mode 100644 index 7fa6feb3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_271_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_272_logo.png b/tests/input/res/drawable-xhdpi/ch_272_logo.png deleted file mode 100644 index af7e34e6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_272_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_273_logo.png b/tests/input/res/drawable-xhdpi/ch_273_logo.png deleted file mode 100644 index a8a041af..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_273_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_274_logo.png b/tests/input/res/drawable-xhdpi/ch_274_logo.png deleted file mode 100644 index 0f498f23..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_274_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_275_logo.png b/tests/input/res/drawable-xhdpi/ch_275_logo.png deleted file mode 100644 index d598fb35..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_275_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_276_logo.png b/tests/input/res/drawable-xhdpi/ch_276_logo.png deleted file mode 100644 index 7cbac69a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_276_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_277_logo.png b/tests/input/res/drawable-xhdpi/ch_277_logo.png deleted file mode 100644 index 70a7f3ea..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_277_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_278_logo.png b/tests/input/res/drawable-xhdpi/ch_278_logo.png deleted file mode 100644 index fcb332a5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_278_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_279_logo.png b/tests/input/res/drawable-xhdpi/ch_279_logo.png deleted file mode 100644 index a95b4a96..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_279_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_27_logo.png b/tests/input/res/drawable-xhdpi/ch_27_logo.png deleted file mode 100644 index 138b7dc8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_27_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_280_logo.png b/tests/input/res/drawable-xhdpi/ch_280_logo.png deleted file mode 100644 index 2f862b39..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_280_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_281_logo.png b/tests/input/res/drawable-xhdpi/ch_281_logo.png deleted file mode 100644 index 6d888d16..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_281_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_282_logo.png b/tests/input/res/drawable-xhdpi/ch_282_logo.png deleted file mode 100644 index 81db1656..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_282_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_283_logo.png b/tests/input/res/drawable-xhdpi/ch_283_logo.png deleted file mode 100644 index 4933bc3c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_283_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_284_logo.png b/tests/input/res/drawable-xhdpi/ch_284_logo.png deleted file mode 100644 index b4880b8b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_284_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_285_logo.png b/tests/input/res/drawable-xhdpi/ch_285_logo.png deleted file mode 100644 index eea6175e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_285_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_286_logo.png b/tests/input/res/drawable-xhdpi/ch_286_logo.png deleted file mode 100644 index d2b5b5bd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_286_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_287_logo.png b/tests/input/res/drawable-xhdpi/ch_287_logo.png deleted file mode 100644 index f374d9d0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_287_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_288_logo.png b/tests/input/res/drawable-xhdpi/ch_288_logo.png deleted file mode 100644 index 16072cfa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_288_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_289_logo.png b/tests/input/res/drawable-xhdpi/ch_289_logo.png deleted file mode 100644 index b76c2f48..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_289_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_28_logo.png b/tests/input/res/drawable-xhdpi/ch_28_logo.png deleted file mode 100644 index 284301b0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_28_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_290_logo.png b/tests/input/res/drawable-xhdpi/ch_290_logo.png deleted file mode 100644 index 2778664f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_290_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_291_logo.png b/tests/input/res/drawable-xhdpi/ch_291_logo.png deleted file mode 100644 index 52da29f6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_291_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_292_logo.png b/tests/input/res/drawable-xhdpi/ch_292_logo.png deleted file mode 100644 index a1d8e6f8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_292_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_293_logo.png b/tests/input/res/drawable-xhdpi/ch_293_logo.png deleted file mode 100644 index 74020c30..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_293_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_294_logo.png b/tests/input/res/drawable-xhdpi/ch_294_logo.png deleted file mode 100644 index 49d72b5d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_294_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_295_logo.png b/tests/input/res/drawable-xhdpi/ch_295_logo.png deleted file mode 100644 index cae47aeb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_295_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_296_logo.png b/tests/input/res/drawable-xhdpi/ch_296_logo.png deleted file mode 100644 index 4b38d480..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_296_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_297_logo.png b/tests/input/res/drawable-xhdpi/ch_297_logo.png deleted file mode 100644 index 3edd15d0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_297_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_298_logo.png b/tests/input/res/drawable-xhdpi/ch_298_logo.png deleted file mode 100644 index cb472f31..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_298_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_299_logo.png b/tests/input/res/drawable-xhdpi/ch_299_logo.png deleted file mode 100644 index b672881e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_299_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_29_logo.png b/tests/input/res/drawable-xhdpi/ch_29_logo.png deleted file mode 100644 index 9f1c523f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_29_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_2_logo.png b/tests/input/res/drawable-xhdpi/ch_2_logo.png deleted file mode 100644 index d6887b05..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_2_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_300_logo.png b/tests/input/res/drawable-xhdpi/ch_300_logo.png deleted file mode 100644 index 9b842d77..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_300_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_301_logo.png b/tests/input/res/drawable-xhdpi/ch_301_logo.png deleted file mode 100644 index 4c7fda85..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_301_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_302_logo.png b/tests/input/res/drawable-xhdpi/ch_302_logo.png deleted file mode 100644 index e39f5acb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_302_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_303_logo.png b/tests/input/res/drawable-xhdpi/ch_303_logo.png deleted file mode 100644 index 78f057f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_303_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_304_logo.png b/tests/input/res/drawable-xhdpi/ch_304_logo.png deleted file mode 100644 index b8a68630..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_304_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_305_logo.png b/tests/input/res/drawable-xhdpi/ch_305_logo.png deleted file mode 100644 index 7f698610..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_305_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_306_logo.png b/tests/input/res/drawable-xhdpi/ch_306_logo.png deleted file mode 100644 index 0458b383..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_306_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_307_logo.png b/tests/input/res/drawable-xhdpi/ch_307_logo.png deleted file mode 100644 index f2a35cd5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_307_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_308_logo.png b/tests/input/res/drawable-xhdpi/ch_308_logo.png deleted file mode 100644 index 49d2b4b3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_308_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_309_logo.png b/tests/input/res/drawable-xhdpi/ch_309_logo.png deleted file mode 100644 index 81da237b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_309_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_30_logo.png b/tests/input/res/drawable-xhdpi/ch_30_logo.png deleted file mode 100644 index 72db9c52..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_30_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_310_logo.png b/tests/input/res/drawable-xhdpi/ch_310_logo.png deleted file mode 100644 index 901a9113..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_310_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_311_logo.png b/tests/input/res/drawable-xhdpi/ch_311_logo.png deleted file mode 100644 index 0aa3b284..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_311_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_312_logo.png b/tests/input/res/drawable-xhdpi/ch_312_logo.png deleted file mode 100644 index 0cfead7e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_312_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_313_logo.png b/tests/input/res/drawable-xhdpi/ch_313_logo.png deleted file mode 100644 index b552c871..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_313_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_314_logo.png b/tests/input/res/drawable-xhdpi/ch_314_logo.png deleted file mode 100644 index 354ab843..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_314_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_315_logo.png b/tests/input/res/drawable-xhdpi/ch_315_logo.png deleted file mode 100644 index 2db60cca..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_315_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_316_logo.png b/tests/input/res/drawable-xhdpi/ch_316_logo.png deleted file mode 100644 index da4d32a3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_316_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_317_logo.png b/tests/input/res/drawable-xhdpi/ch_317_logo.png deleted file mode 100644 index d344ef36..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_317_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_318_logo.png b/tests/input/res/drawable-xhdpi/ch_318_logo.png deleted file mode 100644 index 9150c2b1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_318_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_319_logo.png b/tests/input/res/drawable-xhdpi/ch_319_logo.png deleted file mode 100644 index 4b1b7c9d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_319_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_31_logo.png b/tests/input/res/drawable-xhdpi/ch_31_logo.png deleted file mode 100644 index 5386601d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_31_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_320_logo.png b/tests/input/res/drawable-xhdpi/ch_320_logo.png deleted file mode 100644 index 4efe21d4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_320_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_321_logo.png b/tests/input/res/drawable-xhdpi/ch_321_logo.png deleted file mode 100644 index d523277d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_321_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_322_logo.png b/tests/input/res/drawable-xhdpi/ch_322_logo.png deleted file mode 100644 index cf2500dc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_322_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_323_logo.png b/tests/input/res/drawable-xhdpi/ch_323_logo.png deleted file mode 100644 index e838c0c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_323_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_324_logo.png b/tests/input/res/drawable-xhdpi/ch_324_logo.png deleted file mode 100644 index cc0cf6d2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_324_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_325_logo.png b/tests/input/res/drawable-xhdpi/ch_325_logo.png deleted file mode 100644 index adfda884..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_325_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_326_logo.png b/tests/input/res/drawable-xhdpi/ch_326_logo.png deleted file mode 100644 index 434d2cd6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_326_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_327_logo.png b/tests/input/res/drawable-xhdpi/ch_327_logo.png deleted file mode 100644 index 0a7f0b95..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_327_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_328_logo.png b/tests/input/res/drawable-xhdpi/ch_328_logo.png deleted file mode 100644 index 77129370..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_328_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_329_logo.png b/tests/input/res/drawable-xhdpi/ch_329_logo.png deleted file mode 100644 index 9ff0a89f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_329_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_32_logo.png b/tests/input/res/drawable-xhdpi/ch_32_logo.png deleted file mode 100644 index 39351ac1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_32_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_330_logo.png b/tests/input/res/drawable-xhdpi/ch_330_logo.png deleted file mode 100644 index 54adc2e7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_330_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_331_logo.png b/tests/input/res/drawable-xhdpi/ch_331_logo.png deleted file mode 100644 index 6c29286f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_331_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_332_logo.png b/tests/input/res/drawable-xhdpi/ch_332_logo.png deleted file mode 100644 index ccab9144..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_332_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_333_logo.png b/tests/input/res/drawable-xhdpi/ch_333_logo.png deleted file mode 100644 index a35ce116..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_333_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_334_logo.png b/tests/input/res/drawable-xhdpi/ch_334_logo.png deleted file mode 100644 index a191d804..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_334_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_335_logo.png b/tests/input/res/drawable-xhdpi/ch_335_logo.png deleted file mode 100644 index a5680b24..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_335_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_336_logo.png b/tests/input/res/drawable-xhdpi/ch_336_logo.png deleted file mode 100644 index 42292bf8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_336_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_337_logo.png b/tests/input/res/drawable-xhdpi/ch_337_logo.png deleted file mode 100644 index d231fca3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_337_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_338_logo.png b/tests/input/res/drawable-xhdpi/ch_338_logo.png deleted file mode 100644 index 000988ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_338_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_339_logo.png b/tests/input/res/drawable-xhdpi/ch_339_logo.png deleted file mode 100644 index 3150c92c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_339_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_33_logo.png b/tests/input/res/drawable-xhdpi/ch_33_logo.png deleted file mode 100644 index 4931e205..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_33_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_340_logo.png b/tests/input/res/drawable-xhdpi/ch_340_logo.png deleted file mode 100644 index d35d772e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_340_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_341_logo.png b/tests/input/res/drawable-xhdpi/ch_341_logo.png deleted file mode 100644 index 3ad19a0b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_341_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_342_logo.png b/tests/input/res/drawable-xhdpi/ch_342_logo.png deleted file mode 100644 index 8d6cad2c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_342_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_343_logo.png b/tests/input/res/drawable-xhdpi/ch_343_logo.png deleted file mode 100644 index 6e16dc96..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_343_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_344_logo.png b/tests/input/res/drawable-xhdpi/ch_344_logo.png deleted file mode 100644 index e66c5a19..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_344_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_345_logo.png b/tests/input/res/drawable-xhdpi/ch_345_logo.png deleted file mode 100644 index ed451fde..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_345_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_346_logo.png b/tests/input/res/drawable-xhdpi/ch_346_logo.png deleted file mode 100644 index 31059671..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_346_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_347_logo.png b/tests/input/res/drawable-xhdpi/ch_347_logo.png deleted file mode 100644 index 65ee51eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_347_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_348_logo.png b/tests/input/res/drawable-xhdpi/ch_348_logo.png deleted file mode 100644 index 54e9cca7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_348_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_349_logo.png b/tests/input/res/drawable-xhdpi/ch_349_logo.png deleted file mode 100644 index 7a11ae34..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_349_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_34_logo.png b/tests/input/res/drawable-xhdpi/ch_34_logo.png deleted file mode 100644 index 27a217e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_34_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_350_logo.png b/tests/input/res/drawable-xhdpi/ch_350_logo.png deleted file mode 100644 index 5c486ab7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_350_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_351_logo.png b/tests/input/res/drawable-xhdpi/ch_351_logo.png deleted file mode 100644 index 17fff276..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_351_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_352_logo.png b/tests/input/res/drawable-xhdpi/ch_352_logo.png deleted file mode 100644 index 2d9c412c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_352_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_353_logo.png b/tests/input/res/drawable-xhdpi/ch_353_logo.png deleted file mode 100644 index 38c76b38..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_353_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_354_logo.png b/tests/input/res/drawable-xhdpi/ch_354_logo.png deleted file mode 100644 index 8ea7d467..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_354_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_355_logo.png b/tests/input/res/drawable-xhdpi/ch_355_logo.png deleted file mode 100644 index 0c94dcf6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_355_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_356_logo.png b/tests/input/res/drawable-xhdpi/ch_356_logo.png deleted file mode 100644 index 3f2b288a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_356_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_357_logo.png b/tests/input/res/drawable-xhdpi/ch_357_logo.png deleted file mode 100644 index 63d43c31..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_357_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_358_logo.png b/tests/input/res/drawable-xhdpi/ch_358_logo.png deleted file mode 100644 index 1f5771fe..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_358_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_359_logo.png b/tests/input/res/drawable-xhdpi/ch_359_logo.png deleted file mode 100644 index 7a4114bd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_359_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_35_logo.png b/tests/input/res/drawable-xhdpi/ch_35_logo.png deleted file mode 100644 index af2cae5d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_35_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_360_logo.png b/tests/input/res/drawable-xhdpi/ch_360_logo.png deleted file mode 100644 index 4e49a1f9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_360_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_361_logo.png b/tests/input/res/drawable-xhdpi/ch_361_logo.png deleted file mode 100644 index 43b16ac6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_361_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_362_logo.png b/tests/input/res/drawable-xhdpi/ch_362_logo.png deleted file mode 100644 index efb32dab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_362_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_363_logo.png b/tests/input/res/drawable-xhdpi/ch_363_logo.png deleted file mode 100644 index c59eb0b3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_363_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_364_logo.png b/tests/input/res/drawable-xhdpi/ch_364_logo.png deleted file mode 100644 index 9d649d47..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_364_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_365_logo.png b/tests/input/res/drawable-xhdpi/ch_365_logo.png deleted file mode 100644 index 96cae282..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_365_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_366_logo.png b/tests/input/res/drawable-xhdpi/ch_366_logo.png deleted file mode 100644 index 3c3a5cf6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_366_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_367_logo.png b/tests/input/res/drawable-xhdpi/ch_367_logo.png deleted file mode 100644 index 7467625a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_367_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_368_logo.png b/tests/input/res/drawable-xhdpi/ch_368_logo.png deleted file mode 100644 index adb62ffc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_368_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_369_logo.png b/tests/input/res/drawable-xhdpi/ch_369_logo.png deleted file mode 100644 index 773f6c5c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_369_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_36_logo.png b/tests/input/res/drawable-xhdpi/ch_36_logo.png deleted file mode 100644 index 4580833f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_36_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_370_logo.png b/tests/input/res/drawable-xhdpi/ch_370_logo.png deleted file mode 100644 index d60583bc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_370_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_371_logo.png b/tests/input/res/drawable-xhdpi/ch_371_logo.png deleted file mode 100644 index 91958a07..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_371_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_372_logo.png b/tests/input/res/drawable-xhdpi/ch_372_logo.png deleted file mode 100644 index 4b2d757c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_372_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_373_logo.png b/tests/input/res/drawable-xhdpi/ch_373_logo.png deleted file mode 100644 index f8aae527..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_373_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_374_logo.png b/tests/input/res/drawable-xhdpi/ch_374_logo.png deleted file mode 100644 index 3549da94..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_374_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_375_logo.png b/tests/input/res/drawable-xhdpi/ch_375_logo.png deleted file mode 100644 index c19806e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_375_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_376_logo.png b/tests/input/res/drawable-xhdpi/ch_376_logo.png deleted file mode 100644 index 86144b73..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_376_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_377_logo.png b/tests/input/res/drawable-xhdpi/ch_377_logo.png deleted file mode 100644 index 74f693c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_377_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_378_logo.png b/tests/input/res/drawable-xhdpi/ch_378_logo.png deleted file mode 100644 index 40253dab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_378_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_379_logo.png b/tests/input/res/drawable-xhdpi/ch_379_logo.png deleted file mode 100644 index 5459058f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_379_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_37_logo.png b/tests/input/res/drawable-xhdpi/ch_37_logo.png deleted file mode 100644 index 7fce3b4d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_37_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_380_logo.png b/tests/input/res/drawable-xhdpi/ch_380_logo.png deleted file mode 100644 index a47a40d8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_380_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_381_logo.png b/tests/input/res/drawable-xhdpi/ch_381_logo.png deleted file mode 100644 index 7059114c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_381_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_382_logo.png b/tests/input/res/drawable-xhdpi/ch_382_logo.png deleted file mode 100644 index a61201a5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_382_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_383_logo.png b/tests/input/res/drawable-xhdpi/ch_383_logo.png deleted file mode 100644 index c126d6e3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_383_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_384_logo.png b/tests/input/res/drawable-xhdpi/ch_384_logo.png deleted file mode 100644 index 0bd5f454..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_384_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_385_logo.png b/tests/input/res/drawable-xhdpi/ch_385_logo.png deleted file mode 100644 index 864ff5c6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_385_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_386_logo.png b/tests/input/res/drawable-xhdpi/ch_386_logo.png deleted file mode 100644 index 643db67a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_386_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_387_logo.png b/tests/input/res/drawable-xhdpi/ch_387_logo.png deleted file mode 100644 index 206ec14f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_387_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_388_logo.png b/tests/input/res/drawable-xhdpi/ch_388_logo.png deleted file mode 100644 index 37c9dbae..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_388_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_389_logo.png b/tests/input/res/drawable-xhdpi/ch_389_logo.png deleted file mode 100644 index 958ffb63..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_389_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_38_logo.png b/tests/input/res/drawable-xhdpi/ch_38_logo.png deleted file mode 100644 index 6e864bf8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_38_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_390_logo.png b/tests/input/res/drawable-xhdpi/ch_390_logo.png deleted file mode 100644 index a5a6547e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_390_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_391_logo.png b/tests/input/res/drawable-xhdpi/ch_391_logo.png deleted file mode 100644 index 0d1c076b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_391_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_392_logo.png b/tests/input/res/drawable-xhdpi/ch_392_logo.png deleted file mode 100644 index 25e3a87a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_392_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_393_logo.png b/tests/input/res/drawable-xhdpi/ch_393_logo.png deleted file mode 100644 index 92ebefe2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_393_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_394_logo.png b/tests/input/res/drawable-xhdpi/ch_394_logo.png deleted file mode 100644 index b220319a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_394_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_395_logo.png b/tests/input/res/drawable-xhdpi/ch_395_logo.png deleted file mode 100644 index 29df3f8f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_395_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_396_logo.png b/tests/input/res/drawable-xhdpi/ch_396_logo.png deleted file mode 100644 index f1e6f983..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_396_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_397_logo.png b/tests/input/res/drawable-xhdpi/ch_397_logo.png deleted file mode 100644 index 7f44d63c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_397_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_398_logo.png b/tests/input/res/drawable-xhdpi/ch_398_logo.png deleted file mode 100644 index 03b14271..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_398_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_399_logo.png b/tests/input/res/drawable-xhdpi/ch_399_logo.png deleted file mode 100644 index a15aaa4e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_399_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_39_logo.png b/tests/input/res/drawable-xhdpi/ch_39_logo.png deleted file mode 100644 index 633a1172..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_39_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_3_logo.png b/tests/input/res/drawable-xhdpi/ch_3_logo.png deleted file mode 100644 index 9c921385..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_3_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_400_logo.png b/tests/input/res/drawable-xhdpi/ch_400_logo.png deleted file mode 100644 index 15442662..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_400_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_401_logo.png b/tests/input/res/drawable-xhdpi/ch_401_logo.png deleted file mode 100644 index c4d06916..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_401_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_402_logo.png b/tests/input/res/drawable-xhdpi/ch_402_logo.png deleted file mode 100644 index b8719f32..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_402_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_403_logo.png b/tests/input/res/drawable-xhdpi/ch_403_logo.png deleted file mode 100644 index 054b98b7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_403_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_404_logo.png b/tests/input/res/drawable-xhdpi/ch_404_logo.png deleted file mode 100644 index 64566990..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_404_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_405_logo.png b/tests/input/res/drawable-xhdpi/ch_405_logo.png deleted file mode 100644 index 2ceab3fb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_405_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_406_logo.png b/tests/input/res/drawable-xhdpi/ch_406_logo.png deleted file mode 100644 index f7557109..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_406_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_407_logo.png b/tests/input/res/drawable-xhdpi/ch_407_logo.png deleted file mode 100644 index 8228e1c5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_407_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_408_logo.png b/tests/input/res/drawable-xhdpi/ch_408_logo.png deleted file mode 100644 index d9c09b85..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_408_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_409_logo.png b/tests/input/res/drawable-xhdpi/ch_409_logo.png deleted file mode 100644 index fd2b76e5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_409_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_40_logo.png b/tests/input/res/drawable-xhdpi/ch_40_logo.png deleted file mode 100644 index 63600168..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_40_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_410_logo.png b/tests/input/res/drawable-xhdpi/ch_410_logo.png deleted file mode 100644 index 8b05de2e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_410_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_411_logo.png b/tests/input/res/drawable-xhdpi/ch_411_logo.png deleted file mode 100644 index 7306991e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_411_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_412_logo.png b/tests/input/res/drawable-xhdpi/ch_412_logo.png deleted file mode 100644 index 55964214..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_412_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_413_logo.png b/tests/input/res/drawable-xhdpi/ch_413_logo.png deleted file mode 100644 index c744901d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_413_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_414_logo.png b/tests/input/res/drawable-xhdpi/ch_414_logo.png deleted file mode 100644 index 304dc7da..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_414_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_415_logo.png b/tests/input/res/drawable-xhdpi/ch_415_logo.png deleted file mode 100644 index 1a9b1f19..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_415_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_416_logo.png b/tests/input/res/drawable-xhdpi/ch_416_logo.png deleted file mode 100644 index 53318edf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_416_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_417_logo.png b/tests/input/res/drawable-xhdpi/ch_417_logo.png deleted file mode 100644 index 763bad1b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_417_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_418_logo.png b/tests/input/res/drawable-xhdpi/ch_418_logo.png deleted file mode 100644 index afa06409..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_418_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_419_logo.png b/tests/input/res/drawable-xhdpi/ch_419_logo.png deleted file mode 100644 index 4741b659..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_419_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_41_logo.png b/tests/input/res/drawable-xhdpi/ch_41_logo.png deleted file mode 100644 index 6002ae31..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_41_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_420_logo.png b/tests/input/res/drawable-xhdpi/ch_420_logo.png deleted file mode 100644 index 22a72aec..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_420_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_421_logo.png b/tests/input/res/drawable-xhdpi/ch_421_logo.png deleted file mode 100644 index f4c301dd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_421_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_422_logo.png b/tests/input/res/drawable-xhdpi/ch_422_logo.png deleted file mode 100644 index e70d59ac..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_422_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_423_logo.png b/tests/input/res/drawable-xhdpi/ch_423_logo.png deleted file mode 100644 index a4c96fbd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_423_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_424_logo.png b/tests/input/res/drawable-xhdpi/ch_424_logo.png deleted file mode 100644 index 9dc1713e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_424_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_425_logo.png b/tests/input/res/drawable-xhdpi/ch_425_logo.png deleted file mode 100644 index a79e8f5c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_425_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_426_logo.png b/tests/input/res/drawable-xhdpi/ch_426_logo.png deleted file mode 100644 index 34bcbef0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_426_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_427_logo.png b/tests/input/res/drawable-xhdpi/ch_427_logo.png deleted file mode 100644 index 6133494d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_427_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_428_logo.png b/tests/input/res/drawable-xhdpi/ch_428_logo.png deleted file mode 100644 index d91391a4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_428_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_429_logo.png b/tests/input/res/drawable-xhdpi/ch_429_logo.png deleted file mode 100644 index f24a64f9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_429_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_42_logo.png b/tests/input/res/drawable-xhdpi/ch_42_logo.png deleted file mode 100644 index c2f641c0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_42_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_430_logo.png b/tests/input/res/drawable-xhdpi/ch_430_logo.png deleted file mode 100644 index e656d6da..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_430_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_431_logo.png b/tests/input/res/drawable-xhdpi/ch_431_logo.png deleted file mode 100644 index 4827d71d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_431_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_432_logo.png b/tests/input/res/drawable-xhdpi/ch_432_logo.png deleted file mode 100644 index bb5493e2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_432_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_433_logo.png b/tests/input/res/drawable-xhdpi/ch_433_logo.png deleted file mode 100644 index f777f36a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_433_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_434_logo.png b/tests/input/res/drawable-xhdpi/ch_434_logo.png deleted file mode 100644 index f342df85..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_434_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_435_logo.png b/tests/input/res/drawable-xhdpi/ch_435_logo.png deleted file mode 100644 index 70667e7a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_435_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_436_logo.png b/tests/input/res/drawable-xhdpi/ch_436_logo.png deleted file mode 100644 index 1895a238..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_436_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_437_logo.png b/tests/input/res/drawable-xhdpi/ch_437_logo.png deleted file mode 100644 index 9b6b335b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_437_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_438_logo.png b/tests/input/res/drawable-xhdpi/ch_438_logo.png deleted file mode 100644 index 5070cdb1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_438_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_439_logo.png b/tests/input/res/drawable-xhdpi/ch_439_logo.png deleted file mode 100644 index adcad27b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_439_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_43_logo.png b/tests/input/res/drawable-xhdpi/ch_43_logo.png deleted file mode 100644 index 5ea7d814..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_43_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_440_logo.png b/tests/input/res/drawable-xhdpi/ch_440_logo.png deleted file mode 100644 index 0b1f76c6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_440_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_441_logo.png b/tests/input/res/drawable-xhdpi/ch_441_logo.png deleted file mode 100644 index 65870b6e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_441_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_442_logo.png b/tests/input/res/drawable-xhdpi/ch_442_logo.png deleted file mode 100644 index 9812a1cb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_442_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_443_logo.png b/tests/input/res/drawable-xhdpi/ch_443_logo.png deleted file mode 100644 index d539f5c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_443_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_444_logo.png b/tests/input/res/drawable-xhdpi/ch_444_logo.png deleted file mode 100644 index fbf615d9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_444_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_445_logo.png b/tests/input/res/drawable-xhdpi/ch_445_logo.png deleted file mode 100644 index 440085aa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_445_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_446_logo.png b/tests/input/res/drawable-xhdpi/ch_446_logo.png deleted file mode 100644 index d26f1c37..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_446_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_447_logo.png b/tests/input/res/drawable-xhdpi/ch_447_logo.png deleted file mode 100644 index 0967878d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_447_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_448_logo.png b/tests/input/res/drawable-xhdpi/ch_448_logo.png deleted file mode 100644 index b979c062..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_448_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_449_logo.png b/tests/input/res/drawable-xhdpi/ch_449_logo.png deleted file mode 100644 index 2232b90e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_449_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_44_logo.png b/tests/input/res/drawable-xhdpi/ch_44_logo.png deleted file mode 100644 index be6cbd3b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_44_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_450_logo.png b/tests/input/res/drawable-xhdpi/ch_450_logo.png deleted file mode 100644 index 8e25df9a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_450_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_451_logo.png b/tests/input/res/drawable-xhdpi/ch_451_logo.png deleted file mode 100644 index c744e56e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_451_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_452_logo.png b/tests/input/res/drawable-xhdpi/ch_452_logo.png deleted file mode 100644 index 050b0196..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_452_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_453_logo.png b/tests/input/res/drawable-xhdpi/ch_453_logo.png deleted file mode 100644 index 4ccdbf14..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_453_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_454_logo.png b/tests/input/res/drawable-xhdpi/ch_454_logo.png deleted file mode 100644 index 10aa3779..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_454_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_455_logo.png b/tests/input/res/drawable-xhdpi/ch_455_logo.png deleted file mode 100644 index 7b607a6c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_455_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_456_logo.png b/tests/input/res/drawable-xhdpi/ch_456_logo.png deleted file mode 100644 index f2d57066..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_456_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_457_logo.png b/tests/input/res/drawable-xhdpi/ch_457_logo.png deleted file mode 100644 index 483e5912..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_457_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_458_logo.png b/tests/input/res/drawable-xhdpi/ch_458_logo.png deleted file mode 100644 index 447d5942..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_458_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_459_logo.png b/tests/input/res/drawable-xhdpi/ch_459_logo.png deleted file mode 100644 index 80d6c5b6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_459_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_45_logo.png b/tests/input/res/drawable-xhdpi/ch_45_logo.png deleted file mode 100644 index 0db467c8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_45_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_460_logo.png b/tests/input/res/drawable-xhdpi/ch_460_logo.png deleted file mode 100644 index 22d785cb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_460_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_461_logo.png b/tests/input/res/drawable-xhdpi/ch_461_logo.png deleted file mode 100644 index 658a1046..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_461_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_462_logo.png b/tests/input/res/drawable-xhdpi/ch_462_logo.png deleted file mode 100644 index dd0c5d14..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_462_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_463_logo.png b/tests/input/res/drawable-xhdpi/ch_463_logo.png deleted file mode 100644 index 7b72b915..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_463_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_464_logo.png b/tests/input/res/drawable-xhdpi/ch_464_logo.png deleted file mode 100644 index cc15444c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_464_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_465_logo.png b/tests/input/res/drawable-xhdpi/ch_465_logo.png deleted file mode 100644 index a17cb719..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_465_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_466_logo.png b/tests/input/res/drawable-xhdpi/ch_466_logo.png deleted file mode 100644 index 604644f4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_466_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_467_logo.png b/tests/input/res/drawable-xhdpi/ch_467_logo.png deleted file mode 100644 index 4c74b50d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_467_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_468_logo.png b/tests/input/res/drawable-xhdpi/ch_468_logo.png deleted file mode 100644 index 8e329206..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_468_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_469_logo.png b/tests/input/res/drawable-xhdpi/ch_469_logo.png deleted file mode 100644 index 14a1d99e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_469_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_46_logo.png b/tests/input/res/drawable-xhdpi/ch_46_logo.png deleted file mode 100644 index f9d83ea9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_46_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_470_logo.png b/tests/input/res/drawable-xhdpi/ch_470_logo.png deleted file mode 100644 index 567e879a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_470_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_471_logo.png b/tests/input/res/drawable-xhdpi/ch_471_logo.png deleted file mode 100644 index 4a128a0e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_471_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_472_logo.png b/tests/input/res/drawable-xhdpi/ch_472_logo.png deleted file mode 100644 index f00d1cb0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_472_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_473_logo.png b/tests/input/res/drawable-xhdpi/ch_473_logo.png deleted file mode 100644 index ee7bbfc9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_473_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_474_logo.png b/tests/input/res/drawable-xhdpi/ch_474_logo.png deleted file mode 100644 index a1b7e0eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_474_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_475_logo.png b/tests/input/res/drawable-xhdpi/ch_475_logo.png deleted file mode 100644 index 14db7abe..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_475_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_476_logo.png b/tests/input/res/drawable-xhdpi/ch_476_logo.png deleted file mode 100644 index 89c71e80..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_476_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_477_logo.png b/tests/input/res/drawable-xhdpi/ch_477_logo.png deleted file mode 100644 index 60509130..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_477_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_478_logo.png b/tests/input/res/drawable-xhdpi/ch_478_logo.png deleted file mode 100644 index a2c3069b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_478_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_479_logo.png b/tests/input/res/drawable-xhdpi/ch_479_logo.png deleted file mode 100644 index 2be6ae4f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_479_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_47_logo.png b/tests/input/res/drawable-xhdpi/ch_47_logo.png deleted file mode 100644 index ed4b464a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_47_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_480_logo.png b/tests/input/res/drawable-xhdpi/ch_480_logo.png deleted file mode 100644 index f2a2d6fa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_480_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_481_logo.png b/tests/input/res/drawable-xhdpi/ch_481_logo.png deleted file mode 100644 index b71fcfa8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_481_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_482_logo.png b/tests/input/res/drawable-xhdpi/ch_482_logo.png deleted file mode 100644 index 8d317e47..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_482_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_483_logo.png b/tests/input/res/drawable-xhdpi/ch_483_logo.png deleted file mode 100644 index ea417f9e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_483_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_484_logo.png b/tests/input/res/drawable-xhdpi/ch_484_logo.png deleted file mode 100644 index b46584a4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_484_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_485_logo.png b/tests/input/res/drawable-xhdpi/ch_485_logo.png deleted file mode 100644 index 3564dcd5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_485_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_486_logo.png b/tests/input/res/drawable-xhdpi/ch_486_logo.png deleted file mode 100644 index 8227575f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_486_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_487_logo.png b/tests/input/res/drawable-xhdpi/ch_487_logo.png deleted file mode 100644 index edb15bee..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_487_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_488_logo.png b/tests/input/res/drawable-xhdpi/ch_488_logo.png deleted file mode 100644 index f358d335..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_488_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_489_logo.png b/tests/input/res/drawable-xhdpi/ch_489_logo.png deleted file mode 100644 index 5122c67e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_489_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_48_logo.png b/tests/input/res/drawable-xhdpi/ch_48_logo.png deleted file mode 100644 index e1be7313..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_48_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_490_logo.png b/tests/input/res/drawable-xhdpi/ch_490_logo.png deleted file mode 100644 index 1901d995..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_490_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_491_logo.png b/tests/input/res/drawable-xhdpi/ch_491_logo.png deleted file mode 100644 index 04b0a021..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_491_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_492_logo.png b/tests/input/res/drawable-xhdpi/ch_492_logo.png deleted file mode 100644 index b18dea9b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_492_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_493_logo.png b/tests/input/res/drawable-xhdpi/ch_493_logo.png deleted file mode 100644 index 19044655..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_493_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_494_logo.png b/tests/input/res/drawable-xhdpi/ch_494_logo.png deleted file mode 100644 index b5b0f76c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_494_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_495_logo.png b/tests/input/res/drawable-xhdpi/ch_495_logo.png deleted file mode 100644 index 6124dc83..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_495_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_496_logo.png b/tests/input/res/drawable-xhdpi/ch_496_logo.png deleted file mode 100644 index 6b0e472b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_496_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_497_logo.png b/tests/input/res/drawable-xhdpi/ch_497_logo.png deleted file mode 100644 index 31fcd048..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_497_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_498_logo.png b/tests/input/res/drawable-xhdpi/ch_498_logo.png deleted file mode 100644 index 472c09b6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_498_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_499_logo.png b/tests/input/res/drawable-xhdpi/ch_499_logo.png deleted file mode 100644 index 2d653f61..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_499_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_49_logo.png b/tests/input/res/drawable-xhdpi/ch_49_logo.png deleted file mode 100644 index 46bc774d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_49_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_4_logo.png b/tests/input/res/drawable-xhdpi/ch_4_logo.png deleted file mode 100644 index d5e6517c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_4_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_500_logo.png b/tests/input/res/drawable-xhdpi/ch_500_logo.png deleted file mode 100644 index ecfa17ec..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_500_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_501_logo.png b/tests/input/res/drawable-xhdpi/ch_501_logo.png deleted file mode 100644 index 0c81a787..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_501_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_502_logo.png b/tests/input/res/drawable-xhdpi/ch_502_logo.png deleted file mode 100644 index 5dd20644..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_502_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_503_logo.png b/tests/input/res/drawable-xhdpi/ch_503_logo.png deleted file mode 100644 index a48ec52d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_503_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_504_logo.png b/tests/input/res/drawable-xhdpi/ch_504_logo.png deleted file mode 100644 index 999a641e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_504_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_505_logo.png b/tests/input/res/drawable-xhdpi/ch_505_logo.png deleted file mode 100644 index d7c600e4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_505_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_506_logo.png b/tests/input/res/drawable-xhdpi/ch_506_logo.png deleted file mode 100644 index ac80e6dd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_506_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_507_logo.png b/tests/input/res/drawable-xhdpi/ch_507_logo.png deleted file mode 100644 index c4d434f8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_507_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_508_logo.png b/tests/input/res/drawable-xhdpi/ch_508_logo.png deleted file mode 100644 index 35330059..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_508_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_509_logo.png b/tests/input/res/drawable-xhdpi/ch_509_logo.png deleted file mode 100644 index 2077eac5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_509_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_50_logo.png b/tests/input/res/drawable-xhdpi/ch_50_logo.png deleted file mode 100644 index 901373e6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_50_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_510_logo.png b/tests/input/res/drawable-xhdpi/ch_510_logo.png deleted file mode 100644 index 469bf77d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_510_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_511_logo.png b/tests/input/res/drawable-xhdpi/ch_511_logo.png deleted file mode 100644 index 80d727bb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_511_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_512_logo.png b/tests/input/res/drawable-xhdpi/ch_512_logo.png deleted file mode 100644 index 37ba7e2b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_512_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_513_logo.png b/tests/input/res/drawable-xhdpi/ch_513_logo.png deleted file mode 100644 index ef7c4f2a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_513_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_514_logo.png b/tests/input/res/drawable-xhdpi/ch_514_logo.png deleted file mode 100644 index 450f5963..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_514_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_515_logo.png b/tests/input/res/drawable-xhdpi/ch_515_logo.png deleted file mode 100644 index 6533b2fd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_515_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_516_logo.png b/tests/input/res/drawable-xhdpi/ch_516_logo.png deleted file mode 100644 index 08cd2f0d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_516_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_517_logo.png b/tests/input/res/drawable-xhdpi/ch_517_logo.png deleted file mode 100644 index 1935d709..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_517_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_518_logo.png b/tests/input/res/drawable-xhdpi/ch_518_logo.png deleted file mode 100644 index 806fbbbf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_518_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_519_logo.png b/tests/input/res/drawable-xhdpi/ch_519_logo.png deleted file mode 100644 index 938bb559..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_519_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_51_logo.png b/tests/input/res/drawable-xhdpi/ch_51_logo.png deleted file mode 100644 index 07a5f4ad..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_51_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_520_logo.png b/tests/input/res/drawable-xhdpi/ch_520_logo.png deleted file mode 100644 index 7f9f68b2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_520_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_521_logo.png b/tests/input/res/drawable-xhdpi/ch_521_logo.png deleted file mode 100644 index e8d0432c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_521_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_522_logo.png b/tests/input/res/drawable-xhdpi/ch_522_logo.png deleted file mode 100644 index d7345b8c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_522_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_523_logo.png b/tests/input/res/drawable-xhdpi/ch_523_logo.png deleted file mode 100644 index 61573120..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_523_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_524_logo.png b/tests/input/res/drawable-xhdpi/ch_524_logo.png deleted file mode 100644 index 9a8b2fdd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_524_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_525_logo.png b/tests/input/res/drawable-xhdpi/ch_525_logo.png deleted file mode 100644 index 9ed318fa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_525_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_526_logo.png b/tests/input/res/drawable-xhdpi/ch_526_logo.png deleted file mode 100644 index 5000af2a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_526_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_527_logo.png b/tests/input/res/drawable-xhdpi/ch_527_logo.png deleted file mode 100644 index 38fbd9eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_527_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_528_logo.png b/tests/input/res/drawable-xhdpi/ch_528_logo.png deleted file mode 100644 index 565e8b2a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_528_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_529_logo.png b/tests/input/res/drawable-xhdpi/ch_529_logo.png deleted file mode 100644 index b0924df7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_529_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_52_logo.png b/tests/input/res/drawable-xhdpi/ch_52_logo.png deleted file mode 100644 index 6faa9702..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_52_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_530_logo.png b/tests/input/res/drawable-xhdpi/ch_530_logo.png deleted file mode 100644 index 0eec0234..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_530_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_531_logo.png b/tests/input/res/drawable-xhdpi/ch_531_logo.png deleted file mode 100644 index 2f0243d1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_531_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_532_logo.png b/tests/input/res/drawable-xhdpi/ch_532_logo.png deleted file mode 100644 index a6ece89e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_532_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_533_logo.png b/tests/input/res/drawable-xhdpi/ch_533_logo.png deleted file mode 100644 index a1695158..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_533_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_534_logo.png b/tests/input/res/drawable-xhdpi/ch_534_logo.png deleted file mode 100644 index 00ad0b07..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_534_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_535_logo.png b/tests/input/res/drawable-xhdpi/ch_535_logo.png deleted file mode 100644 index a536ff32..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_535_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_536_logo.png b/tests/input/res/drawable-xhdpi/ch_536_logo.png deleted file mode 100644 index 60fcdf33..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_536_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_537_logo.png b/tests/input/res/drawable-xhdpi/ch_537_logo.png deleted file mode 100644 index 0446d9bc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_537_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_538_logo.png b/tests/input/res/drawable-xhdpi/ch_538_logo.png deleted file mode 100644 index 6f98e08d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_538_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_539_logo.png b/tests/input/res/drawable-xhdpi/ch_539_logo.png deleted file mode 100644 index b6f17bbb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_539_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_53_logo.png b/tests/input/res/drawable-xhdpi/ch_53_logo.png deleted file mode 100644 index 441e1d97..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_53_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_540_logo.png b/tests/input/res/drawable-xhdpi/ch_540_logo.png deleted file mode 100644 index 61950bff..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_540_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_541_logo.png b/tests/input/res/drawable-xhdpi/ch_541_logo.png deleted file mode 100644 index eea9a988..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_541_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_542_logo.png b/tests/input/res/drawable-xhdpi/ch_542_logo.png deleted file mode 100644 index 684cf6d1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_542_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_543_logo.png b/tests/input/res/drawable-xhdpi/ch_543_logo.png deleted file mode 100644 index 44e4e6e0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_543_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_544_logo.png b/tests/input/res/drawable-xhdpi/ch_544_logo.png deleted file mode 100644 index adf6c97a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_544_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_545_logo.png b/tests/input/res/drawable-xhdpi/ch_545_logo.png deleted file mode 100644 index 57f8edc6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_545_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_546_logo.png b/tests/input/res/drawable-xhdpi/ch_546_logo.png deleted file mode 100644 index 4ac92311..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_546_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_547_logo.png b/tests/input/res/drawable-xhdpi/ch_547_logo.png deleted file mode 100644 index ca857974..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_547_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_548_logo.png b/tests/input/res/drawable-xhdpi/ch_548_logo.png deleted file mode 100644 index 1037011c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_548_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_549_logo.png b/tests/input/res/drawable-xhdpi/ch_549_logo.png deleted file mode 100644 index fd10a2ee..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_549_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_54_logo.png b/tests/input/res/drawable-xhdpi/ch_54_logo.png deleted file mode 100644 index 50f22f48..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_54_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_550_logo.png b/tests/input/res/drawable-xhdpi/ch_550_logo.png deleted file mode 100644 index 992666b6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_550_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_551_logo.png b/tests/input/res/drawable-xhdpi/ch_551_logo.png deleted file mode 100644 index 41369f0b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_551_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_552_logo.png b/tests/input/res/drawable-xhdpi/ch_552_logo.png deleted file mode 100644 index 56be3e73..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_552_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_553_logo.png b/tests/input/res/drawable-xhdpi/ch_553_logo.png deleted file mode 100644 index 04812119..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_553_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_554_logo.png b/tests/input/res/drawable-xhdpi/ch_554_logo.png deleted file mode 100644 index b7e943a3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_554_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_555_logo.png b/tests/input/res/drawable-xhdpi/ch_555_logo.png deleted file mode 100644 index fb74741b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_555_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_556_logo.png b/tests/input/res/drawable-xhdpi/ch_556_logo.png deleted file mode 100644 index 8c6d4046..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_556_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_557_logo.png b/tests/input/res/drawable-xhdpi/ch_557_logo.png deleted file mode 100644 index fe65c25c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_557_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_558_logo.png b/tests/input/res/drawable-xhdpi/ch_558_logo.png deleted file mode 100644 index 598e6424..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_558_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_559_logo.png b/tests/input/res/drawable-xhdpi/ch_559_logo.png deleted file mode 100644 index 68a46085..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_559_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_55_logo.png b/tests/input/res/drawable-xhdpi/ch_55_logo.png deleted file mode 100644 index 59f21e84..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_55_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_560_logo.png b/tests/input/res/drawable-xhdpi/ch_560_logo.png deleted file mode 100644 index db6d6693..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_560_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_561_logo.png b/tests/input/res/drawable-xhdpi/ch_561_logo.png deleted file mode 100644 index b53f08f0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_561_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_562_logo.png b/tests/input/res/drawable-xhdpi/ch_562_logo.png deleted file mode 100644 index 970f0174..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_562_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_563_logo.png b/tests/input/res/drawable-xhdpi/ch_563_logo.png deleted file mode 100644 index 1c426378..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_563_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_564_logo.png b/tests/input/res/drawable-xhdpi/ch_564_logo.png deleted file mode 100644 index 61a89f16..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_564_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_565_logo.png b/tests/input/res/drawable-xhdpi/ch_565_logo.png deleted file mode 100644 index 956342b2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_565_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_566_logo.png b/tests/input/res/drawable-xhdpi/ch_566_logo.png deleted file mode 100644 index a935ec4a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_566_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_567_logo.png b/tests/input/res/drawable-xhdpi/ch_567_logo.png deleted file mode 100644 index 4d6e407e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_567_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_568_logo.png b/tests/input/res/drawable-xhdpi/ch_568_logo.png deleted file mode 100644 index f4578ed0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_568_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_569_logo.png b/tests/input/res/drawable-xhdpi/ch_569_logo.png deleted file mode 100644 index 807c7323..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_569_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_56_logo.png b/tests/input/res/drawable-xhdpi/ch_56_logo.png deleted file mode 100644 index dc296510..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_56_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_570_logo.png b/tests/input/res/drawable-xhdpi/ch_570_logo.png deleted file mode 100644 index ef03c654..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_570_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_571_logo.png b/tests/input/res/drawable-xhdpi/ch_571_logo.png deleted file mode 100644 index 422f9aa4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_571_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_572_logo.png b/tests/input/res/drawable-xhdpi/ch_572_logo.png deleted file mode 100644 index 68d08376..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_572_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_573_logo.png b/tests/input/res/drawable-xhdpi/ch_573_logo.png deleted file mode 100644 index 1d8ca402..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_573_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_574_logo.png b/tests/input/res/drawable-xhdpi/ch_574_logo.png deleted file mode 100644 index f582224a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_574_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_575_logo.png b/tests/input/res/drawable-xhdpi/ch_575_logo.png deleted file mode 100644 index 44fc827b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_575_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_576_logo.png b/tests/input/res/drawable-xhdpi/ch_576_logo.png deleted file mode 100644 index a8c9b36d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_576_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_577_logo.png b/tests/input/res/drawable-xhdpi/ch_577_logo.png deleted file mode 100644 index 328c67a9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_577_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_578_logo.png b/tests/input/res/drawable-xhdpi/ch_578_logo.png deleted file mode 100644 index d856dac5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_578_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_579_logo.png b/tests/input/res/drawable-xhdpi/ch_579_logo.png deleted file mode 100644 index 2a989c37..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_579_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_57_logo.png b/tests/input/res/drawable-xhdpi/ch_57_logo.png deleted file mode 100644 index 72981424..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_57_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_580_logo.png b/tests/input/res/drawable-xhdpi/ch_580_logo.png deleted file mode 100644 index 4c8034c8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_580_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_581_logo.png b/tests/input/res/drawable-xhdpi/ch_581_logo.png deleted file mode 100644 index 7423ef65..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_581_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_582_logo.png b/tests/input/res/drawable-xhdpi/ch_582_logo.png deleted file mode 100644 index ffbc803d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_582_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_583_logo.png b/tests/input/res/drawable-xhdpi/ch_583_logo.png deleted file mode 100644 index d6b92dce..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_583_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_584_logo.png b/tests/input/res/drawable-xhdpi/ch_584_logo.png deleted file mode 100644 index cecbe6c7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_584_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_585_logo.png b/tests/input/res/drawable-xhdpi/ch_585_logo.png deleted file mode 100644 index 99b994f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_585_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_586_logo.png b/tests/input/res/drawable-xhdpi/ch_586_logo.png deleted file mode 100644 index a54afdd4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_586_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_587_logo.png b/tests/input/res/drawable-xhdpi/ch_587_logo.png deleted file mode 100644 index e09f40ff..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_587_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_588_logo.png b/tests/input/res/drawable-xhdpi/ch_588_logo.png deleted file mode 100644 index e4b3c192..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_588_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_589_logo.png b/tests/input/res/drawable-xhdpi/ch_589_logo.png deleted file mode 100644 index 2c451a54..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_589_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_58_logo.png b/tests/input/res/drawable-xhdpi/ch_58_logo.png deleted file mode 100644 index 07f71a01..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_58_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_590_logo.png b/tests/input/res/drawable-xhdpi/ch_590_logo.png deleted file mode 100644 index 56e5e60c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_590_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_591_logo.png b/tests/input/res/drawable-xhdpi/ch_591_logo.png deleted file mode 100644 index 09e1a554..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_591_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_592_logo.png b/tests/input/res/drawable-xhdpi/ch_592_logo.png deleted file mode 100644 index 82635ce4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_592_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_593_logo.png b/tests/input/res/drawable-xhdpi/ch_593_logo.png deleted file mode 100644 index 21f8e54c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_593_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_594_logo.png b/tests/input/res/drawable-xhdpi/ch_594_logo.png deleted file mode 100644 index 8de16c08..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_594_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_595_logo.png b/tests/input/res/drawable-xhdpi/ch_595_logo.png deleted file mode 100644 index 7fd01896..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_595_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_596_logo.png b/tests/input/res/drawable-xhdpi/ch_596_logo.png deleted file mode 100644 index 35452d61..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_596_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_597_logo.png b/tests/input/res/drawable-xhdpi/ch_597_logo.png deleted file mode 100644 index c812338a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_597_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_598_logo.png b/tests/input/res/drawable-xhdpi/ch_598_logo.png deleted file mode 100644 index 6a7a4da0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_598_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_599_logo.png b/tests/input/res/drawable-xhdpi/ch_599_logo.png deleted file mode 100644 index 9bb000ac..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_599_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_59_logo.png b/tests/input/res/drawable-xhdpi/ch_59_logo.png deleted file mode 100644 index a7a0155f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_59_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_5_logo.png b/tests/input/res/drawable-xhdpi/ch_5_logo.png deleted file mode 100644 index 2de0c9cc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_5_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_600_logo.png b/tests/input/res/drawable-xhdpi/ch_600_logo.png deleted file mode 100644 index e3b247e9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_600_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_601_logo.png b/tests/input/res/drawable-xhdpi/ch_601_logo.png deleted file mode 100644 index ff572b8c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_601_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_602_logo.png b/tests/input/res/drawable-xhdpi/ch_602_logo.png deleted file mode 100644 index 56d0505a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_602_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_603_logo.png b/tests/input/res/drawable-xhdpi/ch_603_logo.png deleted file mode 100644 index e0d30043..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_603_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_604_logo.png b/tests/input/res/drawable-xhdpi/ch_604_logo.png deleted file mode 100644 index 4a592ff9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_604_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_605_logo.png b/tests/input/res/drawable-xhdpi/ch_605_logo.png deleted file mode 100644 index e415b276..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_605_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_606_logo.png b/tests/input/res/drawable-xhdpi/ch_606_logo.png deleted file mode 100644 index 2c7b490b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_606_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_607_logo.png b/tests/input/res/drawable-xhdpi/ch_607_logo.png deleted file mode 100644 index 649d0327..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_607_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_608_logo.png b/tests/input/res/drawable-xhdpi/ch_608_logo.png deleted file mode 100644 index 5581b4d9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_608_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_609_logo.png b/tests/input/res/drawable-xhdpi/ch_609_logo.png deleted file mode 100644 index da8f4bb3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_609_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_60_logo.png b/tests/input/res/drawable-xhdpi/ch_60_logo.png deleted file mode 100644 index 0ee771f8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_60_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_610_logo.png b/tests/input/res/drawable-xhdpi/ch_610_logo.png deleted file mode 100644 index 7cdbcbd6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_610_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_611_logo.png b/tests/input/res/drawable-xhdpi/ch_611_logo.png deleted file mode 100644 index b14d3f54..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_611_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_612_logo.png b/tests/input/res/drawable-xhdpi/ch_612_logo.png deleted file mode 100644 index d56de9ff..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_612_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_613_logo.png b/tests/input/res/drawable-xhdpi/ch_613_logo.png deleted file mode 100644 index 7cfec8b5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_613_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_614_logo.png b/tests/input/res/drawable-xhdpi/ch_614_logo.png deleted file mode 100644 index cc7841ce..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_614_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_615_logo.png b/tests/input/res/drawable-xhdpi/ch_615_logo.png deleted file mode 100644 index 6c38d269..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_615_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_616_logo.png b/tests/input/res/drawable-xhdpi/ch_616_logo.png deleted file mode 100644 index e11b777d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_616_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_617_logo.png b/tests/input/res/drawable-xhdpi/ch_617_logo.png deleted file mode 100644 index 78aeb608..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_617_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_618_logo.png b/tests/input/res/drawable-xhdpi/ch_618_logo.png deleted file mode 100644 index e1f883fb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_618_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_619_logo.png b/tests/input/res/drawable-xhdpi/ch_619_logo.png deleted file mode 100644 index 75f4eeba..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_619_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_61_logo.png b/tests/input/res/drawable-xhdpi/ch_61_logo.png deleted file mode 100644 index 801cb923..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_61_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_620_logo.png b/tests/input/res/drawable-xhdpi/ch_620_logo.png deleted file mode 100644 index b6380b73..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_620_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_621_logo.png b/tests/input/res/drawable-xhdpi/ch_621_logo.png deleted file mode 100644 index e6a53185..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_621_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_622_logo.png b/tests/input/res/drawable-xhdpi/ch_622_logo.png deleted file mode 100644 index 1cde4b8f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_622_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_623_logo.png b/tests/input/res/drawable-xhdpi/ch_623_logo.png deleted file mode 100644 index f271e516..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_623_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_624_logo.png b/tests/input/res/drawable-xhdpi/ch_624_logo.png deleted file mode 100644 index 21343a8c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_624_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_625_logo.png b/tests/input/res/drawable-xhdpi/ch_625_logo.png deleted file mode 100644 index e15b9853..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_625_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_626_logo.png b/tests/input/res/drawable-xhdpi/ch_626_logo.png deleted file mode 100644 index c1f8e273..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_626_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_627_logo.png b/tests/input/res/drawable-xhdpi/ch_627_logo.png deleted file mode 100644 index c07973b5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_627_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_628_logo.png b/tests/input/res/drawable-xhdpi/ch_628_logo.png deleted file mode 100644 index 369240ca..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_628_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_629_logo.png b/tests/input/res/drawable-xhdpi/ch_629_logo.png deleted file mode 100644 index ac9f7bd7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_629_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_62_logo.png b/tests/input/res/drawable-xhdpi/ch_62_logo.png deleted file mode 100644 index c4b48ea7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_62_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_630_logo.png b/tests/input/res/drawable-xhdpi/ch_630_logo.png deleted file mode 100644 index 4959855a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_630_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_631_logo.png b/tests/input/res/drawable-xhdpi/ch_631_logo.png deleted file mode 100644 index e03e2706..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_631_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_632_logo.png b/tests/input/res/drawable-xhdpi/ch_632_logo.png deleted file mode 100644 index 5be9d478..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_632_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_633_logo.png b/tests/input/res/drawable-xhdpi/ch_633_logo.png deleted file mode 100644 index 9c51c4a7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_633_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_634_logo.png b/tests/input/res/drawable-xhdpi/ch_634_logo.png deleted file mode 100644 index 72d30bc6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_634_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_635_logo.png b/tests/input/res/drawable-xhdpi/ch_635_logo.png deleted file mode 100644 index 6c03f3c5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_635_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_636_logo.png b/tests/input/res/drawable-xhdpi/ch_636_logo.png deleted file mode 100644 index 928a67c2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_636_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_637_logo.png b/tests/input/res/drawable-xhdpi/ch_637_logo.png deleted file mode 100644 index 4a07f186..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_637_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_638_logo.png b/tests/input/res/drawable-xhdpi/ch_638_logo.png deleted file mode 100644 index 33813958..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_638_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_639_logo.png b/tests/input/res/drawable-xhdpi/ch_639_logo.png deleted file mode 100644 index be6ba122..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_639_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_63_logo.png b/tests/input/res/drawable-xhdpi/ch_63_logo.png deleted file mode 100644 index dd84e2e6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_63_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_640_logo.png b/tests/input/res/drawable-xhdpi/ch_640_logo.png deleted file mode 100644 index f6e1750c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_640_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_641_logo.png b/tests/input/res/drawable-xhdpi/ch_641_logo.png deleted file mode 100644 index 421019c8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_641_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_642_logo.png b/tests/input/res/drawable-xhdpi/ch_642_logo.png deleted file mode 100644 index e441876c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_642_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_643_logo.png b/tests/input/res/drawable-xhdpi/ch_643_logo.png deleted file mode 100644 index 29745014..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_643_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_644_logo.png b/tests/input/res/drawable-xhdpi/ch_644_logo.png deleted file mode 100644 index f5e4f2a0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_644_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_645_logo.png b/tests/input/res/drawable-xhdpi/ch_645_logo.png deleted file mode 100644 index bfa0d2cd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_645_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_646_logo.png b/tests/input/res/drawable-xhdpi/ch_646_logo.png deleted file mode 100644 index d148f910..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_646_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_647_logo.png b/tests/input/res/drawable-xhdpi/ch_647_logo.png deleted file mode 100644 index cccc2c66..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_647_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_648_logo.png b/tests/input/res/drawable-xhdpi/ch_648_logo.png deleted file mode 100644 index 1fa3cad7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_648_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_649_logo.png b/tests/input/res/drawable-xhdpi/ch_649_logo.png deleted file mode 100644 index 0958fd36..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_649_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_64_logo.png b/tests/input/res/drawable-xhdpi/ch_64_logo.png deleted file mode 100644 index 9a6a84ec..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_64_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_650_logo.png b/tests/input/res/drawable-xhdpi/ch_650_logo.png deleted file mode 100644 index bd3ab1f0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_650_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_651_logo.png b/tests/input/res/drawable-xhdpi/ch_651_logo.png deleted file mode 100644 index 03e859a0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_651_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_652_logo.png b/tests/input/res/drawable-xhdpi/ch_652_logo.png deleted file mode 100644 index 81b61689..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_652_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_653_logo.png b/tests/input/res/drawable-xhdpi/ch_653_logo.png deleted file mode 100644 index f6665e5f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_653_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_654_logo.png b/tests/input/res/drawable-xhdpi/ch_654_logo.png deleted file mode 100644 index 6ca0e57c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_654_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_655_logo.png b/tests/input/res/drawable-xhdpi/ch_655_logo.png deleted file mode 100644 index 51e7b392..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_655_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_656_logo.png b/tests/input/res/drawable-xhdpi/ch_656_logo.png deleted file mode 100644 index bae25480..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_656_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_657_logo.png b/tests/input/res/drawable-xhdpi/ch_657_logo.png deleted file mode 100644 index 029e91c9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_657_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_658_logo.png b/tests/input/res/drawable-xhdpi/ch_658_logo.png deleted file mode 100644 index 61a77eac..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_658_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_659_logo.png b/tests/input/res/drawable-xhdpi/ch_659_logo.png deleted file mode 100644 index f12e97ee..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_659_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_65_logo.png b/tests/input/res/drawable-xhdpi/ch_65_logo.png deleted file mode 100644 index b770749e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_65_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_660_logo.png b/tests/input/res/drawable-xhdpi/ch_660_logo.png deleted file mode 100644 index 1408fb45..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_660_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_661_logo.png b/tests/input/res/drawable-xhdpi/ch_661_logo.png deleted file mode 100644 index f8224b76..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_661_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_662_logo.png b/tests/input/res/drawable-xhdpi/ch_662_logo.png deleted file mode 100644 index 08d36a1b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_662_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_663_logo.png b/tests/input/res/drawable-xhdpi/ch_663_logo.png deleted file mode 100644 index b95c596c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_663_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_664_logo.png b/tests/input/res/drawable-xhdpi/ch_664_logo.png deleted file mode 100644 index 4c42427a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_664_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_665_logo.png b/tests/input/res/drawable-xhdpi/ch_665_logo.png deleted file mode 100644 index c8918dce..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_665_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_666_logo.png b/tests/input/res/drawable-xhdpi/ch_666_logo.png deleted file mode 100644 index 6839f828..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_666_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_667_logo.png b/tests/input/res/drawable-xhdpi/ch_667_logo.png deleted file mode 100644 index 28702195..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_667_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_668_logo.png b/tests/input/res/drawable-xhdpi/ch_668_logo.png deleted file mode 100644 index 944a8d46..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_668_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_669_logo.png b/tests/input/res/drawable-xhdpi/ch_669_logo.png deleted file mode 100644 index 2677744f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_669_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_66_logo.png b/tests/input/res/drawable-xhdpi/ch_66_logo.png deleted file mode 100644 index e298eb86..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_66_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_670_logo.png b/tests/input/res/drawable-xhdpi/ch_670_logo.png deleted file mode 100644 index bcb1d377..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_670_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_671_logo.png b/tests/input/res/drawable-xhdpi/ch_671_logo.png deleted file mode 100644 index 3e01dba8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_671_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_672_logo.png b/tests/input/res/drawable-xhdpi/ch_672_logo.png deleted file mode 100644 index 478d3f22..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_672_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_673_logo.png b/tests/input/res/drawable-xhdpi/ch_673_logo.png deleted file mode 100644 index 123522db..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_673_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_674_logo.png b/tests/input/res/drawable-xhdpi/ch_674_logo.png deleted file mode 100644 index 25c44806..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_674_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_675_logo.png b/tests/input/res/drawable-xhdpi/ch_675_logo.png deleted file mode 100644 index 223ba448..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_675_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_676_logo.png b/tests/input/res/drawable-xhdpi/ch_676_logo.png deleted file mode 100644 index 12cca720..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_676_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_677_logo.png b/tests/input/res/drawable-xhdpi/ch_677_logo.png deleted file mode 100644 index ec735de1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_677_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_678_logo.png b/tests/input/res/drawable-xhdpi/ch_678_logo.png deleted file mode 100644 index 1eee9496..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_678_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_679_logo.png b/tests/input/res/drawable-xhdpi/ch_679_logo.png deleted file mode 100644 index 231b68eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_679_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_67_logo.png b/tests/input/res/drawable-xhdpi/ch_67_logo.png deleted file mode 100644 index 5fcf7946..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_67_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_680_logo.png b/tests/input/res/drawable-xhdpi/ch_680_logo.png deleted file mode 100644 index 24c09b87..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_680_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_681_logo.png b/tests/input/res/drawable-xhdpi/ch_681_logo.png deleted file mode 100644 index 2bae5da6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_681_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_682_logo.png b/tests/input/res/drawable-xhdpi/ch_682_logo.png deleted file mode 100644 index 6bb1047f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_682_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_683_logo.png b/tests/input/res/drawable-xhdpi/ch_683_logo.png deleted file mode 100644 index 04be69f4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_683_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_684_logo.png b/tests/input/res/drawable-xhdpi/ch_684_logo.png deleted file mode 100644 index 02e0666d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_684_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_685_logo.png b/tests/input/res/drawable-xhdpi/ch_685_logo.png deleted file mode 100644 index ff410e54..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_685_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_686_logo.png b/tests/input/res/drawable-xhdpi/ch_686_logo.png deleted file mode 100644 index f93e7d7f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_686_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_687_logo.png b/tests/input/res/drawable-xhdpi/ch_687_logo.png deleted file mode 100644 index a97a96f4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_687_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_688_logo.png b/tests/input/res/drawable-xhdpi/ch_688_logo.png deleted file mode 100644 index f094f64c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_688_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_689_logo.png b/tests/input/res/drawable-xhdpi/ch_689_logo.png deleted file mode 100644 index c557b3f1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_689_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_68_logo.png b/tests/input/res/drawable-xhdpi/ch_68_logo.png deleted file mode 100644 index 957bcb55..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_68_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_690_logo.png b/tests/input/res/drawable-xhdpi/ch_690_logo.png deleted file mode 100644 index ee692d35..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_690_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_691_logo.png b/tests/input/res/drawable-xhdpi/ch_691_logo.png deleted file mode 100644 index 7087605e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_691_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_692_logo.png b/tests/input/res/drawable-xhdpi/ch_692_logo.png deleted file mode 100644 index 902b536f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_692_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_693_logo.png b/tests/input/res/drawable-xhdpi/ch_693_logo.png deleted file mode 100644 index 3fb1634e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_693_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_694_logo.png b/tests/input/res/drawable-xhdpi/ch_694_logo.png deleted file mode 100644 index baaa7b69..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_694_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_695_logo.png b/tests/input/res/drawable-xhdpi/ch_695_logo.png deleted file mode 100644 index 1d957bfd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_695_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_696_logo.png b/tests/input/res/drawable-xhdpi/ch_696_logo.png deleted file mode 100644 index ca8ed9e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_696_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_697_logo.png b/tests/input/res/drawable-xhdpi/ch_697_logo.png deleted file mode 100644 index e01c4825..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_697_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_698_logo.png b/tests/input/res/drawable-xhdpi/ch_698_logo.png deleted file mode 100644 index c6540359..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_698_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_699_logo.png b/tests/input/res/drawable-xhdpi/ch_699_logo.png deleted file mode 100644 index 70a22946..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_699_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_69_logo.png b/tests/input/res/drawable-xhdpi/ch_69_logo.png deleted file mode 100644 index 6ccbe33b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_69_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_6_logo.png b/tests/input/res/drawable-xhdpi/ch_6_logo.png deleted file mode 100644 index 22887a6c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_6_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_700_logo.png b/tests/input/res/drawable-xhdpi/ch_700_logo.png deleted file mode 100644 index 1cbf5379..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_700_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_701_logo.png b/tests/input/res/drawable-xhdpi/ch_701_logo.png deleted file mode 100644 index 9b438c00..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_701_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_702_logo.png b/tests/input/res/drawable-xhdpi/ch_702_logo.png deleted file mode 100644 index 92bef76d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_702_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_703_logo.png b/tests/input/res/drawable-xhdpi/ch_703_logo.png deleted file mode 100644 index 78d680b0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_703_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_704_logo.png b/tests/input/res/drawable-xhdpi/ch_704_logo.png deleted file mode 100644 index fcb61adb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_704_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_705_logo.png b/tests/input/res/drawable-xhdpi/ch_705_logo.png deleted file mode 100644 index 87566617..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_705_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_706_logo.png b/tests/input/res/drawable-xhdpi/ch_706_logo.png deleted file mode 100644 index fe9d775b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_706_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_707_logo.png b/tests/input/res/drawable-xhdpi/ch_707_logo.png deleted file mode 100644 index b3b0451c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_707_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_708_logo.png b/tests/input/res/drawable-xhdpi/ch_708_logo.png deleted file mode 100644 index 2a95b3a4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_708_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_709_logo.png b/tests/input/res/drawable-xhdpi/ch_709_logo.png deleted file mode 100644 index 55111fbe..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_709_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_70_logo.png b/tests/input/res/drawable-xhdpi/ch_70_logo.png deleted file mode 100644 index 1860ec9c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_70_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_710_logo.png b/tests/input/res/drawable-xhdpi/ch_710_logo.png deleted file mode 100644 index 6284e6ee..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_710_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_711_logo.png b/tests/input/res/drawable-xhdpi/ch_711_logo.png deleted file mode 100644 index d55d32ca..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_711_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_712_logo.png b/tests/input/res/drawable-xhdpi/ch_712_logo.png deleted file mode 100644 index 7268941c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_712_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_713_logo.png b/tests/input/res/drawable-xhdpi/ch_713_logo.png deleted file mode 100644 index 781301e4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_713_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_714_logo.png b/tests/input/res/drawable-xhdpi/ch_714_logo.png deleted file mode 100644 index 8145eaac..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_714_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_715_logo.png b/tests/input/res/drawable-xhdpi/ch_715_logo.png deleted file mode 100644 index b7715dc5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_715_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_716_logo.png b/tests/input/res/drawable-xhdpi/ch_716_logo.png deleted file mode 100644 index 0c1f26e4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_716_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_717_logo.png b/tests/input/res/drawable-xhdpi/ch_717_logo.png deleted file mode 100644 index de20828f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_717_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_718_logo.png b/tests/input/res/drawable-xhdpi/ch_718_logo.png deleted file mode 100644 index 8d915ecc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_718_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_719_logo.png b/tests/input/res/drawable-xhdpi/ch_719_logo.png deleted file mode 100644 index bd1c668d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_719_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_71_logo.png b/tests/input/res/drawable-xhdpi/ch_71_logo.png deleted file mode 100644 index e3f8b550..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_71_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_720_logo.png b/tests/input/res/drawable-xhdpi/ch_720_logo.png deleted file mode 100644 index 66a60923..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_720_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_721_logo.png b/tests/input/res/drawable-xhdpi/ch_721_logo.png deleted file mode 100644 index c877d396..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_721_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_722_logo.png b/tests/input/res/drawable-xhdpi/ch_722_logo.png deleted file mode 100644 index 9fe37161..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_722_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_723_logo.png b/tests/input/res/drawable-xhdpi/ch_723_logo.png deleted file mode 100644 index 94563ccd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_723_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_724_logo.png b/tests/input/res/drawable-xhdpi/ch_724_logo.png deleted file mode 100644 index 52e5fcd9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_724_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_725_logo.png b/tests/input/res/drawable-xhdpi/ch_725_logo.png deleted file mode 100644 index 518fefaa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_725_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_726_logo.png b/tests/input/res/drawable-xhdpi/ch_726_logo.png deleted file mode 100644 index 1f28222e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_726_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_727_logo.png b/tests/input/res/drawable-xhdpi/ch_727_logo.png deleted file mode 100644 index bffbc57f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_727_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_728_logo.png b/tests/input/res/drawable-xhdpi/ch_728_logo.png deleted file mode 100644 index f036778b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_728_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_729_logo.png b/tests/input/res/drawable-xhdpi/ch_729_logo.png deleted file mode 100644 index a2838d56..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_729_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_72_logo.png b/tests/input/res/drawable-xhdpi/ch_72_logo.png deleted file mode 100644 index 5b2f971c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_72_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_730_logo.png b/tests/input/res/drawable-xhdpi/ch_730_logo.png deleted file mode 100644 index 443d08b8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_730_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_731_logo.png b/tests/input/res/drawable-xhdpi/ch_731_logo.png deleted file mode 100644 index d07e52a1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_731_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_732_logo.png b/tests/input/res/drawable-xhdpi/ch_732_logo.png deleted file mode 100644 index 56f51b3f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_732_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_733_logo.png b/tests/input/res/drawable-xhdpi/ch_733_logo.png deleted file mode 100644 index 18248b0a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_733_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_734_logo.png b/tests/input/res/drawable-xhdpi/ch_734_logo.png deleted file mode 100644 index fb740e33..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_734_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_735_logo.png b/tests/input/res/drawable-xhdpi/ch_735_logo.png deleted file mode 100644 index 9f83309f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_735_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_736_logo.png b/tests/input/res/drawable-xhdpi/ch_736_logo.png deleted file mode 100644 index 768fc5ef..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_736_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_737_logo.png b/tests/input/res/drawable-xhdpi/ch_737_logo.png deleted file mode 100644 index 6af65c67..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_737_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_738_logo.png b/tests/input/res/drawable-xhdpi/ch_738_logo.png deleted file mode 100644 index 3b908312..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_738_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_739_logo.png b/tests/input/res/drawable-xhdpi/ch_739_logo.png deleted file mode 100644 index fea61e37..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_739_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_73_logo.png b/tests/input/res/drawable-xhdpi/ch_73_logo.png deleted file mode 100644 index 13556e6d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_73_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_740_logo.png b/tests/input/res/drawable-xhdpi/ch_740_logo.png deleted file mode 100644 index 7fe9aa19..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_740_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_741_logo.png b/tests/input/res/drawable-xhdpi/ch_741_logo.png deleted file mode 100644 index a437fd22..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_741_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_742_logo.png b/tests/input/res/drawable-xhdpi/ch_742_logo.png deleted file mode 100644 index 5674dccb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_742_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_743_logo.png b/tests/input/res/drawable-xhdpi/ch_743_logo.png deleted file mode 100644 index dbfcaa51..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_743_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_744_logo.png b/tests/input/res/drawable-xhdpi/ch_744_logo.png deleted file mode 100644 index fe6dd633..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_744_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_745_logo.png b/tests/input/res/drawable-xhdpi/ch_745_logo.png deleted file mode 100644 index 703bc014..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_745_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_746_logo.png b/tests/input/res/drawable-xhdpi/ch_746_logo.png deleted file mode 100644 index 15775a4c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_746_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_747_logo.png b/tests/input/res/drawable-xhdpi/ch_747_logo.png deleted file mode 100644 index d5666758..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_747_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_748_logo.png b/tests/input/res/drawable-xhdpi/ch_748_logo.png deleted file mode 100644 index 3bb80444..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_748_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_749_logo.png b/tests/input/res/drawable-xhdpi/ch_749_logo.png deleted file mode 100644 index 92a6954a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_749_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_74_logo.png b/tests/input/res/drawable-xhdpi/ch_74_logo.png deleted file mode 100644 index 484f4c0d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_74_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_750_logo.png b/tests/input/res/drawable-xhdpi/ch_750_logo.png deleted file mode 100644 index 96af0fa6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_750_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_751_logo.png b/tests/input/res/drawable-xhdpi/ch_751_logo.png deleted file mode 100644 index bddc6d97..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_751_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_752_logo.png b/tests/input/res/drawable-xhdpi/ch_752_logo.png deleted file mode 100644 index 38d693f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_752_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_753_logo.png b/tests/input/res/drawable-xhdpi/ch_753_logo.png deleted file mode 100644 index 3c383de7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_753_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_754_logo.png b/tests/input/res/drawable-xhdpi/ch_754_logo.png deleted file mode 100644 index 04d7b62b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_754_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_755_logo.png b/tests/input/res/drawable-xhdpi/ch_755_logo.png deleted file mode 100644 index d7846b43..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_755_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_756_logo.png b/tests/input/res/drawable-xhdpi/ch_756_logo.png deleted file mode 100644 index 6b51e7d1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_756_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_757_logo.png b/tests/input/res/drawable-xhdpi/ch_757_logo.png deleted file mode 100644 index b396d7c3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_757_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_758_logo.png b/tests/input/res/drawable-xhdpi/ch_758_logo.png deleted file mode 100644 index d77783b7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_758_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_759_logo.png b/tests/input/res/drawable-xhdpi/ch_759_logo.png deleted file mode 100644 index ca26792b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_759_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_75_logo.png b/tests/input/res/drawable-xhdpi/ch_75_logo.png deleted file mode 100644 index aa78479e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_75_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_760_logo.png b/tests/input/res/drawable-xhdpi/ch_760_logo.png deleted file mode 100644 index 5d56db66..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_760_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_761_logo.png b/tests/input/res/drawable-xhdpi/ch_761_logo.png deleted file mode 100644 index e505365d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_761_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_762_logo.png b/tests/input/res/drawable-xhdpi/ch_762_logo.png deleted file mode 100644 index 202b7572..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_762_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_763_logo.png b/tests/input/res/drawable-xhdpi/ch_763_logo.png deleted file mode 100644 index 14af6ab4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_763_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_764_logo.png b/tests/input/res/drawable-xhdpi/ch_764_logo.png deleted file mode 100644 index c4e1ea66..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_764_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_765_logo.png b/tests/input/res/drawable-xhdpi/ch_765_logo.png deleted file mode 100644 index 3b5ded8e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_765_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_766_logo.png b/tests/input/res/drawable-xhdpi/ch_766_logo.png deleted file mode 100644 index 61391524..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_766_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_767_logo.png b/tests/input/res/drawable-xhdpi/ch_767_logo.png deleted file mode 100644 index 4fa0441e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_767_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_768_logo.png b/tests/input/res/drawable-xhdpi/ch_768_logo.png deleted file mode 100644 index 80e8293b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_768_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_769_logo.png b/tests/input/res/drawable-xhdpi/ch_769_logo.png deleted file mode 100644 index 63c2e6cc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_769_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_76_logo.png b/tests/input/res/drawable-xhdpi/ch_76_logo.png deleted file mode 100644 index 3ebf6807..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_76_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_770_logo.png b/tests/input/res/drawable-xhdpi/ch_770_logo.png deleted file mode 100644 index b2ee3584..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_770_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_771_logo.png b/tests/input/res/drawable-xhdpi/ch_771_logo.png deleted file mode 100644 index 9a53a498..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_771_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_772_logo.png b/tests/input/res/drawable-xhdpi/ch_772_logo.png deleted file mode 100644 index 1da9e70d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_772_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_773_logo.png b/tests/input/res/drawable-xhdpi/ch_773_logo.png deleted file mode 100644 index 0f35e46a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_773_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_774_logo.png b/tests/input/res/drawable-xhdpi/ch_774_logo.png deleted file mode 100644 index acaa8857..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_774_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_775_logo.png b/tests/input/res/drawable-xhdpi/ch_775_logo.png deleted file mode 100644 index 70ca99e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_775_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_776_logo.png b/tests/input/res/drawable-xhdpi/ch_776_logo.png deleted file mode 100644 index ab54947d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_776_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_777_logo.png b/tests/input/res/drawable-xhdpi/ch_777_logo.png deleted file mode 100644 index ed378329..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_777_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_778_logo.png b/tests/input/res/drawable-xhdpi/ch_778_logo.png deleted file mode 100644 index 01cdc4e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_778_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_779_logo.png b/tests/input/res/drawable-xhdpi/ch_779_logo.png deleted file mode 100644 index ca5175f7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_779_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_77_logo.png b/tests/input/res/drawable-xhdpi/ch_77_logo.png deleted file mode 100644 index c22abf24..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_77_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_780_logo.png b/tests/input/res/drawable-xhdpi/ch_780_logo.png deleted file mode 100644 index 2e5f24e2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_780_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_781_logo.png b/tests/input/res/drawable-xhdpi/ch_781_logo.png deleted file mode 100644 index 81e5ed66..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_781_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_782_logo.png b/tests/input/res/drawable-xhdpi/ch_782_logo.png deleted file mode 100644 index 3236afa9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_782_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_783_logo.png b/tests/input/res/drawable-xhdpi/ch_783_logo.png deleted file mode 100644 index a600800e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_783_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_784_logo.png b/tests/input/res/drawable-xhdpi/ch_784_logo.png deleted file mode 100644 index cd97c5eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_784_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_785_logo.png b/tests/input/res/drawable-xhdpi/ch_785_logo.png deleted file mode 100644 index d50fd82a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_785_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_786_logo.png b/tests/input/res/drawable-xhdpi/ch_786_logo.png deleted file mode 100644 index 7a93ef71..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_786_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_787_logo.png b/tests/input/res/drawable-xhdpi/ch_787_logo.png deleted file mode 100644 index 62b5cf6b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_787_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_788_logo.png b/tests/input/res/drawable-xhdpi/ch_788_logo.png deleted file mode 100644 index c82602d3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_788_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_789_logo.png b/tests/input/res/drawable-xhdpi/ch_789_logo.png deleted file mode 100644 index 7dfbeeb3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_789_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_78_logo.png b/tests/input/res/drawable-xhdpi/ch_78_logo.png deleted file mode 100644 index 03fb415f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_78_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_790_logo.png b/tests/input/res/drawable-xhdpi/ch_790_logo.png deleted file mode 100644 index d44c2181..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_790_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_791_logo.png b/tests/input/res/drawable-xhdpi/ch_791_logo.png deleted file mode 100644 index ee81b961..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_791_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_792_logo.png b/tests/input/res/drawable-xhdpi/ch_792_logo.png deleted file mode 100644 index 37715025..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_792_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_793_logo.png b/tests/input/res/drawable-xhdpi/ch_793_logo.png deleted file mode 100644 index eca3a477..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_793_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_794_logo.png b/tests/input/res/drawable-xhdpi/ch_794_logo.png deleted file mode 100644 index fbbfc372..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_794_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_795_logo.png b/tests/input/res/drawable-xhdpi/ch_795_logo.png deleted file mode 100644 index 0366a2bd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_795_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_796_logo.png b/tests/input/res/drawable-xhdpi/ch_796_logo.png deleted file mode 100644 index e13e6bc1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_796_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_797_logo.png b/tests/input/res/drawable-xhdpi/ch_797_logo.png deleted file mode 100644 index e45decc0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_797_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_798_logo.png b/tests/input/res/drawable-xhdpi/ch_798_logo.png deleted file mode 100644 index 6b9659e2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_798_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_799_logo.png b/tests/input/res/drawable-xhdpi/ch_799_logo.png deleted file mode 100644 index e8cb2ef8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_799_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_79_logo.png b/tests/input/res/drawable-xhdpi/ch_79_logo.png deleted file mode 100644 index 237fa7b2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_79_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_7_logo.png b/tests/input/res/drawable-xhdpi/ch_7_logo.png deleted file mode 100644 index df78066a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_7_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_800_logo.png b/tests/input/res/drawable-xhdpi/ch_800_logo.png deleted file mode 100644 index 6c775941..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_800_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_801_logo.png b/tests/input/res/drawable-xhdpi/ch_801_logo.png deleted file mode 100644 index f0006515..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_801_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_802_logo.png b/tests/input/res/drawable-xhdpi/ch_802_logo.png deleted file mode 100644 index 4c504b58..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_802_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_803_logo.png b/tests/input/res/drawable-xhdpi/ch_803_logo.png deleted file mode 100644 index edfa6869..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_803_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_804_logo.png b/tests/input/res/drawable-xhdpi/ch_804_logo.png deleted file mode 100644 index c7b0dba0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_804_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_805_logo.png b/tests/input/res/drawable-xhdpi/ch_805_logo.png deleted file mode 100644 index bcd6ee28..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_805_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_806_logo.png b/tests/input/res/drawable-xhdpi/ch_806_logo.png deleted file mode 100644 index e926e9f4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_806_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_807_logo.png b/tests/input/res/drawable-xhdpi/ch_807_logo.png deleted file mode 100644 index 25334551..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_807_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_808_logo.png b/tests/input/res/drawable-xhdpi/ch_808_logo.png deleted file mode 100644 index 5d7b51a9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_808_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_809_logo.png b/tests/input/res/drawable-xhdpi/ch_809_logo.png deleted file mode 100644 index 2f6a8e31..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_809_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_80_logo.png b/tests/input/res/drawable-xhdpi/ch_80_logo.png deleted file mode 100644 index ff7a55c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_80_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_810_logo.png b/tests/input/res/drawable-xhdpi/ch_810_logo.png deleted file mode 100644 index 1a083992..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_810_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_811_logo.png b/tests/input/res/drawable-xhdpi/ch_811_logo.png deleted file mode 100644 index 070df630..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_811_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_812_logo.png b/tests/input/res/drawable-xhdpi/ch_812_logo.png deleted file mode 100644 index 376e68cb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_812_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_813_logo.png b/tests/input/res/drawable-xhdpi/ch_813_logo.png deleted file mode 100644 index d72f0a8e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_813_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_814_logo.png b/tests/input/res/drawable-xhdpi/ch_814_logo.png deleted file mode 100644 index b50d9ad1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_814_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_815_logo.png b/tests/input/res/drawable-xhdpi/ch_815_logo.png deleted file mode 100644 index fdbc4cdd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_815_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_816_logo.png b/tests/input/res/drawable-xhdpi/ch_816_logo.png deleted file mode 100644 index f1212200..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_816_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_817_logo.png b/tests/input/res/drawable-xhdpi/ch_817_logo.png deleted file mode 100644 index be4fa008..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_817_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_818_logo.png b/tests/input/res/drawable-xhdpi/ch_818_logo.png deleted file mode 100644 index c9d087d7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_818_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_819_logo.png b/tests/input/res/drawable-xhdpi/ch_819_logo.png deleted file mode 100644 index b8cab83d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_819_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_81_logo.png b/tests/input/res/drawable-xhdpi/ch_81_logo.png deleted file mode 100644 index 58f6fc00..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_81_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_820_logo.png b/tests/input/res/drawable-xhdpi/ch_820_logo.png deleted file mode 100644 index 3bf591fb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_820_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_821_logo.png b/tests/input/res/drawable-xhdpi/ch_821_logo.png deleted file mode 100644 index 16046f46..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_821_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_822_logo.png b/tests/input/res/drawable-xhdpi/ch_822_logo.png deleted file mode 100644 index 58d688c9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_822_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_823_logo.png b/tests/input/res/drawable-xhdpi/ch_823_logo.png deleted file mode 100644 index 6b70ecc1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_823_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_824_logo.png b/tests/input/res/drawable-xhdpi/ch_824_logo.png deleted file mode 100644 index 4c90c96e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_824_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_825_logo.png b/tests/input/res/drawable-xhdpi/ch_825_logo.png deleted file mode 100644 index 5111136c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_825_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_826_logo.png b/tests/input/res/drawable-xhdpi/ch_826_logo.png deleted file mode 100644 index f35941e8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_826_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_827_logo.png b/tests/input/res/drawable-xhdpi/ch_827_logo.png deleted file mode 100644 index 49add1ab..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_827_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_828_logo.png b/tests/input/res/drawable-xhdpi/ch_828_logo.png deleted file mode 100644 index 67936aa4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_828_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_829_logo.png b/tests/input/res/drawable-xhdpi/ch_829_logo.png deleted file mode 100644 index 7bc97945..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_829_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_82_logo.png b/tests/input/res/drawable-xhdpi/ch_82_logo.png deleted file mode 100644 index ddd21127..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_82_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_830_logo.png b/tests/input/res/drawable-xhdpi/ch_830_logo.png deleted file mode 100644 index 3aa669d8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_830_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_831_logo.png b/tests/input/res/drawable-xhdpi/ch_831_logo.png deleted file mode 100644 index f7c2cf1c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_831_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_832_logo.png b/tests/input/res/drawable-xhdpi/ch_832_logo.png deleted file mode 100644 index 19096734..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_832_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_833_logo.png b/tests/input/res/drawable-xhdpi/ch_833_logo.png deleted file mode 100644 index d0a1af2f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_833_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_834_logo.png b/tests/input/res/drawable-xhdpi/ch_834_logo.png deleted file mode 100644 index 52e2ef58..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_834_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_835_logo.png b/tests/input/res/drawable-xhdpi/ch_835_logo.png deleted file mode 100644 index 7ac421e1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_835_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_836_logo.png b/tests/input/res/drawable-xhdpi/ch_836_logo.png deleted file mode 100644 index 7f4577b4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_836_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_837_logo.png b/tests/input/res/drawable-xhdpi/ch_837_logo.png deleted file mode 100644 index df5e1c86..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_837_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_838_logo.png b/tests/input/res/drawable-xhdpi/ch_838_logo.png deleted file mode 100644 index 579557ed..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_838_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_839_logo.png b/tests/input/res/drawable-xhdpi/ch_839_logo.png deleted file mode 100644 index 3988cc5b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_839_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_83_logo.png b/tests/input/res/drawable-xhdpi/ch_83_logo.png deleted file mode 100644 index dd97e15c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_83_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_840_logo.png b/tests/input/res/drawable-xhdpi/ch_840_logo.png deleted file mode 100644 index 29e88d2a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_840_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_841_logo.png b/tests/input/res/drawable-xhdpi/ch_841_logo.png deleted file mode 100644 index 9ee069a4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_841_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_842_logo.png b/tests/input/res/drawable-xhdpi/ch_842_logo.png deleted file mode 100644 index edbd8c57..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_842_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_843_logo.png b/tests/input/res/drawable-xhdpi/ch_843_logo.png deleted file mode 100644 index 7ee70de2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_843_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_844_logo.png b/tests/input/res/drawable-xhdpi/ch_844_logo.png deleted file mode 100644 index 6a0a95a8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_844_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_845_logo.png b/tests/input/res/drawable-xhdpi/ch_845_logo.png deleted file mode 100644 index ae4aa7eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_845_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_846_logo.png b/tests/input/res/drawable-xhdpi/ch_846_logo.png deleted file mode 100644 index 79eb61d9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_846_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_847_logo.png b/tests/input/res/drawable-xhdpi/ch_847_logo.png deleted file mode 100644 index 2a8fd743..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_847_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_848_logo.png b/tests/input/res/drawable-xhdpi/ch_848_logo.png deleted file mode 100644 index c2e82237..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_848_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_849_logo.png b/tests/input/res/drawable-xhdpi/ch_849_logo.png deleted file mode 100644 index 44d58b8d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_849_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_84_logo.png b/tests/input/res/drawable-xhdpi/ch_84_logo.png deleted file mode 100644 index 5ad6a473..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_84_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_850_logo.png b/tests/input/res/drawable-xhdpi/ch_850_logo.png deleted file mode 100644 index 358dc326..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_850_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_851_logo.png b/tests/input/res/drawable-xhdpi/ch_851_logo.png deleted file mode 100644 index 7c5ef9cd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_851_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_852_logo.png b/tests/input/res/drawable-xhdpi/ch_852_logo.png deleted file mode 100644 index 0ab1e98b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_852_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_853_logo.png b/tests/input/res/drawable-xhdpi/ch_853_logo.png deleted file mode 100644 index c5b6a11b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_853_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_854_logo.png b/tests/input/res/drawable-xhdpi/ch_854_logo.png deleted file mode 100644 index 5c34d4e2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_854_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_855_logo.png b/tests/input/res/drawable-xhdpi/ch_855_logo.png deleted file mode 100644 index b8f10da8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_855_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_856_logo.png b/tests/input/res/drawable-xhdpi/ch_856_logo.png deleted file mode 100644 index 1b790da7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_856_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_857_logo.png b/tests/input/res/drawable-xhdpi/ch_857_logo.png deleted file mode 100644 index a7f3b55d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_857_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_858_logo.png b/tests/input/res/drawable-xhdpi/ch_858_logo.png deleted file mode 100644 index e3555be6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_858_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_859_logo.png b/tests/input/res/drawable-xhdpi/ch_859_logo.png deleted file mode 100644 index 970ee63e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_859_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_85_logo.png b/tests/input/res/drawable-xhdpi/ch_85_logo.png deleted file mode 100644 index 35c5c992..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_85_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_860_logo.png b/tests/input/res/drawable-xhdpi/ch_860_logo.png deleted file mode 100644 index 72720501..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_860_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_861_logo.png b/tests/input/res/drawable-xhdpi/ch_861_logo.png deleted file mode 100644 index bf6d0be9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_861_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_862_logo.png b/tests/input/res/drawable-xhdpi/ch_862_logo.png deleted file mode 100644 index ee6c88f2..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_862_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_863_logo.png b/tests/input/res/drawable-xhdpi/ch_863_logo.png deleted file mode 100644 index 9b92ef30..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_863_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_864_logo.png b/tests/input/res/drawable-xhdpi/ch_864_logo.png deleted file mode 100644 index 2024e828..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_864_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_865_logo.png b/tests/input/res/drawable-xhdpi/ch_865_logo.png deleted file mode 100644 index 80b87668..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_865_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_866_logo.png b/tests/input/res/drawable-xhdpi/ch_866_logo.png deleted file mode 100644 index 6dfe8538..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_866_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_867_logo.png b/tests/input/res/drawable-xhdpi/ch_867_logo.png deleted file mode 100644 index 6cb51f22..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_867_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_868_logo.png b/tests/input/res/drawable-xhdpi/ch_868_logo.png deleted file mode 100644 index e179e5cf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_868_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_869_logo.png b/tests/input/res/drawable-xhdpi/ch_869_logo.png deleted file mode 100644 index 940780eb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_869_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_86_logo.png b/tests/input/res/drawable-xhdpi/ch_86_logo.png deleted file mode 100644 index 93bbad73..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_86_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_870_logo.png b/tests/input/res/drawable-xhdpi/ch_870_logo.png deleted file mode 100644 index caa05fe8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_870_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_871_logo.png b/tests/input/res/drawable-xhdpi/ch_871_logo.png deleted file mode 100644 index 9085b8a8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_871_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_872_logo.png b/tests/input/res/drawable-xhdpi/ch_872_logo.png deleted file mode 100644 index 695dabaf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_872_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_873_logo.png b/tests/input/res/drawable-xhdpi/ch_873_logo.png deleted file mode 100644 index 21871509..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_873_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_874_logo.png b/tests/input/res/drawable-xhdpi/ch_874_logo.png deleted file mode 100644 index 2bbcf9cc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_874_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_875_logo.png b/tests/input/res/drawable-xhdpi/ch_875_logo.png deleted file mode 100644 index 494b8ebc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_875_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_876_logo.png b/tests/input/res/drawable-xhdpi/ch_876_logo.png deleted file mode 100644 index eded05e4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_876_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_877_logo.png b/tests/input/res/drawable-xhdpi/ch_877_logo.png deleted file mode 100644 index cf22732d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_877_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_878_logo.png b/tests/input/res/drawable-xhdpi/ch_878_logo.png deleted file mode 100644 index 3ca881cf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_878_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_879_logo.png b/tests/input/res/drawable-xhdpi/ch_879_logo.png deleted file mode 100644 index 8f629b25..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_879_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_87_logo.png b/tests/input/res/drawable-xhdpi/ch_87_logo.png deleted file mode 100644 index 1a661da8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_87_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_880_logo.png b/tests/input/res/drawable-xhdpi/ch_880_logo.png deleted file mode 100644 index f23459d8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_880_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_881_logo.png b/tests/input/res/drawable-xhdpi/ch_881_logo.png deleted file mode 100644 index 810320be..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_881_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_882_logo.png b/tests/input/res/drawable-xhdpi/ch_882_logo.png deleted file mode 100644 index cee19fc9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_882_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_883_logo.png b/tests/input/res/drawable-xhdpi/ch_883_logo.png deleted file mode 100644 index b7cd2a11..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_883_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_884_logo.png b/tests/input/res/drawable-xhdpi/ch_884_logo.png deleted file mode 100644 index ee24315c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_884_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_885_logo.png b/tests/input/res/drawable-xhdpi/ch_885_logo.png deleted file mode 100644 index aac4f31f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_885_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_886_logo.png b/tests/input/res/drawable-xhdpi/ch_886_logo.png deleted file mode 100644 index d929436e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_886_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_887_logo.png b/tests/input/res/drawable-xhdpi/ch_887_logo.png deleted file mode 100644 index 4d38b4c9..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_887_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_888_logo.png b/tests/input/res/drawable-xhdpi/ch_888_logo.png deleted file mode 100644 index b8bf4191..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_888_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_889_logo.png b/tests/input/res/drawable-xhdpi/ch_889_logo.png deleted file mode 100644 index e38f60dd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_889_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_88_logo.png b/tests/input/res/drawable-xhdpi/ch_88_logo.png deleted file mode 100644 index 9cf19a3b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_88_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_890_logo.png b/tests/input/res/drawable-xhdpi/ch_890_logo.png deleted file mode 100644 index 46e8755c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_890_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_891_logo.png b/tests/input/res/drawable-xhdpi/ch_891_logo.png deleted file mode 100644 index 8fcae156..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_891_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_892_logo.png b/tests/input/res/drawable-xhdpi/ch_892_logo.png deleted file mode 100644 index 4672b7e1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_892_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_893_logo.png b/tests/input/res/drawable-xhdpi/ch_893_logo.png deleted file mode 100644 index 170586f5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_893_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_894_logo.png b/tests/input/res/drawable-xhdpi/ch_894_logo.png deleted file mode 100644 index 4b366898..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_894_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_895_logo.png b/tests/input/res/drawable-xhdpi/ch_895_logo.png deleted file mode 100644 index 1855d24c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_895_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_896_logo.png b/tests/input/res/drawable-xhdpi/ch_896_logo.png deleted file mode 100644 index dc47d447..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_896_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_897_logo.png b/tests/input/res/drawable-xhdpi/ch_897_logo.png deleted file mode 100644 index 909a60e0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_897_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_898_logo.png b/tests/input/res/drawable-xhdpi/ch_898_logo.png deleted file mode 100644 index a57a6344..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_898_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_899_logo.png b/tests/input/res/drawable-xhdpi/ch_899_logo.png deleted file mode 100644 index fa99418a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_899_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_89_logo.png b/tests/input/res/drawable-xhdpi/ch_89_logo.png deleted file mode 100644 index 0c7edc8c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_89_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_8_logo.png b/tests/input/res/drawable-xhdpi/ch_8_logo.png deleted file mode 100644 index 0886b851..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_8_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_900_logo.png b/tests/input/res/drawable-xhdpi/ch_900_logo.png deleted file mode 100644 index cf6327bb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_900_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_901_logo.png b/tests/input/res/drawable-xhdpi/ch_901_logo.png deleted file mode 100644 index 17e1c9ef..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_901_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_902_logo.png b/tests/input/res/drawable-xhdpi/ch_902_logo.png deleted file mode 100644 index f98b40cb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_902_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_903_logo.png b/tests/input/res/drawable-xhdpi/ch_903_logo.png deleted file mode 100644 index d3d331d8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_903_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_904_logo.png b/tests/input/res/drawable-xhdpi/ch_904_logo.png deleted file mode 100644 index d3e29be6..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_904_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_905_logo.png b/tests/input/res/drawable-xhdpi/ch_905_logo.png deleted file mode 100644 index 65ab0adc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_905_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_906_logo.png b/tests/input/res/drawable-xhdpi/ch_906_logo.png deleted file mode 100644 index 1cc924d7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_906_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_907_logo.png b/tests/input/res/drawable-xhdpi/ch_907_logo.png deleted file mode 100644 index 9fd37e6e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_907_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_908_logo.png b/tests/input/res/drawable-xhdpi/ch_908_logo.png deleted file mode 100644 index 15597dba..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_908_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_909_logo.png b/tests/input/res/drawable-xhdpi/ch_909_logo.png deleted file mode 100644 index db80fbef..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_909_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_90_logo.png b/tests/input/res/drawable-xhdpi/ch_90_logo.png deleted file mode 100644 index 2f2960da..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_90_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_910_logo.png b/tests/input/res/drawable-xhdpi/ch_910_logo.png deleted file mode 100644 index dd6f8a31..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_910_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_911_logo.png b/tests/input/res/drawable-xhdpi/ch_911_logo.png deleted file mode 100644 index 23266b64..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_911_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_912_logo.png b/tests/input/res/drawable-xhdpi/ch_912_logo.png deleted file mode 100644 index 677197d5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_912_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_913_logo.png b/tests/input/res/drawable-xhdpi/ch_913_logo.png deleted file mode 100644 index 41c4ae03..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_913_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_914_logo.png b/tests/input/res/drawable-xhdpi/ch_914_logo.png deleted file mode 100644 index 2a2b4a2d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_914_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_915_logo.png b/tests/input/res/drawable-xhdpi/ch_915_logo.png deleted file mode 100644 index 85941edf..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_915_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_916_logo.png b/tests/input/res/drawable-xhdpi/ch_916_logo.png deleted file mode 100644 index fbf4a41e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_916_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_917_logo.png b/tests/input/res/drawable-xhdpi/ch_917_logo.png deleted file mode 100644 index 018dff5e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_917_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_918_logo.png b/tests/input/res/drawable-xhdpi/ch_918_logo.png deleted file mode 100644 index 2c8b0b80..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_918_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_919_logo.png b/tests/input/res/drawable-xhdpi/ch_919_logo.png deleted file mode 100644 index de2e6073..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_919_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_91_logo.png b/tests/input/res/drawable-xhdpi/ch_91_logo.png deleted file mode 100644 index 3992165b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_91_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_920_logo.png b/tests/input/res/drawable-xhdpi/ch_920_logo.png deleted file mode 100644 index 33839469..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_920_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_921_logo.png b/tests/input/res/drawable-xhdpi/ch_921_logo.png deleted file mode 100644 index 019c36f5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_921_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_922_logo.png b/tests/input/res/drawable-xhdpi/ch_922_logo.png deleted file mode 100644 index 79e4e07d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_922_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_923_logo.png b/tests/input/res/drawable-xhdpi/ch_923_logo.png deleted file mode 100644 index 6738d938..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_923_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_924_logo.png b/tests/input/res/drawable-xhdpi/ch_924_logo.png deleted file mode 100644 index 3cbc4e30..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_924_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_925_logo.png b/tests/input/res/drawable-xhdpi/ch_925_logo.png deleted file mode 100644 index 500111c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_925_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_926_logo.png b/tests/input/res/drawable-xhdpi/ch_926_logo.png deleted file mode 100644 index bd3f94ad..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_926_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_927_logo.png b/tests/input/res/drawable-xhdpi/ch_927_logo.png deleted file mode 100644 index 51ad2eaa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_927_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_928_logo.png b/tests/input/res/drawable-xhdpi/ch_928_logo.png deleted file mode 100644 index 6f579bb3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_928_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_929_logo.png b/tests/input/res/drawable-xhdpi/ch_929_logo.png deleted file mode 100644 index 79f11bb1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_929_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_92_logo.png b/tests/input/res/drawable-xhdpi/ch_92_logo.png deleted file mode 100644 index 25508036..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_92_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_930_logo.png b/tests/input/res/drawable-xhdpi/ch_930_logo.png deleted file mode 100644 index 4c06ad09..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_930_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_931_logo.png b/tests/input/res/drawable-xhdpi/ch_931_logo.png deleted file mode 100644 index b7ac61ff..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_931_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_932_logo.png b/tests/input/res/drawable-xhdpi/ch_932_logo.png deleted file mode 100644 index cac5a477..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_932_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_933_logo.png b/tests/input/res/drawable-xhdpi/ch_933_logo.png deleted file mode 100644 index 959f957f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_933_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_934_logo.png b/tests/input/res/drawable-xhdpi/ch_934_logo.png deleted file mode 100644 index e9e5b263..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_934_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_935_logo.png b/tests/input/res/drawable-xhdpi/ch_935_logo.png deleted file mode 100644 index 9741307e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_935_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_936_logo.png b/tests/input/res/drawable-xhdpi/ch_936_logo.png deleted file mode 100644 index 2c5964de..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_936_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_937_logo.png b/tests/input/res/drawable-xhdpi/ch_937_logo.png deleted file mode 100644 index 5392a73c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_937_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_938_logo.png b/tests/input/res/drawable-xhdpi/ch_938_logo.png deleted file mode 100644 index 8350910c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_938_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_939_logo.png b/tests/input/res/drawable-xhdpi/ch_939_logo.png deleted file mode 100644 index f0635df0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_939_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_93_logo.png b/tests/input/res/drawable-xhdpi/ch_93_logo.png deleted file mode 100644 index 976c07cb..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_93_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_940_logo.png b/tests/input/res/drawable-xhdpi/ch_940_logo.png deleted file mode 100644 index b9c76dbc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_940_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_941_logo.png b/tests/input/res/drawable-xhdpi/ch_941_logo.png deleted file mode 100644 index d9e3361f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_941_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_942_logo.png b/tests/input/res/drawable-xhdpi/ch_942_logo.png deleted file mode 100644 index 997643c1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_942_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_943_logo.png b/tests/input/res/drawable-xhdpi/ch_943_logo.png deleted file mode 100644 index 4c6f1752..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_943_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_944_logo.png b/tests/input/res/drawable-xhdpi/ch_944_logo.png deleted file mode 100644 index 40a11f40..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_944_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_945_logo.png b/tests/input/res/drawable-xhdpi/ch_945_logo.png deleted file mode 100644 index 9cc401a7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_945_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_946_logo.png b/tests/input/res/drawable-xhdpi/ch_946_logo.png deleted file mode 100644 index bdb5bab4..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_946_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_947_logo.png b/tests/input/res/drawable-xhdpi/ch_947_logo.png deleted file mode 100644 index 541b632d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_947_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_948_logo.png b/tests/input/res/drawable-xhdpi/ch_948_logo.png deleted file mode 100644 index 4db04738..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_948_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_949_logo.png b/tests/input/res/drawable-xhdpi/ch_949_logo.png deleted file mode 100644 index 399a4e51..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_949_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_94_logo.png b/tests/input/res/drawable-xhdpi/ch_94_logo.png deleted file mode 100644 index e22800b8..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_94_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_950_logo.png b/tests/input/res/drawable-xhdpi/ch_950_logo.png deleted file mode 100644 index 5db6be41..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_950_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_951_logo.png b/tests/input/res/drawable-xhdpi/ch_951_logo.png deleted file mode 100644 index 6fbf5951..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_951_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_952_logo.png b/tests/input/res/drawable-xhdpi/ch_952_logo.png deleted file mode 100644 index 0e5e39aa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_952_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_953_logo.png b/tests/input/res/drawable-xhdpi/ch_953_logo.png deleted file mode 100644 index 430a5e4d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_953_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_954_logo.png b/tests/input/res/drawable-xhdpi/ch_954_logo.png deleted file mode 100644 index 8ddc6cee..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_954_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_955_logo.png b/tests/input/res/drawable-xhdpi/ch_955_logo.png deleted file mode 100644 index 69ec92b5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_955_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_956_logo.png b/tests/input/res/drawable-xhdpi/ch_956_logo.png deleted file mode 100644 index bc1edbd7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_956_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_957_logo.png b/tests/input/res/drawable-xhdpi/ch_957_logo.png deleted file mode 100644 index 20db4202..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_957_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_958_logo.png b/tests/input/res/drawable-xhdpi/ch_958_logo.png deleted file mode 100644 index 8e9a7a68..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_958_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_959_logo.png b/tests/input/res/drawable-xhdpi/ch_959_logo.png deleted file mode 100644 index 4d61c092..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_959_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_95_logo.png b/tests/input/res/drawable-xhdpi/ch_95_logo.png deleted file mode 100644 index 50cc5990..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_95_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_960_logo.png b/tests/input/res/drawable-xhdpi/ch_960_logo.png deleted file mode 100644 index b2a54413..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_960_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_961_logo.png b/tests/input/res/drawable-xhdpi/ch_961_logo.png deleted file mode 100644 index 5dc31323..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_961_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_962_logo.png b/tests/input/res/drawable-xhdpi/ch_962_logo.png deleted file mode 100644 index 4ef2a219..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_962_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_963_logo.png b/tests/input/res/drawable-xhdpi/ch_963_logo.png deleted file mode 100644 index 22633d33..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_963_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_964_logo.png b/tests/input/res/drawable-xhdpi/ch_964_logo.png deleted file mode 100644 index ba8ad46c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_964_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_965_logo.png b/tests/input/res/drawable-xhdpi/ch_965_logo.png deleted file mode 100644 index 2c935852..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_965_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_966_logo.png b/tests/input/res/drawable-xhdpi/ch_966_logo.png deleted file mode 100644 index 8b751988..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_966_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_967_logo.png b/tests/input/res/drawable-xhdpi/ch_967_logo.png deleted file mode 100644 index 974712fd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_967_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_968_logo.png b/tests/input/res/drawable-xhdpi/ch_968_logo.png deleted file mode 100644 index d1edc191..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_968_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_969_logo.png b/tests/input/res/drawable-xhdpi/ch_969_logo.png deleted file mode 100644 index 7774ed92..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_969_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_96_logo.png b/tests/input/res/drawable-xhdpi/ch_96_logo.png deleted file mode 100644 index e37da468..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_96_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_970_logo.png b/tests/input/res/drawable-xhdpi/ch_970_logo.png deleted file mode 100644 index 5dd52457..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_970_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_971_logo.png b/tests/input/res/drawable-xhdpi/ch_971_logo.png deleted file mode 100644 index 0aca871c..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_971_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_972_logo.png b/tests/input/res/drawable-xhdpi/ch_972_logo.png deleted file mode 100644 index 2f8803b5..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_972_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_973_logo.png b/tests/input/res/drawable-xhdpi/ch_973_logo.png deleted file mode 100644 index 35ec5c90..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_973_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_974_logo.png b/tests/input/res/drawable-xhdpi/ch_974_logo.png deleted file mode 100644 index 98f9a7aa..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_974_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_975_logo.png b/tests/input/res/drawable-xhdpi/ch_975_logo.png deleted file mode 100644 index bdc31316..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_975_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_976_logo.png b/tests/input/res/drawable-xhdpi/ch_976_logo.png deleted file mode 100644 index 078d35b7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_976_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_977_logo.png b/tests/input/res/drawable-xhdpi/ch_977_logo.png deleted file mode 100644 index c6fbb7be..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_977_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_978_logo.png b/tests/input/res/drawable-xhdpi/ch_978_logo.png deleted file mode 100644 index 00a6e5e1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_978_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_979_logo.png b/tests/input/res/drawable-xhdpi/ch_979_logo.png deleted file mode 100644 index fd1f56d1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_979_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_97_logo.png b/tests/input/res/drawable-xhdpi/ch_97_logo.png deleted file mode 100644 index f852548a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_97_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_980_logo.png b/tests/input/res/drawable-xhdpi/ch_980_logo.png deleted file mode 100644 index 362a1988..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_980_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_981_logo.png b/tests/input/res/drawable-xhdpi/ch_981_logo.png deleted file mode 100644 index 9f879c4b..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_981_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_982_logo.png b/tests/input/res/drawable-xhdpi/ch_982_logo.png deleted file mode 100644 index 0d945a11..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_982_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_983_logo.png b/tests/input/res/drawable-xhdpi/ch_983_logo.png deleted file mode 100644 index ffeaddfc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_983_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_984_logo.png b/tests/input/res/drawable-xhdpi/ch_984_logo.png deleted file mode 100644 index d8f92211..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_984_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_985_logo.png b/tests/input/res/drawable-xhdpi/ch_985_logo.png deleted file mode 100644 index b6c6f0dc..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_985_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_986_logo.png b/tests/input/res/drawable-xhdpi/ch_986_logo.png deleted file mode 100644 index 8e2c0b50..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_986_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_987_logo.png b/tests/input/res/drawable-xhdpi/ch_987_logo.png deleted file mode 100644 index ce8c107a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_987_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_988_logo.png b/tests/input/res/drawable-xhdpi/ch_988_logo.png deleted file mode 100644 index ffa8ded7..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_988_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_989_logo.png b/tests/input/res/drawable-xhdpi/ch_989_logo.png deleted file mode 100644 index 8522a03a..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_989_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_98_logo.png b/tests/input/res/drawable-xhdpi/ch_98_logo.png deleted file mode 100644 index 13fe760f..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_98_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_990_logo.png b/tests/input/res/drawable-xhdpi/ch_990_logo.png deleted file mode 100644 index bd6df061..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_990_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_991_logo.png b/tests/input/res/drawable-xhdpi/ch_991_logo.png deleted file mode 100644 index 8611d57d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_991_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_992_logo.png b/tests/input/res/drawable-xhdpi/ch_992_logo.png deleted file mode 100644 index 36d26bc0..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_992_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_993_logo.png b/tests/input/res/drawable-xhdpi/ch_993_logo.png deleted file mode 100644 index f67e0eec..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_993_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_994_logo.png b/tests/input/res/drawable-xhdpi/ch_994_logo.png deleted file mode 100644 index a63d5ee1..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_994_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_995_logo.png b/tests/input/res/drawable-xhdpi/ch_995_logo.png deleted file mode 100644 index b7b45167..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_995_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_996_logo.png b/tests/input/res/drawable-xhdpi/ch_996_logo.png deleted file mode 100644 index 82c042c3..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_996_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_997_logo.png b/tests/input/res/drawable-xhdpi/ch_997_logo.png deleted file mode 100644 index d70e4793..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_997_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_998_logo.png b/tests/input/res/drawable-xhdpi/ch_998_logo.png deleted file mode 100644 index f6c69d2e..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_998_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_999_logo.png b/tests/input/res/drawable-xhdpi/ch_999_logo.png deleted file mode 100644 index 844c06fd..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_999_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_99_logo.png b/tests/input/res/drawable-xhdpi/ch_99_logo.png deleted file mode 100644 index d8be447d..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_99_logo.png and /dev/null differ diff --git a/tests/input/res/drawable-xhdpi/ch_9_logo.png b/tests/input/res/drawable-xhdpi/ch_9_logo.png deleted file mode 100644 index 8ba6bc55..00000000 Binary files a/tests/input/res/drawable-xhdpi/ch_9_logo.png and /dev/null differ diff --git a/tests/input/res/values/strings.xml b/tests/input/res/values/strings.xml index 3f2ab3f7..4ef43955 100644 --- a/tests/input/res/values/strings.xml +++ b/tests/input/res/values/strings.xml @@ -15,7 +15,6 @@ --> Test TV Inputs - About TV Test Inputs Version: %1$s Test TV Input Test Input diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java index 659b341f..621ceacb 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputService.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java @@ -326,11 +326,16 @@ public class TestTvInputService extends TvInputService { if (currentSurface != null) { String now = new Date(mCurrentPositionMs).toString(); String name = currentChannel == null ? "Null" : currentChannel.name; - Canvas c = currentSurface.lockCanvas(null); - c.drawColor(0xFF888888); - c.drawText(name, 100f, 200f, mTextPaint); - c.drawText(now, 100f, 400f, mTextPaint); - currentSurface.unlockCanvasAndPost(c); + try { + Canvas c = currentSurface.lockCanvas(null); + c.drawColor(0xFF888888); + c.drawText(name, 100f, 200f, mTextPaint); + c.drawText(now, 100f, 400f, mTextPaint); + // Assuming c.drawXXX will never fail. + currentSurface.unlockCanvasAndPost(c); + } catch (IllegalArgumentException e) { + // The surface might have been abandoned. Ignore the exception. + } if (DEBUG) { Log.v(TAG, "Post to canvas"); } diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java index 732972cc..a793ac71 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java @@ -20,13 +20,10 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.media.tv.TvContract; import android.media.tv.TvInputInfo; -import android.net.Uri; import android.os.Bundle; import android.util.Log; @@ -58,39 +55,27 @@ public class TestTvInputSetupActivity extends Activity { private void registerChannels(int channelCount) { TestTvInputSetupActivity context = this; - registerChannels(context, mInputId, false, channelCount); + registerChannels(context, mInputId, channelCount); } - public static void registerChannels(Context context, String inputId, boolean updateBrowsable, - int channelCount) { + public static void registerChannels(Context context, String inputId, int channelCount) { Log.i(TAG, "Registering " + channelCount + " channels"); List channels = new ArrayList<>(); for (int i = 1; i <= channelCount; i++) { channels.add(ChannelInfo.create(context, i)); } ChannelUtils.updateChannels(context, inputId, channels); - if (updateBrowsable) { - updateChannelsBrowsable(context.getContentResolver(), inputId); - } // Reload channels so we have the ids. Map channelIdToInfoMap = ChannelUtils.queryChannelInfoMapForTvInput(context, inputId); for (Long channelId : channelIdToInfoMap.keySet()) { - // TODO: http://b/21705569 Create better program info for tests ProgramInfo programInfo = ProgramInfo.create(); ProgramUtils.populatePrograms(context, TvContract.buildChannelUri(channelId), programInfo); } } - private static void updateChannelsBrowsable(ContentResolver contentResolver, String inputId) { - Uri uri = TvContract.buildChannelsUriForInput(inputId); - ContentValues values = new ContentValues(); - values.put(TvContract.Channels.COLUMN_BROWSABLE, 1); - contentResolver.update(uri, values, null, null); - } - public static class MyAlertDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { diff --git a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java index 379bce86..48e485c5 100644 --- a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java +++ b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java @@ -104,7 +104,7 @@ public class TestSetupInstrumentation extends Instrumentation { throw new TestSetupException( "Unknown " + TEST_SETUP_MODE_ARG + " of " + testSetupMode); } - TestTvInputSetupActivity.registerChannels(getContext(), mInputId, true, channelCount); + TestTvInputSetupActivity.registerChannels(getContext(), mInputId, channelCount); } } diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk index 210a75ea..b71ea1b7 100644 --- a/tests/jank/Android.mk +++ b/tests/jank/Android.mk @@ -15,8 +15,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ ub-janktesthelper \ ub-uiautomator \ -LOCAL_JAVA_LIBRARIES := legacy-android-test - LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_SDK_VERSION := current diff --git a/tests/jank/OWNERS b/tests/jank/OWNERS new file mode 100644 index 00000000..4aa5fe52 --- /dev/null +++ b/tests/jank/OWNERS @@ -0,0 +1,2 @@ +nchalko@google.com +shubang@google.com diff --git a/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java b/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java index b0463e71..ef936e32 100644 --- a/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java @@ -15,20 +15,15 @@ */ package com.android.tv.tests.jank; -import android.content.res.Resources; import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; -import android.support.test.jank.JankTestBase; -import android.support.test.uiautomator.UiDevice; - -import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper; /** * Jank tests for channel zapping. */ @MediumTest -public class ChannelZappingJankTest extends JankTestBase { +public class ChannelZappingJankTest extends LiveChannelsTestCase { private static final String TAG = "ChannelZappingJankTest"; private static final String STARTING_CHANNEL = "13"; @@ -45,16 +40,9 @@ public class ChannelZappingJankTest extends JankTestBase { private static final int EXPECTED_FRAMES = 100; private static final int WARM_UP_CHANNEL_ZAPPING_COUNT = 2; - private UiDevice mDevice; - @Override protected void setUp() throws Exception { super.setUp(); - mDevice = UiDevice.getInstance(getInstrumentation()); - Resources targetResources = getInstrumentation().getTargetContext().getResources(); - LiveChannelsUiDeviceHelper liveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, - targetResources, getInstrumentation().getContext()); - liveChannelsHelper.assertAppStarted(); Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } diff --git a/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java b/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java new file mode 100644 index 00000000..6de01036 --- /dev/null +++ b/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.tests.jank; + +import android.content.res.Resources; +import android.support.test.jank.JankTestBase; +import android.support.test.uiautomator.UiDevice; + +import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper; + +/** + * Base test case for LiveChannel jank tests. + */ +abstract class LiveChannelsTestCase extends JankTestBase { + protected UiDevice mDevice; + protected Resources mTargetResources; + protected LiveChannelsUiDeviceHelper mLiveChannelsHelper; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mDevice = UiDevice.getInstance(getInstrumentation()); + mTargetResources = getInstrumentation().getTargetContext().getResources(); + mLiveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, mTargetResources, + getInstrumentation().getContext()); + mLiveChannelsHelper.assertAppStarted(); + } + + @Override + protected void tearDown() throws Exception { + // Destroys the activity to make sure next test case's activity launch check works well. + mLiveChannelsHelper.assertAppStopped(); + super.tearDown(); + } +} diff --git a/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java b/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java index 47ebea3b..411a0bb9 100644 --- a/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java @@ -15,21 +15,17 @@ */ package com.android.tv.tests.jank; -import android.content.res.Resources; import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; -import android.support.test.jank.JankTestBase; -import android.support.test.uiautomator.UiDevice; -import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper; import com.android.tv.testing.uihelper.MenuHelper; /** * Jank tests for the program guide. */ @MediumTest -public class MenuJankTest extends JankTestBase { +public class MenuJankTest extends LiveChannelsTestCase { private static final String STARTING_CHANNEL = "1"; /** @@ -42,22 +38,12 @@ public class MenuJankTest extends JankTestBase { * @see Jank Test Helper Best Practices */ private static final int EXPECTED_FRAMES = 200; - - protected UiDevice mDevice; - - protected Resources mTargetResources; protected MenuHelper mMenuHelper; - protected LiveChannelsUiDeviceHelper mLiveChannelsHelper; @Override protected void setUp() throws Exception { super.setUp(); - mDevice = UiDevice.getInstance(getInstrumentation()); - mTargetResources = getInstrumentation().getTargetContext().getResources(); mMenuHelper = new MenuHelper(mDevice, mTargetResources); - mLiveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, mTargetResources, - getInstrumentation().getContext()); - mLiveChannelsHelper.assertAppStarted(); Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } diff --git a/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java index 7d751c4c..d8860dd7 100644 --- a/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java @@ -17,29 +17,21 @@ package com.android.tv.tests.jank; import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition; -import android.content.res.Resources; import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; -import android.support.test.jank.JankTestBase; -import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; import com.android.tv.testing.uihelper.Constants; -import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper; import com.android.tv.testing.uihelper.MenuHelper; -import com.android.tv.testing.uihelper.UiDeviceUtils; /** * Jank tests for the program guide. */ @MediumTest -public class ProgramGuideJankTest extends JankTestBase { - private static final boolean DEBUG = false; - private static final String TAG = "ProgramGuideJank"; - +public class ProgramGuideJankTest extends LiveChannelsTestCase { private static final String STARTING_CHANNEL = "13"; /** @@ -53,20 +45,12 @@ public class ProgramGuideJankTest extends JankTestBase { */ private static final int EXPECTED_FRAMES = 200; - private UiDevice mDevice; - - private Resources mTargetResources; private MenuHelper mMenuHelper; @Override protected void setUp() throws Exception { super.setUp(); - mDevice = UiDevice.getInstance(getInstrumentation()); - mTargetResources = getInstrumentation().getTargetContext().getResources(); mMenuHelper = new MenuHelper(mDevice, mTargetResources); - LiveChannelsUiDeviceHelper liveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, - mTargetResources, getInstrumentation().getContext()); - liveChannelsHelper.assertAppStarted(); Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } @@ -83,7 +67,7 @@ public class ProgramGuideJankTest extends JankTestBase { } @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeLoop = "showProgramGuide", + beforeLoop = "showAndFocusProgramGuide", afterLoop = "clearProgramGuide") @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testScrollDown() { @@ -95,7 +79,7 @@ public class ProgramGuideJankTest extends JankTestBase { } @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeLoop = "showProgramGuide", + beforeLoop = "showAndFocusProgramGuide", afterLoop = "clearProgramGuide") @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testScrollRight() { @@ -128,11 +112,17 @@ public class ProgramGuideJankTest extends JankTestBase { assertWaitForCondition(mDevice, Until.gone(Constants.PROGRAM_GUIDE)); } - // It's public to be used with @JankTest annotation. public void showProgramGuide() { selectProgramGuideMenuItem(); mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE)); + } + + // It's public to be used with @JankTest annotation. + public void showAndFocusProgramGuide() { + selectProgramGuideMenuItem(); + mDevice.pressDPadCenter(); + assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE)); // If the side panel grid is visible (and thus has focus), move right to clear it. if (mDevice.hasObject( ByResource.id(mTargetResources, R.id.program_guide_side_panel_grid_view))) { diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk index f53220de..3632fe94 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -12,7 +12,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target \ tv-test-common \ -LOCAL_JAVA_LIBRARIES := android.test.mock.sdk legacy-android-test + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res LOCAL_PACKAGE_NAME := TVUnitTests @@ -22,3 +23,4 @@ LOCAL_SDK_VERSION := system_current LOCAL_MIN_SDK_VERSION := 23 # M include $(BUILD_PACKAGE) + diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml index aebb3772..d073f8ac 100644 --- a/tests/unit/AndroidManifest.xml +++ b/tests/unit/AndroidManifest.xml @@ -18,7 +18,7 @@ - + { +public abstract class BaseMainActivityTestCase { private static final String TAG = "BaseMainActivityTest"; private static final int CHANNEL_LOADING_CHECK_INTERVAL_MS = 10; + @Rule + public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(MainActivity.class); + protected final TestInputControlConnection mConnection = new TestInputControlConnection(); protected MainActivity mActivity; - public BaseMainActivityTestCase(Class activityClass) { - super(activityClass); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { + mActivity = mActivityTestRule.getActivity(); // TODO: ensure the SampleInputs are setup. - setActivityInitialTouchMode(false); - mActivity = getActivity(); - getInstrumentation().getContext() + getInstrumentation().getTargetContext() .bindService(TestInputControlUtils.createIntent(), mConnection, Context.BIND_AUTO_CREATE); } - @Override - protected void tearDown() throws Exception { + @Before + public void tearDown() { if (mConnection.isBound()) { - getInstrumentation().getContext().unbindService(mConnection); + getInstrumentation().getTargetContext().unbindService(mConnection); } - super.tearDown(); } /** @@ -72,16 +73,12 @@ public abstract class BaseMainActivityTestCase */ protected void tuneToChannel(final Channel channel) { // Run on UI thread so views can be modified - try { - runTestOnUiThread(new Runnable() { - @Override - public void run() { - mActivity.tuneToChannel(channel); - } - }); - } catch (Throwable throwable) { - throw new RuntimeException(throwable); - } + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mActivity.tuneToChannel(channel); + } + }); } /** @@ -130,13 +127,17 @@ public abstract class BaseMainActivityTestCase private Channel findChannelWithName(String displayName) { waitUntilChannelLoadingFinish(); - List channelList = mActivity.getChannelDataManager().getChannelList(); + Channel channel = null; + List channelList = mActivity.getChannelDataManager().getChannelList(); for (Channel c : channelList) { if (TextUtils.equals(c.getDisplayName(), displayName)) { - return c; + channel = c; + break; } } - throw new AssertionError("'" + displayName + "' channel not found"); + if (channel == null) { + throw new AssertionError("'" + displayName + "' channel not found"); + } + return channel; } - } diff --git a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java index 899083f3..f2917181 100644 --- a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java +++ b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java @@ -18,33 +18,37 @@ package com.android.tv; import static com.android.tv.TimeShiftManager.INVALID_TIME; import static com.android.tv.TimeShiftManager.REQUEST_TIMEOUT_MS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import android.support.test.annotation.UiThreadTest; import android.support.test.filters.MediumTest; -import android.test.UiThreadTest; + +import org.junit.Before; +import org.junit.Test; @MediumTest public class CurrentPositionMediatorTest extends BaseMainActivityTestCase { private TimeShiftManager.CurrentPositionMediator mMediator; - public CurrentPositionMediatorTest() { - super(MainActivity.class); - } - @Override - protected void setUp() throws Exception { + @Before + public void setUp() { super.setUp(); mMediator = mActivity.getTimeShiftManager().mCurrentPositionMediator; } @UiThreadTest - public void testInitialize() throws Throwable { + @Test + public void testInitialize() { long currentTimeMs = System.currentTimeMillis(); mMediator.initialize(currentTimeMs); assertCurrentPositionMediator(INVALID_TIME, currentTimeMs); } @UiThreadTest - public void testOnSeekRequested() throws Throwable { + @Test + public void testOnSeekRequested() { long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3; mMediator.onSeekRequested(seekToTimeMs); assertNotSame("Seek request time", INVALID_TIME, mMediator.mSeekRequestTimeMs); @@ -52,7 +56,8 @@ public class CurrentPositionMediatorTest extends BaseMainActivityTestCase { } @UiThreadTest - public void testOnCurrentPositionChangedInvalidInput() throws Throwable { + @Test + public void testOnCurrentPositionChangedInvalidInput() { long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3; long newCurrentTimeMs = seekToTimeMs + REQUEST_TIMEOUT_MS; mMediator.onSeekRequested(seekToTimeMs); @@ -63,7 +68,8 @@ public class CurrentPositionMediatorTest extends BaseMainActivityTestCase { } @UiThreadTest - public void testOnCurrentPositionChangedValidInput() throws Throwable { + @Test + public void testOnCurrentPositionChangedValidInput() { long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3; long newCurrentTimeMs = seekToTimeMs + REQUEST_TIMEOUT_MS - 1; mMediator.onSeekRequested(seekToTimeMs); diff --git a/tests/unit/src/com/android/tv/FeaturesTest.java b/tests/unit/src/com/android/tv/FeaturesTest.java index 644c4248..9d61e757 100644 --- a/tests/unit/src/com/android/tv/FeaturesTest.java +++ b/tests/unit/src/com/android/tv/FeaturesTest.java @@ -16,19 +16,21 @@ package com.android.tv; +import static org.junit.Assert.assertFalse; + import android.support.test.filters.SmallTest; -import junit.framework.TestCase; +import org.junit.Test; /** * Test for features. */ @SmallTest -public class FeaturesTest extends TestCase { - +public class FeaturesTest { + @Test public void testPropertyFeatureKeyLength() { // This forces the class to be loaded and verifies all PropertyFeature key lengths. // If any keys are too long the test will fail to load. - assertEquals(false, Features.TEST_FEATURE.isEnabled(null)); + assertFalse(Features.TEST_FEATURE.isEnabled(null)); } } diff --git a/tests/unit/src/com/android/tv/MainActivityTest.java b/tests/unit/src/com/android/tv/MainActivityTest.java index b2fe6745..15805032 100644 --- a/tests/unit/src/com/android/tv/MainActivityTest.java +++ b/tests/unit/src/com/android/tv/MainActivityTest.java @@ -15,6 +15,10 @@ */ package com.android.tv; +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.MediumTest; import android.view.View; import android.widget.TextView; @@ -23,6 +27,8 @@ import com.android.tv.data.Channel; import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.ui.ChannelBannerView; +import org.junit.Test; + import java.util.List; /** @@ -30,25 +36,22 @@ import java.util.List; */ @MediumTest public class MainActivityTest extends BaseMainActivityTestCase { - - public MainActivityTest() { - super(MainActivity.class); - } - + @Test public void testInitialConditions() { waitUntilChannelLoadingFinish(); List channelList = mActivity.getChannelDataManager().getChannelList(); assertTrue("Expected at least one channel", channelList.size() > 0); - assertFalse("PIP disabled", mActivity.isPipEnabled()); } - public void testTuneToChannel() throws Throwable { + @Test + public void testTuneToChannel() { tuneToChannel(TvTestInputConstants.CH_2); assertChannelBannerShown(true); assertChannelName(TvTestInputConstants.CH_2.name); } - public void testShowProgramGuide() throws Throwable { + @Test + public void testShowProgramGuide() { tuneToChannel(TvTestInputConstants.CH_2); showProgramGuide(); getInstrumentation().waitForIdleSync(); @@ -56,7 +59,7 @@ public class MainActivityTest extends BaseMainActivityTestCase { assertProgramGuide(true); } - private void showProgramGuide() throws Throwable { + private void showProgramGuide() { // Run on UI thread so views can be modified getInstrumentation().runOnMainSync(new Runnable() { @Override @@ -81,7 +84,7 @@ public class MainActivityTest extends BaseMainActivityTestCase { } private View assertExpectedBannerSceneClassShown(Class expectedClass, - boolean expectedShown) throws AssertionError { + boolean expectedShown) { View v = assertViewIsShown(expectedClass.getSimpleName(), R.id.scene_transition_common, expectedShown); if (v != null) { @@ -90,8 +93,7 @@ public class MainActivityTest extends BaseMainActivityTestCase { return v; } - private View assertViewIsShown(String viewName, int viewId, boolean expected) - throws AssertionError { + private View assertViewIsShown(String viewName, int viewId, boolean expected) { View view = mActivity.findViewById(viewId); if (view == null) { if (expected) { @@ -103,5 +105,4 @@ public class MainActivityTest extends BaseMainActivityTestCase { assertEquals(viewName + " shown", expected, view.isShown()); return view; } - } diff --git a/tests/unit/src/com/android/tv/TimeShiftManagerTest.java b/tests/unit/src/com/android/tv/TimeShiftManagerTest.java index f7c6f622..052b5d19 100644 --- a/tests/unit/src/com/android/tv/TimeShiftManagerTest.java +++ b/tests/unit/src/com/android/tv/TimeShiftManagerTest.java @@ -22,23 +22,25 @@ import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVI import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE; import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY; import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND; +import static org.junit.Assert.assertEquals; import android.support.test.filters.MediumTest; +import org.junit.Before; +import org.junit.Test; + @MediumTest public class TimeShiftManagerTest extends BaseMainActivityTestCase { private TimeShiftManager mTimeShiftManager; - public TimeShiftManagerTest() { - super(MainActivity.class); - } - @Override - protected void setUp() throws Exception { + @Before + public void setUp() { super.setUp(); mTimeShiftManager = mActivity.getTimeShiftManager(); } + @Test public void testDisableActions() { enableAllActions(true); assertActionState(true, true, true, true, true, true); @@ -56,6 +58,7 @@ public class TimeShiftManagerTest extends BaseMainActivityTestCase { assertActionState(false, false, false, false, false, false); } + @Test public void testEnableActions() { enableAllActions(false); assertActionState(false, false, false, false, false, false); diff --git a/tests/unit/src/com/android/tv/common/TvContentRatingCacheTest.java b/tests/unit/src/com/android/tv/common/TvContentRatingCacheTest.java deleted file mode 100644 index eadc50d5..00000000 --- a/tests/unit/src/com/android/tv/common/TvContentRatingCacheTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.common; - -import android.content.ComponentCallbacks2; -import android.media.tv.TvContentRating; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; -import android.test.MoreAsserts; - -import com.android.tv.testing.TvContentRatingConstants; -import com.android.tv.util.Utils; - -/** - * Test for {@link android.media.tv.TvContentRating} tests in {@link Utils}. - */ -@SmallTest -public class TvContentRatingCacheTest extends AndroidTestCase { - - /** - * US_TV_MA and US_TV_Y7 in order - */ - public static final String MA_AND_Y7 = TvContentRatingConstants.STRING_US_TV_MA + "," - + TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV; - - /** - * US_TV_MA and US_TV_Y7 not in order - */ - public static final String Y7_AND_MA = TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV + "," - + TvContentRatingConstants.STRING_US_TV_MA; - final TvContentRatingCache mCache = TvContentRatingCache.getInstance(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); - } - - @Override - protected void tearDown() throws Exception { - mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); - super.tearDown(); - } - - public void testGetRatings_US_TV_MA() { - TvContentRating[] result = mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA); - MoreAsserts.assertEquals(asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA), result); - } - - public void testGetRatings_US_TV_MA_same() { - TvContentRating[] first = mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA); - TvContentRating[] second = mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA); - assertSame(first, second); - } - - public void testGetRatings_US_TV_MA_diffAfterClear() { - TvContentRating[] first = mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA); - mCache.performTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); - TvContentRating[] second = mCache.getRatings(TvContentRatingConstants.STRING_US_TV_MA); - assertNotSame(first, second); - } - - public void testGetRatings_TWO_orderDoesNotMatter() { - TvContentRating[] first = mCache.getRatings(MA_AND_Y7); - TvContentRating[] second = mCache.getRatings(Y7_AND_MA); - assertSame(first, second); - } - - public void testContentRatingsToString_null() { - String result = TvContentRatingCache.contentRatingsToString(null); - assertEquals("ratings string", null, result); - } - - public void testContentRatingsToString_none() { - String result = TvContentRatingCache.contentRatingsToString(asArray()); - assertEquals("ratings string", null, result); - } - - public void testContentRatingsToString_one() { - String result = TvContentRatingCache - .contentRatingsToString(asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA)); - assertEquals("ratings string", TvContentRatingConstants.STRING_US_TV_MA, result); - } - - public void testContentRatingsToString_twoInOrder() { - String result = TvContentRatingCache.contentRatingsToString( - asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA, - TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV)); - assertEquals("ratings string", MA_AND_Y7, result); - } - - public void testContentRatingsToString_twoNotInOrder() { - String result = TvContentRatingCache.contentRatingsToString(asArray( - TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV, - TvContentRatingConstants.CONTENT_RATING_US_TV_MA)); - assertEquals("ratings string", MA_AND_Y7, result); - } - - public void testContentRatingsToString_double() { - String result = TvContentRatingCache.contentRatingsToString(asArray( - TvContentRatingConstants.CONTENT_RATING_US_TV_MA, - TvContentRatingConstants.CONTENT_RATING_US_TV_MA)); - assertEquals("ratings string", TvContentRatingConstants.STRING_US_TV_MA, result); - } - - public void testStringToContentRatings_null() { - assertNull(TvContentRatingCache.stringToContentRatings(null)); - } - - public void testStringToContentRatings_none() { - assertNull(TvContentRatingCache.stringToContentRatings("")); - } - - public void testStringToContentRatings_bad() { - assertNull(TvContentRatingCache.stringToContentRatings("bad")); - } - - public void testStringToContentRatings_oneGoodOneBad() { - TvContentRating[] results = TvContentRatingCache - .stringToContentRatings(TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV + ",bad"); - MoreAsserts.assertEquals("ratings", - asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV), results); - } - - public void testStringToContentRatings_one() { - TvContentRating[] results = TvContentRatingCache - .stringToContentRatings(TvContentRatingConstants.STRING_US_TV_Y7_US_TV_FV); - MoreAsserts.assertEquals("ratings", - asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV), results); - } - - public void testStringToContentRatings_twoNotInOrder() { - TvContentRating[] results = TvContentRatingCache.stringToContentRatings(Y7_AND_MA); - MoreAsserts.assertEquals("ratings", - asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA, - TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV), results); - } - - public void testStringToContentRatings_twoInOrder() { - TvContentRating[] results = TvContentRatingCache.stringToContentRatings(MA_AND_Y7); - MoreAsserts.assertEquals("ratings", - asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA, - TvContentRatingConstants.CONTENT_RATING_US_TV_Y7_US_TV_FV), results); - } - - public void testStringToContentRatings_double() { - TvContentRating[] results = TvContentRatingCache.stringToContentRatings( - TvContentRatingConstants.STRING_US_TV_MA + "," - + TvContentRatingConstants.STRING_US_TV_MA); - MoreAsserts - .assertEquals("ratings", asArray(TvContentRatingConstants.CONTENT_RATING_US_TV_MA), - results); - } - - private static TvContentRating[] asArray(TvContentRating... ratings) { - return ratings; - } -} diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java index 9f9ada1f..7a4a4982 100644 --- a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java @@ -16,6 +16,12 @@ package com.android.tv.data; +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; @@ -26,9 +32,7 @@ import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.net.Uri; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import android.test.MoreAsserts; -import android.test.UiThreadTest; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.mock.MockCursor; @@ -38,9 +42,11 @@ import android.util.SparseArray; import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.Constants; -import com.android.tv.testing.Utils; import com.android.tv.util.TvInputManagerHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; @@ -57,14 +63,13 @@ import java.util.concurrent.TimeUnit; * Note that all the methods of {@link ChannelDataManager} should be called from the UI thread. */ @SmallTest -public class ChannelDataManagerTest extends AndroidTestCase { +public class ChannelDataManagerTest { private static final boolean DEBUG = false; private static final String TAG = "ChannelDataManagerTest"; // Wait time for expected success. private static final long WAIT_TIME_OUT_MS = 1000L; private static final String DUMMY_INPUT_ID = "dummy"; - // TODO: Use Channels.COLUMN_BROWSABLE and Channels.COLUMN_LOCKED instead. private static final String COLUMN_BROWSABLE = "browsable"; private static final String COLUMN_LOCKED = "locked"; @@ -73,51 +78,59 @@ public class ChannelDataManagerTest extends AndroidTestCase { private FakeContentResolver mContentResolver; private FakeContentProvider mContentProvider; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { assertTrue("More than 2 channels to test", Constants.UNIT_TEST_CHANNEL_COUNT > 2); - mContentProvider = new FakeContentProvider(getContext()); + mContentProvider = new FakeContentProvider(getTargetContext()); mContentResolver = new FakeContentResolver(); mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); mListener = new TestChannelDataManagerListener(); - Utils.runOnMainSync(new Runnable() { + getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class); Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())).thenReturn(true); - mChannelDataManager = new ChannelDataManager(getContext(), mockHelper, + mChannelDataManager = new ChannelDataManager(getTargetContext(), mockHelper, mContentResolver); mChannelDataManager.addListener(mListener); } }); } - @Override - protected void tearDown() throws Exception { - Utils.runOnMainSync(new Runnable() { + @After + public void tearDown() { + getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mChannelDataManager.stop(); } }); - super.tearDown(); } - private void startAndWaitForComplete() throws Exception { - mChannelDataManager.start(); + private void startAndWaitForComplete() throws InterruptedException { + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mChannelDataManager.start(); + } + }); assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); } - private void restart() throws Exception { - mChannelDataManager.stop(); - mListener.reset(); + private void restart() throws InterruptedException { + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mChannelDataManager.stop(); + mListener.reset(); + } + }); startAndWaitForComplete(); } - @UiThreadTest - public void testIsDbLoadFinished() throws Exception { + @Test + public void testIsDbLoadFinished() throws InterruptedException { startAndWaitForComplete(); assertTrue(mChannelDataManager.isDbLoadFinished()); } @@ -128,8 +141,8 @@ public class ChannelDataManagerTest extends AndroidTestCase { * - {@link ChannelDataManager#getChannelList} * - {@link ChannelDataManager#getChannel} */ - @UiThreadTest - public void testGetChannels() throws Exception { + @Test + public void testGetChannels() throws InterruptedException { startAndWaitForComplete(); // Test {@link ChannelDataManager#getChannelCount} @@ -138,7 +151,7 @@ public class ChannelDataManagerTest extends AndroidTestCase { // Test {@link ChannelDataManager#getChannelList} List channelInfoList = new ArrayList<>(); for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { - channelInfoList.add(ChannelInfo.create(getContext(), i)); + channelInfoList.add(ChannelInfo.create(getTargetContext(), i)); } List channelList = mChannelDataManager.getChannelList(); for (Channel channel : channelList) { @@ -163,8 +176,8 @@ public class ChannelDataManagerTest extends AndroidTestCase { /** * Test for {@link ChannelDataManager#getChannelCount} when no channel is available. */ - @UiThreadTest - public void testGetChannels_noChannels() throws Exception { + @Test + public void testGetChannels_noChannels() throws InterruptedException { mContentProvider.clear(); startAndWaitForComplete(); assertEquals(0, mChannelDataManager.getChannelCount()); @@ -175,12 +188,12 @@ public class ChannelDataManagerTest extends AndroidTestCase { * - {@link ChannelDataManager#updateBrowsable} * - {@link ChannelDataManager#applyUpdatedValuesToDb} */ - @UiThreadTest - public void testBrowsable() throws Exception { + @Test + public void testBrowsable() throws InterruptedException { startAndWaitForComplete(); // Test if all channels are browsable - List channelList = new ArrayList<>(mChannelDataManager.getChannelList()); + List channelList = mChannelDataManager.getChannelList(); List browsableChannelList = mChannelDataManager.getBrowsableChannelList(); for (Channel browsableChannel : browsableChannelList) { boolean found = channelList.remove(browsableChannel); @@ -189,9 +202,10 @@ public class ChannelDataManagerTest extends AndroidTestCase { assertEquals(0, channelList.size()); // Prepare for next tests. + channelList = mChannelDataManager.getChannelList(); TestChannelDataManagerChannelListener channelListener = new TestChannelDataManagerChannelListener(); - Channel channel1 = mChannelDataManager.getChannelList().get(0); + Channel channel1 = channelList.get(0); mChannelDataManager.addChannelListener(channel1.getId(), channelListener); // Test {@link ChannelDataManager#updateBrowsable} & notification. @@ -216,15 +230,16 @@ public class ChannelDataManagerTest extends AndroidTestCase { * - {@link ChannelDataManager#updateBrowsable} * - {@link ChannelDataManager#applyUpdatedValuesToDb} */ - @UiThreadTest - public void testBrowsable_skipNotification() throws Exception { + @Test + public void testBrowsable_skipNotification() throws InterruptedException { startAndWaitForComplete(); + List channels = mChannelDataManager.getChannelList(); // Prepare for next tests. TestChannelDataManagerChannelListener channelListener = new TestChannelDataManagerChannelListener(); - Channel channel1 = mChannelDataManager.getChannelList().get(0); - Channel channel2 = mChannelDataManager.getChannelList().get(1); + Channel channel1 = channels.get(0); + Channel channel2 = channels.get(1); mChannelDataManager.addChannelListener(channel1.getId(), channelListener); mChannelDataManager.addChannelListener(channel2.getId(), channelListener); @@ -252,8 +267,8 @@ public class ChannelDataManagerTest extends AndroidTestCase { * - {@link ChannelDataManager#updateLocked} * - {@link ChannelDataManager#applyUpdatedValuesToDb} */ - @UiThreadTest - public void testLocked() throws Exception { + @Test + public void testLocked() throws InterruptedException { startAndWaitForComplete(); // Test if all channels aren't locked at the first time. @@ -283,14 +298,14 @@ public class ChannelDataManagerTest extends AndroidTestCase { /** * Test ChannelDataManager when channels in TvContract are updated, removed, or added. */ - @UiThreadTest - public void testChannelListChanged() throws Exception { + @Test + public void testChannelListChanged() throws InterruptedException { startAndWaitForComplete(); // Test channel add. mListener.reset(); long testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; - ChannelInfo testChannelInfo = ChannelInfo.create(getContext(), (int) testChannelId); + ChannelInfo testChannelInfo = ChannelInfo.create(getTargetContext(), (int) testChannelId); testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; mContentProvider.simulateInsert(testChannelInfo); assertTrue( @@ -376,7 +391,7 @@ public class ChannelDataManagerTest extends AndroidTestCase { super(context); for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { mChannelInfoList.put(i, - new ChannelInfoWrapper(ChannelInfo.create(getContext(), i))); + new ChannelInfoWrapper(ChannelInfo.create(getTargetContext(), i))); } } @@ -466,8 +481,8 @@ public class ChannelDataManagerTest extends AndroidTestCase { */ public void simulateInsert(ChannelInfo testChannelInfo) { long channelId = testChannelInfo.originalNetworkId; - mChannelInfoList.put((int) channelId, - new ChannelInfoWrapper(ChannelInfo.create(getContext(), (int) channelId))); + mChannelInfoList.put((int) channelId, new ChannelInfoWrapper( + ChannelInfo.create(getTargetContext(), (int) channelId))); mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); } diff --git a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java index 4e6e9f3c..827dcdbd 100644 --- a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java @@ -16,22 +16,24 @@ package com.android.tv.data; import static com.android.tv.data.ChannelNumber.parseChannelNumber; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import android.support.test.filters.SmallTest; import com.android.tv.testing.ComparableTester; -import junit.framework.TestCase; +import org.junit.Test; /** * Tests for {@link ChannelNumber}. */ @SmallTest -public class ChannelNumberTest extends TestCase { - +public class ChannelNumberTest { /** * Test method for {@link ChannelNumber#ChannelNumber()}. */ + @Test public void testChannelNumber() { assertChannelEquals(new ChannelNumber(), "", false, ""); } @@ -40,32 +42,32 @@ public class ChannelNumberTest extends TestCase { * Test method for * {@link com.android.tv.data.ChannelNumber#parseChannelNumber(java.lang.String)}. */ + @Test public void testParseChannelNumber() { assertNull(parseChannelNumber("")); - assertNull(parseChannelNumber(" ")); + assertNull(parseChannelNumber("-")); assertNull(parseChannelNumber("abcd12")); assertNull(parseChannelNumber("12abcd")); assertNull(parseChannelNumber("-12")); assertChannelEquals(parseChannelNumber("1"), "1", false, ""); - assertChannelEquals(parseChannelNumber("1234 4321"), "1234", true, "4321"); + assertChannelEquals(parseChannelNumber("1234-4321"), "1234", true, "4321"); assertChannelEquals(parseChannelNumber("3-4"), "3", true, "4"); - assertChannelEquals(parseChannelNumber("5.6"), "5", true, "6"); + assertChannelEquals(parseChannelNumber("5-6"), "5", true, "6"); } /** * Test method for {@link ChannelNumber#compareTo(com.android.tv.data.ChannelNumber)}. */ + @Test public void testCompareTo() { new ComparableTester() .addEquivalentGroup(parseChannelNumber("1"), parseChannelNumber("1")) .addEquivalentGroup(parseChannelNumber("2")) - .addEquivalentGroup(parseChannelNumber("2 1"), parseChannelNumber("2.1"), - parseChannelNumber("2-1")) + .addEquivalentGroup(parseChannelNumber("2-1")) .addEquivalentGroup(parseChannelNumber("2-2")) .addEquivalentGroup(parseChannelNumber("2-10")) .addEquivalentGroup(parseChannelNumber("3")) - .addEquivalentGroup(parseChannelNumber("4"), parseChannelNumber("4 0"), - parseChannelNumber("4.0"), parseChannelNumber("4-0")) + .addEquivalentGroup(parseChannelNumber("4"), parseChannelNumber("4-0")) .addEquivalentGroup(parseChannelNumber("10")) .addEquivalentGroup(parseChannelNumber("100")) .test(); @@ -74,6 +76,7 @@ public class ChannelNumberTest extends TestCase { /** * Test method for {@link ChannelNumber#compare(java.lang.String, java.lang.String)}. */ + @Test public void testCompare() { // Only need to test nulls, the reset is tested by testCompareTo assertEquals("compareTo(null,null)", 0, ChannelNumber.compare(null, null)); diff --git a/tests/unit/src/com/android/tv/data/ChannelTest.java b/tests/unit/src/com/android/tv/data/ChannelTest.java index 95e3ee90..d270e277 100644 --- a/tests/unit/src/com/android/tv/data/ChannelTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelTest.java @@ -16,17 +16,21 @@ package com.android.tv.data; +import static org.junit.Assert.assertEquals; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import com.android.tv.testing.ComparatorTester; import com.android.tv.util.TvInputManagerHelper; +import org.junit.Before; +import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -38,7 +42,7 @@ import java.util.Comparator; * Tests for {@link Channel}. */ @SmallTest -public class ChannelTest extends AndroidTestCase { +public class ChannelTest { // 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 = @@ -59,9 +63,8 @@ public class ChannelTest extends AndroidTestCase { private Intent mInvalidIntent; private Intent mValidIntent; - @Override - public void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws NameNotFoundException { mInvalidIntent = new Intent(Intent.ACTION_VIEW); mInvalidIntent.setComponent(new ComponentName(INVALID_TV_INPUT_PACKAGE_NAME, ".test")); mValidIntent = new Intent(Intent.ACTION_VIEW); @@ -103,6 +106,7 @@ public class ChannelTest extends AndroidTestCase { Mockito.when(mMockContext.getPackageManager()).thenReturn(mockPackageManager); } + @Test public void testGetAppLinkType_NoText_NoIntent() { assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, null); assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, null); @@ -226,7 +230,6 @@ public class ChannelTest extends AndroidTestCase { * See b/23031603. */ public void testComparatorLabel() { - TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); Mockito.when(manager.isPartnerInput(Matchers.anyString())).thenAnswer( new Answer() { @@ -254,6 +257,29 @@ public class ChannelTest extends AndroidTestCase { comparatorTester.test(); } + public void testNormalizeChannelNumber() { + assertNormalizedDisplayNumber(null, null); + assertNormalizedDisplayNumber("", ""); + assertNormalizedDisplayNumber("1", "1"); + assertNormalizedDisplayNumber("abcde", "abcde"); + assertNormalizedDisplayNumber("1-1", "1-1"); + assertNormalizedDisplayNumber("1.1", "1-1"); + assertNormalizedDisplayNumber("1 1", "1-1"); + assertNormalizedDisplayNumber("1\u058a1", "1-1"); + assertNormalizedDisplayNumber("1\u05be1", "1-1"); + assertNormalizedDisplayNumber("1\u14001", "1-1"); + assertNormalizedDisplayNumber("1\u18061", "1-1"); + assertNormalizedDisplayNumber("1\u20101", "1-1"); + assertNormalizedDisplayNumber("1\u20111", "1-1"); + assertNormalizedDisplayNumber("1\u20121", "1-1"); + assertNormalizedDisplayNumber("1\u20131", "1-1"); + assertNormalizedDisplayNumber("1\u20141", "1-1"); + } + + private void assertNormalizedDisplayNumber(String displayNumber, String normalized) { + assertEquals(normalized, Channel.normalizeDisplayNumber(displayNumber)); + } + private class TestChannelComparator extends Channel.DefaultComparator { public TestChannelComparator(TvInputManagerHelper manager) { super(null, manager); diff --git a/tests/unit/src/com/android/tv/data/GenreItemTest.java b/tests/unit/src/com/android/tv/data/GenreItemTest.java index fb48fd41..fdbcb599 100644 --- a/tests/unit/src/com/android/tv/data/GenreItemTest.java +++ b/tests/unit/src/com/android/tv/data/GenreItemTest.java @@ -16,23 +16,32 @@ package com.android.tv.data; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import android.media.tv.TvContract.Programs.Genres; import android.os.Build; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; + +import org.junit.Test; /** * Tests for {@link Channel}. */ @SmallTest -public class GenreItemTest extends AndroidTestCase { +public class GenreItemTest { private static final String INVALID_GENRE = "INVALID GENRE"; + @Test public void testGetLabels() { // Checks if no exception is thrown. - GenreItems.getLabels(getContext()); + GenreItems.getLabels(getTargetContext()); } + @Test public void testGetCanonicalGenre() { int count = GenreItems.getGenreCount(); assertNull(GenreItems.getCanonicalGenre(GenreItems.ID_ALL_CHANNELS)); @@ -41,6 +50,7 @@ public class GenreItemTest extends AndroidTestCase { } } + @Test public void testGetId_base() { int count = GenreItems.getGenreCount(); assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(null)); @@ -58,6 +68,7 @@ public class GenreItemTest extends AndroidTestCase { assertInRange(GenreItems.getId(Genres.GAMING), 1, count - 1); } + @Test public void testGetId_lmp_mr1() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.ARTS)); diff --git a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java index 430eef91..5457051f 100644 --- a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java @@ -16,14 +16,20 @@ package com.android.tv.data; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.media.tv.TvContract; import android.net.Uri; import android.os.HandlerThread; +import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.mock.MockCursor; @@ -35,6 +41,10 @@ import com.android.tv.testing.FakeClock; import com.android.tv.testing.ProgramInfo; import com.android.tv.util.Utils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -45,7 +55,7 @@ import java.util.concurrent.TimeUnit; * Test for {@link com.android.tv.data.ProgramDataManager} */ @SmallTest -public class ProgramDataManagerTest extends AndroidTestCase { +public class ProgramDataManagerTest { private static final boolean DEBUG = false; private static final String TAG = "ProgramDataManagerTest"; @@ -66,31 +76,28 @@ public class ProgramDataManagerTest extends AndroidTestCase { private FakeContentResolver mContentResolver; private FakeContentProvider mContentProvider; - @Override - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() { mClock = FakeClock.createWithCurrentTime(); mListener = new TestProgramDataManagerListener(); - mContentProvider = new FakeContentProvider(getContext()); + mContentProvider = new FakeContentProvider(getTargetContext()); mContentResolver = new FakeContentResolver(); mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mProgramDataManager = new ProgramDataManager( - mContentResolver, mClock, mHandlerThread.getLooper(), null); + mContentResolver, mClock, mHandlerThread.getLooper()); mProgramDataManager.setPrefetchEnabled(true); mProgramDataManager.addListener(mListener); } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() { mHandlerThread.quitSafely(); mProgramDataManager.stop(); } - private void startAndWaitForComplete() throws Exception { + private void startAndWaitForComplete() throws InterruptedException { mProgramDataManager.start(); assertTrue(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); } @@ -98,12 +105,13 @@ public class ProgramDataManagerTest extends AndroidTestCase { /** * Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. */ + @Test public void testProgramUtils() { ProgramInfo stub = ProgramInfo.create(); for (long channelId = 1; channelId < Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) { int index = stub.getIndex(mClock.currentTimeMillis(), channelId); long startTimeMs = stub.getStartTimeMs(index, channelId); - ProgramInfo programAt = stub.build(getContext(), index); + ProgramInfo programAt = stub.build(InstrumentationRegistry.getContext(), index); assertTrue(startTimeMs <= mClock.currentTimeMillis()); assertTrue(mClock.currentTimeMillis() < startTimeMs + programAt.durationMs); } @@ -118,7 +126,8 @@ public class ProgramDataManagerTest extends AndroidTestCase { * {@link ProgramDataManager#setPrefetchTimeRange(long)}. *

*/ - public void testGetPrograms() throws Exception { + @Test + public void testGetPrograms() throws InterruptedException { // Initial setup to test {@link ProgramDataManager#setPrefetchTimeRange(long)}. long preventSnapDelayMs = ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS * 2; long prefetchTimeRangeStartMs = System.currentTimeMillis() + preventSnapDelayMs; @@ -140,7 +149,7 @@ public class ProgramDataManagerTest extends AndroidTestCase { ProgramInfo stub = ProgramInfo.create(); int index = stub.getIndex(mClock.currentTimeMillis(), channelId); for (Program program : programs) { - ProgramInfo programInfoAt = stub.build(getContext(), index); + ProgramInfo programInfoAt = stub.build(InstrumentationRegistry.getContext(), index); long startTimeMs = stub.getStartTimeMs(index, channelId); assertProgramEquals(startTimeMs, programInfoAt, program); index++; @@ -167,14 +176,15 @@ public class ProgramDataManagerTest extends AndroidTestCase { * {@link ProgramDataManager#removeOnCurrentProgramUpdatedListener}. *

*/ - public void testCurrentProgramListener() throws Exception { + @Test + public void testCurrentProgramListener() throws InterruptedException { final long testChannelId = 1; ProgramInfo stub = ProgramInfo.create(); int index = stub.getIndex(mClock.currentTimeMillis(), testChannelId); // Set current time to few seconds before the current program ends, // so we can see if callback is called as expected. long nextProgramStartTimeMs = stub.getStartTimeMs(index + 1, testChannelId); - ProgramInfo nextProgramInfo = stub.build(getContext(), index + 1); + ProgramInfo nextProgramInfo = stub.build(InstrumentationRegistry.getContext(), index + 1); mClock.setCurrentTimeMillis(nextProgramStartTimeMs - (WAIT_TIME_OUT_MS / 2)); startAndWaitForComplete(); @@ -196,7 +206,8 @@ public class ProgramDataManagerTest extends AndroidTestCase { /** * Test if program data is refreshed after the program insertion. */ - public void testContentProviderUpdate() throws Exception { + @Test + public void testContentProviderUpdate() throws InterruptedException { final long testChannelId = 1; startAndWaitForComplete(); // Force program data manager to update program data whenever it's changes. @@ -217,7 +228,8 @@ public class ProgramDataManagerTest extends AndroidTestCase { /** * Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. */ - public void testSetPauseProgramUpdate() throws Exception { + @Test + public void testSetPauseProgramUpdate() throws InterruptedException { final long testChannelId = 1; startAndWaitForComplete(); // Force program data manager to update program data whenever it's changes. @@ -290,7 +302,7 @@ public class ProgramDataManagerTest extends AndroidTestCase { int index = stub.getIndex(startTimeMs, i); long programStartTimeMs = stub.getStartTimeMs(index, i); while (programStartTimeMs < endTimeMs) { - ProgramInfo programAt = stub.build(getContext(), index); + ProgramInfo programAt = stub.build(InstrumentationRegistry.getContext(), index); programInfoList.add( new ProgramInfoWrapper(index, programStartTimeMs, programAt)); index++; @@ -340,7 +352,8 @@ public class ProgramDataManagerTest extends AndroidTestCase { ProgramInfo stub = ProgramInfo.create(); ProgramInfoWrapper last = programList.get(programList.size() - 1); while (last.startTimeMs < endTimeMs) { - ProgramInfo nextProgramInfo = stub.build(getContext(), last.index + 1); + ProgramInfo nextProgramInfo = stub.build(InstrumentationRegistry.getContext(), + last.index + 1); ProgramInfoWrapper next = new ProgramInfoWrapper(last.index + 1, last.startTimeMs + last.programInfo.durationMs, nextProgramInfo); programList.add(next); diff --git a/tests/unit/src/com/android/tv/data/ProgramTest.java b/tests/unit/src/com/android/tv/data/ProgramTest.java index 7e474cd6..1d1f6c10 100644 --- a/tests/unit/src/com/android/tv/data/ProgramTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramTest.java @@ -17,17 +17,18 @@ package com.android.tv.data; import static android.media.tv.TvContract.Programs.Genres.COMEDY; import static android.media.tv.TvContract.Programs.Genres.FAMILY_KIDS; - -import com.android.tv.data.Program.CriticScore; -import com.android.tv.dvr.SeriesRecording; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import android.media.tv.TvContentRating; import android.media.tv.TvContract.Programs.Genres; import android.os.Parcel; import android.support.test.filters.SmallTest; -import android.util.Log; -import junit.framework.TestCase; +import com.android.tv.data.Program.CriticScore; + +import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; @@ -37,19 +38,20 @@ import java.util.List; * Tests for {@link Program}. */ @SmallTest -public class ProgramTest extends TestCase { - +public class ProgramTest { private static final int NOT_FOUND_GENRE = 987; private static final int FAMILY_GENRE_ID = GenreItems.getId(FAMILY_KIDS); private static final int COMEDY_GENRE_ID = GenreItems.getId(COMEDY); + @Test public void testBuild() { Program program = new Program.Builder().build(); assertEquals("isValid", false, program.isValid()); } + @Test public void testNoGenres() { Program program = new Program.Builder() .setCanonicalGenres("") @@ -61,6 +63,7 @@ public class ProgramTest extends TestCase { assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true); } + @Test public void testFamilyGenre() { Program program = new Program.Builder() .setCanonicalGenres(FAMILY_KIDS) @@ -72,6 +75,7 @@ public class ProgramTest extends TestCase { assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true); } + @Test public void testFamilyComedyGenre() { Program program = new Program.Builder() .setCanonicalGenres(FAMILY_KIDS + ", " + COMEDY) @@ -83,6 +87,7 @@ public class ProgramTest extends TestCase { assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true); } + @Test public void testOtherGenre() { Program program = new Program.Builder() .setCanonicalGenres("other") @@ -94,7 +99,8 @@ public class ProgramTest extends TestCase { assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true); } - public void testParcelable() throws Exception { + @Test + public void testParcelable() { List criticScores = new ArrayList<>(); criticScores.add(new CriticScore("1", "2", "3")); criticScores.add(new CriticScore("4", "5", "6")); @@ -139,6 +145,7 @@ public class ProgramTest extends TestCase { } } + @Test public void testParcelableWithCriticScore() { Program program = new Program.Builder() .setTitle("MyTitle") diff --git a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java index f5504d48..b4682dd9 100644 --- a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java +++ b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java @@ -19,8 +19,6 @@ package com.android.tv.data; import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; -import android.support.test.filters.Suppress; -import android.test.AndroidTestCase; import android.util.Pair; import com.android.tv.testing.ComparatorTester; @@ -28,6 +26,7 @@ import com.android.tv.util.SetupUtils; import com.android.tv.util.TestUtils; import com.android.tv.util.TvInputManagerHelper; +import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -40,16 +39,16 @@ import java.util.LinkedHashMap; * Test for {@link TvInputNewComparator} */ @SmallTest -public class TvInputNewComparatorTest extends AndroidTestCase { +public class TvInputNewComparatorTest { + @Test public void testComparator() throws Exception { - final LinkedHashMap> INPUT_ID_TO_NEW_INPUT = - new LinkedHashMap<>(); - INPUT_ID_TO_NEW_INPUT.put("2_new_input", new Pair(true, false)); - INPUT_ID_TO_NEW_INPUT.put("4_new_input", new Pair(true, false)); - INPUT_ID_TO_NEW_INPUT.put("4_old_input", new Pair(false, false)); - INPUT_ID_TO_NEW_INPUT.put("0_old_input", new Pair(false, true)); - INPUT_ID_TO_NEW_INPUT.put("1_old_input", new Pair(false, true)); - INPUT_ID_TO_NEW_INPUT.put("3_old_input", new Pair(false, true)); + LinkedHashMap> inputIdToNewInput = new LinkedHashMap<>(); + inputIdToNewInput.put("2_new_input", new Pair<>(true, false)); + inputIdToNewInput.put("4_new_input", new Pair<>(true, false)); + inputIdToNewInput.put("4_old_input", new Pair<>(false, false)); + inputIdToNewInput.put("0_old_input", new Pair<>(false, true)); + inputIdToNewInput.put("1_old_input", new Pair<>(false, true)); + inputIdToNewInput.put("3_old_input", new Pair<>(false, true)); SetupUtils setupUtils = Mockito.mock(SetupUtils.class); Mockito.when(setupUtils.isNewInput(Matchers.anyString())).thenAnswer( @@ -57,7 +56,7 @@ public class TvInputNewComparatorTest extends AndroidTestCase { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { String inputId = (String) invocation.getArguments()[0]; - return INPUT_ID_TO_NEW_INPUT.get(inputId).first; + return inputIdToNewInput.get(inputId).first; } } ); @@ -66,7 +65,7 @@ public class TvInputNewComparatorTest extends AndroidTestCase { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { String inputId = (String) invocation.getArguments()[0]; - return INPUT_ID_TO_NEW_INPUT.get(inputId).second; + return inputIdToNewInput.get(inputId).second; } } ); @@ -83,7 +82,7 @@ public class TvInputNewComparatorTest extends AndroidTestCase { ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); - for (String id : INPUT_ID_TO_NEW_INPUT.keySet()) { + for (String id : inputIdToNewInput.keySet()) { // Put mock resolveInfo to prevent NPE in {@link TvInputInfo#toString} TvInputInfo info1 = TestUtils.createTvInputInfo( resolveInfo, id, "test1", TvInputInfo.TYPE_TUNER, false); diff --git a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java index c86bb724..7eea1be7 100644 --- a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java +++ b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java @@ -16,60 +16,57 @@ package com.android.tv.data; -import android.support.test.filters.SmallTest; -import android.support.test.filters.Suppress; -import android.test.AndroidTestCase; -import android.test.UiThreadTest; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Looper; +import android.support.test.filters.MediumTest; import com.android.tv.data.WatchedHistoryManager.WatchedRecord; -import com.android.tv.testing.Utils; -import java.util.concurrent.CountDownLatch; +import org.junit.Before; +import org.junit.Test; + import java.util.concurrent.TimeUnit; /** * Test for {@link com.android.tv.data.WatchedHistoryManagerTest} + *

+ * This is a medium test because it load files which accessing SharedPreferences. */ -@SmallTest -@Suppress // http://b/27156462 -public class WatchedHistoryManagerTest extends AndroidTestCase { - private static final boolean DEBUG = false; - private static final String TAG = "WatchedHistoryManager"; - +@MediumTest +public class WatchedHistoryManagerTest { // Wait time for expected success. - private static final long WAIT_TIME_OUT_MS = 1000L; private static final int MAX_HISTORY_SIZE = 100; private WatchedHistoryManager mWatchedHistoryManager; private TestWatchedHistoryManagerListener mListener; - @Override - protected void setUp() throws Exception { - super.setUp(); - Utils.runOnMainSync(new Runnable() { - @Override - public void run() { - mWatchedHistoryManager = new WatchedHistoryManager(getContext(), MAX_HISTORY_SIZE); - mListener = new TestWatchedHistoryManagerListener(); - mWatchedHistoryManager.setListener(mListener); - } - }); + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mWatchedHistoryManager = new WatchedHistoryManager(getTargetContext(), MAX_HISTORY_SIZE); + mListener = new TestWatchedHistoryManagerListener(); + mWatchedHistoryManager.setListener(mListener); } - private void startAndWaitForComplete() throws Exception { + private void startAndWaitForComplete() throws InterruptedException { mWatchedHistoryManager.start(); - assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(mListener.mLoadFinished); } - @UiThreadTest - public void testIsLoaded() throws Exception { - assertFalse(mWatchedHistoryManager.isLoaded()); + @Test + public void testIsLoaded() throws InterruptedException { startAndWaitForComplete(); assertTrue(mWatchedHistoryManager.isLoaded()); } - @UiThreadTest - public void testLogChannelViewStop() throws Exception { + @Test + public void testLogChannelViewStop() throws InterruptedException { startAndWaitForComplete(); long fakeId = 100000000; long time = System.currentTimeMillis(); @@ -86,8 +83,8 @@ public class WatchedHistoryManagerTest extends AndroidTestCase { assertEquals(record, recordFromSharedPreferences); } - @UiThreadTest - public void testCircularHistoryQueue() throws Exception { + @Test + public void testCircularHistoryQueue() throws InterruptedException { startAndWaitForComplete(); final long startChannelId = 100000000; long time = System.currentTimeMillis(); @@ -111,7 +108,7 @@ public class WatchedHistoryManagerTest extends AndroidTestCase { mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE)); } - @UiThreadTest + @Test public void testWatchedRecordEquals() { assertTrue(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 3))); assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 4))); @@ -119,8 +116,8 @@ public class WatchedHistoryManagerTest extends AndroidTestCase { assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(4, 2, 3))); } - @UiThreadTest - public void testEncodeDecodeWatchedRecord() throws Exception { + @Test + public void testEncodeDecodeWatchedRecord() { long fakeId = 100000000; long time = System.currentTimeMillis(); long duration = TimeUnit.MINUTES.toMillis(10); @@ -131,11 +128,11 @@ public class WatchedHistoryManagerTest extends AndroidTestCase { } private class TestWatchedHistoryManagerListener implements WatchedHistoryManager.Listener { - public final CountDownLatch loadFinishedLatch = new CountDownLatch(1); + boolean mLoadFinished; @Override public void onLoadFinished() { - loadFinishedLatch.countDown(); + mLoadFinished = true; } @Override diff --git a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java b/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java index 1292759e..5f0ae15c 100644 --- a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java +++ b/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java @@ -16,35 +16,51 @@ package com.android.tv.dvr; +import static android.support.test.InstrumentationRegistry.getContext; + +import android.os.Build; import android.support.annotation.NonNull; +import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import android.test.MoreAsserts; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + import java.util.List; import java.util.concurrent.TimeUnit; -/** - * Tests for {@link BaseDvrDataManager} using {@link DvrDataManagerInMemoryImpl}. - */ +/** Tests for {@link BaseDvrDataManager} using {@link DvrDataManagerInMemoryImpl}. */ @SmallTest -public class BaseDvrDataManagerTest extends AndroidTestCase { +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class BaseDvrDataManagerTest { private static final String INPUT_ID = "input_id"; private static final int CHANNEL_ID = 273; + private final TestableFeature mDvrFeature = CommonFeatures.DVR; private DvrDataManagerInMemoryImpl mDvrDataManager; private FakeClock mFakeClock; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { + mDvrFeature.enableForTest(); mFakeClock = FakeClock.createWithCurrentTime(); mDvrDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); } + @After + public void tearDown() { + mDvrFeature.resetForTests(); + } + + @Test public void testGetNonStartedScheduledRecordings() { ScheduledRecording recording = mDvrDataManager .addScheduledRecordingInternal(createNewScheduledRecordingStartingNow()); @@ -52,6 +68,7 @@ public class BaseDvrDataManagerTest extends AndroidTestCase { MoreAsserts.assertContentsInAnyOrder(result, recording); } + @Test public void testGetNonStartedScheduledRecordings_past() { mDvrDataManager.addScheduledRecordingInternal(createNewScheduledRecordingStartingNow()); mFakeClock.increment(TimeUnit.MINUTES, 6); diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java index b822f164..9771a2e5 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java +++ b/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java @@ -16,24 +16,29 @@ package com.android.tv.dvr; +import static org.junit.Assert.assertEquals; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; -import junit.framework.TestCase; +import org.junit.Test; import java.util.ArrayList; import java.util.List; -/** - * Tests for {@link DvrDataManagerImpl} - */ +/** Tests for {@link DvrDataManagerImpl} */ @SmallTest -public class DvrDataManagerImplTest extends TestCase { +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class DvrDataManagerImplTest { private static final String INPUT_ID = "input_id"; private static final int CHANNEL_ID = 273; - public void testGetNextScheduledStartTimeAfter() throws Exception { + @Test + public void testGetNextScheduledStartTimeAfter() { long id = 1; List scheduledRecordings = new ArrayList<>(); assertNextStartTime(scheduledRecordings, 0L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java index 85e35c4d..0a7ab46c 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java +++ b/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java @@ -17,14 +17,19 @@ package com.android.tv.dvr; import android.content.Context; +import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.test.filters.SdkSuppress; import android.text.TextUtils; import android.util.Log; import android.util.Range; import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording.RecordingState; +import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Clock; import java.util.ArrayList; @@ -34,10 +39,9 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -/** - * A DVR Data manager that stores values in memory suitable for testing. - */ -final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { +/** A DVR Data manager that stores values in memory suitable for testing. */ +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { private final static String TAG = "DvrDataManagerInMemory"; private final AtomicLong mNextId = new AtomicLong(1); private final Map mScheduledRecordings = new HashMap<>(); diff --git a/tests/unit/src/com/android/tv/dvr/DvrDbSyncTest.java b/tests/unit/src/com/android/tv/dvr/DvrDbSyncTest.java deleted file mode 100644 index 7cb3721c..00000000 --- a/tests/unit/src/com/android/tv/dvr/DvrDbSyncTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; - -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; - -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link DvrScheduleManager} - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class DvrDbSyncTest extends AndroidTestCase { - private static final String INPUT_ID = "input_id"; - private static final long BASE_PROGRAM_ID = 1; - private static final long BASE_START_TIME_MS = 0; - private static final long BASE_END_TIME_MS = 1; - private static final String BASE_SEASON_NUMBER = "2"; - private static final String BASE_EPISODE_NUMBER = "3"; - private static final Program BASE_PROGRAM = new Program.Builder().setId(BASE_PROGRAM_ID) - .setStartTimeUtcMillis(BASE_START_TIME_MS).setEndTimeUtcMillis(BASE_END_TIME_MS) - .setSeasonNumber(BASE_SEASON_NUMBER).setEpisodeNumber(BASE_EPISODE_NUMBER).build(); - private static final ScheduledRecording BASE_SCHEDULE = - ScheduledRecording.builder(INPUT_ID, BASE_PROGRAM).build(); - - private DvrDbSync mDbSync; - @Mock private DvrDataManagerImpl mDataManager; - @Mock private ChannelDataManager mChannelDataManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); - mDbSync = new DvrDbSync(getContext(), mDataManager, mChannelDataManager); - } - - public void testHandleUpdateProgram_null() { - addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); - mDbSync.handleUpdateProgram(null, BASE_PROGRAM_ID); - verify(mDataManager).removeScheduledRecording(BASE_SCHEDULE); - } - - public void testHandleUpdateProgram_changeTimeNotStarted() { - addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); - long startTimeMs = BASE_START_TIME_MS + 1; - long endTimeMs = BASE_END_TIME_MS + 1; - Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) - .setEndTimeUtcMillis(endTimeMs).build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - assertUpdateScheduleCalled(program); - } - - public void testHandleUpdateProgram_changeTimeInProgressNotCalled() { - addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS).build()); - long startTimeMs = BASE_START_TIME_MS + 1; - Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) - .build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - verify(mDataManager, never()).updateScheduledRecording(anyObject()); - } - - public void testHandleUpdateProgram_changeSeason() { - addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); - String seasonNumber = BASE_SEASON_NUMBER + "1"; - String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = new Program.Builder(BASE_PROGRAM).setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber).build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - assertUpdateScheduleCalled(program); - } - - public void testHandleUpdateProgram_finished() { - addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); - String seasonNumber = BASE_SEASON_NUMBER + "1"; - String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = new Program.Builder(BASE_PROGRAM).setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber).build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - verify(mDataManager, never()).updateScheduledRecording(anyObject()); - } - - private void addSchedule(long programId, ScheduledRecording schedule) { - when(mDataManager.getScheduledRecordingForProgramId(programId)).thenReturn(schedule); - } - - private void assertUpdateScheduleCalled(Program program) { - verify(mDataManager).updateScheduledRecording( - eq(ScheduledRecording.builder(INPUT_ID, program).build())); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/DvrRecordingServiceTest.java b/tests/unit/src/com/android/tv/dvr/DvrRecordingServiceTest.java deleted file mode 100644 index 0a203ede..00000000 --- a/tests/unit/src/com/android/tv/dvr/DvrRecordingServiceTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.mockito.Mockito.verify; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.ServiceTestCase; - -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.testing.FakeClock; - -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link DvrRecordingService}. - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class DvrRecordingServiceTest extends ServiceTestCase { - @Mock Scheduler mMockScheduler; - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - private final FakeClock mFakeClock = FakeClock.createWithCurrentTime(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - mDvrFeature.enableForTest(); - MockitoAnnotations.initMocks(this); - setupService(); - DvrRecordingService service = getService(); - service.setScheduler(mMockScheduler); - } - - @Override - protected void tearDown() throws Exception { - mDvrFeature.resetForTests(); - super.tearDown(); - } - - public DvrRecordingServiceTest() { - super(DvrRecordingService.class); - } - - public void testStartService_null() throws Exception { - startService(null); - verify(mMockScheduler, Mockito.only()).update(); - } -} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java b/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java index 2850a5f7..1c77aa0e 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java +++ b/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java @@ -16,32 +16,39 @@ package com.android.tv.dvr; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import android.util.Range; +import com.android.tv.dvr.DvrScheduleManager.ConflictInfo; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; -import junit.framework.TestCase; +import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; -/** - * Tests for {@link DvrScheduleManager} - */ +/** Tests for {@link DvrScheduleManager} */ @SmallTest -public class DvrScheduleManagerTest extends TestCase { +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class DvrScheduleManagerTest { private static final String INPUT_ID = "input_id"; + @Test public void testGetConflictingSchedules_emptySchedule() { List schedules = new ArrayList<>(); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); } + @Test public void testGetConflictingSchedules_noConflict() { long priority = 0; long channelId = 0; @@ -68,6 +75,7 @@ public class DvrScheduleManagerTest extends TestCase { MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); } + @Test public void testGetConflictingSchedules_noTuner() { long priority = 0; long channelId = 0; @@ -82,6 +90,7 @@ public class DvrScheduleManagerTest extends TestCase { assertEquals(schedules, DvrScheduleManager.getConflictingSchedules(schedules, 0)); } + @Test public void testGetConflictingSchedules_conflict() { long priority = 0; long channelId = 0; @@ -160,6 +169,7 @@ public class DvrScheduleManagerTest extends TestCase { MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); } + @Test public void testGetConflictingSchedules_conflict2() { // The case when there is a long schedule. long priority = 0; @@ -186,6 +196,7 @@ public class DvrScheduleManagerTest extends TestCase { MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); } + @Test public void testGetConflictingSchedules_reverseOrder() { long priority = 0; long channelId = 0; @@ -264,6 +275,7 @@ public class DvrScheduleManagerTest extends TestCase { MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); } + @Test public void testGetConflictingSchedules_period1() { long priority = 0; long channelId = 0; @@ -281,6 +293,7 @@ public class DvrScheduleManagerTest extends TestCase { Collections.singletonList(new Range<>(110L, 120L))), r1); } + @Test public void testGetConflictingSchedules_period2() { long priority = 0; long channelId = 0; @@ -298,6 +311,7 @@ public class DvrScheduleManagerTest extends TestCase { Collections.singletonList(new Range<>(110L, 120L))), r1); } + @Test public void testGetConflictingSchedules_period3() { long priority = 0; long channelId = 0; @@ -328,6 +342,7 @@ public class DvrScheduleManagerTest extends TestCase { ranges), r2, r1); } + @Test public void testGetConflictingSchedules_addSchedules1() { long priority = 0; long channelId = 0; @@ -351,6 +366,7 @@ public class DvrScheduleManagerTest extends TestCase { schedules, 1), r1); } + @Test public void testGetConflictingSchedules_addSchedules2() { long priority = 0; long channelId = 0; @@ -374,6 +390,7 @@ public class DvrScheduleManagerTest extends TestCase { schedules, 1), r2, r1); } + @Test public void testGetConflictingSchedules_addLowestPriority() { long priority = 0; long channelId = 0; @@ -394,6 +411,7 @@ public class DvrScheduleManagerTest extends TestCase { schedules, 1), r1); } + @Test public void testGetConflictingSchedules_sameChannel() { long priority = 0; long channelId = 1; @@ -405,6 +423,7 @@ public class DvrScheduleManagerTest extends TestCase { MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); } + @Test public void testGetConflictingSchedule_startEarlyAndFail() { long priority = 0; long channelId = 0; @@ -423,6 +442,7 @@ public class DvrScheduleManagerTest extends TestCase { r2); } + @Test public void testGetConflictingSchedule_startLate() { long priority = 0; long channelId = 0; @@ -441,6 +461,7 @@ public class DvrScheduleManagerTest extends TestCase { r2, r1); } + @Test public void testGetConflictingSchedulesForTune_canTune() { // Can tune to the recorded channel if tuner count is 1. long priority = 0; @@ -452,6 +473,7 @@ public class DvrScheduleManagerTest extends TestCase { channelId, 0L, priority + 1, schedules, 1)); } + @Test public void testGetConflictingSchedulesForTune_cannotTune() { // Can't tune to a channel if other channel is recording and tuner count is 1. long priority = 0; @@ -463,6 +485,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelId + 1, 0L, priority + 1, schedules, 1), schedules.get(0)); } + @Test public void testGetConflictingSchedulesForWatching_otherChannels() { // The other channels are to be recorded. long priority = 0; @@ -481,6 +504,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), r1); } + @Test public void testGetConflictingSchedulesForWatching_sameChannel1() { long priority = 0; long channelToWatch = 1; @@ -498,6 +522,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r2); } + @Test public void testGetConflictingSchedulesForWatching_sameChannel2() { long priority = 0; long channelToWatch = 1; @@ -515,6 +540,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r1); } + @Test public void testGetConflictingSchedulesForWatching_sameChannelConflict1() { long priority = 0; long channelToWatch = 1; @@ -537,6 +563,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r2, r1); } + @Test public void testGetConflictingSchedulesForWatching_sameChannelConflict2() { long priority = 0; long channelToWatch = 1; @@ -559,6 +586,7 @@ public class DvrScheduleManagerTest extends TestCase { INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r3, r1); } + @Test public void testPartiallyConflictingSchedules() { long priority = 100; long channelId = 0; @@ -586,49 +614,80 @@ public class DvrScheduleManagerTest extends TestCase { RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, --priority, 50L, 900L) )); - Map conflictsInfo = DvrScheduleManager - .getConflictingSchedulesInfo(schedules, 1); - - assertNull(conflictsInfo.get(schedules.get(0))); - assertFalse(conflictsInfo.get(schedules.get(1))); - assertTrue(conflictsInfo.get(schedules.get(2))); - assertTrue(conflictsInfo.get(schedules.get(3))); - assertNull(conflictsInfo.get(schedules.get(4))); - assertTrue(conflictsInfo.get(schedules.get(5))); - assertNull(conflictsInfo.get(schedules.get(6))); - assertFalse(conflictsInfo.get(schedules.get(7))); - assertFalse(conflictsInfo.get(schedules.get(8))); - assertFalse(conflictsInfo.get(schedules.get(9))); - assertFalse(conflictsInfo.get(schedules.get(10))); - - conflictsInfo = DvrScheduleManager - .getConflictingSchedulesInfo(schedules, 2); - - assertNull(conflictsInfo.get(schedules.get(0))); - assertNull(conflictsInfo.get(schedules.get(1))); - assertNull(conflictsInfo.get(schedules.get(2))); - assertNull(conflictsInfo.get(schedules.get(3))); - assertNull(conflictsInfo.get(schedules.get(4))); - assertNull(conflictsInfo.get(schedules.get(5))); - assertNull(conflictsInfo.get(schedules.get(6))); - assertFalse(conflictsInfo.get(schedules.get(7))); - assertFalse(conflictsInfo.get(schedules.get(8))); - assertFalse(conflictsInfo.get(schedules.get(9))); - assertTrue(conflictsInfo.get(schedules.get(10))); - - conflictsInfo = DvrScheduleManager - .getConflictingSchedulesInfo(schedules, 3); - - assertNull(conflictsInfo.get(schedules.get(0))); - assertNull(conflictsInfo.get(schedules.get(1))); - assertNull(conflictsInfo.get(schedules.get(2))); - assertNull(conflictsInfo.get(schedules.get(3))); - assertNull(conflictsInfo.get(schedules.get(4))); - assertNull(conflictsInfo.get(schedules.get(5))); - assertNull(conflictsInfo.get(schedules.get(6))); - assertNull(conflictsInfo.get(schedules.get(7))); - assertTrue(conflictsInfo.get(schedules.get(8))); - assertNull(conflictsInfo.get(schedules.get(9))); - assertTrue(conflictsInfo.get(schedules.get(10))); + List conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 1); + + assertNotInList(schedules.get(0), conflicts); + assertFullConflict(schedules.get(1), conflicts); + assertPartialConflict(schedules.get(2), conflicts); + assertPartialConflict(schedules.get(3), conflicts); + assertNotInList(schedules.get(4), conflicts); + assertPartialConflict(schedules.get(5), conflicts); + assertNotInList(schedules.get(6), conflicts); + assertFullConflict(schedules.get(7), conflicts); + assertFullConflict(schedules.get(8), conflicts); + assertFullConflict(schedules.get(9), conflicts); + assertFullConflict(schedules.get(10), conflicts); + + conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 2); + + assertNotInList(schedules.get(0), conflicts); + assertNotInList(schedules.get(1), conflicts); + assertNotInList(schedules.get(2), conflicts); + assertNotInList(schedules.get(3), conflicts); + assertNotInList(schedules.get(4), conflicts); + assertNotInList(schedules.get(5), conflicts); + assertNotInList(schedules.get(6), conflicts); + assertFullConflict(schedules.get(7), conflicts); + assertFullConflict(schedules.get(8), conflicts); + assertFullConflict(schedules.get(9), conflicts); + assertPartialConflict(schedules.get(10), conflicts); + + conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 3); + + assertNotInList(schedules.get(0), conflicts); + assertNotInList(schedules.get(1), conflicts); + assertNotInList(schedules.get(2), conflicts); + assertNotInList(schedules.get(3), conflicts); + assertNotInList(schedules.get(4), conflicts); + assertNotInList(schedules.get(5), conflicts); + assertNotInList(schedules.get(6), conflicts); + assertNotInList(schedules.get(7), conflicts); + assertPartialConflict(schedules.get(8), conflicts); + assertNotInList(schedules.get(9), conflicts); + assertPartialConflict(schedules.get(10), conflicts); + } + + private void assertNotInList(ScheduledRecording schedule, List conflicts) { + for (ConflictInfo conflictInfo : conflicts) { + if (conflictInfo.schedule.equals(schedule)) { + fail(schedule + " conflicts with others."); + } + } + } + + private void assertPartialConflict(ScheduledRecording schedule, List conflicts) { + for (ConflictInfo conflictInfo : conflicts) { + if (conflictInfo.schedule.equals(schedule)) { + if (conflictInfo.partialConflict) { + return; + } else { + fail(schedule + " fully conflicts with others."); + } + } + } + fail(schedule + " doesn't conflict"); + } + + private void assertFullConflict(ScheduledRecording schedule, List conflicts) { + for (ConflictInfo conflictInfo : conflicts) { + if (conflictInfo.schedule.equals(schedule)) { + if (!conflictInfo.partialConflict) { + return; + } else { + fail(schedule + " partially conflicts with others."); + } + } + } + fail(schedule + " doesn't conflict"); } } \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/EpisodicProgramLoadTaskTest.java b/tests/unit/src/com/android/tv/dvr/EpisodicProgramLoadTaskTest.java deleted file mode 100644 index 2172d488..00000000 --- a/tests/unit/src/com/android/tv/dvr/EpisodicProgramLoadTaskTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; - -import com.android.tv.dvr.EpisodicProgramLoadTask.ScheduledEpisode; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tests for {@link EpisodicProgramLoadTask} - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class EpisodicProgramLoadTaskTest extends AndroidTestCase { - private static final long SERIES_RECORDING_ID1 = 1; - private static final long SERIES_RECORDING_ID2 = 2; - private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; - private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; - private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; - private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; - - public void testEpisodeAlreadyScheduled_true() { - List episodes = new ArrayList<>(); - ScheduledEpisode episode = new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, - EPISODE_NUMBER1); - episodes.add(episode); - assertTrue(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1))); - } - - public void testEpisodeAlreadyScheduled_false() { - List episodes = new ArrayList<>(); - ScheduledEpisode episode = new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, - EPISODE_NUMBER1); - episodes.add(episode); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1))); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1))); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2))); - } - - public void testEpisodeAlreadyScheduled_null() { - List episodes = new ArrayList<>(); - ScheduledEpisode episode = new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, - EPISODE_NUMBER1); - episodes.add(episode); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1))); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, SEASON_NUMBER1, null))); - assertFalse(EpisodicProgramLoadTask.isEpisodeScheduled(episodes, - new ScheduledEpisode(SERIES_RECORDING_ID1, null, null))); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/InputTaskSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/InputTaskSchedulerTest.java deleted file mode 100644 index 85c78ce2..00000000 --- a/tests/unit/src/com/android/tv/dvr/InputTaskSchedulerTest.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.after; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.AlarmManager; -import android.media.tv.TvInputInfo; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; - -import com.android.tv.InputSessionManager; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.InputTaskScheduler.RecordingTaskFactory; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import com.android.tv.util.Clock; -import com.android.tv.util.TestUtils; - -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link InputTaskScheduler}. - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class InputTaskSchedulerTest extends AndroidTestCase { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 1; - private static final long LISTENER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); - private static final int TUNER_COUNT_ONE = 1; - private static final int TUNER_COUNT_TWO = 2; - private static final long LOW_PRIORITY = 1; - private static final long HIGH_PRIORITY = 2; - - private FakeClock mFakeClock; - private InputTaskScheduler mScheduler; - @Mock private DvrManager mDvrManager; - @Mock private WritableDvrDataManager mDataManager; - @Mock private InputSessionManager mSessionManager; - @Mock private AlarmManager mMockAlarmManager; - @Mock private ChannelDataManager mChannelDataManager; - private List mRecordingTasks; - - @Override - protected void setUp() throws Exception { - super.setUp(); - if (Looper.myLooper() == null) { - Looper.prepare(); - } - Handler fakeMainHandler = new Handler(); - Handler workerThreadHandler = new Handler(); - mRecordingTasks = new ArrayList(); - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithCurrentTime(); - TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE); - mScheduler = new InputTaskScheduler(getContext(), input, Looper.myLooper(), - mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mFakeClock, - fakeMainHandler, workerThreadHandler, new RecordingTaskFactory() { - @Override - public RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, - Channel channel, DvrManager dvrManager, - InputSessionManager sessionManager, WritableDvrDataManager dataManager, - Clock clock) { - RecordingTask task = mock(RecordingTask.class); - when(task.getPriority()).thenReturn(scheduledRecording.getPriority()); - when(task.getEndTimeMs()).thenReturn(scheduledRecording.getEndTimeMs()); - mRecordingTasks.add(task); - return task; - } - }); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testAddSchedule_past() throws Exception { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, 0L, 1L); - when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1)) - .changeState(any(ScheduledRecording.class), - eq(ScheduledRecording.STATE_RECORDING_FAILED)); - } - - public void testAddSchedule_start() throws Exception { - mScheduler.handleAddSchedule(RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - } - - public void testAddSchedule_consecutiveNoStop() throws Exception { - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - // The first schedule should not be stopped because the second one should wait for the end - // of the first schedule. - verify(mRecordingTasks.get(0), after((int) LISTENER_TIMEOUT_MS).never()).stop(); - } - - public void testAddSchedule_consecutiveNoFail() throws Exception { - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - when(mDataManager.getScheduledRecording(anyLong())).thenReturn(ScheduledRecording - .builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - verify(mRecordingTasks.get(0), after((int) LISTENER_TIMEOUT_MS).never()).stop(); - // The second schedule should not fail because it can starts after the first one finishes. - verify(mDataManager, after((int) LISTENER_TIMEOUT_MS).never()) - .changeState(any(ScheduledRecording.class), - eq(ScheduledRecording.STATE_RECORDING_FAILED)); - } - - public void testAddSchedule_consecutiveUseLessSession() throws Exception { - TvInputInfo input = createTvInputInfo(TUNER_COUNT_TWO); - mScheduler.updateTvInputInfo(input); - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - verify(mRecordingTasks.get(0), after((int) LISTENER_TIMEOUT_MS).never()).stop(); - // The second schedule should wait until the first one finishes rather than creating a new - // session even though there are available tuners. - assertTrue(mRecordingTasks.size() == 1); - } - - public void testUpdateSchedule_noCancel() throws Exception { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - mScheduler.handleUpdateSchedule(r); - verify(mRecordingTasks.get(0), after((int) LISTENER_TIMEOUT_MS).never()).cancel(); - } - - public void testUpdateSchedule_cancel() throws Exception { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - mScheduler.handleUpdateSchedule(ScheduledRecording.buildFrom(r) - .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) - .build()); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel(); - } - - private TvInputInfo createTvInputInfo(int tunerCount) throws Exception { - return TestUtils.createTvInputInfo(null, null, null, 0, false, true, tunerCount); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/RecordingTaskTest.java b/tests/unit/src/com/android/tv/dvr/RecordingTaskTest.java deleted file mode 100644 index 7404a554..00000000 --- a/tests/unit/src/com/android/tv/dvr/RecordingTaskTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.hamcrest.MockitoHamcrest.longThat; - -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; - -import com.android.tv.InputSessionManager; -import com.android.tv.InputSessionManager.RecordingSession; -import com.android.tv.data.Channel; -import com.android.tv.dvr.RecordingTask.State; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.compat.ArgumentMatcher; - -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link RecordingTask}. - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class RecordingTaskTest extends AndroidTestCase { - private static final long DURATION = TimeUnit.MINUTES.toMillis(30); - private static final long START_OFFSET_MS = Scheduler.MS_TO_WAKE_BEFORE_START; - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDataManager; - @Mock Handler mMockHandler; - @Mock DvrManager mDvrManager; - @Mock InputSessionManager mMockSessionManager; - @Mock RecordingSession mMockRecordingSession; - - @Override - protected void setUp() throws Exception { - super.setUp(); - if (Looper.myLooper() == null) { - Looper.prepare(); - } - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); - } - - public void testHandle_init() { - Channel channel = createTestChannel(); - ScheduledRecording r = createRecording(channel); - RecordingTask task = createRecordingTask(r, channel); - String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); - when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); - assertTrue(task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE))); - assertEquals(State.CONNECTION_PENDING, task.getState()); - verify(mMockSessionManager).createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong()); - verify(mMockRecordingSession).tune(eq(inputId), eq(channel.getUri())); - verifyNoMoreInteractions(mMockHandler, mMockRecordingSession, mMockSessionManager); - } - - private static Channel createTestChannel() { - return new Channel.Builder().setInputId(INPUT_ID).setId(CHANNEL_ID) - .setDisplayName("Test Ch " + CHANNEL_ID).build(); - } - - public void testOnConnected() { - Channel channel = createTestChannel(); - ScheduledRecording r = createRecording(channel); - mDataManager.addScheduledRecording(r); - RecordingTask task = createRecordingTask(r, channel); - String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); - when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); - task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE)); - task.onTuned(channel.getUri()); - assertEquals(State.CONNECTED, task.getState()); - } - - private ScheduledRecording createRecording(Channel c) { - long startTime = mFakeClock.currentTimeMillis() + START_OFFSET_MS; - long endTime = startTime + DURATION; - return RecordingTestUtils.createTestRecordingWithPeriod(c.getInputId(), c.getId(), - startTime, endTime); - } - - private RecordingTask createRecordingTask(ScheduledRecording r, Channel channel) { - RecordingTask recordingTask = new RecordingTask(getContext(), r, channel, mDvrManager, - mMockSessionManager, mDataManager, mFakeClock); - recordingTask.setHandler(mMockHandler); - return recordingTask; - } - - private void verifySendMessageAt(int what, long when) { - verify(mMockHandler).sendMessageAtTime(argThat(messageMatchesWhat(what)), delta(when, 100)); - } - - private static long delta(final long value, final long delta) { - return longThat(new BaseMatcher() { - @Override - public boolean matches(Object item) { - Long other = (Long) item; - return other >= value - delta && other <= value + delta; - } - - @Override - public void describeTo(Description description) { - description.appendText("eq " + value + "±" + delta); - - } - }); - } - - private Message createMessage(int what) { - Message msg = new Message(); - msg.setTarget(mMockHandler); - msg.what = what; - return msg; - } - - private static ArgumentMatcher messageMatchesWhat(final int what) { - return new ArgumentMatcher() { - @Override - public boolean matchesObject(Object argument) { - Message message = (Message) argument; - return message.what == what; - } - }; - } -} diff --git a/tests/unit/src/com/android/tv/dvr/ScheduledProgramReaperTest.java b/tests/unit/src/com/android/tv/dvr/ScheduledProgramReaperTest.java deleted file mode 100644 index 847540c2..00000000 --- a/tests/unit/src/com/android/tv/dvr/ScheduledProgramReaperTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.test.MoreAsserts; - -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; - -import junit.framework.TestCase; - -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link ScheduledProgramReaper}. - */ -public class ScheduledProgramReaperTest extends TestCase { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - private static final long DURATION = TimeUnit.HOURS.toMillis(1); - - private ScheduledProgramReaper mReaper; - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDvrDataManager; - @Mock private DvrManager mDvrManager; - - - @Override - protected void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithTimeOne(); - mDvrDataManager = new DvrDataManagerInMemoryImpl(null, mFakeClock); - mReaper = new ScheduledProgramReaper(mDvrDataManager, mFakeClock); - } - - public void testRun_noRecordings() { - MoreAsserts.assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings()); - mReaper.run(); - MoreAsserts.assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings()); - } - - public void testRun_oneRecordingsTomorrow() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - } - - public void testRun_oneRecordingsStarted() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS); - mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - } - - public void testRun_oneRecordingsFinished() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS); - mFakeClock.increment(TimeUnit.MINUTES, 2); - mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - } - - public void testRun_oneRecordingsExpired() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS, 1 + ScheduledProgramReaper.DAYS); - mFakeClock.increment(TimeUnit.MILLISECONDS, DURATION); - // After the cutoff and enough so we can see on the clock - mFakeClock.increment(TimeUnit.SECONDS, 1); - - mReaper.run(); - MoreAsserts.assertContentsInAnyOrder( - "Recordings after reaper at " + com.android.tv.util.Utils - .toIsoDateTimeString(mFakeClock.currentTimeMillis()), - mDvrDataManager.getAllScheduledRecordings()); - } - - private ScheduledRecording addNewScheduledRecordingForTomorrow() { - long startTime = mFakeClock.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); - ScheduledRecording recording = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, startTime, startTime + DURATION); - return mDvrDataManager.addScheduledRecordingInternal( - ScheduledRecording.buildFrom(recording) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java b/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java index 96036418..b98af603 100644 --- a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java +++ b/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java @@ -16,34 +16,37 @@ package com.android.tv.dvr; -import static com.android.tv.testing.dvr.RecordingTestUtils - .createTestRecordingWithIdAndPeriod; +import static com.android.tv.testing.dvr.RecordingTestUtils.createTestRecordingWithIdAndPeriod; import static com.android.tv.testing.dvr.RecordingTestUtils.normalizePriority; +import static junit.framework.TestCase.assertEquals; +import android.os.Build; +import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import android.util.Range; import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; -import junit.framework.TestCase; +import org.junit.Test; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -/** - * Tests for {@link ScheduledRecordingTest} - */ +/** Tests for {@link ScheduledRecordingTest} */ @SmallTest -public class ScheduledRecordingTest extends TestCase { +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class ScheduledRecordingTest { private static final String INPUT_ID = "input_id"; private static final int CHANNEL_ID = 273; - public void testIsOverLapping() throws Exception { + @Test + public void testIsOverLapping() { ScheduledRecording r = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); assertOverLapping(false, 1L, 9L, r); @@ -59,6 +62,7 @@ public class ScheduledRecordingTest extends TestCase { assertOverLapping(false, 21L, 29L, r); } + @Test public void testBuildProgram() { Channel c = new Channel.Builder().build(); Program p = new Program.Builder().build(); @@ -67,12 +71,14 @@ public class ScheduledRecordingTest extends TestCase { assertEquals("type", ScheduledRecording.TYPE_PROGRAM, actual.getType()); } + @Test public void testBuildTime() { ScheduledRecording actual = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); assertEquals("type", ScheduledRecording.TYPE_TIMED, actual.getType()); } + @Test public void testBuildFrom() { ScheduledRecording expected = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); @@ -80,6 +86,7 @@ public class ScheduledRecordingTest extends TestCase { RecordingTestUtils.assertRecordingEquals(expected, actual); } + @Test public void testBuild_priority() { ScheduledRecording a = normalizePriority( createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L)); diff --git a/tests/unit/src/com/android/tv/dvr/SchedulerTest.java b/tests/unit/src/com/android/tv/dvr/SchedulerTest.java deleted file mode 100644 index 30ac1ff1..00000000 --- a/tests/unit/src/com/android/tv/dvr/SchedulerTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.os.Build; -import android.os.Looper; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; - -import com.android.tv.InputSessionManager; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import com.android.tv.util.TvInputManagerHelper; - -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link Scheduler}. - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SchedulerTest extends AndroidTestCase { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDataManager; - private Scheduler mScheduler; - @Mock DvrManager mDvrManager; - @Mock InputSessionManager mSessionManager; - @Mock AlarmManager mMockAlarmManager; - @Mock ChannelDataManager mChannelDataManager; - @Mock TvInputManagerHelper mInputManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); - Mockito.when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); - mScheduler = new Scheduler(Looper.myLooper(), mDvrManager, mSessionManager, mDataManager, - mChannelDataManager, mInputManager, getContext(), mFakeClock, mMockAlarmManager); - } - - public void testUpdate_none() throws Exception { - mScheduler.start(); - mScheduler.update(); - verifyZeroInteractions(mMockAlarmManager); - } - - public void testUpdate_nextIn12Hours() throws Exception { - long now = mFakeClock.currentTimeMillis(); - long startTime = now + TimeUnit.HOURS.toMillis(12); - ScheduledRecording r = RecordingTestUtils - .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, - startTime + TimeUnit.HOURS.toMillis(1)); - mDataManager.addScheduledRecording(r); - mScheduler.start(); - verify(mMockAlarmManager).set( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - Scheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); - Mockito.reset(mMockAlarmManager); - mScheduler.update(); - verify(mMockAlarmManager).set( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - Scheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); - } - - public void testStartsWithin() throws Exception { - long now = mFakeClock.currentTimeMillis(); - long startTime = now + 3; - ScheduledRecording r = RecordingTestUtils - .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, startTime + 100); - assertFalse(mScheduler.startsWithin(r, 2)); - assertTrue(mScheduler.startsWithin(r, 3)); - } -} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/SeriesRecordingSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/SeriesRecordingSchedulerTest.java deleted file mode 100644 index efefb93c..00000000 --- a/tests/unit/src/com/android/tv/dvr/SeriesRecordingSchedulerTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; -import android.test.MoreAsserts; -import android.util.LongSparseArray; - -import com.android.tv.data.Program; -import com.android.tv.testing.FakeClock; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Tests for {@link SeriesRecordingScheduler} - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SeriesRecordingSchedulerTest extends AndroidTestCase { - private static final String PROGRAM_TITLE = "MyProgram"; - private static final long CHANNEL_ID = 123; - private static final long SERIES_RECORDING_ID1 = 1; - private static final String SERIES_ID = "SERIES_ID"; - private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; - private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; - private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; - private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; - - private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - - private DvrDataManagerInMemoryImpl mDataManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - FakeClock fakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getContext(), fakeClock); - } - - public void testPickOneProgramPerEpisode_onePerEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1).build(); - programs.add(program1); - Program program2 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2).build(); - programs.add(program2); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); - } - - public void testPickOneProgramPerEpisode_manyPerEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1).setStartTimeUtcMillis(0).build(); - programs.add(program1); - Program program2 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); - programs.add(program2); - Program program3 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2).build(); - programs.add(program3); - Program program4 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); - programs.add(program4); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program3); - } - - public void testPickOneProgramPerEpisode_nullEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(0).build(); - programs.add(program1); - Program program2 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(1).build(); - programs.add(program2); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/SeriesRecordingTest.java b/tests/unit/src/com/android/tv/dvr/SeriesRecordingTest.java deleted file mode 100644 index c48fec02..00000000 --- a/tests/unit/src/com/android/tv/dvr/SeriesRecordingTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.os.Build; -import android.os.Parcel; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; - -import com.android.tv.data.Program; - -import junit.framework.TestCase; - -/** - * Tests for {@link SeriesRecording}. - */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SeriesRecordingTest extends TestCase { - private static final String PROGRAM_TITLE = "MyProgram"; - private static final long CHANNEL_ID = 123; - private static final long OTHER_CHANNEL_ID = 321; - private static final String SERIES_ID = "SERIES_ID"; - private static final String OTHER_SERIES_ID = "OTHER_SERIES_ID"; - - private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - private final SeriesRecording mSeriesRecordingSeason2 = SeriesRecording - .buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build(); - private final SeriesRecording mSeriesRecordingSeason2Episode5 = SeriesRecording - .buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build(); - private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - - public void testParcelable() throws Exception { - SeriesRecording r1 = new SeriesRecording.Builder() - .setId(1) - .setChannelId(2) - .setPriority(3) - .setTitle("4") - .setDescription("5") - .setLongDescription("5-long") - .setSeriesId("6") - .setStartFromEpisode(7) - .setStartFromSeason(8) - .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) - .setCanonicalGenreIds(new int[] {9, 10}) - .setPosterUri("11") - .setPhotoUri("12") - .build(); - Parcel p1 = Parcel.obtain(); - Parcel p2 = Parcel.obtain(); - try { - r1.writeToParcel(p1, 0); - byte[] bytes = p1.marshall(); - p2.unmarshall(bytes, 0, bytes.length); - p2.setDataPosition(0); - SeriesRecording r2 = SeriesRecording.fromParcel(p2); - assertEquals(r1, r2); - } finally { - p1.recycle(); - p2.recycle(); - } - } - - public void testDoesProgramMatch_simpleMatch() { - assertDoesProgramMatch(mBaseProgram, mBaseSeriesRecording, true); - } - - public void testDoesProgramMatch_differentSeriesId() { - Program program = new Program.Builder(mBaseProgram).setSeriesId(OTHER_SERIES_ID).build(); - assertDoesProgramMatch(program, mBaseSeriesRecording, false); - } - - public void testDoesProgramMatch_differentChannel() { - Program program = new Program.Builder(mBaseProgram).setChannelId(OTHER_CHANNEL_ID).build(); - assertDoesProgramMatch(program, mBaseSeriesRecording, false); - } - - public void testDoesProgramMatch_startFromSeason2() { - Program program = mBaseProgram; - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - program = new Program.Builder(program).setSeasonNumber("1").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, false); - program = new Program.Builder(program).setSeasonNumber("2").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - program = new Program.Builder(program).setSeasonNumber("3").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - } - - public void testDoesProgramMatch_startFromSeason2episode5() { - Program program = mBaseProgram; - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setSeasonNumber("2").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setEpisodeNumber("4").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, false); - program = new Program.Builder(program).setEpisodeNumber("5").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setEpisodeNumber("6").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setSeasonNumber("3").setEpisodeNumber("1").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - } - - private void assertDoesProgramMatch(Program p, SeriesRecording seriesRecording, - boolean expected) { - assertEquals(seriesRecording + " doesProgramMatch " + p, expected, - seriesRecording.matchProgram(p)); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java b/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java new file mode 100644 index 00000000..790b2ee8 --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.data; + +import static org.junit.Assert.assertEquals; + +import android.os.Build; +import android.os.Parcel; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.data.Program; + +import org.junit.Test; + +/** + * Tests for {@link SeriesRecording}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class SeriesRecordingTest { + private static final String PROGRAM_TITLE = "MyProgram"; + private static final long CHANNEL_ID = 123; + private static final long OTHER_CHANNEL_ID = 321; + private static final String SERIES_ID = "SERIES_ID"; + private static final String OTHER_SERIES_ID = "OTHER_SERIES_ID"; + + private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() + .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + private final SeriesRecording mSeriesRecordingSeason2 = SeriesRecording + .buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build(); + private final SeriesRecording mSeriesRecordingSeason2Episode5 = SeriesRecording + .buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build(); + private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + + @Test + public void testParcelable() { + SeriesRecording r1 = new SeriesRecording.Builder() + .setId(1) + .setChannelId(2) + .setPriority(3) + .setTitle("4") + .setDescription("5") + .setLongDescription("5-long") + .setSeriesId("6") + .setStartFromEpisode(7) + .setStartFromSeason(8) + .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) + .setCanonicalGenreIds(new int[] {9, 10}) + .setPosterUri("11") + .setPhotoUri("12") + .build(); + Parcel p1 = Parcel.obtain(); + Parcel p2 = Parcel.obtain(); + try { + r1.writeToParcel(p1, 0); + byte[] bytes = p1.marshall(); + p2.unmarshall(bytes, 0, bytes.length); + p2.setDataPosition(0); + SeriesRecording r2 = SeriesRecording.fromParcel(p2); + assertEquals(r1, r2); + } finally { + p1.recycle(); + p2.recycle(); + } + } + + @Test + public void testDoesProgramMatch_simpleMatch() { + assertDoesProgramMatch(mBaseProgram, mBaseSeriesRecording, true); + } + + @Test + public void testDoesProgramMatch_differentSeriesId() { + Program program = new Program.Builder(mBaseProgram).setSeriesId(OTHER_SERIES_ID).build(); + assertDoesProgramMatch(program, mBaseSeriesRecording, false); + } + + @Test + public void testDoesProgramMatch_differentChannel() { + Program program = new Program.Builder(mBaseProgram).setChannelId(OTHER_CHANNEL_ID).build(); + assertDoesProgramMatch(program, mBaseSeriesRecording, false); + } + + @Test + public void testDoesProgramMatch_startFromSeason2() { + Program program = mBaseProgram; + assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); + program = new Program.Builder(program).setSeasonNumber("1").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2, false); + program = new Program.Builder(program).setSeasonNumber("2").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); + program = new Program.Builder(program).setSeasonNumber("3").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); + } + + @Test + public void testDoesProgramMatch_startFromSeason2episode5() { + Program program = mBaseProgram; + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); + program = new Program.Builder(program).setSeasonNumber("2").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); + program = new Program.Builder(program).setEpisodeNumber("4").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, false); + program = new Program.Builder(program).setEpisodeNumber("5").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); + program = new Program.Builder(program).setEpisodeNumber("6").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); + program = new Program.Builder(program).setSeasonNumber("3").setEpisodeNumber("1").build(); + assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); + } + + private void assertDoesProgramMatch(Program p, SeriesRecording seriesRecording, + boolean expected) { + assertEquals(seriesRecording + " doesProgramMatch " + p, expected, + seriesRecording.matchProgram(p)); + } +} diff --git a/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java b/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java new file mode 100644 index 00000000..94f88a51 --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.provider; + +import static android.support.test.InstrumentationRegistry.getContext; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManagerImpl; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.recorder.SeriesRecordingScheduler; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.tv.dvr.DvrScheduleManager} + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class DvrDbSyncTest { + private static final String INPUT_ID = "input_id"; + private static final long BASE_PROGRAM_ID = 1; + private static final long BASE_START_TIME_MS = 0; + private static final long BASE_END_TIME_MS = 1; + private static final String BASE_SEASON_NUMBER = "2"; + private static final String BASE_EPISODE_NUMBER = "3"; + private static final Program BASE_PROGRAM = new Program.Builder().setId(BASE_PROGRAM_ID) + .setStartTimeUtcMillis(BASE_START_TIME_MS).setEndTimeUtcMillis(BASE_END_TIME_MS) + .build(); + private static final Program BASE_SERIES_PROGRAM = new Program.Builder().setId(BASE_PROGRAM_ID) + .setStartTimeUtcMillis(BASE_START_TIME_MS).setEndTimeUtcMillis(BASE_END_TIME_MS) + .setSeasonNumber(BASE_SEASON_NUMBER).setEpisodeNumber(BASE_EPISODE_NUMBER).build(); + private static final ScheduledRecording BASE_SCHEDULE = + ScheduledRecording.builder(INPUT_ID, BASE_PROGRAM).build(); + private static final ScheduledRecording BASE_SERIES_SCHEDULE = + ScheduledRecording.builder(INPUT_ID, BASE_SERIES_PROGRAM).build(); + + private DvrDbSync mDbSync; + @Mock private DvrManager mDvrManager; + @Mock private DvrDataManagerImpl mDataManager; + @Mock private ChannelDataManager mChannelDataManager; + @Mock private SeriesRecordingScheduler mSeriesRecordingScheduler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); + when(mDvrManager.addSeriesRecording(anyObject(), anyObject(), anyInt())) + .thenReturn(SeriesRecording.builder(INPUT_ID, BASE_PROGRAM).build()); + mDbSync = new DvrDbSync(getContext(), mDataManager, mChannelDataManager, + mDvrManager, mSeriesRecordingScheduler); + } + + @Test + public void testHandleUpdateProgram_null() { + addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); + mDbSync.handleUpdateProgram(null, BASE_PROGRAM_ID); + verify(mDataManager).removeScheduledRecording(BASE_SCHEDULE); + } + + @Test + public void testHandleUpdateProgram_changeTimeNotStarted() { + addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); + long startTimeMs = BASE_START_TIME_MS + 1; + long endTimeMs = BASE_END_TIME_MS + 1; + Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) + .setEndTimeUtcMillis(endTimeMs).build(); + mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); + assertUpdateScheduleCalled(program); + } + + @Test + public void testHandleUpdateProgram_changeTimeInProgressNotCalled() { + addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SCHEDULE) + .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS).build()); + long startTimeMs = BASE_START_TIME_MS + 1; + Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) + .build(); + mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); + verify(mDataManager, never()).updateScheduledRecording(anyObject()); + } + + @Test + public void testHandleUpdateProgram_changeSeason() { + addSchedule(BASE_PROGRAM_ID, BASE_SERIES_SCHEDULE); + String seasonNumber = BASE_SEASON_NUMBER + "1"; + String episodeNumber = BASE_EPISODE_NUMBER + "1"; + Program program = new Program.Builder(BASE_SERIES_PROGRAM).setSeasonNumber(seasonNumber) + .setEpisodeNumber(episodeNumber).build(); + mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); + assertUpdateScheduleCalled(program); + } + + @Test + public void testHandleUpdateProgram_finished() { + addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SERIES_SCHEDULE) + .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); + String seasonNumber = BASE_SEASON_NUMBER + "1"; + String episodeNumber = BASE_EPISODE_NUMBER + "1"; + Program program = new Program.Builder(BASE_SERIES_PROGRAM).setSeasonNumber(seasonNumber) + .setEpisodeNumber(episodeNumber).build(); + mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); + verify(mDataManager, never()).updateScheduledRecording(anyObject()); + } + + private void addSchedule(long programId, ScheduledRecording schedule) { + when(mDataManager.getScheduledRecordingForProgramId(programId)).thenReturn(schedule); + } + + private void assertUpdateScheduleCalled(Program program) { + verify(mDataManager).updateScheduledRecording( + eq(ScheduledRecording.builder(INPUT_ID, program).build())); + } +} diff --git a/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java b/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java new file mode 100644 index 00000000..216d4d5b --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.provider; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.dvr.data.SeasonEpisodeNumber; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link EpisodicProgramLoadTask} + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class EpisodicProgramLoadTaskTest { + private static final long SERIES_RECORDING_ID1 = 1; + private static final long SERIES_RECORDING_ID2 = 2; + private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; + private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; + private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; + private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; + + @Test + public void testEpisodeAlreadyScheduled_true() { + List seasonEpisodeNumbers = new ArrayList<>(); + SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + seasonEpisodeNumbers.add(seasonEpisodeNumber); + assertTrue(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1))); + } + + @Test + public void testEpisodeAlreadyScheduled_false() { + List seasonEpisodeNumbers = new ArrayList<>(); + SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + seasonEpisodeNumbers.add(seasonEpisodeNumber); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1))); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1))); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2))); + } + + @Test + public void testEpisodeAlreadyScheduled_null() { + List seasonEpisodeNumbers = new ArrayList<>(); + SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + seasonEpisodeNumbers.add(seasonEpisodeNumber); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1))); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, null))); + assertFalse(seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, null))); + } +} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java b/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java new file mode 100644 index 00000000..8f7dcaf2 --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import static org.mockito.Mockito.verify; + +import android.content.Intent; +import android.os.Build; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; +import android.test.ServiceTestCase; + +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DvrRecordingService}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class DvrRecordingServiceTest + extends ServiceTestCase { + private final TestableFeature mDvrFeature = CommonFeatures.DVR; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mDvrFeature.enableForTest(); + MockitoAnnotations.initMocks(this); + setupService(); + } + + @Override + protected void tearDown() throws Exception { + mDvrFeature.resetForTests(); + super.tearDown(); + } + + public DvrRecordingServiceTest() { + super(MockDvrRecordingService.class); + } + + public void testStartService_null() throws Exception { + // Not recording + startService(null); + assertFalse(getService().mInForeground); + + // Recording + getService().startRecording(); + startService(null); + assertTrue(getService().mInForeground); + assertTrue(getService().mIsRecording); + getService().reset(); + } + + public void testStartService_noUpcomingRecording() throws Exception { + Intent intent = new Intent(getContext(), DvrRecordingServiceTest.class); + intent.putExtra(DvrRecordingService.EXTRA_START_FOR_RECORDING, false); + + // Not recording + startService(intent); + assertTrue(getService().mInForeground); + assertFalse(getService().mForegroundForUpcomingRecording); + getService().stopForegroundIfNotRecordingInternal(); + assertFalse(getService().mInForeground); + + // Recording, ended quickly + getService().startRecording(); + startService(intent); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopRecording(); + assertFalse(getService().mInForeground); + assertFalse(getService().mIsRecording); + getService().stopForegroundIfNotRecordingInternal(); + assertFalse(getService().mInForeground); + assertFalse(getService().mIsRecording); + getService().reset(); + + // Recording, ended later + getService().startRecording(); + startService(intent); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopForegroundIfNotRecordingInternal(); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopRecording(); + assertFalse(getService().mInForeground); + assertFalse(getService().mIsRecording); + getService().reset(); + } + + public void testStartService_hasUpcomingRecording() throws Exception { + Intent intent = new Intent(getContext(), DvrRecordingServiceTest.class); + intent.putExtra(DvrRecordingService.EXTRA_START_FOR_RECORDING, true); + + // Not recording + startService(intent); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertFalse(getService().mIsRecording); + getService().startRecording(); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopRecording(); + assertFalse(getService().mInForeground); + assertFalse(getService().mIsRecording); + getService().reset(); + + // Recording + getService().startRecording(); + startService(intent); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().startRecording(); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopRecording(); + assertTrue(getService().mInForeground); + assertTrue(getService().mForegroundForUpcomingRecording); + assertTrue(getService().mIsRecording); + getService().stopRecording(); + assertFalse(getService().mInForeground); + assertFalse(getService().mIsRecording); + getService().reset(); + } + + public static class MockDvrRecordingService extends DvrRecordingService { + private int mRecordingCount = 0; + private boolean mInForeground; + private boolean mForegroundForUpcomingRecording; + + @Override + protected void startForegroundInternal(boolean hasUpcomingRecording) { + mForegroundForUpcomingRecording = hasUpcomingRecording; + mInForeground = true; + } + + @Override + protected void stopForegroundInternal() { + mInForeground = false; + } + + private void startRecording() { + mOnRecordingSessionChangeListener.onRecordingSessionChange(true, ++mRecordingCount); + } + + private void stopRecording() { + mOnRecordingSessionChangeListener.onRecordingSessionChange(false, --mRecordingCount); + } + + private void reset() { + mRecordingCount = 0; + mInForeground = false; + mIsRecording = false; + } + } +} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java new file mode 100644 index 00000000..e5c27e2c --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import static android.support.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlarmManager; +import android.media.tv.TvInputInfo; +import android.os.Build; +import android.os.Looper; +import android.os.SystemClock; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.InputSessionManager; +import com.android.tv.data.Channel; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.WritableDvrDataManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.InputTaskScheduler.RecordingTaskFactory; +import com.android.tv.testing.FakeClock; +import com.android.tv.testing.dvr.RecordingTestUtils; +import com.android.tv.util.Clock; +import com.android.tv.util.TestUtils; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link InputTaskScheduler}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class InputTaskSchedulerTest { + private static final String INPUT_ID = "input_id"; + private static final int CHANNEL_ID = 1; + private static final long LISTENER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); + private static final int TUNER_COUNT_ONE = 1; + private static final int TUNER_COUNT_TWO = 2; + private static final long LOW_PRIORITY = 1; + private static final long HIGH_PRIORITY = 2; + + private FakeClock mFakeClock; + private InputTaskScheduler mScheduler; + @Mock private DvrManager mDvrManager; + @Mock private WritableDvrDataManager mDataManager; + @Mock private InputSessionManager mSessionManager; + @Mock private AlarmManager mMockAlarmManager; + @Mock private ChannelDataManager mChannelDataManager; + private List mRecordingTasks; + + @Before + public void setUp() throws Exception { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mRecordingTasks = new ArrayList(); + MockitoAnnotations.initMocks(this); + mFakeClock = FakeClock.createWithCurrentTime(); + TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE); + mScheduler = new InputTaskScheduler(getContext(), input, Looper.myLooper(), + mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mFakeClock, + new RecordingTaskFactory() { + @Override + public RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, + Channel channel, DvrManager dvrManager, + InputSessionManager sessionManager, WritableDvrDataManager dataManager, + Clock clock) { + RecordingTask task = mock(RecordingTask.class); + when(task.getPriority()).thenReturn(scheduledRecording.getPriority()); + when(task.getEndTimeMs()).thenReturn(scheduledRecording.getEndTimeMs()); + mRecordingTasks.add(task); + return task; + } + }); + } + + @Test + public void testAddSchedule_past() { + ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, + CHANNEL_ID, 0L, 1L); + when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r); + mScheduler.handleAddSchedule(r); + mScheduler.handleBuildSchedule(); + verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1)) + .changeState(any(ScheduledRecording.class), + eq(ScheduledRecording.STATE_RECORDING_FAILED)); + } + + @Test + public void testAddSchedule_start() { + mScheduler.handleAddSchedule(RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, + CHANNEL_ID, mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); + mScheduler.handleBuildSchedule(); + verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); + } + + @Test + public void testAddSchedule_consecutiveNoStop() { + long startTimeMs = mFakeClock.currentTimeMillis(); + long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + long id = 0; + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + LOW_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + startTimeMs = endTimeMs; + endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + HIGH_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); + // The first schedule should not be stopped because the second one should wait for the end + // of the first schedule. + SystemClock.sleep(LISTENER_TIMEOUT_MS); + verify(mRecordingTasks.get(0), never()).stop(); + } + + @Test + public void testAddSchedule_consecutiveNoFail() { + long startTimeMs = mFakeClock.currentTimeMillis(); + long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + long id = 0; + when(mDataManager.getScheduledRecording(anyLong())).thenReturn(ScheduledRecording + .builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + HIGH_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + startTimeMs = endTimeMs; + endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + LOW_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); + SystemClock.sleep(LISTENER_TIMEOUT_MS); + verify(mRecordingTasks.get(0), never()).stop(); + // The second schedule should not fail because it can starts after the first one finishes. + SystemClock.sleep(LISTENER_TIMEOUT_MS); + verify(mDataManager, never()) + .changeState(any(ScheduledRecording.class), + eq(ScheduledRecording.STATE_RECORDING_FAILED)); + } + + @Test + public void testAddSchedule_consecutiveUseLessSession() throws Exception { + TvInputInfo input = createTvInputInfo(TUNER_COUNT_TWO); + mScheduler.updateTvInputInfo(input); + long startTimeMs = mFakeClock.currentTimeMillis(); + long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + long id = 0; + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + LOW_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + startTimeMs = endTimeMs; + endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, + HIGH_PRIORITY, startTimeMs, endTimeMs)); + mScheduler.handleBuildSchedule(); + verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); + SystemClock.sleep(LISTENER_TIMEOUT_MS); + verify(mRecordingTasks.get(0), never()).stop(); + // The second schedule should wait until the first one finishes rather than creating a new + // session even though there are available tuners. + assertTrue(mRecordingTasks.size() == 1); + } + + @Test + public void testUpdateSchedule_noCancel() { + ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, + CHANNEL_ID, mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); + mScheduler.handleAddSchedule(r); + mScheduler.handleBuildSchedule(); + mScheduler.handleUpdateSchedule(r); + SystemClock.sleep(LISTENER_TIMEOUT_MS); + verify(mRecordingTasks.get(0), never()).cancel(); + } + + @Test + public void testUpdateSchedule_cancel() { + ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, + CHANNEL_ID, mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); + mScheduler.handleAddSchedule(r); + mScheduler.handleBuildSchedule(); + mScheduler.handleUpdateSchedule(ScheduledRecording.buildFrom(r) + .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) + .build()); + verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel(); + } + + private TvInputInfo createTvInputInfo(int tunerCount) throws Exception { + return TestUtils.createTvInputInfo(null, null, null, 0, false, true, tunerCount); + } +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java b/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java new file mode 100644 index 00000000..37561a42 --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import static android.support.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.InputSessionManager; +import com.android.tv.InputSessionManager.RecordingSession; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.data.Channel; +import com.android.tv.dvr.DvrDataManagerInMemoryImpl; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.RecordingTask.State; +import com.android.tv.testing.FakeClock; +import com.android.tv.testing.dvr.RecordingTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link RecordingTask}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class RecordingTaskTest { + private static final long DURATION = TimeUnit.MINUTES.toMillis(30); + private static final long START_OFFSET_MS = RecordingScheduler.MS_TO_WAKE_BEFORE_START; + private static final String INPUT_ID = "input_id"; + private static final int CHANNEL_ID = 273; + + private FakeClock mFakeClock; + private DvrDataManagerInMemoryImpl mDataManager; + @Mock Handler mMockHandler; + @Mock DvrManager mDvrManager; + @Mock InputSessionManager mMockSessionManager; + @Mock RecordingSession mMockRecordingSession; + private final TestableFeature mDvrFeature = CommonFeatures.DVR; + + @Before + public void setUp() { + mDvrFeature.enableForTest(); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + MockitoAnnotations.initMocks(this); + mFakeClock = FakeClock.createWithCurrentTime(); + mDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); + } + + @After + public void tearDown() { + mDvrFeature.resetForTests(); + } + + @Test + public void testHandle_init() { + Channel channel = createTestChannel(); + ScheduledRecording r = createRecording(channel); + RecordingTask task = createRecordingTask(r, channel); + String inputId = channel.getInputId(); + when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), + eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); + when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); + assertTrue(task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE))); + assertEquals(State.CONNECTION_PENDING, task.getState()); + verify(mMockSessionManager).createRecordingSession(eq(inputId), anyString(), eq(task), + eq(mMockHandler), anyLong()); + verify(mMockRecordingSession).tune(eq(inputId), eq(channel.getUri())); + verifyNoMoreInteractions(mMockHandler, mMockRecordingSession, mMockSessionManager); + } + + private static Channel createTestChannel() { + return new Channel.Builder().setInputId(INPUT_ID).setId(CHANNEL_ID) + .setDisplayName("Test Ch " + CHANNEL_ID).build(); + } + + @Test + public void testOnConnected() { + Channel channel = createTestChannel(); + ScheduledRecording r = createRecording(channel); + mDataManager.addScheduledRecording(r); + RecordingTask task = createRecordingTask(r, channel); + String inputId = channel.getInputId(); + when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), + eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); + when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); + task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE)); + task.onTuned(channel.getUri()); + assertEquals(State.CONNECTED, task.getState()); + } + + private ScheduledRecording createRecording(Channel c) { + long startTime = mFakeClock.currentTimeMillis() + START_OFFSET_MS; + long endTime = startTime + DURATION; + return RecordingTestUtils.createTestRecordingWithPeriod(c.getInputId(), c.getId(), + startTime, endTime); + } + + private RecordingTask createRecordingTask(ScheduledRecording r, Channel channel) { + RecordingTask recordingTask = new RecordingTask(getContext(), r, channel, mDvrManager, + mMockSessionManager, mDataManager, mFakeClock); + recordingTask.setHandler(mMockHandler); + return recordingTask; + } + + private Message createMessage(int what) { + Message msg = new Message(); + msg.setTarget(mMockHandler); + msg.what = what; + return msg; + } +} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java b/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java new file mode 100644 index 00000000..ca72e13f --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import static android.support.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.assertTrue; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; +import android.test.MoreAsserts; + +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.dvr.DvrDataManagerInMemoryImpl; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.testing.FakeClock; +import com.android.tv.testing.dvr.RecordingTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link ScheduledProgramReaper}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class ScheduledProgramReaperTest { + private static final String INPUT_ID = "input_id"; + private static final int CHANNEL_ID = 273; + private static final long DURATION = TimeUnit.HOURS.toMillis(1); + + private ScheduledProgramReaper mReaper; + private FakeClock mFakeClock; + private DvrDataManagerInMemoryImpl mDvrDataManager; + @Mock private DvrManager mDvrManager; + private final TestableFeature mDvrFeature = CommonFeatures.DVR; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDvrFeature.enableForTest(); + mFakeClock = FakeClock.createWithTimeOne(); + mDvrDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); + mReaper = new ScheduledProgramReaper(mDvrDataManager, mFakeClock); + } + + @After + public void tearDown() { + mDvrFeature.resetForTests(); + } + + @Test + public void testRun_noRecordings() { + assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty()); + mReaper.run(); + assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty()); + } + + @Test + public void testRun_oneRecordingsTomorrow() { + ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + mReaper.run(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + } + + @Test + public void testRun_oneRecordingsStarted() { + ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + mFakeClock.increment(TimeUnit.DAYS); + mReaper.run(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + } + + @Test + public void testRun_oneRecordingsFinished() { + ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + mFakeClock.increment(TimeUnit.DAYS); + mFakeClock.increment(TimeUnit.MINUTES, 2); + mReaper.run(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + } + + @Test + public void testRun_oneRecordingsExpired() { + ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); + MoreAsserts + .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + mFakeClock.increment(TimeUnit.DAYS, 1 + ScheduledProgramReaper.DAYS); + mFakeClock.increment(TimeUnit.MILLISECONDS, DURATION); + // After the cutoff and enough so we can see on the clock + mFakeClock.increment(TimeUnit.SECONDS, 1); + + mReaper.run(); + assertTrue("Recordings after reaper at " + com.android.tv.util.Utils + .toIsoDateTimeString(mFakeClock.currentTimeMillis()), + mDvrDataManager.getAllScheduledRecordings().isEmpty()); + } + + private ScheduledRecording addNewScheduledRecordingForTomorrow() { + long startTime = mFakeClock.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); + ScheduledRecording recording = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, + CHANNEL_ID, startTime, startTime + DURATION); + return mDvrDataManager.addScheduledRecordingInternal( + ScheduledRecording.buildFrom(recording) + .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); + } +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java new file mode 100644 index 00000000..a5154729 --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.recorder; + +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.os.Build; +import android.os.Looper; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; + +import com.android.tv.InputSessionManager; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.dvr.DvrDataManagerInMemoryImpl; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.testing.FakeClock; +import com.android.tv.testing.dvr.RecordingTestUtils; +import com.android.tv.util.TvInputManagerHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link RecordingScheduler}. + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class SchedulerTest { + private static final String INPUT_ID = "input_id"; + private static final int CHANNEL_ID = 273; + + private FakeClock mFakeClock; + private DvrDataManagerInMemoryImpl mDataManager; + private RecordingScheduler mScheduler; + @Mock DvrManager mDvrManager; + @Mock InputSessionManager mSessionManager; + @Mock AlarmManager mMockAlarmManager; + @Mock ChannelDataManager mChannelDataManager; + @Mock TvInputManagerHelper mInputManager; + private final TestableFeature mDvrFeature = CommonFeatures.DVR; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDvrFeature.enableForTest(); + mFakeClock = FakeClock.createWithCurrentTime(); + mDataManager = new DvrDataManagerInMemoryImpl(getTargetContext(), mFakeClock); + Mockito.when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); + mScheduler = new RecordingScheduler(Looper.myLooper(), mDvrManager, mSessionManager, mDataManager, + mChannelDataManager, mInputManager, getTargetContext(), mFakeClock, + mMockAlarmManager); + } + + @After + public void tearDown() { + mDvrFeature.resetForTests(); + } + + @Test + public void testUpdate_none() { + mScheduler.updateAndStartServiceIfNeeded(); + verifyZeroInteractions(mMockAlarmManager); + } + + @Test + public void testUpdate_nextIn12Hours() { + long now = mFakeClock.currentTimeMillis(); + long startTime = now + TimeUnit.HOURS.toMillis(12); + ScheduledRecording r = RecordingTestUtils + .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, + startTime + TimeUnit.HOURS.toMillis(1)); + mDataManager.addScheduledRecording(r); + verify(mMockAlarmManager).setExactAndAllowWhileIdle( + eq(AlarmManager.RTC_WAKEUP), + eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), + any(PendingIntent.class)); + Mockito.reset(mMockAlarmManager); + mScheduler.updateAndStartServiceIfNeeded(); + verify(mMockAlarmManager).setExactAndAllowWhileIdle( + eq(AlarmManager.RTC_WAKEUP), + eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), + any(PendingIntent.class)); + } + + @Test + public void testStartsWithin() { + long now = mFakeClock.currentTimeMillis(); + long startTime = now + 3; + ScheduledRecording r = RecordingTestUtils + .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, startTime + 100); + assertFalse(mScheduler.startsWithin(r, 2)); + assertTrue(mScheduler.startsWithin(r, 3)); + } +} \ No newline at end of file diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java new file mode 100644 index 00000000..16fa1baf --- /dev/null +++ b/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.recorder; + +import static android.support.test.InstrumentationRegistry.getContext; + +import android.os.Build; +import android.support.test.filters.SdkSuppress; +import android.support.test.filters.SmallTest; +import android.test.MoreAsserts; +import android.util.LongSparseArray; + +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.data.Program; +import com.android.tv.dvr.DvrDataManagerInMemoryImpl; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.testing.FakeClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for {@link SeriesRecordingScheduler} + */ +@SmallTest +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) +public class SeriesRecordingSchedulerTest { + private static final String PROGRAM_TITLE = "MyProgram"; + private static final long CHANNEL_ID = 123; + private static final long SERIES_RECORDING_ID1 = 1; + private static final String SERIES_ID = "SERIES_ID"; + private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; + private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; + private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; + private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; + + private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() + .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + private final TestableFeature mDvrFeature = CommonFeatures.DVR; + + private DvrDataManagerInMemoryImpl mDataManager; + + @Before + public void setUp() { + mDvrFeature.enableForTest(); + FakeClock fakeClock = FakeClock.createWithCurrentTime(); + mDataManager = new DvrDataManagerInMemoryImpl(getContext(), fakeClock); + } + + @After + public void tearDown() { + mDvrFeature.resetForTests(); + } + + @Test + public void testPickOneProgramPerEpisode_onePerEpisode() { + SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) + .setId(SERIES_RECORDING_ID1).build(); + mDataManager.addSeriesRecording(seriesRecording); + List programs = new ArrayList<>(); + Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) + .setEpisodeNumber(EPISODE_NUMBER1).build(); + programs.add(program1); + Program program2 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) + .setEpisodeNumber(EPISODE_NUMBER2).build(); + programs.add(program2); + LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); + MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); + } + + @Test + public void testPickOneProgramPerEpisode_manyPerEpisode() { + SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) + .setId(SERIES_RECORDING_ID1).build(); + mDataManager.addSeriesRecording(seriesRecording); + List programs = new ArrayList<>(); + Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) + .setEpisodeNumber(EPISODE_NUMBER1).setStartTimeUtcMillis(0).build(); + programs.add(program1); + Program program2 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); + programs.add(program2); + Program program3 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) + .setEpisodeNumber(EPISODE_NUMBER2).build(); + programs.add(program3); + Program program4 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); + programs.add(program4); + LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); + MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program3); + } + + @Test + public void testPickOneProgramPerEpisode_nullEpisode() { + SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) + .setId(SERIES_RECORDING_ID1).build(); + mDataManager.addSeriesRecording(seriesRecording); + List programs = new ArrayList<>(); + Program program1 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(0).build(); + programs.add(program1); + Program program2 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(1).build(); + programs.add(program2); + LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); + MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); + } +} diff --git a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java b/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java index a571e626..5667ee6b 100644 --- a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java +++ b/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java @@ -22,6 +22,9 @@ import android.support.v17.leanback.widget.ObjectAdapter; import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; + import java.util.Arrays; import java.util.Comparator; import java.util.Objects; @@ -31,29 +34,30 @@ import java.util.Objects; */ @SmallTest public class SortedArrayAdapterTest extends TestCase { - - public static final TestData P1 = TestData.create(1, "one"); - public static final TestData P2 = TestData.create(2, "before"); - public static final TestData P3 = TestData.create(3, "other"); - public static final TestData EXTRA = TestData.create(4, "extra"); + public static final TestData P1 = TestData.create(1, "c"); + public static final TestData P2 = TestData.create(2, "b"); + public static final TestData P3 = TestData.create(3, "a"); + public static final TestData EXTRA = TestData.create(4, "k"); private TestSortedArrayAdapter mAdapter; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, null); } + @Test public void testContents_empty() { assertEmpty(); } + @Test public void testAdd_one() { mAdapter.add(P1); assertNotEmpty(); assertContentsInOrder(mAdapter, P1); } + @Test public void testAdd_two() { mAdapter.add(P1); mAdapter.add(P2); @@ -61,12 +65,14 @@ public class SortedArrayAdapterTest extends TestCase { assertContentsInOrder(mAdapter, P2, P1); } + @Test public void testSetInitialItems_two() { mAdapter.setInitialItems(Arrays.asList(P1, P2)); assertNotEmpty(); assertContentsInOrder(mAdapter, P2, P1); } + @Test public void testMaxInitialCount() { mAdapter = new TestSortedArrayAdapter(1, null); mAdapter.setInitialItems(Arrays.asList(P1, P2)); @@ -75,6 +81,7 @@ public class SortedArrayAdapterTest extends TestCase { assertEquals(mAdapter.get(0), P2); } + @Test public void testExtraItem() { mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, EXTRA); mAdapter.setInitialItems(Arrays.asList(P1, P2)); @@ -88,6 +95,7 @@ public class SortedArrayAdapterTest extends TestCase { assertEquals(mAdapter.get(0), EXTRA); } + @Test public void testExtraItemWithMaxCount() { mAdapter = new TestSortedArrayAdapter(1, EXTRA); mAdapter.setInitialItems(Arrays.asList(P1, P2)); @@ -100,6 +108,7 @@ public class SortedArrayAdapterTest extends TestCase { assertEquals(mAdapter.get(0), EXTRA); } + @Test public void testRemove() { mAdapter.add(P1); mAdapter.add(P2); @@ -111,8 +120,47 @@ public class SortedArrayAdapterTest extends TestCase { assertContentsInOrder(mAdapter, P1); mAdapter.remove(P1); assertEmpty(); + mAdapter.add(P1); + mAdapter.add(P2); + mAdapter.add(P3); + assertContentsInOrder(mAdapter, P3, P2, P1); + mAdapter.removeItems(0, 2); + assertContentsInOrder(mAdapter, P1); + mAdapter.add(P2); + mAdapter.add(P3); + mAdapter.addExtraItem(EXTRA); + assertContentsInOrder(mAdapter, P3, P2, P1, EXTRA); + mAdapter.removeItems(1, 1); + assertContentsInOrder(mAdapter, P3, P1, EXTRA); + mAdapter.removeItems(1, 2); + assertContentsInOrder(mAdapter, P3); + mAdapter.addExtraItem(EXTRA); + mAdapter.addExtraItem(P2); + mAdapter.add(P1); + assertContentsInOrder(mAdapter, P3, P1, EXTRA, P2); + mAdapter.removeItems(1, 2); + assertContentsInOrder(mAdapter, P3, P2); + mAdapter.add(P1); + assertContentsInOrder(mAdapter, P3, P1, P2); + } + + @Test + public void testReplace() { + mAdapter.add(P1); + mAdapter.add(P2); + assertNotEmpty(); + assertContentsInOrder(mAdapter, P2, P1); + mAdapter.replace(1, P3); + assertContentsInOrder(mAdapter, P3, P2); + mAdapter.replace(0, P1); + assertContentsInOrder(mAdapter, P2, P1); + mAdapter.addExtraItem(EXTRA); + assertContentsInOrder(mAdapter, P2, P1, EXTRA); + mAdapter.replace(2, P3); + assertContentsInOrder(mAdapter, P2, P1, P3); } + @Test public void testChange_sorting() { TestData p2_changed = TestData.create(2, "z changed"); mAdapter.add(P1); @@ -123,6 +171,7 @@ public class SortedArrayAdapterTest extends TestCase { assertContentsInOrder(mAdapter, P1, p2_changed); } + @Test public void testChange_new() { mAdapter.change(P1); assertNotEmpty(); @@ -194,7 +243,7 @@ public class SortedArrayAdapterTest extends TestCase { } @Override - long getId(TestData item) { + protected long getId(TestData item) { return item.mId; } } diff --git a/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java b/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java new file mode 100644 index 00000000..3f827ce1 --- /dev/null +++ b/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.experiments; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; + +import com.android.tv.common.BuildConfig; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link Experiments}. + */ +@SmallTest +public class ExperimentsTest { + @Before + public void setUp() { + ExperimentFlag.initForTest(); + } + + + @Test + public void testEngOnlyDefault() { + assertEquals("ENABLE_DEVELOPER_FEATURES", Boolean.valueOf(BuildConfig.ENG), + Experiments.ENABLE_DEVELOPER_FEATURES.get()); + } + + +} diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index 35e2a0fe..e8cfdbef 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -15,11 +15,16 @@ */ package com.android.tv.menu; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; +import org.junit.Before; +import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -29,23 +34,23 @@ import org.mockito.stubbing.Answer; * Tests for {@link Menu}. */ @SmallTest -public class MenuTest extends AndroidTestCase { +public class MenuTest { private Menu mMenu; private IMenuView mMenuView; private OnMenuVisibilityChangeListener mVisibilityChangeListener; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { mMenuView = Mockito.mock(IMenuView.class); MenuRowFactory factory = Mockito.mock(MenuRowFactory.class); Mockito.when(factory.createMenuRow(Mockito.any(Menu.class), Mockito.any(Class.class))) .thenReturn(null); mVisibilityChangeListener = Mockito.mock(OnMenuVisibilityChangeListener.class); - mMenu = new Menu(getContext(), mMenuView, factory, mVisibilityChangeListener); + mMenu = new Menu(getTargetContext(), mMenuView, factory, mVisibilityChangeListener); mMenu.disableAnimationForTest(); } + @Test public void testScheduleHide() { mMenu.show(Menu.REASON_NONE); setMenuVisible(true); @@ -67,6 +72,7 @@ public class MenuTest extends AndroidTestCase { assertFalse("Hide is scheduled", mMenu.isHideScheduled()); } + @Test public void testShowHide_ReasonNone() { // Show with REASON_NONE mMenu.show(Menu.REASON_NONE); @@ -87,6 +93,7 @@ public class MenuTest extends AndroidTestCase { Mockito.verify(mMenuView).onHide(); } + @Test public void testShowHide_ReasonGuide() { // Show with REASON_GUIDE mMenu.show(Menu.REASON_GUIDE); @@ -107,6 +114,7 @@ public class MenuTest extends AndroidTestCase { Mockito.verify(mMenuView).onHide(); } + @Test public void testShowHide_ReasonPlayControlsFastForward() { // Show with REASON_PLAY_CONTROLS_FAST_FORWARD mMenu.show(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD); diff --git a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java index 6b0726d9..49ba8514 100644 --- a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java +++ b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java @@ -15,18 +15,24 @@ */ package com.android.tv.menu; +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import android.media.tv.TvTrackInfo; import android.os.SystemClock; import android.support.test.filters.MediumTest; +import android.text.TextUtils; import com.android.tv.BaseMainActivityTestCase; -import com.android.tv.MainActivity; -import com.android.tv.customization.CustomAction; import com.android.tv.testing.Constants; -import com.android.tv.testing.Utils; +import com.android.tv.testing.testinput.ChannelState; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TvTestInputConstants; +import org.junit.Before; +import org.junit.Test; + import java.util.Collections; import java.util.List; @@ -35,24 +41,22 @@ import java.util.List; */ @MediumTest public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { - private static final int WAIT_TRACK_SIZE_TIMEOUT_MS = 300; - public static final int TRACK_SIZE_CHECK_INTERVAL_MS = 10; + private static final int WAIT_TRACK_EVENT_TIMEOUT_MS = 300; + public static final int TRACK_CHECK_INTERVAL_MS = 10; // TODO: Refactor TvOptionsRowAdapter so it does not rely on MainActivity private TvOptionsRowAdapter mTvOptionsRowAdapter; - public TvOptionsRowAdapterTest() { - super(MainActivity.class); - } - @Override - protected void setUp() throws Exception { + @Before + public void setUp() { super.setUp(); mTvOptionsRowAdapter = new TvOptionsRowAdapter(mActivity, Collections.emptyList()); tuneToChannel(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY); waitUntilAudioTracksHaveSize(1); + waitUntilAudioTrackSelected(ChannelState.DEFAULT.getSelectedAudioTrackId()); // update should be called on the main thread to avoid the multi-thread problem. - Utils.runOnMainSync(new Runnable() { + getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mTvOptionsRowAdapter.update(); @@ -60,11 +64,13 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { }); } + @Test public void testUpdateAudioAction_2tracks() { ChannelStateData data = new ChannelStateData(); data.mTvTrackInfos.add(Constants.GENERIC_AUDIO_TRACK); updateThenTune(data, TvTestInputConstants.CH_2); waitUntilAudioTracksHaveSize(2); + waitUntilAudioTrackSelected(Constants.EN_STEREO_AUDIO_TRACK.getId()); boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); assertEquals("update Action had change", true, result); @@ -72,46 +78,87 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } + @Test public void testUpdateAudioAction_1track() { ChannelStateData data = new ChannelStateData(); data.mTvTrackInfos.clear(); data.mTvTrackInfos.add(Constants.GENERIC_AUDIO_TRACK); + data.mSelectedVideoTrackId = null; + data.mSelectedAudioTrackId = Constants.GENERIC_AUDIO_TRACK.getId(); updateThenTune(data, TvTestInputConstants.CH_2); waitUntilAudioTracksHaveSize(1); + waitUntilAudioTrackSelected(Constants.GENERIC_AUDIO_TRACK.getId()); boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); - assertEquals("update Action had change", false, result); + assertEquals("update Action had change", true, result); assertEquals("Multi Audio enabled", false, MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } + @Test public void testUpdateAudioAction_noTracks() { ChannelStateData data = new ChannelStateData(); data.mTvTrackInfos.clear(); + data.mTvTrackInfos.add(ChannelState.DEFAULT_VIDEO_TRACK); + data.mSelectedVideoTrackId = ChannelState.DEFAULT_VIDEO_TRACK.getId(); + data.mSelectedAudioTrackId = null; updateThenTune(data, TvTestInputConstants.CH_2); - waitUntilAudioTracksHaveSize(0); + // Wait for the video tracks, because there's no audio track. + waitUntilVideoTracksHaveSize(1); + waitUntilVideoTrackSelected(data.mSelectedVideoTrackId); boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); - assertEquals("update Action had change", false, result); + assertEquals("update Action had change", true, result); assertEquals("Multi Audio enabled", false, MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } private void waitUntilAudioTracksHaveSize(int expected) { + waitUntilTracksHaveSize(TvTrackInfo.TYPE_AUDIO, expected); + } + + private void waitUntilVideoTracksHaveSize(int expected) { + waitUntilTracksHaveSize(TvTrackInfo.TYPE_VIDEO, expected); + } + + private void waitUntilTracksHaveSize(int trackType, int expected) { long start = SystemClock.elapsedRealtime(); int size = -1; - while (SystemClock.elapsedRealtime() < start + WAIT_TRACK_SIZE_TIMEOUT_MS) { + while (SystemClock.elapsedRealtime() < start + WAIT_TRACK_EVENT_TIMEOUT_MS) { getInstrumentation().waitForIdleSync(); - List tracks = mActivity.getTracks(TvTrackInfo.TYPE_AUDIO); + List tracks = mActivity.getTracks(trackType); if (tracks != null) { size = tracks.size(); if (size == expected) { return; } } - SystemClock.sleep(TRACK_SIZE_CHECK_INTERVAL_MS); + SystemClock.sleep(TRACK_CHECK_INTERVAL_MS); } - fail("Waited for " + WAIT_TRACK_SIZE_TIMEOUT_MS + " milliseconds for track size to be " + fail("Waited for " + WAIT_TRACK_EVENT_TIMEOUT_MS + " milliseconds for track size to be " + expected + " but was " + size); } + + private void waitUntilAudioTrackSelected(String trackId) { + waitUntilTrackSelected(TvTrackInfo.TYPE_AUDIO, trackId); + } + + private void waitUntilVideoTrackSelected(String trackId) { + waitUntilTrackSelected(TvTrackInfo.TYPE_VIDEO, trackId); + } + + private void waitUntilTrackSelected(int trackType, String trackId) { + long start = SystemClock.elapsedRealtime(); + String selectedTrackId = null; + while (SystemClock.elapsedRealtime() < start + WAIT_TRACK_EVENT_TIMEOUT_MS) { + getInstrumentation().waitForIdleSync(); + selectedTrackId = mActivity.getSelectedTrack(trackType); + if (TextUtils.equals(selectedTrackId, trackId)) { + return; + } + SystemClock.sleep(TRACK_CHECK_INTERVAL_MS); + } + fail("Waited for " + WAIT_TRACK_EVENT_TIMEOUT_MS + " milliseconds for track ID to be " + + trackId + " but was " + selectedTrackId); + } } diff --git a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java index c76de8fb..db765109 100644 --- a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java +++ b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java @@ -16,11 +16,16 @@ package com.android.tv.recommendation; +import static android.support.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.assertEquals; + import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import com.android.tv.testing.Utils; +import org.junit.Before; +import org.junit.Test; + import java.util.Random; import java.util.concurrent.TimeUnit; @@ -28,31 +33,33 @@ import java.util.concurrent.TimeUnit; * Unit tests for {@link ChannelRecord}. */ @SmallTest -public class ChannelRecordTest extends AndroidTestCase { +public class ChannelRecordTest { private static final int CHANNEL_RECORD_MAX_HISTORY_SIZE = ChannelRecord.MAX_HISTORY_SIZE; private Random mRandom; private ChannelRecord mChannelRecord; private long mLatestWatchEndTimeMs; - @Override - public void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { mLatestWatchEndTimeMs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); mChannelRecord = new ChannelRecord(getContext(), null, false); mRandom = Utils.createTestRandom(); } + @Test public void testGetLastWatchEndTime_noHistory() { assertEquals(0, mChannelRecord.getLastWatchEndTimeMs()); } + @Test public void testGetLastWatchEndTime_oneHistory() { addWatchLog(); assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs()); } + @Test public void testGetLastWatchEndTime_maxHistories() { for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE; ++i) { addWatchLog(); @@ -61,6 +68,7 @@ public class ChannelRecordTest extends AndroidTestCase { assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs()); } + @Test public void testGetLastWatchEndTime_moreThanMaxHistories() { for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE + 1; ++i) { addWatchLog(); @@ -69,16 +77,19 @@ public class ChannelRecordTest extends AndroidTestCase { assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs()); } + @Test public void testGetTotalWatchDuration_noHistory() { assertEquals(0, mChannelRecord.getTotalWatchDurationMs()); } + @Test public void testGetTotalWatchDuration_oneHistory() { long durationMs = addWatchLog(); assertEquals(durationMs, mChannelRecord.getTotalWatchDurationMs()); } + @Test public void testGetTotalWatchDuration_maxHistories() { long totalWatchTimeMs = 0; for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE; ++i) { @@ -89,6 +100,7 @@ public class ChannelRecordTest extends AndroidTestCase { assertEquals(totalWatchTimeMs, mChannelRecord.getTotalWatchDurationMs()); } + @Test public void testGetTotalWatchDuration_moreThanMaxHistories() { long totalWatchTimeMs = 0; long firstDurationMs = 0; diff --git a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java index 0255947b..853fb245 100644 --- a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java +++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java @@ -16,30 +16,35 @@ package com.android.tv.recommendation; -import android.test.AndroidTestCase; +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.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.recommendation.Recommender.Evaluator; import com.android.tv.testing.Utils; +import org.junit.Before; + import java.util.ArrayList; import java.util.List; /** * Base test case for Recommendation Evaluator Unit tests. */ -public abstract class EvaluatorTestCase extends AndroidTestCase { +public abstract class EvaluatorTestCase { private static final long INVALID_CHANNEL_ID = -1; + private static final double SCORE_DELTA = 0.01; + private ChannelRecordSortedMapHelper mChannelRecordSortedMap; private RecommendationDataManager mDataManager; public T mEvaluator; - @Override - public void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext()); mDataManager = RecommendationUtils .createMockRecommendationDataManager(mChannelRecordSortedMap); @@ -91,8 +96,9 @@ public abstract class EvaluatorTestCase extends AndroidTest * Check whether scores of each channels are valid. */ protected void assertChannelScoresValid() { - assertEquals(Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(INVALID_CHANNEL_ID)); - assertEquals(Evaluator.NOT_RECOMMENDED, + assertEqualScores(Evaluator.NOT_RECOMMENDED, + mEvaluator.evaluateChannel(INVALID_CHANNEL_ID)); + assertEqualScores(Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(mChannelRecordSortedMap.size())); for (long channelId : mChannelRecordSortedMap.keySet()) { @@ -109,6 +115,14 @@ public abstract class EvaluatorTestCase extends AndroidTest mEvaluator.onChannelRecordListChanged(new ArrayList<>(mChannelRecordSortedMap.values())); } + void assertEqualScores(double expected, double actual) { + assertEquals(expected, actual, SCORE_DELTA); + } + + void assertEqualScores(String message, double expected, double actual) { + assertEquals(message, expected, actual, SCORE_DELTA); + } + private class FakeRecommender extends Recommender { public FakeRecommender() { super(new Recommender.Listener() { @@ -120,7 +134,7 @@ public abstract class EvaluatorTestCase extends AndroidTest public void onRecommendationChanged() { } }, true, mDataManager); - } + } @Override public ChannelRecord getChannelRecord(long channelId) { diff --git a/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java index aae3a932..ac701af9 100644 --- a/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java +++ b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java @@ -16,8 +16,12 @@ package com.android.tv.recommendation; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.SmallTest; +import org.junit.Test; + import java.util.List; import java.util.concurrent.TimeUnit; @@ -38,14 +42,16 @@ public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase channelIdList = getChannelIdListSorted(); for (long channelId : channelIdList) { - assertEquals(Recommender.Evaluator.NOT_RECOMMENDED, + assertEqualScores(Recommender.Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(channelId)); } } + @Test public void testMultiChannelsWithRandomWatchLogs() { addChannels(DEFAULT_NUMBER_OF_CHANNELS); addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS, @@ -75,6 +83,7 @@ public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase mEvaluator.evaluateChannel(channelTwo)); } + @Test public void testScoreIncreasesWithNewWatchLog() { long channelId = addChannel().getId(); addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS, diff --git a/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java index 55a4e4ac..8f092238 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java @@ -16,8 +16,12 @@ package com.android.tv.recommendation; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.SmallTest; +import org.junit.Test; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,14 +44,16 @@ public class RecentChannelEvaluatorTest extends EvaluatorTestCase channelIdList = getChannelIdListSorted(); for (long channelId : channelIdList) { - assertEquals(Recommender.Evaluator.NOT_RECOMMENDED, + assertEqualScores(Recommender.Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(channelId)); } } + @Test public void testMultiChannelsWithRandomWatchLogs() { addChannels(DEFAULT_NUMBER_OF_CHANNELS); addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS, @@ -77,6 +85,7 @@ public class RecentChannelEvaluatorTest extends EvaluatorTestCase { + private static final String AUTHORITY = "com.android.tv.search"; + private static final String KEYWORD = "keyword"; + private static final Uri BASE_SEARCH_URI = Uri.parse("content://" + AUTHORITY + "/" + + SearchManager.SUGGEST_URI_PATH_QUERY + "/" + KEYWORD); + private static final Uri WRONG_SERACH_URI = Uri.parse("content://" + AUTHORITY + "/wrong_path/" + + KEYWORD); + + private ApplicationSingletons mOldAppSingletons; + MockApplicationSingletons mMockAppSingletons; + @Mock PerformanceMonitor mMockPerformanceMointor; + @Mock SearchInterface mMockSearchInterface; + + public LocalSearchProviderTest() { + super(LocalSearchProvider.class, AUTHORITY); + } + + @Before + @Override + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + setContext(getTargetContext()); + mOldAppSingletons = TvApplication.sAppSingletons; + mMockAppSingletons = new MockApplicationSingletons(getTargetContext()); + mMockAppSingletons.setPerformanceMonitor(mMockPerformanceMointor); + TvApplication.sAppSingletons = mMockAppSingletons; + super.setUp(); + getProvider().setSearchInterface(mMockSearchInterface); + } + + @After + @Override + public void tearDown() throws Exception { + TvApplication.sAppSingletons = mOldAppSingletons; + super.tearDown(); + } + + @Test + public void testQuery_normalUri() { + verifyQueryWithArguments(null, null); + verifyQueryWithArguments(1, null); + verifyQueryWithArguments(null, 1); + verifyQueryWithArguments(1, 1); + } + + @Test + public void testQuery_invalidUri() { + try (Cursor c = getProvider().query(WRONG_SERACH_URI, null, null, null, null)) { + fail("Query with invalid URI should fail."); + } catch (IllegalArgumentException e) { + // Success. + } + } + + @Test + public void testQuery_invalidLimit() { + verifyQueryWithArguments(-1, null); + } + + @Test + public void testQuery_invalidAction() { + verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_START - 1); + verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_END + 1); + } + + private void verifyQueryWithArguments(Integer limit, Integer action) { + Uri uri = BASE_SEARCH_URI; + if (limit != null || action != null) { + Uri.Builder builder = uri.buildUpon(); + if (limit != null) { + builder.appendQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT, + limit.toString()); + } + if (action != null) { + builder.appendQueryParameter(LocalSearchProvider.SUGGEST_PARAMETER_ACTION, + action.toString()); + } + uri = builder.build(); + } + try (Cursor c = getProvider().query(uri, null, null, null, null)) { + // Do nothing. + } + int expectedLimit = limit == null || limit <= 0 ? + LocalSearchProvider.DEFAULT_SEARCH_LIMIT : limit; + int expectedAction = (action == null || action < SearchInterface.ACTION_TYPE_START + || action > SearchInterface.ACTION_TYPE_END) ? + LocalSearchProvider.DEFAULT_SEARCH_ACTION : action; + verify(mMockSearchInterface).search(KEYWORD, expectedLimit, expectedAction); + clearInvocations(mMockSearchInterface); + } +} diff --git a/tests/unit/src/com/android/tv/tests/TvActivityTest.java b/tests/unit/src/com/android/tv/tests/TvActivityTest.java index 3479785f..aa33f770 100644 --- a/tests/unit/src/com/android/tv/tests/TvActivityTest.java +++ b/tests/unit/src/com/android/tv/tests/TvActivityTest.java @@ -16,19 +16,28 @@ package com.android.tv.tests; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.MediumTest; -import android.test.ActivityInstrumentationTestCase2; +import android.support.test.rule.ActivityTestRule; import com.android.tv.TvActivity; +import com.android.tv.testing.Utils; -@MediumTest -public class TvActivityTest extends ActivityInstrumentationTestCase2 { +import org.junit.Rule; +import org.junit.Test; - public TvActivityTest() { - super(TvActivity.class); - } +@MediumTest +public class TvActivityTest { + @Rule + public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(TvActivity.class, false, false); + @Test public void testLifeCycle() { - getActivity(); + assertTrue("TvActivity should be enabled.", Utils.isTvActivityEnabled(getTargetContext())); + assertNotNull(mActivityTestRule.launchActivity(null)); } } diff --git a/tests/unit/src/com/android/tv/util/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/ImageCacheTest.java index e185d5f1..a76194b8 100644 --- a/tests/unit/src/com/android/tv/util/ImageCacheTest.java +++ b/tests/unit/src/com/android/tv/util/ImageCacheTest.java @@ -17,19 +17,21 @@ package com.android.tv.util; import static com.android.tv.util.BitmapUtils.createScaledBitmapInfo; +import static org.junit.Assert.assertSame; import android.graphics.Bitmap; import android.support.test.filters.MediumTest; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; /** * Tests for {@link ImageCache}. */ @MediumTest -public class ImageCacheTest extends TestCase { +public class ImageCacheTest { private static final Bitmap ORIG = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565); private static final String KEY = "same"; @@ -40,14 +42,14 @@ public class ImageCacheTest extends TestCase { private ImageCache mImageCache; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { mImageCache = ImageCache.newInstance(0.1f); } //TODO: Empty the cache in the setup. Try using @VisibleForTesting + @Test public void testPutIfLarger_smaller() throws Exception { mImageCache.putIfNeeded( INFO_50); @@ -57,6 +59,7 @@ public class ImageCacheTest extends TestCase { assertSame("after", INFO_50, mImageCache.get(KEY)); } + @Test public void testPutIfLarger_larger() throws Exception { mImageCache.putIfNeeded( INFO_50); assertSame("before", INFO_50, mImageCache.get(KEY)); @@ -65,6 +68,7 @@ public class ImageCacheTest extends TestCase { assertSame("after", INFO_100, mImageCache.get(KEY)); } + @Test public void testPutIfLarger_alreadyMax() throws Exception { mImageCache.putIfNeeded( INFO_100); diff --git a/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java b/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java new file mode 100644 index 00000000..4cfc7f8a --- /dev/null +++ b/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util; + +import android.content.Context; + +import com.android.tv.ApplicationSingletons; +import com.android.tv.InputSessionManager; +import com.android.tv.MainActivityWrapper; +import com.android.tv.TvApplication; +import com.android.tv.analytics.Analytics; +import com.android.tv.analytics.Tracker; +import com.android.tv.config.RemoteConfig; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.PreviewDataManager; +import com.android.tv.data.ProgramDataManager; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.DvrStorageStatusManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.perf.PerformanceMonitor; + +/** + * Mock {@link ApplicationSingletons} class. + */ +public class MockApplicationSingletons implements ApplicationSingletons { + private final TvApplication mApp; + + private PerformanceMonitor mPerformanceMonitor; + + public MockApplicationSingletons(Context context) { + mApp = (TvApplication) context.getApplicationContext(); + } + + @Override + public Analytics getAnalytics() { + return mApp.getAnalytics(); + } + + @Override + public ChannelDataManager getChannelDataManager() { + return mApp.getChannelDataManager(); + } + + @Override + public boolean isChannelDataManagerLoadFinished() { + return mApp.isChannelDataManagerLoadFinished(); + } + + @Override + public ProgramDataManager getProgramDataManager() { + return mApp.getProgramDataManager(); + } + + @Override + public boolean isProgramDataManagerCurrentProgramsLoadFinished() { + return mApp.isProgramDataManagerCurrentProgramsLoadFinished(); + } + + @Override + public PreviewDataManager getPreviewDataManager() { + return mApp.getPreviewDataManager(); + } + + @Override + public DvrDataManager getDvrDataManager() { + return mApp.getDvrDataManager(); + } + + @Override + public DvrStorageStatusManager getDvrStorageStatusManager() { + return mApp.getDvrStorageStatusManager(); + } + + @Override + public DvrScheduleManager getDvrScheduleManager() { + return mApp.getDvrScheduleManager(); + } + + @Override + public DvrManager getDvrManager() { + return mApp.getDvrManager(); + } + + @Override + public RecordingScheduler getRecordingScheduler() { + return mApp.getRecordingScheduler(); + } + + @Override + public DvrWatchedPositionManager getDvrWatchedPositionManager() { + return mApp.getDvrWatchedPositionManager(); + } + + @Override + public InputSessionManager getInputSessionManager() { + return mApp.getInputSessionManager(); + } + + @Override + public Tracker getTracker() { + return mApp.getTracker(); + } + + @Override + public TvInputManagerHelper getTvInputManagerHelper() { + return mApp.getTvInputManagerHelper(); + } + + @Override + public MainActivityWrapper getMainActivityWrapper() { + return mApp.getMainActivityWrapper(); + } + + @Override + public AccountHelper getAccountHelper() { + return mApp.getAccountHelper(); + } + + @Override + public RemoteConfig getRemoteConfig() { + return mApp.getRemoteConfig(); + } + + @Override + public boolean isRunningInMainProcess() { + return mApp.isRunningInMainProcess(); + } + + @Override + public PerformanceMonitor getPerformanceMonitor() { + return mPerformanceMonitor != null ? mPerformanceMonitor : mApp.getPerformanceMonitor(); + } + + public void setPerformanceMonitor(PerformanceMonitor performanceMonitor) { + mPerformanceMonitor = performanceMonitor; + } +} diff --git a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java index fe094fb3..7335f207 100644 --- a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java +++ b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java @@ -16,10 +16,13 @@ package com.android.tv.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + import android.support.test.filters.SmallTest; import android.test.MoreAsserts; -import junit.framework.TestCase; +import org.junit.Test; import java.util.Collections; @@ -27,19 +30,21 @@ import java.util.Collections; * Tests for {@link MultiLongSparseArray}. */ @SmallTest -public class MultiLongSparseArrayTest extends TestCase { - +public class MultiLongSparseArrayTest { + @Test public void testEmpty() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); assertSame(Collections.EMPTY_SET, sparseArray.get(0)); } + @Test public void testOneElement() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); sparseArray.put(0, "foo"); MoreAsserts.assertContentsInAnyOrder(sparseArray.get(0), "foo"); } + @Test public void testTwoElements() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); sparseArray.put(0, "foo"); @@ -48,6 +53,7 @@ public class MultiLongSparseArrayTest extends TestCase { } + @Test public void testClearEmptyCache() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); sparseArray.clearEmptyCache(); @@ -59,6 +65,7 @@ public class MultiLongSparseArrayTest extends TestCase { assertEquals(0, sparseArray.getEmptyCacheSize()); } + @Test public void testMaxEmptyCacheSize() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); sparseArray.clearEmptyCache(); @@ -75,6 +82,7 @@ public class MultiLongSparseArrayTest extends TestCase { assertEquals(0, sparseArray.getEmptyCacheSize()); } + @Test public void testReuseEmptySets() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); sparseArray.clearEmptyCache(); diff --git a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java index 36d25a10..2714e2e9 100644 --- a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java +++ b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java @@ -1,24 +1,29 @@ package com.android.tv.util; +import static org.junit.Assert.assertEquals; + import android.graphics.Bitmap; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import org.junit.Test; + /** * Tests for {@link ScaledBitmapInfo}. */ @SmallTest -public class ScaledBitmapInfoTest extends AndroidTestCase { +public class ScaledBitmapInfoTest { private static final Bitmap B80x100 = Bitmap.createBitmap(80, 100, Bitmap.Config.RGB_565); private static final Bitmap B960x1440 = Bitmap.createBitmap(960, 1440, Bitmap.Config.RGB_565); + @Test public void testSize_B100x100to50x50() { ScaledBitmapInfo actual = BitmapUtils.createScaledBitmapInfo("B80x100", B80x100, 50, 50); assertScaledBitmapSize(2, 40, 50, actual); } + @Test public void testNeedsToReload_B100x100to50x50() { ScaledBitmapInfo actual = BitmapUtils.createScaledBitmapInfo("B80x100", B80x100, 50, 50); assertNeedsToReload(false, actual, 25, 25); @@ -31,6 +36,7 @@ public class ScaledBitmapInfoTest extends AndroidTestCase { /** * Reproduces b/20488453. */ + @Test public void testBug20488453() { ScaledBitmapInfo actual = BitmapUtils .createScaledBitmapInfo("B960x1440", B960x1440, 284, 160); diff --git a/tests/unit/src/com/android/tv/util/TestUtils.java b/tests/unit/src/com/android/tv/util/TestUtils.java index e3bda138..d200733d 100644 --- a/tests/unit/src/com/android/tv/util/TestUtils.java +++ b/tests/unit/src/com/android/tv/util/TestUtils.java @@ -21,8 +21,8 @@ import android.content.pm.ServiceInfo; import android.graphics.drawable.Icon; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvInputInfo; +import android.os.Build; import android.os.Bundle; -import android.support.v4.os.BuildCompat; import java.lang.reflect.Constructor; @@ -47,13 +47,35 @@ public class TestUtils { int type, boolean isHardwareInput, boolean canRecord, int tunerCount) throws Exception { // Create a mock TvInputInfo by using private constructor // Note that mockito doesn't support mock/spy on final object. - if (BuildCompat.isAtLeastN()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return createTvInputInfoForO(service, id, parentId, type, isHardwareInput, canRecord, + tunerCount); + + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return createTvInputInfoForNyc(service, id, parentId, type, isHardwareInput, canRecord, tunerCount); } return createTvInputInfoForMnc(service, id, parentId, type, isHardwareInput); } + /** + * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, + * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, + * String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, + * boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { + */ + private static TvInputInfo createTvInputInfoForO(ResolveInfo service, String id, + String parentId, int type, boolean isHardwareInput, boolean canRecord, int tunerCount) + throws Exception { + Constructor constructor = TvInputInfo.class.getDeclaredConstructor( + ResolveInfo.class, String.class, int.class, boolean.class, CharSequence.class, + int.class, Icon.class, Icon.class, Icon.class, String.class, boolean.class, + int.class, HdmiDeviceInfo.class, boolean.class, String.class, Bundle.class); + constructor.setAccessible(true); + return constructor.newInstance(service, id, type, isHardwareInput, null, 0, null, null, + null, null, canRecord, tunerCount, null, false, parentId, null); + } + /** * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, @@ -87,6 +109,7 @@ public class TestUtils { resolveInfo.serviceInfo = new ServiceInfo(); resolveInfo.serviceInfo.packageName = packageName; resolveInfo.serviceInfo.name = name; + resolveInfo.serviceInfo.metaData = new Bundle(); return resolveInfo; } } diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java index ba1e0b0b..404ee5d3 100644 --- a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java +++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java @@ -16,58 +16,155 @@ package com.android.tv.util; +import static android.support.test.InstrumentationRegistry.getContext; + import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; -import android.support.test.filters.Suppress; -import android.test.AndroidTestCase; import com.android.tv.testing.ComparatorTester; +import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.util.LinkedHashMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; /** * Test for {@link TvInputManagerHelper} */ @SmallTest -public class TvInputManagerHelperTest extends AndroidTestCase { - public void testComparator() throws Exception { - final LinkedHashMap INPUT_ID_TO_PARTNER_INPUT = new LinkedHashMap<>(); - INPUT_ID_TO_PARTNER_INPUT.put("2_partner_input", true); - INPUT_ID_TO_PARTNER_INPUT.put("3_partner_input", true); - INPUT_ID_TO_PARTNER_INPUT.put("1_3rd_party_input", false); - INPUT_ID_TO_PARTNER_INPUT.put("4_3rd_party_input", false); +public class TvInputManagerHelperTest { + final HashMap TEST_INPUT_MAP = new HashMap<>(); + + @Test + public void testComparatorInternal() { + ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); + + List inputs = new ArrayList<>(); + inputs.add(createTvInputInfo(resolveInfo, "2_partner_input", null, 0, false, + "2_partner_input", null, true)); + inputs.add(createTvInputInfo(resolveInfo, "3_partner_input", null, 0, false, + "3_partner_input", null, true)); + inputs.add(createTvInputInfo(resolveInfo, "1_3rd_party_input", null, 0, false, + "1_3rd_party_input", null, false)); + inputs.add(createTvInputInfo(resolveInfo, "4_3rd_party_input", null, 0, false, + "4_3rd_party_input", null, false)); + + TvInputManagerHelper manager = createMockTvInputManager(); + + ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + new TvInputManagerHelper.InputComparatorInternal(manager)); + for (TvInputInfo input : inputs) { + comparatorTester.addComparableGroup(input); + } + comparatorTester.test(); + } + + @Test + public void testHardwareInputComparatorHdmi() { + ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); + + TvInputInfo hdmi1 = createTvInputInfo(resolveInfo, "HDMI1", null, TvInputInfo.TYPE_HDMI, + true, "HDMI1", null, false); + TvInputInfo hdmi2 = createTvInputInfo(resolveInfo, "HDMI2", null, TvInputInfo.TYPE_HDMI, + true, "HDMI2", "DVD", false); + TvInputInfo hdmi3 = createTvInputInfo(resolveInfo, "HDMI3", null, TvInputInfo.TYPE_HDMI, + true, "HDMI3", "Cable", false); + TvInputInfo hdmi4 = createTvInputInfo(resolveInfo, "HDMI4", null, TvInputInfo.TYPE_HDMI, + true, "HDMI4", null, false); + + TvInputManagerHelper manager = createMockTvInputManager(); + + ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + new TvInputManagerHelper.HardwareInputComparator(getContext(), manager)); + comparatorTester.addComparableGroup(hdmi3) + .addComparableGroup(hdmi2) + .addComparableGroup(hdmi1) + .addComparableGroup(hdmi4) + .test(); + } + + @Test + public void testHardwareInputComparatorCec() { + ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); + + TvInputInfo hdmi1 = createTvInputInfo(resolveInfo, "HDMI1", null, TvInputInfo.TYPE_HDMI, + true, "HDMI1", null, false); + TvInputInfo hdmi2 = createTvInputInfo(resolveInfo, "HDMI2", null, TvInputInfo.TYPE_HDMI, + true, "HDMI2", null, false); + TvInputInfo cec1 = createTvInputInfo(resolveInfo, "2_cec", "HDMI1", TvInputInfo.TYPE_HDMI, + true, "2_cec", null, false); + TvInputInfo cec2 = createTvInputInfo(resolveInfo, "1_cec", "HDMI2", TvInputInfo.TYPE_HDMI, + true, "1_cec", null, false); + + TvInputManagerHelper manager = createMockTvInputManager(); + + ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + new TvInputManagerHelper.HardwareInputComparator(getContext(), manager)); + comparatorTester.addComparableGroup(cec1) + .addComparableGroup(cec2) + .test(); + } + + private TvInputManagerHelper createMockTvInputManager() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); Mockito.doAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; - return INPUT_ID_TO_PARTNER_INPUT.get(info.getId()); + return TEST_INPUT_MAP.get(info.getId()).mIsPartnerInput; } }).when(manager).isPartnerInput(Mockito.any()); Mockito.doAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; - return info.getId(); + return TEST_INPUT_MAP.get(info.getId()).mLabel; } }).when(manager).loadLabel(Mockito.any()); + Mockito.doAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; + return TEST_INPUT_MAP.get(info.getId()).mCustomLabel; + } + }).when(manager).loadCustomLabel(Mockito.any()); + Mockito.doAnswer(new Answer() { + @Override + public TvInputInfo answer(InvocationOnMock invocation) throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + TvInputInfoWrapper inputWrapper = TEST_INPUT_MAP.get(inputId); + return inputWrapper == null ? null : inputWrapper.mInput; + } + }).when(manager).getTvInputInfo(Mockito.any()); + return manager; + } - ComparatorTester comparatorTester = - ComparatorTester.withoutEqualsTest( - new TvInputManagerHelper.TvInputInfoComparator(manager)); - ResolveInfo resolveInfo1 = TestUtils.createResolveInfo("1_test", "1_test"); - ResolveInfo resolveInfo2 = TestUtils.createResolveInfo("2_test", "2_test"); - for (String inputId : INPUT_ID_TO_PARTNER_INPUT.keySet()) { - TvInputInfo info1 = TestUtils.createTvInputInfo(resolveInfo1, inputId, null, 0, false); - TvInputInfo info2 = TestUtils.createTvInputInfo(resolveInfo2, inputId, null, 0, false); - comparatorTester.addComparableGroup(info1, info2); + private TvInputInfo createTvInputInfo(ResolveInfo service, String id, + String parentId, int type, boolean isHardwareInput, String label, String customLabel, + boolean isPartnerInput) { + TvInputInfoWrapper inputWrapper = new TvInputInfoWrapper(); + try { + inputWrapper.mInput = + TestUtils.createTvInputInfo(service, id, parentId, type, isHardwareInput); + } catch (Exception e) { } - comparatorTester.test(); + inputWrapper.mLabel = label; + inputWrapper.mIsPartnerInput = isPartnerInput; + inputWrapper.mCustomLabel = customLabel; + TEST_INPUT_MAP.put(id, inputWrapper); + return inputWrapper.mInput; + } + + private static class TvInputInfoWrapper { + TvInputInfo mInput; + String mLabel; + String mCustomLabel; + boolean mIsPartnerInput; } } diff --git a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java index 9600fc0b..4512bb7d 100644 --- a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java +++ b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java @@ -16,13 +16,14 @@ package com.android.tv.util; import static com.android.tv.util.TvTrackInfoUtils.getBestTrackInfo; +import static org.junit.Assert.assertEquals; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; import com.android.tv.testing.ComparatorTester; -import junit.framework.TestCase; +import org.junit.Test; import java.util.Arrays; import java.util.Collections; @@ -33,8 +34,7 @@ import java.util.List; * Tests for {@link com.android.tv.util.TvTrackInfoUtils}. */ @SmallTest -public class TvTrackInfoUtilsTest extends TestCase { - +public class TvTrackInfoUtilsTest { private static final String UN_MATCHED_ID = "no matching ID"; private static final TvTrackInfo INFO_1_EN_1 = create("1", "en", 1); @@ -59,46 +59,55 @@ public class TvTrackInfoUtilsTest extends TestCase { private static final List NULL_LANGUAGE_TRACKS = Arrays.asList(INFO_4_NULL_2, INFO_5_NULL_6); + @Test public void testGetBestTrackInfo_empty() { TvTrackInfo result = getBestTrackInfo(Collections.emptyList(), UN_MATCHED_ID, "en", 1); assertEquals("best track ", null, result); } + @Test public void testGetBestTrackInfo_exactMatch() { TvTrackInfo result = getBestTrackInfo(ALL, "1", "en", 1); assertEquals("best track ", INFO_1_EN_1, result); } + @Test public void testGetBestTrackInfo_langAndChannelCountMatch() { TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "en", 5); assertEquals("best track ", INFO_2_EN_5, result); } + @Test public void testGetBestTrackInfo_languageOnlyMatch() { TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "fr", 1); assertEquals("best track ", INFO_3_FR_8, result); } + @Test public void testGetBestTrackInfo_channelCountOnlyMatchWithNullLanguage() { TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, null, 8); assertEquals("best track ", INFO_3_FR_8, result); } + @Test public void testGetBestTrackInfo_noMatches() { TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "kr", 1); assertEquals("best track ", INFO_1_EN_1, result); } + @Test public void testGetBestTrackInfo_noMatchesWithNullLanguage() { TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, null, 0); assertEquals("best track ", INFO_1_EN_1, result); } + @Test public void testGetBestTrackInfo_channelCountAndIdMatch() { TvTrackInfo result = getBestTrackInfo(NULL_LANGUAGE_TRACKS, "5", null, 6); assertEquals("best track ", INFO_5_NULL_6, result); } + @Test public void testComparator() { Comparator comparator = TvTrackInfoUtils.createComparator("1", "en", 1); ComparatorTester.withoutEqualsTest(comparator) diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java index 9dfb992e..e61802f5 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java @@ -15,10 +15,16 @@ */ package com.android.tv.util; +import static android.support.test.InstrumentationRegistry.getContext; +import static org.junit.Assert.assertEquals; + import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; import android.text.format.DateUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; @@ -32,7 +38,7 @@ import java.util.Locale; * and it should be defined in TV app, not this test. */ @SmallTest -public class UtilsTest_GetDurationString extends AndroidTestCase { +public class UtilsTest_GetDurationString { // TODO: Mock Context so we can specify current time and locale for test. private Locale mLocale; private static final long DATE_THIS_YEAR_2_1_MS = getFebOfThisYearInMillis(1, 0, 0); @@ -40,14 +46,19 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { // All possible list for a parameter to test parameter independent result. private static final boolean[] PARAM_USE_SHORT_FORMAT = {false, true}; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { // Set locale to US mLocale = Locale.getDefault(); Locale.setDefault(Locale.US); } + @After + public void tearDown() { + // Revive system locale. + Locale.setDefault(mLocale); + } + /** * Return time in millis assuming that whose year is this year and month is Jan. */ @@ -76,6 +87,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { return new GregorianCalendar().get(GregorianCalendar.YEAR); } + @Test public void testSameDateAndTime() { assertEquals("3:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(1, 3), false, @@ -85,6 +97,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testDurationWithinToday() { assertEquals("12:00 – 3:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, @@ -96,6 +109,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testDurationFromYesterdayToToday() { assertEquals("Jan 31, 3:00 AM – Feb 1, 4:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, @@ -115,6 +129,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { true, DateUtils.FORMAT_24HOUR)); } + @Test public void testDurationFromTodayToTomorrow() { assertEquals("Feb 1, 3:00 AM – Feb 2, 4:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, @@ -154,6 +169,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testDurationWithinTomorrow() { assertEquals("Feb 2, 2:00 – 4:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, @@ -173,6 +189,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testStartOfDay() { assertEquals("12:00 – 1:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, @@ -201,6 +218,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testEndOfDay() { for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) { assertEquals("11:00 PM – 12:00 AM", @@ -241,6 +259,7 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } + @Test public void testMidnight() { for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) { assertEquals("12:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, @@ -251,11 +270,4 @@ public class UtilsTest_GetDurationString extends AndroidTestCase { DateUtils.FORMAT_24HOUR)); } } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - // Revive system locale. - Locale.setDefault(mLocale); - } } diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java index 8a6f3e3b..1e75342b 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java @@ -16,21 +16,26 @@ package com.android.tv.util; +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static org.junit.Assert.assertEquals; + import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; + +import org.junit.Test; /** * Tests for {@link com.android.tv.util.Utils#getMultiAudioString}. */ @SmallTest -public class UtilsTest_GetMultiAudioString extends AndroidTestCase { +public class UtilsTest_GetMultiAudioString { private static final String TRACK_ID = "test_track_id"; private static final int AUDIO_SAMPLE_RATE = 48000; + @Test public void testAudioTrackLanguage() { - Context context = getContext(); + Context context = getTargetContext(); assertEquals("Korean", Utils.getMultiAudioString(context, createAudioTrackInfo("kor"), false)); assertEquals("English", @@ -42,8 +47,9 @@ public class UtilsTest_GetMultiAudioString extends AndroidTestCase { assertEquals("abc", Utils.getMultiAudioString(context, createAudioTrackInfo("abc"), false)); } + @Test public void testAudioTrackCount() { - Context context = getContext(); + Context context = getTargetContext(); assertEquals("English", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", -1), false)); assertEquals("English", @@ -66,11 +72,14 @@ public class UtilsTest_GetMultiAudioString extends AndroidTestCase { Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 8), false)); } + @Test public void testShowSampleRate() { assertEquals("Korean (48kHz)", - Utils.getMultiAudioString(getContext(), createAudioTrackInfo("kor", 0), true)); + Utils.getMultiAudioString(getTargetContext(), + createAudioTrackInfo("kor", 0), true)); assertEquals("Korean (7.1 surround, 48kHz)", - Utils.getMultiAudioString(getContext(), createAudioTrackInfo("kor", 8), true)); + Utils.getMultiAudioString(getTargetContext(), + createAudioTrackInfo("kor", 8), true)); } private static TvTrackInfo createAudioTrackInfo(String language) { diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java index 926deb07..2b43abc1 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java @@ -16,8 +16,12 @@ package com.android.tv.util; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import android.support.test.filters.SmallTest; -import android.test.AndroidTestCase; + +import org.junit.Test; import java.util.Calendar; import java.util.GregorianCalendar; @@ -27,19 +31,22 @@ import java.util.TimeZone; * Tests for {@link com.android.tv.util.Utils#isInGivenDay}. */ @SmallTest -public class UtilsTest_IsInGivenDay extends AndroidTestCase { +public class UtilsTest_IsInGivenDay { + @Test public void testIsInGivenDay() { assertTrue(Utils.isInGivenDay( new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), new GregorianCalendar(2015, Calendar.JANUARY, 1, 0, 30).getTimeInMillis())); } + @Test public void testIsNotInGivenDay() { assertFalse(Utils.isInGivenDay( new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), new GregorianCalendar(2015, Calendar.JANUARY, 2).getTimeInMillis())); } + @Test public void testIfTimeZoneApplied() { TimeZone timeZone = TimeZone.getDefault(); diff --git a/usbtuner-res/animator/setup_before_entry.xml b/usbtuner-res/animator/setup_before_entry.xml deleted file mode 100644 index 82ed7992..00000000 --- a/usbtuner-res/animator/setup_before_entry.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/usbtuner-res/animator/setup_before_exit.xml b/usbtuner-res/animator/setup_before_exit.xml deleted file mode 100644 index 5c00064c..00000000 --- a/usbtuner-res/animator/setup_before_exit.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - diff --git a/usbtuner-res/animator/setup_entry.xml b/usbtuner-res/animator/setup_entry.xml deleted file mode 100644 index 35fcd4a3..00000000 --- a/usbtuner-res/animator/setup_entry.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - diff --git a/usbtuner-res/animator/setup_exit.xml b/usbtuner-res/animator/setup_exit.xml deleted file mode 100644 index 4ce89cd6..00000000 --- a/usbtuner-res/animator/setup_exit.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - diff --git a/usbtuner-res/drawable-xhdpi/ic_setup_antenna.png b/usbtuner-res/drawable-xhdpi/ic_setup_antenna.png deleted file mode 100644 index bb6d416e..00000000 Binary files a/usbtuner-res/drawable-xhdpi/ic_setup_antenna.png and /dev/null differ diff --git a/usbtuner-res/drawable/ut_selector_background.xml b/usbtuner-res/drawable/ut_selector_background.xml deleted file mode 100644 index fb3899aa..00000000 --- a/usbtuner-res/drawable/ut_selector_background.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - diff --git a/usbtuner-res/layout/ut_activity_playback.xml b/usbtuner-res/layout/ut_activity_playback.xml deleted file mode 100644 index b640e6d5..00000000 --- a/usbtuner-res/layout/ut_activity_playback.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/usbtuner-res/layout/ut_guidance.xml b/usbtuner-res/layout/ut_guidance.xml deleted file mode 100644 index 4f7d3f7a..00000000 --- a/usbtuner-res/layout/ut_guidance.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - diff --git a/usbtuner-res/layout/ut_guidedactions.xml b/usbtuner-res/layout/ut_guidedactions.xml deleted file mode 100644 index ae7efc0d..00000000 --- a/usbtuner-res/layout/ut_guidedactions.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/usbtuner-res/raw/ut_euro_dvbt_all b/usbtuner-res/raw/ut_euro_dvbt_all new file mode 100644 index 00000000..101ee3eb --- /dev/null +++ b/usbtuner-res/raw/ut_euro_dvbt_all @@ -0,0 +1,287 @@ +# Euro DVB-T frequencies +# Only Germany and England frequencies are added now. + +# Frequencies from Germany +T 474000000 QAM16 +T 474000000 QAM64 +T 482000000 QAM16 +T 490000000 QAM16 +T 498000000 QAM16 +T 498000000 QAM64 +T 506000000 QAM16 +T 506000000 QAM64 +T 514000000 QAM16 +T 522000000 QAM16 +T 522000000 QAM64 +T 530000000 QAM16 +T 538000000 QAM16 +T 538000000 QAM64 +T 546000000 QAM16 +T 554000000 QAM16 +T 554000000 QAM64 +T 562000000 QAM16 +T 562000000 QAM64 +T 570000000 QAM16 +T 578000000 QAM16 +T 578000000 QAM64 +T 586000000 QAM16 +T 594000000 QAM16 +T 602000000 QAM16 +T 602000000 QAM64 +T 610000000 QAM16 +T 610000000 QAM64 +T 618000000 QAM16 +T 618000000 QAM64 +T 626000000 QAM16 +T 634000000 QAM16 +T 634000000 QAM64 +T 642000000 QAM16 +T 650000000 QAM16 +T 658000000 QAM16 +T 666000000 QAM16 +T 674000000 QAM16 +T 674000000 QAM64 +T 682000000 QAM16 +T 690000000 QAM16 +T 690000000 QAM64 +T 698000000 QAM16 +T 698000000 QAM64 +T 706000000 QAM16 +T 722000000 QAM16 +T 730000000 QAM16 +T 730000000 QAM64 +T 738000000 QAM16 +T 738000000 QAM64 +T 746000000 QAM16 +T 746000000 QPSK +T 754000000 QAM16 +T 762000000 QAM16 +T 770000000 QAM16 +T 778000000 QAM16 +T 786000000 QAM16 + +# Frequencies from England +T 474000000 QAM16 +T 474000000 QAM64 +T 474167000 QAM16 +T 474167000 QAM64 +T 481833000 QAM16 +T 481833000 QAM64 +T 481833330 QAM64 +T 482000000 QAM16 +T 482167000 QAM16 +T 482167000 QAM64 +T 489833000 QAM16 +T 489833000 QAM64 +T 490000000 QAM16 +T 490000000 QAM64 +T 490167000 QAM16 +T 490167000 QAM64 +T 497833000 QAM16 +T 497833000 QAM64 +T 498000000 QAM16 +T 498000000 QAM64 +T 498167000 QAM16 +T 498167000 QAM64 +T 505833000 QAM16 +T 505833000 QAM64 +T 505833330 QAM16 +T 506000000 QAM16 +T 506000000 QAM64 +T 506167000 QAM16 +T 506167000 QAM64 +T 513833000 QAM16 +T 514000000 QAM16 +T 514000000 QAM64 +T 514167000 QAM16 +T 514167000 QAM64 +T 521833000 QAM64 +T 522000000 QAM16 +T 522000000 QAM64 +T 522167000 QAM16 +T 529833000 QAM16 +T 529833000 QAM64 +T 529833330 QAM16 +T 530000000 QAM16 +T 530000000 QAM64 +T 530167000 QAM16 +T 530167000 QAM64 +T 537833000 QAM64 +T 537833330 QAM16 +T 538000000 QAM16 +T 538000000 QAM64 +T 538167000 QAM16 +T 538167000 QAM64 +T 545833000 QAM16 +T 545833000 QAM64 +T 546000000 QAM16 +T 546000000 QAM64 +T 546167000 QAM16 +T 546167000 QAM64 +T 553833000 QAM16 +T 553833000 QAM64 +T 554000000 QAM16 +T 554000000 QAM64 +T 561833000 QAM16 +T 561833000 QAM64 +T 561833330 QAM64 +T 562000000 QAM16 +T 562000000 QAM64 +T 562167000 QAM16 +T 569833000 QAM64 +T 570000000 QAM16 +T 570167000 QAM16 +T 577833000 QAM16 +T 578000000 QAM16 +T 578000000 QAM64 +T 578166670 QAM16 +T 578167000 QAM16 +T 578167000 QAM64 +T 586000000 QAM16 +T 594000000 QAM16 +T 602000000 QAM16 +T 602000000 QAM64 +T 610000000 QAM16 +T 610000000 QAM64 +T 618000000 QAM16 +T 618000000 QAM64 +T 618167000 QAM16 +T 625833000 QAM64 +T 626000000 QAM16 +T 626000000 QAM64 +T 626167000 QAM16 +T 626167000 QAM64 +T 633833000 QAM64 +T 634000000 QAM16 +T 634000000 QAM64 +T 634167000 QAM16 +T 634167000 QAM64 +T 641833000 QAM16 +T 642000000 QAM16 +T 642000000 QAM64 +T 642167000 QAM64 +T 649833000 QAM16 +T 650000000 QAM16 +T 650000000 QAM64 +T 650167000 QAM16 +T 650167000 QAM64 +T 657833000 QAM16 +T 657833000 QAM64 +T 658000000 QAM64 +T 658000000 QAM16 +T 658167000 QAM16 +T 658167000 QAM64 +T 665833000 QAM16 +T 665833000 QAM64 +T 666000000 QAM16 +T 666000000 QAM64 +T 666167000 QAM16 +T 673833000 QAM16 +T 674000000 QAM16 +T 674000000 QAM64 +T 674167000 QAM16 +T 681833000 QAM16 +T 682000000 QAM16 +T 682000000 QAM64 +T 682167000 QAM16 +T 682167000 QAM64 +T 689833000 QAM64 +T 690000000 QAM16 +T 690000000 QAM64 +T 690167000 QAM16 +T 690167000 QAM64 +T 697833000 QAM16 +T 698000000 QAM16 +T 698000000 QAM64 +T 698167000 QAM16 +T 705833000 QAM16 +T 706000000 QAM16 +T 706000000 QAM64 +T 706167000 QAM16 +T 706167000 QAM64 +T 713833000 QAM16 +T 713833000 QAM64 +T 714000000 QAM16 +T 714000000 QAM64 +T 714167000 QAM16 +T 714167000 QAM64 +T 721833000 QAM16 +T 722000000 QAM16 +T 722167000 QAM16 +T 722167000 QAM64 +T 729833000 QAM16 +T 729833000 QAM64 +T 730000000 QAM16 +T 730000000 QAM64 +T 730167000 QAM16 +T 737833000 QAM16 +T 738000000 QAM16 +T 738000000 QAM64 +T 738167000 QAM16 +T 738167000 QAM64 +T 745833000 QAM16 +T 745833000 QAM64 +T 746000000 QAM16 +T 746000000 QAM64 +T 746000000 QPSK +T 746167000 QAM64 +T 753833000 QAM16 +T 753833000 QAM64 +T 754000000 QAM16 +T 754000000 QAM64 +T 754167000 QAM16 +T 761833000 QAM16 +T 761833000 QAM64 +T 762000000 QAM16 +T 762000000 QAM256 +T 762000000 QAM64 +T 762167000 QAM16 +T 762167000 QAM64 +T 769833000 QAM64 +T 770000000 QAM16 +T 770000000 QAM64 +T 770167000 QAM16 +T 770167000 QAM64 +T 777833000 QAM16 +T 777833000 QAM64 +T 778000000 QAM16 +T 778000000 QAM64 +T 778167000 QAM16 +T 778167000 QAM64 +T 785833000 QAM16 +T 785833000 QAM64 +T 786000000 QAM16 +T 786000000 QAM64 +T 786167000 QAM16 +T 786167000 QAM64 +T 793833000 QAM64 +T 794000000 QAM64 +T 794167000 QAM16 +T 794167000 QAM64 +T 801833000 QAM16 +T 801833000 QAM64 +T 802000000 QAM16 +T 802000000 QAM64 +T 802167000 QAM16 +T 802167000 QAM64 +T 809833000 QAM16 +T 810000000 QAM16 +T 810167000 QAM16 +T 817833000 QAM16 +T 818000000 QAM16 +T 818167000 QAM16 +T 825833000 QAM16 +T 825833000 QAM64 +T 826000000 QAM16 +T 826167000 QAM16 +T 826167000 QAM64 +T 833833000 QAM64 +T 834000000 QAM16 +T 834000000 QAM64 +T 834167000 QAM16 +T 834167000 QAM64 +T 842000000 QAM16 +T 842000000 QAM64 +T 842167000 QAM64 +T 850000000 QAM16 +T 850000000 QAM64 \ No newline at end of file diff --git a/usbtuner-res/raw/ut_kr_all b/usbtuner-res/raw/ut_kr_all index 85320125..9c4d7e64 100644 --- a/usbtuner-res/raw/ut_kr_all +++ b/usbtuner-res/raw/ut_kr_all @@ -1,5 +1,4 @@ # KR ATSC center frequencies -# TODO: Verify this. A 473028615 8VSB A 479028615 8VSB diff --git a/usbtuner-res/raw/ut_kr_atsc_center_frequencies_8vsb b/usbtuner-res/raw/ut_kr_atsc_center_frequencies_8vsb index 4ddbf0b6..14678d3a 100644 --- a/usbtuner-res/raw/ut_kr_atsc_center_frequencies_8vsb +++ b/usbtuner-res/raw/ut_kr_atsc_center_frequencies_8vsb @@ -1,5 +1,4 @@ # KR ATSC center frequencies -# TODO: Verify this. A 473028615 8VSB A 479028615 8VSB diff --git a/usbtuner-res/raw/ut_us_all b/usbtuner-res/raw/ut_us_all index f3d2d8fa..a5bd846a 100644 --- a/usbtuner-res/raw/ut_us_all +++ b/usbtuner-res/raw/ut_us_all @@ -1,5 +1,4 @@ # US ATSC center frequencies -# TODO: Verify this. A 57028615 8VSB 2 A 63028615 8VSB 3 @@ -71,7 +70,6 @@ A 797028615 8VSB 68 A 803028615 8VSB 69 # US EIA/NCTA Standard Cable center frequencies # Channels are in ascending EIA/NCTA channel designation order -# TODO: Verify this. #2 A 57000000 QAM256 diff --git a/usbtuner-res/raw/ut_us_atsc_center_frequencies_8vsb b/usbtuner-res/raw/ut_us_atsc_center_frequencies_8vsb index bb59e07e..245a68a8 100644 --- a/usbtuner-res/raw/ut_us_atsc_center_frequencies_8vsb +++ b/usbtuner-res/raw/ut_us_atsc_center_frequencies_8vsb @@ -1,5 +1,4 @@ # US ATSC center frequencies -# TODO: Verify this. A 57028615 8VSB 2 A 63028615 8VSB 3 diff --git a/usbtuner-res/raw/ut_us_cable_standard_center_frequencies_qam256 b/usbtuner-res/raw/ut_us_cable_standard_center_frequencies_qam256 index d2d5e302..f42a5141 100644 --- a/usbtuner-res/raw/ut_us_cable_standard_center_frequencies_qam256 +++ b/usbtuner-res/raw/ut_us_cable_standard_center_frequencies_qam256 @@ -1,6 +1,5 @@ # US EIA/NCTA Standard Cable center frequencies # Channels are in ascending EIA/NCTA channel designation order -# TODO: Verify this. #2 A 57000000 QAM256 diff --git a/usbtuner-res/values-af/strings.xml b/usbtuner-res/values-af/strings.xml index 4337a696..8fd50706 100644 --- a/usbtuner-res/values-af/strings.xml +++ b/usbtuner-res/values-af/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV-ontvanger" "USB-TV-ontvanger" - "Aan" - "Af" + "Netwerk-TV-ontvanger (BETA)" "Wag asseblief dat verwerking voltooi word" - "Kies jou kanaalbron" - "Geen sein nie" - "Kon nie op %s inskakel nie" - "Kon nie inskakel nie" "Ontvangersagteware is onlangs opgedateer. Herskandeer die kanale asseblief." "Aktiveer omringklank in stelselklankinstellings om oudio te aktiveer" + "Kan nie oudio speel nie. Probeer asseblief \'n ander TV" "Opstelling van kanaalontvanger" "Opstelling van TV-ontvanger" "Opstelling van USB-kanaalontvanger" + "Opstelling van netwerkontvanger" "Maak seker dat jou TV aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy plasing of rigting moet verander om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." "Maak seker dat die USB-ontvanger ingeprop en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy posisie of rigting moet verander om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." + "Maak seker dat die netwerkontvanger aangeskakel is en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy plasing of rigting waarin hy wys, moet verstel om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." "Gaan voort" "Nie nou nie" @@ -40,6 +38,7 @@ "Doen kanaalopstelling weer?" "Dit sal die kanale wat ontvang is, uit die TV-ontvanger verwyder en weer nuwe kanale soek.\n\nMaak seker dat jou TV aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy plasing of rigting moet verander om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." "Dit sal die kanale wat gevind is, uit die USB-ontvanger verwyder en weer nuwe kanale soek.\n\nMaak seker dat die USB-ontvanger ingeprop en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy posisie of rigting moet verander om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." + "Dit sal die kanale wat gevind is van die netwerkontvanger af verwyder en weer na nuwe kanale soek.\n\nMaak seker dat die netwerkontvanger aangeskakel is en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, sal jy dalk sy plasing of rigting waarin hy wys, moet verstel om die meeste kanale te ontvang. Plaas dit vir die beste resultate hoog en naby \'n venster." "Gaan voort" "Kanselleer" @@ -54,6 +53,7 @@ "Opstelling van TV-ontvanger" "Opstelling van USB-kanaalontvanger" + "Opstelling van netwerkkanaalontvanger" "Dit kan \'n paar minute neem" "Seinontvanger is tydelik nie beskikbaar nie of word reeds deur opname gebruik." @@ -76,12 +76,15 @@ "Geen kanale gevind nie" "Geen kanale is in die soektog gevind nie. Maak seker dat jou TV aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, verander sy plasing of rigting. Plaas dit vir die beste resultate hoog en naby \'n venster en soek weer." "Geen kanale is in die soektog gevind nie. Maak seker dat die USB-ontvanger ingeprop en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, verander sy posisie of rigting. Plaas dit vir die beste resultate hoog en naby \'n venster en soek weer." + "Geen kanale is in die soektog gevind nie. Maak seker dat die netwerkontvanger aangeskakel is en aan \'n TV-seinbron gekoppel is.\n\nAs jy \'n oor-die-lug-antenna gebruik, verstel sy plasing of rigting waarin hy wys. Plaas dit vir die beste resultate hoog en naby \'n venster en soek weer." "Soek weer" "Klaar" - "Soek TV-kanale" - "Opstelling van TV-ontvanger" - "Opstelling van USB-TV-ontvanger" - "USB-TV-ontvanger is ontkoppel." + "Soek TV-kanale" + "Opstelling van TV-ontvanger" + "Opstelling van USB-TV-ontvanger" + "Opstelling van netwerk-TV-ontvanger" + "USB-TV-ontvanger is ontkoppel." + "Netwerkontvanger is ontkoppel." diff --git a/usbtuner-res/values-am/strings.xml b/usbtuner-res/values-am/strings.xml index a903ee76..cbf1e9fe 100644 --- a/usbtuner-res/values-am/strings.xml +++ b/usbtuner-res/values-am/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "የቴሌቪዥን መቃኛ" "የዩኤስቢ ቴሌቪዥን መቃኛ" - "በርቷል" - "ጠፍቷል" + "የአውታረ መረብ ቴሌቪዥን መቃኛ (ቅድመ-ይሁንታ)" "ማስኬድን ለማጠናቀቅ እባክዎ ይጠብቁ" - "የጣቢያ ምንጭዎን ይምረጡ" - "ምንም ሲግናል የለም" - "ወደ %s መቃኘት አልተሳካም" - "መቃኘት አልተሳካም" "የቴሌቪዥን መቃኛ ሶፍትዌር በቅርብ ጊዜ ተዘምኗል። እባክዎ ሰርጦቹን እንደገና ይቃኟቸው።" "ኦዲዮን ለማንቃት በስርዓት ድምጽ ቅንብሮች ውስጥ የዙሪያ ድምጽን ያንቁ" + "ኦዲዮ ማጫወት አይቻልም። እባክዎ ሌላ ቲቪ ይሞክሩ።" "የጣቢያ መቃኛ ማዋቀር" "የቴሌቪዥን መቃኛ ማዋቀር" "የዩኤስቢ ጣቢያ መቃኛ ማዋቀር" + "የአውታረ መረብ መቃኛ ማዋቀር" "የእርስዎ ቴሌቪዥን ከቴሌቪዥን ሲግናል ምልክት ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አብዛኛዎቹን ጣቢያዎች ለመቀበል አቀማመጡን ወይም አቅጣጫውን ማስተካከል ሊኖርብዎት ይችላል። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" "የዩኤስቢ መቃኛው መሰካቱን እና ከቴሌቪዥን ምልክት ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" + "የአውታረ መረብ መቃኛው እና ከቴሌቪዥን ምልክት ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" "ቀጥል" "አሁን አይደለም" @@ -40,6 +38,7 @@ "የጣቢያ ቅንብር እንደገና እንዲሄድ ይደረግ?" "ይሄ ከቴሌቪዥን መቃኛ የተገኙ ጣቢያዎችን አስወግዶ አዲስ ጣቢያዎችን እንደገና ይቃኛል።\n\nየእርስዎ ቴሌቪዥን ከቴሌቪዥን ሲግናል ምልክት ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አብዛኛዎቹን ጣቢያዎች ለመቀበል አቀማመጡን ወይም አቅጣጫውን ማስተካከል ሊኖርብዎት ይችላል። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" "ይሄ ከዩኤስቢ መቃኛ የተገኙ ጣቢያዎችን አስወግዶ አዲስ ጣቢያዎችን እንደገና ይቃኛል።\n\nየዩኤስቢ መቃኛው መሰካቱን እና ከቴሌቪዥን ምልክት ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" + "ይሄ ከአውታረ መረብ መቃኛ የተገኙ ጣቢያዎችን አስወግዶ አዲስ ጣቢያዎችን እንደገና ይቃኛል።\n\nየአውታረ መረብ መቃኛው እና ከቴሌቪዥን ምልክት ምንጩ መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት።" "ቀጥል" "ይቅር" @@ -54,6 +53,7 @@ "የቴሌቪዥን መቃኛ ማዋቀር" "የዩኤስቢ ጣቢያ መቃኛ ማዋቀር" + "የአውታረ መረብ ጣቢያ መቃኛ ማዋቀር" "ይሄ በርካታ ደቂቃዎችን ሊወስድ ይችላል" "መቃኛው ለጊዜው አይገኝም ወይም አስቀድሞ በቀረጻው ጥቅም ላይ ውሏል።" @@ -76,12 +76,15 @@ "ምንም ጣቢያዎች አልተገኙም" "ቅኝቱ ምንም አዲስ ጣቢያዎችን አላገኘም። የእርስዎ ቴሌቪዥን ከቴሌቪዥን ሲግናል ምንጭ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና ከሆነ የሚጠቀሙት አቀማመጡን ወይም አቅጣጫውን ያስተካክሉት። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡትና እንደገና ይቃኙ።" "ቅኝቱ ምንም ጣቢያዎችን አላገኘም። የዩኤስቢ መቃኛው መሰካቱን እና ከቴሌቪዥን ሲግናል ምንጩ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት እና እንደገና ይቃኙ።" + "ቅኝቱ ምንም ጣቢያዎችን አላገኘም። የአውታረ መረብ መቃኛው እንደበራ እና ከቴሌቪዥን ሲግናል ምንጩ ጋር መገናኘቱን ያረጋግጡ።\n\nየአየር ላይ አንቴና የሚጠቀሙ ከሆነ አቀማመጡን ወይም አቅጣጫውን ያስተካክሉ። ለተሻሉ ውጤቶች ከፍ አድርገው ከመስኮት አጠገብ ያስቀምጡት እና እንደገና ይቃኙ።" "እንደገና ቃኝ" "ተከናውኗል" - "የቲቪ ጣቢያዎችን ቃኝ" - "የቴሌቪዥን መቃኛ ማዋቀር" - "የዩኤስቢ ቴሌቪዥን መቃኛ ማዋቀር" - "የUSB TV መቃኛ ግንኙነቱ ተቋርጧል።" + "የቴሌቪዥን ጣቢያዎችን ቃኝ" + "የቴሌቪዥን መቃኛ ማዋቀር" + "የዩኤስቢ ቴሌቪዥን መቃኛ ማዋቀር" + "የአውታረ መረብ ቴሌቪዥን መቃኛ ማዋቀር" + "የዩኤስቢ ቴሌቪዥን መቃኛው ግንኙነት ተቋርጧል።" + "የአውታረ መረብ መቃኛ ግንኙነት ተቋርጧል።" diff --git a/usbtuner-res/values-ar/strings.xml b/usbtuner-res/values-ar/strings.xml index ecfde3e8..ae197d19 100644 --- a/usbtuner-res/values-ar/strings.xml +++ b/usbtuner-res/values-ar/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "موالف التلفزيون" "‏موالف التلفزيون عبر USB" - "تشغيل" - "إيقاف" + "موالف التلفزيون على الشبكة (تجريبي)" "الرجاء الانتظار لحين انتهاء المعالجة" - "تحديد مصدر القنوات" - "لا توجد إشارة" - "أخفق الضبط على %s" - "أخفق الضبط" "تم تحديث برنامج الموالف مؤخرًا. الرجاء إعادة البحث عن القنوات." "يمكنك تشغيل الصوت المحيطي في إعدادات صوت النظام لتفعيل الصوت" + "لا يمكن تشغيل الصوت. الرجاء تجربة تلفزيون آخر." "إعداد موالف القنوات" "إعداد موالف التلفزيون" "‏إعداد موالف قنوات USB" + "إعداد موالف الشبكة" "تحقق من توصيل التلفزيون بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فقد تحتاج إلى ضبط موضعه أو اتجاهه لاستقبال معظم القنوات، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة." "‏تحقق من توصيل الموالف عبر USB بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فقد تحتاج إلى ضبط موضعه أو اتجاهه لاستقبال معظم القنوات، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة." + "تحقق من تشغيل موالف الشبكة وتوصيله بمصدر إشارة التلفزيون.\n\nفي حالة استخدام هوائي للتحديث عبر الهواء، قد تحتاج إلى ضبط موضعه أو تجاهه لاستقبال معظم القنوات. وللحصول على أفضل النتائج، يمكنك وضعه في مكان مرتفع أو بالقرب من النافذة." "متابعة" "ليس الآن" @@ -40,6 +38,7 @@ "هل تريد إعادة تشغيل إعداد القنوات؟" "سيؤدي هذا إلى إزالة القنوات التي تم العثور عليها من موالف التلفزيون والبحث مرة أخرى عن قنوات جديدة.\n\nتحقق من توصيل التلفزيون بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فقد تحتاج إلى ضبط موضعه أو اتجاهه لاستقبال معظم القنوات، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة." "‏سيؤدي هذا إلى إزالة القنوات التي تم العثور عليها من الموالف عبر USB والبحث مرة أخرى عن قنوات جديدة.\n\nتحقق من توصيل الموالف عبر USB بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فقد تحتاج إلى ضبط موضعه أو اتجاهه لاستقبال معظم القنوات، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة." + "سيؤدي هذا إلى إزالة القنوات الموجودة من موالف الشبكة وإعادة المسح بحثًا عن القنوات الجديدة.\n\nتحقق من تشغيل موالف الشبكة وتوصيله بمصدر إشارة التلفزيون.\n\nفي حالة استخدام هوائي للتحديث عبر الهواء، قد تحتاج إلى ضبط موضعه أو تجاهه لاستقبال معظم القنوات. وللحصول على أفضل النتائج، يمكنك وضعه في مكان مرتفع أو بالقرب من النافذة." "متابعة" "إلغاء" @@ -54,6 +53,7 @@ "إعداد موالف التلفزيون" "‏إعداد موالف قنوات USB" + "إعداد موالف قناة الشبكة" "قد يستغرق هذا عدة دقائق" "لا يتوفر الموالف مؤقتًا أو سبق استخدامه بواسطة التسجيل." @@ -88,12 +88,15 @@ "لم يتم العثور على قنوات" "لم يتم العثور على أي قنوات أثناء البحث، لذا عليك التحقق من توصيل التلفزيون بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فاضبط موضعه أو اتجاهه، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة ثم أعد البحث." "‏لم يتم العثور على أي قنوات أثناء البحث، تحقق من توصيل الموالف عبر USB بمصدر إشارة البث التلفزيوني.\n\nإذا كنت تستخدم هوائيًا للتحديث عبر الهواء، فاضبط موضعه أو اتجاهه، وللحصول على أفضل النتائج، ضعه عاليًا بالقرب من النافذة ثم أعد البحث." + "لم يتم العثور على أي قنوات خلال المسح. تحقق من تشغيل موالف الشبكة وتوصيله بمصدر إشارة التلفزيون.\n\nفي حالة استخدام هوائي للتحديث عبر الهواء، يجب ضبط موضعه أو تجاهه. وللحصول على أفضل النتائج، يمكنك وضعه في مكان مرتفع أو بالقرب من النافذة وإعادة المسح." "بحث مرة أخرى" "تم" - "البحث عن قنوات تلفزيونية" - "إعداد موالف التلفزيون" - "‏إعداد موالف التلفزيون عبر USB" - "‏تم فصل موالف التلفزيون عبر USB." + "البحث عن قنوات تلفزيونية" + "إعداد موالف التلفزيون" + "‏إعداد موالف التلفزيون عبر USB" + "إعداد موالف التلفزيون على الشبكة" + "‏تم فصل موالف التلفزيون عبر USB." + "تم فصل موالف الشبكة." diff --git a/usbtuner-res/values-az-rAZ/strings.xml b/usbtuner-res/values-az-rAZ/strings.xml new file mode 100644 index 00000000..2022ad7b --- /dev/null +++ b/usbtuner-res/values-az-rAZ/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV Kökləyici" + "USB TV Kökləyici" + "Network TV Tuner (BETA)" + "Lütfən, prosesi başa çatdırmaq üçün gözləyin" + "Sazlayıcı proqram təminatı yenicə güncəllənib. Kanalları yenidən skan edin." + "Audionu aktiv etmək üçün sistem səs ayarlarında əhatəli səsi aktiv edin" + "Audio oxuna bilmir. Digər TV-dən istifadə edin" + "Kanal kökləyici quraşdırması" + "TV Kökləyici quraşdırması" + "USB Kanal kökləyici quraşdırması" + "Şəbəkə kökləyici quraşdırması" + "TV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "USB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "Şəbəkə kökləyicinin yanılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + + "Davam edin" + "İndi yox" + + "Kanal quraşdırması yenidən işə salınsın?" + "Bu, TV kökləyici ilə tapılmış kanalları siləcək və yeni kanalları yenidən skan edəcək.\n\nTV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "Bu USB kökləyici ilə tapılmış kanalları siləcək və yeni kanalları yenidən skan edəcək.\n\nUSB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "Bu Şəbəkə kökləyici ilə tapılmış kanalları siləcək və yeni kanalları yenidən skan edəcək.\n\nŞəbəkə kökləyicinin yanılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + + "Davam edin" + "Ləğv edin" + + "Bağlantı növünü seçin" + "Əgər kökləyiciyə xarici antena qoşulubsa Antena seçimini edin. Əgər kanallar kabel xidməti provayderi ilə gəlirsə, onda Kabel seçimini edin. Əgər əmin deyilsinizsə, hər iki növ skan ediləcək, amma bu çox vaxt çəkəcək." + + "Antena" + "Kabel" + "Əmin deyiləm" + "Yalnız inkişaf" + + "TV kökləyici quraşdırması" + "USB Kanal kökləyici quraşdırması" + "Şəbəkə kanalı kökləyici quraşdırması" + "Bu bir neçə dəqiqə çəkə bilər" + "Kökləyici müvəqqəti əlçatan deyil və qeydə alma tərəfindən istifadə olunub." + + %1$d kanal tapıldı + %1$d kanal tapıldı + + "KANAL SKANINI DAYANDIRIN" + + %1$d kanal tapıldı + %1$d kanal tapıldı + + + Yaxşıdır! Kanal skanı zamanı %1$d kanal tapıldı. Əgər düzgün deyilsə, antenanın yerini dəyişin və yenidən skan edin. + Yaxşıdır! Kanal skanı zamanı %1$d kanal tapıldı. Əgər düzgün deyilsə, antenanın yerini dəyişin və yenidən skan edin. + + + "Hazırdır" + "Yenidən skan edin" + + "Kanal tapılmadı" + "Skan ilə heç bir kanal tapılmadı. TV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "Skan ilə heç bir kanal tapılmadı. USB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + "Skan ilə heç bir kanal tapılmadı. Şəbəkə kökləyicinin yanılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." + + "Yenidən skan edin" + "Hazırdır" + + "TV kanalları üçün skan" + "TV Kökləyici quraşdırması" + "USB TV Kökləyici quraşdırması" + "Şəbəkə TV Kökləyici quraşdırması" + "USB TV kökləyicisinin bağlantısı kəsildi." + "Şəbəkə kökləyicisinin bağlantısı kəsildi." + diff --git a/usbtuner-res/values-az/strings.xml b/usbtuner-res/values-az/strings.xml deleted file mode 100644 index f5305817..00000000 --- a/usbtuner-res/values-az/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV Kökləyici" - "USB TV Kökləyici" - "Aktiv" - "Deaktiv" - "Lütfən, prosesi başa çatdırmaq üçün gözləyin" - "Kanal mənbəyinizi seçin" - "Siqnal Yoxdur" - "%s kanalına sazlamaq mümkün olmadı" - "Sazlamaq uğursuz oldu" - "Sazlayıcı proqram təminatı yenicə güncəllənib. Kanalları yenidən skan edin." - "Audionu aktiv etmək üçün sistem səs ayarlarında əhatəli səsi aktiv edin" - "Kanal kökləyici quraşdırması" - "TV Kökləyici quraşdırması" - "USB Kanal kökləyici quraşdırması" - "TV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - "USB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - - "Davam edin" - "İndi yox" - - "Kanal quraşdırması yenidən işə salınsın?" - "Bu, TV kökləyici ilə tapılmış kanalları siləcək və yeni kanalları yenidən skan edəcək.\n\nTV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - "Bu USB kökləyici ilə tapılmış kanalları siləcək və yeni kanalları yenidən skan edəcək.\n\nUSB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - - "Davam edin" - "Ləğv edin" - - "Bağlantı növünü seçin" - "Əgər kökləyiciyə xarici antena qoşulubsa Antena seçimini edin. Əgər kanallar kabel xidməti provayderi ilə gəlirsə, onda Kabel seçimini edin. Əgər əmin deyilsinizsə, hər iki növ skan ediləcək, amma bu çox vaxt çəkəcək." - - "Antena" - "Kabel" - "Əmin deyiləm" - "Yalnız inkişaf" - - "TV kökləyici quraşdırması" - "USB Kanal kökləyici quraşdırması" - "Bu bir neçə dəqiqə çəkə bilər" - "Kökləyici müvəqqəti əlçatan deyil və qeydə alma tərəfindən istifadə olunub." - - %1$d kanal tapıldı - %1$d kanal tapıldı - - "KANAL SKANINI DAYANDIRIN" - - %1$d kanal tapıldı - %1$d kanal tapıldı - - - Yaxşıdır! Kanal skanı zamanı %1$d kanal tapıldı. Əgər düzgün deyilsə, antenanın yerini dəyişin və yenidən skan edin. - Yaxşıdır! Kanal skanı zamanı %1$d kanal tapıldı. Əgər düzgün deyilsə, antenanın yerini dəyişin və yenidən skan edin. - - - "Hazırdır" - "Yenidən skan edin" - - "Kanal tapılmadı" - "Skan ilə heç bir kanal tapılmadı. TV-nizin TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - "Skan ilə heç bir kanal tapılmadı. USB kökləyicinin taxılı olduğunu və TV siqnal mənbəyinə qoşulu olduğunu doğrulayın.\n\nHava antenası istifadə etdikdə, daha çox kanal üçün onun yerini və istiqamətini tənzimləməlisiniz. Daha yaxşı nəticələr üçün hündür yerə və pəncərəyə yaxın yerləşdirin." - - "Yenidən skan edin" - "Hazırdır" - - "TV kanalları üçün skan" - "TV Kökləyici quraşdırması" - "USB TV Kökləyici quraşdırması" - "USB TV kökləyicisinin bağlantısı kəsildi." - diff --git a/usbtuner-res/values-bg/strings.xml b/usbtuner-res/values-bg/strings.xml index 0854c09e..996023ff 100644 --- a/usbtuner-res/values-bg/strings.xml +++ b/usbtuner-res/values-bg/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Телевизионен тунер" "Телевизионен USB тунер" - "Включване" - "Изключване" + "Network TV Tuner (БЕТА)" "Моля, изчакайте обработването да завърши" - "Изберете източник на канали" - "Няма сигнал" - "Превключването към „%s“ не бе успешно" - "Превключването не бе успешно" "Софтуерът на тунера е актуализиран наскоро. Моля, сканирайте отново каналите." "Активирайте обемния звук от настройките за системния, за да включите аудиото" + "Звукът не може да се възпроизведе. Моля, опитайте на друг телевизор" "Настройване на тунера за канали" "Настройване на телевизионния тунер" "Настройване на USB тунера за канали" + "Настройване на мрежовия тунер" "Уверете се, че телевизорът ви е свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да получите оптимален брой канали. За най-добри резултати я поставете високо и близо до прозорец." "Уверете се, че USB тунерът е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да получите оптимален брой канали. За най-добри резултати я поставете високо и близо до прозорец." + "Уверете се, че мрежовият тунер е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да уловите най-много канали. За най-добри резултати я поставете високо и близо до прозорец." "Напред" "Не сега" @@ -40,6 +38,7 @@ "Да се стартира ли отново настройването на каналите?" "Така ще премахнете намерените от телевизионния тунер канали и ще сканирате за нови.\n\nУверете се, че телевизорът ви е свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да получите оптимален брой канали. За най-добри резултати я поставете високо и близо до прозорец." "Така ще премахнете намерените от USB тунера канали и ще сканирате за нови.\n\nУверете се, че USB тунерът е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да получите оптимален брой канали. За най-добри резултати я поставете високо и близо до прозорец." + "Така ще премахнете намерените от мрежовия тунер канали и ще сканирате за нови.\n\nУверете се, че мрежовият тунер е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, може да се наложи да коригирате разположението или посоката й, за да уловите най-много канали. За най-добри резултати я поставете високо и близо до прозорец." "Напред" "Отказ" @@ -54,6 +53,7 @@ "Настройване на телевизионния тунер" "Настройване на USB тунера за канали" + "Настройване на мрежовия тунер за канали" "Това може да отнеме няколко минути" "Временно няма достъп до тунера или той вече се използва за запис." @@ -76,12 +76,15 @@ "Няма намерени канали" "При сканирането не бяха открити канали. Уверете се, че телевизорът ви е свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, коригирайте разположението или посоката й. За най-добри резултати я поставете високо и близо до прозорец и сканирайте отново." "При сканирането не бяха открити канали. Уверете се, че USB тунерът е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, коригирайте разположението или посоката й. За най-добри резултати я поставете високо и близо до прозорец и сканирайте отново." + "При сканирането не бяха открити канали. Уверете се, че мрежовият тунер е включен и свързан с източник на телевизионен сигнал.\n\nАко използвате безжична антена, коригирайте разположението или посоката й. За най-добри резултати я поставете високо и близо до прозорец и сканирайте отново." "Повторно сканиране" "Готово" - "Сканиране за телевизионни канали" - "Настройване на телевизионния тунер" - "Настройване на телевизионния USB тунер" - "Връзката с телевизионния USB тунер е прекратена." + "Сканиране за телевизионни канали" + "Настройване на телевизионния тунер" + "Настройване на телевизионния USB тунер" + "Настройване на Network TV Tuner" + "Връзката с телевизионния USB тунер е прекратена." + "Връзката с мрежовия тунер е прекратена." diff --git a/usbtuner-res/values-bn-rBD/strings.xml b/usbtuner-res/values-bn-rBD/strings.xml new file mode 100644 index 00000000..8eb8a558 --- /dev/null +++ b/usbtuner-res/values-bn-rBD/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV টিউনার" + "USB TV টিউনার" + "নেটওয়ার্ক TV টিউনার (বিটা)" + "প্রক্রিয়াকরণ সম্পূর্ণ না হওয়া পর্যন্ত অনুগ্রহ করে অপেক্ষা করুন" + "টিউনার সফ্টওয়্যার সম্প্রতি আপডেট করা হয়েছে৷ অনুগ্রহ করে চ্যানেলগুলি আবার স্ক্যান করুন৷" + "অডিও সক্ষম করতে সিস্টেম সাউন্ড সেটিংসে সারাউন্ড সাউন্ড সক্ষম করুন" + "অডিও প্লে করা যাবে না৷ অনুগ্রহ করে অন্য টিভি ব্যবহার করার চেষ্ট করুন" + "চ্যানেল টিউনার সেট আপ" + "TV টিউনার সেট আপ" + "USB চ্যানেল টিউনার সেটআপ" + "নেটওয়ার্ক টিউনার সেট আপ" + "আপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "USB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "নেটওয়ার্ক টিউনার চালু রয়েছে এবং কোনো TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উঁচুতে কোনো জানলার সামনে রেখে আবার স্ক্যান করুন৷" + + "চালিয়ে যান" + "এখনই নয়" + + "আবার চ্যানেল সেট আপ করবেন?" + "এটি TV টিউনার থেকে পাওয়া চ্যানেলগুলিকে মুছবে এবং নতুন চ্যানেলগুলির জন্য আবার স্ক্যান করবে৷\n\nআপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "এটি USB টিউনার থেকে পাওয়া চ্যানেলগুলিকে মুছবে এবং নতুন চ্যানেলগুলির জন্য আবার স্ক্যান করবে৷\n\nUSB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "এটি নেটওয়ার্ক টিউনার থেকে পাওয়া চ্যানেলগুলি মুছবে এবং নতুন চ্যানেলগুলিকে আবার স্ক্যান করবে৷\n\nনেটওয়ার্ক টিউনার চালু রয়েছে এবং কোনো TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উঁচুতে কোনো জানলার সামনে রেখে আবার স্ক্যান করুন৷" + + "চালিয়ে যান" + "বাতিল করুন" + + "সংযোগের প্রকার নির্বাচন করুন" + "যদি আপনার টিউনারের সাথে কোনো বাহ্যিক অ্যান্টেনা সংযুক্ত থাকে তাহলে অ্যান্টেনা বাছুন৷ আপনার চ্যানেলগুলি যদি কোনো কেবল পরিষেবা প্রদানকারীর থেকে আসে তাহলে কেবল বাছুন৷ আপনি যদি নিশ্চিত না হন তাহলে উভয় প্রকারের স্ক্যান করা হবে, কিন্তু এটি অনেক সময় নিতে পারে৷" + + "অ্যান্টেনা" + "কেবল" + "নিশ্চিত নই" + "শুধুমাত্র বিকাশের উদ্দেশ্য" + + "TV টিউনার সেট আপ" + "USB চ্যানেল টিউনার সেটআপ" + "নেটওয়ার্ক চ্যানেল টিউনার সেটআপ" + "এটি কয়েক মিনিট সময় নিতে পারে" + "টিউনার অস্থায়ীভাবে অনুপলব্ধ বা রেকডিংয়ে ইতিমধ্যেই ব্যবহৃত হয়েছে৷" + + %1$dটি চ্যানেল পাওয়া গেছে + %1$dটি চ্যানেল পাওয়া গেছে + + "চ্যানেল স্ক্যান করা থামান" + + %1$dটি চ্যানেল পাওয়া গেছে + %1$dটি চ্যানেল পাওয়া গেছে + + + সুন্দর! চ্যানেল স্ক্যান করার সময়ে %1$dটি চ্যানেল পাওয়া গেছে। এটিকে যদি ঠিক বলে না মনে হয়, তাহলে অ্যান্টেনার অবস্থান ঠিক করার চেষ্টা করুন ও আবার স্ক্যান করুন। + সুন্দর! চ্যানেল স্ক্যান করার সময়ে %1$dটি চ্যানেল পাওয়া গেছে। এটিকে যদি ঠিক বলে না মনে হয়, তাহলে অ্যান্টেনার অবস্থান ঠিক করার চেষ্টা করুন ও আবার স্ক্যান করুন। + + + "সম্পন্ন" + "আবার স্ক্যান করুন" + + "কোনো চ্যানেল খুঁজে পাওয়া যায়নি" + "স্ক্যান করে কোনো চ্যানেল খুঁজে পাওয়া যায়নি৷ আপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে সেটির অবস্থান এবং দিক ঠিক করুন৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "স্ক্যান করে কোনো চ্যানেল খুঁজে পাওয়া যায়নি৷ USB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে সেটির অবস্থান এবং দিক ঠিক করুন৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" + "স্ক্যান করে কোনো চ্যানেল খুঁজে পাওয়া যায়নি৷ নেটওয়ার্ক টিউনার চালু এবং কোনো TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে সেটির অবস্থান এবং দিক ঠিক করুন৷ আরো ভাল ফলাফলের জন্য, এটিকে উঁচুতে কোনো জানলার সামনে রেখে আবার স্ক্যান করুন৷" + + "আবার স্ক্যান করুন" + "সম্পন্ন" + + "টিভি চ্যানেলগুলি স্ক্যান করুন" + "TV টিউনার সেট আপ" + "USB টিভি টিউনার সেট আপ" + "নেটওয়ার্ক TV টিউনার সেট আপ" + "USB টিভি টিউনারের সংযোগ বিচ্ছিন্ন হয়েছে।" + "নেটওয়ার্ক টিউনারের সংযোগ বিচ্ছিন্ন হয়েছে।" + diff --git a/usbtuner-res/values-bn/strings.xml b/usbtuner-res/values-bn/strings.xml deleted file mode 100644 index 236e2d96..00000000 --- a/usbtuner-res/values-bn/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV টিউনার" - "USB TV টিউনার" - "চালু আছে" - "বন্ধ করুন" - "প্রক্রিয়াকরণ সম্পূর্ণ না হওয়া পর্যন্ত অনুগ্রহ করে অপেক্ষা করুন" - "আপনার চ্যানেলের উৎস নির্বাচন করুন" - "কোনো সংকেত নেই" - "%s এ টিউন করতে ব্যর্থ হয়েছে" - "টিউন করতে ব্যর্থ হয়েছে" - "টিউনার সফ্টওয়্যার সম্প্রতি আপডেট করা হয়েছে৷ অনুগ্রহ করে চ্যানেলগুলি পুনরায় স্ক্যান করুন৷" - "অডিও সক্ষম করতে সিস্টেম সাউন্ড সেটিংসে সারাউন্ড সাউন্ড সক্ষম করুন" - "চ্যানেল টিউনার সেট আপ" - "TV টিউনার সেট আপ" - "USB চ্যানেল টিউনার সেটআপ" - "আপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - "USB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - - "চালিয়ে যান" - "এখনই নয়" - - "পুনরায় চ্যানেল সেট আপ করবেন?" - "এটি TV টিউনার থেকে পাওয়া চ্যানেলগুলিকে মুছবে এবং নতুন চ্যানেলগুলির জন্য আবার স্ক্যান করবে৷\n\nআপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - "এটি USB টিউনার থেকে পাওয়া চ্যানেলগুলিকে মুছবে এবং নতুন চ্যানেলগুলির জন্য আবার স্ক্যান করবে৷\n\nUSB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে অধিকাংশ চ্যানেল পাওয়ার জন্য আপনাকে সেটির অবস্থান এবং দিক ঠিক করতে হতে পারে৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - - "চালিয়ে যান" - "বাতিল করুন" - - "সংযোগের প্রকার নির্বাচন করুন" - "যদি আপনার টিউনারের সাথে কোনো বাহ্যিক অ্যান্টেনা সংযুক্ত থাকে তাহলে অ্যান্টেনা বাছুন৷ আপনার চ্যানেলগুলি যদি কোনো কেবল পরিষেবা প্রদানকারীর থেকে আসে তাহলে কেবল বাছুন৷ আপনি যদি নিশ্চিত না হন তাহলে উভয় প্রকারের স্ক্যান করা হবে, কিন্তু এটি অনেক সময় নিতে পারে৷" - - "অ্যান্টেনা" - "কেবল" - "নিশ্চিত নই" - "শুধুমাত্র বিকাশের উদ্দেশ্য" - - "TV টিউনার সেট আপ" - "USB চ্যানেল টিউনার সেটআপ" - "এটি কয়েক মিনিট সময় নিতে পারে" - "টিউনার অস্থায়ীভাবে অনুপলব্ধ বা রেকডিংয়ে ইতিমধ্যেই ব্যবহৃত হয়েছে৷" - - %1$dটি চ্যানেল পাওয়া গেছে - %1$dটি চ্যানেল পাওয়া গেছে - - "চ্যানেল স্ক্যান করা থামান" - - %1$dটি চ্যানেল পাওয়া গেছে - %1$dটি চ্যানেল পাওয়া গেছে - - - সুন্দর! চ্যানেল স্ক্যান করার সময়ে %1$dটি চ্যানেল পাওয়া গেছে। এটিকে যদি ঠিক বলে না মনে হয়, তাহলে অ্যান্টেনার অবস্থান ঠিক করার চেষ্টা করুন ও আবার স্ক্যান করুন। - সুন্দর! চ্যানেল স্ক্যান করার সময়ে %1$dটি চ্যানেল পাওয়া গেছে। এটিকে যদি ঠিক বলে না মনে হয়, তাহলে অ্যান্টেনার অবস্থান ঠিক করার চেষ্টা করুন ও আবার স্ক্যান করুন। - - - "সম্পন্ন" - "আবার স্ক্যান করুন" - - "কোনো চ্যানেল খুঁজে পাওয়া যায়নি" - "স্ক্যান করে কোনো চ্যানেল খুঁজে পাওয়া যায়নি৷ আপনার TV একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে কিনা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে সেটির অবস্থান এবং দিক ঠিক করুন৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - "স্ক্যান করে কোনো চ্যানেল খুঁজে পাওয়া যায়নি৷ USB টিউনার প্ল্যাগ ইন রয়েছে এবং একটি TV সিগন্যাল উৎসের সাথে সংযুক্ত রয়েছে তা যাচাই করুন৷\n\nযদি কোনো ওভার-দ্য-এয়ার অ্যান্টেনা ব্যবহার করা হয় তাহলে সেটির অবস্থান এবং দিক ঠিক করুন৷ আরো ভাল ফলাফলের জন্য, এটিকে উচুঁতে কোনো জানলার সামনে রাখুন এবং আবার স্ক্যান করুন৷" - - "আবার স্ক্যান করুন" - "সম্পন্ন" - - "টিভি চ্যানেলগুলি স্ক্যান করুন" - "TV টিউনার সেট আপ" - "USB টিভি টিউনার সেট আপ" - "USB টিভি টিউনারের সংযোগ বিচ্ছিন্ন হয়েছে।" - diff --git a/usbtuner-res/values-ca/strings.xml b/usbtuner-res/values-ca/strings.xml index af90c3d6..d005204e 100644 --- a/usbtuner-res/values-ca/strings.xml +++ b/usbtuner-res/values-ca/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonitzador de televisió" "Sintonitzador de televisió USB" - "Activa" - "Desactiva" + "Sintonitzador de televisió en xarxa (BETA)" "Espera per finalitzar el processament" - "Selecciona la font del canal" - "Sense senyal" - "No s\'ha pogut sintonitzar %s" - "No s\'ha pogut sintonitzar" "El programari del sintonitzador s\'ha actualitzat fa poc. Torna a cercar els canals." "Activa el so envoltant a la configuració de so del sistema per activar l\'àudio" + "No es pot reproduir l\'àudio. Prova-ho amb un altre televisor." "Configuració del sintonitzador de canals" "Configuració del sintonitzador de televisió" "Configuració del sintonitzador de canals USB" + "Configuració del sintonitzador en xarxa" "Verifica que el teu televisor estigui connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra." "Verifica que el sintonitzador USB estigui endollat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra." + "Comprova que el sintonitzador en xarxa estigui engegat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir uns resultats millors, col·loca-la en un lloc elevat i a prop d\'una finestra." "Continua" "Ara no" @@ -40,6 +38,7 @@ "Vols tornar a executar la configuració de canals?" "Això farà que se suprimeixin del sintonitzador de televisió els canals que s\'han trobat i que es tornin a cercar canals nous.\n\nVerifica que el teu televisor estigui connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra." "Això farà que se suprimeixin del sintonitzador USB els canals que s\'han trobat i que es tornin a cercar canals nous.\n\nVerifica que el sintonitzador USB estigui endollat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra." + "Això farà que se suprimeixin del sintonitzador en xarxa els canals trobats i que se\'n tornin a cercar de nous.\n\nComprova que el sintonitzador en xarxa estigui engegat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, pot ser que calgui ajustar-ne la ubicació o la direcció per rebre el màxim de canals. Per obtenir uns resultats millors, col·loca-la en un lloc elevat i a prop d\'una finestra." "Continua" "Cancel·la" @@ -54,6 +53,7 @@ "Configuració del sintonitzador de televisió" "Configuració del sintonitzador de canals USB" + "Configuració del sintonitzador de canals en xarxa" "Aquesta acció pot tardar uns quants minuts" "El sintonitzador no està disponible en aquest moment o bé ja s\'està utilitzant en un enregistrament." @@ -76,12 +76,15 @@ "No s\'ha trobat cap canal" "No s\'ha trobat cap canal. Verifica que el teu televisor estigui connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, ajusta\'n la ubicació o la direcció. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra i torna a cercar canals." "La cerca no ha trobat cap canal. Verifica que el sintonitzador USB estigui endollat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, ajusta\'n la ubicació o la direcció. Per obtenir els millors resultats, col·loca-la en un lloc elevat i a prop d\'una finestra i torna a cercar." + "No s\'ha trobat cap canal. Comprova que el sintonitzador en xarxa estigui engegat i connectat a una font de senyal de televisió.\n\nSi fas servir una antena aèria, ajusta\'n la ubicació o la direcció. Per obtenir uns resultats millors, col·loca-la en un lloc elevat i a prop d\'una finestra i torna a cercar." "Torna a cercar" "Fet" - "Cerca canals de televisió" - "Configuració del sintonitzador de televisió" - "Configuració del sintonitzador de televisió USB" - "El sintonitzador de televisió USB no està connectat." + "Cerca canals de televisió" + "Configuració del sintonitzador de televisió" + "Configuració del sintonitzador de televisió USB" + "Configuració del sintonitzador de televisió en xarxa" + "El sintonitzador de televisió USB no està connectat." + "El sintonitzador de la xarxa no està connectat." diff --git a/usbtuner-res/values-cs/strings.xml b/usbtuner-res/values-cs/strings.xml index 151083c6..69436042 100644 --- a/usbtuner-res/values-cs/strings.xml +++ b/usbtuner-res/values-cs/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Televizní tuner" "Televizní tuner USB" - "Zapnout" - "Vypnout" + "Síťový televizní tuner (BETA)" "Vyčkejte prosím, než bude zpracování dokončeno" - "Vyberte zdroj kanálu" - "Žádný signál" - "Kanál %s se nepodařilo naladit" - "Nelze naladit" "Software tuneru byl nedávno aktualizován. Vyhledejte prosím kanály znovu." "Chcete-li zapnout zvuk, v nastavení systémového zvuku povolte prostorový zvuk" + "Zvuk nelze přehrát. Zkuste použít jinou televizi." "Nastavení tuneru kanálů" "Nastavení televizního tuneru" "Nastavení tuneru kanálů USB" + "Nastavení síťového tuneru" "Zkontrolujte, zda je televize připojena ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." "Zkontrolujte, zda je tuner USB připojen k zařízení a ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." + "Zkontrolujte, zda je síťový tuner zapnut a připojen ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." "Pokračovat" "Teď ne" @@ -40,6 +38,7 @@ "Znovu spustit nastavení kanálů?" "Tímto odstraníte kanály nalezené pomocí televizního tuneru a znovu vyhledáte nové kanály.\n\nZkontrolujte, zda je televize připojena ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." "Tímto odstraníte kanály nalezené pomocí tuneru USB a znovu vyhledáte nové kanály.\n\nZkontrolujte, zda je tuner USB připojen k zařízení a ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." + "Tímto odstraníte kanály nalezené pomocí síťového tuneru a znovu vyhledáte nové kanály.\n\nZkontrolujte, zda je síťový tuner zapnut a připojen ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, možná budete muset za účelem příjmu co největšího počtu kanálů upravit její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna." "Pokračovat" "Zrušit" @@ -54,6 +53,7 @@ "Nastavení televizního tuneru" "Nastavení tuneru kanálů USB" + "Nastavení kanálů síťového tuneru" "Tato akce může trvat několik minut." "Tuner dočasně není k dispozici, případně je právě používán k nahrávání." @@ -82,12 +82,15 @@ "Nebyly nalezeny žádné kanály" "Při vyhledávání nebyly nalezeny žádné kanály. Zkontrolujte, zda je televize připojena ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, upravte její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna. Poté spusťte vyhledávání znovu." "Při vyhledávání nebyly nalezeny žádné kanály. Zkontrolujte, zda je tuner USB připojen k zařízení a ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, upravte její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna. Poté spusťte vyhledávání znovu." + "Při vyhledávání nebyly nalezeny žádné kanály. Zkontrolujte, zda je síťový tuner zapnut a připojen ke zdroji televizního signálu.\n\nPokud používáte bezdrátovou anténu, upravte její umístění nebo nasměrování. Nejlepších výsledků dosáhnete, pokud ji umístíte vysoko a blízko okna. Poté spusťte vyhledávání znovu." "Znovu vyhledat" "Hotovo" - "Vyhledejte televizní kanály" - "Nastavení televizního tuneru" - "Nastavení televizního tuneru USB" - "Televizní tuner USB byl odpojen." + "Vyhledejte televizní kanály" + "Nastavení televizního tuneru" + "Nastavení televizního tuneru USB" + "Nastavení síťového televizního tuneru" + "Televizní tuner USB byl odpojen." + "Síťový tuner byl odpojen." diff --git a/usbtuner-res/values-da/strings.xml b/usbtuner-res/values-da/strings.xml index cea5d3c3..96a902b8 100644 --- a/usbtuner-res/values-da/strings.xml +++ b/usbtuner-res/values-da/strings.xml @@ -19,27 +19,26 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Tuner" "USB-tuner til fjernsynet" - "Til" - "Fra" + "Netværkstuner til tv (beta)" "Vent, mens behandlingen afsluttes" - "Vælg din kanalkilde" - "Intet signal" - "%s kunne ikke indlæses" - "Indlæsningen lykkedes ikke" "Tunerens software er blevet opdateret for nylig. Scan efter kanalerne igen." "Aktivér surroundsound i systemets lydindstillinger for at aktivere lyd" + "Der kan ikke afspilles lyd. Prøv på et andet fjernsyn" "Konfiguration af kanaltuneren" "Konfiguration med TV Tuner" "Konfiguration af USB-kanaltuneren" - "Kontrollér, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." - "Kontrollér, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Konfiguration af netværkstuner" + "Tjek, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Tjek, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Tjek, at netværkstuneren er tændt og sluttet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." "Fortsæt" "Ikke nu" "Vil du gentage kanalkonfigurationen?" - "Dette fjerner de kanaler, som blev fundet, fra fjernsynets tuner og scanner efter nye kanaler igen.\n\nKontrollér, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." - "Denne handling fjerner de kanaler, der blev fundet af USB-tuneren, og starter en ny scanning efter kanaler.\n\nKontrollér, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Dette fjerner de kanaler, som blev fundet, fra fjernsynets tuner og scanner efter nye kanaler igen.\n\nTjek, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Denne handling fjerner de kanaler, der blev fundet af USB-tuneren, og starter en ny scanning efter kanaler.\n\nTjek, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." + "Denne handling fjerner de kanaler, der blev fundet af netværkstuneren, og starter en ny kanalsøgning.\n\nTjek, at netværkstuneren er tændt og sluttet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, kan det være nødvendigt at justere dens position eller retning for at modtage flest muligt kanaler. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue." "Fortsæt" "Annuller" @@ -54,6 +53,7 @@ "Konfiguration med fjernsynets tuner" "Konfiguration af USB-kanaltuneren" + "Konfiguration af netværkstuner til kanalsøgning" "Dette kan tage flere minutter" "Tuneren er midlertidigt utilgængelig eller benyttes allerede til en optagelse." @@ -74,14 +74,17 @@ "Scan igen" "Der blev ikke fundet nogen kanaler" - "Der blev ikke fundet nogen kanaler under scanningen. Kontrollér, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, skal du justere dens position eller retning. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue. Scan igen." - "Der blev ikke fundet nogen kanaler under scanningen. Kontrollér, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, skal du justere dens position eller retning. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue. Scan igen." + "Der blev ikke fundet nogen kanaler under scanningen. Tjek, at dit fjernsyn er forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, skal du justere dens position eller retning. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue. Scan igen." + "Der blev ikke fundet nogen kanaler under scanningen. Tjek, at USB-tuneren er tilsluttet og forbundet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, skal du justere dens position eller retning. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue. Scan igen." + "Der blev ikke fundet nogen kanaler under søgningen. Tjek, at netværkstuneren er tændt og sluttet til en tv-signalkilde.\n\nHvis du bruger en luftantenne, skal du justere dens position eller retning. Du opnår det bedste resultat ved at placere den højt oppe og i nærheden af et vindue. Søg igen." "Scan igen" "Udført" - "Scan efter tv-kanaler" - "Konfiguration med TV Tuner" - "Konfiguration af USB-TV Tuner" - "USB-tuneren til fjernsynet af frakoblet." + "Søg efter tv-kanaler" + "Konfiguration af tuner til fjernsynet" + "Konfiguration af USB-tuner til fjernsynet" + "Konfiguration af netværkstuner til fjernsynet" + "USB-tuneren til fjernsynet er ikke tilsluttet." + "Netværkstuneren er ikke tilsluttet." diff --git a/usbtuner-res/values-de/strings.xml b/usbtuner-res/values-de/strings.xml index eab5fb1d..b0f17dfc 100644 --- a/usbtuner-res/values-de/strings.xml +++ b/usbtuner-res/values-de/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV-Tuner" "USB-TV-Tuner" - "An" - "Aus" + "Netzwerk-TV-Tuner (BETA)" "Bitte warten Sie, bis die Verarbeitung abgeschlossen ist" - "Kanalquelle auswählen" - "Kein Signal" - "\"%s\" konnte nicht eingestellt werden" - "Fehler beim Einstellen" "Die Tunersoftware wurde kürzlich aktualisiert. Bitte führen Sie die Kanalsuche noch einmal durch." "Aktivieren Sie in den Systemeinstellungen Surround-Sound, um Audio einschalten zu können" + "Audio kann nicht wiedergegeben werden. Bitte versuch es mit einem anderen Fernseher." "Kanaleinrichtung über den Tuner" "TV-Tuner einrichten" "Kanaleinrichtung über den USB-Tuner" + "Einrichtung des Netzwerk-Tuners" "Vergewissern Sie sich, dass Ihr Fernseher mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung, um mehr Kanäle zu finden. Die besten Ergebnisse erhalten Sie, wenn Sie sie an eine erhöhte Position in Fensternähe stellen." "Vergewissern Sie sich, dass der USB-Tuner angeschlossen und mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung, um mehr Kanäle zu finden. Die besten Ergebnisse erhalten Sie, wenn Sie sie an einer erhöhten Position in Fensternähe stellen." + "Vergewissere dich, dass der Netzwerk-Tuner eingeschaltet und mit einer TV-Signalquelle verbunden ist.\n\nWenn du eine terrestrische Antenne verwendest, ändere die Position oder Ausrichtung, um mehr Kanäle zu empfangen. Die besten Ergebnisse erhältst du, wenn du die Antenne an eine erhöhte Position in Fensternähe stellst." "Weiter" "Jetzt nicht" @@ -40,6 +38,7 @@ "Kanaleinrichtung erneut durchführen?" "Dies entfernt die vom TV-Tuner gefundenen Kanäle und sucht noch einmal nach neuen Kanälen.\n\nVergewissern Sie sich, dass Ihr Fernseher mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung, um mehr Kanäle zu finden. Die besten Ergebnisse erhalten Sie, wenn Sie sie an eine erhöhte Position in Fensternähe stellen." "Durch diese Aktion werden die gefundenen Kanäle vom USB-Tuner entfernt und die Kanalsuche wird erneut gestartet.\n\nVergewissern Sie sich, dass der USB-Tuner angeschlossen und mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung, um mehr Kanäle zu finden. Die besten Ergebnisse erhalten Sie, wenn Sie sie an einer erhöhten Position in Fensternähe stellen." + "Durch diese Aktion werden die vom Netzwerk-Tuner gefundenen Kanäle entfernt und die Kanalsuche wird neu gestartet.\n\nVergewissere dich, dass der Netzwerk-Tuner eingeschaltet und mit einer TV-Signalquelle verbunden ist.\n\nWenn du eine terrestrische Antenne verwendest, ändere die Position oder Ausrichtung, um mehr Kanäle zu empfangen. Die besten Ergebnisse erhältst du, wenn du die Antenne an eine erhöhte Position in Fensternähe stellst." "Weiter" "Abbrechen" @@ -54,6 +53,7 @@ "TV-Tuner einrichten" "Kanaleinrichtung über den USB-Tuner" + "Kanaleinrichtung über den Netzwerk-Tuner" "Dies kann einige Minuten dauern" "Der Tuner ist vorübergehend nicht verfügbar oder wird schon für eine Aufnahme verwendet." @@ -76,12 +76,15 @@ "Keine Kanäle gefunden" "Bei der Suche wurden keine Kanäle gefunden. Vergewissern Sie sich, dass Ihr Fernseher mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung. Um die besten Ergebnisse zu erhalten, stellen Sie sie an eine erhöhte Position in Fensternähe und führen Sie die Suche noch einmal durch." "Bei der Suche wurden keine Kanäle gefunden. Vergewissern Sie sich, dass der USB-Tuner angeschlossen und mit einer TV-Signalquelle verbunden ist.\n\nWenn Sie eine terrestrische Antenne verwenden, ändern Sie die Position oder Ausrichtung. Die besten Ergebnisse erhalten Sie, wenn Sie sie an einer erhöhten Position in Fensternähe stellen. Führen Sie dann die Suche erneut durch." + "Bei der Suche wurden keine Kanäle gefunden. Vergewissere dich, dass der Netzwerk-Tuner eingeschaltet und mit einer TV-Signalquelle verbunden ist.\n\nWenn du eine terrestrische Antenne verwendest, ändere die Position oder Ausrichtung. Die besten Ergebnisse erhältst du, wenn du sie an eine erhöhte Position in Fensternähe stellst und die Suche noch einmal durchführst." "Noch einmal suchen" "Fertig" - "Nach TV-Kanälen suchen" - "TV-Tuner einrichten" - "USB-TV-Tuner einrichten" - "Verbindung zum USB-TV-Empfänger wurde aufgehoben." + "Nach TV-Kanälen suchen" + "Einrichtung des TV-Tuners" + "Einrichtung des USB-TV-Tuners" + "Einrichtung des Netzwerk-TV-Tuners" + "Verbindung zum USB-TV-Tuner wurde aufgehoben." + "Verbindung zum Netzwerk-Tuner wurde aufgehoben." diff --git a/usbtuner-res/values-el/strings.xml b/usbtuner-res/values-el/strings.xml index 6a033e3c..1ea637d4 100644 --- a/usbtuner-res/values-el/strings.xml +++ b/usbtuner-res/values-el/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Δέκτης τηλεόρασης" "Δέκτης τηλεόρασης USB" - "Ενεργό" - "Ανενεργό" + "Δέκτης τηλεόρασης δικτύου (BETA)" "Περιμένετε να ολοκληρωθεί η επεξεργασία" - "Επιλέξτε την πηγή του καναλιού σας" - "Χωρίς σήμα" - "Αποτυχία συντονισμού %s" - "Αποτυχία συντονισμού" "Το λογισμικό δέκτη ενημερώθηκε πρόσφατα. Επαναλάβετε τη σάρωση των καναλιών." "Ενεργοποιήστε τον περιφερειακό ήχο στις ρυθμίσεις ήχου συστήματος για να ενεργοποιήσετε τον ήχο" + "Δεν είναι δυνατή η αναπαραγωγή ήχου. Δοκιμάστε μια άλλη τηλεόραση." "Ρύθμιση δέκτη καναλιών" "Ρύθμιση δέκτη τηλεόρασης" "Ρύθμιση δέκτη καναλιών USB" + "Ρύθμιση δέκτη δικτύου" "Βεβαιωθείτε ότι η τηλεόρασή σας είναι συνδεδεμένη σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." "Βεβαιωθείτε ότι ο δέκτης USB είναι συνδεδεμένος στην πρίζα και σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." + "Βεβαιωθείτε ότι ο δέκτης του δικτύου είναι ενεργοποιημένος και συνδεδεμένος σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." "Συνέχεια" "Όχι τώρα" @@ -40,6 +38,7 @@ "Επανάληψη ρύθμισης καναλιών;" "Με αυτόν τον τρόπο θα καταργηθούν τα κανάλια που βρέθηκαν από τον δέκτη τηλεόρασης και θα γίνει ξανά σάρωση για νέα κανάλια.\n\nΒεβαιωθείτε ότι η τηλεόρασή σας είναι συνδεδεμένη σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." "Με αυτόν τον τρόπο θα καταργηθούν τα κανάλια που βρέθηκαν από τον δέκτη USB και θα γίνει ξανά σάρωση για νέα κανάλια.\n\nΒεβαιωθείτε ότι ο δέκτης USB είναι συνδεδεμένος στην πρίζα και σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." + "Με αυτόν τον τρόπο, τα κανάλια που βρέθηκαν από τον δέκτη του δικτύου θα καταργηθούν και θα γίνει ξανά σάρωση για νέα κανάλια.\n\nΒεβαιωθείτε ότι ο δέκτης του δικτύου είναι ενεργοποιημένος και συνδεδεμένος σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, ίσως χρειαστεί να προσαρμόσετε την τοποθέτηση ή την κατεύθυνσή της για να λάβετε περισσότερα κανάλια. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο." "Συνέχεια" "Ακύρωση" @@ -54,6 +53,7 @@ "Ρύθμιση δέκτη τηλεόρασης" "Ρύθμιση δέκτη καναλιών USB" + "Ρύθμιση δέκτη καναλιών δικτύου" "Αυτό μπορεί να διαρκέσει αρκετά λεπτά" "Ο δέκτης δεν είναι διαθέσιμος προσωρινά ή χρησιμοποιείται ήδη από την εγγραφή." @@ -76,12 +76,15 @@ "Δεν βρέθηκαν κανάλια" "Δεν βρέθηκαν κανάλια κατά τη σάρωση. Βεβαιωθείτε ότι η τηλεόρασή σας είναι συνδεδεμένη σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, προσαρμόστε την τοποθέτηση ή την κατεύθυνσή της. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο και επαναλάβετε τη σάρωση." "Δεν βρέθηκαν κανάλια κατά τη σάρωση. Βεβαιωθείτε ότι ο δέκτης USB είναι συνδεδεμένος στην πρίζα και σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, προσαρμόστε την τοποθέτηση ή την κατεύθυνσή της. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε ένα παράθυρο και επαναλάβετε τη σάρωση." + "Δεν εντοπίστηκε κανένα κανάλι κατά τη σάρωση. Βεβαιωθείτε ότι ο δέκτης του δικτύου είναι ενεργοποιημένος και συνδεδεμένος σε μια πηγή τηλεοπτικού σήματος.\n\nΕάν χρησιμοποιείτε ασύρματη κεραία, προσαρμόστε την τοποθέτηση ή την κατεύθυνσή της. Για καλύτερα αποτελέσματα, τοποθετήστε την ψηλά και κοντά σε κάποιο παράθυρο και επαναλάβετε τη σάρωση." "Εκ νέου σάρωση" "Ολοκληρώθηκε" - "Σάρωση για τηλεοπτικά κανάλια" - "Ρύθμιση δέκτη τηλεόρασης" - "Ρύθμιση δέκτη τηλεόρασης USB" - "Ο δέκτης τηλεόρασης USB έχει αποσυνδεθεί." + "Σάρωση για τηλεοπτικά κανάλια" + "Ρύθμιση δέκτη τηλεόρασης" + "Ρύθμιση δέκτη τηλεόρασης USB" + "Ρύθμιση δέκτη τηλεόρασης δικτύου" + "Ο δέκτης τηλεόρασης USB έχει αποσυνδεθεί." + "Ο δέκτης δικτύου έχει αποσυνδεθεί." diff --git a/usbtuner-res/values-en-rAU/strings.xml b/usbtuner-res/values-en-rAU/strings.xml index 11e639fd..dccba99e 100644 --- a/usbtuner-res/values-en-rAU/strings.xml +++ b/usbtuner-res/values-en-rAU/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Tuner" "USB TV Tuner" - "On" - "Off" + "Network TV Tuner (BETA)" "Please wait to finish processing" - "Select your channel source" - "No Signal" - "Failed to tune to %s" - "Failed to tune" "Tuner software has been recently updated. Please re-scan the channels." "To enable audio, enable surround sound in system sound settings" + "Cannot play audio. Please try another TV" "Channel tuner setup" "TV Tuner setup" "USB channel tuner setup" + "Network Tuner Setup" "Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Not now" @@ -40,6 +38,7 @@ "Re-run channel setup?" "This will remove the channels found from the TV tuner and scan for new channels again.\n\nCheck that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "This will remove the channels found from the USB tuner and scan for new channels again.\n\nCheck that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "This will remove the channels found from the network tuner and scan for new channels again.\n\nVerify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Cancel" @@ -54,6 +53,7 @@ "TV tuner setup" "USB channel tuner setup" + "Network channel tuner setup" "This may take several minutes" "Tuner is temporarily unavailable or already used by recording." @@ -76,12 +76,15 @@ "No channels found" "The scan did not find any channels. Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." "The scan did not find any channels. Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." + "The scan did not find any channels. Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, adjust its placement or direction. For best results, place it high and near a window and scan again." "Scan again" "Finished" - "Scan for TV channels" - "TV Tuner setup" - "USB TV Tuner setup" - "USB TV tuner disconnected." + "Scan for TV channels" + "TV Tuner setup" + "USB TV Tuner setup" + "Network TV Tuner setup" + "USB TV tuner disconnected." + "Network tuner disconnected." diff --git a/usbtuner-res/values-en-rGB/strings.xml b/usbtuner-res/values-en-rGB/strings.xml index 11e639fd..dccba99e 100644 --- a/usbtuner-res/values-en-rGB/strings.xml +++ b/usbtuner-res/values-en-rGB/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Tuner" "USB TV Tuner" - "On" - "Off" + "Network TV Tuner (BETA)" "Please wait to finish processing" - "Select your channel source" - "No Signal" - "Failed to tune to %s" - "Failed to tune" "Tuner software has been recently updated. Please re-scan the channels." "To enable audio, enable surround sound in system sound settings" + "Cannot play audio. Please try another TV" "Channel tuner setup" "TV Tuner setup" "USB channel tuner setup" + "Network Tuner Setup" "Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Not now" @@ -40,6 +38,7 @@ "Re-run channel setup?" "This will remove the channels found from the TV tuner and scan for new channels again.\n\nCheck that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "This will remove the channels found from the USB tuner and scan for new channels again.\n\nCheck that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "This will remove the channels found from the network tuner and scan for new channels again.\n\nVerify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Cancel" @@ -54,6 +53,7 @@ "TV tuner setup" "USB channel tuner setup" + "Network channel tuner setup" "This may take several minutes" "Tuner is temporarily unavailable or already used by recording." @@ -76,12 +76,15 @@ "No channels found" "The scan did not find any channels. Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." "The scan did not find any channels. Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." + "The scan did not find any channels. Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, adjust its placement or direction. For best results, place it high and near a window and scan again." "Scan again" "Finished" - "Scan for TV channels" - "TV Tuner setup" - "USB TV Tuner setup" - "USB TV tuner disconnected." + "Scan for TV channels" + "TV Tuner setup" + "USB TV Tuner setup" + "Network TV Tuner setup" + "USB TV tuner disconnected." + "Network tuner disconnected." diff --git a/usbtuner-res/values-en-rIN/strings.xml b/usbtuner-res/values-en-rIN/strings.xml index 11e639fd..dccba99e 100644 --- a/usbtuner-res/values-en-rIN/strings.xml +++ b/usbtuner-res/values-en-rIN/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Tuner" "USB TV Tuner" - "On" - "Off" + "Network TV Tuner (BETA)" "Please wait to finish processing" - "Select your channel source" - "No Signal" - "Failed to tune to %s" - "Failed to tune" "Tuner software has been recently updated. Please re-scan the channels." "To enable audio, enable surround sound in system sound settings" + "Cannot play audio. Please try another TV" "Channel tuner setup" "TV Tuner setup" "USB channel tuner setup" + "Network Tuner Setup" "Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Not now" @@ -40,6 +38,7 @@ "Re-run channel setup?" "This will remove the channels found from the TV tuner and scan for new channels again.\n\nCheck that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "This will remove the channels found from the USB tuner and scan for new channels again.\n\nCheck that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." + "This will remove the channels found from the network tuner and scan for new channels again.\n\nVerify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window." "Continue" "Cancel" @@ -54,6 +53,7 @@ "TV tuner setup" "USB channel tuner setup" + "Network channel tuner setup" "This may take several minutes" "Tuner is temporarily unavailable or already used by recording." @@ -76,12 +76,15 @@ "No channels found" "The scan did not find any channels. Check that your TV is connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." "The scan did not find any channels. Check that the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air aerial, adjust its placement or direction. For best results, place it high and near a window and scan again." + "The scan did not find any channels. Verify the network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air antenna, adjust its placement or direction. For best results, place it high and near a window and scan again." "Scan again" "Finished" - "Scan for TV channels" - "TV Tuner setup" - "USB TV Tuner setup" - "USB TV tuner disconnected." + "Scan for TV channels" + "TV Tuner setup" + "USB TV Tuner setup" + "Network TV Tuner setup" + "USB TV tuner disconnected." + "Network tuner disconnected." diff --git a/usbtuner-res/values-es-rUS/strings.xml b/usbtuner-res/values-es-rUS/strings.xml index da7ec699..cca69f4e 100644 --- a/usbtuner-res/values-es-rUS/strings.xml +++ b/usbtuner-res/values-es-rUS/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonizador de TV" "Sintonizador de TV USB" - "Activar" - "Desactivar" + "Sintonizador de TV de red (BETA)" "Espera a que finalice el procesamiento" - "Seleccionar la fuente del canal" - "Sin señal" - "No se pudo sintonizar el canal %s" - "No se pudo sintonizar el canal" "El software del sintonizador se actualizó recientemente. Vuelve a buscar los canales." "Para habilitar el audio, deberás activar el sonido envolvente en la configuración del sistema de sonido" + "No se puede reproducir el audio. Intenta usar otra TV." "Configuración del sintonizador de canales" "Configuración del sintonizador de TV" "Configuración del sintonizador de canales USB" + "Configuración del sintonizador de red" "Verifica que tu TV esté conectada a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, es posible que tengas que ajustar su posición o dirección para recibir la mayor cantidad de canales posible. Para obtener mejores resultados, ubica la antena en un lugar alto y cerca de una ventana." "Verifica que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, es posible que necesites ajustar su ubicación y dirección para recibir la mayor cantidad de canales. Para obtener mejores resultados, ubica la antena en un lugar alto y cerca de una ventana." + "Comprueba que el sintonizador de red esté encendido y conectado a una fuente de señal de TV.\n\nSi estás usando una antena inalámbrica, es posible que debas ajustar su ubicación o dirección para recibir la mayoría de los canales. Si deseas obtener mejores resultados, colócala en un lugar alto y cerca de una ventana." "Continuar" "Ahora no" @@ -40,6 +38,7 @@ "¿Quieres volver a configurar los canales?" "Esta acción quitará los canales encontrados desde el sintonizador de TV y se volverán a buscar canales nuevos.\n\nVerifica que tu TV esté conectada a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, es posible que tengas que ajustar su posición o dirección para recibir la mayor cantidad de canales posible. Para obtener mejores resultados, ubica la antena en un lugar alto y cerca de una ventana." "Esta acción quitará los canales encontrados desde el sintonizador de TV y se volverán a buscar canales nuevos.\n\nVerifica que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, es posible que necesites ajustar su ubicación y dirección para recibir la mayor cantidad de canales. Para obtener mejores resultados, ubica la antena en un lugar alto y cerca de una ventana." + "De esta manera, se quitarán los canales encontrados desde el sintonizador de red y se buscarán canales nuevos.\n\nComprueba que el sintonizador de red esté encendido y conectado a una fuente de señal de TV.\n\nSi estás usando una antena inalámbrica, es posible que debas ajustar su ubicación o dirección para recibir la mayor cantidad de canales. Para obtener mejores resultados, colócala en un lugar alto y cerca de una ventana." "Continuar" "Cancelar" @@ -54,6 +53,7 @@ "Configuración del sintonizador de TV" "Configuración del sintonizador de canales USB" + "Configuración del sintonizador de canales de red" "Este proceso podría demorar varios minutos" "El sintonizador no está disponible en este momento o está grabando." @@ -76,12 +76,15 @@ "No se encontraron canales" "No se encontró ningún canal. Verifica que tu TV esté conectada a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, ajusta su posición o dirección. Para obtener mejores resultados, ubícala en un lugar alto y cerca de una ventana. Luego, vuelve a hacer la búsqueda." "No se encontraron canales en la búsqueda. Verifica que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi usas una antena inalámbrica, ajusta su ubicación o dirección. Para obtener mejores resultados, ubícala en un lugar alto y cerca de una ventana. Luego, vuelve a hacer la búsqueda." + "No se encontró ningún canal durante la búsqueda. Comprueba que el sintonizador de red esté encendido y conectado a una fuente de señal de TV.\n\nSi estás usando una antena inalámbrica, ajusta su ubicación o dirección. Para obtener mejores resultados, colócala en un lugar alto y cerca de una ventana, y repite la búsqueda." "Volver a buscar" "Listo" - "Busca canales de TV" - "Configuración del sintonizador de TV" - "Configuración del sintonizador de TV USB" - "Se desconectó el sintonizador de TV USB." + "Busca canales de TV" + "Configuración del sintonizador de TV" + "Configuración del sintonizador de TV USB" + "Configuración del sintonizador de TV de red" + "Se desconectó el sintonizador de TV USB." + "Se desconectó el sintonizador de red." diff --git a/usbtuner-res/values-es/strings.xml b/usbtuner-res/values-es/strings.xml index e8e34cb2..26742e65 100644 --- a/usbtuner-res/values-es/strings.xml +++ b/usbtuner-res/values-es/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonizador de canales" "Sintonizador de canales USB" - "Activar" - "Desactivar" + "Sintonizador de TV en red (BETA)" "Espera hasta que finalice el procesamiento" - "Selecciona la fuente de canales" - "Sin señal" - "No se ha podido sintonizar %s" - "No se podido sintonizar" "El software del sintonizador se ha actualizado recientemente. Vuelve a buscar los canales." "Habilita el sonido envolvente en los ajustes del sistema de sonido para activar el audio" + "No se puede reproducir el audio. Prueba con otra TV" "Configuración del sintonizador de canales" "Configuración del sintonizador de canales" "Configuración del sintonizador de canales USB" + "Configuración del sintonizador en red" "Comprueba que la TV esté conectada a una fuente de señal de TV.\n\n Si utilizas una antena inalámbrica, puede que tengas que cambiar su posición o dirección para recibir la mayor cantidad posible de canales. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana." - "Comprueba que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, puede que tengas que cambiar su ubicación o dirección para recibir la mayor cantidad posible de canales. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana." + "Comprueba que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, puede que tengas que cambiar su emplazamiento o dirección para recibir la mayor cantidad posible de canales. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana." + "Comprueba que el sintonizador en red esté encendido y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, es posible que tengas que cambiar su emplazamiento o su orientación para ver la mayoría de los canales. Para obtener los mejores resultados, colócala en un lugar alto cerca de una ventana." "Continuar" "Ahora no" @@ -40,6 +38,7 @@ "¿Quieres volver a ejecutar la configuración de canales?" "Se quitarán los canales encontrados del sintonizador de canales y se volverán a buscar nuevos canales.\n\nComprueba que la TV esté conectada a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, puede que tengas que cambiar su posición o dirección para recibir la mayor cantidad posible de canales. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana." "Se quitarán los canales encontrados con el sintonizador USB y se volverán a buscar nuevos canales.\n\nComprueba que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, puede que tengas que cambiar su ubicación o dirección para recibir la mayor cantidad posible de canales. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana." + "Se quitarán los canales encontrados del sintonizador en red y se repetirá la búsqueda de canales.\n\nComprueba que el sintonizador en red esté encendido y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, es posible que tengas que cambiar su emplazamiento o su orientación para ver la mayoría de los canales. Para obtener los mejores resultados, colócala en un lugar alto cerca de una ventana." "Continuar" "Cancelar" @@ -54,6 +53,7 @@ "Configuración del sintonizador de canales" "Configuración del sintonizador de canales USB" + "Configuración del sintonizador de canales en red" "Este proceso puede tardar varios minutos" "El sintonizador no está disponible temporalmente o se está utilizando en otra grabación." @@ -75,13 +75,16 @@ "No se han encontrado canales" "No se ha encontrado ningún canal. Comprueba que la TV esté conectada a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, cambia su posición o dirección. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana. A continuación, vuelve a realizar la búsqueda." - "No se ha encontrado ningún canal. Comprueba que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, cambia su ubicación o dirección. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana. A continuación, vuelve a realizar la búsqueda." + "No se ha encontrado ningún canal. Comprueba que el sintonizador USB esté enchufado y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, cambia su emplazamiento o dirección. Para conseguir los mejores resultados, colócala en alto y cerca de una ventana. A continuación, vuelve a realizar la búsqueda." + "El análisis no ha encontrado ningún canal. Comprueba que el sintonizador en red esté encendido y conectado a una fuente de señal de TV.\n\nSi utilizas una antena inalámbrica, cambia su emplazamiento u orientación. Para obtener los mejores resultados, colócala en un lugar alto cerca de una ventana y repite el análisis." "Volver a buscar" "Listo" - "Busca canales de TV" - "Configuración del sintonizador de canales" - "Configuración de sintonizador de canales USB" - "Sintonizador de canales USB desconectado." + "Buscar canales de TV" + "Configuración del sintonizador de TV" + "Configuración de sintonizador de TV USB" + "Configuración del sintonizador de TV en red" + "El sintonizador de canales USB está desconectado." + "El sintonizador en red está desconectado." diff --git a/usbtuner-res/values-et-rEE/strings.xml b/usbtuner-res/values-et-rEE/strings.xml new file mode 100644 index 00000000..3d97a31a --- /dev/null +++ b/usbtuner-res/values-et-rEE/strings.xml @@ -0,0 +1,90 @@ + + + + + "Telerituuner" + "USB-telerituuner" + "Teleri võrgutuuner (BEETA)" + "Oodake, kuni töötlemine on lõppenud" + "Tuuneri tarkvara värskendati hiljuti. Otsige kanaleid uuesti." + "Heli lubamiseks lubage ruumiline heli süsteemi heliseadetes" + "Heli ei saa esitada. Proovige teist telerit" + "Kanalituuneri seadistus" + "Telerituuneri seadistus" + "USB-kanalituuneri seadistus" + "Võrgutuuneri seadistus" + "Veenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate võimalikult paljude kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." + "Veenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate enamiku kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." + "Veenduge, et võrgutuuner oleks sisse lülitatud ja teleri signaaliallikaga ühendatud.\n\nKui kasutate õhuantenni, peate võib-olla muutma selle asendit või suunda, et rohkem kanaleid leida. Parimate tulemuste saavutamiseks asetage see kõrgesse kohta akna lähedale." + + "Jätka" + "Mitte praegu" + + "Kas käitada kanali seadistust uuesti?" + "See eemaldab telerituuneri leitud kanalid ja otsib uuesti uusi kanaleid.\n\nVeenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate võimalikult paljude kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." + "See eemaldab USB-tuuneri leitud kanalid ja otsib uuesti uusi kanaleid.\n\nVeenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate enamiku kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." + "See eemaldab võrgutuunerist leitud kanalid ja skannib uuesti uusi kanaleid.\n\nVeenduge, et võrgutuuner oleks sisse lülitatud ja teleri signaaliallikaga ühendatud.\n\nKui kasutate õhuantenni, peate võib-olla muutma selle asendit või suunda, et rohkem kanaleid leida. Parimate tulemuste saavutamiseks asetage see kõrgesse kohta akna lähedale." + + "Jätka" + "Tühista" + + "Valige ühenduse tüüp" + "Kui tuuneriga on ühendatud väline antenn, tehke valik Antenn. Kui kanaleid pakub kaabelteenuse pakkuja, tehke valik Kaabel. Kui te pole kindel, otsitakse mõlemat tüüpi, kuid see võib kauem aega võtta." + + "Antenn" + "Kaabel" + "Ei ole kindel" + "Ainult arenduseks" + + "Telerituuneri seadistus" + "USB-kanalituuneri seadistus" + "Võrgutuuneri kanalite seadistus" + "See võib võtta mitu minutit" + "Tuuner pole ajutiselt saadaval või seda kasutatakse juba salvestamiseks." + + Leiti %1$d kanalit + Leiti %1$d kanal + + "PEATA KANALITE OTSIMINE" + + Leiti %1$d kanalit + Leiti %1$d kanal + + + Hästi! Kanalite otsimisel leiti %1$d kanalit. Kui see ei tundu õige, kohandage antenni asendit ja otsige uuesti. + Hästi! Kanalite otsimisel leiti %1$d kanal. Kui see ei tundu õige, kohandage antenni asendit ja otsige uuesti. + + + "Valmis" + "Otsi uuesti" + + "Ühtegi kanalit ei leitud" + "Otsimisel ei leitud ühtegi kanalit. Veenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, kohandage selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale ning otsige uuesti." + "Otsimisel ei leitud ühtegi kanalit. Veenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, kohandage selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale ning otsige uuesti." + "Skannimisel ei leitud ühtegi kanalit. Veenduge, et võrgutuuner oleks sisse lülitatud ja teleri signaaliallikaga ühendatud.\n\nKui kasutate õhuantenni, muutke selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgesse kohta akna lähedale ja skannige uuesti." + + "Otsi uuesti" + "Valmis" + + "Telekanalite otsimine" + "Telerituuneri seadistus" + "USB-telerituuneri seadistus" + "Teleri võrgutuuneri seadistus" + "USB-telerituuner eemaldati." + "Võrgutuuner eemaldati." + diff --git a/usbtuner-res/values-et/strings.xml b/usbtuner-res/values-et/strings.xml deleted file mode 100644 index 70cb238a..00000000 --- a/usbtuner-res/values-et/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Telerituuner" - "USB-telerituuner" - "Sees" - "Väljas" - "Oodake, kuni töötlemine on lõppenud" - "Valige kanali allikas" - "Pole signaali" - "Kanalile %s häälestamine ebaõnnestus" - "Häälestamine ebaõnnestus" - "Tuuneri tarkvara värskendati hiljuti. Otsige kanaleid uuesti." - "Heli lubamiseks lubage ruumiline heli süsteemi heliseadetes" - "Kanalituuneri seadistus" - "Telerituuneri seadistus" - "USB-kanalituuneri seadistus" - "Veenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate võimalikult paljude kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." - "Veenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate enamiku kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." - - "Jätka" - "Mitte praegu" - - "Kas käitada kanali seadistust uuesti?" - "See eemaldab telerituuneri leitud kanalid ja otsib uuesti uusi kanaleid.\n\nVeenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate võimalikult paljude kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." - "See eemaldab USB-tuuneri leitud kanalid ja otsib uuesti uusi kanaleid.\n\nVeenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, peate enamiku kanalite nägemiseks võib-olla kohandama selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale." - - "Jätka" - "Tühista" - - "Valige ühenduse tüüp" - "Kui tuuneriga on ühendatud väline antenn, tehke valik Antenn. Kui kanaleid pakub kaabelteenuse pakkuja, tehke valik Kaabel. Kui te pole kindel, otsitakse mõlemat tüüpi, kuid see võib kauem aega võtta." - - "Antenn" - "Kaabel" - "Ei ole kindel" - "Ainult arenduseks" - - "Telerituuneri seadistus" - "USB-kanalituuneri seadistus" - "See võib võtta mitu minutit" - "Tuuner pole ajutiselt saadaval või seda kasutatakse juba salvestamiseks." - - Leiti %1$d kanalit - Leiti %1$d kanal - - "PEATA KANALITE OTSIMINE" - - Leiti %1$d kanalit - Leiti %1$d kanal - - - Hästi! Kanalite otsimisel leiti %1$d kanalit. Kui see ei tundu õige, kohandage antenni asendit ja otsige uuesti. - Hästi! Kanalite otsimisel leiti %1$d kanal. Kui see ei tundu õige, kohandage antenni asendit ja otsige uuesti. - - - "Valmis" - "Otsi uuesti" - - "Ühtegi kanalit ei leitud" - "Otsimisel ei leitud ühtegi kanalit. Veenduge, et teler oleks ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, kohandage selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale ning otsige uuesti." - "Otsimisel ei leitud ühtegi kanalit. Veenduge, et USB-tuuner oleks pistikus ja ühendatud teleri signaaliallikaga.\n\nKui kasutate õhuantenni, kohandage selle asendit või suunda. Parimate tulemuste saavutamiseks asetage see kõrgele ja akna lähedale ning otsige uuesti." - - "Otsi uuesti" - "Valmis" - - "Otsige telekanaleid" - "Telerituuneri seadistus" - "USB-telerituuneri seadistus" - "USB-telerituuner eemaldati." - diff --git a/usbtuner-res/values-eu-rES/strings.xml b/usbtuner-res/values-eu-rES/strings.xml new file mode 100644 index 00000000..0f7ac943 --- /dev/null +++ b/usbtuner-res/values-eu-rES/strings.xml @@ -0,0 +1,90 @@ + + + + + "Sintonizadorea" + "USB bidezko sintonizadorea" + "Sareko sintonizadorea (BETA)" + "Itxaron prozesatzen amaitu arte" + "Sintonizadorearen softwarea berriki eguneratu da. Bilatu kanalak berriro." + "Audioa gaitzeko, joan sistemaren soinuaren ezarpenetara eta gaitu soinu inguratzailea" + "Ezin da erreproduzitu audioa. Saiatu beste telebista batean." + "Kanal-sintonizadorearen konfigurazioa" + "Sintonizadorearen konfigurazioa" + "USB kanal-sintonizadorearen konfigurazioa" + "Sareko sintonizadorearen konfigurazioa" + "Egiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." + "Egiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nHari gabeko antena badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." + "Egiaztatu sintonizadorea piztuta dagoela eta telebista-seinalea igortzen duen iturburu batera konektatu duzula.\n\nHari-gabeko antena bat erabiltzen ari bazara, doi ezazu haren kokapena edo norabidea ahal bezain beste kanal aurkitzeko. Emaitzarik onenak lortzeko, ezar ezazu ahal bezain altu eta leiho batetik gertu." + + "Jarraitu" + "Orain ez" + + "Berriro konfiguratu nahi dituzu kanalak?" + "Sintonizadoreak aurkitutako kanalak kenduko dira eta berriro bilatuko dira kanalak.\n\nEgiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." + "USB sintonizadoreak aurkitutako kanalak kenduko dira eta berriro bilatuko dira kanalak.\n\nEgiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." + "Hori eginez gero, kendu egingo dira sareko sintonizadoreak aurkitutako kanalak eta zerotik hasiko da berriro kanalak bilatzen.\n\n Egiaztatu sintonizadorea piztuta dagoela eta telebista-seinalea igortzen duen iturburu batera konektatu duzula.\n\nHari-gabeko antena bat erabiltzen ari bazara, doi ezazu haren kokapena edo norabidea ahal bezain beste kanal aurkitzeko. Emaitzarik onenak lortzeko, ezar ezazu ahal bezain altu eta leiho batetik gertu." + + "Jarraitu" + "Utzi" + + "Hautatu konexio mota" + "Kanpoko antena badago sintonizadorera konektatuta, aukeratu Antena. Kableko zerbitzu-hornitzaile batek eskaintzen badizkizu kanalak, aukeratu Kablea. Ziur ez bazaude, mota bietakoak bilatuko dira, baina zertxobait luzatuko da." + + "Antena" + "Kablea" + "Ez dakit ziur" + "Garapenerako soilik" + + "Sintonizadorearen konfigurazioa" + "USB kanal-sintonizadorearen konfigurazioa" + "Sareko kanalen sintonizadorearen konfigurazioa" + "Zenbait minutu beharko dira" + "Sintonizadorea ez dago erabilgarri edo beste zerbait ari da grabatzen." + + %1$d kanal aurkitu dira + %1$d kanal aurkitu da + + "UTZI KANALAK BILATZEARI" + + %1$d kanal aurkitu dira + %1$d kanal aurkitu da + + + Primeran! %1$d kanal aurkitu da kanalak bilatzean. Oker gaudela uste baduzu, doitu antenaren posizioa eta saiatu berriro. + Primeran! %1$d kanal aurkitu da kanalak bilatzean. Oker gaudela uste baduzu, doitu antenaren posizioa eta saiatu berriro. + + + "Eginda" + "Bilatu berriro" + + "Ez da aurkitu kanalik" + "Bilaketak ez du aurkitu kanalik. Egiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." + "Bilaketak ez du aurkitu kanalik. Egiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nAntena analogiko bat badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." + "Ez da aurkitu kanalik. Egiaztatu sintonizadorea piztuta dagoela eta telebista-seinalea igortzen duen iturburu batera konektatu duzula.\n\nHari-gabeko antena bat erabiltzen ari bazara, doi ezazu haren kokapena edo norabidea. Emaitzarik onenak lortzeko, ezar ezazu ahal bezain altu eta leiho batetik gertu. Ondoren, bila itzazu kanalak berriro." + + "Bilatu berriro" + "Eginda" + + "Bilatu telebista-kateak" + "Sintonizadorearen konfigurazioa" + "USB bidezko sintonizadorearen konfigurazioa" + "Sareko sintonizadorearen konfigurazioa" + "Deskonektatu da USB bidezko sintonizadorea." + "Deskonektatu da sareko sintonizadorea." + diff --git a/usbtuner-res/values-eu/strings.xml b/usbtuner-res/values-eu/strings.xml deleted file mode 100644 index a0d456eb..00000000 --- a/usbtuner-res/values-eu/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Sintonizadorea" - "USB bidezko sintonizadorea" - "Aktibatuta" - "Desaktibatuta" - "Itxaron prozesatzen amaitu arte" - "Hautatu kanalaren iturburua" - "Ez dago seinalerik" - "Ezin izan da sintonizatu %s" - "Ezin izan da sintonizatu" - "Sintonizadorearen softwarea berriki eguneratu da. Bilatu kanalak berriro." - "Audioa gaitzeko, joan sistemaren soinuaren ezarpenetara eta gaitu soinu inguratzailea" - "Kanal-sintonizadorearen konfigurazioa" - "Sintonizadorearen konfigurazioa" - "USB kanal-sintonizadorearen konfigurazioa" - "Egiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." - "Egiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nHari gabeko antena badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." - - "Jarraitu" - "Orain ez" - - "Berriro konfiguratu nahi dituzu kanalak?" - "Sintonizadoreak aurkitutako kanalak kenduko dira eta berriro bilatuko dira kanalak.\n\nEgiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." - "USB sintonizadoreak aurkitutako kanalak kenduko dira eta berriro bilatuko dira kanalak.\n\nEgiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nAntena analogiko bat badarabilzu, agian bere kokapena edo norabidea doitu beharko dituzu kanal gehienak eskuratzeko. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan." - - "Jarraitu" - "Utzi" - - "Hautatu konexio mota" - "Kanpoko antena badago sintonizadorera konektatuta, aukeratu Antena. Kableko zerbitzu-hornitzaile batek eskaintzen badizkizu kanalak, aukeratu Kablea. Ziur ez bazaude, mota bietakoak bilatuko dira, baina zertxobait luzatuko da." - - "Antena" - "Kablea" - "Ez dakit ziur" - "Garapenerako soilik" - - "Sintonizadorearen konfigurazioa" - "USB kanal-sintonizadorearen konfigurazioa" - "Zenbait minutu beharko dira" - "Sintonizadorea ez dago erabilgarri edo beste zerbait ari da grabatzen." - - %1$d kanal aurkitu dira - %1$d kanal aurkitu da - - "UTZI KANALAK BILATZEARI" - - %1$d kanal aurkitu dira - %1$d kanal aurkitu da - - - Primeran! %1$d kanal aurkitu da kanalak bilatzean. Oker gaudela uste baduzu, doitu antenaren posizioa eta saiatu berriro. - Primeran! %1$d kanal aurkitu da kanalak bilatzean. Oker gaudela uste baduzu, doitu antenaren posizioa eta saiatu berriro. - - - "Eginda" - "Bilatu berriro" - - "Ez da aurkitu kanalik" - "Bilaketak ez du aurkitu kanalik. Egiaztatu telebista-seinalearen iturburu batera konektatuta dagoela telebista.\n\nAntena analogiko bat badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." - "Bilaketak ez du aurkitu kanalik. Egiaztatu USB sintonizadorea entxufatuta eta telebistako seinale-iturburu batera konektatuta dagoela.\n\nAntena analogiko bat badarabilzu, doitu bere kokapena edo norabidea. Emaitzarik onenak lortzeko, ezarri toki altu batean edo leiho baten ondoan eta gauzatu bilaketa berriro." - - "Bilatu berriro" - "Eginda" - - "Bilatu telebista-kanalak" - "Sintonizadorearen konfigurazioa" - "USB bidezko sintonizadorearen konfigurazioa" - "USB bidezko sintonizadorea deskonektatu da." - diff --git a/usbtuner-res/values-fa/strings.xml b/usbtuner-res/values-fa/strings.xml index 8a51a4fc..586fc926 100644 --- a/usbtuner-res/values-fa/strings.xml +++ b/usbtuner-res/values-fa/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "تنظیم‌کننده تلویزیون" "‏تنظیم‌کننده تلویزیون USB" - "روشن" - "خاموش" + "تنظیم‌کننده تلویزیون شبکه (بتا)" "لطفاً تا پایان پردازش صبر کنید" - "منبع کانال را انتخاب کنید" - "بدون سیگنال" - "تنظیم به %s انجام نشد" - "تنظیم ناموفق بود" "نرم‌افزار تنظیم‌کننده اخیراً به‌روزرسانی شده است. لطفاً کانال‌ها را دوباره اسکن کنید." "برای فعال کردن صدا، صدای فراگیر را در تنظیمات صدای سیستم فعال کنید" + "صوت پخش نمی‌شود. لطفاً تلویزیون دیگری را امتحان کنید." "راه‌اندازی تنظیم‌کننده کانال" "راه‌اندازی تنظیم‌کننده تلویزیون" "‏راه‌اندازی تنظیم‌کننده کانال USB" + "راه‌اندازی تنظیم‌کننده شبکه‌ای" "مطمئن شوید تلویزیونتان به منبع سیگنال تلویزیونی متصل است.\n\nدر صورت استفاده از آنتن بی‌سیم شاید لازم باشد برای دریافت کانال‌های بیشتر، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید." "‏مطمئن شوید تنظیم‌کننده USB به منبع نیرو و منبع سیگنال تلویزیونی متصل است.\n\nدر صورت استفاده از آنتن بی‌سیم، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید." + "مطمئن شوید تنظیم‌کننده شبکه‌ای به برق و منبع سیگنال تلویزیونی وصل است.\n\nاگر از آنتن هوایی استفاده می‌کنید، ممکن است لازم باشد برای دریافت بیشترین تعداد کانال مکان و موقعیت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید." "ادامه" "فعلاً نه" @@ -40,6 +38,7 @@ "تنظیم کانال دوباره اجرا شود؟" "با این کار کانال‌های پیداشده از تنظیم‌کننده تلویزیون حذف می‌شوند و دوباره برای کانال‌های جدید اسکن می‌کند.\n\nمطمئن شوید تلویزیونتان به منبع سیگنال تلویزیونی متصل است.\n\nدر صورت استفاده از آنتن بی‌سیم، شاید لازم باشد برای دریافت کانال‌های بیشتر، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید." "‏این کار کانال‌های پیداشده از تنظیم‌کننده USB را حذف می‌کند و دوباره کانال‌های جدید را اسکن می‌کند.\n\nمطمئن شوید تنظیم‌کننده USB به منبع نیرو و منبع سیگنال تلویزیونی متصل است.\n\nدر صورت استفاده از آنتن بی‌سیم، برای دریافت کانال‌های بیشتر، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید و دوباره اسکن کنید." + "این کار کانال‌های پیداشده را از تنظیم‌کننده شبکه‌ای حذف می‌کند و دوباره کانال‌های جدید را اسکن می‌کند.\n\nمطمئن شوید تنظیم‌کننده شبکه‌ای به برق و منبع سیگنال تلویزیونی وصل است.\n\nاگر از آنتن هوایی استفاده می‌کنید، ممکن است لازم باشد برای دریافت بیشترین تعداد کانال مکان و موقعیت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید." "ادامه" "لغو" @@ -54,6 +53,7 @@ "راه‌اندازی تنظیم‌کننده تلویزیون" "‏راه‌اندازی تنظیم‌کننده کانال USB" + "راه‌اندازی تنظیم‌کننده کانال شبکه‌ای" "ممکن است چند دقیقه طول بکشد" "تنظیم‌کننده موقتاً دردسترس نیست یا در این لحظه در ضبط شدن استفاده می‌شود." @@ -76,12 +76,15 @@ "کانالی پیدا نشد" "اسکن هیچ کانالی پیدا نکرد. مطمئن شوید تلویزیونتان به یک منبع سیگنال تلویزیونی متصل است. \n\nدر صورت استفاده از آنتن بی‌سیم، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید و دوباره اسکن کنید." "‏اسکن هیچ کانالی پیدا نکرد. مطمئن شوید تنظیم‌کننده USB به منبع نیرو و منبع سیگنال تلویزیونی متصل است.\n\nدر صورت استفاده از آنتن بی‌سیم، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید و دوباره اسکن کنید." + "اسکن، کانالی پیدا نکرد. مطمئن شوید تنظیم‌کننده شبکه‌ای به برق و منبع سیگنال تلویزیونی وصل است.\n\nاگر از آنتن هوایی استفاده می‌کنید، موقعیت یا جهت آن را تنظیم کنید. برای بهترین نتایج، آن را در جای بلند و نزدیک پنجره قرار دهید و دوباره اسکن کنید." "اسکن دوباره" "تمام" - "اسکن کانال‌های تلویزیون" - "راه‌اندازی تنظیم‌کننده تلویزیون" - "‏راه‌اندازی تنظیم‌کننده تلویزیون USB" - "‏تیونر تلویزیون USB قطع شد." + "اسکن کانال‌های تلویزیون" + "راه‌اندازی تنظیم‌کننده تلویزیون" + "‏راه‌اندازی تنظیم‌کننده تلویزیون USB" + "راه‌اندازی تنظیم‌کننده تلویزیون شبکه‌ای" + "‏ارتباط تیونر USB تلویزیون قطع شد." + "ارتباط تیونر شبکه قطع شد." diff --git a/usbtuner-res/values-fi/strings.xml b/usbtuner-res/values-fi/strings.xml index 79d0c7f5..4dabc97c 100644 --- a/usbtuner-res/values-fi/strings.xml +++ b/usbtuner-res/values-fi/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV-viritin" "USB-TV-viritin" - "Ota käyttöön" - "Poista käytöstä" + "Antenni-TV-viritin (BETA)" "Odota, että käsittely on valmis." - "Valitse kanavalähde." - "Ei signaalia" - "Kanavan %s virittäminen epäonnistui." - "Virittäminen epäonnistui." "Viritinohjelmisto on päivitetty äskettäin. Hae kanavat uudelleen." "Ota ääni käyttöön kytkemällä tilaääni päälle järjestelmän ääniasetuksissa." + "Äänen toistaminen ei onnistu. Yritä käyttää toista TV:tä." "Kanavavirittimen määritys" "TV-virittimen määritys" "USB-kanavavirittimen määritys" + "Antennivirittimen määritys" "Tarkista, että TV on liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, useampien kanavien löytäminen saattaa edellyttää sen paikan tai suuntauksen säätämistä. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa." "Tarkista, että USB-viritin on kytketty oikein ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, useampien kanavien löytäminen saattaa edellyttää sen paikan ja suuntauksen säätämistä. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa." + "Tarkista, että viritin on päällä ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, säädä sen paikkaa tai suuntausta. Aseta antenni korkealle ja lähelle ikkunaa, jotta saat parhaat tulokset, ja tee uusi haku." "Jatka" "Ei nyt" @@ -40,6 +38,7 @@ "Haetaanko kanavia uudelleen?" "Tämä poistaa TV-virittimen löytämät kanavat ja tekee kanavahaun uudelleen.\n\nTarkista, että TV on liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, useampien kanavien löytäminen saattaa edellyttää sen paikan tai suuntauksen säätämistä. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa." "Tämä poistaa USB-virittimen löytämät kanavat ja tekee kanavahaun uudelleen.\n\nTarkista, että USB-viritin on kytketty oikein ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, useampien kanavien löytäminen saattaa edellyttää sen paikan tai suuntauksen säätämistä. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa." + "Tämä poistaa virittimen löytämät kanavat ja etsii kanavia uudestaan.\n\nTarkista, että viritin on päällä ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, säädä sen paikkaa tai suuntausta. Aseta antenni korkealle ja lähelle ikkunaa, jotta saat parhaat tulokset, ja tee uusi haku." "Jatka" "Peruuta" @@ -54,6 +53,7 @@ "TV-virittimen määritys" "USB-kanavavirittimen määritys" + "Antennikanavavirittimen määritys" "Tämä voi kestää useita minuutteja." "Viritin ei ole toistaiseksi käytettävissä tai se on nauhoitteen käytössä." @@ -76,12 +76,15 @@ "Kanavia ei löytynyt" "Haku ei löytänyt yhtään kanavaa. Tarkista, että TV on liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, säädä sen paikkaa tai suuntausta. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa. Tee haku sitten uudelleen." "Haku ei löytänyt yhtään kanavaa. Tarkista, että USB-viritin on kytketty oikein ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, säädä sen paikkaa tai suuntausta. Saat parhaan tuloksen asettamalla antennin korkealle ja lähelle ikkunaa. Tee haku sitten uudelleen." + "Haussa ei löytynyt kanavia. Tarkista, että viritin on päällä ja liitetty TV-signaalilähteeseen.\n\nJos käytät antennia, säädä sen paikkaa tai suuntausta. Aseta antenni korkealle ja lähelle ikkunaa, jotta saat parhaat tulokset, ja tee uusi haku." "Hae uudelleen" "Valmis" - "Hae TV-kanavia" - "TV-virittimen määritys" - "USB-TV-virittimen määritys" - "USB-TV-viritin ei ole kytketty" + "Hae TV-kanavia" + "TV-virittimen määritys" + "USB-TV-virittimen määritys" + "Antenni-TV-virittimen määritys" + "USB-TV-viritin ei ole kytketty." + "Verkkoviritin ei ole kytketty." diff --git a/usbtuner-res/values-fr-rCA/strings.xml b/usbtuner-res/values-fr-rCA/strings.xml index 54e8759e..754649ae 100644 --- a/usbtuner-res/values-fr-rCA/strings.xml +++ b/usbtuner-res/values-fr-rCA/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Syntoniseur télé" "Syntoniseur télé USB" - "Activer" - "Désactivé" + "Syntoniseur télé réseau (BÊTA)" "Veuillez patienter jusqu\'à la fin du traitement" - "Sélectionnez votre source de chaînes" - "Aucun signal" - "Impossible de syntoniser %s" - "Échec de syntonisation" "Le logiciel du syntoniseur a été mis à jour récemment. Veuillez rechercher les chaînes à nouveau." "Pour activer l\'audio, vous devez activer le son ambiophonique dans les paramètres sonores du système" + "Impossible de lire l\'audio. Veuillez essayer sur un autre téléviseur." "Configuration du syntoniseur de chaînes" "Configuration du syntoniseur télé" "Configurer les chaînes du syntoniseur USB" + "Configuration du syntoniseur réseau" "Vérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." "Vérifiez que le syntoniseur USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." + "Assurez-vous que le syntoniseur réseau est allumé et connecté à une source de signal télé.\n\nSi vous utilisez une antenne, il se peut que vous deviez ajuster sa position ou sa direction pour capter un maximum de chaînes. Pour obtenir des résultats optimaux, placez-la en hauteur et près d\'une fenêtre." "Continuer" "Pas maintenant" @@ -40,6 +38,7 @@ "Relancer la configuration de la chaîne?" "Cette opération permet de supprimer les chaînes détectées du syntoniseur télé et d\'en rechercher de nouvelles.\n\nVérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." "Cette opération permet de supprimer les chaînes détectées du syntoniseur USB et d\'en rechercher de nouvelles.\n\nVérifiez que le syntoniseur USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." + "Cela supprimera les chaînes trouvées du syntoniseur réseau et lancera une nouvelle recherche.\n\nAssurez-vous que le syntoniseur réseau est allumé et connecté à une source de signal télé.\n\nSi vous utilisez une antenne, il se peut que vous deviez ajuster sa position ou sa direction pour capter un maximum de chaînes. Pour obtenir des résultats optimaux, placez-la en hauteur et près d\'une fenêtre." "Continuer" "Annuler" @@ -54,6 +53,7 @@ "Configuration du syntoniseur télé" "Configurer les chaînes du syntoniseur USB" + "Configuration du syntoniseur de chaînes réseau" "Cela peut prendre plusieurs minutes" "Le syntoniseur n\'est pas accessible ou bien il est en cours d\'utilisation par l\'enregistreur." @@ -76,12 +76,15 @@ "Aucune chaîne" "La recherche n\'a détecté aucune chaîne. Vérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." "La recherche n\'a détecté aucune chaîne. Vérifiez que le syntoniseur USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." + "La recherche n\'a détecté aucune chaîne. Assurez-vous que le syntoniseur réseau est allumé et connecté à une source de signal télé.\n\nSi vous utilisez une antenne, ajustez sa position ou sa direction. Pour obtenir des résultats optimaux, placez-la en hauteur et près d\'une fenêtre, puis relancez la recherche." "Rechercher à nouveau" "Terminé" - "Rechercher les chaînes de télévision" - "Configuration du syntoniseur télé" - "Configurer le syntoniseur télé USB" - "Le syntoniseur télé USB est débranché." + "Rechercher les chaînes de télévision" + "Configuration du syntoniseur télé" + "Configurer le syntoniseur télé USB" + "Configuration du syntoniseur télé réseau" + "Le syntoniseur télé USB est déconnecté." + "Le syntoniseur réseau est déconnecté." diff --git a/usbtuner-res/values-fr/strings.xml b/usbtuner-res/values-fr/strings.xml index 4cb551f7..3b4a6589 100644 --- a/usbtuner-res/values-fr/strings.xml +++ b/usbtuner-res/values-fr/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tuner TV" "Tuner TV USB" - "Activé" - "Désactivé" + "Tuner TV réseau (VERSION BÊTA)" "Veuillez patienter jusqu\'à la fin du traitement." - "Sélectionnez la source de la chaîne." - "Aucun signal" - "Échec de sélection de la chaîne %s" - "Échec de la sélection de la chaîne" "Le logiciel du tuner a été mis à jour récemment. Veuillez lancer une nouvelle recherche des chaînes." "Activer le son surround dans les paramètres sonores du système pour activer l\'audio" + "Impossible de lire le fichier audio. Veuillez utiliser un autre téléviseur" "Configuration du tuner de chaînes" "Configuration du tuner TV" "Configuration du tuner de chaînes USB" + "Configuration du tuner réseau" "Vérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." "Vérifiez que le tuner USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." + "Vérifiez que le tuner réseau est allumé et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour de meilleurs résultats, placez-la en hauteur et près d\'une fenêtre." "Continuer" "Pas maintenant" @@ -40,6 +38,7 @@ "Relancer la configuration de la chaîne ?" "Cette opération permet de supprimer les chaînes détectées du tuner TV et d\'en rechercher de nouvelles.\n\nVérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." "Cette opération permet de supprimer les chaînes détectées du tuner USB et d\'en rechercher de nouvelles.\n\nVérifiez que le tuner USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre." + "Cette opération permet de supprimer les chaînes détectées du tuner réseau et d\'en rechercher de nouvelles.\n\nVérifiez que le tuner réseau est allumé et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, vous devrez peut-être ajuster sa position ou son orientation pour recevoir le plus de chaînes possible. Pour de meilleurs résultats, placez-la en hauteur et près d\'une fenêtre." "Continuer" "Annuler" @@ -54,6 +53,7 @@ "Configuration du tuner TV" "Configuration du tuner de chaînes USB" + "Configuration du tuner de chaînes réseau" "Cette opération peut prendre plusieurs minutes." "Le tuner est temporairement indisponible ou est déjà utilisé pour l\'enregistrement." @@ -76,12 +76,15 @@ "Aucune chaîne n\'a été détectée" "La recherche n\'a détecté aucune chaîne. Vérifiez que le téléviseur est connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." "La recherche n\'a détecté aucune chaîne. Vérifiez que le tuner USB est branché et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, ajustez sa position ou son orientation. Pour obtenir les meilleurs résultats, placez-la dans une position élevée et près d\'une fenêtre, puis relancez la recherche." + "La recherche n\'a détecté aucune chaîne. Vérifiez que le tuner réseau est allumé et connecté à la source d\'un signal de télévision.\n\nSi vous utilisez une antenne Over The Air, ajustez sa position ou son orientation. Pour de meilleurs résultats, placez-la en hauteur et près d\'une fenêtre, puis relancez la recherche." "Rechercher à nouveau" "OK" - "Rechercher les chaînes de télévision" - "Configuration du tuner TV" - "Configuration du tuner TV USB" - "Le tuner TV USB est déconnecté." + "Rechercher les chaînes de télévision" + "Configuration du tuner TV" + "Configuration du tuner TV USB" + "Configuration du tuner TV réseau" + "Le tuner TV USB est déconnecté." + "Tuner réseau déconnecté." diff --git a/usbtuner-res/values-gl-rES/strings.xml b/usbtuner-res/values-gl-rES/strings.xml new file mode 100644 index 00000000..4c75fc65 --- /dev/null +++ b/usbtuner-res/values-gl-rES/strings.xml @@ -0,0 +1,90 @@ + + + + + "Sintonizador de televisión" + "Sintonizador USB de televisión" + "Sintonizador de televisión de rede (beta)" + "Espera a que finalice o procesamento" + "O software do sintonizador actualizouse recentemente. Volve buscar canles." + "Activa o son envolvente na configuración de son do sistema para activar o audio" + "Non se pode reproducir o audio. Proba con outra televisión" + "Configuración do sintonizador de canles" + "Configuración do sintonizador de televisión" + "Configuración do sintonizador de canles USB" + "Configuración do sintonizador de rede" + "Comproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + "Comproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + "Verifica que o sintonizador de rede estea acendido e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + + "Continuar" + "Agora non" + + "Queres volver executar a configuración da canle?" + "Esta acción eliminará as canles que atopaches co sintonizador de televisión e buscará outras novas.\n\nComproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + "Esta acción eliminará as canles que atopaches co sintonizador USB e buscará outras novas.\n\nComproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + "Con esta acción quitaranse as canles atopadas do teu sintonizador de rede e buscaranse novas canles outra vez.\n\nVerifica que o sintonizador de rede estea acendido e conectado a unha fonte de sinal de televisión.\n\n}Se usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." + + "Continuar" + "Cancelar" + + "Seleccionar tipo de conexión" + "Selecciona Antena se hai unha antena externa conectada ao sintonizador. Selecciona Cable se as túas canles proceden dun fornecedor de servizo de cable. Se non o sabes con seguridade, realizarase a busca usando os dous tipos de conexión, pero este proceso pode tardar máis tempo." + + "Antena" + "Cable" + "Non estou seguro" + "Só desenvolvemento" + + "Configuración do sintonizador de televisión" + "Configuración do sintonizador de canles USB" + "Configuración do sintonizador de canles de rede" + "Esta acción pode tardar varios minutos" + "O sintonizador non está dispoñible temporalmente ou xa se utiliza para unha gravación." + + Atopáronse %1$d canles + Atopouse %1$d canle + + "DETER BUSCA DE CANLES" + + Atopáronse %1$d canles + Atopouse %1$d canle + + + Estupendo! Atopáronse %1$d canles durante a busca de canles. Se non che parece correcto, tenta axustar a posición da antena e realiza a busca de novo. + Estupendo! Atopouse %1$d canle durante a busca de canles. Se non che parece correcto, tenta axustar a posición da antena e realiza a busca de novo. + + + "Feito" + "Buscar de novo" + + "Non se atopou ningunha canle" + "Durante a busca non se atopou ningunha canle. Comproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, axusta a súa posición ou dirección. Para conseguir os mellores resultados, colócaa nun lugar alto, preto dunha ventá e realiza a fai a busca de novo." + "A busca non atopou ningunha canle. Comproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, axusta a súa posición ou dirección. Para conseguir os mellores resultados, colócaa nun lugar alto, preto dunha ventá e realiza a busca de novo." + "A busca de canles non obtivo resultados. Verifica que o sintonizador de rede estea acendido e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, axusta a súa posición ou dirección. Para conseguir os mellores resultados, colócaa nun lugar alto, preto dunha ventá, e realiza a busca de novo." + + "Buscar de novo" + "Feito" + + "Buscar canles de televisión" + "Configuración do sintonizador de televisión" + "Configuración do sintonizador USB de televisión" + "Configuración do sintonizador de televisión de rede" + "Desconectouse o sintonizador de televisión USB." + "Desconectouse o sintonizador de rede." + diff --git a/usbtuner-res/values-gl/strings.xml b/usbtuner-res/values-gl/strings.xml deleted file mode 100644 index 58c14605..00000000 --- a/usbtuner-res/values-gl/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Sintonizador de televisión" - "Sintonizador USB de televisión" - "Activar" - "Desactivar" - "Espera a que finalice o procesamento" - "Selecciona a fonte da túa canle" - "Sen sinal" - "Produciuse un erro ao sintonizar %s" - "Produciuse un erro ao sintonizar" - "O software do sintonizador actualizouse recentemente. Volve buscar canles." - "Activa o son envolvente na configuración de son do sistema para activar o audio" - "Configuración do sintonizador de canles" - "Configuración do sintonizador de televisión" - "Configuración do sintonizador de canles USB" - "Comproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." - "Comproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." - - "Continuar" - "Agora non" - - "Queres volver executar a configuración da canle?" - "Esta acción eliminará as canles que atopaches co sintonizador de televisión e buscará outras novas.\n\nComproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." - "Esta acción eliminará as canles que atopaches co sintonizador USB e buscará outras novas.\n\nComproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, é posible que teñas que axustar a súa posición ou dirección para recibir a maioría das canles. Para conseguir os mellores resultados, colócaa nun lugar alto e preto dunha ventá." - - "Continuar" - "Cancelar" - - "Seleccionar tipo de conexión" - "Selecciona Antena se hai unha antena externa conectada ao sintonizador. Selecciona Cable se as túas canles proceden dun fornecedor de servizo de cable. Se non o sabes con seguridade, realizarase a busca usando os dous tipos de conexión, pero este proceso pode tardar máis tempo." - - "Antena" - "Cable" - "Non estou seguro" - "Só desenvolvemento" - - "Configuración do sintonizador de televisión" - "Configuración do sintonizador de canles USB" - "Esta acción pode tardar varios minutos" - "O sintonizador non está dispoñible temporalmente ou xa se utiliza para unha gravación." - - Atopáronse %1$d canles - Atopouse %1$d canle - - "DETER BUSCA DE CANLES" - - Atopáronse %1$d canles - Atopouse %1$d canle - - - Estupendo! Atopáronse %1$d canles durante a busca de canles. Se non che parece correcto, tenta axustar a posición da antena e realiza a busca de novo. - Estupendo! Atopouse %1$d canle durante a busca de canles. Se non che parece correcto, tenta axustar a posición da antena e realiza a busca de novo. - - - "Feito" - "Buscar de novo" - - "Non se atopou ningunha canle" - "Durante a busca non se atopou ningunha canle. Comproba que a televisión está conectada a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, axusta a súa posición ou dirección. Para conseguir os mellores resultados, colócaa nun lugar alto, preto dunha ventá e realiza a fai a busca de novo." - "A busca non atopou ningunha canle. Comproba que o sintonizador USB está enchufado e conectado a unha fonte de sinal de televisión.\n\nSe usas unha antena sen fíos, axusta a súa posición ou dirección. Para conseguir os mellores resultados, colócaa nun lugar alto, preto dunha ventá e realiza a busca de novo." - - "Buscar de novo" - "Feito" - - "Busca canles de televisión" - "Configuración do sintonizador de televisión" - "Configuración do sintonizador USB de televisión" - "Desconectouse o sintonizador de televisión USB." - diff --git a/usbtuner-res/values-hi/strings.xml b/usbtuner-res/values-hi/strings.xml index ea0d51c1..265670e7 100644 --- a/usbtuner-res/values-hi/strings.xml +++ b/usbtuner-res/values-hi/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "टीवी ट्यूनर" "USB टीवी ट्यूनर" - "चालू करें" - "बंद करें" + "नेटवर्क टीवी ट्यूनर (बीटा)" "कृपया प्रक्रिया पूरी होने का इंतज़ार करें" - "अपना चैनल स्रोत चुनें" - "कोई सिग्नल नहीं" - "%s को ट्यून करने में विफल रहा" - "ट्यून करने में विफल रहा" "ट्यूनर सॉफ़्टवेयर को हाल ही में अपडेट किया गया है. कृपया चैनलों के लिए दोबारा स्कैन करें." "ऑडियो सक्षम करने के लिए सिस्टम साउंड सेटिंग में सराउंड साउंड सक्षम करें" + "ऑडियो नहीं चल पा रहा है. कृपया कोई दूसरा टीवी आज़माएं" "चैनल ट्यूनर सेटअप" "टीवी ट्यूनर सेटअप" "USB चैनल ट्यूनर सेटअप" + "नेटवर्क ट्यूनर सेटअप" "पुष्टि करें कि आपका टीवी किसी टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल पाने के लिए आपको उसकी स्थिति या दिशा समायोजित करने की आवश्यकता हो सकती है. सबसे अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं." "पुष्टि करें कि USB ट्यूनर प्लग इन है और टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि आप ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल पाने के लिए आपको उसकी स्थिति या दिशा समायोजित करने की आवश्यकता हो सकती है. अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं." + "सत्यापित करें कि नेटवर्क ट्यूनर चालू है और किसी टीवी सिग्नल स्रोत से कनेक्ट है.\n\nयदि किसी ओवर-द-एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल प्राप्त करने के लिए आपको एंटेना का स्थान या दिशा समायोजित करनी पड़ सकती है. सर्वश्रेष्ठ परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास रखें." "जारी रखें" "अभी नहीं" @@ -40,6 +38,7 @@ "चैनल सेटअप फिर से चलाएं?" "इससे टीवी ट्यूनर से मिले चैनल निकल जाएंगे और नए चैनल दोबारा स्कैन किए जाएंगे.\n\nपुष्टि करें कि आपका टीवी किसी टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल पाने के लिए आपको उसकी स्थिति या दिशा समायोजित करने की आवश्यकता हो सकती है. सबसे अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं." "इससे USB ट्यूनर से मिले चैनल निकल जाएंगे और नए चैनलों के लिए फिर से स्कैन किया जाएगा.\n\nपुष्टि करें कि USB ट्यूनर प्लग इन है और टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि आप ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल पाने के लिए आपको उसकी स्थिति या दिशा समायोजित करने की आवश्यकता हो सकती है. अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं." + "ऐसा करने से नेटवर्क ट्यूनर से मिले चैनल निकाल दिए जाएंगे और नए चैनल के लिए फिर से स्कैन किया जाएगा.\n\nसत्यापित करें कि नेटवर्क ट्यूनर चालू है और किसी टीवी सिग्नल स्रोत से कनेक्ट है.\n\nयदि किसी ओवर-द-एयर एंटेना का उपयोग कर रहे हैं, तो अधिकांश चैनल प्राप्त करने के लिए आपको एंटेना का स्थान या दिशा समायोजित करनी पड़ सकती है. सर्वश्रेष्ठ परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास रखें." "जारी रखें" "रद्द करें" @@ -54,8 +53,9 @@ "टीवी ट्यूनर सेटअप" "USB चैनल ट्यूनर सेटअप" + "नेटवर्क चैनल ट्यूनर सेटअप" "इसमें कुछ मिनट लग सकते हैं" - "ट्यूनर अस्थायी रूप से उपलब्ध नहीं है या रिकॉर्डिंग में उसका उपयोग पहले ही कर लिया गया है." + "ट्यूनर अस्थायी रूप से उपलब्ध नहीं है या रिकॉर्डिंग में उसका उपयोग पहले ही हो रहा है." %1$d चैनल मिले %1$d चैनल मिले @@ -76,12 +76,15 @@ "कोई चैनल नहीं मिला" "स्कैन करने से कोई भी चैनल नहीं मिला. पुष्टि करें कि आपका टीवी किसी टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो उसकी स्थिति या दिशा समायोजित करें. सबसे अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं और दोबारा स्कैन करें." "स्कैन करने से कोई चैनल नहीं मिला. पुष्टि करें कि USB ट्यूनर प्लग इन है और टीवी सिग्नल स्रोत से कनेक्ट किया हुआ है.\n\nयदि आप ओवर द एयर एंटेना का उपयोग कर रहे हैं, तो उसकी स्थिति या दिशा समायोजित करें. अच्छे परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास लगाएं और दोबारा स्कैन करें." + "स्कैन में कोई चैनल नहीं मिला. सत्यापित करें कि नेटवर्क ट्यूनर चालू है और किसी टीवी संकेत स्रोत से कनेक्ट है.\n\nयदि किसी ओवर-द-एयर एंटेना का उपयोग कर रहे हैं, तो उसका स्थान या दिशा समायोजित करें. सर्वश्रेष्ठ परिणामों के लिए, उसे ऊंचाई पर और किसी खिड़की के पास रखें और फिर से स्कैन करें." "दोबारा स्‍कैन करें" "हो गया" - "टीवी चैनलों के लिए स्‍कैन करें" - "टीवी ट्यूनर सेटअप" - "USB टीवी ट्यूनर सेटअप" - "USB टीवी ट्यूनर डिस्कनेक्ट किया गया." + "टीवी चैनलों के लिए स्‍कैन करें" + "टीवी ट्यूनर सेटअप" + "USB टीवी ट्यूनर सेटअप" + "नेटवर्क टीवी ट्यूनर सेटअप" + "USB टीवी ट्यूनर डिसकनेक्ट किया गया." + "नेटवर्क ट्यूनर डिसकनेक्ट किया गया." diff --git a/usbtuner-res/values-hr/strings.xml b/usbtuner-res/values-hr/strings.xml index 4a00deaf..42f5c3aa 100644 --- a/usbtuner-res/values-hr/strings.xml +++ b/usbtuner-res/values-hr/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV prijemnik" "USB TV prijemnik" - "Uključeno" - "Isključeno" + "Mrežni TV prijemnik (BETA)" "Pričekajte da obrada završi" - "Odaberite izvor kanala" - "Nema signala" - "Namještanje na kanal %s nije uspjelo" - "Namještanje nije uspjelo" "Softver prijemnika nedavno je ažuriran. Ponovite traženje kanala." "Omogućite okružujući zvuk u postavkama zvuka na razini sustava da biste omogućili audio" + "Zvuk se ne može reproducirati. Pokušajte s drugim televizorom" "Postavljanje prijemnika kanala" "Postavljanje TV prijemnika" "Postavljanje USB prijemnika za kanale" + "Postavljanje mrežnog prijemnika" "Provjerite je li televizor povezan s izvorom televizijskog signala.\n\nAko upotrebljavate antenu zemaljske televizije, možda ćete joj morati promijeniti položaj ili smjer da biste pronašli najviše kanala. Za najbolje rezultate postavite je visoko i u blizini prozora." "Provjerite je li USB prijemnik priključen i povezan s izvorom TV signala.\n\nAko upotrebljavate antenu zemaljske televizije, možda trebate prilagoditi njezin položaj ili smjer da biste primali najviše kanala. Za najbolje rezultate postavite je visoko i u blizini prozora." + "Provjerite je li mrežni prijemnik uključen i povezan s izvorom TV signala.\n\nAko upotrebljavate bežičnu antenu, možda ćete trebati prilagoditi položaj ili smjer da biste pronašli najviše kanala. Za najbolje rezultate postavite je visoko i blizu prozora." "Nastavi" "Ne sada" @@ -40,6 +38,7 @@ "Želite li ponovo pokrenuti postavljanje kanala?" "Time će se ukloniti kanali pronađeni pomoću TV prijemnika i ponovo pokrenuti pretraživanje kanala.\n\nProvjerite je li televizor povezan s izvorom televizijskog signala.\n\nAko upotrebljavate antenu zemaljske televizije, možda ćete joj morati promijeniti položaj ili smjer da biste pronašli najviše kanala. Za najbolje rezultate postavite je visoko i u blizini prozora." "Time će se ukloniti kanali pronađeni putem USB prijemnika i ponoviti pretraživanje kanala.\n\nProvjerite je li USB prijemnik priključen i povezan s izvorom televizijskog signala.\n\nAko upotrebljavate antenu zemaljske televizije, možda trebate prilagoditi njezin položaj ili smjer da biste primali najviše kanala. Za najbolje rezultate postavite je visoko i u blizini prozora." + "Time će se ukloniti kanali pronađeni mrežnim prijemnikom i pokrenuti pretraživanje novih kanala.\n\nProvjerite je li mrežni prijemnik uključen i povezan s izvorom TV signala.\n\nAko upotrebljavate bežičnu antenu, možda ćete trebati prilagoditi položaj ili smjer da biste pronašli najviše kanala. Za najbolje rezultate postavite je visoko i blizu prozora." "Nastavi" "Odustani" @@ -54,6 +53,7 @@ "Postavljanje TV prijemnika" "Postavljanje USB prijemnika za kanale" + "Postavljanje prijemnika za mrežne kanale" "To može potrajati nekoliko minuta" "Prijemnik trenutačno nije dostupan ili se već upotrebljava za snimanje." @@ -79,12 +79,15 @@ "Nije pronađen nijedan kanal" "Tijekom pretraživanja nije pronađen nijedan kanal. Provjerite je li televizor povezan s izvorom televizijskog signala.\n\nAko upotrebljavate antenu zemaljske televizije, promijenite joj položaj ili smjer. Za najbolje rezultate postavite je visoko i u blizini prozora, a zatim pretražite ponovo." "Pretraživanjem nije pronađen nijedan kanal. Provjerite je li USB prijemnik priključen i povezan s izvorom televizijskog signala.\n\nAko upotrebljavate antenu zemaljske televizije, prilagodite joj položaj ili smjer. Za najbolje rezultate postavite je visoko i u blizini prozora i pretražite ponovo." + "Nije pronađen nijedan kanal. Provjerite je li mrežni prijemnik uključen i povezan s izvorom TV signala.\n\nAko upotrebljavate bežičnu antenu, prilagodite položaj ili smjer. Za najbolje rezultate postavite je visoko i blizu prozora i pretražite ponovo." "Pretraži ponovo" "Gotovo" - "Pretražite TV kanale" - "Postavljanje TV prijemnika" - "Postavljanje USB TV prijemnika" - "USB TV prijemnik isključen." + "Pretražite TV kanale" + "Postavljanje TV prijemnika" + "Postavljanje USB TV prijemnika" + "Postavljanje mrežnog TV prijemnika" + "USB TV prijemnik isključen." + "Mrežni prijemnik isključen." diff --git a/usbtuner-res/values-hu/strings.xml b/usbtuner-res/values-hu/strings.xml index e2bca51c..0d83fe31 100644 --- a/usbtuner-res/values-hu/strings.xml +++ b/usbtuner-res/values-hu/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tévétuner" "USB-s tévétuner" - "Be" - "Ki" + "Hálózati tévétuner (BÉTA)" "Kérjük, várja meg a folyamat befejezését" - "Válassza ki a csatornaforrást" - "Nincs jel" - "Nem sikerült behangolni a(z) %s csatornát" - "Nem sikerült a hangolás" "A tuner szoftverét nemrég frissítették. Kérjük, ismételje meg a csatornakeresést." "A hang aktiválásához engedélyezze a térhatású hangot a rendszerszintű hangbeállításokban" + "A hangot nem lehet lejátszani. Kérjük, próbálkozzon másik tévén" "Csatornatuner beállítása" "Tévétuner beállítása" "USB-s csatornatuner beállítása" + "Hálózati tuner beállítása" "Ellenőrizze, hogy tévéje csatlakoztatva van-e a televíziós jelforráshoz.\n\nAntenna használata esetén szükség lehet az elhelyezés, illetve az irány módosítására a lehető legtöbb csatorna befogásához. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." "Győződjön meg arról, hogy az USB-tuner be van dugva, és csatlakoztatva van a televíziós jelforráshoz.\n\nAntenna használata esetén szükség lehet az elhelyezés, illetve irány módosítására a lehető legtöbb csatorna befogásához. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." + "Ellenőrizze, hogy a hálózati tuner be van-e kapcsolva, és csatlakozik-e televíziós jelforráshoz.\n\nHa antennát használ, módosítsa az elhelyezkedését, illetve irányát, hogy minél több csatornát fogjon. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." "Folytatás" "Most nem" @@ -40,6 +38,7 @@ "Újra végrehajtja a csatornabeállítást?" "Ezzel eltávolítja a tévétuner segítségével megtalált csatornákat, és új csatornakeresést indít.\n\nEllenőrizze, hogy tévéje csatlakoztatva van-e a televíziós jelforráshoz.\n\nAntenna használata esetén szükség lehet az elhelyezés, illetve az irány módosítására a lehető legtöbb csatorna befogásához. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." "Ezzel eltávolítja a már megtalált csatornákat az USB-tunerről, és újból elvégzi a csatornakeresést.\n\nGyőződjön meg arról, hogy az USB-tuner be van dugva, és csatlakoztatva van a televíziós jelforráshoz.\n\nAntenna használata esetén szükség lehet az elhelyezés, illetve irány módosítására a lehető legtöbb csatorna befogásához. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." + "Ezzel eltávolítja a hálózati tuner által talált csatornákat, és újabb csatornakeresést indít el.\n\nEllenőrizze, hogy a hálózati tuner be van-e kapcsolva, és csatlakozik-e televíziós jelforráshoz.\n\nHa antennát használ, módosítsa az elhelyezkedését, illetve irányát, hogy minél több csatornát fogjon. A legjobb eredmény érdekében helyezze magasra és ablak közelébe." "Folytatás" "Mégse" @@ -54,6 +53,7 @@ "Tévétuner beállítása" "USB-s csatornatuner beállítása" + "Hálózati csatornatuner beállítása" "Ez néhány percet is igénybe vehet" "A tuner átmenetileg nem áll rendelkezésre, vagy már fel lett használva a felvétel során." @@ -76,12 +76,15 @@ "Nem található csatorna" "A keresés nem talált csatornát. Ellenőrizze, hogy tévéje csatlakoztatva van-e a televíziós jelforráshoz.\n\nAntenna használata esetén módosítsa annak elhelyezését, illetve irányát. A legjobb eredmény érdekében helyezze magasra és ablak közelébe, majd ismételje meg a keresést." "A keresés nem talált csatornát. Győződjön meg arról, hogy az USB-tuner be van dugva, és csatlakoztatva van a televíziós jelforráshoz.\n\nHa antennát használ, állítson annak helyzetén, illetve irányán. A legjobb eredmény érdekében helyezze magasra és ablak közelébe, majd ismételje meg a keresést." + "A rendszer nem talált egyetlen csatornát sem. Ellenőrizze, hogy a hálózati tuner be van-e kapcsolva, és csatlakozik-e televíziós jelforráshoz.\n\nHa antennát használ, módosítsa az elhelyezkedését, illetve irányát. A legjobb eredmény érdekében helyezze magasra és ablak közelébe, majd ismételje meg a keresést." "Keresés újra" "Kész" - "Tévécsatornák keresése" - "Tévétuner beállítása" - "USB-s tévétuner beállítása" - "Megszakadt a kapcsolat az USB-s tévétunerrel." + "Tévécsatornák keresése" + "Tévétuner beállítása" + "USB-s tévétuner beállítása" + "Hálózati tévétuner beállítása" + "Megszakadt a kapcsolat az USB-s tévétunerrel." + "Megszakadt a kapcsolat a hálózati tunerrel." diff --git a/usbtuner-res/values-hy-rAM/strings.xml b/usbtuner-res/values-hy-rAM/strings.xml new file mode 100644 index 00000000..2d3080c1 --- /dev/null +++ b/usbtuner-res/values-hy-rAM/strings.xml @@ -0,0 +1,90 @@ + + + + + "Հեռուստակարգավորիչ" + "USB հեռուստակարգավորիչ" + "Ցանցային հեռուստաընդունիչ (ԲԵՏԱ)" + "Սպասեք՝ մինչ որոնումը ավարտվի" + "Ընդունիչի ծրագրակազմը վերջերս թարմացվել է: Նորից որոնեք ալիքները:" + "Ձայնը միացնելու համար համակարգի ձայնի կարգավորումներում ակտիվացրեք ծավալային ձայնը" + "Հնարավոր չէ վերարտադրել ձայնը: Փորձեք մեկ այլ հեռուստացույց:" + "Ալիքների կարգավորիչի տեղադրում" + "Հեռուստակարգավորիչի տեղադրում" + "Ալիքների USB կարգավորիչի տեղադրում" + "Ցանցային ընդունիչի կարգավորում" + "Համոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + "Համոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + "Համոզվեք, որ ցանցային ընդունիրչը միացված է և կապակցված հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + + "Շարունակել" + "Ոչ հիմա" + + "Կրկի՞ն կարգավորել ալիքները:" + "Այս գործողության արդյունքում կհեռացվեն հեռուստակարգավորիչից ստացված ալիքները և կկատարվի ալիքների նոր որոնում:\n\nՀամոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + "Այս գործողության արդյունքում կհեռացվեն USB կարգավորիչից ստացված ալիքները և կկատարվի ալիքների նոր որոնում:\n\nՀամոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + "Այս գործողության արդյունքում կհեռացվեն ցանցային ընդունիչից ստացված ալիքները և կկատարվի ալիքների նոր որոնում:\n\nՀամոզվեք, որ ցանցային ընդունիչը միացված է և կապակցված հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" + + "Շարունակել" + "Չեղարկել" + + "Ընտրեք միացման տեսակը" + "Եթե ընդունիչին միացված է արտաքին ալեհավաք, ապա ընտրեք Ալեհավաքը: Եթե ալիքների ազդանշանը ստանում եք մալուխային հեռուստաընկերությունից, ապա ընտրեք Մալուխը: Եթե համոզված չեք, ապա կարող եք որոնել երկու տեսակն էլ, սակայն դա ավելի երկար կտևի:" + + "Ալեհավաք" + "Մալուխ" + "Չգիտեմ" + "Միայն մշակման" + + "Հեռուստակարգավորիչի տեղադրում" + "Ալիքների USB կարգավորիչի տեղադրում" + "Ցանցային ալիքների ընդունիչի տեղադրում" + "Դա կարող է տևել մի քանի րոպե" + "Ընդունիչը ժամանակավորապես անհասանելի է կամ արդեն օգտագործվում է տեսագրելու համար:" + + %1$d channels found + Գտնվել է %1$d ալիք + + "ԴԱԴԱՐԵՑՆԵԼ ԱԼԻՔՆԵՐԻ ՈՐՈՆՈՒՄԸ" + + %1$d channels found + Գտնվել է %1$d ալիք + + + Nice! %1$d channels were found during the channel scan. If this doesn’t seem right, try adjusting the antenna position and scan again. + Գերազանց է: Ալիքների որոնման արդյունքում գտնվել է %1$d ալիք: Եթե դա բավարար չէ, փորձեք փոխել ալեհավաքի դիրքը և որոնել նորից: + + + "Պատրաստ է" + "Կրկին որոնել" + + "Ալիքներ չեն գտնվել" + "Որոնման արդյունքում ալիքներ չեն գտնվել: Համոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս կարգավորեք դրա դիրքն ու ուղղությունը: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ, ապա որոնեք նորից:" + "Որոնման արդյունքում ալիքներ չեն գտնվել: Համոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս կարգավորեք դրա դիրքն ու ուղղությունը: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ, ապա որոնեք նորից:" + "Որոնման արդյունքում ալիքներ չեն գտնվել: Համոզվեք, որ ցանցային ընդունիչը միացված է և կապակցված հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս կարգավորեք դրա դիրքն ու ուղղությունը: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ, ապա որոնեք նորից:" + + "Կրկին որոնել" + "Պատրաստ է" + + "TV ալիքների որոնում" + "TV ընդունիչի տեղադրում" + "USB TV ընդունիչի տեղադրում" + "Ցանցային TV ընդունիչի տեղադրում" + "USB հեռուստաընդունիչն անջատված է:" + "Ցանցային ընդունիչն անջատված է։" + diff --git a/usbtuner-res/values-hy/strings.xml b/usbtuner-res/values-hy/strings.xml deleted file mode 100644 index a8e33eac..00000000 --- a/usbtuner-res/values-hy/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Հեռուստակարգավորիչ" - "USB հեռուստակարգավորիչ" - "Միացնել" - "Անջատել" - "Սպասեք՝ մինչ որոնումը ավարտվի" - "Ընտրեք ալիքի աղբյուրը" - "Ազդանշան չկա" - "Չհաջողվեց անցնել %s ալիքին" - "Չհաջողվեց անցնել ալիքին" - "Ընդունիչի ծրագրակազմը վերջերս թարմացվել է: Նորից որոնեք ալիքները:" - "Ձայնը միացնելու համար համակարգի ձայնի կարգավորումներում ակտիվացրեք ծավալային ձայնը" - "Ալիքների կարգավորիչի տեղադրում" - "Հեռուստակարգավորիչի տեղադրում" - "Ալիքների USB կարգավորիչի տեղադրում" - "Համոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" - "Համոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" - - "Շարունակել" - "Ոչ հիմա" - - "Կրկի՞ն կարգավորել ալիքները:" - "Այս գործողության արդյունքում կհեռացվեն հեռուստակարգավորիչից ստացված ալիքները և կկատարվի ալիքների նոր որոնում:\n\nՀամոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" - "Այս գործողության արդյունքում կհեռացվեն USB կարգավորիչից ստացված ալիքները և կկատարվի ալիքների նոր որոնում:\n\nՀամոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս հնարավոր է՝ անհրաժեշտ լինի կարգավորել դրա դիրքն ու ուղղությունը՝ ավելի շատ ալիքներ գտնելու համար: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ:" - - "Շարունակել" - "Չեղարկել" - - "Ընտրեք միացման տեսակը" - "Եթե ընդունիչին միացված է արտաքին ալեհավաք, ապա ընտրեք Ալեհավաքը: Եթե ալիքների ազդանշանը ստանում եք մալուխային հեռուստաընկերությունից, ապա ընտրեք Մալուխը: Եթե համոզված չեք, ապա կարող եք որոնել երկու տեսակն էլ, սակայն դա ավելի երկար կտևի:" - - "Ալեհավաք" - "Մալուխ" - "Չգիտեմ" - "Միայն մշակման" - - "Հեռուստակարգավորիչի տեղադրում" - "Ալիքների USB կարգավորիչի տեղադրում" - "Դա կարող է տևել մի քանի րոպե" - "Ընդունիչը ժամանակավորապես անհասանելի է կամ արդեն օգտագործվում է տեսագրելու համար:" - - %1$d channels found - Գտնվել է %1$d ալիք - - "ԴԱԴԱՐԵՑՆԵԼ ԱԼԻՔՆԵՐԻ ՈՐՈՆՈՒՄԸ" - - %1$d channels found - Գտնվել է %1$d ալիք - - - Nice! %1$d channels were found during the channel scan. If this doesn’t seem right, try adjusting the antenna position and scan again. - Գերազանց է: Ալիքների որոնման արդյունքում գտնվել է %1$d ալիք: Եթե դա բավարար չէ, փորձեք փոխել ալեհավաքի դիրքը և որոնել նորից: - - - "Պատրաստ է" - "Կրկին որոնել" - - "Ալիքներ չեն գտնվել" - "Որոնման արդյունքում ալիքներ չեն գտնվել: Համոզվեք, որ հեռուստացույցը միացված է հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս կարգավորեք դրա դիրքն ու ուղղությունը: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ, ապա որոնեք նորից:" - "Որոնման արդյունքում ալիքներ չեն գտնվել: Համոզվեք, որ USB կարգավորիչը միացված է ցանցին և հեռուստատեսային ազդանշանի աղբյուրին:\n\nԵթերային ալեհավաք օգտագործելիս կարգավորեք դրա դիրքն ու ուղղությունը: Լավագույն արդյունքներն ապահովելու համար այն դրեք բարձր տեղում՝ պատուհանի մոտ, ապա որոնեք նորից:" - - "Կրկին որոնել" - "Պատրաստ է" - - "Հեռուստաալիքների որոնում" - "Հեռուստակարգավորիչի տեղադրում" - "USB հեռուստակարգավորիչի տեղադրում" - "USB հեռուստակարգավորիչն անջատված է:" - diff --git a/usbtuner-res/values-in/strings.xml b/usbtuner-res/values-in/strings.xml index e534c9c6..8f3aa4d1 100644 --- a/usbtuner-res/values-in/strings.xml +++ b/usbtuner-res/values-in/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tuner TV" "Tuner TV USB" - "Aktif" - "Nonaktif" + "Tuner TV Jaringan (BETA)" "Harap tunggu sampai pemrosesan selesai" - "Pilih sumber saluran Anda" - "Tidak Ada Sinyal" - "Gagal menyetel ke %s" - "Gagal menyetel" - "Perangkat lunak tuner ini baru saja diperbarui. Pindai ulang saluran Anda." + "Software tuner ini baru saja diperbarui. Pindai ulang saluran Anda." "Aktifkan suara surround di setelan suara sistem untuk mengaktifkan audio" + "Tidak dapat memutar audio. Coba TV lainnya" "Penyiapan penyetel saluran" "Penyiapan Tuner TV" "Penyiapan tuner saluran USB" + "Penyiapan tuner jaringan" "Pastikan TV terhubung ke sumber sinyal TV.\n\nJika menggunakan antena udara, Anda mungkin perlu menyesuaikan tempat atau arahnya untuk menerima sebagian besar saluran. Untuk hasil terbaik, tempatkan di lokasi yang tinggi dan dekat dengan jendela." "Pastikan tuner USB dicolokkan dan tersambung ke sumber sinyal TV.\n\nJika menggunakan antena udara, Anda mungkin perlu menyesuaikan tempat atau arahnya untuk menerima jumlah saluran paling banyak. Untuk hasil terbaik, letakkan di tempat yang tinggi dan dekat jendela." + "Pastikan tuner jaringan aktif dan terhubung ke sumber sinyal TV.\n\nJika menggunakan antena udara, Anda mungkin perlu menyesuaikan tempat dan arahnya agar dapat menerima sebagian besar saluran. Untuk hasil terbaik, tempatkan antena di lokasi yang tinggi dan dekat jendela." "Lanjutkan" "Jangan sekarang" @@ -40,6 +38,7 @@ "Jalankan lagi penyiapan saluran?" "Tindakan ini akan menghapus saluran yang ditemukan dari tuner TV dan memindai saluran baru lagi.\n\nPastikan TV terhubung ke sumber sinyal TV.\n\nJika menggunakan antena udara, mungkin Anda perlu menyesuaikan tempat atau arahnya untuk menerima sebagian besar saluran. Untuk hasil terbaik, tempatkan di lokasi yang tinggi dan dekat dengan jendela." "Tindakan ini akan menghapus saluran yang ditemukan dari tuner USB dan memindai saluran baru lagi.\n\nPastikan tuner USB dicolokkan dan tersambung ke sumber sinyal TV.\n\nJika menggunakan antena udara, Anda mungkin perlu menyesuaikan tempat atau arahnya untuk menerima jumlah saluran paling banyak. Untuk hasil terbaik, letakkan di tempat yang tinggi dan dekat jendela." + "Hal ini akan menghapus saluran yang ditemukan dari tuner jaringan dan memindai saluran baru lagi.\n\nPastikan tuner jaringan aktif dan terhubung ke sumber sinyal TV.\n\nJika menggunakan antena udara, Anda mungkin perlu menyesuaikan penempatan atau arahnya agar dapat menerima sebagian besar saluran. Untuk hasil terbaik, tempatkan antena di lokasi yang tinggi dan dekat dengan jendela." "Lanjutkan" "Batal" @@ -54,6 +53,7 @@ "Penyiapan tuner TV" "Penyiapan tuner saluran USB" + "Penyiapan tuner saluran jaringan" "Proses ini dapat memakan waktu beberapa menit" "Tuner sementara tidak tersedia atau sudah digunakan oleh rekaman." @@ -76,12 +76,15 @@ "Tidak ditemukan Saluran" "Pemindaian tidak menemukan saluran apa pun. Pastikan TV terhubung ke sumber sinyal TV.\n\nJika menggunakan antena udara, sesuaikan tempat atau arahnya. Untuk hasil terbaik, tempatkan di lokasi yang tinggi dan dekat dengan jendela kemudian pindai lagi." "Pemindaian tidak menemukan saluran apa pun. Pastikan tuner USB dicolokkan dan tersambung ke sumber sinyal TV.\n\nJika menggunakan antena udara, sesuaikan tempat atau arahnya. Untuk hasil terbaik, letakkan di tempat yang tinggi dan dekat jendela lalu pindai lagi." + "Tidak menemukan saluran apa pun saat memindai. Pastikan tuner jaringan aktif dan terhubung ke sumber sinyal TV.\n\nJika Anda menggunakan antena udara, sesuaikan penempatan atau arahnya. Untuk hasil terbaik, tempatkan di lokasi yang tinggi dan dekat jendela, lalu pindai lagi." "Pindai lagi" "Selesai" - "Pindai saluran TV" - "Penyiapan Tuner TV" - "Penyiapan Tuner TV USB" - "Koneksi tuner TV USB terputus." + "Pindai saluran TV" + "Penyiapan Tuner TV" + "Penyiapan Tuner TV USB" + "Penyiapan Tuner TV Jaringan" + "Koneksi tuner TV USB terputus." + "Koneksi tuner jaringan terputus." diff --git a/usbtuner-res/values-is-rIS/strings.xml b/usbtuner-res/values-is-rIS/strings.xml new file mode 100644 index 00000000..fa1e85c7 --- /dev/null +++ b/usbtuner-res/values-is-rIS/strings.xml @@ -0,0 +1,90 @@ + + + + + "Sjónvarpsmóttakari" + "USB-sjónvarpsmóttakari" + "Netsjónvarpsmóttakari (TILRAUNAÚTGÁFA)" + "Bíddu þar til vinnslu lýkur" + "Hugbúnaður sjónvarpsmóttakarans var uppfærður nýlega. Leitaðu aftur að rásum." + "Kveikja á víðómastillingu í hljóðstillingum kerfisins til að kveikja á hljóði" + "Ekki er hægt að spila hljóð. Prófaðu annað sjónvarp" + "Uppsetning sjónvarpskorts" + "Uppsetning sjónvarpsmóttakara" + "Uppsetning USB-sjónvarpsrásakorts" + "Uppsetning netsjónvarpsmóttakara" + "Gakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + "Gakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt til að ná fleiri rásum. Best er að hafa það hátt uppi og nálægt glugga." + "Gakktu úr skugga um að kveikt sé á netsjónvarpsmóttakaranum og að hann sé tengdur við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + + "Halda áfram" + "Ekki núna" + + "Viltu keyra rásauppsetningu aftur?" + "Þetta fjarlægir stöðvar sem sjónvarpsmóttakarinn fann og leitar aftur að nýjum stöðvum.\n\nGakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + "Þetta fjarlægir rásir sem skráðar eru á USB-sjónvarpskortinu og leitar að nýjum stöðvum.\n\nGakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt til að ná fleiri rásum. Best er að hafa það hátt uppi og nálægt glugga." + "Þetta fjarlægir rásirnar sem sjónvarpsmóttakarinn þinn fann og leitar aftur að nýjum rásum.\n\nGakktu úr skugga um að kveikt sé á netsjónvarpsmóttakaranum og að hann sé tengdur við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + + "Halda áfram" + "Hætta við" + + "Veldu tengigerðina" + "Veldu „Loftnet“ ef utanáliggjandi loftnet er tengt við sjónvarpskortið. Veldu „Kapall“ ef rásirnar berast þér í gegnum kapal. Ef þú ert ekki viss verður leitað að báðum gerðum og það kann að taka lengri tíma." + + "Loftnet" + "Kapall" + "Ekki viss" + "Aðeins þróunaraðilar" + + "Uppsetning sjónvarpsmóttakara" + "Uppsetning USB-sjónvarpsrásakorts" + "Uppsetning netsjónvarpsmóttakara" + "Þetta getur tekið nokkrar mínútur" + "Móttakari er tímabundið ekki í boði eða er þegar að taka upp." + + %1$d rás fannst + %1$d rásir fundust + + "STÖÐVA RÁSALEIT" + + %1$d rás fannst + %1$d rásir fundust + + + Glæsilegt! %1$d rás fannst við leitina. Ef þetta er ekki eins og það á að vera skaltu prófa að stilla loftnetið og leita aftur. + Glæsilegt! %1$d rásir fundust við leitina. Ef þetta er ekki eins og það á að vera skaltu prófa að stilla loftnetið og leita aftur. + + + "Lokið" + "Leita aftur" + + "Engar rásir fundust" + "Engar rásir fundust. Gakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + "Engar rásir fundust. Gakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + "Leitin fann engar stöðvar. Gakktu úr skugga um að kveikt sé á netsjónvarpsmóttakaranum og hann sé tengdur við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." + + "Leita aftur" + "Lokið" + + "Skanna eftir sjónvarpsstöðvum" + "Uppsetning sjónvarpsmóttakara" + "Uppsetning USB-sjónvarpsmóttakara" + "Uppsetning netsjónvarpsmóttakara" + "USB-sjónvarpsmóttakari tekinn úr sambandi." + "Netmóttakari tekinn úr sambandi." + diff --git a/usbtuner-res/values-is/strings.xml b/usbtuner-res/values-is/strings.xml deleted file mode 100644 index f63d9810..00000000 --- a/usbtuner-res/values-is/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Sjónvarpsmóttakari" - "USB-sjónvarpsmóttakari" - "Kveikja" - "Slökkva" - "Bíddu þar til vinnslu lýkur" - "Veldu inntak rása" - "Ekkert merki" - "Mistókst að stilla á %s" - "Mistókst að stilla" - "Hugbúnaður sjónvarpsmóttakarans var uppfærður nýlega. Leitaðu aftur að rásum." - "Kveikja á víðómastillingu í hljóðstillingum kerfisins til að kveikja á hljóði" - "Uppsetning sjónvarpskorts" - "Uppsetning sjónvarpsmóttakara" - "Uppsetning USB-sjónvarpsrásakorts" - "Gakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." - "Gakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt til að ná fleiri rásum. Best er að hafa það hátt uppi og nálægt glugga." - - "Halda áfram" - "Ekki núna" - - "Viltu keyra rásauppsetningu aftur?" - "Þetta fjarlægir stöðvar sem sjónvarpsmóttakarinn fann og leitar aftur að nýjum stöðvum.\n\nGakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." - "Þetta fjarlægir rásir sem skráðar eru á USB-sjónvarpskortinu og leitar að nýjum stöðvum.\n\nGakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt til að ná fleiri rásum. Best er að hafa það hátt uppi og nálægt glugga." - - "Halda áfram" - "Hætta við" - - "Veldu tengigerðina" - "Veldu „Loftnet“ ef utanáliggjandi loftnet er tengt við sjónvarpskortið. Veldu „Kapall“ ef rásirnar berast þér í gegnum kapal. Ef þú ert ekki viss verður leitað að báðum gerðum og það kann að taka lengri tíma." - - "Loftnet" - "Kapall" - "Ekki viss" - "Aðeins þróunaraðilar" - - "Uppsetning sjónvarpsmóttakara" - "Uppsetning USB-sjónvarpsrásakorts" - "Þetta getur tekið nokkrar mínútur" - "Móttakari er tímabundið ekki í boði eða er þegar að taka upp." - - %1$d rás fannst - %1$d rásir fundust - - "STÖÐVA RÁSALEIT" - - %1$d rás fannst - %1$d rásir fundust - - - Glæsilegt! %1$d rás fannst við leitina. Ef þetta er ekki eins og það á að vera skaltu prófa að stilla loftnetið og leita aftur. - Glæsilegt! %1$d rásir fundust við leitina. Ef þetta er ekki eins og það á að vera skaltu prófa að stilla loftnetið og leita aftur. - - - "Lokið" - "Leita aftur" - - "Engar rásir fundust" - "Engar rásir fundust. Gakktu úr skugga um að sjónvarpið sé tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." - "Engar rásir fundust. Gakktu úr skugga um að USB-sjónvarpskortið sé í sambandi og tengt við sjónvarpstengi.\n\nEf þú ert að nota loftnet skaltu færa það á annan stað eða snúa því í aðra átt. Best er að hafa það hátt uppi og nálægt glugga þegar leitað er." - - "Leita aftur" - "Lokið" - - "Skanna eftir sjónvarpsstöðvum" - "Uppsetning sjónvarpsmóttakara" - "Uppsetning USB-sjónvarpsmóttakara" - "USB-sjónvarpsmóttakari tekinn úr sambandi." - diff --git a/usbtuner-res/values-it/strings.xml b/usbtuner-res/values-it/strings.xml index 7787d019..3e15fb24 100644 --- a/usbtuner-res/values-it/strings.xml +++ b/usbtuner-res/values-it/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonizzatore TV" "Sintonizzatore TV USB" - "Attiva" - "Disattiva" + "Network TV Tuner (BETA)" "Attendi il completamento dell\'elaborazione" - "Seleziona la tua fonte canale" - "Nessun segnale" - "Sintonizzazione su %s non riuscita" - "Sintonizzazione non riuscita" "Il software del sintonizzatore è stato aggiornato di recente. Esegui nuovamente la scansione dei canali." "Per attivare l\'audio, attiva l\'audio surround nelle impostazioni del sistema" + "Impossibile riprodurre l\'audio. Prova con un\'altra TV" "Configurazione del sintonizzatore di canali" "Configurazione del sintonizzatore TV" "Configurazione del sintonizzatore di canali USB" + "Configurazione del sintonizzatore di rete" "Verifica che la TV sia connessa a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA, regolane la posizione o la direzione per ricevere la maggior parte dei canali. Per ottenere risultati ottimali, posizionala in alto e vicino a una finestra." "Verifica che il sintonizzatore USB sia collegato e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA, potresti dover regolare la sua posizione o direzione per ricevere la maggior parte dei canali. Per risultati ottimali, posizionala in alto e vicino a una finestra." + "Verifica che il sintonizzatore di rete sia acceso e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA (over-the-air), potresti doverne regolare la posizione o la direzione per ricevere il maggior numero di canali. Per ottenere risultati ottimali, posizionala in alto, vicino a una finestra." "Continua" "Non ora" @@ -40,6 +38,7 @@ "Eseguire nuovamente la configurazione dei canali?" "Questa operazione rimuoverà i canali trovati dal sintonizzatore TV ed eseguirà la ricerca di nuovi canali.\n\nVerifica che la TV sia connessa a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA, regolane la posizione o la direzione per ricevere la maggior parte dei canali. Per ottenere risultati ottimali, posizionala in alto e vicino a una finestra." "Questa operazione rimuoverà i canali trovati dal sintonizzatore USB ed eseguirà la ricerca di nuovi canali.\n\nVerifica che il sintonizzatore USB sia collegato e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA, potresti dover regolare la sua posizione o direzione per ricevere il maggior numero di canali. Per ottenere risultati ottimali, posizionala in alto e vicino a una finestra." + "I canali trovati dal sintonizzatore di rete verranno rimossi e verrà eseguita nuovamente la ricerca di nuovi canali.\n\nVerifica che il sintonizzatore di rete sia acceso e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA (over-the-air), potresti doverne regolare la posizione o la direzione per ricevere il maggior numero di canali. Per ottenere risultati ottimali, posizionala in alto, vicino a una finestra." "Continua" "Annulla" @@ -54,6 +53,7 @@ "Configurazione del sintonizzatore TV" "Configurazione del sintonizzatore di canali USB" + "Configurazione del sintonizzatore di canali della rete" "L\'operazione potrebbe richiedere alcuni minuti" "Il sintonizzatore è temporaneamente non disponibile o già utilizzato dal registratore." @@ -76,12 +76,15 @@ "Nessun canale trovato" "Nessun canale trovato durante la ricerca. Verifica che la TV sia connessa a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA, regolane la posizione o la direzione. Per ottenere risultati ottimali, posizionala in alto, vicino a una finestra ed esegui nuovamente la ricerca." "Nessun canale trovato durante la ricerca. Verifica che il sintonizzatore USB sia collegato e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA (over-the-air), regolane la posizione o la direzione. Per ottenere risultati ottimali, posizionala in alto, vicino a una finestra ed esegui nuovamente la ricerca." + "Nessun canale trovato durante la ricerca. Verifica che il sintonizzatore di rete sia acceso e connesso a un\'origine del segnale TV.\n\nSe utilizzi un\'antenna OTA (over-the-air), regolane la posizione o la direzione. Per ottenere risultati ottimali, posizionala in alto, vicino a una finestra ed esegui nuovamente la ricerca." "Cerca di nuovo" "Fine" - "Cerca canali TV" - "Configurazione del sintonizzatore TV" - "Configurazione del sintonizzatore TV USB" - "Sintonizzatore TV USB disconnesso." + "Cerca canali TV" + "Configurazione del sintonizzatore TV" + "Configurazione del sintonizzatore TV USB" + "Configurazione del sintonizzatore TV di rete" + "Sintonizzatore TV USB disconnesso." + "Sintonizzatore di rete disconnesso." diff --git a/usbtuner-res/values-iw/strings.xml b/usbtuner-res/values-iw/strings.xml index 5dab5f15..d30fdac5 100644 --- a/usbtuner-res/values-iw/strings.xml +++ b/usbtuner-res/values-iw/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "טיונר טלוויזיה" "‏טיונר ה-USB בטלוויזיה" - "מופעל" - "כבוי" + "טיונר טלוויזיה לרשת (ביטא)" "המתן לסיום העיבוד" - "בחר את מקור הערוצים" - "אין אות" - "לא ניתן היה לכוון לערוץ %s" - "הכוונון נכשל" "תוכנת הטיונר עודכנה לאחרונה. סרוק מחדש את הערוצים." "הפעל סראונד בהגדרות צלילי מערכת כדי להפעיל אודיו" + "אין אפשרות להשמיע אודיו. נסה ערוץ טלוויזיה אחר." "הגדרת טיונר ערוצים" "הגדרת טיונר טלוויזיה" "‏הגדרת טיונר ערוצים בחיבור USB" + "הגדרת מקלט הרשת" "ודא שהטלוויזיה מחוברת למקור אות טלוויזיה.\n\nאם אתה משתמש באנטנה אלחוטית, ייתכן שיהיה עליך לשנות את המיקום או את הכיוון שלה כדי לקלוט כמה שיותר ערוצים. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון." "‏ודא שטיונר USB מחובר למקור אות בטלוויזיה. \n\n אם אתה משתמש באנטנה אלחוטית, ייתכן שיהיה עליך לכוון את מיקומה או את כיוונה כדי לקלוט כמה שיותר ערוצים. לתוצאות מיטביות, הצב אותה במקום גבוה ליד חלון וסרוק שוב." + "ודא שמקלט הרשת מופעל ומחובר למקור אות של טלוויזיה.\n\nאם אתה משתמש באנטנה לקליטת שידורים אלחוטיים, ייתכן שיהיה עליך לשנות את המיקום או את הכיוון שלה כדי לקלוט כמה שיותר ערוצים. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון." "המשך" "לא עכשיו" @@ -40,6 +38,7 @@ "האם להפעיל מחדש את הגדרת הערוצים?" "פעולה זו תסיר את הערוצים שנמצאו בטיונר הטלוויזיה ותסרוק שוב ערוצים חדשים.\n\nודא שהטלוויזיה מחוברת למקור אות טלוויזיה.\n\nאם אתה משתמש באנטנה אלחוטית, ייתכן שיהיה עליך לשנות את המיקום או את הכיוון שלה כדי לקלוט כמה שיותר ערוצים. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון." "‏פעולה זו תסיר את הערוצים שנמצאו מטיונר ה-USB ותתבצע סריקה נוספת לערוצים חדשים.\n\nודא שטיונר USB מחובר למקור אות בטלוויזיה.\n\nאם אתה משתמש באנטנה אלחוטית, ייתכן שיהיה עליך לשנות את מיקומה או את כיוונה כדי לקלוט כמה שיותר ערוצים. לתוצאות מיטביות, הצב אותה במקום גבוה ליד חלון וסרוק שוב." + "הפעולה תסיר את הערוצים שנמצאו ממקלט הרשת ותתבצע שוב סריקה לאיתור ערוצים חדשים.\n\nודא שמקלט הרשת מופעל ומחובר למקור אות של טלוויזיה.\n\nאם אתה משתמש באנטנה לקליטת שידורים אלחוטיים, ייתכן שיהיה עליך לשנות את המיקום או את הכיוון שלה כדי לקלוט כמה שיותר ערוצים. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון." "המשך" "ביטול" @@ -54,6 +53,7 @@ "הגדרת טיונר טלוויזיה" "‏הגדרת טיונר ערוצים בחיבור USB" + "הגדרת מקלט ערוצים לרשת" "פעולה זו עשויה להימשך מספר דקות" "הטיונר אינו זמין באופן זמני או שהוא כבר נמצא בשימוש של הקלטה." @@ -82,12 +82,15 @@ "לא נמצאו ערוצים" "בסריקה לא נמצאו ערוצים. ודא שהטלוויזיה מחוברת למקור אות טלוויזיה.\n\nאם אתה משתמש באנטנה אלחוטית, שנה את המיקום או את הכיוון שלה. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון וסרוק שוב." "‏לא נמצאו ערוצים בסריקה. ודא שטיונר USB מחובר למקור אות בטלוויזיה. \n\n אם אתה משתמש באנטנה אלחוטית, שנה את מיקומה או את כיוונה. לתוצאות מיטביות, הצב אותה במקום גבוה ליד חלון וסרוק שוב." + "בסריקה לא נמצאו ערוצים. ודא שמקלט הרשת מופעל ומחובר למקור אות של טלוויזיה.\n\nאם אתה משתמש באנטנה לקליטת שידורים אלחוטיים, שנה את המיקום או את הכיוון שלה. לקבלת התוצאות הטובות ביותר, הצב אותה במקום גבוה ליד חלון וסרוק שוב." "סרוק שוב" "סיום" - "סריקה לאיתור ערוצי טלוויזיה" - "הגדרת טיונר טלוויזיה" - "‏הגדרת טיונר ה-USB בטלוויזיה" - "‏טיונר ה-USB שבטלוויזיה מנותק." + "סריקה לאיתור ערוצי טלוויזיה" + "הגדרה של טיונר טלוויזיה" + "‏הגדרה של טיונר USB בטלוויזיה" + "הגדרה של טיונר טלוויזיה לרשת" + "‏טיונר ה-USB שבטלוויזיה מנותק." + "טיונר הרשת מנותק." diff --git a/usbtuner-res/values-ja/strings.xml b/usbtuner-res/values-ja/strings.xml index 88af255a..53223b41 100644 --- a/usbtuner-res/values-ja/strings.xml +++ b/usbtuner-res/values-ja/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "テレビ チューナー" "USB テレビ チューナー" - "ON" - "OFF" + "ネットワーク テレビ チューナー(ベータ版)" "処理が完了するまでこのままお待ちください" - "チャンネル ソースを選択してください" - "信号がありません" - "「%s」に合わせることができませんでした" - "合わせることができませんでした" "最近チューナー ソフトウェアが更新されています。チャンネルをもう一度スキャンしてください。" "音声を有効にするには、システムのサウンド設定でサラウンド サウンドをオンにしてください" + "音声を再生できません。別のテレビをお試しください" "チャンネル チューナーのセットアップ" "テレビ チューナーのセットアップ" "USB チャンネル チューナーのセットアップ" + "ネットワーク チューナーのセットアップ" "テレビがテレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" "USB チューナーが電源に接続され、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" + "ネットワーク チューナーの電源がオンになっていて、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" "次へ" "後で" @@ -40,6 +38,7 @@ "もう一度チャンネルを設定しますか?" "テレビ チューナーで見つかったチャンネルを削除して新しいチャンネルをもう一度スキャンします。\n\nテレビがテレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" "USB チューナーで見つかったチャンネルを削除して新しいチャンネルをもう一度スキャンします。\n\nUSB チューナーが電源に接続され、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" + "ネットワーク チューナーで見つかったチャンネルを削除して新しいチャンネルをもう一度スキャンします。\n\nネットワーク チューナーの電源がオンになっていて、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、チャンネルの多くを受信するためにアンテナの場所や方向の調節が必要になる場合があります。最良の結果を得るには、アンテナを窓際の高い位置に設置します。" "次へ" "キャンセル" @@ -54,6 +53,7 @@ "テレビ チューナーのセットアップ" "USB チャンネル チューナーのセットアップ" + "ネットワーク チャンネル チューナーのセットアップ" "この処理には数分かかることがあります" "チューナーを一時的に使用できないか、すでに録画に使用しています。" @@ -76,12 +76,15 @@ "チャンネルが見つかりませんでした" "スキャンの結果、チャンネルは見つかりませんでした。テレビがテレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、アンテナの場所や方向を調節してください。最良の結果を得るには、アンテナを窓際の高い位置に設置してから、もう一度スキャンします。" "スキャンの結果、チャンネルは見つかりませんでした。USB チューナーが電源に接続され、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、アンテナの場所や方向を調節してください。最良の結果を得るには、アンテナを窓際の高い位置に設置してから、もう一度スキャンします。" + "スキャンの結果、チャンネルは見つかりませんでした。ネットワーク チューナーの電源がオンになっていて、テレビの信号源に接続されていることを確認してください。\n\n無線アンテナを使用している場合は、アンテナの場所や方向を調節してください。最良の結果を得るには、アンテナを窓際の高い位置に設置してから、もう一度スキャンします。" "再スキャン" "完了" - "テレビのチャンネルをスキャン" - "テレビ チューナーのセットアップ" - "USB テレビ チューナーのセットアップ" - "USB TV チューナーの接続が解除されています。" + "テレビのチャンネルをスキャン" + "テレビ チューナーのセットアップ" + "USB テレビ チューナーのセットアップ" + "ネットワーク テレビ チューナーのセットアップ" + "USB テレビ チューナーの接続が解除されています。" + "ネットワーク チューナーの接続が解除されています。" diff --git a/usbtuner-res/values-ka-rGE/strings.xml b/usbtuner-res/values-ka-rGE/strings.xml new file mode 100644 index 00000000..055c4a99 --- /dev/null +++ b/usbtuner-res/values-ka-rGE/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV-ტუნერი" + "USB TV-ტუნერი" + "ქსელის TV-ტუნერი (Beta)" + "გთხოვთ, მოითმინოთ დამუშავების დასრულებამდე" + "ტუნერის პროგრამული უზრუნველყოფა ახლახან განახლდა. გთხოვთ, ხელახლა დაასკანიროთ არხები." + "აუდიოს ჩასართავად, სისტემის ხმის პარამეტრებში ჩართეთ მოცულობითი ხმა" + "აუდიოს დაკვრა ვერ ხერხდება. გთხოვთ, ცადოთ სხვა არხი" + "არხების ტუნერის დაყენება" + "TV-ტუნერის დაყენება" + "არხების USB ტუნერის დაყენება" + "ქსელის ტუნერის დაყენება" + "დარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან..\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + "დარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + "დარწმუნდით, რომ ქსელის ტუნერი ჩართულია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულირება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + + "გაგრძელება" + "ახლა არა" + + "გსურთ არხების ხელახლა დაყენება?" + "ეს მოქმედება ამოშლის TV ტუნერით ნაპოვნ არხებს და ახალი არხების სკანირება ხელახლა მოხდება.\n\nდარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + "ეს მოქმედება ამოშლის USB ტუნერით ნაპოვნ არხებს და ხელახლა მოხდება ახალი არხების სკანირება.\n\nდარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + "ეს მოქმედება ამოშლის ქსელის ტუნერით ნაპოვნ არხებს და ახალი არხების სკანირება ხელახლა მოხდება.\n\nდარწმუნდით, რომ ქსელის ტუნერი ჩართულია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულირება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." + + "გაგრძელება" + "გაუქმება" + + "აირჩიეთ კავშირის ტიპი" + "აირჩიეთ ანტენა, თუ ტუნერთან მიერთებულია გარე ანტენა. აირჩიეთ კაბელი, თუ არხებს საკაბელო სერვისის პროვაიდერისგან იღებთ. თუ დარწმუნებული არ ხართ, დასკანირდება ორივე ტიპი, მაგრამ ამას შეიძლება მეტი დრო დასჭირდეს." + + "ანტენა" + "კაბელი" + "არ ვიცი" + "მხოლოდ დეველოპერებისთვის" + + "TV-ტუნერის დაყენება" + "არხების USB ტუნერის დაყენება" + "არხების ქსელის ტუნერის დაყენება" + "ამას შეიძლება რამდენიმე წუთი დასჭირდეს" + "ტუნერი დროებით მიუწვდომელია, ან უკვე გამოიყენება ჩასაწერად." + + მოიძებნა %1$d არხი + მოიძებნა %1$d არხი + + "არხების სკანირების შეწყვეტა" + + მოიძებნა %1$d არხი + მოიძებნა %1$d არხი + + + მშვენიერია! არხების სკანირებისას მოიძებნა %1$d არხი. თუ სათანადო შედეგს ვერ მიიღებთ, ცადეთ ანტენის პოზიციის დარეგულირება და ხელახლა დაასკანირეთ. + მშვენიერია! არხების სკანირებისას მოიძებნა %1$d არხი. თუ სათანადო შედეგს ვერ მიიღებთ, ცადეთ ანტენის პოზიციის დარეგულირება და ხელახლა დაასკანირეთ. + + + "მზადაა" + "ხელახლა სკანირება" + + "არხები ვერ მოიძებნა" + "სკანირებისას არხები ვერ მოიძებნა. დარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, დაარეგულირეთ მისი განლაგება ან მიმართულება. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს და ხელახლა დაასკანირეთ." + "სკანირებისას არხები ვერ მოიძებნა. დარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან. \n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, დაარეგულირეთ მისი განლაგება ან მიმართულება. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს და ხელახლა დაასკანირეთ." + "სკანირებისას არხები ვერ მოიძებნა. დარწმუნდით, რომ ქსელის ტუნერი ჩართულია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან. \n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, დაარეგულირეთ მისი განლაგება ან მიმართულება. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს და ხელახლა დაასკანირეთ." + + "ხელახლა სკანირება" + "მზადაა" + + "სკანირება სატელევიზიო არხების აღმოსაჩენად" + "TV-ტუნერის დაყენება" + "USB TV-ტუნერის დაყენება" + "ქსელის TV-ტუნერის დაყენება" + "USB TV-ტუნერი გაითიშა." + "ქსელის ტუნერი გაითიშა." + diff --git a/usbtuner-res/values-ka/strings.xml b/usbtuner-res/values-ka/strings.xml deleted file mode 100644 index e09428d7..00000000 --- a/usbtuner-res/values-ka/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV-ტუნერი" - "USB TV-ტუნერი" - "ჩართვა" - "გამორთვა" - "გთხოვთ, მოითმინოთ დამუშავების დასრულებამდე" - "აირჩიეთ არხების წყარო" - "სიგნალი არ არის" - "%s-ზე გადართვა ვერ მოხერხდა" - "არხზე გადართვა ვერ მოხერხდა" - "ტუნერის პროგრამული უზრუნველყოფა ახლახან განახლდა. გთხოვთ, ხელახლა დაასკანიროთ არხები." - "აუდიოს ჩასართავად, სისტემის ხმის პარამეტრებში ჩართეთ მოცულობითი ხმა" - "არხების ტუნერის დაყენება" - "TV-ტუნერის დაყენება" - "არხების USB ტუნერის დაყენება" - "დარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან..\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." - "დარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." - - "გაგრძელება" - "ახლა არა" - - "გსურთ არხების ხელახლა დაყენება?" - "ეს მოქმედება ამოშლის TV ტუნერით ნაპოვნ არხებს და ახალი არხების სკანირება ხელახლა მოხდება.\n\nდარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." - "ეს მოქმედება ამოშლის USB ტუნერით ნაპოვნ არხებს და ხელახლა მოხდება ახალი არხების სკანირება.\n\nდარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, არხების უმეტესობის მისაღებად, მისი განლაგების ან მიმართულების დარეგულება მოგიწევთ. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს." - - "გაგრძელება" - "გაუქმება" - - "აირჩიეთ კავშირის ტიპი" - "აირჩიეთ ანტენა, თუ ტუნერთან მიერთებულია გარე ანტენა. აირჩიეთ კაბელი, თუ არხებს საკაბელო სერვისის პროვაიდერისგან იღებთ. თუ დარწმუნებული არ ხართ, დასკანირდება ორივე ტიპი, მაგრამ ამას შეიძლება მეტი დრო დასჭირდეს." - - "ანტენა" - "კაბელი" - "არ ვიცი" - "მხოლოდ დეველოპერებისთვის" - - "TV-ტუნერის დაყენება" - "არხების USB ტუნერის დაყენება" - "ამას შეიძლება რამდენიმე წუთი დასჭირდეს" - "ტუნერი დროებით მიუწვდომელია, ან უკვე გამოიყენება ჩასაწერად." - - მოიძებნა %1$d არხი - მოიძებნა %1$d არხი - - "არხების სკანირების შეწყვეტა" - - მოიძებნა %1$d არხი - მოიძებნა %1$d არხი - - - მშვენიერია! არხების სკანირებისას მოიძებნა %1$d არხი. თუ სათანადო შედეგს ვერ მიიღებთ, ცადეთ ანტენის პოზიციის დარეგულირება და ხელახლა დაასკანირეთ. - მშვენიერია! არხების სკანირებისას მოიძებნა %1$d არხი. თუ სათანადო შედეგს ვერ მიიღებთ, ცადეთ ანტენის პოზიციის დარეგულირება და ხელახლა დაასკანირეთ. - - - "მზადაა" - "ხელახლა სკანირება" - - "არხები ვერ მოიძებნა" - "სკანირებისას არხები ვერ მოიძებნა. დარწმუნდით, რომ თქვენი ტელევიზორი მიერთებულია სატელევიზიო სიგნალის წყაროსთან.\n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, დაარეგულირეთ მისი განლაგება ან მიმართულება. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს და ხელახლა დაასკანირეთ." - "სკანირებისას არხები ვერ მოიძებნა. დარწმუნდით, რომ USB ტუნერი ბუდეშია და მიერთებულია სატელევიზიო სიგნალის წყაროსთან. \n\nტერესტრიული ანტენის გამოყენების შემთხვევაში, დაარეგულირეთ მისი განლაგება ან მიმართულება. საუკეთესო შედეგის მისაღებად, განათავსეთ ის სიმაღლეზე, ფანჯარასთან ახლოს და ხელახლა დაასკანირეთ." - - "ხელახლა სკანირება" - "მზადაა" - - "სატელევიზიო არხების სკანირება" - "TV-ტუნერის დაყენება" - "USB TV-ტუნერის დაყენება" - "USB TV-ტუნერი გაითიშა." - diff --git a/usbtuner-res/values-kk-rKZ/strings.xml b/usbtuner-res/values-kk-rKZ/strings.xml new file mode 100644 index 00000000..7ca732b2 --- /dev/null +++ b/usbtuner-res/values-kk-rKZ/strings.xml @@ -0,0 +1,90 @@ + + + + + "ТД тюнері" + "USB TД тюнері" + "Желілік теледидар тюнері (БЕТА НҰСҚАСЫ)" + "Өңдеу аяқталғанша күте тұрыңыз" + "Тюнердің бағдарламалық құралы жақында жаңартылды. Арналарды қайта іздеңіз." + "Аудиомазмұнды қосу үшін жүйенің параметрлерінде көлемдік дыбысты қосыңыз" + "Аудиомазмұн ойнатылмайды. Басқа теледидар арнасын қосып көріңіз" + "Арна тюнерін орнату" + "ТД тюнерін орнату" + "USB арна тюнерін орнату" + "Желі тюнерін реттеу" + "Теледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." + "USB тюнерін жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." + "USB тюнері қосылып, теледидардың сигнал көзіне жалғанғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын ауыстырыңыз немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын жерге орналастырыңыз." + + "Жалғастыру" + "Қазір емес" + + "Арналарды қайта орнату қажет пе?" + "ТД тюнерінде табылған арналар жойылып, жаңа арналар ізделеді.\n\nТеледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." + "USB тюнерінде табылған арналар жойылып, жаңа арналар ізделеді.\n\nUSB тюнері жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." + "Желі тюнерінде табылған арналар өшіріліп, жаңа арналар ізделеді.\n\nЖелі тюнері қосылып, теледидардың сигнал көзіне жалғанғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын ауыстырыңыз немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын жерге орналастырыңыз." + + "Жалғастыру" + "Бас тарту" + + "Қосылу түрін таңдау" + "Тюнерге қосымша антенна қосылған болса, \"Антенна\" опциясын таңдаңыз. Арналар кабельді қызмет провайдерінен алынған болса, \"Кабель\" опциясын таңдаңыз. Нақты білмесеңіз, екі түрі де ізделеді, бірақ бұған көбірек уақыт кетуі мүмкін." + + "Антенна" + "Кабель" + "Нақты білмеймін" + "Тек әзірлеу" + + "ТД тюнерін орнату" + "USB арна тюнерін орнату" + "Желілік арна тюнерін реттеу" + "Бұл бірнеше минутты алуы мүмкін" + "Тюнер қолжетімсіз немесе жазу барысында қолданылуда." + + %1$d арна табылды + %1$d арна табылды + + "АРНА ІЗДЕУДІ ТОҚТАТУ" + + %1$d арна табылды + %1$d арна табылды + + + Тамаша! Арналарды іздеу кезінде %1$d арна табылды. Жеткіліксіз болса, антеннаның орнын ауыстырып, қайта іздеп көріңіз. + Тамаша! Арналарды іздеу кезінде %1$d арна табылды. Жеткіліксіз болса, антеннаның орнын ауыстырып, қайта іздеп көріңіз. + + + "Орындалды" + "Қайта іздеу" + + "Арналар табылмады" + "Іздеу нәтижесінде ешқандай арна табылмады. Теледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, орнын немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырып, қайта іздеп көріңіз." + "Іздеу нәтижесінде арналар табылмады. USB тюнері жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, орнын немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырып, қайта іздеп көріңіз." + "Іздеу нәтижесінде арналар табылмады. Желі тюнері қосылып, теледидардың сигнал көзіне жалғанғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, оның орнын ауыстырыңыз немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын жерге орналастырып, қайта іздеп көріңіз." + + "Қайта іздеу" + "Орындалды" + + "ТД арналарын іздеу" + "ТД тюнерін орнату" + "USB TД тюнерін орнату" + "Желілік ТД тюнерін реттеу" + "USB TД тюнері ажыратылды." + "Желі тюнері ажыратылды." + diff --git a/usbtuner-res/values-kk/strings.xml b/usbtuner-res/values-kk/strings.xml deleted file mode 100644 index 3c2ba4f3..00000000 --- a/usbtuner-res/values-kk/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ТД тюнері" - "USB TД тюнері" - "Қосу" - "Өшіру" - "Өңдеу аяқталғанша күте тұрыңыз" - "Арна көзін таңдаңыз" - "Сигнал жоқ" - "%s арнасына реттелмеді" - "Реттелмеді" - "Тюнердің бағдарламалық құралы жақында жаңартылды. Арналарды қайта іздеңіз." - "Аудиомазмұнды қосу үшін жүйенің параметрлерінде көлемдік дыбысты қосыңыз" - "Арна тюнерін орнату" - "ТД тюнерін орнату" - "USB арна тюнерін орнату" - "Теледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." - "USB тюнерін жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." - - "Жалғастыру" - "Қазір емес" - - "Арналарды қайта орнату қажет пе?" - "ТД тюнерінде табылған арналар жойылып, жаңа арналар ізделеді.\n\nТеледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." - "USB тюнерінде табылған арналар жойылып, жаңа арналар ізделеді.\n\nUSB тюнері жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, көптеген арналарды қабылдау үшін оның орнын немесе бағытын реттеу қажет болады. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырыңыз." - - "Жалғастыру" - "Бас тарту" - - "Қосылу түрін таңдау" - "Тюнерге қосымша антенна қосылған болса, \"Антенна\" опциясын таңдаңыз. Арналар кабельді қызмет провайдерінен алынған болса, \"Кабель\" опциясын таңдаңыз. Нақты білмесеңіз, екі түрі де ізделеді, бірақ бұған көбірек уақыт кетуі мүмкін." - - "Антенна" - "Кабель" - "Нақты білмеймін" - "Тек әзірлеу" - - "ТД тюнерін орнату" - "USB арна тюнерін орнату" - "Бұл бірнеше минутты алуы мүмкін" - "Тюнер қол жетімсіз немесе жазу барысында қолданылуда." - - %1$d арна табылды - %1$d арна табылды - - "АРНА ІЗДЕУДІ ТОҚТАТУ" - - %1$d арна табылды - %1$d арна табылды - - - Тамаша! Арналарды іздеу кезінде %1$d арна табылды. Жеткіліксіз болса, антеннаның орнын ауыстырып, қайта іздеп көріңіз. - Тамаша! Арналарды іздеу кезінде %1$d арна табылды. Жеткіліксіз болса, антеннаның орнын ауыстырып, қайта іздеп көріңіз. - - - "Орындалды" - "Қайта іздеу" - - "Арналар табылмады" - "Іздеу нәтижесінде ешқандай арна табылмады. Теледидардың ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, орнын немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырып, қайта іздеп көріңіз." - "Іздеу нәтижесінде арналар табылмады. USB тюнері жалғанғанын және ТД сигнал көзіне қосылғанын тексеріңіз.\n\nСымсыз антеннаны пайдалансаңыз, орнын немесе бағытын реттеңіз. Ең жақсы нәтижеге қол жеткізу үшін оны жоғарырақ және терезеге жақын орналастырып, қайта іздеп көріңіз." - - "Қайта іздеу" - "Орындалды" - - "ТД арналарын іздеу" - "ТД тюнерін орнату" - "USB TД тюнерін орнату" - "USB TД тюнері ажыратылды." - diff --git a/usbtuner-res/values-km-rKH/strings.xml b/usbtuner-res/values-km-rKH/strings.xml new file mode 100644 index 00000000..5bca2a02 --- /dev/null +++ b/usbtuner-res/values-km-rKH/strings.xml @@ -0,0 +1,90 @@ + + + + + "អង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" + "ឧបករណ៍ USB រាវប៉ុស្តិ៍ទូរទស្សន៍" + "កម្មវិធី​រាវ​បណ្តាញ​ទូរទស្សន៍ (បេតា)" + "សូមរង់ចាំដើម្បីបញ្ចប់ដំណើរការ" + "កម្មវិធីអង្គរាវប៉ុស្តិ៍បានអាប់ដេតថ្មីៗនេះ។ សូមស្កេនរកប៉ុស្តិ៍ទាំងនេះម្តងទៀត។" + "បើកដំណើរការសំឡេងជុំវិញនៅក្នុងការកំណត់សំឡេងប្រព័ន្ធដើម្បីបើកដំណើរការអូឌីយ៉ូ" + "មិនអាចចាក់អូឌីយ៉ូបានទេ។ សូមសាកល្បងប្រើទូរទស្សន៍ផ្សេង" + "ការដំឡើងអង្គរាវប៉ុស្តិ៍" + "ការដំឡើងអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" + "ការដំឡើងឧបករណ៍ USB រាវប៉ុស្តិ៍" + "រៀបចំឧបករណ៍រាវបណ្តាញ" + "សូមផ្ទៀងផ្ទាត់ថាប៉ុស្តិ៍ទូរទស្សន៍របស់អ្នកត្រូវបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង ឬទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងខ្ពស់ និងនៅជិតបង្អួច។" + "ផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវកែសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" + "ផ្ទៀងផ្ទាត់ថាអ្នកបានបើកឧបករណ៍រាវបណ្តាញនេះ និងបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកត្រូវកែសម្រួលទីតាំង និងទិសដៅរបស់វា ដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" + + "បន្ត" + "មិនមែនឥឡូវនេះទេ" + + "ដំណើរការដំឡើងប៉ុស្តិ៍ឡើងវិញឬទេ?" + "វានឹងលុបប៉ុស្តិ៍ដែលបានរកឃើញដោយអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍ចេញ ហើយស្កេនរកប៉ុស្តិ៍ថ្មីម្តងទៀត។\n\nផ្ទៀងផ្ទាត់ថាអ្នកបានដោតទូរទស្សន៍ទៅប្រភពរលកសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" + "វានឹងលុបប៉ុស្តិ៍ដែលបានរកឃើញដោយឧបករណ៍ USB រាវប៉ុស្តិ៍ចេញ ហើយស្កេនរកប៉ុស្តិ៍ថ្មីម្តងទៀត។\n\nផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" + "វានឹងលុបប៉ុស្តិ៍ដែលបានរកឃើញដោយឧបករណ៍រាវបណ្តាញ ហើយបន្ទាប់មកស្កេនរកបណ្តាញថ្មី។\n\nផ្ទៀងផ្ទាត់ថាអ្នកបានបើកអង្គរាវបណ្តាញនេះ និងបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកត្រូវសម្រួលទីតាំង និងទិសដៅរបស់វា ដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" + + "បន្ត" + "បោះបង់" + + "ជ្រើសប្រភេទនៃការតភ្ជាប់" + "សូមជ្រើសរើសអង់តែន ប្រសិនបើអ្នកភ្ជាប់អង់តែនខាងក្រៅទៅនឹងអង្គរាវប៉ុស្តិ៍។ ឬជ្រើសរើសខ្សែកាប ប្រសិនបើប៉ុស្តិ៍របស់អ្នកផ្តល់ដោយក្រុមហ៊ុនផ្តល់សេវាខ្សែកាប។ ប្រសិនបើអ្នកមិនប្រាកដទេ អ្នកអាចជ្រើសរើសប្រភេទទាំងពីរដើម្បីស្កេន ប៉ុន្តែវាអាចចំណាយពេលយូរ។" + + "អង់តែន" + "ខ្សែកាប" + "មិនប្រាកដ" + "ការអភិវឌ្ឍន៍ប៉ុណ្ណោះ" + + "ការដំឡើងអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" + "ការដំឡើងឧបករណ៍ USB រាវប៉ុស្តិ៍ទូរទស្សន៍" + "ការរៀបចំឧបករណ៍រាវបណ្តាញប៉ុស្តិ៍" + "វាអាចចំណាយពេលច្រើននាទី" + "អង្គរាវប៉ុស្តិ៍មិនអាចប្រើបានជាបណ្តោះអាសន្ន ឬបានប្រើសម្រាប់ការថតរួចទៅហើយ។" + + បានរកឃើញប៉ុស្តិ៍ %1$d + បានរកឃើញប៉ុស្តិ៍ %1$d + + "បញ្ឈប់ការស្កេនរកប៉ុស្តិ៍" + + បានរកឃើញប៉ុស្តិ៍ %1$d + បានរកឃើញប៉ុស្តិ៍ %1$d + + + ល្អណាស់! បានរកឃើញប៉ុស្តិ៍ %1$d អំឡុងពេលស្កេន។ ប្រសិនបើការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ទេ សូមសាកល្បងសម្រួលទីតាំងអង់តែន ហើយស្កេនម្តងទៀត។ + ល្អណាស់! បានរកឃើញប៉ុស្តិ៍ %1$d អំឡុងពេលស្កេន។ ប្រសិនបើការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ទេ សូមសាកល្បងសម្រួលទីតាំងអង់តែន ហើយស្កេនម្តងទៀត។ + + + "រួចរាល់" + "ស្កេនម្តងទៀត" + + "រកមិនឃើញប៉ុស្តិ៍ទេ" + "ការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ណាមួយឡើយ។ សូមផ្ទៀងផ្ទាត់ថាប៉ុស្តិ៍ទូរទស្សន៍របស់អ្នកត្រូវបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង ឬទិសដៅរបស់វា។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងខ្ពស់ និងនៅជិតបង្អួច បន្ទាប់មកធ្វើការស្កេនម្តងទៀត។" + "ការស្កេនរកមិនឃើញប៉ុស្តិ៍ទេ។ ផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង និងទិសដៅរបស់វា។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច ហើយបន្ទាប់មកស្កេនម្តងទៀត។" + "ការស្កេនរកមិនឃើញប៉ុស្តិ៍ណាមួយទេ។ ផ្ទៀងផ្ទាត់ថាអ្នកបានបើកឧបករណ៍រាវបណ្តាញនេះ និងបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង និងទិសដៅរបស់វា។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច បន្ទាប់មកស្កេនម្តងទៀត។" + + "ស្កេនម្តងទៀត" + "រួចរាល់" + + "ស្កេនរកប៉ុស្តិ៍ទូរទស្សន៍" + "ការរៀបចំអង្គរាវរកប៉ុស្តិ៍ទូរទស្សន៍" + "ការរៀបចំអង្គរាវរកប៉ុស្តិ៍ទូរទស្សន៍តាម USB" + "ការរៀបចំអង្គរាវរកបណ្តាញទូរទស្សន៍" + "អង្គរាវរកប៉ុស្តិ៍ USB សម្រាប់ទូរទស្សន៍ត្រូវបានផ្តាច់។" + "អង្គរាវរក​បណ្តាញ​ត្រូវ​បាន​ផ្ដាច់។" + diff --git a/usbtuner-res/values-km/strings.xml b/usbtuner-res/values-km/strings.xml deleted file mode 100644 index 904f3e5d..00000000 --- a/usbtuner-res/values-km/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "អង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" - "ឧបករណ៍ USB រាវប៉ុស្តិ៍ទូរទស្សន៍" - "បើក" - "បិទ" - "សូមរង់ចាំដើម្បីបញ្ចប់ដំណើរការ" - "ជ្រើសប្រភពប៉ុស្តិ៍របស់អ្នក" - "គ្មានរលកសញ្ញាទេ" - "បានបរាជ័យក្នុងការបើកប៉ុស្តិ៍ %s" - "បរាជ័យក្នុងការរាវប៉ុស្តិ៍" - "កម្មវិធីអង្គរាវប៉ុស្តិ៍បានអាប់ដេតថ្មីៗនេះ។ សូមស្កេនរកប៉ុស្តិ៍ទាំងនេះម្តងទៀត។" - "បើកដំណើរការសំឡេងជុំវិញនៅក្នុងការកំណត់សំឡេងប្រព័ន្ធដើម្បីបើកដំណើរការអូឌីយ៉ូ" - "ការដំឡើងអង្គរាវប៉ុស្តិ៍" - "ការដំឡើងអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" - "ការដំឡើងឧបករណ៍ USB រាវប៉ុស្តិ៍" - "សូមផ្ទៀងផ្ទាត់ថាប៉ុស្តិ៍ទូរទស្សន៍របស់អ្នកត្រូវបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង ឬទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងខ្ពស់ និងនៅជិតបង្អួច។" - "ផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវកែសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" - - "បន្ត" - "មិនមែនឥឡូវនេះទេ" - - "ដំណើរការដំឡើងប៉ុស្តិ៍ឡើងវិញឬទេ?" - "វានឹងលុបប៉ុស្តិ៍ដែលបានរកឃើញដោយអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍ចេញ ហើយស្កេនរកប៉ុស្តិ៍ថ្មីម្តងទៀត។\n\nផ្ទៀងផ្ទាត់ថាអ្នកបានដោតទូរទស្សន៍ទៅប្រភពរលកសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" - "វានឹងលុបប៉ុស្តិ៍ដែលបានរកឃើញដោយឧបករណ៍ USB រាវប៉ុស្តិ៍ចេញ ហើយស្កេនរកប៉ុស្តិ៍ថ្មីម្តងទៀត។\n\nផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ អ្នកប្រហែលជាត្រូវសម្រួលទីតាំង និងទិសដៅរបស់វាដើម្បីទទួលបានប៉ុស្តិ៍ច្រើនបំផុត។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច។" - - "បន្ត" - "បោះបង់" - - "ជ្រើសប្រភេទនៃការតភ្ជាប់" - "សូមជ្រើសរើសអង់តែន ប្រសិនបើអ្នកភ្ជាប់អង់តែនខាងក្រៅទៅនឹងអង្គរាវប៉ុស្តិ៍។ ឬជ្រើសរើសខ្សែកាប ប្រសិនបើប៉ុស្តិ៍របស់អ្នកផ្តល់ដោយក្រុមហ៊ុនផ្តល់សេវាខ្សែកាប។ ប្រសិនបើអ្នកមិនប្រាកដទេ អ្នកអាចជ្រើសរើសប្រភេទទាំងពីរដើម្បីស្កេន ប៉ុន្តែវាអាចចំណាយពេលយូរ។" - - "អង់តែន" - "ខ្សែកាប" - "មិនប្រាកដ" - "ការអភិវឌ្ឍន៍ប៉ុណ្ណោះ" - - "ការដំឡើងអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" - "ការដំឡើងឧបករណ៍ USB រាវប៉ុស្តិ៍ទូរទស្សន៍" - "វាអាចចំណាយពេលច្រើននាទី" - "អង្គរាវប៉ុស្តិ៍មិនអាចប្រើបានជាបណ្តោះអាសន្ន ឬបានប្រើសម្រាប់ការថតរួចទៅហើយ។" - - បានរកឃើញប៉ុស្តិ៍ %1$d - បានរកឃើញប៉ុស្តិ៍ %1$d - - "បញ្ឈប់ការស្កេនរកប៉ុស្តិ៍" - - បានរកឃើញប៉ុស្តិ៍ %1$d - បានរកឃើញប៉ុស្តិ៍ %1$d - - - ល្អណាស់! បានរកឃើញប៉ុស្តិ៍ %1$d អំឡុងពេលស្កេន។ ប្រសិនបើការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ទេ សូមសាកល្បងសម្រួលទីតាំងអង់តែន ហើយស្កេនម្តងទៀត។ - ល្អណាស់! បានរកឃើញប៉ុស្តិ៍ %1$d អំឡុងពេលស្កេន។ ប្រសិនបើការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ទេ សូមសាកល្បងសម្រួលទីតាំងអង់តែន ហើយស្កេនម្តងទៀត។ - - - "រួចរាល់" - "ស្កេនម្តងទៀត" - - "រកមិនឃើញប៉ុស្តិ៍ទេ" - "ការស្កេននេះរកមិនឃើញប៉ុស្តិ៍ណាមួយឡើយ។ សូមផ្ទៀងផ្ទាត់ថាប៉ុស្តិ៍ទូរទស្សន៍របស់អ្នកត្រូវបានភ្ជាប់ទៅប្រភពរលកសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង ឬទិសដៅរបស់វា។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងខ្ពស់ និងនៅជិតបង្អួច បន្ទាប់មកធ្វើការស្កេនម្តងទៀត។" - "ការស្កេនរកមិនឃើញប៉ុស្តិ៍ទេ។ ផ្ទៀងផ្ទាត់ថាអ្នកបានដោតឧបករណ៍ USB រាវប៉ុស្តិ៍ និងបានភ្ជាប់ទៅប្រភពសញ្ញាទូរទស្សន៍។\n\nប្រសិនបើអ្នកប្រើអង់តែនឥតខ្សែ សូមសម្រួលទីតាំង និងទិសដៅរបស់វា។ ដើម្បីទទួលបានលទ្ធផលល្អបំផុត សូមដាក់វានៅកន្លែងដែលខ្ពស់ក្បែរបង្អួច ហើយបន្ទាប់មកស្កេនម្តងទៀត។" - - "ស្កេនម្តងទៀត" - "រួចរាល់" - - "ស្កេនរកប៉ុស្តិ៍ទូរទស្សន៍" - "ការដំឡើងអង្គរាវប៉ុស្តិ៍ទូរទស្សន៍" - "ការដំឡើងឧបករណ៍ USB រាវប៉ុស្តិ៍ទូរទស្សន៍" - "អង្គរាវប៉ុស្តិ៍ USB សម្រាប់ទូរទស្សន៍ត្រូវបានផ្តាច់" - diff --git a/usbtuner-res/values-kn-rIN/strings.xml b/usbtuner-res/values-kn-rIN/strings.xml new file mode 100644 index 00000000..af903462 --- /dev/null +++ b/usbtuner-res/values-kn-rIN/strings.xml @@ -0,0 +1,90 @@ + + + + + "ಟಿವಿ ಟ್ಯೂನರ್" + "USB ಟಿವಿ ಟ್ಯೂನರ್" + "ನೆಟ್‌ವರ್ಕ್ ಟಿವಿ ಟ್ಯೂನರ್ (ಬೀಟಾ)‌‌" + "ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವುದನ್ನು ಪೂರೈಸಲು ದಯವಿಟ್ಟು ಕಾಯಿರಿ" + "ಟ್ಯೂನರ್ ಸಾಫ್ಟ್‌ವೇರ್‍ ಅನ್ನು ಇತ್ತೀಚಿಗೆ ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾಗಿದೆ. ದಯವಿಟ್ಟು ಚಾನಲ್‌ಗಳನ್ನು ಮರು-ಸ್ಕ್ಯಾನ್‌ ಮಾಡಿ." + "ಆಡಿಯೊ ಸಕ್ರಿಯಗೊಳಿಸಲು ಸಿಸ್ಟಂ ಧ್ವನಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಸರೌಂಡ್ ಧ್ವನಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ" + "ಆಡಿಯೊವನ್ನು ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ದಯವಿಟ್ಟು ಬೇರೊಂದು ಟಿವಿಯಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ" + "ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "USB ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." + "USB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." + "ನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನರ್ ಆನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಅದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." + + "ಮುಂದುವರಿಸು" + "ಸದ್ಯಕ್ಕೆ ಬೇಡ" + + "ಚಾನಲ್ ಸೆಟಪ್ ಅನ್ನು ಮರುರನ್ ಮಾಡುವುದೇ?" + "ಟಿವಿ ಟ್ಯೂನರ್‌ನಿಂದ ಪತ್ತೆ ಮಾಡಲಾದ ಚಾನಲ್‌ಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ ಹಾಗೂ ಮತ್ತೆ ಹೊಸ ಚಾನಲ್‌ಗಳಿಗೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ.\n\nನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." + "USB ಟ್ಯೂನರ್‌ನಿಂದ ಪತ್ತೆ ಮಾಡಲಾದ ಚಾನಲ್‌ಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ ಹಾಗೂ ಮತ್ತೆ ಹೊಸ ಚಾನಲ್‌ಗಳಿಗೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ.\n\nUSB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." + "ನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನರ್‌ನಿಂದ ಪತ್ತೆ ಮಾಡಲಾದ ಚಾನಲ್‌ಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ ಹಾಗೂ ಮತ್ತೆ ಹೊಸ ಚಾನಲ್‌ಗಳಿಗೆ ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತದೆ.\n\nನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನಲ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಅದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." + + "ಮುಂದುವರಿಸು" + "ರದ್ದುಮಾಡಿ" + + "ಸಂಪರ್ಕದ ಪ್ರಕಾರ ಆಯ್ಕೆಮಾಡಿ" + "ಟ್ಯೂನರ್‌ಗೆ ಬಾಹ್ಯ ಆಂಟೆನಾವನ್ನು ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದ್ದರೆ ಆಂಟೆನಾ ಆರಿಸಿಕೊಳ್ಳಿ. ನಿಮ್ಮ ಚಾನಲ್‌ಗಳು ಕೇಬಲ್ ಸೇವೆ ಪೂರೈಕೆದಾರರಿಂದ ಬರುತ್ತಿದ್ದರೆ ಕೇಬಲ್ ಆರಿಸಿಕೊಳ್ಳಿ. ನಿಮಗೆ ಯಾವುದು ಎಂದು ಖಚಿತವಿಲ್ಲದಿದ್ದರೆ, ಎರಡೂ ಪ್ರಕಾರಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ, ಆದರೆ ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು." + + "ಆಂಟೆನಾ" + "ಕೇಬಲ್" + "ಖಚಿತವಾಗಿಲ್ಲ" + "ಅಭಿವೃದ್ಧಿ ಮಾತ್ರ" + + "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "USB ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ನೆಟ್‌ವರ್ಕ್ ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ಇದಕ್ಕೆ ಹಲವಾರು ನಿಮಿಷಗಳು ತೆಗೆದುಕೊಳ್ಳಬಹುದು" + "ಟ್ಯೂನರ್ ತಾತ್ಕಾಲಿಕವಾಗಿ ಲಭ್ಯವಿಲ್ಲ ಅಥವಾ ಈಗಾಗಲೇ ರೆಕಾರ್ಡಿಂಗ್‌ಗೆ ಬಳಸಲಾಗಿದೆ." + + %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ + %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ + + "ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಮಾಡುವುದನ್ನು ನಿಲ್ಲಿಸಿ" + + %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ + %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ + + + ಉತ್ತಮ! ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಸಮಯದಲ್ಲಿ %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ. ಇದು ಸರಿಯಲ್ಲವೆಂದು ತೋರಿದರೆ, ಆಂಟೆನಾ ಸ್ಥಿತಿಯನ್ನು ಹೊಂದಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ. + ಉತ್ತಮ! ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಸಮಯದಲ್ಲಿ %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ. ಇದು ಸರಿಯಲ್ಲವೆಂದು ತೋರಿದರೆ, ಆಂಟೆನಾ ಸ್ಥಿತಿಯನ್ನು ಹೊಂದಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ. + + + "ಮುಗಿದಿದೆ" + "ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡು" + + "ಯಾವುದೇ ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ" + "ಸ್ಕ್ಯಾನ್ ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿಲ್ಲ. ನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." + "ಸ್ಕ್ಯಾನ್ ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿಲ್ಲ. USB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಸರಿಹೊಂದಿಸಿ. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." + "ಸ್ಕ್ಯಾನ್ ನಂತರ ಯಾವುದೇ ಚಾನಲ್ ಕಂಡುಬಂದಿಲ್ಲ. ನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನರ್ ಆನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಅದರ ಸ್ಥಳ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಿ. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಅದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೊಮ್ಮೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." + + "ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡು" + "ಮುಗಿದಿದೆ" + + "ಟಿವಿ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿ" + "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "USB ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "ನೆಟ್‌ವರ್ಕ್ ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" + "USB ಟಿವಿ ಟ್ಯೂನರ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ." + "ನೆಟ್‌ವರ್ಕ್ ಟ್ಯೂನರ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ." + diff --git a/usbtuner-res/values-kn/strings.xml b/usbtuner-res/values-kn/strings.xml deleted file mode 100644 index fb97ae2b..00000000 --- a/usbtuner-res/values-kn/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ಟಿವಿ ಟ್ಯೂನರ್" - "USB ಟಿವಿ ಟ್ಯೂನರ್" - "ಆನ್" - "ಆಫ್" - "ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವುದನ್ನು ಪೂರೈಸಲು ದಯವಿಟ್ಟು ಕಾಯಿರಿ" - "ನಿಮ್ಮ ಚಾನಲ್ ಮೂಲವನ್ನು ಆಯ್ಕೆಮಾಡಿ" - "ಯಾವುದೇ ಸಂಕೇತವಿಲ್ಲ" - "%s ಗೆ ಟ್ಯೂನ್ ಮಾಡುವಲ್ಲಿ ವಿಫಲವಾಗಿದೆ" - "ಟ್ಯೂನ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ" - "ಟ್ಯೂನರ್ ಸಾಫ್ಟ್‌ವೇರ್‍ ಅನ್ನು ಇತ್ತೀಚಿಗೆ ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾಗಿದೆ. ದಯವಿಟ್ಟು ಚಾನಲ್‌ಗಳನ್ನು ಮರು-ಸ್ಕ್ಯಾನ್‌ ಮಾಡಿ." - "ಆಡಿಯೊ ಸಕ್ರಿಯಗೊಳಿಸಲು ಸಿಸ್ಟಂ ಧ್ವನಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಸರೌಂಡ್ ಧ್ವನಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ" - "ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "USB ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "ನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಾನ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." - "USB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಾನ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." - - "ಮುಂದುವರಿಸು" - "ಸದ್ಯಕ್ಕೆ ಬೇಡ" - - "ಚಾನಲ್ ಸೆಟಪ್ ಅನ್ನು ಮರುರನ್ ಮಾಡುವುದೇ?" - "ಟಿವಿ ಟ್ಯೂನರ್‌ನಿಂದ ಪತ್ತೆ ಮಾಡಲಾದ ಚಾನಲ್‌ಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ ಹಾಗೂ ಮತ್ತೆ ಹೊಸ ಚಾನಲ್‌ಗಳಿಗೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ.\n\nನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಾನ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ." - "USB ಟ್ಯೂನರ್‌ನಿಂದ ಪತ್ತೆ ಮಾಡಲಾದ ಚಾನಲ್‌ಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ ಹಾಗೂ ಮತ್ತೆ ಹೊಸ ಚಾನಲ್‌ಗಳಿಗೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ.\n\nUSB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚಿನ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಅದರ ಸ್ಥಾನ ಅಥವಾ ದಿಕ್ಕನ್ನು ಹೊಂದಿಸಬೇಕಾಗಬಹುದು. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." - - "ಮುಂದುವರಿಸು" - "ರದ್ದುಮಾಡಿ" - - "ಸಂಪರ್ಕದ ಪ್ರಕಾರ ಆಯ್ಕೆಮಾಡಿ" - "ಟ್ಯೂನರ್‌ಗೆ ಬಾಹ್ಯ ಆಂಟೆನಾವನ್ನು ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದ್ದರೆ ಆಂಟೆನಾ ಆರಿಸಿಕೊಳ್ಳಿ. ನಿಮ್ಮ ಚಾನಲ್‌ಗಳು ಕೇಬಲ್ ಸೇವೆ ಪೂರೈಕೆದಾರರಿಂದ ಬರುತ್ತಿದ್ದರೆ ಕೇಬಲ್ ಆರಿಸಿಕೊಳ್ಳಿ. ನಿಮಗೆ ಯಾವುದು ಎಂದು ಖಚಿತವಿಲ್ಲದಿದ್ದರೆ, ಎರಡೂ ಪ್ರಕಾರಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ, ಆದರೆ ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು." - - "ಆಂಟೆನಾ" - "ಕೇಬಲ್" - "ಖಚಿತವಾಗಿಲ್ಲ" - "ಅಭಿವೃದ್ಧಿ ಮಾತ್ರ" - - "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "USB ಚಾನಲ್ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "ಇದಕ್ಕೆ ಹಲವಾರು ನಿಮಿಷಗಳು ತೆಗೆದುಕೊಳ್ಳಬಹುದು" - "ಟ್ಯೂನರ್ ತಾತ್ಕಾಲಿಕವಾಗಿ ಲಭ್ಯವಿಲ್ಲ ಅಥವಾ ಈಗಾಗಲೇ ರೆಕಾರ್ಡಿಂಗ್‌ಗೆ ಬಳಸಲಾಗಿದೆ." - - %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ - %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ - - "ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಮಾಡುವುದನ್ನು ನಿಲ್ಲಿಸು" - - %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ - %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ - - - ಉತ್ತಮ! ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಸಮಯದಲ್ಲಿ %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ. ಇದು ಸರಿಯಲ್ಲವೆಂದು ತೋರಿದರೆ, ಆಂಟೆನಾ ಸ್ಥಿತಿಯನ್ನು ಹೊಂದಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ. - ಉತ್ತಮ! ಚಾನಲ್ ಸ್ಕ್ಯಾನ್ ಸಮಯದಲ್ಲಿ %1$d ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿವೆ. ಇದು ಸರಿಯಲ್ಲವೆಂದು ತೋರಿದರೆ, ಆಂಟೆನಾ ಸ್ಥಿತಿಯನ್ನು ಹೊಂದಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ. - - - "ಮುಗಿದಿದೆ" - "ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡು" - - "ಯಾವುದೇ ಚಾನಲ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ" - "ಸ್ಕ್ಯಾನ್ ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿಲ್ಲ. ನಿಮ್ಮ ಟಿವಿಯನ್ನು ಟಿವಿ ಸಿಗ್ನಲ್‌ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nನೀವು ಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಇದನ್ನು ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." - "ಸ್ಕ್ಯಾನ್ ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿಲ್ಲ. USB ಟ್ಯೂನಲ್ ಅನ್ನು ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆಯೇ ಮತ್ತು ಟಿವಿ ಸಿಗ್ನಲ್ ಮೂಲಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆಯೆ ಎಂದು ಪರಿಶೀಲಿಸಿ.\n\nಪ್ರಸಾರದ ಮೂಲಕ ಆಂಟೆನಾ ಬಳಸುತ್ತಿದ್ದರೆ, ಅದರ ಸ್ಥಾನ ಅಥವಾ ದಿಕ್ಕನ್ನು ಸರಿಹೊಂದಿಸಿ. ಉತ್ತಮ ಫಲಿತಾಂಶಗಳಿಗೆ, ಎತ್ತರದಲ್ಲಿ ಮತ್ತು ಕಿಟಕಿಯ ಬಳಿ ಇರಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ." - - "ಮತ್ತೆ ಸ್ಕ್ಯಾನ್ ಮಾಡು" - "ಮುಗಿದಿದೆ" - - "ಟಿವಿ ಚಾನಲ್‌ಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿ" - "ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "USB ಟಿವಿ ಟ್ಯೂನರ್ ಸೆಟಪ್" - "USB ಟಿವಿ ಟ್ಯೂನರ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ." - diff --git a/usbtuner-res/values-ko/strings.xml b/usbtuner-res/values-ko/strings.xml index 2c67ab6f..fa5b6e60 100644 --- a/usbtuner-res/values-ko/strings.xml +++ b/usbtuner-res/values-ko/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV 튜너" "USB TV 튜너" - "사용" - "사용 안함" + "네트워크 TV 튜너(BETA)" "처리가 완료될 때까지 기다려 주세요." - "채널 소스 선택" - "신호 없음" - "%s에 맞추지 못함" - "조정 실패" "튜너 소프트웨어가 최근 업데이트되었습니다. 채널을 다시 스캔하세요." "오디오를 사용하려면 시스템 사운드 설정에서 서라운드 사운드를 사용 설정하세요." + "오디오를 재생할 수 없습니다. 다른 TV를 사용해 보세요." "채널 튜너 설정" "TV 튜너 설정" "USB 채널 튜너 설정" + "네트워크 튜너 설정" "TV가 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 대부분의 채널을 수신하려면 위치나 방향을 조정해야 할 수도 있습니다. 안테나를 창가 가까이에 높게 설치하면 가장 좋습니다." "USB 튜너가 전원 및 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용 중인 경우 안테나 위치나 방향을 조정하세요. 창가 가까이에 높게 설치하면 가장 좋습니다." + "네트워크 튜너의 전원이 켜져 있고 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 안테나 위치나 방향을 조정하세요. 최상의 결과를 얻으려면 안테나를 창가 가까이에 높게 설치하세요." "계속" "나중에" @@ -40,6 +38,7 @@ "채널 설정을 다시 실행하시겠습니까?" "이렇게 하면 TV 튜너에서 찾은 채널을 삭제하고 새로운 채널을 다시 스캔하게 됩니다.\n\nTV가 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 대부분의 채널을 수신하려면 위치나 방향을 조정해야 할 수 있습니다. 안테나를 창가 가까이에 높게 설치하면 가장 좋습니다." "이 작업을 수행하면 USB 튜너에서 찾은 채널이 삭제되며 새로운 채널을 다시 스캔합니다.\n\nUSB 튜너가 전원 및 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용 중인 경우 안테나 위치나 방향을 조정하세요. 창가 가까이에 높게 설치하면 가장 좋습니다." + "네트워크 튜너에서 찾은 채널이 삭제되며 새로운 채널을 다시 스캔합니다.\n\n네트워크 튜너의 전원이 켜져 있고 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 안테나 위치나 방향을 조정하세요. 최상의 결과를 얻으려면 안테나를 창가 가까이에 높게 설치하고 다시 스캔해보세요." "계속" "취소" @@ -54,8 +53,9 @@ "TV 튜너 설정" "USB 채널 튜너 설정" + "네트워크 채널 튜너 설정" "이 작업은 몇 분 정도 걸릴 수 있습니다." - "일시적으로 튜너를 사용할 수 없거나 녹화에서 이미 사용하고 있습니다." + "일시적으로 튜너를 사용할 수 없거나 이미 녹화에 사용하고 있습니다." 채널 %1$d개 발견 채널 %1$d개 발견 @@ -76,12 +76,15 @@ "채널 없음" "스캔 결과 채널을 찾지 못했습니다. TV가 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 위치나 방향을 조정하세요. 안테나를 창가 가까이에 높게 설치하면 가장 좋습니다." "스캔하여 채널을 찾을 수 없습니다. USB 튜너가 전원 및 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용 중인 경우 안테나 위치나 방향을 조정하세요. 창가 가까이에 높게 설치하면 가장 좋습니다. 그런 다음 다시 스캔해 보세요." + "스캔 결과 채널을 찾지 못했습니다. 네트워크 튜너의 전원이 켜져 있고 TV 신호 소스에 연결되어 있는지 확인하세요.\n\n무선 안테나를 사용하는 경우 안테나 위치나 방향을 조정하세요. 최상의 결과를 얻으려면 안테나를 창가 가까이에 높게 설치하고 다시 스캔해보세요." "다시 스캔" "완료" - "TV 채널 스캔" - "TV 튜너 설정" - "USB TV 튜너 설정" - "USB TV 튜너 연결 끊어짐" + "TV 채널 스캔" + "TV 튜너 설정" + "USB TV 튜너 설정" + "네트워크 TV 튜너 설정" + "USB TV 튜너의 연결이 끊겼습니다." + "네트워크 튜너의 연결이 끊겼습니다." diff --git a/usbtuner-res/values-ky-rKG/strings.xml b/usbtuner-res/values-ky-rKG/strings.xml new file mode 100644 index 00000000..18118225 --- /dev/null +++ b/usbtuner-res/values-ky-rKG/strings.xml @@ -0,0 +1,90 @@ + + + + + "Сыналгы күүлөгүчү" + "USB сыналгы күүлөгүчү" + "Тармактык ТВ-тюнер (БЕТА)" + "Иштетүүнү бүтүрүү үчүн күтө туруңуз" + "Күүлөгүчтүн программасы жакында жаңыртылды. Каналдарды кайрадан издеңиз." + "Каналдын үнүн чыгаруу үчүн тутумдун үн жөндөөлөрүнө өтүп, көлөмдүү добушту иштетүү керек" + "Аудио ойнотулбай жатат. Башка каналды байкап көрүңүз" + "Канал күүлөгүчтү жөндөө" + "Сыналгынын күүлөгүчүн жөндөө" + "USB канал күүлөгүчүнүн жөндөөсү" + "Тармактык тюнерди жөндөө" + "Сыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз. \n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "USB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "Тармактык тюнер сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Антенна жакшы кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеп көрүңүз." + + "Улантуу" + "Азыр эмес" + + "Канал кайра жөндөлсүнбү?" + "Ушуну менен сыналгынын күүлөгүчүнөн табылган каналдар алынып салынып, жаңы каналдар кайра изделет.\n\nСыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nIЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "Ушуну менен USB күүлөгүчтөн табылган каналдар алынып салынып, жаңы каналдар кайра изделет.\n\nUSB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "Ушуну менен тармактык тюнер аркылуу табылган каналдар алынып салынып, жаңы каналдар кайра изделет.\n\nТармактык тюнер сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Антенна жакшы кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеп көрүңүз." + + "Улантуу" + "Токтотуу" + + "Туташуу түрүн тандаңыз" + "Күүлөгүчтө туташтырылган тышкы антенна болсо, Антеннаны тандаңыз. Эгер каналдарыңыз кабелдик кызмат камсыздоочусунан алынса, Кабелди тандаңыз. Эгер так билбесеңиз, эки түрү тең изделет, бирок ал узагыраак созулушу мүмкүн." + + "Антенна" + "Кабель" + "Так айта албайм" + "Иштеп чыгуучулар үчүн гана" + + "Сыналгы күүлөгүчүн жөндөө" + "USB канал күүлөгүчүн жөндөө" + "Тармактык каналдын тюнерин жөндөө" + "Бир нече мүнөт созулушу мүмкүн" + "Тюнер убактылуу жеткиликсиз же жаздыруу үчүн колдонулууда." + + %1$d канал табылды + %1$d канал табылды + + "КАНАЛ ИЗДӨӨНҮ ТОКТОТУУ" + + %1$d канал табылды + %1$d канал табылды + + + Сонун! %1$d канал табылды. Керектүү каналдар табылбаса, антеннанын багытын өзгөртүп, кайра издеп көрүңүз. + Сонун! %1$d канал табылды. Керектүү каналдар табылбаса, антеннанын багытын өзгөртүп, кайра издеп көрүңүз. + + + "Бүттү" + "Кайра издөө" + + "Бир да канал табылган жок" + "Издөөдөн эч бир канал табылган жок. Сыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "Издөөдөн эч бир канал табылган жок. USB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." + "Каналдар табылган жок. Тармактык тюнер сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Антенна жакшы кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеп көрүңүз." + + "Кайра издөө" + "Бүттү" + + "Сыналгы каналдарын издөө" + "ТВ-тюнерди жөндөө" + "USB ТВ-тюнерин жөндөө" + "Тармактык ТВ-тюнерди жөндөө" + "USB TV күүлөгүчү ажыратылды." + "Тармак күүлөгүчү ажыратылды." + diff --git a/usbtuner-res/values-ky/strings.xml b/usbtuner-res/values-ky/strings.xml deleted file mode 100644 index 39e9d1df..00000000 --- a/usbtuner-res/values-ky/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Сыналгы күүлөгүчү" - "USB сыналгы күүлөгүчү" - "Күйүк" - "Өчүк" - "Иштетүүнү бүтүрүү үчүн күтө туруңуз" - "Каналыңыздын булагын тандаңыз" - "Сигнал жок" - "%s каналы кармалган жок" - "Канал кармалбай койду" - "Күүлөгүчтүн программасы жакында жаңыртылды. Каналдарды кайрадан издеңиз." - "Каналдын үнүн чыгаруу үчүн тутумдун үн жөндөөлөрүнө өтүп, көлөмдүү добушту иштетүү керек" - "Канал күүлөгүчтү жөндөө" - "Сыналгынын күүлөгүчүн жөндөө" - "USB канал күүлөгүчүнүн жөндөөсү" - "Сыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз. \n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - "USB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - - "Улантуу" - "Азыр эмес" - - "Канал кайра жөндөлсүнбү?" - "Ушуну менен сыналгынын күүлөгүчүнөн табылган каналдар алынып салынып, жаңы каналдар кайра изделет.\n\nСыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nIЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - "Ушуну менен USB күүлөгүчтөн табылган каналдар алынып салынып, жаңы каналдар кайра изделет.\n\nUSB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, көпчүлүк каналдарды табуу үчүн анын ордун же багытын тууралашыңыз керек болот. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - - "Улантуу" - "Токтотуу" - - "Туташуу түрүн тандаңыз" - "Күүлөгүчтө туташтырылган тышкы антенна болсо, Антеннаны тандаңыз. Эгер каналдарыңыз кабелдик кызмат камсыздоочусунан алынса, Кабелди тандаңыз. Эгер так билбесеңиз, эки түрү тең изделет, бирок ал узагыраак созулушу мүмкүн." - - "Антенна" - "Кабель" - "Так айта албайм" - "Иштеп чыгуучулар үчүн гана" - - "Сыналгы күүлөгүчүн жөндөө" - "USB канал күүлөгүчүн жөндөө" - "Бир нече мүнөт созулушу мүмкүн" - "Тюнер убактылуу жеткиликсиз же жаздыруу үчүн колдонулууда." - - %1$d канал табылды - %1$d канал табылды - - "КАНАЛ ИЗДӨӨНҮ ТОКТОТУУ" - - %1$d канал табылды - %1$d канал табылды - - - Сонун! %1$d канал табылды. Керектүү каналдар табылбаса, антеннанын багытын өзгөртүп, кайра издеп көрүңүз. - Сонун! %1$d канал табылды. Керектүү каналдар табылбаса, антеннанын багытын өзгөртүп, кайра издеп көрүңүз. - - - "Бүттү" - "Кайра издөө" - - "Бир да канал табылган жок" - "Издөөдөн эч бир канал табылган жок. Сыналдыңыз сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - "Издөөдөн эч бир канал табылган жок. USB күүлөгүч сайылып турганын жана сыналгынын сигнал булагына туташтырылганын текшериңиз.\n\nЭгер телеантенна колдонулуп жатса, анын ордун же багытын тууралаңыз. Мыкты кармашы үчүн аны бийик жана терезеге жакын жерге коюп, кайра издеңиз." - - "Кайра издөө" - "Бүттү" - - "Сыналгы каналдарын издөө" - "Сыналгы күүлөгүчүн жөндөө" - "USB күүлөгүчүнүн жөндөөсү" - "USB TV күүлөгүчү ажыратылды." - diff --git a/usbtuner-res/values-lo-rLA/strings.xml b/usbtuner-res/values-lo-rLA/strings.xml new file mode 100644 index 00000000..306576a2 --- /dev/null +++ b/usbtuner-res/values-lo-rLA/strings.xml @@ -0,0 +1,90 @@ + + + + + "ຕົວຮັບສັນຍານໂທລະພາບ" + "ຕົວຮັບສັນຍານໂທລະພາບ USB" + "Network TV Tuner (ເບຕ້າ)" + "ກະລຸນາລໍຖ້າເພື່ອປະມວນຜົນໃຫ້ສຳເລັດ" + "ຊອບແວຕົວປັບສັນຍານໄດ້ຮັບການອັບເດດເມື່ອບໍ່ດົນມານີ້ແລ້ວ. ກະລຸນາສະແກນຫາຊ່ອງຄືນໃໝ່." + "ເປີດໃຊ້ສຽງຮອບທິດທາງໃນການຕັ້ງຄ່າລະບົບສຽງເພື່ອເປີດໃຊ້ສຽງ." + "Cannot play audio. Please try another TV" + "ຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ" + "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບ" + "ຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ USB" + "Network tuner setup" + "ໃຫ້ກວດສອບວ່າທ່ານເຊື່ອມຕໍ່ໂທລະພາບຂອງທ່ານຫາແຫລ່ງສັນຍານໂທລະພາບແລ້ວ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າ ໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານເຄືອຂ່າຍວ່າ ໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫຼຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ່ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + + "ສືບຕໍ່" + "ບໍ່ແມ່ນຕອນນີ້" + + "ຕັ້ງຄ່າຊ່ອງຄືນໃໝ່ບໍ່?" + "ນີ້ເປັນການລຶບຊ່ອງທີ່ພົບແລ້ວອອກໄປຈາກຕົວຮັບສັນຍານໂທລະພາບ ແລະ ສະແກນຫາຊ່ອງໃໝ່ອີກຄັ້ງ.\n\nກວດສອບເບິ່ງຕົວຮັບສັນຍານໂທລະພາບວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ນີ້ຈະເປັນການລຶບຊ່ອງທີ່ພົບແລ້ວອອກໄປຈາກເຄື່ອງຮັບສັນຍານ USB ແລະ ສະແກນຫາຊ່ອງໃໝ່ອີກ.\n\nໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າ ໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ນີ້ຈະເປັນການລຶບຊ່ອງທີ່ພົບແລ້ວອອກໄປຈາກເຄື່ອງຮັບສັນຍານເຄືອຂ່າຍ ແລະ ສະແກນຫາຊ່ອງໃໝ່ອີກຄັ້ງ.\n\nໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານເຄືອຂ່າຍວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ທ່ານອາດຈະຕ້ອງໄດ້ປັບການຕັ້ງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນດີຂຶ້ນ, ວາງມັນໄວ້ສູງ ແລະ ໃກ້ກັບປ່ອງຢ້ຽມແລ້ວສະແກນໃໝ່ອີກຄັ້ງ." + + "ສືບຕໍ່" + "ຍົກເລີກ" + + "ເລືອກປະເພດການເຊື່ອມຕໍ່" + "ເລືອກເສົາອາກາດ ຖ້າມີເສົາອາກາດແບບແຍກທີ່ເຊື່ອມຕໍ່ກັບເຄື່ອງຮັບສັນຍານ. ເລືອກສາຍເຄເບິ້ນ ຖ້າຊ່ອງຂອງທ່ານມາຈາກຜູ້ໃຫ້ບໍລິການສາຍເຄເບິ້ນ. ຖ້າທ່ານບໍ່ແນ່ໃຈ, ຈະມີການສະແກນທັງສອງປະເພດ, ແຕ່ແບບນີ້ຈະໃຊ້ເວລາດົນກວ່າ." + + "ເສົາອາກາດ" + "ສາຍຕໍ່" + "ບໍ່ແນ່ໃຈ" + "ການພັດທະນາເທົ່ານັ້ນ" + + "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບ" + "ການຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ USB" + "ຕັ້ງຄ່າຕົວຈູນສັນຍານຊ່ອງເຄືອຂ່າຍ" + "ຂັ້ນຕອນນີ້ອາດຈະໃຊ້ເວລາຫຼາຍນາທີ" + "ຈູນເນີບໍ່ສາມາດໃຊ້ໄດ້ຊົ່ວຄາວ ຫຼື ຖືກໃຊ້ໂດຍການບັນທຶກໃດໜຶ່ງຢູ່ກ່ອນແລ້ວ." + + ພົບ %1$d ຊ່ອງ + ພົບ %1$d ຊ່ອງ + + "ຢຸດການສະແກນຊ່ອງ" + + ພົບ %1$d ຊ່ອງ + ພົບ %1$d ຊ່ອງ + + + ດີຫຼາຍ! ພົບ %1$d ຊ່ອງໃນລະຫວ່າງການສະແກນຫາຊ່ອງ. ຖ້ານີ້ປາກົດວ່າບໍ່ຖືກຕ້ອງ, ໃຫ້ລອງປັບຕຳແໜ່ງເສົາອາກາດ ແລ້ວສະແກນໃໝ່. + ດີຫຼາຍ! ພົບ %1$d ຊ່ອງໃນລະຫວ່າງການສະແກນຫາຊ່ອງ. ຖ້ານີ້ປາກົດວ່າບໍ່ຖືກຕ້ອງ, ໃຫ້ລອງປັບຕຳແໜ່ງເສົາອາກາດ ແລ້ວສະແກນໃໝ່. + + + "ແລ້ວໆ" + "ສະແກນອີກ" + + "ບໍ່ພົບຊ່ອງໃດ" + "ການສະແກນບໍ່ພົບຊ່ອງໃດໆເລີຍ. ໃຫ້ຢັ້ງຢືນວ່າທ່ານເຊື່ອມຕໍ່ໂທລະພາບຂອງທ່ານຫາແຫລ່ງສັນຍານໂທລະພາບແລ້ວ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ການສະແກນບໍ່ພົບຊ່ອງໃດ. ໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + "ການສະແກນບໍ່ພົບຊ່ອງໃດ. ໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານເຄືອຂ່າຍວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." + + "ສະແກນອີກ" + "ແລ້ວໆ" + + "ສະແກນຫາຊ່ອງໂທລະທັດ" + "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະທັດ" + "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະທັດແບບ USB" + "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະທັດເຄືອຂ່າຍ" + "ຕັດການເຊື່ອມຕໍ່ USB TV tuner ແລ້ວ." + "ຕັດການເຊື່ອມຕໍ່ຕົວຈູນສັນຍານເຄືອຂ່າຍແລ້ວ." + diff --git a/usbtuner-res/values-lo/strings.xml b/usbtuner-res/values-lo/strings.xml deleted file mode 100644 index 954baabe..00000000 --- a/usbtuner-res/values-lo/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ຕົວຮັບສັນຍານໂທລະພາບ" - "ຕົວຮັບສັນຍານໂທລະພາບ USB" - "ເປີດ" - "ປິດ" - "ກະລຸນາລໍຖ້າເພື່ອປະມວນຜົນໃຫ້ສຳເລັດ" - "ເລືອກແຫຼ່ງສັນຍານຊ່ອງຂອງທ່ານ" - "ບໍ່ມີສັນຍານ" - "ປັບຫາ %s ບໍ່ສຳເລັດ" - "ປັບຊ່ອງບໍ່ສຳເລັດ" - "ຊອບແວຕົວປັບສັນຍານໄດ້ຮັບການອັບເດດເມື່ອບໍ່ດົນມານີ້ແລ້ວ. ກະລຸນາສະແກນຫາຊ່ອງຄືນໃໝ່." - "ເປີດໃຊ້ສຽງຮອບທິດທາງໃນການຕັ້ງຄ່າລະບົບສຽງເພື່ອເປີດໃຊ້ສຽງ." - "ຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ" - "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບ" - "ຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ USB" - "ໃຫ້ກວດສອບວ່າທ່ານເຊື່ອມຕໍ່ໂທລະພາບຂອງທ່ານຫາແຫລ່ງສັນຍານໂທລະພາບແລ້ວ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - "ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າ ໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - - "ສືບຕໍ່" - "ບໍ່ແມ່ນຕອນນີ້" - - "ຕັ້ງຄ່າຊ່ອງຄືນໃໝ່ບໍ່?" - "ນີ້ເປັນການລຶບຊ່ອງທີ່ພົບແລ້ວອອກໄປຈາກຕົວຮັບສັນຍານໂທລະພາບ ແລະ ສະແກນຫາຊ່ອງໃໝ່ອີກຄັ້ງ.\n\nກວດສອບເບິ່ງຕົວຮັບສັນຍານໂທລະພາບວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - "ນີ້ຈະເປັນການລຶບຊ່ອງທີ່ພົບແລ້ວອອກໄປຈາກເຄື່ອງຮັບສັນຍານ USB ແລະ ສະແກນຫາຊ່ອງໃໝ່ອີກ.\n\nໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າ ໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - - "ສືບຕໍ່" - "ຍົກເລີກ" - - "ເລືອກປະເພດການເຊື່ອມຕໍ່" - "ເລືອກເສົາອາກາດ ຖ້າມີເສົາອາກາດແບບແຍກທີ່ເຊື່ອມຕໍ່ກັບເຄື່ອງຮັບສັນຍານ. ເລືອກສາຍເຄເບິ້ນ ຖ້າຊ່ອງຂອງທ່ານມາຈາກຜູ້ໃຫ້ບໍລິການສາຍເຄເບິ້ນ. ຖ້າທ່ານບໍ່ແນ່ໃຈ, ຈະມີການສະແກນທັງສອງປະເພດ, ແຕ່ແບບນີ້ຈະໃຊ້ເວລາດົນກວ່າ." - - "ເສົາອາກາດ" - "ສາຍຕໍ່" - "ບໍ່ແນ່ໃຈ" - "ການພັດທະນາເທົ່ານັ້ນ" - - "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບ" - "ການຕັ້ງເຄື່ອງຮັບສັນຍານຊ່ອງ USB" - "ຂັ້ນຕອນນີ້ອາດຈະໃຊ້ເວລາຫຼາຍນາທີ" - "ຈູນເນີບໍ່ສາມາດໃຊ້ໄດ້ຊົ່ວຄາວ ຫຼື ຖືກໃຊ້ໂດຍການບັນທຶກໃດໜຶ່ງຢູ່ກ່ອນແລ້ວ." - - ພົບ %1$d ຊ່ອງ - ພົບ %1$d ຊ່ອງ - - "ຢຸດການສະແກນຊ່ອງ" - - ພົບ %1$d ຊ່ອງ - ພົບ %1$d ຊ່ອງ - - - ດີຫຼາຍ! ພົບ %1$d ຊ່ອງໃນລະຫວ່າງການສະແກນຫາຊ່ອງ. ຖ້ານີ້ປາກົດວ່າບໍ່ຖືກຕ້ອງ, ໃຫ້ລອງປັບຕຳແໜ່ງເສົາອາກາດ ແລ້ວສະແກນໃໝ່. - ດີຫຼາຍ! ພົບ %1$d ຊ່ອງໃນລະຫວ່າງການສະແກນຫາຊ່ອງ. ຖ້ານີ້ປາກົດວ່າບໍ່ຖືກຕ້ອງ, ໃຫ້ລອງປັບຕຳແໜ່ງເສົາອາກາດ ແລ້ວສະແກນໃໝ່. - - - "ແລ້ວໆ" - "ສະແກນອີກ" - - "ບໍ່ພົບຊ່ອງໃດ" - "ການສະແກນບໍ່ພົບຊ່ອງໃດໆເລີຍ. ໃຫ້ຢັ້ງຢືນວ່າທ່ານເຊື່ອມຕໍ່ໂທລະພາບຂອງທ່ານຫາແຫລ່ງສັນຍານໂທລະພາບແລ້ວ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - "ການສະແກນບໍ່ພົບຊ່ອງໃດ. ໃຫ້ກວດສອບເບິ່ງເຄື່ອງຮັບສັນຍານ USB ວ່າໄດ້ສຽບສາຍ ແລະ ເຊື່ອມຕໍ່ກັບແຫຼ່ງສັນຍານໂທລະພາບແລ້ວຫຼືຍັງ.\n\nຫາກໃຊ້ເສົາອາກາດ, ໃຫ້ຍ້າຍບ້ອນວາງ ຫຼື ທິດທາງຂອງມັນ. ເພື່ອໃຫ້ໄດ້ຜົນທີ່ດີທີ່ສຸດ, ໃຫ້ວາງໄວ້ບ່ອນສູງໃກ້ໆປ່ອງຢ້ຽມ ແລ້ວລອງສະແກນໃໝ່." - - "ສະແກນອີກ" - "ແລ້ວໆ" - - "ສະແກນຫາຊ່ອງໂທລະພາບ" - "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບ" - "ຕັ້ງຄ່າຕົວຮັບສັນຍານໂທລະພາບແບບ USB" - "ການເຊື່ອມຕໍ່ USB TV tuner ຖືກຕັດແລ້ວ." - diff --git a/usbtuner-res/values-lt/strings.xml b/usbtuner-res/values-lt/strings.xml index 3ce2efc7..c55020bd 100644 --- a/usbtuner-res/values-lt/strings.xml +++ b/usbtuner-res/values-lt/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV imtuvas" "USB TV imtuvas" - "Įjungta" - "Išjungti" + "Tinklo TV imtuvas (BETA)" "Palaukite, kol baigsis apdorojimas" - "Pasirinkite kanalo šaltinį" - "Nėra signalo" - "Nepavyko įjungti kanalo „%s“" - "Nepavyko suderinti" "Imtuvo programinė įranga buvo neseniai atnaujinta. Iš naujo nuskaitykite kanalus." "Įgalinkite erdvinį garsą sistemos garso nustatymuose, kad galėtumėte įgalinti garsą" + "Negalima paleisti garso įrašo. Bandykite naudoti kitą TV" "Kanalų imtuvo sąranka" "TV imtuvo sąranka" "USB kanalų imtuvo sąranka" + "Tinklo imtuvo sąranka" "Įsitikinkite, kad jūsų TV prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango." "Įsitikinkite, kad USB imtuvas prijungtas prie maitinimo ir TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį, kad būtų rodoma kuo daugiau kanalų. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango." + "Įsitikinkite, kad tinklo imtuvas yra įjungtas ir prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango ir nuskaitykite dar kartą." "Tęsti" "Ne dabar" @@ -40,6 +38,7 @@ "Iš naujo vykdyti kanalų sąranką?" "Tai atlikus bus pašalinti kanalai, kuriuos aptiko TV imtuvas, ir bus vėl ieškoma naujų kanalų.\n\nNuskaičius nerasta jokių kanalų. Įsitikinkite, kad jūsų TV prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango." "Taip bus pašalinti rasti kanalai iš USB imtuvo ir nauji kanalai nuskaityti dar kartą.\n\nĮsitikinkite, kad USB imtuvas prijungtas prie maitinimo ir TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį, kad būtų rodoma kuo daugiau kanalų. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango." + "Atlikus šį veiksmą iš tinklo imtuvo bus pašalinti rasti kanalai ir bus dar kartą nuskaitoma ieškant naujų kanalų.\n\nĮsitikinkite, kad tinklo imtuvas yra įjungtas ir prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango ir nuskaitykite dar kartą." "Tęsti" "Atšaukti" @@ -54,6 +53,7 @@ "TV imtuvo sąranka" "USB kanalų imtuvo sąranka" + "Tinklo kanalų imtuvo sąranka" "Tai gali užtrukti kelias minutes" "Derintuvas laikinai nepasiekiamas arba jau yra naudojamas įrašant." @@ -82,12 +82,15 @@ "Nerasta kanalų" "Nuskaičius nerasta jokių kanalų. Įsitikinkite, kad jūsų TV prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango ir nuskaitykite dar kartą." "Nuskaitant nerasta kanalų. Patvirtinkite, ar USB imtuvas prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango ir nuskaitykite dar kartą." + "Nuskaičius nerasta jokių kanalų. Įsitikinkite, kad tinklo imtuvas yra įjungtas ir prijungtas prie TV signalo šaltinio.\n\nJei naudojate belaidę anteną, koreguokite jos vietą arba kryptį. Kad pasiektumėte geriausių rezultatų, ją padėkite aukštai prie lango ir nuskaitykite dar kartą." "Nuskaityti dar kartą" "Atlikta" - "Vykdykite TV kanalų nuskaitymą" - "TV imtuvo sąranka" - "USB TV imtuvo sąranka" - "USB TV imtuvas atjungtas." + "Nuskaitykite TV kanalus" + "TV imtuvo sąranka" + "USB TV imtuvo sąranka" + "Tinklo TV imtuvo sąranka" + "USB TV imtuvas atjungtas." + "Tinklo imtuvas atjungtas." diff --git a/usbtuner-res/values-lv/strings.xml b/usbtuner-res/values-lv/strings.xml index 9337e7b3..df910fab 100644 --- a/usbtuner-res/values-lv/strings.xml +++ b/usbtuner-res/values-lv/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV kanālu meklētājs" "USB TV kanālu meklētājs" - "Ieslēgta" - "Izslēgta" + "Interneta TV kanālu meklētājs (BETA)" "Lūdzu, uzgaidiet, līdz tiks pabeigta apstrāde!" - "Atlasiet kanāla avotu" - "Nav signāla" - "Neizdevās atrast kanālu %s." - "Neizdevās atrast" "Nesen tika atjaunināta kanālu meklētāja programmatūra. Lūdzu, atkārtoti meklējiet kanālus." "Lai ieslēgtu audio, sistēmas skaņas iestatījumos iespējojiet ieskaujošo skaņu." + "Nevar atskaņot audio. Lūdzu, izmantojiet citu televizoru." "Kanālu meklētāja iestatīšana" "TV kanālu meklētāja iestatīšana" "USB kanālu meklētāja iestatīšana" + "Interneta kanālu meklētāja iestatīšana" "Pārbaudiet, vai televizors ir pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, iespējams, būs jāmaina tās novietojums vai virziens, lai uztvertu lielāko daļu kanālu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga." "Pārbaudiet, vai USB kanālu meklētājs ir pievienots strāvas avotam un TV signāla avotam.\n\nJa izmantojat bezvadu antenu, mainiet tās novietojumu un virzienu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga un atkārtojiet kanālu meklēšanu." + "Pārbaudiet, vai interneta kanālu meklētājs ir ieslēgts un pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, iespējams, jāmaina tās novietojums vai virziens, lai uztvertu lielāko daļu kanālu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga." "Turpināt" "Vēlāk" @@ -40,6 +38,7 @@ "Vai atkārtot kanālu iestatīšanu?" "Šādi no TV kanālu meklētāja tiks noņemti atrastie kanāli un tiks vēlreiz meklēti jauni kanāli.\n\nPārbaudiet, vai televizors ir pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, iespējams, būs jāmaina tās novietojums vai virziens, lai uztvertu lielāko daļu kanālu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga." "Tādējādi tiks noņemti kanāli, kas tika atrasti ar USB kanālu meklētāju, un tiks atkārtota kanālu meklēšana.\n\nPārbaudiet, vai USB kanālu meklētājs ir pievienots strāvas avotam un TV signāla avotam.\n\nJa izmantojat bezvadu antenu, mainiet tās novietojumu un virzienu, lai uztvertu lielāko daļu kanālu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga." + "Šādi no interneta kanālu meklētāja tiks noņemti atrastie kanāli un tiks vēlreiz meklēti jauni kanāli.\n\nPārbaudiet, vai interneta kanālu meklētājs ir ieslēgts un pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, iespējams, jāmaina tās novietojums vai virziens, lai uztvertu lielāko daļu kanālu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga." "Turpināt" "Atcelt" @@ -54,6 +53,7 @@ "TV kanālu meklētāja iestatīšana" "USB kanālu meklētāja iestatīšana" + "Interneta kanālu meklētāja iestatīšana" "Tas var ilgt vairākas minūtes." "Kanālu meklētājs īslaicīgi nav pieejams, vai arī tas jau tiek izmantots ierakstīšanai." @@ -79,12 +79,15 @@ "Netika atrasts neviens kanāls" "Veicot meklēšanu, netika atrasts neviens kanāls. Pārbaudiet, vai televizors pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, mainiet tās novietojumu vai virzienu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga, bet pēc tam vēlreiz veiciet meklēšanu." "Netika atrasts neviens kanāls. Pārbaudiet, vai USB kanālu meklētājs ir pievienots strāvas avotam un TV signāla avotam.\n\nJa izmantojat bezvadu antenu, mainiet tās novietojumu un virzienu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga un atkārtojiet kanālu meklēšanu." + "Netika atrasts neviens kanāls. Pārbaudiet, vai interneta kanālu meklētājs ir ieslēgts un pievienots TV signāla avotam.\n\nJa izmantojat bezvadu antenu, mainiet tās novietojumu un virzienu. Lai iegūtu labākos rezultātus, novietojiet antenu augstu pie loga un atkārtojiet kanālu meklēšanu." "Meklēt vēlreiz" "Gatavs" - "TV kanālu meklēšana" - "TV kanālu meklētāja iestatīšana" - "USB TV kanālu meklētāja iestatīšana" - "USB TV kanālu meklētājs ir atvienots." + "TV kanālu meklēšana" + "TV kanālu meklētāja iestatīšana" + "USB TV kanālu meklētāja iestatīšana" + "Interneta TV kanālu meklētāja iestatīšana" + "USB TV kanālu meklētājs ir atvienots." + "Interneta kanālu meklētājs ir atvienots." diff --git a/usbtuner-res/values-mk-rMK/strings.xml b/usbtuner-res/values-mk-rMK/strings.xml new file mode 100644 index 00000000..2db086e1 --- /dev/null +++ b/usbtuner-res/values-mk-rMK/strings.xml @@ -0,0 +1,90 @@ + + + + + "ТВ приемник" + "ТВ приемник со USB" + "Мрежен ТВ приемник (БЕТА)" + "Почекајте да заврши обработувањето" + "Софтверот на приемникот неодамна е ажуриран. Скенирајте ги каналите повторно." + "Овозможете опкружувачки звук во поставките за звуци на системот за да овозможите аудио" + "Не може да се пушти аудио. Обидете се со друг ТВ" + "Поставување приемник на канали" + "Поставување ТВ приемник" + "Поставување на USB-приемникот за канали" + "Поставување мрежен приемник" + "Потврдете дека телевизорот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." + "Потврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." + "Потврдете дека мрежниот приемник е вклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, може ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." + + "Продолжи" + "Не сега" + + "Да се изврши поставувањето на каналите повторно?" + "Ова ќе ги отстрани каналите од ТВ приемникот и ќе скенира за нови канали повторно.\n\nПотврдете дека телевизорот е поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец." + "Ова ќе ги отстрани каналите од USB-приемникот и ќе скенира за нови канали повторно.\n\nПотврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." + "Ова ќе ги отстрани каналите од мрежниот приемник и ќе скенира за нови канали повторно.\n\nПотврдете дека мрежниот приемник е вклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, може ќе треба да ја приспособите поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец." + + "Продолжи" + "Откажи" + + "Изберете го типот врска" + "Изберете „Антена“ ако има надворешна антена поврзана со приемникот. Изберете „Кабел“ ако каналите ви ги обезбедува кабелски оператор. Ако не сте сигурни, и двата типа ќе се скенираат, но тоа може да трае подолго." + + "Антена" + "Кабел" + "Не сум сигурен" + "Само за програмери" + + "Поставување ТВ приемник" + "Поставување на USB-приемникот за канали" + "Поставување мрежен приемник на канали" + "Ова може да трае неколку минути" + "Приемникот е привремено недостапен или веќе се користи за снимање." + + Пронајден е %1$d канал + Пронајдени се %1$d канали + + "ЗАПРИ ГО СКЕНИРАЊЕТО КАНАЛИ" + + Пронајден е %1$d канал + Пронајдени се %1$d канали + + + Убаво! Пронајдени се %1$d канали во текот на скенирањето канали. Ако се чини дека тоа не е во ред, обидете се со приспособување на позицијата на антената и скенирајте повторно. + Убаво! Пронајдени се %1$d канали во текот на скенирањето канали. Ако се чини дека тоа не е во ред, обидете се со приспособување на позицијата на антената и скенирајте повторно. + + + "Готово" + "Скенирај пак" + + "Не се пронајдени канали" + "Скенирањето не пронајде ниеден канал. Потврдете дека телевизорот е поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец и скенирајте повторно." + "Скенирањето не пронајде ниеден канал. Потврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец и скенирајте повторно." + "Скенирањето не пронајде ниеден канал. Потврдете дека мрежниот приемник е вклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец, па скенирајте повторно." + + "Скенирај пак" + "Готово" + + "Скенирајте ТВ-канали" + "Поставување ТВ приемник" + "Поставување ТВ приемник со USB" + "Поставување мрежен ТВ приемник" + "USB-приемникот за TV е исклучен." + "Мрежниот приемник е исклучен." + diff --git a/usbtuner-res/values-mk/strings.xml b/usbtuner-res/values-mk/strings.xml deleted file mode 100644 index fb2e71e7..00000000 --- a/usbtuner-res/values-mk/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ТВ приемник" - "ТВ приемник со USB" - "Вклучено" - "Исклучено" - "Почекајте да заврши обработувањето" - "Изберете го изворот на каналот" - "Нема сигнал" - "Не успеа да се избере %s" - "Не успеа да се избере" - "Софтверот на приемникот неодамна е ажуриран. Скенирајте ги каналите повторно." - "Овозможете опкружувачки звук во поставките за звуци на системот за да овозможите аудио" - "Поставување приемник на канали" - "Поставување ТВ приемник" - "Поставување на USB-приемникот за канали" - "Потврдете дека телевизорот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." - "Потврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." - - "Продолжи" - "Не сега" - - "Да се изврши поставувањето на каналите повторно?" - "Ова ќе ги отстрани каналите од ТВ приемникот и ќе скенира за нови канали повторно.\n\nПотврдете дека телевизорот е поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец." - "Ова ќе ги отстрани каналите од USB-приемникот и ќе скенира за нови канали повторно.\n\nПотврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, можеби ќе треба да ја приспособите поставеноста или насоката за да примате најмногу канали. За најдобри резултати, поставете ја високо и во близина на прозорец." - - "Продолжи" - "Откажи" - - "Изберете го типот врска" - "Изберете „Антена“ ако има надворешна антена поврзана со приемникот. Изберете „Кабел“ ако каналите ви ги обезбедува кабелски оператор. Ако не сте сигурни, и двата типа ќе се скенираат, но тоа може да трае подолго." - - "Антена" - "Кабел" - "Не сум сигурен" - "Само за програмери" - - "Поставување ТВ приемник" - "Поставување на USB-приемникот за канали" - "Ова може да трае неколку минути" - "Приемникот е привремено недостапен или веќе се користи за снимање." - - Пронајден е %1$d канал - Пронајдени се %1$d канали - - "ЗАПРИ ГО СКЕНИРАЊЕТО КАНАЛИ" - - Пронајден е %1$d канал - Пронајдени се %1$d канали - - - Убаво! Пронајдени се %1$d канали во текот на скенирањето канали. Ако се чини дека тоа не е во ред, обидете се со приспособување на позицијата на антената и скенирајте повторно. - Убаво! Пронајдени се %1$d канали во текот на скенирањето канали. Ако се чини дека тоа не е во ред, обидете се со приспособување на позицијата на антената и скенирајте повторно. - - - "Готово" - "Скенирај пак" - - "Не се пронајдени канали" - "Скенирањето не пронајде ниеден канал. Потврдете дека телевизорот е поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец и скенирајте повторно." - "Скенирањето не пронајде ниеден канал. Потврдете дека USB-приемникот е приклучен и поврзан со изворот на ТВ сигналот.\n\nАко користите безжична антена, приспособете ја поставеноста или насоката. За најдобри резултати, поставете ја високо и во близина на прозорец и скенирајте повторно." - - "Скенирај пак" - "Готово" - - "Скенирај ТВ-канали" - "Поставување ТВ приемник" - "Поставување ТВ приемник со USB" - "TV приемникот преку USB е исклучен." - diff --git a/usbtuner-res/values-ml-rIN/strings.xml b/usbtuner-res/values-ml-rIN/strings.xml new file mode 100644 index 00000000..2befa74c --- /dev/null +++ b/usbtuner-res/values-ml-rIN/strings.xml @@ -0,0 +1,90 @@ + + + + + "ടിവി ട്യൂണർ" + "USB ടിവി ട്യൂണർ" + "നെറ്റ്‌വർക്ക് ടിവി ട്യൂണർ (ബീറ്റ)" + "പ്രോസസ്സുചെയ്യൽ പൂർത്തിയാകുന്നത് വരെ കാത്തിരിക്കുക" + "ട്യൂണർ സോഫ്‌റ്റ്‌വെയർ അടുത്തിടെ അപ്‌ഡേറ്റുചെയ്‌തു. ചാനലുകൾ വീണ്ടും സ്‌കാൻ ചെയ്യുക." + "ഓഡിയോ പ്രവർത്തനക്ഷമമാക്കുന്നതിന് സിസ്റ്റം ശബ്ദ ക്രമീകരണത്തിൽ സറൗണ്ട് ശബ്‌ദം പ്രവർത്തനക്ഷമമാക്കുക" + "ഓഡിയോ പ്ലേ ചെയ്യാൻ കഴിയുന്നില്ല. മറ്റൊരു ടിവിയിൽ ശ്രമിച്ചുനോക്കൂ" + "ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" + "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" + "USB ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" + "നെറ്റ്‌വർക്ക് ട്യൂണർ സജ്ജമാക്കല്‍‌" + "ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + "USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + "നെറ്റ്‌വർക്ക് ട്യൂണർ ഓണാക്കിയിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + + "തുടരുക" + "ഇപ്പോൾ വേണ്ട" + + "ചാനൽ സജ്ജമാക്കൽ വീണ്ടും റൺ ചെയ്യണോ?" + "ടിവി ട്യൂണറിൽ നിന്ന് കണ്ടെത്തിയ ചാനലുകളെ ഇത് നീക്കംചെയ്യും, പുതിയ ചാനലുകൾക്കായി വീണ്ടും സ്കാൻ ചെയ്യും.\n\nഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + "USB ട്യൂണറിൽ നിന്ന് കണ്ടെത്തിയ ചാനലുകളെ ഇത് നീക്കംചെയ്യും, പുതിയ ചാനലുകൾക്കായി വീണ്ടും സ്കാൻ ചെയ്യും.\n\nസ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + "നെറ്റ്‌വർക്ക് ട്യൂണറിൽ നിന്ന് കണ്ടെത്തിയ ചാനലുകളെ ഇത് നീക്കംചെയ്യും, പുതിയ ചാനലുകൾക്കായി വീണ്ടും സ്കാൻ ചെയ്യും.\n\nനെറ്റ്‌വർക്ക് ട്യൂണർ ഓണാക്കിയിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." + + "തുടരുക" + "റദ്ദാക്കൂ" + + "കണക്ഷൻ തരം തിരഞ്ഞെടുക്കുക" + "ട്യൂണറിലേക്ക് കണക്റ്റുചെയ്തിട്ടുള്ള ഒരു ബാഹ്യ ആന്റിന ഉണ്ടെങ്കിൽ, ആന്റിന തിരഞ്ഞെടുക്കുക. കേബിൾ സേവന ദാതാവിൽ നിന്നാണ് ചാനലുകൾ ലഭിക്കുന്നതെങ്കിൽ കേബിൾ തിരഞ്ഞെടുക്കുക. ഏതാണ് ഉള്ളതെന്ന് നിങ്ങൾക്ക് അറിയില്ലെങ്കിൽ, രണ്ട് രീതികളും സ്കാൻ ചെയ്യപ്പെടും, എന്നാലിതിന് സമയമെടുക്കും." + + "ആന്റിന" + "കേബിള്‍" + "ഉറപ്പില്ല" + "ഡെവലപ്പ്‌മെന്റ് മാത്രം" + + "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" + "USB ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" + "നെറ്റ്‌വർക്ക് ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" + "ഇതിന് കുറച്ച് സമയം എടുത്തേക്കാം" + "ട്യൂണർ നിലവിൽ ലഭ്യമല്ല അല്ലെങ്കിൽ ഇതിനകം തന്നെ റെക്കോർഡിംഗ് ഉപയോഗിച്ചുകൊണ്ടിരിക്കുന്നു." + + %1$d ചാനലുകൾ കണ്ടെത്തി + %1$d ചാനൽ കണ്ടെത്തി + + "ചാനൽ സ്കാൻ ചെയ്യുന്നത് നിർത്തുക" + + %1$d ചാനലുകൾ കണ്ടെത്തി + %1$d ചാനൽ കണ്ടെത്തി + + + കൊള്ളാം! ചാനൽ സ്കാൻ വേളയിൽ %1$d ചാനലുകൾ കണ്ടെത്തി. എന്തോ കുഴപ്പമുണ്ടെന്ന് തോന്നുന്നുവെങ്കിൽ, ആന്റിനയുടെ ദിശ ക്രമീകരിച്ചുകൊണ്ട് വീണ്ടും സ്കാൻ ചെയ്യുക. + കൊള്ളാം! ചാനൽ സ്കാൻ വേളയിൽ %1$d ചാനൽ കണ്ടെത്തി. എന്തോ കുഴപ്പമുണ്ടെന്ന് തോന്നുന്നുവെങ്കിൽ, ആന്റിനയുടെ ദിശ ക്രമീകരിച്ചുകൊണ്ട് വീണ്ടും സ്കാൻ ചെയ്യുക. + + + "പൂർത്തിയായി" + "വീണ്ടും സ്കാൻ ചെയ്യുക" + + "ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല" + "സ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കുക. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിച്ച് വീണ്ടും സ്കാൻ ചെയ്യുക." + "സ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കുക. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിച്ച് വീണ്ടും സ്കാൻ ചെയ്യുക." + "സ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. നെറ്റ്‌വർക്ക് ട്യൂണർ ഓണാക്കിയിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കുക. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിച്ച് വീണ്ടും സ്കാൻ ചെയ്യുക." + + "വീണ്ടും സ്കാൻ ചെയ്യുക" + "പൂർത്തിയായി" + + "ടിവി ചാനലുകൾക്കായി സ്കാൻ ചെയ്യുക" + "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" + "USB ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" + "നെറ്റ്‌വർക്ക് ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" + "USB ടിവി ട്യൂണർ വിച്ഛേദിച്ചു." + "നെറ്റ്‌വർക്ക് ട്യൂണർ വിച്ഛേദിച്ചു." + diff --git a/usbtuner-res/values-ml/strings.xml b/usbtuner-res/values-ml/strings.xml deleted file mode 100644 index d025ec7b..00000000 --- a/usbtuner-res/values-ml/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ടിവി ട്യൂണർ" - "USB ടിവി ട്യൂണർ" - "ഓണാക്കുക" - "ഓഫാക്കുക" - "പ്രോസസ്സുചെയ്യൽ പൂർത്തിയാകുന്നത് വരെ കാത്തിരിക്കുക" - "നിങ്ങളുടെ ചാനൽ ഉറവിടം തിരഞ്ഞെടുക്കുക" - "സിഗ്‌നൽ ഇല്ല" - "%s എന്നതിലേക്ക് ട്യൂൺ ചെയ്യുന്നത് പരാജയപ്പെട്ടു" - "ട്യൂൺ ചെയ്യുന്നത് പരാജയപ്പെട്ടു" - "ട്യൂണർ സോഫ്‌റ്റ്‌വെയർ അടുത്തിടെ അപ്‌ഡേറ്റുചെയ്‌തു. ചാനലുകൾ വീണ്ടും സ്‌കാൻ ചെയ്യുക." - "ഓഡിയോ പ്രവർത്തനക്ഷമമാക്കുന്നതിന് സിസ്റ്റം ശബ്ദ ക്രമീകരണത്തിൽ സറൗണ്ട് ശബ്‌ദം പ്രവർത്തനക്ഷമമാക്കുക" - "ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" - "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" - "USB ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" - "ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." - "USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." - - "തുടരുക" - "ഇപ്പോൾ വേണ്ട" - - "ചാനൽ സജ്ജമാക്കൽ വീണ്ടും റൺ ചെയ്യണോ?" - "ടിവി ട്യൂണറിൽ നിന്ന് കണ്ടെത്തിയ ചാനലുകളെ ഇത് നീക്കംചെയ്യും, പുതിയ ചാനലുകൾക്കായി വീണ്ടും സ്കാൻ ചെയ്യും.\n\nഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." - "USB ട്യൂണറിൽ നിന്ന് കണ്ടെത്തിയ ചാനലുകളെ ഇത് നീക്കംചെയ്യും, പുതിയ ചാനലുകൾക്കായി വീണ്ടും സ്കാൻ ചെയ്യും.\n\nസ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ, പരമാവധി ചാനലുകൾ ലഭിക്കുന്നതിന്, അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കേണ്ടി വരാം. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിക്കുക." - - "തുടരുക" - "റദ്ദാക്കൂ" - - "കണക്ഷൻ തരം തിരഞ്ഞെടുക്കുക" - "ട്യൂണറിലേക്ക് കണക്റ്റുചെയ്തിട്ടുള്ള ഒരു ബാഹ്യ ആന്റിന ഉണ്ടെങ്കിൽ, ആന്റിന തിരഞ്ഞെടുക്കുക. കേബിൾ സേവന ദാതാവിൽ നിന്നാണ് ചാനലുകൾ ലഭിക്കുന്നതെങ്കിൽ കേബിൾ തിരഞ്ഞെടുക്കുക. ഏതാണ് ഉള്ളതെന്ന് നിങ്ങൾക്ക് അറിയില്ലെങ്കിൽ, രണ്ട് രീതികളും സ്കാൻ ചെയ്യപ്പെടും, എന്നാലിതിന് സമയമെടുക്കും." - - "ആന്റിന" - "കേബിള്‍" - "ഉറപ്പില്ല" - "ഡെവലപ്പ്‌മെന്റ് മാത്രം" - - "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" - "USB ചാനൽ ട്യൂണർ സജ്ജമാക്കല്‍‌" - "ഇതിന് കുറച്ച് സമയം എടുത്തേക്കാം" - "ട്യൂണർ നിലവിൽ ലഭ്യമല്ല അല്ലെങ്കിൽ ഇതിനകം തന്നെ റെക്കോർഡിംഗ് ഉപയോഗിച്ചുകൊണ്ടിരിക്കുന്നു." - - %1$d ചാനലുകൾ കണ്ടെത്തി - %1$d ചാനൽ കണ്ടെത്തി - - "ചാനൽ സ്കാൻ ചെയ്യുന്നത് നിർത്തുക" - - %1$d ചാനലുകൾ കണ്ടെത്തി - %1$d ചാനൽ കണ്ടെത്തി - - - കൊള്ളാം! ചാനൽ സ്കാൻ വേളയിൽ %1$d ചാനലുകൾ കണ്ടെത്തി. എന്തോ കുഴപ്പമുണ്ടെന്ന് തോന്നുന്നുവെങ്കിൽ, ആന്റിനയുടെ ദിശ ക്രമീകരിച്ചുകൊണ്ട് വീണ്ടും സ്കാൻ ചെയ്യുക. - കൊള്ളാം! ചാനൽ സ്കാൻ വേളയിൽ %1$d ചാനൽ കണ്ടെത്തി. എന്തോ കുഴപ്പമുണ്ടെന്ന് തോന്നുന്നുവെങ്കിൽ, ആന്റിനയുടെ ദിശ ക്രമീകരിച്ചുകൊണ്ട് വീണ്ടും സ്കാൻ ചെയ്യുക. - - - "പൂർത്തിയായി" - "വീണ്ടും സ്കാൻ ചെയ്യുക" - - "ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല" - "സ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് നിങ്ങളുടെ ടിവി കണക്റ്റുചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കുക. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിച്ച് വീണ്ടും സ്കാൻ ചെയ്യുക." - "സ്കാൻ ചെയ്തപ്പോൾ ചാനലുകളൊന്നും കണ്ടെത്തിയില്ല. USB ട്യൂണർ പ്ലഗിൻ ചെയ്തിട്ടുണ്ടെന്നും ഒരു ടിവി സിഗ്നൽ ഉറവിടത്തിലേക്ക് കണക്റ്റുചെയ്തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക.\n\nഉയരത്തിൽ വയ്ക്കേണ്ട തരത്തിലുള്ള ആന്റിനയാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നതെങ്കിൽ അതിന്റെ ഇരിപ്പോ ദിശയോ ക്രമീകരിക്കുക. മികച്ച ഫലം ലഭിക്കാൻ, കുറച്ചുകൂടി ഉയരത്തിലും ജാലകത്തിന് അരികിലായും അത് സ്ഥാപിച്ച് വീണ്ടും സ്കാൻ ചെയ്യുക." - - "വീണ്ടും സ്കാൻ ചെയ്യുക" - "പൂർത്തിയായി" - - "ടിവി ചാനലുകൾക്കായി സ്കാൻ ചെയ്യുക" - "ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" - "USB ടിവി ട്യൂണർ സജ്ജമാക്കല്‍‌" - "USB ടിവി ട്യൂണർ വിച്ഛേദിച്ചു." - diff --git a/usbtuner-res/values-mn-rMN/strings.xml b/usbtuner-res/values-mn-rMN/strings.xml new file mode 100644 index 00000000..996341b4 --- /dev/null +++ b/usbtuner-res/values-mn-rMN/strings.xml @@ -0,0 +1,90 @@ + + + + + "ТВ тохируулагч" + "USB ТВ тохируулагч" + "Сүлжээний ТВ Тохируулагч (БЭТА)" + "Боловсруулж дуусах хүртэл хүлээнэ үү" + "Тохируулагчийн програмыг саяхан шинэчилсэн байна. Дахин суваг хайна уу." + "Аудиог идэвхжүүлэхийн тулд орчны дууны системийг дууны тохиргоонд идэвхжүүлнэ үү" + "Аудиог тоглуулах боломжгүй байна. Өөр ТВ дээр оролдож үзнэ үү" + "Суваг тохируулагчийн тохиргоо" + "ТВ тохируулагчийн тохиргоо" + "USB суваг тохируулагчийн тохиргоо" + "Сүлжээ тохируулагчийн тохиргоо" + "ТВ-ээ ТВ дохионы үүсвэртэй холбосон эсэхээ батална уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." + "USB тохируулагчийг залгасан ба ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй байдаг." + "Сүлжээ тохируулагчийг асааж, ТВ дохионы үүсвэртэй холбосон эсэхээ баталгаажуулна уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулна уу." + + "Үргэлжлүүлэх" + "Одоо биш" + + "Сувгийн тохируулгыг дахин ажиллуулах уу?" + "Энэ нь ТВ тохируулагчаас олсон сувгийг устгаад, шинэ суваг хайх болно.\n\nТВ тохируулагчаа ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." + "Энэ нь USB тохируулагчаас олдсон сувгийг устгаад, шинэ суваг хайх болно.\n\nUSB тохируулагчийг залгасан, ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." + "Энэ нь сүлжээ тохируулагчаас олдсон сувгийг устгаж, шинэ суваг хайх болно.\n\nСүлжээ тохируулагчийг асааж, ТВ дохио үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв антен хэрэглэж байгаа бол антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулаад, дахин хайна уу." + + "Үргэлжлүүлэх" + "Цуцлах" + + "Холболтын төрөл сонгоно уу" + "Тохируулагчтай нэмэлт антен холбосон бол Антенаа сонгоно уу. Хэрэв сувгаа кабелийн үйлчилгээ эрхлэгчээс авдаг бол Кабелиа сонгоно уу. Та итгэлгүй байвал энэ хоёр төрлийг хоёуланг нь хайх боловч хэсэг хугацаа зарцуулах болно." + + "Антен" + "Кабель" + "Итгэлгүй байна" + "Зөвхөн хөгжүүлэлтийн хэрэгцээнд" + + "ТВ тохируулагчийн тохиргоо" + "USB суваг тохируулагчийн тохиргоо" + "Сүлжээний суваг тохируулагчийн тохиргоо" + "Хэдэн минут шаардлагатай" + "Суваг солигч одоогоор боломжгүй, эсвэл үүнийг өөр бичлэгт ашиглаж байна." + + %1$d суваг оллоо + %1$d суваг оллоо + + "СУВАГ ХАЙХЫГ ЗОГСООХ" + + %1$d суваг оллоо + %1$d суваг оллоо + + + Сайн байна! Суваг хайх явцад %1$d сувгийг олсон байна. Хэрэв илүү олон суваг хайх бол антенаа хөдөлгөөд, дахин хайна уу. + Сайн байна! Суваг хайх явцад %1$d суваг олдсон. Хэрэв илүү олон суваг хайх бол антенаа хөдөлгөөд, дахин хайна уу. + + + "Дууссан" + "Дахин хайх" + + "Суваг олсонгүй" + "Хайлтын явцад суваг олсонгүй. ТВ-ээ ТВ дохионы үүсвэрт холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол антены байршлыг эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулаад, дахин хайх нь илүү үр дүнтэй." + "Хайлтын явцад суваг олдсонгүй. USB тохируулагчийг залгасан бөгөөд ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулахаад, дахин хайх нь илүү үр дүнтэй." + "Хайлтын явцад суваг олдсонгүй. Сүлжээ тохируулагчийг асааж, ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол антены байршил, эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулаад, дахин хайна уу." + + "Дахин хайх" + "Дууссан" + + "TВ-н суваг хайх" + "ТВ тохируулагчийн тохиргоо" + "USB ТВ тохируулагчийн тохиргоо" + "Сүлжээний ТВ тохируулагчийн тохиргоо" + "USB ТВ тохируулагч салсан байна." + "Сүлжээ тааруулагч салсан байна." + diff --git a/usbtuner-res/values-mn/strings.xml b/usbtuner-res/values-mn/strings.xml deleted file mode 100644 index 51f7a8dd..00000000 --- a/usbtuner-res/values-mn/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "ТВ тохируулагч" - "USB ТВ тохируулагч" - "Идэвхтэй" - "Идэвхгүй" - "Боловсруулж дуусах хүртэл хүлээнэ үү" - "Сувгийн эх үүсвэрээ сонгоно уу" - "Дохио алга" - "%s-д тохируулж чадсангүй" - "Тохируулж чадсангүй" - "Тохируулагчийн програмыг саяхан шинэчилсэн байна. Дахин суваг хайна уу." - "Аудиог идэвхжүүлэхийн тулд орчны дууны системийг дууны тохиргоонд идэвхжүүлнэ үү" - "Суваг тохируулагчийн тохиргоо" - "ТВ тохируулагчийн тохиргоо" - "USB суваг тохируулагчийн тохиргоо" - "ТВ-ээ ТВ дохионы үүсвэртэй холбосон эсэхээ батална уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." - "USB тохируулагчийг залгасан ба ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй байдаг." - - "Үргэлжлүүлэх" - "Одоо биш" - - "Сувгийн тохируулгыг дахин ажиллуулах уу?" - "Энэ нь ТВ тохируулагчаас олсон сувгийг устгаад, шинэ суваг хайх болно.\n\nТВ тохируулагчаа ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." - "Энэ нь USB тохируулагчаас олдсон сувгийг устгаад, шинэ суваг хайх болно.\n\nUSB тохируулагчийг залгасан, ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол олон суваг авахын тулд антены байршлыг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулах нь илүү үр дүнтэй." - - "Үргэлжлүүлэх" - "Цуцлах" - - "Холболтын төрөл сонгоно уу" - "Тохируулагчтай нэмэлт антен холбосон бол Антенаа сонгоно уу. Хэрэв сувгаа кабелийн үйлчилгээ эрхлэгчээс авдаг бол Кабелиа сонгоно уу. Та итгэлгүй байвал энэ хоёр төрлийг хоёуланг нь хайх боловч хэсэг хугацаа зарцуулах болно." - - "Антен" - "Кабель" - "Итгэлгүй байна" - "Зөвхөн хөгжүүлэлтийн хэрэгцээнд" - - "ТВ тохируулагчийн тохиргоо" - "USB суваг тохируулагчийн тохиргоо" - "Хэдэн минут шаардлагатай" - "Суваг солигч одоогоор боломжгүй, эсвэл үүнийг өөр бичлэгт ашиглаж байна." - - %1$d суваг оллоо - %1$d суваг оллоо - - "СУВАГ ХАЙХЫГ ЗОГСООХ" - - %1$d суваг оллоо - %1$d суваг оллоо - - - Сайн байна! Суваг хайх явцад %1$d сувгийг олсон байна. Хэрэв илүү олон суваг хайх бол антенаа хөдөлгөөд, дахин хайна уу. - Сайн байна! Суваг хайх явцад %1$d суваг олдсон. Хэрэв илүү олон суваг хайх бол антенаа хөдөлгөөд, дахин хайна уу. - - - "Дууссан" - "Дахин хайх" - - "Суваг олсонгүй" - "Хайлтын явцад суваг олсонгүй. ТВ-ээ ТВ дохионы үүсвэрт холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол антены байршлыг эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулаад, дахин хайх нь илүү үр дүнтэй." - "Хайлтын явцад суваг олдсонгүй. USB тохируулагчийг залгасан бөгөөд ТВ дохионы үүсвэртэй холбосон эсэхээ шалгана уу.\n\nХэрэв агаарын антен хэрэглэж байгаа бол антены байршил эсвэл чиглэлийг өөрчилнө үү. Антеныг өндөрт, цонхны дэргэд байрлуулахаад, дахин хайх нь илүү үр дүнтэй." - - "Дахин хайх" - "Дууссан" - - "TВ-н суваг хайх" - "ТВ тохируулагчийн тохиргоо" - "USB ТВ тохируулагчийн тохиргоо" - "USB ТВ тохируулагч салсан байна." - diff --git a/usbtuner-res/values-mr-rIN/strings.xml b/usbtuner-res/values-mr-rIN/strings.xml new file mode 100644 index 00000000..3b8c9011 --- /dev/null +++ b/usbtuner-res/values-mr-rIN/strings.xml @@ -0,0 +1,90 @@ + + + + + "टीव्ही ट्यूनर" + "USB टीव्ही ट्यूनर" + "नेटवर्क टीव्ही ट्यूनर (बीटा)" + "कृपया प्रक्रिया पूर्ण होण्‍याची प्रतीक्षा करा" + "ट्यूनर सॉफ्टवेअर अलीकडे अद्यतनित केले आहे. कृपया चॅनेल पुन्हा स्कॅन करा." + "ऑडिओ सक्षम करण्यासाठी सिस्टीम ध्वनी सेटिंग्ज मध्ये सराउंड ध्वनी सक्षम करा" + "ऑडिओ प्ले करू शकत नाही. कृपया दुसरा टीव्ही वापरून पहा" + "चॅनेल ट्यूनर सेटअप" + "टीव्ही ट्यूनर सेटअप" + "USB चॅनेल ट्यूनर सेटअप" + "नेटवर्क ट्यूनर सेटअप" + "टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याची खात्री करा. \n\n बिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + "USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + "नेटवर्क ट्यूनर प्लगिन केले आणि TV सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + + "सुरू ठेवा" + "सध्या नाही" + + "चॅनेल सेटअप वर परत यायचे?" + "हे टीव्ही ट्यूनर वरून शोधलेले चॅनेल काढेल आणि नवीन चॅनेलसाठी पुन्हा स्कॅन करेल.\n\n टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याचे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + "हे USB ट्यूनर वरून शोधलेले चॅनेल काढेल आणि नवीन चॅनेलसाठी पुन्हा स्कॅन करेल.\n\n USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्त्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + "हे नेटवर्क ट्यूनर वरून शोधलेले चॅनेल काढेल आणि नवीन चॅनेलसाठी पुन्हा स्कॅन करेल.\n\n नेटवर्क ट्यूनर प्लगिन केले आणि टीव्ही सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." + + "सुरू ठेवा" + "रद्द करा" + + "कनेक्‍शन प्रकार निवडा" + "ट्यूनरशी बाह्य अँटेना कनेक्‍ट केला असल्‍यास अँटेना निवडा. आपले चॅनेल केबल सेवा प्रदात्याकडून येत असल्‍यास केबल निवडा. आपल्‍याला खात्री नसल्‍यास, दोन्ही प्रकार स्कॅन केले जातील परंतु यास वेळ लागेल." + + "अँटेना" + "केबल" + "खात्री नाही" + "केवळ विकास" + + "टीव्ही ट्यूनर सेटअप" + "USB चॅनेल ट्यूनर सेटअप" + "नेटवर्क चॅनेल ट्यूनर सेटअप" + "यास काही मिनिटे लागू शकतात" + "ट्यूनर तात्पुरते उपलब्ध नाही किंवा रेकॉर्डिंगद्वारे आधीपासून वापरले गेले आहे." + + %1$d चॅनेल सापडले + %1$d चॅनेल सापडले + + "चॅनेल स्कॅन करणे थांबवा" + + %1$d चॅनेल सापडले + %1$d चॅनेल सापडले + + + छान! चॅनेल स्कॅन करताना %1$d चॅनेल सापडले. हे योग्य वाटत नसल्यास, अँटेना स्थिती समायोजित करून पहा आणि पुन्हा स्कॅन करा. + छान! चॅनेल स्कॅन करताना %1$d चॅनेल सापडले. हे योग्य वाटत नसल्यास, अँटेना स्थिती समायोजित करून पहा आणि पुन्हा स्कॅन करा. + + + "पूर्ण झाले" + "पुन्हा स्कॅन करा" + + "कोणतेही चॅनेल आढळले नाही" + "स्कॅन करताना कोणतेही चॅनेल आढळले नाही. टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याचे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, त्याचे स्थान किंवा दिशा समायोजित करा. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा आणि पुन्हा स्कॅन करा." + "स्कॅन करताना कोणतेही चॅनेल आढळले नाही. USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, त्याचे स्थान किंवा दिशा समायोजित करा. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा आणि पुन्हा स्कॅन करा." + "स्कॅन करताना कोणतेही चॅनेल आढळले नाहीत. नेटवर्क ट्यूनर प्लगिन केले आणि टीव्ही सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, त्याचे स्थान किंवा दिशा समायोजित करा. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा आणि पुन्हा स्कॅन करा." + + "पुन्हा स्कॅन करा" + "पूर्ण झाले" + + "टीव्ही चॅनेलसाठी स्कॅन करा" + "टीव्ही ट्यूनर सेटअप" + "USB टीव्ही ट्यूनर सेटअप" + "नेटवर्क टीव्ही ट्यूनर सेटअप" + "USB टीव्ही ट्यूनर डिस्कनेक्ट केला." + "नेटवर्क ट्यूनर डिस्कनेक्ट केले." + diff --git a/usbtuner-res/values-mr/strings.xml b/usbtuner-res/values-mr/strings.xml deleted file mode 100644 index 2ea242f1..00000000 --- a/usbtuner-res/values-mr/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "टीव्ही ट्यूनर" - "USB टीव्ही ट्यूनर" - "चालू" - "बंद" - "कृपया प्रक्रिया पूर्ण होण्‍याची प्रतीक्षा करा" - "आपला चॅनेल स्रोत निवडा" - "सिग्नल नाही" - "%s वर ट्यून करण्‍यात अयशस्वी झाले" - "ट्यून करण्यात अयशस्वी झाले" - "ट्यूनर सॉफ्टवेअर अलीकडे अद्यतनित केले आहे. कृपया चॅनेल पुन्हा स्कॅन करा." - "ऑडिओ सक्षम करण्यासाठी सिस्टीम ध्वनी सेटिंग्ज मध्ये सराउंड ध्वनी सक्षम करा" - "चॅनेल ट्यूनर सेटअप" - "टीव्ही ट्यूनर सेटअप" - "USB चॅनेल ट्यूनर सेटअप" - "टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याची खात्री करा. \n\n बिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." - "USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." - - "सुरू ठेवा" - "सध्या नाही" - - "चॅनेल सेटअप वर परत यायचे?" - "हे टीव्ही ट्यूनर वरून शोधलेले चॅनेल काढेल आणि नवीन चॅनेलसाठी पुन्हा स्कॅन करेल.\n\n टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याचे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." - "हे USB ट्यूनर वरून शोधलेले चॅनेल काढेल आणि नवीन चॅनेलसाठी पुन्हा स्कॅन करेल.\n\n USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्त्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, आपल्‍याला कदाचित सर्वाधिक चॅनेल मिळविण्‍यासाठी त्याचे स्थान किंवा दिशा समायोजित करावी लागेल. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा." - - "सुरू ठेवा" - "रद्द करा" - - "कनेक्‍शन प्रकार निवडा" - "ट्यूनरशी बाह्य अँटेना कनेक्‍ट केला असल्‍यास अँटेना निवडा. आपले चॅनेल केबल सेवा प्रदात्याकडून येत असल्‍यास केबल निवडा. आपल्‍याला खात्री नसल्‍यास, दोन्ही प्रकार स्कॅन केले जातील परंतु यास वेळ लागेल." - - "अँटेना" - "केबल" - "खात्री नाही" - "केवळ विकास" - - "टीव्ही ट्यूनर सेटअप" - "USB चॅनेल ट्यूनर सेटअप" - "यास काही मिनिटे लागू शकतात" - "ट्यूनर तात्पुरते उपलब्ध नाही किंवा रेकॉर्डिंगद्वारे आधीपासून वापरले गेले आहे." - - %1$d चॅनेल सापडले - %1$d चॅनेल सापडले - - "चॅनेल स्कॅन करणे थांबवा" - - %1$d चॅनेल सापडले - %1$d चॅनेल सापडले - - - छान! चॅनेल स्कॅन करताना %1$d चॅनेल सापडले. हे योग्य वाटत नसल्यास, अँटेना स्थिती समायोजित करून पहा आणि पुन्हा स्कॅन करा. - छान! चॅनेल स्कॅन करताना %1$d चॅनेल सापडले. हे योग्य वाटत नसल्यास, अँटेना स्थिती समायोजित करून पहा आणि पुन्हा स्कॅन करा. - - - "पूर्ण झाले" - "पुन्हा स्कॅन करा" - - "कोणतेही चॅनेल आढळले नाही" - "स्कॅन करताना कोणतेही चॅनेल आढळले नाही. टीव्ही सिग्नल स्रोताशी आपला टीव्ही कनेक्ट केला असल्याचे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, त्याचे स्थान किंवा दिशा समायोजित करा. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा आणि पुन्हा स्कॅन करा." - "स्कॅन करताना कोणतेही चॅनेल आढळले नाही. USB ट्यूनर प्लगिन केले आणि TV सिग्नल स्रोताशी कनेक्‍ट केले आहे हे सत्यापित करा.\n\nबिनतारी अँटेना वापरत असल्‍यास, त्याचे स्थान किंवा दिशा समायोजित करा. उत्कृष्‍ट परिणामांसाठी, त्‍यास उंचावर आणि खिडकी जवळ ठेवा आणि पुन्हा स्कॅन करा." - - "पुन्हा स्कॅन करा" - "पूर्ण झाले" - - "टीव्ही चॅनेलसाठी स्कॅन करा" - "टीव्ही ट्यूनर सेटअप" - "USB टीव्ही ट्यूनर सेटअप" - "USB TV ट्यूनर डिस्कनेक्ट केला." - diff --git a/usbtuner-res/values-ms-rMY/strings.xml b/usbtuner-res/values-ms-rMY/strings.xml new file mode 100644 index 00000000..c85ce142 --- /dev/null +++ b/usbtuner-res/values-ms-rMY/strings.xml @@ -0,0 +1,90 @@ + + + + + "Penala TV" + "Penala TV USB" + "Penala TV Rangkaian (BETA)" + "Sila tunggu sehingga proses selesai" + "Perisian penala telah dikemas kini baru-baru ini. Sila imbas semula saluran." + "Dayakan bunyi keliling dalam tetapan bunyi sistem untuk mendayakan audio" + "Tidak dapat memainkan audio. Sila cuba TV lain" + "Persediaan penala saluran" + "Persediaan Penala TV" + "Persediaan penala saluran USB" + "Persediaan penala rangkaian" + "Sahkan bahawa TV anda disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + "Sahkan bahawa penala USB dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + "Sahkan bahawa penala rangkaian telah dihidupkan dan disambungkan pada sumber isyarat TV\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + + "Teruskan" + "Bukan sekarang" + + "Jalankan semula persediaan saluran?" + "Tindakan ini akan mengalih keluar saluran yang ditemui daripada penala TV dan mengimbas saluran baharu sekali lagi.\n\nSahkan bahawa penala TV telah disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + "Tindakan ini akan mengalih keluar saluran yang ditemui daripada penala USB dan mengimbas saluran baharu sekali lagi.\n\nSahkan bahawa penala USB telah dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + "Tindakan ini akan mengalih keluar saluran yang ditemui daripada penala rangkaian dan mengimbas saluran baharu sekali lagi.\n\nSahkan bahawa penala rangkaian telah dihidupkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." + + "Teruskan" + "Batal" + + "Pilih jenis sambungan" + "Pilih Antena jika terdapat antena luaran yang disambungkan pada penala. Pilih Kabel jika saluran anda adalah daripada penyedia perkhidmatan kabel. Jika anda tidak pasti, kedua-dua jenis sambungan akan diimbas tetapi proses ini mungkin mengambil masa yang lebih lama." + + "Antena" + "Kabel" + "Tidak pasti" + "Pembangunan sahaja" + + "Persediaan penala TV" + "Persediaan penala saluran USB" + "Persediaan penala saluran rangkaian" + "Proses ini mungkin mengambil masa beberapa minit" + "Penala tidak tersedia buat sementara waktu atau sudah pun digunakan oleh rakaman." + + %1$d saluran ditemui + %1$d saluran ditemui + + "HENTIKAN PENGIMBASAN SALURAN" + + %1$d saluran ditemui + %1$d saluran ditemui + + + Bagus! %1$d saluran ditemui semasa pengimbasan saluran. Jika hasil pengimbasan ini salah, cuba laraskan kedudukan antena dan imbas sekali lagi. + Bagus! %1$d saluran ditemui semasa pengimbasan saluran. Jika hasil pengimbasan ini salah, cuba laraskan kedudukan antena dan imbas sekali lagi. + + + "Selesai" + "Imbas lagi" + + "Tiada Saluran ditemui" + "Pengimbasan ini tidak menemui sebarang saluran. Sahkan bahawa TV anda disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, laraskan peletakan atau arahnya. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap, kemudian imbas sekali lagi." + "Pengimbasan tidak menemui sebarang saluran. Sahkan bahawa penala USB dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, laraskan peletakan atau arahnya. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap, kemudian imbas sekali lagi." + "Pengimbasan ini tidak menemui sebarang saluran. Sahkan bahawa penala rangkaian telah dihidupkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, laraskan peletakan atau arahnya. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap, kemudian imbas sekali lagi." + + "Imbas lagi" + "Selesai" + + "Imbas saluran TV" + "Persediaan Penala TV" + "Persediaan Penala TV USB" + "Persediaan Penala TV Rangkaian" + "Penala TV USB diputuskan sambungan." + "Penala rangkaian diputuskan sambungan." + diff --git a/usbtuner-res/values-ms/strings.xml b/usbtuner-res/values-ms/strings.xml deleted file mode 100644 index 578e8ae5..00000000 --- a/usbtuner-res/values-ms/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "Penala TV" - "Penala TV USB" - "Hidupkan" - "Matikan" - "Sila tunggu sehingga proses selesai" - "Pilih sumber saluran anda" - "Tiada Isyarat" - "Gagal menala ke %s" - "Gagal menala" - "Perisian penala telah dikemas kini baru-baru ini. Sila imbas semula saluran." - "Dayakan bunyi keliling dalam tetapan bunyi sistem untuk mendayakan audio" - "Persediaan penala saluran" - "Persediaan Penala TV" - "Persediaan penala saluran USB" - "Sahkan bahawa TV anda disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." - "Sahkan bahawa penala USB dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." - - "Teruskan" - "Bukan sekarang" - - "Jalankan semula persediaan saluran?" - "Tindakan ini akan mengalih keluar saluran yang ditemui daripada penala TV dan mengimbas saluran baharu sekali lagi.\n\nSahkan bahawa penala TV telah disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." - "Tindakan ini akan mengalih keluar saluran yang ditemui daripada penala USB dan mengimbas saluran baharu sekali lagi.\n\nSahkan bahawa penala USB telah dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, anda mungkin perlu melaraskan peletakan atau arahnya untuk menerima saluran terbanyak. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap." - - "Teruskan" - "Batal" - - "Pilih jenis sambungan" - "Pilih Antena jika terdapat antena luaran yang disambungkan pada penala. Pilih Kabel jika saluran anda adalah daripada penyedia perkhidmatan kabel. Jika anda tidak pasti, kedua-dua jenis sambungan akan diimbas tetapi proses ini mungkin mengambil masa yang lebih lama." - - "Antena" - "Kabel" - "Tidak pasti" - "Pembangunan sahaja" - - "Persediaan penala TV" - "Persediaan penala saluran USB" - "Proses ini mungkin mengambil masa beberapa minit" - "Penala tidak tersedia buat sementara waktu atau sudah pun digunakan oleh rakaman." - - %1$d saluran ditemui - %1$d saluran ditemui - - "HENTIKAN PENGIMBASAN SALURAN" - - %1$d saluran ditemui - %1$d saluran ditemui - - - Bagus! %1$d saluran ditemui semasa pengimbasan saluran. Jika hasil pengimbasan ini salah, cuba laraskan kedudukan antena dan imbas sekali lagi. - Bagus! %1$d saluran ditemui semasa pengimbasan saluran. Jika hasil pengimbasan ini salah, cuba laraskan kedudukan antena dan imbas sekali lagi. - - - "Selesai" - "Imbas lagi" - - "Tiada Saluran ditemui" - "Pengimbasan ini tidak menemui sebarang saluran. Sahkan bahawa TV anda disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, laraskan peletakan atau arahnya. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap, kemudian imbas sekali lagi." - "Pengimbasan tidak menemui sebarang saluran. Sahkan bahawa penala USB dipalamkan dan disambungkan pada sumber isyarat TV.\n\nJika menggunakan antena siaran, laraskan peletakan atau arahnya. Untuk mendapatkan hasil yang terbaik, letakkan antena itu di tempat yang tinggi dan berdekatan dengan tingkap, kemudian imbas sekali lagi." - - "Imbas lagi" - "Selesai" - - "Imbas saluran TV" - "Persediaan Penala TV" - "Persediaan Penala TV USB" - "Penala TV USB diputuskan sambungan." - diff --git a/usbtuner-res/values-my-rMM/strings.xml b/usbtuner-res/values-my-rMM/strings.xml new file mode 100644 index 00000000..a2cf6c80 --- /dev/null +++ b/usbtuner-res/values-my-rMM/strings.xml @@ -0,0 +1,90 @@ + + + + + "တီဗီချန်နယ်ချိန်ကိရိယာ" + "USB တီဗီချန်နယ်ချိန်ကိရိယာ" + "ကွန်ရက်တီဗီ လိုင်းချိန်စနစ် (စမ်းသပ်ဆော့ဖ်ဝဲ)" + "စီမံဆောင်ရွက်မှု အဆုံးသတ်ရန် ခဏစောင့်ပါ" + "ချန်နယ်ချိန်ဆော့ဖ်ဝဲကို မကြာသေးမီက အပ်ဒိတ်လုပ်ခဲ့သည်။ ချန်နယ်လိုင်းများကို ပြန်ရှာပါ။" + "အသံဖွင့်ရန် ပတ်ပတ်လည်အသံစနစ်ဆက်တင်များကို ဖွင့်ပါ" + "အသံဖွင့်၍မရပါ။ အခြားတီဗီတွင် စမ်းကြည့်ပါ" + "ချန်နယ်ချိန်ကိရိယာ ထည့်သွင်းတပ်ဆင်မှု" + "တီဗီချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" + "USB ချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" + "တီဗီလိုင်းဖမ်းကိရိယာ စနစ်ထည့်သွင်းမှု" + "သင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" + "USB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထားပြီး တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" + "တီဗီလိုင်းဖမ်းကိရိယာကို ပါဝါဖွင့်ထားခြင်း ရှိ မရှိနှင့် တီဗီစလောင်းသို့ ချိတ်ဆက်ထားခြင်းရှိမရှိ စစ်ဆေးပါ။\n\nအင်တင်နာကို အသုံးပြုလျှင် ၎င်း၏အနေအထား သို့မဟုတ် ဦးတည်ချက်တို့ကို ချိန်ညှိရန် လိုအပ်ပါသည်။ အကောင်းဆုံးရလဒ်အတွက် ပြတင်းပေါက်အနီး အမြင့်ပိုင်းတွင် ထားပါ။" + + "ရှေ့ဆက်ရန်" + "မလုပ်သေးပါ" + + "ချန်နယ်စနစ်ထည့်သွင်းမှု ပြန်စမလား။" + "ဤလုပ်ဆောင်ချက်သည် တီဗီချန်နယ်ချိန်ကိရိယာက တွေ့ရှိထားသော ချန်နယ်လိုင်းများကို ဖယ်ရှားလိုက်ပြီး ချန်နယ်အသစ်များကို ထပ်မံရှာဖွေလိမ့်မည်။\n\nသင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" + "ဤလုပ်ဆောင်ချက်သည် USB ချန်နယ်ချိန်ကိရိယာက တွေ့ရှိထားသော ချန်နယ်လိုင်းများကို ဖယ်ရှားလိုက်ပြီး ချန်နယ်အသစ်များကို ထပ်မံရှာဖွေလိမ့်မည်။\n\nUSB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထားပြီး တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" + "၎င်းသည် တီဗီလိုင်းဖမ်းကိရိယာက ရှာဖွေတွေ့ရှိခဲ့သည့် ချန်နယ်များကို ဖယ်ရှားလိုက်မည်ဖြစ်ပြီး ချန်နယ်အသစ်များကို ထပ်မံရှာဖွေသွားပါမည်။\n\nတီဗီလိုင်းဖမ်းကိရိယာကို ပါဝါဖွင့်ထားခြင်း ရှိ မရှိနှင့် တီဗီစလောင်းသို့ ချိတ်ဆက်ထားခြင်းရှိမရှိ စစ်ဆေးပါ။\n\nအင်တင်နာကို အသုံးပြုလျှင် ၎င်း၏အနေအထား သို့မဟုတ် ဦးတည်ချက်တို့ကို ချိန်ညှိရန် လိုအပ်ပါသည်။ အကောင်းဆုံးရလဒ်အတွက် ပြတင်းပေါက်အနီး အမြင့်ပိုင်းတွင် ထားပါ။" + + "ရှေ့ဆက်ရန်" + "မလုပ်တော့" + + "ချိတ်ဆက်မှု အမျိုးအစားကို ရွေးပါ" + "ချန်နယ်ချိန်ကိရိယာနှင့် ချိတ်ဆက်ထားသော ပြင်ပအန်တန်နာရှိပါက အန်တန်နာကို ရွေးပါ။ ချန်နယ်လိုင်းများအသုံးပြုရန် ကေဘယ်စနစ်သုံး ဝန်ဆောင်မှုပေးသူနှင့် ချိတ်ဆက်ထားပါက ကေဘယ်ကြိုးကို ရွေးပါ။ မသေချာဖြစ်နေလျှင် နှစ်မျိုးလုံးဖြင့် ရှာဖွေနိုင်သော်လည်း အချိန်ပိုကြာနိုင်ပါသည်။" + + "အန်တန်နာ" + "ကေဘယ်ကြိုး" + "မသေချာပါ။" + "စမ်းသပ်မှုအတွက်သာ" + + "တီဗီချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" + "USB ချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" + "တီဗီလိုင်းဖမ်းကိရိယာ စနစ်ထည့်သွင်းမှု" + "မိနစ်အနည်းငယ် ကြာနိုင်ပါသည်" + "လိုင်းချိန်စက်သည် ယာယီမရနိုင်သေးပါ သို့မဟုတ် ဖမ်းယူခြင်းအတွက် အသုံးပြုနေပြီး ဖြစ်ပါသည်။" + + ချန်နယ် %1$d လိုင်းတွေ့သည် + ချန်နယ် %1$d လိုင်းတွေ့သည် + + "ချန်နယ်ရှာဖွေမှုကို ရပ်ရန်" + + ချန်နယ် %1$d လိုင်းတွေ့သည် + ချန်နယ် %1$d လိုင်းတွေ့သည် + + + သိပ်ကောင်း။ ချန်နယ်ရှာဖွေရာတွင် ချန်နယ် %1$d လိုင်းတွေ့သည်။ မှားယွင်းနေသည်ဟုထင်လျှင် အန်တန်နာအနေအထားကို ချိန်ညှိပြီး ထပ်ရှာကြည့်ပါ။ + သိပ်ကောင်း။ ချန်နယ်ရှာဖွေရာတွင် ချန်နယ် %1$d လိုင်းတွေ့သည်။ မှားယွင်းနေသည်ဟုထင်လျှင် အန်တန်နာအနေအထားကို ချိန်ညှိပြီး ထပ်ရှာကြည့်ပါ။ + + + "ပြီးသွားပြီ" + "ထပ်ရှာရန်" + + "ချန်နယ်တစ်လိုင်းမျှ မတွေ့ပါ" + "ချန်နယ်များရှာဖွေရာတွင် တစ်လိုင်းမျှ ရှာမတွေ့ပါ။ သင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\n အကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ၎င်း၏ အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိပါ။ ရလဒ်များအကောင်းဆုံးဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပြီး ထပ်ရှာကြည့်ပါ။" + "ချန်နယ်များရှာဖွေရာတွင် တစ်လိုင်းမျှ ရှာမတွေ့ပါ။ USB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထား၍ တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\n အကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ၎င်း၏ အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိပါ။ ရလဒ်များအကောင်းဆုံးဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပြီး ထပ်ရှာကြည့်ပါ။" + "မည်သည့် ချန်နယ်မျှ ရှာမတွေ့ပါ။ တီဗီလိုင်းဖမ်းကိရိယာကို ပါဝါဖွင့်ထားခြင်းရှိ မရှိနှင့် တီဗီစလောင်းသို့ ချိတ်ဆက်ထားခြင်းရှိမရှိ စစ်ဆေးပါ။\n\nအင်တင်နာကို အသုံးပြုလျှင် ၎င်း၏အနေအထား သို့မဟုတ် ဦးတည်ချက်တို့ကို ချိန်ညှိရန် လိုအပ်ပါသည်။ အကောင်းဆုံးရလဒ်အတွက် ပြတင်းပေါက်အနီး အမြင့်ပိုင်းတွင် ထားပါ။" + + "ထပ်ရှာရန်" + "ပြီးသွားပြီ" + + "TV ချန်နယ်ရှာမည်" + "တီဗီဖမ်းစက် အပြင်အဆင်" + "USB တီဗီဖမ်းစက် အပြင်အဆင်" + "အင်တာနက် တီဗီဖမ်းစက် အပြင်အဆင်" + "USB တီဗီလိုင်းချိန်ကိရိယာကို ဖြုတ်လိုက်ပါပြီ။" + "ကွန်ရက်လိုင်းချိန်ကိရိယာကို ဖြုတ်လိုက်ပါပြီ။" + diff --git a/usbtuner-res/values-my/strings.xml b/usbtuner-res/values-my/strings.xml deleted file mode 100644 index 56a29e51..00000000 --- a/usbtuner-res/values-my/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "တီဗီချန်နယ်ချိန်ကိရိယာ" - "USB တီဗီချန်နယ်ချိန်ကိရိယာ" - "ဖွင့်ပါ" - "ပိတ်ပါ" - "စီမံဆောင်ရွက်မှု အဆုံးသတ်ရန် ခဏစောင့်ပါ" - "သင့်ချန်နယ်လိုင်းထုတ်လွှင့်ရာ အရင်းအမြစ်ကို ရွေးပါ" - "လိုင်းမမိပါ" - "%s သို့ ချိန်ညှိခြင်း မအောင်မြင်ပါ" - "ချိန်ညှိ၍ မရခဲ့ပါ" - "ချန်နယ်ချိန်ဆော့ဖ်ဝဲကို မကြာသေးမီက အပ်ဒိတ်လုပ်ခဲ့သည်။ ချန်နယ်လိုင်းများကို ပြန်ရှာပါ။" - "အသံဖွင့်ရန် ပတ်ပတ်လည်အသံစနစ်ဆက်တင်များကို ဖွင့်ပါ" - "ချန်နယ်ချိန်ကိရိယာ ထည့်သွင်းတပ်ဆင်မှု" - "တီဗီချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" - "USB ချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" - "သင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" - "USB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထားပြီး တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" - - "ရှေ့ဆက်ရန်" - "မလုပ်သေးပါ" - - "ချန်နယ်စနစ်ထည့်သွင်းမှု ပြန်စမလား။" - "ဤလုပ်ဆောင်ချက်သည် တီဗီချန်နယ်ချိန်ကိရိယာက တွေ့ရှိထားသော ချန်နယ်လိုင်းများကို ဖယ်ရှားလိုက်ပြီး ချန်နယ်အသစ်များကို ထပ်မံရှာဖွေလိမ့်မည်။\n\nသင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" - "ဤလုပ်ဆောင်ချက်သည် USB ချန်နယ်ချိန်ကိရိယာက တွေ့ရှိထားသော ချန်နယ်လိုင်းများကို ဖယ်ရှားလိုက်ပြီး ချန်နယ်အသစ်များကို ထပ်မံရှာဖွေလိမ့်မည်။\n\nUSB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထားပြီး တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\nအကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ချန်နယ်အများစုကို ဖမ်းယူနိုင်ရန် ၎င်း၏အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိရန် လိုပါသည်။ ရလဒ်များအကောင်းဆုံး ဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပါ။" - - "ရှေ့ဆက်ရန်" - "မလုပ်တော့" - - "ချိတ်ဆက်မှု အမျိုးအစားကို ရွေးပါ" - "ချန်နယ်ချိန်ကိရိယာနှင့် ချိတ်ဆက်ထားသော ပြင်ပအန်တန်နာရှိပါက အန်တန်နာကို ရွေးပါ။ ချန်နယ်လိုင်းများအသုံးပြုရန် ကေဘယ်စနစ်သုံး ဝန်ဆောင်မှုပေးသူနှင့် ချိတ်ဆက်ထားပါက ကေဘယ်ကြိုးကို ရွေးပါ။ မသေချာဖြစ်နေလျှင် နှစ်မျိုးလုံးဖြင့် ရှာဖွေနိုင်သော်လည်း အချိန်ပိုကြာနိုင်ပါသည်။" - - "အန်တန်နာ" - "ကေဘယ်ကြိုး" - "မသေချာပါ။" - "စမ်းသပ်မှုအတွက်သာ" - - "တီဗီချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" - "USB ချန်နယ်ချိန်ကိရိယာ ပြင်ဆင်သတ်မှတ်မှု" - "မိနစ်အနည်းငယ် ကြာနိုင်ပါသည်" - "လိုင်းချိန်စက်သည် ယာယီမရနိုင်သေးပါ သို့မဟုတ် ဖမ်းယူခြင်းအတွက် အသုံးပြုနေပြီး ဖြစ်ပါသည်။" - - ချန်နယ် %1$d လိုင်းတွေ့သည် - ချန်နယ် %1$d လိုင်းတွေ့သည် - - "ချန်နယ်ရှာဖွေမှုကို ရပ်ရန်" - - ချန်နယ် %1$d လိုင်းတွေ့သည် - ချန်နယ် %1$d လိုင်းတွေ့သည် - - - သိပ်ကောင်း။ ချန်နယ်ရှာဖွေရာတွင် ချန်နယ် %1$d လိုင်းတွေ့သည်။ မှားယွင်းနေသည်ဟုထင်လျှင် အန်တန်နာအနေအထားကို ချိန်ညှိပြီး ထပ်ရှာကြည့်ပါ။ - သိပ်ကောင်း။ ချန်နယ်ရှာဖွေရာတွင် ချန်နယ် %1$d လိုင်းတွေ့သည်။ မှားယွင်းနေသည်ဟုထင်လျှင် အန်တန်နာအနေအထားကို ချိန်ညှိပြီး ထပ်ရှာကြည့်ပါ။ - - - "ပြီးသွားပြီ" - "ထပ်ရှာရန်" - - "ချန်နယ်တစ်လိုင်းမျှ မတွေ့ပါ" - "ချန်နယ်များရှာဖွေရာတွင် တစ်လိုင်းမျှ ရှာမတွေ့ပါ။ သင့်တီဗီသည် တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\n အကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ၎င်း၏ အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိပါ။ ရလဒ်များအကောင်းဆုံးဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပြီး ထပ်ရှာကြည့်ပါ။" - "ချန်နယ်များရှာဖွေရာတွင် တစ်လိုင်းမျှ ရှာမတွေ့ပါ။ USB ချန်နယ်ချိန်ကိရိယာကို တပ်ဆင်ထား၍ တီဗီချန်နယ်ထုတ်လွှင့်ရာ အရင်းအမြစ်တစ်ခုနှင့် ချိတ်ဆက်ထားကြောင်း အတည်ပြုပါ။\n\n အကယ်၍ ကောင်းကင်အန်တန်နာတိုင်ကို အသုံးပြုနေပါက ၎င်း၏ အနေအထား (သို့) ဦးတည်ဘက်ကို ချိန်ညှိပါ။ ရလဒ်များအကောင်းဆုံးဖြစ်စေရန် ၎င်းကို ပြတင်းပေါက်နားတွင် မြင့်မြင့်ထားပြီး ထပ်ရှာကြည့်ပါ။" - - "ထပ်ရှာရန်" - "ပြီးသွားပြီ" - - "တီဗီချန်နယ်များကို ရှာပါ" - "တီဗီချန်နယ်ချိန် ကိရိယာအက်ပ် ထည့်သွင်းမှု" - "USB တီဗီချန်နယ်ချိန်အက်ပ် ထည့်သွင်းမှု" - "USB တီဗီချိန်စက်ကို ဖြုတ်လိုက်ပါပြီ" - diff --git a/usbtuner-res/values-nb/strings.xml b/usbtuner-res/values-nb/strings.xml index bce9e176..ed45555b 100644 --- a/usbtuner-res/values-nb/strings.xml +++ b/usbtuner-res/values-nb/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV-tuner" "USB-tuneren for TV" - "På" - "Av" + "Nettverkstuner for TV (betaversjon)" "Vent til behandlingen er fullført" - "Velg en kanalkilde" - "Ikke noe signal" - "Kunne ikke bytte kanal til %s" - "Kunne ikke bytte kanal" "Programvaren for tuneren er nylig blitt oppdatert. Du må skanne kanalene på nytt." "Slå på surroundlyd i innstillingene for systemlyd for å slå på lyd" + "Kan ikke spille av lyd. Prøv en annen TV" "Konfigurasjon av kanaler via tuneren" "Konfigurasjon av TV-tuneren" "Konfigurasjon av kanaler via USB-tuneren" + "Konfigurasjon av nettverkstuner" "Bekreft at TV-en din er koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan det hende du må justere posisjonen eller retningen for å motta flest mulig kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." "Bekreft at USB-tuneren er plugget i og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan det hende du må justere posisjonen eller retningen for å motta flest mulig kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." + "Bekreft at nettverkstuneren er slått på og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, må du kanskje justere posisjonen eller retningen for å motta flere kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." "Fortsett" "Ikke nå" @@ -40,6 +38,7 @@ "Vil du kjøre kanalkonfigureringen på nytt?" "Dette fjerner kanalene som ble funnet med TV-tuneren, og skanner etter nye kanaler igjen.\n\nBekreft at TV-en din er koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan det hende du må justere posisjonen eller retningen for å motta flest mulig kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." "Dette fjerner kanaler som er funnet via USB-tuneren, og skanner på nytt etter nye kanaler.\n\nBekreft at USB-tuneren er plugget i og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan det hende du må justere posisjonen eller retningen for å motta flest mulig kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." + "Dette fjerner kanalene som ble funnet av nettverkstuneren, og skanner på nytt etter nye kanaler.\n\nBekreft at nettverkstuneren er slått på og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, må du kanskje justere posisjonen eller retningen for å motta flere kanaler. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu." "Fortsett" "Avbryt" @@ -54,6 +53,7 @@ "Konfigurasjon av TV-tuneren" "Konfigurasjon av kanaler via USB-tuneren" + "Konfigurasjon av tuner for nettverkskanaler" "Dette kan ta flere minutter" "Tuneren er midlertidig utilgjengelig eller brukes allerede av opptak." @@ -76,12 +76,15 @@ "Fant ingen kanaler" "Ingen kanaler ble funnet under skanningen. Bekreft at TV-en din er koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan du justere posisjonen eller retningen. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu. Deretter skanner du på nytt." "Fant ingen kanaler under skanningen. Bekreft at USB-tuneren er plugget i og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, kan du justere posisjonen eller retningen. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu. Deretter skanner du på nytt." + "Skanningen fant ingen kanaler. Bekreft at nettverkstuneren er slått på og koblet til en TV-signalkilde.\n\nHvis du bruker en trådløs antenne, må du justere posisjonen eller retningen. For å få de beste resultatene bør du plassere antennen høyt og i nærheten av et vindu. Deretter skanner du på nytt." "Skann på nytt" "Ferdig" - "Skann etter TV-kanaler" - "Konfigurasjon av TV-tuneren" - "Konfigurasjon av USB-tuner for TV" - "USB-tuneren for TV er frakoblet." + "Skann etter TV-kanaler" + "Konfigurasjon av TV-tuneren" + "Konfigurasjon av USB-tuner for TV" + "Konfigurasjon av nettverkstuner for TV" + "USB-tuneren for TV er frakoblet." + "Nettverkstuneren er frakoblet." diff --git a/usbtuner-res/values-ne-rNP/strings.xml b/usbtuner-res/values-ne-rNP/strings.xml new file mode 100644 index 00000000..5f1e3330 --- /dev/null +++ b/usbtuner-res/values-ne-rNP/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV ट्युनर" + "USB TV ट्युनर" + "नेटवर्कको TV ट्युनर (बिटा)" + "कृपया प्रक्रिया सम्पन्न हुने प्रतीक्षा गर्नुहोस्" + "ट्युनरको सफ्टवेयरलाई हालसालै अद्यावधिक गरिएको छ। कृपया च्यानलहरू पुन:स्क्यान गर्नुहोस्।" + "अडियोलाई सक्षम पार्न प्रणालीको ध्वनि सम्बन्धी सेटिङहरूमा गई सराउन्ड साउन्डलाई सक्षम पार्नुहोस्" + "अडियो बजाउन सकिँदैन। कृपया अर्को TV को प्रयोग गरी हेर्नुहोस्" + "च्यानल ट्युनरको सेटअप" + "TV ट्युनरको सेटअप" + "USB च्यानल ट्युनरको सेटअप" + "नेटवर्क ट्युनरको सेटअप" + "तपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + "USB ट्युनर प्लगइन रहेको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + "नेटवर्क ट्युनर सक्रिय रहेको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईंले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + + "जारी राख्नुहोस्" + "अहिले होइन" + + "च्यानलको सेटअप पुनःसञ्चालन गर्ने हो?" + "यसले USB ट्युनरबाट भेट्टिएका च्यानलहरूलाई हटाउनेछ र नयाँ च्यानलहरू भेट्टाउन फेरि स्क्यान गर्नेछ।\n\nतपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + "यसले USB ट्युनरबाट भेट्टिएका च्यानलहरूलाई हटाउनेछ र नयाँ च्यानलहरू भेट्टाउन फेरि स्क्यान गर्नेछ।\n\nUSB ट्युनर प्लगइन गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + "यसले नेटवर्क ट्युनर मार्फत भेट्टिएका च्यानलहरूलाई हटाउनेछ र नयाँ च्यानलहरू भेट्टाउन फेरि स्क्यान गर्नेछ।\n\nनेटवर्क ट्युनरलाई सक्रिय गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईंले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" + + "जारी राख्नुहोस्" + "रद्द गर्नुहोस्" + + "जडानको प्रकार चयन गर्नुहोस्" + "यदि उक्त ट्युनरमा कुनै बाह्य एन्टेना जडान गरिएको छ भने एन्टेना छनौट गर्नुहोस्। यदि तपाईंका च्यानलहरू केबल सेवा प्रदायक मार्फत आउँछन् भने केबल छनौट गर्नुहोस्। यदि तपाईं निश्चित हुनुहुन्न भने दुवै प्रकारहरू स्क्यान गरिने छन् तर यसमा लामो समय लाग्न सक्छ।" + + "एन्टेना" + "केबल" + "निश्चित छैन" + "विकासका लागि मात्र" + + "TV ट्युनरको सेटअप" + "USB च्यानल ट्युनरको सेटअप" + "नेटवर्कको च्यानल ट्युनरको सेटअप" + "यसमा धेरै मिनेट लाग्न सक्छ" + "ट्युनर अस्थायी रूपले अनुपलब्ध छ वा रेकर्डिङद्वारा पहिले नै प्रयोग गरिएको छ।" + + %1$d च्यानलहरू भेट्टिए + %1$d च्यानल भेट्टियो + + "च्यानल स्क्यान गर्न रोक्नुहोस्" + + %1$d च्यानलहरू भेट्टिए + %1$d च्यानल भेट्टियो + + + राम्रो खबर छ! च्यानल स्क्यान गर्दा %1$d च्यानलहरू भेट्टिए। यदि यो सही हो जस्तो लाग्दैन भने एन्टेनाको स्थिति समायोजन गरी हेर्नुहोस् र फेरि स्क्यान गर्नुहोस्। + राम्रो खबर छ! च्यानल स्क्यान गर्दा %1$d च्यानल भेट्टियो। यदि यो सही हो जस्तो लाग्दैन भने एन्टेनाको स्थिति समायोजन गरी हेर्नुहोस् र फेरि स्क्यान गर्नुहोस्। + + + "सम्पन्न भयो" + "फेरि स्क्यान गर्नुहोस्" + + "कुनै च्यानल भेट्टिएन" + "उक्त स्क्यानले कुनै पनि च्यानल भेट्टाएन। तपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने त्यसको स्थान वा दिशा समायोजन गर्नुहोस्। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राखी फेरि स्क्यान गर्नुहोस्।" + "उक्त स्क्यानले कुनै पनि च्यानल भेट्टाएन। USB ट्युनर प्लगइन गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने त्यसको स्थान वा दिशा समायोजन गर्नुहोस्। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राखी फेरि स्क्यान गर्नुहोस्।" + "उक्त स्क्यानले कुनै पनि च्यानल भेट्टाएन। नेटवर्क ट्युनरलाई सक्रिय गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने त्यसको स्थान वा दिशा समायोजन गर्नुहोस्। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राखी फेरि स्क्यान गर्नुहोस्।" + + "फेरि स्क्यान गर्नुहोस्" + "सम्पन्न भयो" + + "TV च्यानलहरू भेट्टाउन स्क्यान गर्नुहोस्" + "TV ट्युनरको सेटअप" + "USB TV ट्युनरको सेटअप" + "नेटवर्कको TV ट्युनरको सेटअप" + "USB TV ट्युनरलाई विच्छेद गरियो।" + "नेटवर्क ट्युनरलाई विच्छेद गरियो।" + diff --git a/usbtuner-res/values-ne/strings.xml b/usbtuner-res/values-ne/strings.xml deleted file mode 100644 index 07d68e2c..00000000 --- a/usbtuner-res/values-ne/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV ट्युनर" - "USB TV ट्युनर" - "सक्रिय" - "निष्क्रिय" - "कृपया प्रक्रिया सम्पन्न हुने प्रतीक्षा गर्नुहोस्" - "आफ्नो च्यानलको स्रोत चयन गर्नुहोस्" - "कुनै सिग्‍नल छैन" - "%s मा ट्युन गर्न सकिएन" - "ट्युन गर्न सकिएन" - "ट्युनरको सफ्टवेयरलाई हालसालै अद्यावधिक गरिएको छ। कृपया च्यानलहरू पुन:स्क्यान गर्नुहोस्।" - "अडियोलाई सक्षम पार्न प्रणालीको ध्वनि सम्बन्धी सेटिङहरूमा गई सराउन्ड साउन्डलाई सक्षम पार्नुहोस्" - "च्यानल ट्युनरको सेटअप" - "TV ट्युनरको सेटअप" - "USB च्यानल ट्युनरको सेटअप" - "तपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" - "USB ट्युनर प्लगइन रहेको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" - - "जारी राख्नुहोस्" - "अहिले होइन" - - "च्यानलको सेटअप पुनःसञ्चालन गर्ने हो?" - "यसले USB ट्युनरबाट भेट्टिएका च्यानलहरूलाई हटाउनेछ र नयाँ च्यानलहरू भेट्टाउन फेरि स्क्यान गर्नेछ।\n\nतपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" - "यसले USB ट्युनरबाट भेट्टिएका च्यानलहरूलाई हटाउनेछ र नयाँ च्यानलहरू भेट्टाउन फेरि स्क्यान गर्नेछ।\n\nUSB ट्युनर प्लगइन गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने धेरै च्यानलहरू प्राप्त गर्न तपाईँले त्यसको स्थान वा दिशा समायोजन गर्नुपर्ने हुन सक्छ। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राख्नुहोस्।" - - "जारी राख्नुहोस्" - "रद्द गर्नुहोस्" - - "जडानको प्रकार चयन गर्नुहोस्" - "यदि उक्त ट्युनरमा कुनै बाह्य एन्टेना जडान गरिएको छ भने एन्टेना छनौट गर्नुहोस्। यदि तपाईँका च्यानलहरू केबल सेवा प्रदायक मार्फत आउँछन् भने केबल छनौट गर्नुहोस्। यदि तपाईँ निश्चित हुनुहुन्न भने दुवै प्रकारहरू स्क्यान गरिने छन् तर यसमा लामो समय लाग्न सक्छ।" - - "एन्टेना" - "केबल" - "निश्चित छैन" - "विकासका लागि मात्र" - - "TV ट्युनरको सेटअप" - "USB च्यानल ट्युनरको सेटअप" - "यसमा धेरै मिनेट लाग्न सक्छ" - "ट्युनर अस्थायी रूपले अनुपलब्ध छ वा रेकर्डिङद्वारा पहिले नै प्रयोग गरिएको छ।" - - %1$d च्यानलहरू भेट्टिए - %1$d च्यानल भेट्टियो - - "च्यानल स्क्यान गर्न रोक्नुहोस्" - - %1$d च्यानलहरू भेट्टिए - %1$d च्यानल भेट्टियो - - - राम्रो खबर छ! च्यानल स्क्यान गर्दा %1$d च्यानलहरू भेट्टिए। यदि यो सही हो जस्तो लाग्दैन भने एन्टेनाको स्थिति समायोजन गरी हेर्नुहोस् र फेरि स्क्यान गर्नुहोस्। - राम्रो खबर छ! च्यानल स्क्यान गर्दा %1$d च्यानल भेट्टियो। यदि यो सही हो जस्तो लाग्दैन भने एन्टेनाको स्थिति समायोजन गरी हेर्नुहोस् र फेरि स्क्यान गर्नुहोस्। - - - "सम्पन्न भयो" - "फेरि स्क्यान गर्नुहोस्" - - "कुनै च्यानल भेट्टिएन" - "उक्त स्क्यानले कुनै पनि च्यानल भेट्टाएन। तपाईँको TV कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने त्यसको स्थान वा दिशा समायोजन गर्नुहोस्। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राखी फेरि स्क्यान गर्नुहोस्।" - "उक्त स्क्यानले कुनै पनि च्यानल भेट्टाएन। USB ट्युनर प्लगइन गरिएको र कुनै TV सिग्नलको स्रोतमा जडान गरिएको छ भनी पुष्टि गर्नुहोस्।\n\nयदि कुनै ओभर-दि-एयर एन्टेनाको प्रयोग भइरहेको छ भने त्यसको स्थान वा दिशा समायोजन गर्नुहोस्। उत्कृष्ट परिणामहरूका लागि त्यसलाई उच्च स्थानमा र कुनै झ्यालको नजिक राखी फेरि स्क्यान गर्नुहोस्।" - - "फेरि स्क्यान गर्नुहोस्" - "सम्पन्न भयो" - - "TV च्यानलहरू भेट्टाउन स्क्यान गर्नुहोस्" - "TV ट्युनरको सेटअप" - "USB TV ट्युनरको सेटअप" - "USB TV ट्युनरलाई विच्छेद गरियो।" - diff --git a/usbtuner-res/values-nl/strings.xml b/usbtuner-res/values-nl/strings.xml index b9e18682..34b0d927 100644 --- a/usbtuner-res/values-nl/strings.xml +++ b/usbtuner-res/values-nl/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tv-tuner" "USB-tv-tuner" - "Aan" - "Uit" + "Netwerk-tv-tuner (BÈTA)" "Wacht tot het proces is voltooid" - "Selecteer je kanaalbron" - "Geen signaal" - "Kan niet afstemmen op %s" - "Kan niet afstemmen" "De software van de tuner is recent geüpdatet. Scan de kanalen opnieuw." "Schakel surrond sound in via de geluidsinstellingen van het systeem om audio in te schakelen" + "Kan audio niet afspelen. Probeer een andere tv." "Configuratie van kanaaltuner" "Tv-tuner instellen" "Kanaalconfiguratie van USB-tuner" + "Netwerktuner instellen" "Controleer of je tv is aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." "Controleer of de USB-tuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." + "Controleer of de netwerktuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." "Doorgaan" "Niet nu" @@ -40,6 +38,7 @@ "Kanaalconfiguratie opnieuw uitvoeren?" "Hiermee worden de gevonden kanalen verwijderd van de tv-tuner en wordt opnieuw gezocht naar nieuwe kanalen.\n\nControleer of je tv is aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." "Hiermee worden de gevonden kanalen verwijderd van de USB-tuner en wordt opnieuw gescand naar nieuwe kanalen.\n\nControleer of de USB-tuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." + "Hiermee worden de gevonden kanalen verwijderd van de netwerktuner en wordt opnieuw gezocht naar nieuwe kanalen.\n\nControleer of de netwerktuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen om zo veel mogelijk kanalen te ontvangen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam." "Doorgaan" "Annuleren" @@ -54,6 +53,7 @@ "Tv-tuner instellen" "Kanaalconfiguratie van USB-tuner" + "Netwerkkanaaltuner instellen" "Dit kan enkele minuten duren" "De tuner is tijdelijk niet beschikbaar of wordt al gebruikt voor een opname." @@ -76,12 +76,15 @@ "Geen kanalen gevonden" "Er zijn geen kanalen gevonden tijdens de scan. Controleer of je tv is aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, pas je de positie of richting daarvan aan. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam en voer je de scan opnieuw uit." "Er zijn geen kanalen gevonden tijdens de scan. Controleer of de USB-tuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, pas je de positie of richting daarvan aan. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam en voer je de scan opnieuw uit." + "De scan heeft geen kanalen gevonden. Controleer of de netwerktuner is ingeschakeld en aangesloten op een tv-signaalbron.\n\nAls je een over-the-air-antenne gebruikt, moet je de positie of richting daarvan mogelijk aanpassen. Voor de beste resultaten plaats je de antenne op een hoge plek in de buurt van een raam en voer je de scan opnieuw uit." "Opnieuw scannen" "Gereed" - "Scannen naar tv-kanalen" - "Tv-tuner instellen" - "USB-tv-tuner instellen" - "USB-tv-tuner ontkoppeld." + "Zoeken naar tv-zenders" + "Tv-tuner instellen" + "USB-tv-tuner instellen" + "Netwerk-tv-tuner instellen" + "USB-tv-tuner losgekoppeld." + "Netwerktuner losgekoppeld." diff --git a/usbtuner-res/values-pl/strings.xml b/usbtuner-res/values-pl/strings.xml index 0bc7e734..9daa3621 100644 --- a/usbtuner-res/values-pl/strings.xml +++ b/usbtuner-res/values-pl/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tuner TV" "Tuner TV USB" - "Włącz" - "Wyłącz" + "Sieciowy tuner TV (BETA)" "Poczekaj na zakończenie przetwarzania" - "Wybierz źródło kanału" - "Brak sygnału" - "Nie udało się dostroić kanału %s" - "Nie udało się dostroić kanału" "Oprogramowanie tunera zostało niedawno zaktualizowane. Przeskanuj ponownie kanały." "Aby włączyć dźwięk, włącz dźwięk przestrzenny w ustawieniach systemowych dźwięku" + "Nie można odtworzyć dźwięku. Spróbuj użyć innego telewizora" "Konfiguracja kanałów w tunerze" "Konfiguracja tunera TV" "Konfiguracja kanałów w tunerze USB" + "Konfiguracja tunera sieciowego" "Upewnij się, że telewizor jest podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." "Upewnij się, że tuner USB jest podłączony do telewizora oraz do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." + "Upewnij się, że tuner sieciowy jest włączony i został podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." "Kontynuuj" "Nie teraz" @@ -40,6 +38,7 @@ "Skonfigurować ponownie kanały?" "Spowoduje to usunięcie kanałów znalezionych przez tuner TV i ponowne wykonanie skanowania.\n\nUpewnij się, że telewizor jest podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." "Spowoduje to usunięcie kanałów znalezionych przez tuner USB i ponowne wykonanie skanowania.\n\nUpewnij się, że tuner USB jest podłączony do telewizora oraz do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." + "Spowoduje to usunięcie kanałów znalezionych przez tuner sieciowy i ponowne wykonanie skanowania.\n\nUpewnij się, że tuner sieciowy jest włączony i został podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, może być konieczne wyregulowanie jej położenia lub kierunku, by można było odbierać jak najwięcej kanałów. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna." "Kontynuuj" "Anuluj" @@ -54,6 +53,7 @@ "Konfiguracja tunera TV" "Konfiguracja kanałów w tunerze USB" + "Konfiguracja kanałów w tunerze sieciowym" "Może to potrwać kilka minut" "Tuner jest czasowo niedostępny lub właśnie nagrywa." @@ -82,12 +82,15 @@ "Nie znaleziono kanałów" "Podczas skanowania nie znaleziono żadnych kanałów. Upewnij się, że telewizor jest podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, wyreguluj jej położenie lub kierunek. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna, a następnie ponownie wykonaj skanowanie." "Podczas skanowania nie znaleziono żadnych kanałów. Upewnij się, że tuner USB jest podłączony do telewizora oraz do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, wyreguluj jej położenie lub kierunek. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna, a następnie ponownie wykonaj skanowanie." + "Podczas skanowania nie znaleziono żadnych kanałów. Upewnij się, że tuner sieciowy jest włączony i został podłączony do źródła sygnału telewizyjnego.\n\nJeśli używasz anteny telewizyjnej, wyreguluj jej położenie lub kierunek. Aby uzyskać najlepszy sygnał, umieść antenę na podwyższeniu, w pobliżu okna, a następnie ponownie wykonaj skanowanie." "Skanuj ponownie" "Gotowe" - "Wyszukaj kanały TV" - "Konfiguracja tunera TV" - "Konfiguracja tunera TV USB" - "Tuner TV USB odłączony." + "Wyszukaj kanały TV" + "Konfiguracja tunera TV" + "Konfiguracja tunera TV USB" + "Konfiguracja sieciowego tunera TV" + "Telewizyjny tuner USB rozłączony." + "Tuner sieciowy rozłączony." diff --git a/usbtuner-res/values-pt-rPT/strings.xml b/usbtuner-res/values-pt-rPT/strings.xml index c78c92c1..02f4dd9d 100644 --- a/usbtuner-res/values-pt-rPT/strings.xml +++ b/usbtuner-res/values-pt-rPT/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonizador de TV" "Sintonizador de TV USB" - "Ativar" - "Desativar" + "Sintonizador de televisão (BETA)" "Aguarde enquanto o processamento é terminado" - "Selecione a origem do canal" - "Sem sinal" - "Falha ao sintonizar %s" - "Falha ao sintonizar" "O software do sintonizador foi atualizado recentemente. Procure novamente os canais." "Ative o som surround nas definições de som do sistema para ativar o áudio" + "Não é possível reproduzir áudio. Experimente outra TV." "Configuração do sintonizador de canais" "Configuração do sintonizador de TV" "Configuração do sintonizador de canais USB" + "Configuração do sintonizador de rede" "Confirme se a sua TV está ligada a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou a direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." "Confirme se o sintonizador USB está ativado e ligado a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou a direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." + "Confirme se o sintonizador de rede está ligado à alimentação e ligado a uma fonte de sinal de TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." "Continuar" "Agora não" @@ -40,6 +38,7 @@ "Pretende executar novamente a configuração do canal?" "Esta ação remove os canais encontrados pelo sintonizador de TV e procura novamente canais novos.\n\nConfirme se a sua TV está ligada a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou a direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." "Esta ação remove os canais encontrados do sintonizador USB e procura novamente canais novos.\n\nConfirme se o sintonizador USB está ativado e ligado a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou a direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." + "Esta ação vai remover os canais encontrados do sintonizador de rede e procurar novos canais novamente.\n\nConfirme se o sintonizador de rede está ligado à alimentação e ligado a uma fonte de sinal de TV.\n\nSe estiver a utilizar uma antena via rede sem fios, pode ter de ajustar a respetiva posição ou direção para receber a maioria dos canais. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela." "Continuar" "Cancelar" @@ -54,20 +53,21 @@ "Configuração do sintonizador de TV" "Configuração do sintonizador de canais USB" + "Configuração do sintonizador de canais" "Esta operação pode demorar vários minutos" "O sintonizador está temporariamente indisponível ou já está a ser utilizado pela gravação." - %1$d canais encontrados %1$d canal encontrado + %1$d canais encontrados "INTERROMPER A PROCURA DE CANAIS" - %1$d canais encontrados %1$d canal encontrado + %1$d canais encontrados + Boa! Foi encontrado %1$d canal durante a procura de canais. Se isso não lhe parecer correto, experimente ajustar a posição da antena e procurar novamente. Boa! Foram encontrados %1$d canais durante a procura de canais. Se isto não lhe parecer correto, experimente ajustar a posição da antena e procurar novamente. - Boa! Foi encontrado %1$d canal durante a procura de canais. Se isto não lhe parecer correto, experimente ajustar a posição da antena e procurar novamente. "Concluído" @@ -76,12 +76,15 @@ "Não foram encontrados canais" "A procura não encontrou quaisquer canais. Confirme se a TV está ligada a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, ajuste a respetiva posição ou a direção. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela e procure novamente." "Não foram encontrados quaisquer canais. Verifique se o sintonizador USB está ativado e ligado a uma fonte de sinal da TV.\n\nSe estiver a utilizar uma antena via rede sem fios, ajuste a respetiva posição ou a direção. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela e procure novamente." + "A procura não encontrou quaisquer canais. Confirme se o sintonizador de rede está ligado à alimentação e ligado a uma fonte de sinal de TV.\n\nSe estiver a utilizar uma antena via rede sem fios, ajuste a respetiva posição ou direção. Para obter os melhores resultados, coloque-a num ponto alto e junto a uma janela. Em seguida, procure novamente." "Procurar novamente" "Concluído" - "Procurar canais de TV" - "Configuração do sintonizador de TV" - "Configuração do sintonizador de TV USB" - "Sintonizador de TV USB desligado." + "Procurar canais de TV" + "Configuração do sintonizador de TV" + "Configuração do sintonizador de TV USB" + "Configuração do sintonizador de TV por cabo" + "Sintonizador de TV USB desligado." + "Sintonizador de rede desligado." diff --git a/usbtuner-res/values-pt/strings.xml b/usbtuner-res/values-pt/strings.xml index 3dfd30c0..d123306a 100644 --- a/usbtuner-res/values-pt/strings.xml +++ b/usbtuner-res/values-pt/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sintonizador de TV" "Sintonizador de TV USB" - "Ativar" - "Desativar" + "Sintonizador de rede de TV (BETA)" "Aguarde a conclusão do processamento" - "Selecione a fonte do canal" - "Sem sinal" - "Falha ao sintonizar %s" - "Falha ao sintonizar" "O software do sintonizador foi atualizado recentemente. Procure os canais mais uma vez." "Ative o som surround nas configurações de som do sistema para ativar o áudio" + "Não foi possível reproduzir o áudio. Tente em outra TV" "Configuração do sintonizador de canais" "Configuração do Sintonizador de TV" "Configuração do sintonizador de canais USB" + "Configuração do sintonizador de rede" "Verifique se sua TV está conecta a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena Over-the-air (OTA), talvez seja necessário ajustar o posicionamento ou a direção dela para receber o máximo de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." "Verifique se o sintonizador USB está conectado a uma fonte de sinal de TV.\n\nSe você usa uma antena Over the air, talvez seja necessário ajustar o posicionamento ou a direção dela para receber o maior número de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." + "Verifique se o sintonizador de rede está ligado e conectado a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena OTA (over-the-air), talvez seja necessário ajustar o posicionamento ou a direção dela para receber o máximo de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." "Continuar" "Agora não" @@ -40,6 +38,7 @@ "Executar novamente a configuração de canais?" "Isso removerá os canais encontrados do Sintonizador de TV e procurará novos canais mais uma vez.\n\nVerifique se sua TV está conectada a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena Over-the-air (OTA), talvez seja necessário ajustar o posicionamento ou a direção dela para receber o máximo de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." "Essa ação remove os canais encontrados pelo sintonizador USB e procura canais novamente.\n\nVerifique se o sintonizador USB está conectado a uma fonte de sinal de TV.\n\n.Se você usa uma antena Over the air, talvez seja necessário ajustar o posicionamento ou a direção dela para receber o maior número de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." + "Essa ação removerá os canais encontrados do sintonizador de rede e procurará novos canais mais uma vez.\n\nVerifique se o sintonizador de rede está ligado e conectado a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena OTA (over-the-air), talvez seja necessário ajustar o posicionamento ou a direção dela para receber o máximo de canais. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela." "Continuar" "Cancelar" @@ -54,6 +53,7 @@ "Configuração do Sintonizador de TV" "Configuração do sintonizador de canais USB" + "Configuração do sintonizador de canais de rede" "Isso pode demorar alguns minutos" "O sintonizador está temporariamente indisponível ou já está sendo usado pela gravação." @@ -76,12 +76,15 @@ "Nenhum canal encontrado" "A procura não encontrou nenhum canal. Verifique se sua TV está conectada a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena Over-the-air (OTA), ajuste o posicionamento ou a direção dela. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela e procure novamente." "Nenhum canal foi encontrado pela procura. Verifique se o sintonizador USB está conectado a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena Over the air, ajuste o posicionamento ou a direção dela. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela e procure novamente." + "Nenhum canal foi encontrado pela procura. Verifique se o sintonizador de rede está ligado e conectado a uma fonte de sinal de TV.\n\nSe você estiver usando uma antena OTA (over-the-air), ajuste o posicionamento ou a direção dela. Para ter resultados melhores, coloque-a em um lugar alto e perto de uma janela, depois procure novamente." "Procurar novamente" "Concluído" - "Procurar canais de TV" - "Configuração do Sintonizador de TV" - "Configuração do Sintonizador de TV USB" - "Sintonizador de TV USB desconectado." + "Procurar canais de TV" + "Configuração do Sintonizador de TV" + "Configuração do Sintonizador de TV USB" + "Configuração do sintonizador de rede de TV" + "Sintonizador de TV USB desconectado." + "Sintonizador de rede desconectado." diff --git a/usbtuner-res/values-ro/strings.xml b/usbtuner-res/values-ro/strings.xml index 57f1ca48..d30101a4 100644 --- a/usbtuner-res/values-ro/strings.xml +++ b/usbtuner-res/values-ro/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Tuner TV" "Tuner TV USB" - "Activați" - "Dezactivați" + "Tuner de rețea TV (BETA)" "Așteptați ca procesarea să fie finalizată" - "Selectați sursa canalului" - "Fără semnal" - "Canalul %s nu a putut fi selectat" - "Canalul nu a putut fi selectat" "Software-ul tunerului a fost actualizat recent. Scanați din nou canalele." "Pentru a activa conținutul audio, activați sunetul surround din setările de sunet ale sistemului" + "Conținutul audio nu poate fi redat. Încercați pe un alt televizor." "Configurarea tunerului de canale" "Configurarea tunerului TV" "Configurarea tunerului de canale USB" + "Configurarea tunerului de rețea" "Asigurați-vă că televizorul este conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over the air, poate fi necesar să-i ajustați amplasarea sau direcția astfel încât să capteze majoritatea canalelor. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre." "Asigurați-vă că tunerul USB este conectat la o sursă de alimentare și la o sursă de semnal TV.\n\nDacă folosiți o antenă over the air, poate fi necesar să-i ajustați amplasarea sau direcția astfel încât să capteze majoritatea canalelor. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre." + "Verificați dacă tunerul de rețea este pornit și conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over-the-air, ar putea fi necesar să-i ajustați poziția sau direcția pentru a recepționa cât mai multe canale. Pentru rezultate optime, plasați-o sus și lângă o fereastră." "Continuați" "Nu acum" @@ -40,6 +38,7 @@ "Configurați din nou canalul?" "Astfel, vor fi eliminate canalele găsite de tunerul TV și se vor căuta din nou canale.\n\nAsigurați-vă că televizorul este conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over the air, poate fi necesar să-i ajustați amplasarea sau direcția astfel încât să capteze majoritatea canalelor. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre." "Astfel, vor fi eliminate canalele găsite de pe tunerul USB și se vor căuta din nou canale.\n\nAsigurați-vă că tunerul USB este conectat la o sursă de alimentare și la o sursă de semnal TV.\n\nDacă folosiți o antenă over the air, poate fi necesar să-i ajustați amplasarea sau direcția pentru a capta majoritatea canalelor. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre." + "Astfel veți elimina canalele găsite din tunerul de rețea și veți scana iar pentru a găsi canale noi.\n\nVerificați dacă tunerul de rețea este pornit și conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over-the-air, ar putea fi necesar să-i ajustați poziția sau direcția pentru a recepționa cât mai multe canale. Pentru rezultate optime, plasați-o sus și lângă o fereastră." "Continuați" "Anulați" @@ -54,6 +53,7 @@ "Configurarea tunerului TV" "Configurarea tunerului de canale USB" + "Configurarea tunerului de canale de rețea" "Poate dura câteva minute" "Tunerul nu este disponibil temporar sau este folosit deja de înregistrare." @@ -79,12 +79,15 @@ "Nu s-au găsit canale" "Scanarea nu a găsit niciun canal. Asigurați-vă că televizorul este conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over the air, ajustați-i amplasarea sau direcția. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre, apoi repetați scanarea." "Nu s-a găsit niciun canal la scanare. Asigurați-vă că tunerul USB este conectat la o sursă de alimentare și la o sursă de semnal TV. \n\nDacă folosiți o antenă over the air, ajustați-i amplasarea sau direcția. Pentru cele mai bune rezultate, amplasați-o la înălțime și în apropierea unei ferestre, apoi repetați scanarea." + "Scanarea nu a găsit niciun canal. Verificați dacă tunerul de rețea este pornit și conectat la o sursă de semnal TV.\n\nDacă folosiți o antenă over-the-air, ajustați poziția sau direcția. Pentru rezultate optime, plasați-o sus și lângă o fereastră și scanați din nou." "Scanați din nou" "Terminat" - "Căutați canale TV" - "Configurarea tunerului TV" - "Configurarea tunerului TV USB" - "Tunerul TV USB a fost deconectat." + "Căutați canale TV" + "Configurarea tunerului TV" + "Configurarea tunerului TV USB" + "Configurarea tunerului de rețea TV" + "Tunerul TV USB este deconectat." + "Tunerul de rețea este deconectat." diff --git a/usbtuner-res/values-ru/strings.xml b/usbtuner-res/values-ru/strings.xml index 7509ff19..33f97db0 100644 --- a/usbtuner-res/values-ru/strings.xml +++ b/usbtuner-res/values-ru/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ТВ-тюнер" "USB-тюнер" - "Вкл." - "Выкл." + "Сетевой ТВ-тюнер (бета)" "Дождитесь окончания обработки" - "Выберите источник каналов" - "Нет сигнала" - "Не удалось настроиться на канал \"%s\"" - "Не удалось настроиться на канал" "Программное обеспечение тюнера было обновлено. Повторите сканирование." "Включите объемный звук в системных настройках" + "Не удается воспроизвести аудио. Выберите другой канал." "Настройка тюнера" "Настройка ТВ-тюнера" "Настройка USB-тюнера" + "Настройка сетевого тюнера" "Убедитесь, что телевизор подключен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." "Убедитесь, что USB-тюнер включен в сеть и подсоединен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." + "Убедитесь, что сетевой тюнер включен в сеть и подсоединен к источнику ТВ-сигнала.\n\nЕсли вы используете телеантенну, поместите и направьте ее так, чтобы она принимала большинство каналов. Рекомендуем расположить антенну высоко и рядом с окном." "Продолжить" "Не сейчас" @@ -40,6 +38,7 @@ "Настроить каналы заново?" "Все каналы, найденные с помощью ТВ-тюнера, будут удалены, и поиск начнется заново.\n\nУбедитесь, что телевизор подключен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." "Все каналы, найденные с помощью USB-тюнера, будут удалены, и сканирование начнется заново.\n\nУбедитесь, что USB-тюнер включен в сеть и подсоединен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." + "Все каналы, найденные с помощью сетевого тюнера, будут удалены, и сканирование начнется заново.\n\nУбедитесь, что сетевой тюнер включен в сеть и подсоединен к источнику ТВ-сигнала.\n\nЕсли вы используете телеантенну, поместите и направьте ее так, чтобы она принимала большинство каналов. Рекомендуем расположить антенну высоко и рядом с окном." "Продолжить" "Отмена" @@ -54,6 +53,7 @@ "Настройка ТВ-тюнера" "Настройка USB-тюнера" + "Настройка каналов сетевого тюнера" "Это может занять несколько минут" "Тюнер временно недоступен или уже используется для записи." @@ -78,12 +78,15 @@ "Каналы не найдены" "Каналы не найдены. Убедитесь, что телевизор подключен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." "Каналы не найдены. Убедитесь, что USB-тюнер включен в сеть и подсоединен к источнику сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее. Рекомендуем расположить ее высоко и рядом с окном." + "Каналы не найдены. Убедитесь, что сетевой тюнер включен в сеть и подсоединен к источнику ТВ-сигнала.\n\nЕсли вы используете телеантенну, переместите или перенаправьте ее (рекомендуем расположить антенну высоко и рядом с окном). Затем повторите сканирование." "Искать повторно" "Готово" - "Сканирование телеканалов" - "Настройка ТВ-тюнера" - "Настройка USB-тюнера" - "USB-тюнер отключен." + "Сканируйте телеканалы" + "Настройка ТВ-тюнера" + "Настройка USB-тюнера" + "Настройка сетевого ТВ-тюнера" + "USB-тюнер отключен." + "Сетевой тюнер отключен." diff --git a/usbtuner-res/values-si-rLK/strings.xml b/usbtuner-res/values-si-rLK/strings.xml new file mode 100644 index 00000000..577015b6 --- /dev/null +++ b/usbtuner-res/values-si-rLK/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV සුසරකය" + "USB TV සුසරකය" + "Network TV Tuner (BETA)" + "සැකසීම අවසන් කිරීමට කරුණාකර රැඳී සිටින්න" + "සුසරක මෘදුකාංගය පසුගියදා යාවත්කාලීන කර ඇත. කරුණාකර නාලිකා නැවත ස්කෑන් කරන්න." + "ශ්‍රව්‍ය සබල කිරීමට හඬ සැකසීම් තුළ අවට හඬ සබල කරන්න" + "ශ්‍රව්‍යය ධාවනය කළ නොහැකිය. කරුණාකර වෙනත් TV එකක් උත්සාහ කරන්න" + "නාලිකා සුසරක පිහිටුවීම" + "TV සුසරක පිහිටුවීම" + "USB නාලිකා සුසරක පිහිටුවීම" + "ජාල සුසරකය පිහිටුවීම" + "ඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + "සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + "ජාල සුසරකය බලයට සම්බන්ධ කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + + "දිගටම කර ගෙන යන්න" + "දැන් නොවේ" + + "නාලිකා පිහිටුවීම නැවත ධාවනය කරන්නද?" + "මෙය TV සුසරකය වෙතින් සොයා ගත් නාලිකා ඉවත් කරනු ඇති අතර නව නාලිකා සඳහා නැවත ස්කෑන් කරනු ඇත.\n\nඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + "මෙය USB සුසරකය වෙතින් සොයා ගත් නාලිකා ඉවත් කරනු ඇති අතර නව නාලිකා සඳහා නැවත ස්කෑන් කරනු ඇත.\n\nUSB සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + "මෙය ජාල සුසරකය වෙතින් සොයා ගත් නාලිකා ඉවත් කරනු ඇති අතර නව නාලිකා සඳහා නැවත ස්කෑන් කරනු ඇත.\n\nජාල සුසරකය බලයට සම්බන්ධ කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." + + "දිගටම කර ගෙන යන්න" + "අවලංගු කරන්න" + + "සබැඳුම් ආකාරය තෝරන්න" + "සුසරකයට බාහිර අැන්ටනාවක් සම්බන්ධ කර ඇත්නම්, ඇන්ටනාව තෝරන්න. ඔබේ නාලිකා කේබල් සැපයුම්කරුවෙකු වෙතින් පැමිණෙන්නේ නම් කේබලය තෝරන්න. ඔබට ස්ථිර නැත්නම්, ආකාර දෙකම ස්කෑන් කරනු ඇත, නමුත් මෙයට වැඩි කාලයක් ගත විය හැකිය." + + "ඇන්ටනාව" + "කේබලය" + "ස්ථිර නැත" + "සංවර්ධනයට පමණි" + + "TV සුසරක පිහිටුවීම" + "USB නාලිකා සුසරක පිහිටුවීම" + "ජාල නාලිකා සුසරකය පිහිටුවීම" + "මෙය මිනිත්තු කිහිපයක් ගත හැකිය" + "සුසරකය තාවකාලිකව ලබා ගත නොහැකිය නැතහොත් දැනටමත් පටිගත කිරීම මගින් භාවිත කරනු ලැබේ." + + නාලිකා %1$dක් සොයා ගන්නා ලදී + නාලිකා %1$dක් සොයා ගන්නා ලදී + + "නාලිකා ස්කෑන් කිරීම නවත්වන්න" + + නාලිකා %1$dක් සොයා ගන්නා ලදී + නාලිකා %1$dක් සොයා ගන්නා ලදී + + + කදිමයි! නාලිකා ස්කෑන් කිරීම අතරතුර නාලිකා %1$dක් සොයා ගන්නා ලදී. මෙය නිවැරදි බව නොපෙනේ නම්, ඇන්ටනාවේ පිහිටීම සීරුමාරු කිරීම උත්සාහ කර නැවත ස්කෑන් කරන්න. + කදිමයි! නාලිකා ස්කෑන් කිරීම අතරතුර නාලිකා %1$dක් සොයා ගන්නා ලදී. මෙය නිවැරදි බව නොපෙනේ නම්, ඇන්ටනාවේ පිහිටීම සීරුමාරු කිරීම උත්සාහ කර නැවත ස්කෑන් කරන්න. + + + "නිමයි" + "නැවත ස්කෑන් කරන්න" + + "නාලිකා හමු නොවීය" + "ස්කෑන් කිරීමට නාලිකා කිසිවක් හමු නොවීය. ඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, එහි පිහිටීම හෝ දිශාව සීරුමාරු කරන්න. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබා නැවත ස්කෑන් කරන්න." + "ස්කෑන් කිරීමට නාලිකා කිසිවක් හමු නොවීය. USB සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, පිහිටීම හෝ දිශාව සීරුමාරු කරන්න. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබා නැවත ස්කෑන් කරන්න." + "ස්කෑන් කිරීමට නාලිකා කිසිවක් හමු නොවීය. ජාල සුසරකය බලයට සම්බන්ධ කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, පිහිටීම හෝ දිශාව සීරුමාරු කරන්න. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබා නැවත ස්කෑන් කරන්න." + + "නැවත ස්කෑන් කරන්න" + "නිමයි" + + "TV නාලිකා සඳහා ස්කෑන් කරන්න" + "TV සුසරක පිහිටුවීම" + "USB TV සුසරක පිහිටුවීම" + "ජාල TV සුසරක පිහිටුවීම" + "USB TV සුසරකය විසන්ධි වුණි." + "ජාල සුසරකය විසන්ධි වුණි." + diff --git a/usbtuner-res/values-si/strings.xml b/usbtuner-res/values-si/strings.xml deleted file mode 100644 index a792b2c0..00000000 --- a/usbtuner-res/values-si/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV සුසරකය" - "USB TV සුසරකය" - "ක්‍රියාත්මක කරන්න" - "ක්‍රියාවිරහිත කරන්න" - "සැකසීම අවසන් කිරීමට කරුණාකර රැඳී සිටින්න" - "ඔබගේ නාලිකා මූලාශ්‍රය තෝරන්න" - "සංඥාවක් නැත" - "%s වෙත සුසර කිරීම අසාර්ථක විය" - "සුසර කිරීම අසාර්ථක විය" - "සුසරක මෘදුකාංගය පසුගියදා යාවත්කාලීන කර ඇත. කරුණාකර නාලිකා නැවත ස්කෑන් කරන්න." - "ශ්‍රව්‍ය සබල කිරීමට හඬ සැකසීම් තුළ අවට හඬ සබල කරන්න" - "නාලිකා සුසරක පිහිටුවීම" - "TV සුසරක පිහිටුවීම" - "USB නාලිකා සුසරක පිහිටුවීම" - "ඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." - "සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." - - "දිගටම කර ගෙන යන්න" - "දැන් නොවේ" - - "නාලිකා පිහිටුවීම නැවත ධාවනය කරන්නද?" - "මෙය TV සුසරකය වෙතින් සොයා ගත් නාලිකා ඉවත් කරනු ඇති අතර නව නාලිකා සඳහා නැවත ස්කෑන් කරනු ඇත.\n\nඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." - "මෙය USB සුසරකය වෙතින් සොයා ගත් නාලිකා ඉවත් කරනු ඇති අතර නව නාලිකා සඳහා නැවත ස්කෑන් කරනු ඇත.\n\nUSB සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, ඔබට බොහොමයක් නාලිකා ලබා ගැනීමට පිහිටීම හෝ දිශාව සීරුමාරු කිරීමට අවශ්‍ය විය හැකිය. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබන්න." - - "දිගටම කර ගෙන යන්න" - "අවලංගු කරන්න" - - "සබැඳුම් ආකාරය තෝරන්න" - "සුසරකයට බාහිර අැන්ටනාවක් සම්බන්ධ කර ඇත්නම්, ඇන්ටනාව තෝරන්න. ඔබේ නාලිකා කේබල් සැපයුම්කරුවෙකු වෙතින් පැමිණෙන්නේ නම් කේබලය තෝරන්න. ඔබට ස්ථිර නැත්නම්, ආකාර දෙකම ස්කෑන් කරනු ඇත, නමුත් මෙයට වැඩි කාලයක් ගත විය හැකිය." - - "ඇන්ටනාව" - "කේබලය" - "ස්ථිර නැත" - "සංවර්ධනයට පමණි" - - "TV සුසරක පිහිටුවීම" - "USB නාලිකා සුසරක පිහිටුවීම" - "මෙය මිනිත්තු කිහිපයක් ගත හැකිය" - "සුසරකය තාවකාලිකව ලබා ගත නොහැකිය නැතහොත් දැනටමත් පටිගත කිරීම මගින් භාවිත කරනු ලැබේ." - - නාලිකා %1$dක් සොයා ගන්නා ලදී - නාලිකා %1$dක් සොයා ගන්නා ලදී - - "නාලිකා ස්කෑන් කිරීම නවත්වන්න" - - නාලිකා %1$dක් සොයා ගන්නා ලදී - නාලිකා %1$dක් සොයා ගන්නා ලදී - - - කදිමයි! නාලිකා ස්කෑන් කිරීම අතරතුර නාලිකා %1$dක් සොයා ගන්නා ලදී. මෙය නිවැරදි බව නොපෙනේ නම්, ඇන්ටනාවේ පිහිටීම සීරුමාරු කිරීම උත්සාහ කර නැවත ස්කෑන් කරන්න. - කදිමයි! නාලිකා ස්කෑන් කිරීම අතරතුර නාලිකා %1$dක් සොයා ගන්නා ලදී. මෙය නිවැරදි බව නොපෙනේ නම්, ඇන්ටනාවේ පිහිටීම සීරුමාරු කිරීම උත්සාහ කර නැවත ස්කෑන් කරන්න. - - - "නිමයි" - "නැවත ස්කෑන් කරන්න" - - "නාලිකා හමු නොවීය" - "ස්කෑන් කිරීමට නාලිකා කිසිවක් හමු නොවීය. ඔබේ TV, TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, එහි පිහිටීම හෝ දිශාව සීරුමාරු කරන්න. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබා නැවත ස්කෑන් කරන්න." - "ස්කෑන් කිරීමට නාලිකා කිසිවක් හමු නොවීය. USB සුසරකය පේනුගත කර ඇති බව සහ TV සංඥා මූලාශ්‍රයකට සම්බන්ධ කර ඇති බව තහවුරු කර ගන්න.\n\nගුවන-ඔස්සේ ඇන්ටනාවක් භාවිත කරන්නේ නම්, පිහිටීම හෝ දිශාව සීරුමාරු කරන්න. හොඳම ප්‍රතිඵල සඳහා, එය ඉහළින් සහ කවුළුවක් ආසන්නයේ තබා නැවත ස්කෑන් කරන්න." - - "නැවත ස්කෑන් කරන්න" - "නිමයි" - - "TV නාලිකා සඳහා ස්කෑන් කරන්න" - "TV සුසරක පිහිටුවීම" - "USB TV සුසරක පිහිටුවීම" - "USB TV සුසරකය විසන්ධි කරන ලදී." - diff --git a/usbtuner-res/values-sk/strings.xml b/usbtuner-res/values-sk/strings.xml index 67f9829e..60448b2b 100644 --- a/usbtuner-res/values-sk/strings.xml +++ b/usbtuner-res/values-sk/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Televízny tuner" "Televízny tuner s rozhraním USB" - "Zapnúť" - "Vypnúť" + "Televízny sieťový tuner (BETA)" "Počkajte, kým bude spracovanie dokončené" - "Vyberte zdroj kanála" - "Žiadny signál" - "Nepodarilo sa naladiť kanál %s" - "Ladenie zlyhalo" "Softvér tunera bol nedávno aktualizovaný. Znova vyhľadajte kanály." "Ak chcete zapnúť zvuk, v nastaveniach systémového zvuku povoľte priestorový zvuk" + "Zvuk nie je možné prehrať. Skúste použiť iný televízor." "Nastavenie tunera kanálov" "Nastavenie televízneho tunera" "Nastavenie kanálov tunera USB" + "Nastavenie sieťového tunera" "Skontrolujte, či je televízor pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." "Skontrolujte, či je tuner USB zapojený a pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." + "Skontrolujte, či je sieťový tuner zapnutý a pripojený k zdroju televízneho signálu.\n\nAk používate bezdrôtovú anténu, upravte jej umiestnenie alebo orientáciu tak, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." "Pokračovať" "Teraz nie" @@ -40,6 +38,7 @@ "Znova spustiť nastavenie kanálov?" "Táto akcia odstráni nájdené kanály z televízneho tunera a opätovne spustí vyhľadávanie nových kanálov.\n\nSkontrolujte, či je televízor pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." "Táto akcia odstráni nájdené kanály z tunera USB a opätovne spustí vyhľadávanie nových kanálov.\n\nSkontrolujte, či je tuner USB zapojený a pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." + "Táto akcia odstráni nájdené kanály zo sieťového tunera a opätovne spustí vyhľadávanie nových kanálov.\n\nSkontrolujte, či je sieťový tuner zapnutý a pripojený k zdroju televízneho signálu.\n\nAk používate bezdrôtovú anténu, upravte jej umiestnenie alebo orientáciu tak, aby ste získali čo najviac kanálov. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna." "Pokračovať" "Zrušiť" @@ -54,6 +53,7 @@ "Nastavenie televízneho tunera" "Nastavenie kanálov tunera USB" + "Nastavenie kanálov sieťového tunera" "Môže to trvať niekoľko minút" "Tuner nie je dočasne kˆ dispozícii alebo sa práve používa na nahrávanie." @@ -82,12 +82,15 @@ "Nenašli sa žiadne kanály" "Nenašli sa žiadne kanály. Skontrolujte, či je televízor pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna. Potom znova spustite hľadanie." "Nenašli sa žiadne kanály. Skontrolujte, či je tuner USB zapojený a pripojený k zdroju televízneho signálu.\n\nAk používate vzdušnú anténu, upravte jej umiestnenie alebo orientáciu. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna. Potom znova spustite hľadanie." + "Nenašli sa žiadne kanály. Skontrolujte, či je sieťový tuner zapnutý a pripojený k zdroju televízneho signálu.\n\nAk používate bezdrôtovú anténu, upravte jej umiestnenie alebo orientáciu. Najlepšie výsledky dosiahnete umiestnením antény dostatočne vysoko a do blízkosti okna. Potom znova spustite hľadanie." "Hľadať znova" "Hotovo" - "Vyhľadajte televízne kanály" - "Nastavenie televízneho tunera" - "Nastavenie televízneho tunera s rozhraním USB" - "TV tuner s rozhraním USB je odpojený." + "Vyhľadajte televízne kanály" + "Nastavenie televízneho tunera" + "Nastavenie televízneho tunera s rozhraním USB" + "Nastavenie televízneho sieťového tunera" + "TV tuner s rozhraním USB bol odpojený." + "Sieťový tuner bol odpojený." diff --git a/usbtuner-res/values-sl/strings.xml b/usbtuner-res/values-sl/strings.xml index e454e971..2f0812bb 100644 --- a/usbtuner-res/values-sl/strings.xml +++ b/usbtuner-res/values-sl/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Sprejemnik TV-kanalov" "Sprejemnik TV-kanalov USB" - "Vklop" - "Izklop" + "Omrežni sprejemnik TV-kanalov (BETA)" "Počakajte, da se dokonča obdelava" - "Izberite vir kanalov" - "Ni signala" - "Iskanje kanala %s ni uspelo" - "Iskanje kanala ni uspelo" "Programska oprema sprejemnika je bila nedavno posodobljena. Znova poiščite kanale." "V sistemskih nastavitvah zvoka omogočite prostorski zvok, če želite omogočiti zvok" + "Zvoka ni mogoče predvajati. Poskusite na drugem televizorju." "Nastavitev sprejemnika kanalov" "Nastavitev sprejemnika TV-kanalov" "Nastavitev sprejemnika kanalov USB" + "Nastavitev omrežnega sprejemnika" "Preverite, ali je televizor povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna ter iščite kanale znova." "Preverite, ali je sprejemnik USB priključen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, morate morda prilagoditi njen položaj oziroma njeno usmerjenost, če želite prejemati največ kanalov. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna." + "Preverite, ali je omrežni sprejemnik vklopljen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna." "Naprej" "Ne zdaj" @@ -40,6 +38,7 @@ "Ali želite znova zagnati namestitev kanalov?" "S tem bodo odstranjeni kanali, najdeni prek sprejemnika TV-kanalov, in iskanje kanalov se bo začelo znova.\n\nPreverite, ali je televizor povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna ter iščite kanale znova." "S tem bodo odstranjeni kanali, najdeni prek sprejemnika USB, in iskanje kanalov se bo začelo znova.\n\nPreverite, ali je sprejemnik USB priključen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, morate morda prilagoditi njen položaj oziroma njeno usmerjenost, če želite prejemati največ kanalov. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna." + "S tem bodo odstranjeni kanali, najdeni z omrežnim sprejemnikom kanalov, in vnovič se bo začelo iskanje novih kanalov.\n\nPreverite, ali je omrežni sprejemnik vklopljen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna." "Naprej" "Prekliči" @@ -54,6 +53,7 @@ "Nastavitev sprejemnika TV-kanalov" "Nastavitev sprejemnika kanalov USB" + "Nastavitev omrežnega sprejemnika kanalov" "To lahko traja nekaj minut" "Sprejemnik začasno ni na voljo ali se že uporablja za snemanje." @@ -82,12 +82,15 @@ "Ni najdenih kanalov" "Pri iskanju kanali niso bili najdeni. Preverite, ali je televizor povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna ter iščite kanale znova." "Pri iskanju kanali niso bili najdeni. Preverite, ali je sprejemnik USB priključen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna ter iščite kanale znova." + "Pri iskanju ni bil najden noben kanal. Preverite, ali je omrežni sprejemnik vklopljen in povezan z virom TV-signala.\n\nČe uporabljate anteno za prizemno televizijo, prilagodite njen položaj oziroma njeno usmerjenost. Če želite najboljši rezultat, jo postavite na visok položaj in blizu okna ter iščite kanale znova." "Znova išči" "Končano" - "Iskanje TV-kanalov" - "Nastavitev sprejemnika TV-kanalov" - "Nastavitev sprejemnika TV-kanalov USB" - "Povezava s sprejemnikom za TV-kanale USB je prekinjena." + "Iskanje TV-kanalov" + "Nastavitev sprejemnika TV-kanalov" + "Nastavitev sprejemnika TV-kanalov USB" + "Nastavitev omrežnega sprejemnika TV-kanalov" + "Povezava s sprejemnikom za TV-kanale USB je prekinjena." + "Povezava z omrežnim sprejemnikom je prekinjena." diff --git a/usbtuner-res/values-sr/strings.xml b/usbtuner-res/values-sr/strings.xml index 6c1fa1bf..ce7c4d53 100644 --- a/usbtuner-res/values-sr/strings.xml +++ b/usbtuner-res/values-sr/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Тјунер за ТВ" "USB тјунер за ТВ" - "Укључи" - "Искључи" + "Мрежни тјунер за ТВ (BETA)" "Сачекајте да се заврши обрада" - "Изаберите извор канала" - "Нема сигнала" - "Укључивање канала %s није успело" - "Укључивање канала није успело" "Софтвер тјунера је недавно ажуриран. Претражите канале поново." "Омогући звучни систем у подешавањима звука да бисте омогућили аудио" + "Звук не може да се пусти. Испробајте други ТВ уређај" "Подешавање тјунера за канале" "Подешавање тјунера за ТВ" "Подешавање USB тјунера за канале" + "Подешавање мрежног тјунера" "Проверите да ли је ТВ повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." "Проверите да ли је USB тјунер прикључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." + "Проверите да ли је мрежни тјунер укључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." "Настави" "Не сада" @@ -40,6 +38,7 @@ "Желите ли да поново покренете подешавање канала?" "На овај начин уклањате канале пронађене помоћу тјунера за ТВ и поново тражите нове канале.\n\nПроверите да ли је ТВ повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." "На овај начин уклањате канале пронађене помоћу USB тјунера и поново тражите нове канале.\n\nПроверите да ли је USB тјунер прикључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." + "На овај начин уклањате канале пронађене помоћу мрежног тјунера и поново скенирате нове канале.\n\nПроверите да ли је мрежни тјунер укључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, можда треба да прилагодите њен положај или смер да бисте имали највише канала. Ако желите најбоље резултате, поставите је високо и близу прозора." "Настави" "Откажи" @@ -54,6 +53,7 @@ "Подешавање тјунера за ТВ" "Подешавање USB тјунера за канале" + "Подешавање мрежног тјунера за канале" "Ово може да потраје неколико минута" "Тјунер привремено није доступан или се већ користи за снимање." @@ -79,12 +79,15 @@ "Није пронађен ниједан канал" "Претрагом није пронађен ниједан канал. Проверите да ли је ТВ повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, прилагодите њен положај или смер. Ако желите најбоље резултате, поставите је високо и близу прозора, па поново обавите претрагу." "Претрагом није пронађен ниједан канал. Проверите да ли је USB тјунер прикључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, прилагодите њен положај или смер. Ако желите најбоље резултате, поставите је високо и близу прозора, па поново обавите претрагу." + "Скенирањем није пронађен ниједан канал. Проверите да ли је мрежни тјунер укључен и повезан са извором ТВ сигнала.\n\nАко користите антену за сигнал преко мреже, прилагодите њен положај или смер. Ако желите најбоље резултате, поставите је високо и близу прозора, па поново обавите скенирање." "Претражи поново" "Готово" - "Потражите ТВ канале" - "Подешавање тјунера за ТВ" - "Подешавање USB тјунера за ТВ" - "USB тјунер за ТВ није прикључен." + "Потражите ТВ канале" + "Подешавање тјунера за ТВ" + "Подешавање USB тјунера за ТВ" + "Подешавање мрежног тјунера за ТВ" + "USB тјунер за ТВ није прикључен." + "Мрежни тјунер није прикључен." diff --git a/usbtuner-res/values-sv/strings.xml b/usbtuner-res/values-sv/strings.xml index dea421bb..81c37921 100644 --- a/usbtuner-res/values-sv/strings.xml +++ b/usbtuner-res/values-sv/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV-mottagare" "USB-TV-mottagare" - "På" - "Av" + "Mottagare för TV över nätverket (BETA)" "Vänta tills sökningen är klar" - "Välj kanalkälla" - "Ingen signal" - "Det gick inte att ställa in %s" - "Det gick inte att ställa in kanalen" "Programvaran för mottagaren har nyligen uppdaterats. Sök igenom kanalerna igen." "Aktivera surroundljud under inställningarna för systemljud om du vill aktivera ljud" + "Det går inte att spela upp ljud. Testa en annan TV" "Inställning av kanalmottagare" "Konfiguration av TV-mottagare" "Kanalinställning för USB-mottagare" + "Konfiguration av nätverksmottagare" "Verifiera att TV:n är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Verifiera att USB-mottagaren är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar kan du behöva justera dess placering eller riktning för att hitta så många kanaler som möjligt. Placera den högt upp och nära ett fönster för bästa resultat." + "Verifiera att nätverksmottagaren är på och ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Fortsätt" "Inte nu" @@ -40,6 +38,7 @@ "Vill du göra om kanalinställningen?" "Kanalerna som hittats via TV-mottagaren tas bort och en ny kanalsökning startas.\n\nVerifiera att TV:n är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Kanalerna som hittats via USB-mottagaren tas bort och en ny kanalsökning startas.\n\nVerifiera att USB-mottagaren är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar kan du behöva justera dess placering eller riktning för att hitta så många kanaler som möjligt. Placera den högt upp och nära ett fönster för bästa resultat." + "Åtgärden tar bort alla kanaler som hittades av nätverksmottagaren och söker efter nya kanaler igen.\n\nVerifiera att nätverksmottagaren är på och ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Fortsätt" "Avbryt" @@ -54,6 +53,7 @@ "Konfiguration av TV-mottagare" "Kanalinställning för USB-mottagare" + "Konfiguration av mottagare för nätverkskanaler" "Det här kan ta flera minuter" "Mottagaren är inte tillgänglig just nu eller så spelas andra program in med den." @@ -76,12 +76,15 @@ "Inga kanaler hittades" "Inga kanaler hittades vid sökningen. Verifiera att TV:n är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Inga kanaler hittades vid sökningen. Verifiera att USB-mottagaren är ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." + "Inga kanaler hittades under genomsökningen. Verifiera att nätverksmottagaren är på och ansluten till en signalkälla på TV:n.\n\nOm du använder en antenn för over the air-uppdateringar justerar du dess placering eller riktning. Placera den högt upp och nära ett fönster och sök på nytt för bästa resultat." "Sök igen" "Klar" - "Sök efter TV-kanaler" - "Konfiguration av TV-mottagare" - "Konfiguration av USB-TV-mottagare" - "USB-TV-mottagaren har kopplats från" + "Sök efter TV-kanaler" + "Konfiguration av TV-mottagare" + "Konfiguration av USB-ansluten TV-mottagare" + "Konfiguration av nätverksansluten TV-mottagare" + "USB-TV-mottagaren har kopplats från." + "Nätverksmottagaren har kopplats från." diff --git a/usbtuner-res/values-sw/strings.xml b/usbtuner-res/values-sw/strings.xml index 1ade7516..c81614c0 100644 --- a/usbtuner-res/values-sw/strings.xml +++ b/usbtuner-res/values-sw/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Kitafutaji cha vituo vya TV" "Kitafutaji cha Vituo vya TV cha USB" - "Washa" - "Zima" + "Kitafuta Vituo vya TV ya Mtandao (BETA)" "Tafadhali subiri ili shughuli imalizike" - "Chagua chanzo cha kituo" - "Hakuna Mawimbi" - "Imeshindwa kupata kituo cha %s" - "Imeshindwa kupata kituo" "Programu ya kitafutaji cha vituo cha USB ilisasishwa hivi majuzi. Tafadhali tafuta vituo tena." "Washa sauti ya mzunguko katika mipangilio ya mfumo wa sauti ili uruhusu sauti" + "Haiwezi kucheza sauti. Tafadhali jaribu TV nyingine" "Kuweka mipangilio ya kitafutaji cha vituo" "Kuweka mipangilio ya kitafutaji cha vituo vya TV" "Kuweka mipangilio ya kitafutaji cha vituo cha USB" + "Kuweka mipangilio ya kitafuta vituo vya TV" "Hakikisha TV yako imeunganishwa kwenye chanzo cha TV.\n\nIkiwa unatumia antena ya hewani, rekebisha jinsi ilivyowekwa au mwelekeo wake. Ili kupata matokeo bora zaidi, ipandishe juu na uiweke karibu na dirisha." "Thibitisha kuwa kitafutaji cha vituo cha USB kimechomekwa katika chanzo cha umeme na kuunganishwa katika chanzo cha mawimbi ya TV.\n\nKama unatumia antena ya hewani, rekebisha mkao wake au kule inakoelekea. Kwa matokeo bora zaidi, iweke juu , karibu na dirisha." + "Thibitisha kuwa kitafuta vituo kimewashwa na kuunganishwa kwenye chanzo cha mawimbi ya TV. \n\nIkiwa unatumia antena ya hewani, huenda ukahitaji kurekebisha jinsi ilivyowekwa au inakoelekea, ili upate vituo vingi. Kwa matokeo bora zaidi, ipandishe juu na iwe karibu na dirisha." "Endelea" "Si sasa" @@ -40,6 +38,7 @@ "Ungependa kuweka mipangilio ya vituo upya?" "Hatua hii itaondoa vituo vilivyopatikana kwenye kitafutaji cha vituo vya TV na kutafuta vituo vipya tena.\n\nHakikisha TV yako imeunganishwa kwenye chanzo cha mawimbi ya TV.\n\nIkiwa unatumia antena ya hewani, rekebisha jinsi ilivyowekwa au mwelekeo wake. Ili kupata matokeo bora zaidi, ipandishe juu na uiweke karibu na dirisha kisha utafute tena." "Hatua hii itaondoa vituo vilivyopatikana kwenye kitafutaji cha vituo cha USB na kutafuta vituo tena. \n\nThibitisha kuwa kitafutaji cha vituo cha USB kimechomekwa kwenye chanzo cha umeme na kuunganishwa katika chanzo cha mawimbi ya TV.\n\nKama unatumia antena ya hewani, rekebisha mkao wake au kule inakoelekea. Kwa matokeo bora zaidi, iweke juu, karibu na dirisha." + "Hatua hii itaondoa vituo ulivyopata kwenye kitafuta vituo na kutafuta vituo vipya tena.\n\nThibitisha kwamba kitafuta vituo kimewashwa na kuunganishwa kwenye chanzo cha mawimbi ya TV.\n\nIkiwa unatumia antena ya hewani, rekebisha jinsi ilivyowekwa au inakoelekea, ili upate vituo vingi. Kwa matokeo bora zaidi, ipandishe juu na iwe karibu na dirisha." "Endelea" "Ghairi" @@ -54,6 +53,7 @@ "Kuweka mipangilio ya kitafutaji cha vituo vya TV" "Kuweka mipangilio ya kitafutaji cha vituo cha USB" + "Kuweka mipangilio ya kitafuta vituo" "Shughuli hii inaweza kuchukua dakika kadhaa" "Kitafuta vituo hakipatikani kwa sasa au tayari kinatumiwa kurekodi." @@ -76,12 +76,15 @@ "Hakuna Vituo vilivyopatikana" "Hakuna vituo vilivyopatikana baada ya kutafuta. Hakikisha TV yako imeunganishwa kwenye chanzo cha mawimbi ya TV.\n\nIkiwa unatumia antena ya hewani, rekebisha jinsi ilivyowekwa au mwelekeo wake. Ili kupata matokeo bora zaidi, ipandishe juu na uiweke karibu na dirisha kisha utafute tena." "Utafutaji haukupata vituo vyovyote. Thibitisha kuwa kitafutaji cha vituo cha USB kimechomekwa kwenye chanzo cha umeme na kuunganishwa katika chanzo cha mawimbi ya TV.\n\nKama unatumia antena ya hewani, rekebisha mkao wake au kule inakoelekea. Kwa matokeo bora zaidi, iweke juu, karibu na dirisha na utafute tena." + "Haikupata vituo vyovyote. Thibitisha kwamba kitafuta vituo kimewashwa na kuunganishwa kwenye chanzo cha mawimbi ya TV.\n\nIkiwa unatumia antena ya hewani, rekebisha jinsi ilivyowekwa au inakoelekea. Ili upate matokeo bora zaidi, ipandishe juu na karibu na dirisha kisha utafute tena." "Tafuta tena" "Nimemaliza" - "Tafuta vituo vya TV" - "Kuweka mipangilio ya kitafutaji cha vituo vya TV" - "Kuweka mipangilio ya Kitafutaji cha Vituo cha USB" - "Umeondoa kichagua programu cha USB cha TV." + "Tafuta vituo vya TV" + "Kuweka mipangilio ya kitafutaji cha vituo vya TV" + "Kuweka mipangilio ya Kitafutaji cha Vituo vya TV kupitia USB" + "Kuweka mipangilio ya Kitafutaji cha Vituo vya TV kupitia Mtandao" + "Umeondoa kichagua programu cha USB cha TV." + "Umeondoa kitafuta mitandao." diff --git a/usbtuner-res/values-ta-rIN/strings.xml b/usbtuner-res/values-ta-rIN/strings.xml new file mode 100644 index 00000000..1c71722e --- /dev/null +++ b/usbtuner-res/values-ta-rIN/strings.xml @@ -0,0 +1,90 @@ + + + + + "டிவி ட்யூனர்" + "USB டிவி ட்யூனர்" + "நெட்வொர்க் டிவி டியூனர் (பீட்டா)" + "செயலாக்கம் முடியும் வரை காத்திருக்கவும்" + "ட்யூனர் மென்பொருள் சமீபத்தில் புதுப்பிக்கப்பட்டது. சேனல்களை மீண்டும் ஸ்கேன் செய்யவும்." + "ஆடியோவை இயக்க, சாதன ஒலி அமைப்புகளில் \"சரவுண்ட் சவுண்ட்\" என்பதை இயக்கவும்" + "ஆடியோவை இயக்க முடியவில்லை. வேறு டிவியைப் பயன்படுத்தவும்" + "சேனல் ட்யூனர் அமைவு" + "டிவி ட்யூனர் அமைவு" + "USB சேனல் ட்யூனர் அமைவு" + "நெட்வொர்க் டியூனர் அமைவு" + "டிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." + "USB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." + "நெட்வொர்க் டியூனர் ஆன் செய்யப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." + + "தொடர்க" + "இப்போது வேண்டாம்" + + "சேனல் அமைவை மீண்டும் இயக்கவா?" + "இவ்வாறு செய்வதால் டிவி ட்யூனர் மூலம் கண்டறிந்த சேனல்கள் அகற்றப்படுவதுடன், புதிய சேனல்களுக்காக மீண்டும் ஸ்கேன் செய்யும்.\n\nடிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." + "இவ்வாறு செய்வதால் USB ட்யூனர் மூலம் கண்டறிந்த சேனல்கள் அகற்றப்படுவதுடன், புதிய சேனல்களுக்காக மீண்டும் ஸ்கேன் செய்யும்.\n\nUSB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." + "இது நெட்வொர்க் டியூனரிலிருந்து கண்டறிந்த சேனல்களை அகற்றும் மற்றும் புதிய சேனல்களுக்காக மீண்டும் ஸ்கேன் செய்யும்.\n\nநெட்வொர்க் டியூனர் ஆன் செய்யப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக, அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, உயரமான இடத்தில், ஜன்னலுக்கு அருகில் வைக்கவும்." + + "தொடர்க" + "ரத்துசெய்" + + "இணைப்பு வகையைத் தேர்ந்தெடுக்கவும்" + "ட்யூனருடன் வெளிப்புற ஆன்டெனா இணைக்கப்பட்டிருந்தால், ஆன்டெனாவைத் தேர்வுசெய்யவும். கேபிள் சேவை வழங்குநரிடமிருந்து சேனல்கள் கிடைக்கின்றன என்றால், கேபிளைத் தேர்வுசெய்யவும். எதையும் தேர்வுசெய்யவில்லை எனில், இரு வகைகளிலும் ஸ்கேன் செய்யப்படும். ஆனால் இதற்கு அதிக நேரம் ஆகலாம்." + + "ஆன்டெனா" + "கேபிள்" + "நிச்சயமாகத் தெரியவில்லை" + "டெவெலப்மென்ட் மட்டும்" + + "டிவி ட்யூனர் அமைவு" + "USB சேனல் ட்யூனர் அமைவு" + "நெட்வொர்க் சேனல் டியூனர் அமைவு" + "இதற்குச் சில நிமிடங்கள் ஆகலாம்" + "ட்யூனர் தற்காலிகமாகக் கிடைக்கவில்லை அல்லது ரெக்கார்டு செய்வதற்காக ஏற்கனவே பயன்படுத்தப்படுகிறது." + + %1$d சேனல்கள் கண்டறியப்பட்டன + %1$d சேனல் கண்டறியப்பட்டது + + "சேனலை ஸ்கேன் செய்வதை நிறுத்து" + + %1$d சேனல்கள் கண்டறியப்பட்டன + %1$d சேனல் கண்டறியப்பட்டது + + + அருமை! சேனலை ஸ்கேன் செய்யும் போது, %1$d சேனல்கள் கண்டறியப்பட்டன. இவை சரியாகத் தெரியவில்லை என்றால், ஆன்டெனாவின் நிலையை மாற்றி, மீண்டும் ஸ்கேன் செய்யவும். + அருமை! சேனலை ஸ்கேன் செய்யும் போது, %1$d சேனல் கண்டறியப்பட்டது. இது சரியாகத் தெரியவில்லை என்றால், ஆன்டெனாவின் நிலையை மாற்றி, மீண்டும் ஸ்கேன் செய்யவும். + + + "முடிந்தது" + "மீண்டும் ஸ்கேன் செய்" + + "சேனல்கள் இல்லை" + "ஸ்கேன் செய்ததில் சேனல்கள் எவையும் கண்டறியப்படவில்லை. டிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதன் இடம் அல்லது திசையைச் சரிசெய்யவும். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைத்து, மீண்டும் ஸ்கேன் செய்யவும்." + "ஸ்கேன் செய்ததில் சேனல்கள் எவையும் கண்டறியப்படவில்லை. USB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதன் இடம் அல்லது திசையைச் சரிசெய்யவும். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைத்து, மீண்டும் ஸ்கேன் செய்யவும்." + "ஸ்கேன் செய்ததில் சேனல்கள் எதுவும் கண்டறியப்படவில்லை. நெட்வொர்க் டியூனர் ஆன் செய்யப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதன் இடம் அல்லது திசையைச் சரிசெய்யவும். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைத்து, மீண்டும் ஸ்கேன் செய்யவும்." + + "மீண்டும் ஸ்கேன் செய்" + "முடிந்தது" + + "டிவி சேனல்களைத் தேடுக" + "டிவி டியூனர் அமைவு" + "USB டிவி டியூனர் அமைவு" + "நெட்வொர்க் டிவி டியூனர் அமைவு" + "USB டிவி ட்யூனர் துண்டிக்கப்பட்டது." + "நெட்வொர்க் ட்யூனர் துண்டிக்கப்பட்டது." + diff --git a/usbtuner-res/values-ta/strings.xml b/usbtuner-res/values-ta/strings.xml deleted file mode 100644 index dd09420d..00000000 --- a/usbtuner-res/values-ta/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "டிவி ட்யூனர்" - "USB டிவி ட்யூனர்" - "இயக்கு" - "முடக்கு" - "செயலாக்கம் முடியும் வரை காத்திருக்கவும்" - "சேனல் மூலத்தைத் தேர்ந்தெடுக்கவும்" - "சிக்னல் இல்லை" - "%sக்கு ட்யூன் செய்ய முடியவில்லை" - "ட்யூன் செய்ய முடியவில்லை" - "ட்யூனர் மென்பொருள் சமீபத்தில் புதுப்பிக்கப்பட்டது. சேனல்களை மீண்டும் ஸ்கேன் செய்யவும்." - "ஆடியோவை இயக்க, சாதன ஒலி அமைப்புகளில் \"சரவுண்ட் சவுண்ட்\" என்பதை இயக்கவும்" - "சேனல் ட்யூனர் அமைவு" - "டிவி ட்யூனர் அமைவு" - "USB சேனல் ட்யூனர் அமைவு" - "டிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." - "USB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." - - "தொடர்க" - "இப்போது வேண்டாம்" - - "சேனல் அமைவை மீண்டும் இயக்கவா?" - "இவ்வாறு செய்வதால் டிவி ட்யூனர் மூலம் கண்டறிந்த சேனல்கள் அகற்றப்படுவதுடன், புதிய சேனல்களுக்காக மீண்டும் ஸ்கேன் செய்யும்.\n\nடிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." - "இவ்வாறு செய்வதால் USB ட்யூனர் மூலம் கண்டறிந்த சேனல்கள் அகற்றப்படுவதுடன், புதிய சேனல்களுக்காக மீண்டும் ஸ்கேன் செய்யும்.\n\nUSB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதிகமான சேனல்களைப் பெறுவதற்காக அதன் இடம் அல்லது திசையைச் சரிசெய்ய வேண்டியிருக்கலாம். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைக்கவும்." - - "தொடர்க" - "ரத்துசெய்" - - "இணைப்பு வகையைத் தேர்ந்தெடுக்கவும்" - "ட்யூனருடன் வெளிப்புற ஆன்டெனா இணைக்கப்பட்டிருந்தால், ஆன்டெனாவைத் தேர்வுசெய்யவும். கேபிள் சேவை வழங்குநரிடமிருந்து சேனல்கள் கிடைக்கின்றன என்றால், கேபிளைத் தேர்வுசெய்யவும். எதையும் தேர்வுசெய்யவில்லை எனில், இரு வகைகளிலும் ஸ்கேன் செய்யப்படும். ஆனால் இதற்கு அதிக நேரம் ஆகலாம்." - - "ஆன்டெனா" - "கேபிள்" - "நிச்சயமாகத் தெரியவில்லை" - "டெவெலப்மென்ட் மட்டும்" - - "டிவி ட்யூனர் அமைவு" - "USB சேனல் ட்யூனர் அமைவு" - "இதற்குச் சில நிமிடங்கள் ஆகலாம்" - "ட்யூனர் தற்காலிகமாகக் கிடைக்கவில்லை அல்லது ரெக்கார்டு செய்வதற்காக ஏற்கனவே பயன்படுத்தப்படுகிறது." - - %1$d சேனல்கள் கண்டறியப்பட்டன - %1$d சேனல் கண்டறியப்பட்டது - - "சேனலை ஸ்கேன் செய்வதை நிறுத்து" - - %1$d சேனல்கள் கண்டறியப்பட்டன - %1$d சேனல் கண்டறியப்பட்டது - - - அருமை! சேனலை ஸ்கேன் செய்யும் போது, %1$d சேனல்கள் கண்டறியப்பட்டன. இவை சரியாகத் தெரியவில்லை என்றால், ஆன்டெனாவின் நிலையை மாற்றி, மீண்டும் ஸ்கேன் செய்யவும். - அருமை! சேனலை ஸ்கேன் செய்யும் போது, %1$d சேனல் கண்டறியப்பட்டது. இது சரியாகத் தெரியவில்லை என்றால், ஆன்டெனாவின் நிலையை மாற்றி, மீண்டும் ஸ்கேன் செய்யவும். - - - "முடிந்தது" - "மீண்டும் ஸ்கேன் செய்" - - "சேனல்கள் இல்லை" - "ஸ்கேன் செய்ததில் சேனல்கள் எவையும் கண்டறியப்படவில்லை. டிவி சிக்னல் மூலத்துடன் உங்கள் டிவி இணைக்கப்பட்டுள்ளதைச் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதன் இடம் அல்லது திசையைச் சரிசெய்யவும். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைத்து, மீண்டும் ஸ்கேன் செய்யவும்." - "ஸ்கேன் செய்ததில் சேனல்கள் எவையும் கண்டறியப்படவில்லை. USB ட்யூனர் செருகப்பட்டுள்ளதையும் டிவி சிக்னல் மூலத்துடன் இணைக்கப்பட்டுள்ளதையும் சரிபார்க்கவும்.\n\nஆன்டெனாவைப் பயன்படுத்தினால், அதன் இடம் அல்லது திசையைச் சரிசெய்யவும். இன்னும் சிறப்பான முடிவுகளுக்கு, அதை உயரமான இடத்தில் ஜன்னலுக்கு அருகே வைத்து, மீண்டும் ஸ்கேன் செய்யவும்." - - "மீண்டும் ஸ்கேன் செய்" - "முடிந்தது" - - "டிவி சேனல்களை ஸ்கேன் செய்" - "டிவி ட்யூனர் அமைவு" - "USB டிவி ட்யூனர் அமைவு" - "USB டிவி ட்யூனர் வெளியே எடுக்கப்பட்டது." - diff --git a/usbtuner-res/values-te-rIN/strings.xml b/usbtuner-res/values-te-rIN/strings.xml new file mode 100644 index 00000000..fb6bcf37 --- /dev/null +++ b/usbtuner-res/values-te-rIN/strings.xml @@ -0,0 +1,90 @@ + + + + + "టీవీ ట్యూనర్" + "USB టీవీ ట్యూనర్" + "నెట్‌వర్క్ టీవీ ట్యూనర్ (బీటా)" + "దయచేసి ప్రాసెస్ చేయడం పూర్తయ్యే వరకు వేచి ఉండండి" + "ట్యూనర్ సాఫ్ట్‌వేర్ ఇటీవల నవీకరించబడింది. దయచేసి ఛానెల్‌లను మళ్లీ స్కాన్ చేయండి." + "ఆడియోను ప్రారంభించడానికి సిస్టమ్ శబ్ద సెట్టింగ్‌ల్లో పరిసర వ్యాప్త శబ్దాన్ని ప్రారంభించండి" + "ఆడియోను ప్లే చేయడం సాధ్యపడదు. దయచేసి మరో టీవీలో ప్రయత్నించండి" + "ఛానెల్ ట్యూనర్ సెటప్" + "టీవీ ట్యూనర్ సెటప్" + "USB ఛానెల్ ట్యూనర్ సెటప్" + "నెట్‍వర్క్ ట్యూనర్ సెటప్" + "టీవీ.సిగ్నల్ సోర్స్‌కు మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + "USB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడినట్లు ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + "నెట్‍వర్క్ ట్యూనర్ పవర్ ఆన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడినట్లు ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + + "కొనసాగించు" + "ఇప్పుడు కాదు" + + "ఛానెల్ సెటప్‌ను మళ్లీ అమలు చేయాలా?" + "ఇది టీవీ ట్యూనర్ నుండి కనుగొన్న ఛానెల్‌లను తీసివేస్తుంది మరియు మళ్లీ కొత్త ఛానెల్‌ల కోసం స్కాన్ చేస్తుంది.\n\nటీవీ సిగ్నల్ సోర్స్‌కు మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + "ఇది USB ట్యూనర్ నుండి కనుగొన్న ఛానెల్‌లను తీసివేస్తుంది మరియు మళ్లీ కొత్త ఛానెల్‌ల కోసం స్కాన్ చేస్తుంది.\n\nUSB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + "ఇది నెట్‍వర్క్ ట్యూనర్ నుండి కనుగొన్న ఛానెల్‌లను తీసివేస్తుంది మరియు మళ్లీ కొత్త ఛానెల్‌ల కోసం స్కాన్ చేస్తుంది.\n\nనెట్‍వర్క్ ట్యూనర్ పవర్ ఆన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాలి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." + + "కొనసాగించు" + "రద్దు చేయి" + + "కనెక్షన్ రకాన్ని ఎంచుకోండి" + "ట్యూనర్‌కు బాహ్య యాంటెన్నా కనెక్ట్ చేసి ఉంటే యాంటెన్నాను ఎంచుకోండి. మీ ఛానెల్‌‍‍లను కేబుల్ సేవా ప్రదాత అందిస్తుంటే, కేబుల్‌ను ఎంచుకోండి. మీకు ఏ సంగతి ఖచ్చితంగా తెలియకుంటే, రెండు రకాలు స్కాన్ చేయబడతాయి, కానీ దీనికి ఎక్కువ సమయం పట్టవచ్చు." + + "యాంటెన్నా" + "కేబుల్" + "అంత ఖచ్చితంగా తెలియదు" + "అభివృద్ధి మాత్రమే" + + "టీవీ ట్యూనర్ సెటప్" + "USB ఛానెల్ ట్యూనర్ సెటప్" + "నెట్‍వర్క్ ఛానెల్ ట్యూనర్ సెటప్" + "దీనికి కొన్ని నిమిషాలు పట్టవచ్చు" + "ట్యూనర్ తాత్కాలికంగా అందుబాటులో లేదు లేదా ఇప్పటికే రికార్డింగ్ ద్వారా ఉపయోగించబడుతోంది." + + %1$d ఛానెల్‌లు కనుగొనబడ్డాయి + %1$d ఛానెల్ కనుగొనబడింది + + "ఛానెల్ స్కాన్‌ను ఆపివేయి" + + %1$d ఛానెల్‌లు కనుగొనబడ్డాయి + %1$d ఛానెల్ కనుగొనబడింది + + + మంచిది! ఛానెల్ స్కాన్‌లో %1$d ఛానెల్‌లు కనుగొనబడ్డాయి. ఇది సరైనదిగా అనిపించకుంటే, యాంటెన్నా స్థానం సర్దుబాటు చేసి, ఆపై మళ్లీ స్కాన్ చేయడం ప్రయత్నించండి. + మంచిది! ఛానెల్ స్కాన్‌లో %1$d ఛానెల్ కనుగొనబడింది. ఇది సరైనదిగా అనిపించకుంటే, యాంటెన్నా స్థానం సర్దుబాటు చేసి, ఆపై మళ్లీ స్కాన్ చేయడం ప్రయత్నించండి. + + + "పూర్తయింది" + "మళ్లీ స్కాన్ చేయి" + + "ఛానెల్‌లు ఏవీ కనుగొనబడలేదు" + "స్కాన్‌లో ఏ ఛానెల్ కనుగొనబడలేదు. టీవీ సిగ్నల్ సోర్స్‌కి మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి. \n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, దాని స్థానాన్ని లేదా దిశను సర్దుబాటు చేయండి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచి, ఆపై మళ్లీ స్కాన్ చేయండి." + "స్కాన్‌లో ఛానెల్‌లు ఏవీ కనుగొనబడలేదు. USB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, దాని స్థానాన్ని లేదా దిశను సర్దుబాటు చేయండి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచి, ఆపై మళ్లీ స్కాన్ చేయండి." + "స్కాన్‌లో ఛానెల్‌లు ఏవీ కనుగొనబడలేదు. నెట్‍వర్క్ ట్యూనర్ పవర్ ఆన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, దాని స్థానాన్ని లేదా దిశను సర్దుబాటు చేయండి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచి, ఆపై మళ్లీ స్కాన్ చేయండి." + + "మళ్లీ స్కాన్ చేయి" + "పూర్తయింది" + + "టీవీ ఛానెల్‌ల కోసం స్కాన్ చేయండి" + "టీవీ ట్యూనర్ సెటప్" + "USB టీవీ ట్యూనర్ సెటప్" + "నెట్‍వర్క్ టీవీ ట్యూనర్ సెటప్" + "USB టీవీ ట్యూనర్ డిస్‌కనెక్ట్ చేయబడింది." + "నెట్‌వర్క్ ట్యూనర్ డిస్‌కనెక్ట్ చేయబడింది." + diff --git a/usbtuner-res/values-te/strings.xml b/usbtuner-res/values-te/strings.xml deleted file mode 100644 index a80bd1c7..00000000 --- a/usbtuner-res/values-te/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "టీవీ ట్యూనర్" - "USB టీవీ ట్యూనర్" - "ఆన్ చేయి" - "ఆఫ్ చేయి" - "దయచేసి ప్రాసెస్ చేయడం పూర్తయ్యే వరకు వేచి ఉండండి" - "మీ ఛానెల్ మూలాన్ని ఎంచుకోండి" - "సిగ్నల్ లేదు" - "%sకి ట్యూన్ చేయడంలో విఫలమైంది" - "ట్యూన్ చేయడంలో విఫలమైంది" - "ట్యూనర్ సాఫ్ట్‌వేర్ ఇటీవల నవీకరించబడింది. దయచేసి ఛానెల్‌లను మళ్లీ స్కాన్ చేయండి." - "ఆడియోను ప్రారంభించడానికి సిస్టమ్ శబ్ద సెట్టింగ్‌ల్లో పరిసర వ్యాప్త శబ్దాన్ని ప్రారంభించండి" - "ఛానెల్ ట్యూనర్ సెటప్" - "టీవీ ట్యూనర్ సెటప్" - "USB ఛానెల్ ట్యూనర్ సెటప్" - "టీవీ.సిగ్నల్ సోర్స్‌కు మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." - "USB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడినట్లు ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." - - "కొనసాగించు" - "ఇప్పుడు కాదు" - - "ఛానెల్ సెటప్‌ను మళ్లీ అమలు చేయాలా?" - "ఇది టీవీ ట్యూనర్ నుండి కనుగొన్న ఛానెల్‌లను తీసివేస్తుంది మరియు మళ్లీ కొత్త ఛానెల్‌ల కోసం స్కాన్ చేస్తుంది.\n\nటీవీ సిగ్నల్ సోర్స్‌కు మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." - "ఇది USB ట్యూనర్ నుండి కనుగొన్న ఛానెల్‌లను తీసివేస్తుంది మరియు మళ్లీ కొత్త ఛానెల్‌ల కోసం స్కాన్ చేస్తుంది.\n\nUSB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, మీరు మరిన్ని ఛానెల్‌లను స్వీకరించడానికి దాని స్థానాన్ని లేదా దిశను మార్చాల్సి రావచ్చు. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచండి." - - "కొనసాగించు" - "రద్దు చేయి" - - "కనెక్షన్ రకాన్ని ఎంచుకోండి" - "ట్యూనర్‌కు బాహ్య యాంటెన్నా కనెక్ట్ చేసి ఉంటే యాంటెన్నాను ఎంచుకోండి. మీ ఛానెల్‌‍‍లను కేబుల్ సేవా ప్రదాత అందిస్తుంటే, కేబుల్‌ను ఎంచుకోండి. మీకు ఏ సంగతి ఖచ్చితంగా తెలియకుంటే, రెండు రకాలు స్కాన్ చేయబడతాయి, కానీ దీనికి ఎక్కువ సమయం పట్టవచ్చు." - - "యాంటెన్నా" - "కేబుల్" - "అంత ఖచ్చితంగా తెలియదు" - "అభివృద్ధి మాత్రమే" - - "టీవీ ట్యూనర్ సెటప్" - "USB ఛానెల్ ట్యూనర్ సెటప్" - "దీనికి కొన్ని నిమిషాలు పట్టవచ్చు" - "ట్యూనర్ తాత్కాలికంగా అందుబాటులో లేదు లేదా ఇప్పటికే రికార్డింగ్ ద్వారా ఉపయోగించబడుతోంది." - - %1$d ఛానెల్‌లు కనుగొనబడ్డాయి - %1$d ఛానెల్ కనుగొనబడింది - - "ఛానెల్ స్కాన్‌ను ఆపివేయి" - - %1$d ఛానెల్‌లు కనుగొనబడ్డాయి - %1$d ఛానెల్ కనుగొనబడింది - - - మంచిది! ఛానెల్ స్కాన్‌లో %1$d ఛానెల్‌లు కనుగొనబడ్డాయి. ఇది సరైనదిగా అనిపించకుంటే, యాంటెన్నా స్థానం సర్దుబాటు చేసి, ఆపై మళ్లీ స్కాన్ చేయడం ప్రయత్నించండి. - మంచిది! ఛానెల్ స్కాన్‌లో %1$d ఛానెల్ కనుగొనబడింది. ఇది సరైనదిగా అనిపించకుంటే, యాంటెన్నా స్థానం సర్దుబాటు చేసి, ఆపై మళ్లీ స్కాన్ చేయడం ప్రయత్నించండి. - - - "పూర్తయింది" - "మళ్లీ స్కాన్ చేయి" - - "ఛానెల్‌లు ఏవీ కనుగొనబడలేదు" - "స్కాన్‌లో ఏ ఛానెల్ కనుగొనబడలేదు. టీవీ సిగ్నల్ సోర్స్‌కి మీ టీవీ కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి. \n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, దాని స్థానాన్ని లేదా దిశను సర్దుబాటు చేయండి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచి, ఆపై మళ్లీ స్కాన్ చేయండి." - "స్కాన్‌లో ఛానెల్‌లు ఏవీ కనుగొనబడలేదు. USB ట్యూనర్ ప్లగిన్ చేయబడి, టీవీ సిగ్నల్ సోర్స్‌కు కనెక్ట్ చేయబడిందని ధృవపరుచుకోండి.\n\nప్రసారాల కోసం యాంటెన్నాను ఉపయోగిస్తుంటే, దాని స్థానాన్ని లేదా దిశను సర్దుబాటు చేయండి. ఉత్తమ ఫలితాల కోసం, దాన్ని ఎత్తులో కిటికీకి దగ్గరగా ఉంచి, ఆపై మళ్లీ స్కాన్ చేయండి." - - "మళ్లీ స్కాన్ చేయి" - "పూర్తయింది" - - "టీవీ ఛానెల్‌ల కోసం స్కాన్ చేయండి" - "టీవీ ట్యూనర్ సెటప్" - "USB టీవీ ట్యూనర్ సెటప్" - "USB టీవీ ట్యూనర్ డిస్‌కనెక్ట్ చేయబడింది." - diff --git a/usbtuner-res/values-th/strings.xml b/usbtuner-res/values-th/strings.xml index 217520df..703c0f02 100644 --- a/usbtuner-res/values-th/strings.xml +++ b/usbtuner-res/values-th/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ตัวรับสัญญาณทีวี" "ตัวรับสัญญาณทีวีแบบ USB" - "เปิด" - "ปิด" + "ตัวรับสัญญาณทีวีเครือข่าย (เบต้า)" "โปรดรอให้การดำเนินการหยุดลงสักครู่" - "เลือกแหล่งที่มาของช่อง" - "ไม่มีสัญญาณ" - "ไม่สามารถรับสัญญาณ %s" - "ไม่สามารถรับสัญญาณ" "ซอฟต์แวร์ตัวรับสัญญาณมีการอัปเดตเมื่อเร็วๆ นี้ โปรดสแกนช่องอีกครั้ง" "เปิดใช้เสียงเซอร์ราวด์ในการตั้งค่าเสียงของระบบเพื่อเปิดใช้เสียง" + "เล่นเสียงไม่ได้ โปรดลองทีวีเครื่องอื่น" "ตั้งค่าตัวรับสัญญาณช่อง" "ตั้งค่าตัวรับสัญญาณทีวี" "ตั้งค่าตัวรับสัญญาณช่องแบบ USB" + "ตั้งค่าตัวรับสัญญาณเครือข่าย" "โปรดตรวจสอบว่าทีวีของคุณเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ คุณอาจต้องปรับตำแหน่งและทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" "โปรดตรวจสอบว่าได้เสียบปลั๊กตัวรับสัญญาณแบบ USB และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ คุณอาจต้องปรับตำแหน่งหรือทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" + "โปรดตรวจสอบว่าตัวรับสัญญาณเครือข่ายเปิดอยู่และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ (OTA) คุณอาจต้องปรับตำแหน่งหรือทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" "ต่อไป" "ข้ามไปก่อน" @@ -40,6 +38,7 @@ "ต้องการเริ่มการตั้งค่าช่องอีกครั้งใช่ไหม" "วิธีนี้จะนำช่องที่พบจากตัวรับสัญญาณทีวีออกและสแกนหาช่องใหม่อีกครั้ง\n\nโปรดตรวจสอบว่าทีวีของคุณเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ คุณอาจต้องปรับตำแหน่งและทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" "วิธีนี้จะนำช่องที่พบจากตัวรับสัญญาณแบบ USB ออกและสแกนหาช่องใหม่อีกครั้ง\n\nโปรดตรวจสอบว่าได้เสียบปลั๊กตัวรับสัญญาณแบบ USB และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ คุณอาจต้องปรับตำแหน่งหรือทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" + "การดำเนินการนี้จะนำช่องที่พบจากตัวรับสัญญาณเครือข่ายออกแล้วสแกนหาช่องใหม่อีกครั้ง\n\nโปรดตรวจสอบว่าตัวรับสัญญาณเครือข่ายเปิดอยู่และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ (OTA) คุณอาจต้องปรับตำแหน่งหรือทิศทางเพื่อรับช่องให้ได้มากที่สุด เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่าง" "ต่อไป" "ยกเลิก" @@ -54,6 +53,7 @@ "ตั้งค่าตัวรับสัญญาณทีวี" "ตั้งค่าตัวรับสัญญาณช่องแบบ USB" + "ตั้งค่าตัวรับสัญญาณช่องเครือข่าย" "อาจใช้เวลาหลายนาที" "ตัวรับสัญญาณไม่สามารถใช้ได้ชั่วคราว หรือถูกใช้ในการบันทึกแล้ว" @@ -76,12 +76,15 @@ "ไม่พบช่อง" "สแกนไม่พบช่องใดเลย โปรดตรวจสอบว่าทีวีของคุณเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากคุณใช้เสาอากาศแบบผ่านอากาศ ให้ปรับตำแหน่งและทิศทาง เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่างแล้วสแกนอีกครั้ง" "สแกนไม่พบช่องใดเลย โปรดตรวจสอบว่าได้เสียบปลั๊กตัวรับสัญญาณแบบ USB และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากคุณใช้เสาอากาศแบบผ่านอากาศ ให้ปรับตำแหน่งหรือทิศทาง เพื่อให้ได้ผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่างแล้วสแกนอีกครั้ง" + "สแกนไม่พบช่องใดเลย โปรดตรวจสอบว่าตัวรับสัญญาณเครือข่ายเปิดอยู่และเชื่อมต่อกับแหล่งสัญญาณทีวีแล้ว\n\nหากใช้เสาอากาศแบบผ่านอากาศ (OTA) ให้ปรับตำแหน่งหรือทิศทาง เพื่อผลลัพธ์ที่ดีที่สุด ให้วางไว้บนที่สูงใกล้หน้าต่างแล้วสแกนอีกครั้ง" "สแกนอีกครั้ง" "เสร็จสิ้น" - "สแกนหาช่องทีวี" - "ตั้งค่าตัวรับสัญญาณทีวี" - "ตั้งค่าตัวรับสัญญาณทีวีแบบ USB" - "ยกเลิกการเชื่อมต่อตัวรับสัญญาณทีวีผ่าน USB แล้ว" + "สแกนหาช่องทีวี" + "ตั้งค่าตัวรับสัญญาณทีวี" + "ตั้งค่าตัวรับสัญญาณทีวีแบบ USB" + "ตั้งค่าตัวรับสัญญาณทีวีเครือข่าย" + "ยกเลิกการเชื่อมต่อตัวรับสัญญาณทีวีผ่าน USB แล้ว" + "ยกเลิกการเชื่อมต่อตัวรับสัญญาณเครือข่ายแล้ว" diff --git a/usbtuner-res/values-tl/strings.xml b/usbtuner-res/values-tl/strings.xml index 7a0ce826..52c99eac 100644 --- a/usbtuner-res/values-tl/strings.xml +++ b/usbtuner-res/values-tl/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Tuner" "USB TV Tuner" - "I-on" - "I-off" + "Network TV Tuner (BETA)" "Mangyaring maghintay na matapos ang pagpoproseso" - "Piliin ang pinagmulan ng iyong channel" - "Walang Signal" - "Hindi na-tune sa %s" - "Hindi na-tune" "Na-update kamakailan ang software ng tuner. Paki-scan muli ang mga channel." "I-enable ang surround sound sa mga setting ng tunog ng system upang ma-enable ang audio" + "Hindi ma-play ang audio. Mangyaring sumubok ng ibang TV" "Setup ng channel tuner" "Setup ng TV Tuner" "Setup ng USB channel tuner" + "Pag-set up ng network tuner" "I-verify na nakakonekta ang iyong TV sa isang TV signal source.\n\nKung gumagamit ng over-the-air antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang makuha ang pinakamaraming channel. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar at malapit sa isang bintana." "I-verify na nakasaksak at nakakonekta ang USB tuner sa isang TV source signal.\n\nKung gumagamit ka ng isang over-the-air antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang makuha ang pinakamaraming channel. Para sa mga pinakamainam na resulta, ilagay ito sa isang mataas na lugar at malapit sa isang bintana." + "I-verify na naka-on ang network tuner at nakakonekta sa isang pinagmumulan ng TV signal.\n\nKung gumagamit ng over-the-air na antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang masagap ang karamihan ng mga channel. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar at malapit sa bintana." "Magpatuloy" "Hindi ngayon" @@ -40,6 +38,7 @@ "Gusto mo bang muling patakbuhin ang pag-set up ng channel?" "Aalisin nito ang mga nakitang channel mula sa TV tuner at mag-scan muli para sa mga bagong channel.\n\nI-verify na nakakonekta ang iyong TV sa isang TV signal source.\n\nKung gumagamit ka ng over-the-air antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang makuha ang pinakamaraming channel. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar at malapit sa isang bintana." "Aalisin nito ang mga channel na nahanap mula sa USB tuner at mag-scan muli para sa mga bagong channel.\n\nI-verify na nakasaksak at nakakonekta ang USB tuner sa isang TV source signal.\n\nKung gumagamit ng over-the-air antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang makuha ang pinakamaraming channel. Para sa mga pinakamainam na resulta, ilagay ito sa isang mataas na lugar at malapit sa isang bintana." + "Aalisin nito ang mga nahanap na channel sa network tuner at muling magsa-scan ng mga bagong channel.\n\nI-verify na naka-on ang network tuner at nakakonekta sa isang pinagmumulan ng TV signal.\n\nKung gumagamit ng over-the-air na antenna, maaaring kailanganin mong ayusin ang pagkakalagay o direksyon nito upang masagap ang karamihan ng mga channel. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar at malapit sa bintana." "Magpatuloy" "Kanselahin" @@ -54,6 +53,7 @@ "Setup ng TV tuner" "Setup ng USB channel tuner" + "Pag-set up ng network channel tuner" "Maaari itong tumagal ng ilang minuto" "Pansamantalang hindi available ang Tuner o ginagamit na ito ng recording." @@ -76,12 +76,15 @@ "Walang nakitang Mga Channel" "Hindi nakahanap ng anumang mga channel ang pag-scan. I-verify na nakakonekta ang iyong TV sa isang TV signal source.\n\nKung gumagamit ka ng over-the-air antenna, ayusin ang pagkakalagay o direksyon nito. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar malapit sa isang bintana at mag-scan muli." "Walang nahanap na anumang mga channel noong nag-scan. I-verify na nakasaksak at nakakonekta ang USB tuner sa isang TV signal source.\n\nKung gumagamit ka ng over-the-air antenna, ayusin ang pagkakalagay o direksyon nito. Para sa mga pinakamainam na resulta, ilagay ito sa isang mataas na lugar at malapit sa isang bintana mag-scan muli." + "Walang nahanap na channel sa pag-scan. I-verify na naka-on ang network tuner at nakakonekta sa isang pinagmumulan ng TV signal.\n\nKung gumagamit ng over-the-air na antenna, ayusin ang pagkakalagay o direksyon nito. Para sa mga pinakamainam na resulta, ilagay ito sa mataas na lugar at malapit sa bintana at muling mag-scan." "Muling mag-scan" "Tapos Na" - "Mag-scan ng mga channel sa TV" - "Setup ng TV Tuner" - "Setup ng USB TV Tuner" - "Nadiskonekta ang USB TV tuner." + "Mag-scan ng mga channel sa TV" + "Setup ng TV Tuner" + "Setup ng USB TV Tuner" + "Setup ng Network TV Tuner" + "Nadiskonekta ang USB TV tuner." + "Nadiskonekta ang network tuner." diff --git a/usbtuner-res/values-tr/strings.xml b/usbtuner-res/values-tr/strings.xml index 29910f9e..5196bfa3 100644 --- a/usbtuner-res/values-tr/strings.xml +++ b/usbtuner-res/values-tr/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "TV Kanal Ayarlayıcı" "USB TV Kanal Ayarlayıcı" - "Açık" - "Kapalı" + "Ağ TV Kanal Ayarlayıcı (BETA)" "Lütfen işlemin tamamlanmasını bekleyin" - "Kanal kaynağınızı seçin" - "Sinyal Yok" - "%s adlı kanala ayarlanamadı" - "Kanal ayarlanamadı" "Kanal ayarlayıcı yazılımı yakın zamanda güncellendi. Lütfen kanalları yeniden tarayın." "Sesi etkinleştirmek için sistemin ses ayarlarında surround sesi etkinleştirin" + "Ses çalınamıyor. Lütfen başka bir TV deneyin" "Kanal ayarlayıcı kurulumu" "TV Kanal Ayarlayıcı kurulumu" "USB kanal ayarlayıcı kurulumu" + "Ağ kanal ayarlayıcı kurulumu" "TV\'nizin bir TV sinyal kaynağına bağlı olduğunu doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız en çok sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." "USB kanal ayarlayıcının takılı olduğunu ve bir TV sinyali kaynağına bağlandığını doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız en çok sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." + "Ağ kanal ayarlayıcının açık olduğunu ve TV sinyali alabileceği bir kaynağa bağlandığını doğrulayın.\n\nHavadan gelen yayını alacak bir anten kullanıyorsanız en fazla sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonuçları elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." "Devam" "Şimdi değil" @@ -40,6 +38,7 @@ "Kanal kurulumu yeniden çalıştırılsın mı?" "Bu işlem, TV kanal ayarlayıcının bulduğu kanalları kaldıracak ve tekrar yeni kanallar arayacak.\n\nTV\'nizin bir TV sinyal kaynağına bağlı olduğunu doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız en çok sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." "Bu işlem, USB kanal ayarlayıcıdan bulunan kanalları kaldıracak ve yeni kanallar için tekrar tarama yapacaktır.\n\nUSB kanal ayarlayıcının takılı olduğunu ve bir TV sinyali kaynağına bağlandığını doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız en çok sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." + "Bu işlem, bulunan kanalları ağ kanal ayarlayıcıdan kaldıracak ve yeni kanalların bulunması için tekrar tarama yapacak.\n\nAğ kanal ayarlayıcının açık olduğunu ve TV sinyali alabileceği bir kaynağa bağlandığını doğrulayın.\n\nHavadan gelen yayını alacak bir anten kullanıyorsanız en fazla sayıda kanalı almak için antenin yerini veya yönünü ayarlamanız gerekebilir. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." "Devam" "İptal" @@ -54,6 +53,7 @@ "TV kanal ayarlayıcı kurulumu" "USB kanal ayarlayıcı kurulumu" + "Ağ kanal ayarlayıcı kurulumu" "Bu işlem birkaç dakika sürebilir" "Kanal ayarlayıcı geçici olarak kullanılamıyor veya şu anda kayıt yapmak için kullanılıyor." @@ -76,12 +76,15 @@ "Hiçbir kanal bulunamadı" "Tarama işleminde hiçbir kanal bulunamadı. TV\'nizin bir TV sinyal kaynağına bağlı olduğunu doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız yerini veya yönünü ayarlayın. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirerek tekrar tarayın." "Tarama işlemi herhangi bir kanal bulamadı. USB kanal ayarlayıcının takılı olduğunu ve bir TV sinyali kaynağına bağlandığını doğrulayın.\n\nHavadan yayın alan bir anten kullanıyorsanız yerini veya yönünü ayarlayın. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirerek tekrar tarayın." + "Taramada herhangi bir kanal bulunamadı. Ağ kanal ayarlayıcının açık olduğunu ve TV sinyali alabileceği bir kaynağa bağlandığını doğrulayın.\n\nHavadan gelen yayınları alacak bir anten kullanıyorsanız antenin yerini veya yönünü ayarlayın. En iyi sonucu elde etmek için anteni yüksek ve pencereye yakın bir yere yerleştirin." "Yeniden tara" "Bitti" - "TV kanallarını tarayın" - "TV Kanal Ayarlayıcı kurulumu" - "USB TV Kanal Ayarlayıcı kurulumu" - "USB TV kanal ayarlayıcı bağlantısı kesildi." + "TV kanallarını tarayın" + "TV Kanal Ayarlayıcı kurulumu" + "USB TV Kanal Ayarlayıcı kurulumu" + "Ağ TV Kanal Ayarlayıcı kurulumu" + "USB TV kanal ayarlayıcı bağlantısı kesildi." + "Ağ kanal ayarlayıcı bağlantısı kesildi." diff --git a/usbtuner-res/values-uk/strings.xml b/usbtuner-res/values-uk/strings.xml index cf7cc85e..50c84efc 100644 --- a/usbtuner-res/values-uk/strings.xml +++ b/usbtuner-res/values-uk/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "ТВ-тюнер" "ТВ-тюнер USB" - "Увімкнути" - "Вимкнути" + "Мережевий ТВ-тюнер (БЕТА-ВЕРСІЯ)" "Зачекайте, доки завершиться пошук" - "Виберіть джерело каналу" - "Немає сигналу" - "Не вдалося налаштувати канал %s" - "Не вдалося налаштувати" "Програмне забезпечення тюнера нещодавно оновлено. Проскануйте канали знову." "Увімкнути об’ємний звук у налаштуваннях системи, щоб слухати аудіо" + "Не вдається відтворити відео. Спробуйте на іншому телевізорі" "Налаштування тюнера" "Налаштування ТВ-тюнера" "Налаштування USB-тюнера" + "Налаштування мережевого тюнера" "Переконайтеся, що ви під’єднали телевізор до джерела вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення,щоб знайти більше каналів. Розмістіть антену вище та біля вікна." "Переконайтеся, що ви підключили USB-тюнер і під’єднали джерело вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення, щоб знайти більше каналів. Розмістіть антену вище та біля вікна." + "Переконайтеся, що мережевий тюнер увімкнено та під’єднано до джерела телевізійного сигналу.\n\nЯкщо у вас ефірна антена, змініть її положення. Розмістіть антену вище та біля вікна й повторіть спробу." "Продовжити" "Не зараз" @@ -40,6 +38,7 @@ "Відновити налаштування каналу?" "Канали, знайдені за допомогою ТВ-тюнера, буде видалено. Пошук почнеться знову.\n\nПереконайтеся, що ви під’єднали телевізор до джерела вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення, щоб знайти більше каналів. Розмістіть антену вище та біля вікна й повторіть спробу." "Канали, знайдені за допомогою USB-тюнера, буде видалено. Пошук почнеться знову.\n\nПереконайтеся, що ви підключили USB-тюнер і під’єднали джерело вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення, щоб знайти більше каналів. Розмістіть антену вище та біля вікна." + "Канали, знайдені мережевим тюнером, буде видалено. Пошук почнеться знову.\n\nПереконайтеся, що мережевий тюнер увімкнено та під’єднано до джерела телевізійного сигналу.\n\nЯкщо у вас ефірна антена, змініть її положення. Розмістіть антену вище та біля вікна й повторіть спробу." "Продовжити" "Скасувати" @@ -54,6 +53,7 @@ "Налаштування ТВ-тюнера" "Налаштування USB-тюнера" + "Налаштування мережевого тюнера" "Це може зайняти декілька хвилин" "Тюнер тимчасово недоступний або вже використовується для запису." @@ -82,12 +82,15 @@ "Канали не знайдено" "Канали не знайдено. Переконайтеся, що ви під’єднали телевізор до джерела вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення. Розмістіть антену вище та біля вікна й повторіть спробу." "Канали не знайдено. Переконайтеся, що ви підключили USB-тюнер і під’єднали джерело вхідного телевізійного сигналу.\n\nЯкщо ви користуєтесь ефірною антеною, змініть її положення. Розмістіть антену вище та біля вікна й повторіть спробу." + "Каналів не знайдено. Переконайтеся, що мережевий тюнер увімкнено та під’єднано до джерела телевізійного сигналу.\n\nЯкщо у вас ефірна антена, змініть її положення. Розмістіть антену вище та біля вікна й повторіть спробу." "Шукати знову" "Готово" - "Пошук телевізійних каналів" - "Налаштування ТВ-тюнера" - "Налаштування ТВ-тюнера USB" - "ТВ-тюнер USB від’єднано." + "Пошук телевізійних каналів" + "Налаштування ТВ-тюнера" + "Налаштування ТВ-тюнера USB" + "Налаштування мережевого ТВ-тюнера" + "ТВ-тюнер USB від’єднано." + "Мережевий тюнер від’єднано." diff --git a/usbtuner-res/values-ur-rPK/strings.xml b/usbtuner-res/values-ur-rPK/strings.xml new file mode 100644 index 00000000..49dc9e34 --- /dev/null +++ b/usbtuner-res/values-ur-rPK/strings.xml @@ -0,0 +1,90 @@ + + + + + "‏TV ٹیونر" + "‏USB TV ٹیونر" + "‏نیٹ ورک TV ٹیونر (بی ٹا)" + "براہ کرم کارروائی ختم ہونے کا انتظار کریں" + "ٹیونر سافٹ ویئر حال ہی میں اپ ڈیٹ کیا گیا ہے۔ براہ کرم چینلز کو دوبارہ اسکین کریں۔" + "آڈیو کو فعال کرنے کیلئے سسٹم کی آواز کی ترتیبات میں محیط آواز فعال کریں" + "آڈیو نہیں چل رہی۔ براہ کرم کوئی اور ٹی وی آزمائیں" + "چینل ٹیونر سیٹ اپ" + "‏TV ٹیونر سیٹ اپ" + "‏USB چینل ٹیونر سیٹ اپ" + "نیٹ ورک ٹیونر سیٹ اپ" + "‏اپنے TV کے کسی TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + "‏USB ٹیونر کے پلگ ان ہونے اور TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + "‏نیٹ ورک ٹیونر کے آن ہونے اور TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + + "جاری رکھیں" + "ابھی نہیں" + + "چینل سیٹ اپ دوبارہ چلائیں؟" + "‏یہ TV ٹیونر سے ملے چینلز ہٹا دے گا اور دوبارہ نئے چینلز کیلئے اسکین کرے گا۔\n\nاپنے TV کے کسی سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + "‏یہ USB ٹیونر سے ملے چینلز ہٹا دے گا اور دوبارہ نئے چینلز کیلئے اسکین کرے گا۔\n\nTV ٹیونر کے پلگ ان ہونے اور سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + "‏یہ نیٹ ورک ٹیونر سے ملنے والے چیلنز ہٹا دے گا اور دوبارہ نئے چینلز کیلئے اسکین کرے گا۔\n\nTV ٹیونر کے آن ہونے اور سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں، تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کی مقام بندی یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" + + "جاری رکھیں" + "منسوخ کریں" + + "کنکشن کی قسم منتخب کریں" + "اگر ٹیونر سے کوئی بیرونی انٹینا منسلک ہے تو انٹینا کا انتخاب کریں۔ اگر آپ کے چینلز کسی کیبل سروس فراہم کنندہ کی طرف سے موصول ہوتے ہیں تو کیبل کا انتخاب کریں۔ اگر آپ پُر یقین نہیں ہیں تو دونوں اقسام کو اسکین کیا جائے گا، مگر اس میں زیادہ وقت لگ سکتا ہے۔" + + "انٹینا" + "کیبل" + "یقین نہیں ہے" + "صرف ڈیولپمنٹ" + + "‏TV ٹیونر سیٹ اپ" + "‏USB چینل ٹیونر سیٹ اپ" + "نیٹ ورک چینل ٹیونر سیٹ اپ" + "اس میں کئی منٹ لگ سکتے ہیں" + "ٹیونر عارضی طور پر غیر دستیاب ہے یا پہلے سے ریکارڈنگ کی وجہ سے استعمال ہو گیا ہے۔" + + ‏%1$d چینلز ملے + ‏%1$d چینل ملا + + "چینل اسکین روکیں" + + ‏‎%1$d چینلز ملے + ‏%1$d چینل ملا + + + ‏خوب! چینل اسکین کے دوران ‎%1$d چینلز ملے۔ اگر یہ ٹھیک نہیں لگ رہا تو انٹینا کی پوزیشن ایڈجسٹ کرنے کی کوشش کریں اور دوبارہ اسکین کریں۔ + ‏خوب! چینل اسکین کے دوران ‎%1$d چینل ملا۔ اگر یہ ٹھیک نہیں لگ رہا تو انٹینا کی پوزیشن ایڈجسٹ کرنے کی کوشش کریں اور دوبارہ اسکین کریں۔ + + + "ہو گیا" + "دوبارہ اسکین کریں" + + "کوئی چینلز نہیں ملے" + "‏اسکین سے کوئی چینلز نہیں ملے۔ اپنے TV کے ایک TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو اس کا مقام یا سمت ایڈجسٹ کریں۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں اور دوبارہ اسکین کریں۔" + "‏اسکین سے کوئی چینلز نہیں ملے۔ USB ٹیونر کے پلگ ان ہونے اور TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو اس کا مقام یا سمت ایڈجسٹ کریں۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں اور دوبارہ اسکین کریں۔" + "‏اسکین سے کوئی چینلز نہیں ملے۔ توثیق کریں کہ نیٹ ورک ٹیونر آن ہے اور TV سگنل ماخذ سے منسلک ہے۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں، تو اس کا مقام یا سمت ایڈجسٹ کریں۔ بہترین نتائج کیلئے، اسے اونچی جگہ اور کھڑکی کے قریب رکھیں اور دوبارہ اسکین کریں۔" + + "دوبارہ اسکین کریں" + "ہو گیا" + + "‏TV چینلز کے لیے اسکین کریں" + "‏TV ٹیونر سیٹ اپ" + "‏USB TV ٹیونر سیٹ اپ" + "‏نیٹ ورک TV ٹیونر سیٹ اپ" + "‏USB TV ٹیونر غیر منسلک ہے۔" + "نیٹ ورک ٹیونر غیر منسلک ہے۔" + diff --git a/usbtuner-res/values-ur/strings.xml b/usbtuner-res/values-ur/strings.xml deleted file mode 100644 index 68c85d05..00000000 --- a/usbtuner-res/values-ur/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "‏TV ٹیونر" - "‏USB TV ٹیونر" - "آن" - "آف" - "براہ کرم کارروائی ختم ہونے کا انتظار کریں" - "اپنے چینل کا ماخذ منتخب کریں" - "کوئی سگنل نہیں ہے" - "%s پر ٹیون ہونے میں ناکام ہوگیا" - "ٹیون کرنے میں ناکام ہو گیا" - "ٹیونر سافٹ ویئر حال ہی میں اپ ڈیٹ کیا گیا ہے۔ براہ کرم چینلز کو دوبارہ اسکین کریں۔" - "آڈیو کو فعال کرنے کیلئے سسٹم کی آواز کی ترتیبات میں محیط آواز فعال کریں" - "چینل ٹیونر سیٹ اپ" - "‏TV ٹیونر سیٹ اپ" - "‏USB چینل ٹیونر سیٹ اپ" - "‏اپنے TV کے کسی TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" - "‏USB ٹیونر کے پلگ ان ہونے اور TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" - - "جاری رکھیں" - "ابھی نہیں" - - "چینل سیٹ اپ دوبارہ چلائیں؟" - "‏یہ TV ٹیونر سے ملے چینلز ہٹا دے گا اور دوبارہ نئے چینلز کیلئے اسکین کرے گا۔\n\nاپنے TV کے کسی سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" - "‏یہ USB ٹیونر سے ملے چینلز ہٹا دے گا اور دوبارہ نئے چینلز کیلئے اسکین کرے گا۔\n\nTV ٹیونر کے پلگ ان ہونے اور سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو زیادہ تر چینلز موصول کرنے کیلئے آپ کو اس کا مقام یا سمت ایڈجسٹ کرنے کی ضرورت پیش آ سکتی ہے۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں۔" - - "جاری رکھیں" - "منسوخ کریں" - - "کنکشن کی قسم منتخب کریں" - "اگر ٹیونر سے کوئی بیرونی انٹینا منسلک ہے تو انٹینا کا انتخاب کریں۔ اگر آپ کے چینلز کسی کیبل سروس فراہم کنندہ کی طرف سے موصول ہوتے ہیں تو کیبل کا انتخاب کریں۔ اگر آپ پُر یقین نہیں ہیں تو دونوں اقسام کو اسکین کیا جائے گا، مگر اس میں زیادہ وقت لگ سکتا ہے۔" - - "انٹینا" - "کیبل" - "یقین نہیں ہے" - "صرف ڈیولپمنٹ" - - "‏TV ٹیونر سیٹ اپ" - "‏USB چینل ٹیونر سیٹ اپ" - "اس میں کئی منٹ لگ سکتے ہیں" - "ٹیونر عارضی طور پر غیر دستیاب ہے یا پہلے سے ریکارڈنگ کی وجہ سے استعمال ہو گیا ہے۔" - - ‏%1$d چینلز ملے - ‏%1$d چینل ملا - - "چینل اسکین روکیں" - - ‏‎%1$d چینلز ملے - ‏%1$d چینل ملا - - - ‏خوب! چینل اسکین کے دوران ‎%1$d چینلز ملے۔ اگر یہ ٹھیک نہیں لگ رہا تو انٹینا کی پوزیشن ایڈجسٹ کرنے کی کوشش کریں اور دوبارہ اسکین کریں۔ - ‏خوب! چینل اسکین کے دوران ‎%1$d چینل ملا۔ اگر یہ ٹھیک نہیں لگ رہا تو انٹینا کی پوزیشن ایڈجسٹ کرنے کی کوشش کریں اور دوبارہ اسکین کریں۔ - - - "ہو گیا" - "دوبارہ اسکین کریں" - - "کوئی چینلز نہیں ملے" - "‏اسکین سے کوئی چینلز نہیں ملے۔ اپنے TV کے ایک TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو اس کا مقام یا سمت ایڈجسٹ کریں۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں اور دوبارہ اسکین کریں۔" - "‏اسکین سے کوئی چینلز نہیں ملے۔ USB ٹیونر کے پلگ ان ہونے اور TV سگنل ماخذ سے منسلک ہونے کی توثیق کریں۔\n\nاگر وائرلیس نیٹ ورک کنکشن انٹینا استعمال کر رہے ہیں تو اس کا مقام یا سمت ایڈجسٹ کریں۔ بہترین نتائج کیلئے اسے اونچی جگہ اور کھڑکی کے پاس رکھیں اور دوبارہ اسکین کریں۔" - - "دوبارہ اسکین کریں" - "ہو گیا" - - "‏TV چینلز کے لیے اسکین کریں" - "‏TV ٹیونر سیٹ اپ" - "‏USB TV ٹیونر سیٹ اپ" - "‏USB TV ٹیونر غیر منسلک ہے۔" - diff --git a/usbtuner-res/values-uz-rUZ/strings.xml b/usbtuner-res/values-uz-rUZ/strings.xml new file mode 100644 index 00000000..2709dec0 --- /dev/null +++ b/usbtuner-res/values-uz-rUZ/strings.xml @@ -0,0 +1,90 @@ + + + + + "TV-tyuner" + "USB TV-tyuner" + "Tarmoq TV-tyuneri (BETA)" + "Iltimos, jarayon tugashini kuting" + "Tyunerning dasturiy ta’minoti yaqinda yangilandi. Kanallarni qaytadan qidiring." + "Audioni yoqish uchun tizim ovozi sozlamalari orqali qamrovli ovozni yoqing" + "Audioni ijro ettirib bo‘lmadi. Boshqa kanalni sinab ko‘ring." + "Tyunerni sozlash" + "TV-tyunerni sozlash" + "USB-tyunerni sozlash" + "Tarmoq tyunerini sozlash" + "Televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, ko‘proq kanal topilishi uchun uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating." + "USB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, ko‘proq kanal topilishi uchun uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating." + "Tarmoq tyuneri yoqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, ko‘proq kanal topilishi uchun uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating." + + "Davom etish" + "Hozir emas" + + "Kanallar qaytadan sozlansinmi?" + "Buning natijasida TV-tyuner orqali topilgan kanallar o‘chirib tashlanadi va kanallar boshqatdan qidiriladi.\n\nTelevizor signal manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + "Buning natijasida USB-tyuner orqali topilgan kanallar o‘chirib tashlanadi va kanallar boshqatdan qidiriladi.\n\nUSB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + "Buning natijasida tarmoq tyuneri orqali topilgan kanallar o‘chirib tashlanadi va kanallar boshqatdan qidiriladi.\n\nTarmoq tyuneri yoqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + + "Davom etish" + "Bekor qilish" + + "Aloqa turini tanlang" + "Agar tyunerga tashqi antenna ulangan bo‘lsa, “Antenna” variantini tanlang. Agar kanallar kabel televideniye ta’minotchisidan olinadigan bo‘lsa, “Kabel TV” variantini tanlang. Agar qaysi biri ekanligini aniq bilmasangiz, har ikkala tur ham qidiriladi, shuning uchun uzoqroq vaqt ketishi mumkin." + + "Antenna" + "Kabel" + "Aniq bilmayman" + "Faqat dasturchilar uchun" + + "TV-tyunerni sozlash" + "USB-tyunerni sozlash" + "Tarmoq tyuneri kanallarini sozlash" + "Bu bir necha daqiqa vaqt olishi mumkin" + "Tyunerdan vaqtinchalik foydalanib bo‘lmaydi yoki allaqachon yozib olishda foydalanilmoqda." + + %1$d ta kanal topildi + %1$d ta kanal topildi + + "KANAL QIDIRUVINI TO‘XTATISH" + + %1$d ta kanal topildi + %1$d ta kanal topildi + + + Ajoyib! Kanal qidirish natijasida %1$d ta kanal topildi. Agar yanada ko‘proq kanal topilishi kerak deb hisoblasangiz, antenna joylashuvini sozlang va qaytadan qidiring. + Ajoyib! Kanal qidirish natijasida %1$d ta kanal topildi. Agar yanada ko‘proq kanal topilishi kerak deb hisoblasangiz, antenna joylashuvini sozlang va qaytadan qidiring. + + + "Tayyor" + "Yana qidirish" + + "Hech qanday kanal topilmadi" + "Qidiruv natijasida hech qanday kanal topilmadi. Televizor signal manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + "Qidiruv natijasida hech qanday kanal topilmadi. USB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + "Qidiruv natijasida hech qanday kanal topilmadi. Tarmoq tyuneri yoqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." + + "Yana qidirish" + "Tayyor" + + "Telekanallarni qidiring" + "TV-tyunerni sozlash" + "USB TV-tyunerni sozlang" + "Tarmoq TV-tyunerini sozlash" + "USB-tyuner o‘chirib qo‘yildi." + "Tarmoq tyuneri uzib qo‘yildi." + diff --git a/usbtuner-res/values-uz/strings.xml b/usbtuner-res/values-uz/strings.xml deleted file mode 100644 index 9aefd57b..00000000 --- a/usbtuner-res/values-uz/strings.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - "TV-tyuner" - "USB TV-tyuner" - "Yoqish" - "O‘chirib qo‘yish" - "Iltimos, jarayon tugashini kuting" - "Kanal manbasini tanlang" - "Signal yo‘q" - "“%s” kanaliga sozlab bo‘lmadi" - "Sozlab bo‘lmadi" - "Tyunerning dasturiy ta’minoti yaqinda yangilandi. Kanallarni qaytadan qidiring." - "Audioni yoqish uchun tizim ovozi sozlamalari orqali qamrovli ovozni yoqing" - "Tyunerni sozlash" - "TV-tyunerni sozlash" - "USB-tyunerni sozlash" - "Televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, ko‘proq kanal topilishi uchun uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating." - "USB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, ko‘proq kanal topilishi uchun uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating." - - "Davom etish" - "Hozir emas" - - "Kanallar qaytadan sozlansinmi?" - "Buning natijasida TV-tyuner orqali topilgan kanallar o‘chirib tashlanadi va kanallar boshqatdan qidiriladi.\n\nTelevizor signal manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." - "Buning natijasida USB-tyuner orqali topilgan kanallar o‘chirib tashlanadi va kanallar boshqatdan qidiriladi.\n\nUSB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." - - "Davom etish" - "Bekor qilish" - - "Aloqa turini tanlang" - "Agar tyunerga tashqi antenna ulangan bo‘lsa, “Antenna” variantini tanlang. Agar kanallar kabel televideniye ta’minotchisidan olinadigan bo‘lsa, “Kabel TV” variantini tanlang. Agar qaysi biri ekanligini aniq bilmasangiz, har ikkala tur ham qidiriladi, shuning uchun uzoqroq vaqt ketishi mumkin." - - "Antenna" - "Kabel" - "Aniq bilmayman" - "Faqat dasturchilar uchun" - - "TV-tyunerni sozlash" - "USB-tyunerni sozlash" - "Bu bir necha daqiqa vaqt olishi mumkin" - "Tyunerdan vaqtinchalik foydalanib bo‘lmaydi yoki allaqachon yozib olishda foydalanilmoqda." - - %1$d ta kanal topildi - %1$d ta kanal topildi - - "KANAL QIDIRUVINI TO‘XTATISH" - - %1$d ta kanal topildi - %1$d ta kanal topildi - - - Ajoyib! Kanal qidirish natijasida %1$d ta kanal topildi. Agar yanada ko‘proq kanal topilishi kerak deb hisoblasangiz, antenna joylashuvini sozlang va qaytadan qidiring. - Ajoyib! Kanal qidirish natijasida %1$d ta kanal topildi. Agar yanada ko‘proq kanal topilishi kerak deb hisoblasangiz, antenna joylashuvini sozlang va qaytadan qidiring. - - - "Tayyor" - "Yana qidirish" - - "Hech qanday kanal topilmadi" - "Qidiruv natijasida hech qanday kanal topilmadi. Televizor signal manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." - "Qidiruv natijasida hech qanday kanal topilmadi. USB-tyuner suqilgan hamda televizor signali manbasiga ulangan ekanligini tekshiring.\n\nAgar havo orqali to‘lqin tutuvchi antennadan foydalanayotgan bo‘lsangiz, uning joyi va yo‘nalishini sozlang. Eng yaxshi natijaga erishish uchun uni yuqoriroq va oynaga yaqin joyga o‘rnating hamda qaytadan qidiring." - - "Yana qidirish" - "Tayyor" - - "Telekanallarni qidiring" - "TV-tyunerni sozlash" - "USB TV-tyunerni sozlang" - "USB-tyuner o‘chirib qo‘yildi." - diff --git a/usbtuner-res/values-vi/strings.xml b/usbtuner-res/values-vi/strings.xml index 605234e4..6e6cef72 100644 --- a/usbtuner-res/values-vi/strings.xml +++ b/usbtuner-res/values-vi/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Bộ dò TV" "Bộ dò TV USB" - "Bật" - "Tắt" + "Bộ dò TV mạng (BETA)" "Vui lòng đợi để hoàn tất xử lý" - "Chọn nguồn kênh của bạn" - "Không có tín hiệu" - "Dò tới %s không thành công" - "Dò không thành công" "Phần mềm bộ dò đã được cập nhật gần đây. Vui lòng quét lại các kênh." "Bật tính năng âm thanh vòm trong cài đặt âm thanh hệ thống để bật âm thanh" + "Không thể phát âm thanh. Vui lòng thử TV khác" "Thiết lập bộ dò kênh" "Thiết lập bộ dò TV" "Thiết lập bộ dò kênh USB" + "Thiết lập bộ dò mạng" "Hãy xác minh rằng TV của bạn đã được kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây thì bạn có thể cần phải điều chỉnh vị trí hoặc hướng của ăng-ten đó để nhận nhiều kênh nhất. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ." "Xác minh rằng bộ dò USB đã được cắm và được kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, có thể bạn cần phải điều chỉnh vị trí hoặc hướng của ăng-ten đó để nhận được nhiều kênh nhất. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ." + "Hãy xác minh là bộ dò mạng đã được bật nguồn và kết nối với một nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, hãy điều chỉnh vị trí hoặc hướng của ăng-ten để nhận hầu hết kênh. Để có kết quả tốt nhất, hãy đặt ăng-ten ở vị trí cao và gần cửa sổ." "Tiếp tục" "Không phải bây giờ" @@ -40,6 +38,7 @@ "Chạy lại quá trình thiết lập kênh?" "Điều này sẽ xóa các kênh được tìm thấy khỏi bộ dò TV và quét các kênh mới lần nữa.\n\nHãy xác minh rằng TV của bạn đã được kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây thì bạn có thể cần phải điều chỉnh vị trí hoặc hướng của ăng-ten đó để nhận nhiều kênh nhất. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ." "Quá trình này sẽ xóa các kênh bộ dò USB đã tìm thấy và quét lại để tìm các kênh mới.\n\nHãy xác minh rằng bộ dò USB đã được cắm và kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, bạn có thể cần phải điều chỉnh vị trí hoặc hướng của ăng-ten đó để nhận được nhiều kênh nhất. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ." + "Điều này sẽ xóa các kênh được tìm thấy từ bộ dò mạng và quét các kênh mới một lần nữa.\n\nHãy xác minh là bộ dò mạng đã được bật nguồn và kết nối với một nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, hãy điều chỉnh vị trí hoặc hướng của ăng-ten để nhận hầu hết kênh. Để có kết quả tốt nhất, hãy đặt ăng-ten ở vị trí cao và gần cửa sổ." "Tiếp tục" "Hủy" @@ -54,6 +53,7 @@ "Thiết lập bộ dò TV" "Thiết lập bộ dò kênh USB" + "Thiết lập bộ do kênh mạng" "Quá trình này có thể mất vài phút" "Bộ dò tạm thời không có sẵn hoặc đã được sử dụng để ghi." @@ -76,12 +76,15 @@ "Không tìm thấy kênh nào" "Quá trình quét không tìm thấy bất kỳ kênh nào. Hãy xác minh rằng TV của bạn đã được kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, hãy điều chỉnh vị trí hoặc hướng của ăng-ten đó. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ rồi quét lại." "Quá trình quét không tìm thấy bất kỳ kênh nào. Hãy xác minh rằng bộ dò USB đã được cắm và kết nối với nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, hãy điều chỉnh vị trí hoặc hướng của ăng-ten đó. Để có kết quả tốt nhất, hãy đặt ăng-ten lên cao và gần cửa sổ rồi quét lại." + "Quá trình quét không tìm thấy bất kỳ kênh nào. Hãy xác minh là bộ dò mạng đã được bật nguồn và kết nối với một nguồn tín hiệu TV.\n\nNếu sử dụng ăng-ten không dây, hãy điều chỉnh vị trí hoặc hướng của ăng-ten. Để có kết quả tốt nhất, hãy đặt ăng-ten ở vị trí cao và gần cửa sổ và quét lại." "Quét lại" "Xong" - "Quét tìm các kênh TV" - "Thiết lập bộ dò TV" - "Thiết lập bộ dò TV USB." - "Đã ngắt kết nối bộ điều chỉnh TV USB." + "Quét tìm các kênh TV" + "Thiết lập bộ dò TV" + "Thiết lập bộ dò TV USB." + "Thiết lập bộ dò TV mạng" + "Đã ngắt kết nối bộ dò truyền hình USB." + "Đã ngắt kết nối bộ dò mạng." diff --git a/usbtuner-res/values-zh-rCN/strings.xml b/usbtuner-res/values-zh-rCN/strings.xml index a4f6da22..cf2b477f 100644 --- a/usbtuner-res/values-zh-rCN/strings.xml +++ b/usbtuner-res/values-zh-rCN/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "电视调谐器" "USB 电视调谐器" - "开启" - "关闭" + "网络电视调谐器(测试版)" "请耐心等待处理完毕" - "选择您的频道来源" - "无信号" - "无法调到%s" - "无法调到相应频道" "调谐器软件近期已更新。请重新扫描频道。" "在系统声音设置中启用环绕声即可启用音频" + "无法播放音频,请试试其他电视频道" "频道调谐器设置" "电视调谐器设置" "USB 频道调谐器设置" + "网络调谐器设置" "请检查您的电视是否已连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" "请检查 USB 调谐器是否已插好并连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" + "请检查网络调谐器是否已接通电源并连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" "继续" "以后再说" @@ -40,6 +38,7 @@ "要重新进行频道设置吗?" "此操作将移除通过电视调谐器找到的频道,并重新扫描新频道。\n\n请检查您的电视是否已连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" "此操作将移除通过 USB 调谐器找到的频道,并重新扫描新频道。\n\n请检查 USB 调谐器是否已插好并连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" + "此操作将移除通过网络调谐器找到的频道,并重新扫描新频道。\n\n请检查网络调谐器是否已接通电源并连接到电视信号源。\n\n如果您使用的是无线电视,则可能需要调节天线的位置或方向,以便接收尽可能多的频道。要获得最佳效果,请将天线的位置调高并靠近窗户。" "继续" "取消" @@ -54,6 +53,7 @@ "电视调谐器设置" "USB 频道调谐器设置" + "网络频道调谐器设置" "此过程可能需要几分钟时间" "调谐器暂时无法使用或已用于录制。" @@ -76,12 +76,15 @@ "未找到任何频道" "扫描后未找到任何频道。请检查您的电视是否已连接到电视信号源。\n\n如果您使用的是无线电视,请调节天线的位置或方向。要获得最佳效果,请将天线的位置调高并靠近窗户,然后再扫描一次。" "扫描后未找到任何频道。请检查 USB 调谐器是否已插好并连接到电视信号源。\n\n如果您使用的是无线电视,请调节天线的位置或方向。要获得最佳效果,请将天线的位置调高并靠近窗户,然后再扫描一次。" + "扫描后未找到任何频道。请检查网络调谐器是否已接通电源并连接到电视信号源。\n\n如果您使用的是无线电视,请调节天线的位置或方向。要获得最佳效果,请将天线的位置调高并靠近窗户,然后再扫描一次。" "重新扫描" "完成" - "扫描电视频道" - "电视调谐器设置" - "USB 电视调谐器设置" - "USB 电视调谐器已断开连接。" + "扫描电视频道" + "电视调谐器设置" + "USB 电视调谐器设置" + "网络电视调谐器设置" + "USB 电视调谐器已断开连接。" + "网络调谐器已断开连接。" diff --git a/usbtuner-res/values-zh-rHK/strings.xml b/usbtuner-res/values-zh-rHK/strings.xml index 6d40401e..f2b89519 100644 --- a/usbtuner-res/values-zh-rHK/strings.xml +++ b/usbtuner-res/values-zh-rHK/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "電視調諧器" "USB 電視調諧器" - "開啟" - "關閉" + "網絡電視調諧器 (測試版)" "請等待系統完成處理程序" - "請選擇您的頻道來源" - "無訊號" - "無法調校至%s" - "無法調校頻道" "調諧器軟件最近已更新。請重新掃瞄頻道。" "在系統音效設定中啟用環迴立體聲功能即可啟用音效" + "無法播放音效,請嘗試使用其他電視頻道" "頻道調諧器設定" "電視調諧器設定" "USB 頻道調諧器設定" + "網絡調諧器設定" "請確定電視已連接至電視訊號來源。\n\n如果您使用無線天線,可能需要調整天線的位置或方向,以接收最多頻道。您亦可將天線放在較高位置並靠近窗戶,以獲得最佳效果。" "請確定 USB 調諧器已接駁並連接至電視訊號來源。\n\n如果您使用無線天線,可能需要調整天線的位置或方向,以接收最多頻道。您亦可以將天線放在較高位置並靠近窗戶,以獲取最佳效果。" + "請確定網絡調諧器已開啟電源,並連接至電視訊號來源。\n\n如果您使用無線天線,可能需要調整天線的位置或方向,以接收最多頻道。要取得最佳效果,請將天線放在較高位置並靠近窗戶,然後重新掃瞄。" "繼續" "暫時不要" @@ -40,6 +38,7 @@ "要重新設定頻道嗎?" "這項操作將移除電視調諧器找到的頻道,並重新掃瞄新的頻道。\n\n請確定電視已連接至電視訊號來源。\n\n如果您使用無線天線,可能需要調整天線的位置或方向,以接收最多頻道。您亦可將天線放在較高位置並靠近窗戶,以獲得最佳效果。" "這項操作將移除 USB 調諧器找到的頻道,並會重新掃瞄新頻道。\n\n請確定 USB 調諧器已接駁並連接至電視訊號來源。\n\n如果您使用無線天線,可能需要調整天線的位置或方向,以接收最多頻道。您亦可將天線放在較高位置並靠近窗戶,以獲取最佳效果。" + "這項操作將移除網絡調諧器找到的頻道,並再次掃瞄新頻道。\n\n請確定網絡調諧器已開啟電源並連接至電視訊號來源。\n\n如果您使用無線天線,請調整天線的位置或方向。要取得最佳效果,請將天線放在較高位置並靠近窗戶,然後重新掃瞄。" "繼續" "取消" @@ -54,6 +53,7 @@ "電視調諧器設定" "USB 頻道調諧器設定" + "網絡頻道調諧器設定" "可能需時數分鐘" "調諧器暫時無法使用,或已用於錄影。" @@ -76,12 +76,15 @@ "找不到頻道" "掃瞄後找不到頻道。請確定電視已連接至電視訊號來源。\n\n如果您使用無線天線,請調整天線的位置或方向。您亦可將天線放在較高位置並靠近窗戶,然後重新掃瞄,以獲得最佳效果。" "掃瞄找不到頻道。請確定 USB 調諧器已接駁並連接至電視訊號來源。\n\n如果您使用無線天線,請調整天線的位置或方向。您亦可將天線放在較高位置並靠近窗戶,然後重新掃瞄,以獲取最佳效果。" + "掃瞄後找不到任何頻道。請確定網絡調諧器已開啟電源,並連接至電視訊號來源。\n\n如果您使用無線天線,請調整天線的位置或方向。要取得最佳效果,請將天線放在較高位置並靠近窗戶,然後重新掃瞄。" "重新掃瞄" "完成" - "掃瞄電視頻道" - "電視調諧器設定" - "USB 電視調諧器設定" - "已解除 USB 電視調諧器的連接。" + "掃瞄電視頻道" + "電視調諧器設定" + "USB 電視調諧器設定" + "網絡電視調諧器設定" + "USB 電視調諧器已中斷連線。" + "網絡調諧器已中斷連線。" diff --git a/usbtuner-res/values-zh-rTW/strings.xml b/usbtuner-res/values-zh-rTW/strings.xml index 802d8b73..86624ef5 100644 --- a/usbtuner-res/values-zh-rTW/strings.xml +++ b/usbtuner-res/values-zh-rTW/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "電視調諧器" "USB 電視調諧器" - "開啟" - "關閉" + "網路電視調諧器 (測試版)" "請等待處理程序完成" - "選取你的頻道來源" - "無訊號" - "無法轉到「%s」" - "無法轉台" "調諧器軟體最近已更新。請重新掃描頻道。" "前往系統音效設定開啟環繞音效即可啟用音訊" + "無法播放音訊,請改用其他電視頻道" "頻道調諧器設定" "電視調諧器設定" "USB 頻道調諧器設定" + "網路調諧器設定" "請確認你的電視已連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" "請確認 USB 調諧器已插入並連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" + "請確認你的網路調諧器已開啟電源,並連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" "繼續" "暫時不要" @@ -40,6 +38,7 @@ "要重新設定頻道嗎?" "這項動作將移除電視調諧器找到的頻道,並再次掃描新的頻道。\n\n請確認你的電視已連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" "這項動作將移除 USB 調諧器找到的頻道,並再次掃描新的頻道。\n\n請確認 USB 調諧器已插入並連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" + "這個動作將移除網路調諧器找到的頻道,並再次掃描新的頻道。\n\n請確認你的網路調諧器已開啟電源,並連接到電視訊號來源。\n\n如果你使用無線電視天線,可能需要調整天線的位置和方向,以便接收最多頻道。為達最佳效果,請將天線放在靠近窗戶的較高位置。" "繼續" "取消" @@ -54,6 +53,7 @@ "電視調諧器設定" "USB 頻道調諧器設定" + "網路頻道調諧器設定" "這可能需要幾分鐘的時間" "調諧器暫時無法使用,或是已用於錄製。" @@ -76,12 +76,15 @@ "找不到任何頻道" "掃描後並未發現任何頻道。請確認你的電視已連接到電視訊號來源。\n\n如果你使用無線電視天線,請調整天線的位置和方向。為達最佳效果,請將天線放在靠近窗戶的較高位置,然後再掃描一次。" "掃描後並未發現任何頻道。請確認 USB 調諧器已插入並連接到電視訊號來源。\n\n如果你使用無線電視天線,請調整天線的位置和方向。為達最佳效果,請將天線放在靠近窗戶的較高位置,然後再掃描一次。" + "掃描後並未發現任何頻道。請確認你的網路調諧器已開啟電源,並連接到電視訊號來源。\n\n如果你使用無線電視天線,請調整天線的位置和方向。為達最佳效果,請將天線放在靠近窗戶的較高位置,然後再掃描一次。" "重新掃描" "完成" - "掃描電視頻道" - "電視調諧器設定" - "USB 電視調諧器設定" - "USB 電視調諧器已中斷連結。" + "掃描電視頻道" + "電視調諧器設定" + "USB 電視調諧器設定" + "網路電視調諧器設定" + "USB 電視調諧器已中斷連線。" + "網路調諧器已中斷連線。" diff --git a/usbtuner-res/values-zu/strings.xml b/usbtuner-res/values-zu/strings.xml index 905e9a16..fa8df8d3 100644 --- a/usbtuner-res/values-zu/strings.xml +++ b/usbtuner-res/values-zu/strings.xml @@ -19,20 +19,18 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Ishuna ye-TV" "Ishuna ye-USB TV" - "Vuliwe" - "Valiwe" + "Ishuna yenethiwekhi ye-TV (i-BETA)" "Sicela ulinde ukuze uqede ukucubungula" - "Khetha umthombo wesiteshi sakho" - "Ayikho isignali" - "Yehlulekile ukushunela ku-%s" - "Yehlulekile ukushuna" "Isofthiwe yeshuna ibuyekezwe kamuva. Sicela uphinde uskene iziteshi." "Nika amandla umsindo ozungezile kuzilungiselelo zomsindo wesistimu" + "Ayikwazi ukudlala umsindo. Sicela uzame enye i-TV" "Ukusethwa kweshuna yesiteshi" "Ukusethwa kweshuna ye-TV" "Ukusetha kweshuna yesiteshi se-USB" + "Ukusethwa kweshuna yenethiwekhi" "Qinisekisa ukuthi i-TV yakho ixhunywe kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kungenzeka ukuthi kumele ulungise ukubekwa noma ukubheka ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, beka phezulu naseduze kwewindi." "Qinisekisa ukuthi ishuna ye-USB ixhunyiwe futhi ixhumeke kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kuzomele ulungise ukubekwa kwayo noma ukubheka kwayo ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, yibeke phezulu eduze kwewindi." + "Qinisekisa ukuthi ishuna yenethiwekhi ivuliwe yaphinde yaxhunywa kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kungenzeka ukuthi kumele ulungise ukubekwa kwayo noma indawo ebhekiwe ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, ibeke ngaphezulu naseduze kwewindi." "Qhubeka" "Hhayi manje" @@ -40,6 +38,7 @@ "Phinda uqalise ukusethwa kwesiteshi?" "Lokhu kuzosusa iziteshi ezitholwe kusukela kushuna ye-TV kuphinde kuskenele iziteshi ezintsha.\n\nQinisekisa ukuthi i-TV yakho ixhunywe kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kungenzeka ukuthi kumele ulungise ukubekwa noma ukubheka ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, beka phezulu naseduze kwewindi." "Lokhu kuzosusa iziteshi ezitholakele kusukela kushuna ye-USB kuphinde kuskenele iziteshi ezintsha futhi.\n\nQinisekisa ukuthi ishuna ye-USB ixhunyiwe futhi ixhumeke kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kuzomele ulungise ukubekwa kwayo noma ukubheka kwayo ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, yibeke ngaphezulu naseduze kwewindi." + "Lokhu kuzosusa iziteshi ezitholakele kusukela kushuna yenethiwekhi kuphinde kuskenele iziteshi ezintsha futhi.\n\nQinisekisa ukuthi ishuna yenethiwekhi ixhunyiwe futhi ixhumeke kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, kuzomele ulungise ukubekwa kwayo noma ukubheka kwayo ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, yibeke ngaphezulu naseduze kwewindi." "Qhubeka" "Khansela" @@ -54,6 +53,7 @@ "Ukusethwa kweshuna ye-TV" "Ukusetha kweshuna yesiteshi se-USB" + "Ukusethwa kweshuna yesiteshi senethiwekhi" "Lokhu kungathatha amaminithi athile" "Ishuna okwamanje ayitholakali noma isivele isetshenziswa ngokurekhodwa." @@ -76,12 +76,15 @@ "Azikho iziteshi ezitholiwe" "Ukuskena akuzange kuthole iziteshi. Qinisekisa ukuthi i-TV yakho ixhumeke kumthombo wesignali we-TV.\n\nUma usebenzisa i-antenna esemoyeni, kungenzeka ukuthi kumele ulungise ukubekwa noma ukubheka ukuze uthole iziteshi eziningi. Ukuze uthole imiphumela ehamba phambili, beka phezulu naseduze kwewindi." "Iskena asizange sithole noma yiziphi iziteshi. Qinisekisa ukuthi ishuna ye-USB ixhunyiwe futhi ixhumeke kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, lungisa ukubekwa kwayo noma ukuma kwayo. Ukuze uthole imiphumela ehamba phambili, yibeke phezulu naseduze kwewindi uphinde uskene." + "Iskena asizange sithole noma iziphi iziteshi. Qinisekisa ukuthi ishuna ye-USB ixhunyiwe futhi ixhumeke kumthombo wesignali ye-TV.\n\nUma usebenzisa i-antenna esemoyeni, lungisa ukubekwa kwayo noma ukuma kwayo. Ukuze uthole imiphumela ehamba phambili, yibeke phezulu naseduze kwewindi uphinde uskene." "Skena futhi" "Kwenziwe" - "Skenela iziteshi ze-TV" - "Ukusethwa kweshuna ye-TV" - "Ukusetha kweshuna ye-USB TV" - "Isishuni se-USB TV sinqanyuliwe." + "Skenela iziteshi ze-TV" + "Ukusethwa kweshuna ye-TV" + "Ukusetha kweshuna ye-USB TV" + "Ukusethwa kweshuna yenethiwekhi ye-TV" + "Ishuna ye-USB TV inqanyuliwe." + "Ishuna yenethiwekhi inqanyuliwe." diff --git a/usbtuner-res/values/colors.xml b/usbtuner-res/values/colors.xml index bbcc431f..873ecc8e 100644 --- a/usbtuner-res/values/colors.xml +++ b/usbtuner-res/values/colors.xml @@ -16,15 +16,9 @@ --> - #01579B - #EEEEEE - #EEEEEE - #B3EEEEEE - #EEEEEE #EEEEEE #B3EEEEEE #EEEEEE #26EEEEEE #EEEEEE - #26EEEEEE \ No newline at end of file diff --git a/usbtuner-res/values/dimens.xml b/usbtuner-res/values/dimens.xml index 0a09b062..1fe39f6c 100644 --- a/usbtuner-res/values/dimens.xml +++ b/usbtuner-res/values/dimens.xml @@ -16,41 +16,7 @@ --> - - 16dp - 16dp - - 592dp - 368dp - - 100dp - -100dp - - 56dp - 32dp - 27dp - 156dp - - 34sp - 14sp - 14sp - 8dp - - 24dp - 40dp - 27dp - 48dp - 163dp - 48dp - - 48dp - 16dp - 16dp - 14dp - 15dp - 80dp - 80dp 24dp 34sp diff --git a/usbtuner-res/values/integers.xml b/usbtuner-res/values/integers.xml index 21b438e9..65d20c67 100644 --- a/usbtuner-res/values/integers.xml +++ b/usbtuner-res/values/integers.xml @@ -16,10 +16,5 @@ --> - 2 - 10 - 8 2 - 300 - 300 \ No newline at end of file diff --git a/usbtuner-res/values/strings.xml b/usbtuner-res/values/strings.xml index e3a8586d..dd393c3e 100644 --- a/usbtuner-res/values/strings.xml +++ b/usbtuner-res/values/strings.xml @@ -18,26 +18,15 @@ TV Tuner - USB TV Tuner - - On - - Off + + Network TV Tuner (BETA) Please wait to finish processing - - Select your channel source - - No Signal - - Failed to tune to %s - - Failed to tune Tuner software has been recently updated. Please re-scan the @@ -45,12 +34,16 @@ Enable surround sound in system sound settings to enable audio + + Cannot play audio. Please try another TV Channel tuner setup TV Tuner setup USB channel tuner setup + + Network tuner setup Verify your TV is connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or @@ -61,6 +54,11 @@ to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window. + + Verify the network tuner is powered on and connected + to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its + placement or direction to receive the most channels. For best results, + place it high and near a window. @@ -82,6 +80,12 @@ to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust its placement or direction to receive the most channels. For best results, place it high and near a window. + + This will remove the channels found from the network + tuner and scan for new channels again.\n\nVerify the network tuner is powered on and + connected to a TV signal source.\n\nIf using an over-the-air antenna, you may need to adjust + its placement or direction to receive the most channels. For best results, place it high and + near a window. @@ -109,6 +113,8 @@ TV tuner setup USB channel tuner setup + + Network channel tuner setup This may take several minutes @@ -145,29 +151,41 @@ your TV is connected to a TV signal source.\n\nIf using an over-the-air antenna, adjust its placement or direction. For best results, place it high and near a window and scan again. - + The scan did not find any channels. Verify the USB tuner is plugged in and connected to a TV signal source.\n\nIf using an over-the-air antenna, adjust its placement or direction. For best results, place it high and near a window and scan again. + + The scan did not find any channels. Verify the + network tuner is powered on and connected to a TV signal source.\n\nIf using an over-the-air + antenna, adjust its placement or direction. For best results, place it high and near a + window and scan again. Scan again Done - - - Scan for TV channels - + + USB tuner set up + + Scan for TV channels + - TV Tuner setup - - USB TV Tuner setup + USB TV Tuner setup + + Network TV Tuner setup - - USB TV tuner disconnected. + + USB TV tuner disconnected. + + Network tuner disconnected. diff --git a/usbtuner-res/values/styles.xml b/usbtuner-res/values/styles.xml deleted file mode 100644 index c79319ba..00000000 --- a/usbtuner-res/values/styles.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/version.mk b/version.mk index f63f5834..6886dc07 100644 --- a/version.mk +++ b/version.mk @@ -48,13 +48,13 @@ base_version_major := 1 # Change this for each branch -base_version_minor := 11 +base_version_minor := 15 # code_version_major will overflow at 22 code_version_major := $(shell echo $$(($(base_version_major)+3))) # x86 and arm sometimes don't match. -code_version_build := 011 +code_version_build := 007 ##################################################### ##################################################### # Collect automatic version code parameters @@ -96,7 +96,7 @@ version_code_package := $(code_version_major)$(base_version_minor)$(code_version ifdef TARGET_BUILD_APPS ifneq "" "$(filter eng.%,$(BUILD_NUMBER))" git_hash := $(shell git --git-dir $(LOCAL_PATH)/.git log -n 1 --pretty=format:%h) - date_string := $(shell date +%m%d%y_%H%M%S) + date_string := $(shell date +%Y-%m-%d) version_name_package := $(base_version_major).$(base_version_minor).$(code_version_build) (eng.$(USER).$(git_hash).$(date_string)-$(base_version_arch)$(base_version_density)) else version_name_package := $(base_version_major).$(base_version_minor).$(code_version_build) ($(BUILD_NUMBER)-$(base_version_arch)$(base_version_density)) -- cgit v1.2.3 From cc8c60afeddd75ecb1e8eb6de74a16f22e9256e0 Mon Sep 17 00:00:00 2001 From: Dan Willemsen Date: Tue, 7 Nov 2017 12:46:39 -0800 Subject: Stop using custom prebuilt support-tv-provider That file is going away with the latest support drop. Use `android-support-tv-provider`, which will use the correct version of the library that matches all the other android-support-* libraries. Bug: 64723465 Test: mmma packages/apps/TV Test: tapas TV; m Change-Id: Ic250b40982a161cf3858f5c792bb3f3c37b2271e --- Android.mk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Android.mk b/Android.mk index 7970f6f1..a238b192 100644 --- a/Android.mk +++ b/Android.mk @@ -59,6 +59,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-annotations \ android-support-compat \ android-support-core-ui \ + android-support-tv-provider \ android-support-v7-palette \ android-support-v7-recyclerview \ android-support-v17-leanback \ @@ -66,7 +67,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ lib-exoplayer \ lib-exoplayer-v2 \ lib-exoplayer-v2-ext-ffmpeg \ - prebuilt-support-tv-provider \ tv-common \ @@ -121,7 +121,6 @@ LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ lib-exoplayer:libs/exoplayer.jar \ lib-exoplayer-v2:libs/exoplayer_v2.jar \ lib-exoplayer-v2-ext-ffmpeg:libs/exoplayer_v2_ext_ffmpeg.jar \ - prebuilt-support-tv-provider:../../../prebuilts/sdk/current/support/tv-provider/android-support-tv-provider.jar \ include $(BUILD_MULTI_PREBUILT) -- cgit v1.2.3 From d0b4dee914668ad32185d82146947cacade657e2 Mon Sep 17 00:00:00 2001 From: Dake Gu Date: Wed, 8 Nov 2017 15:35:34 -0800 Subject: convert LiveTv to aapt2 To avoid hardcode resource directories. Bug: NA Test: make Change-Id: If8778b8c293eb10818baa1dead38d8ac3ee30cb3 --- Android.mk | 42 ++++++++-------------- common/Android.mk | 16 +++------ .../common/ui/setup/SetupGuidedStepFragment.java | 14 +++++--- .../tv/common/ui/setup/SetupMultiPaneFragment.java | 8 +++-- .../animation/TranslationAnimationCreator.java | 2 +- 5 files changed, 33 insertions(+), 49 deletions(-) diff --git a/Android.mk b/Android.mk index a238b192..76e429b9 100644 --- a/Android.mk +++ b/Android.mk @@ -36,26 +36,23 @@ LOCAL_PRIVILEGED_MODULE := true LOCAL_SDK_VERSION := system_current LOCAL_MIN_SDK_VERSION := 23 # M + +LOCAL_USE_AAPT2 := true + LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/res \ - $(LOCAL_PATH)/usbtuner-res \ - $(LOCAL_PATH)/common/res - -ifdef TARGET_BUILD_APPS -LOCAL_RESOURCE_DIR += \ - $(TOP)/prebuilts/sdk/current/support/compat/res \ - $(TOP)/prebuilts/sdk/current/support/v17/leanback/res \ - $(TOP)/prebuilts/sdk/current/support/v7/recyclerview/res -else # !TARGET_BUILD_APPS -LOCAL_RESOURCE_DIR += \ - $(TOP)/frameworks/support/compat/res \ - $(TOP)/frameworks/support/v17/leanback/res \ - $(TOP)/frameworks/support/v7/recyclerview/res -endif + $(LOCAL_PATH)/usbtuner-res + LOCAL_SRC_FILES += \ src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl LOCAL_STATIC_JAVA_LIBRARIES := \ + icu4j-usbtuner \ + lib-exoplayer \ + lib-exoplayer-v2 \ + lib-exoplayer-v2-ext-ffmpeg + +LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-annotations \ android-support-compat \ android-support-core-ui \ @@ -63,23 +60,11 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-v7-palette \ android-support-v7-recyclerview \ android-support-v17-leanback \ - icu4j-usbtuner \ - lib-exoplayer \ - lib-exoplayer-v2 \ - lib-exoplayer-v2-ext-ffmpeg \ - tv-common \ - - - - + tv-common LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked -LOCAL_AAPT_FLAGS += --auto-add-overlay \ - --extra-packages android.support.compat \ - --extra-packages android.support.v7.recyclerview \ - --extra-packages android.support.v17.leanback \ - --extra-packages com.android.tv.common \ +LOCAL_AAPT_FLAGS += \ --version-name "$(version_name_package)" \ --version-code $(version_code_package) \ @@ -127,3 +112,4 @@ include $(BUILD_MULTI_PREBUILT) include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/common/Android.mk b/common/Android.mk index d632597f..ff19392e 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -9,25 +9,17 @@ LOCAL_MODULE_CLASS := STATIC_JAVA_LIBRARIES LOCAL_MODULE_TAGS := optional LOCAL_SDK_VERSION := system_current -LOCAL_RESOURCE_DIR := \ - $(TOP)/prebuilts/sdk/current/support/compat/res \ - $(TOP)/prebuilts/sdk/current/support/v7/recyclerview/res \ - $(TOP)/prebuilts/sdk/current/support/v17/leanback/res \ - $(LOCAL_PATH)/res \ +LOCAL_USE_AAPT2 := true -LOCAL_STATIC_JAVA_LIBRARIES := \ +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_SHARED_ANDROID_LIBRARIES := \ android-support-annotations \ android-support-compat \ android-support-core-ui \ android-support-v7-recyclerview \ android-support-v17-leanback \ -LOCAL_AAPT_FLAGS := --auto-add-overlay \ - --extra-packages android.support.compat \ - --extra-packages android.support.v7.recyclerview \ - --extra-packages android.support.v17.leanback \ - - include $(LOCAL_PATH)/buildconfig.mk include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java index 88159da9..7bce9c27 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java @@ -45,9 +45,11 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); Bundle arguments = getArguments(); - view.findViewById(R.id.action_fragment_root).setPadding(0, 0, 0, 0); + view.findViewById(android.support.v17.leanback.R.id.action_fragment_root) + .setPadding(0, 0, 0, 0); LinearLayout.LayoutParams guidanceLayoutParams = (LinearLayout.LayoutParams) - view.findViewById(R.id.content_fragment).getLayoutParams(); + view.findViewById(android.support.v17.leanback.R.id.content_fragment) + .getLayoutParams(); guidanceLayoutParams.weight = 0; if (arguments != null && arguments.getBoolean(KEY_THREE_PANE, false)) { // Content fragment. @@ -56,7 +58,7 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { int doneButtonWidth = getResources().getDimensionPixelOffset( R.dimen.setup_done_button_container_width); // Guided actions list - View list = view.findViewById(R.id.guidedactions_list); + View list = view.findViewById(android.support.v17.leanback.R.id.guidedactions_list); MarginLayoutParams marginLayoutParams = (MarginLayoutParams) list.getLayoutParams(); // Use content view to check layout direction while view is being created. if (getResources().getConfiguration().getLayoutDirection() @@ -77,10 +79,12 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { gridView.setWindowAlignmentOffset(offset); gridView.setWindowAlignmentOffsetPercent(0); gridView.setItemAlignmentOffsetPercent(0); - ((ViewGroup) view.findViewById(R.id.guidedactions_list)).setTransitionGroup(false); + ((ViewGroup) view.findViewById(android.support.v17.leanback.R.id.guidedactions_list)) + .setTransitionGroup(false); // Needed for the shared element transition. // content_frame is defined in leanback. - ViewGroup group = (ViewGroup) view.findViewById(R.id.content_frame); + ViewGroup group = (ViewGroup) view.findViewById( + android.support.v17.leanback.R.id.content_frame); group.setClipChildren(false); group.setClipToPadding(false); return view; diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java index b9ad4657..f5c2bf26 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java @@ -102,11 +102,13 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { @Override protected int[] getParentIdsForDelay() { - return new int[] {R.id.content_fragment, R.id.guidedactions_list}; + return new int[] {android.support.v17.leanback.R.id.content_fragment, + android.support.v17.leanback.R.id.guidedactions_list}; } @Override public int[] getSharedElementIds() { - return new int[] {R.id.action_fragment_background, R.id.done_button_container}; + return new int[] {android.support.v17.leanback.R.id.action_fragment_background, + R.id.done_button_container}; } -} \ No newline at end of file +} diff --git a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java index 99b8811a..7705f7a7 100644 --- a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java +++ b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java @@ -9,7 +9,7 @@ import android.transition.Transition; import android.transition.TransitionValues; import android.view.View; -import com.android.tv.common.R; +import android.support.v17.leanback.R; /** * This class is used by Slide and Explode to create an animator that goes from the start position -- cgit v1.2.3 From 278080e13e501c889ac3fc0b8d5cd4cdc799a634 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Fri, 10 Nov 2017 18:50:09 +0000 Subject: Fix import of android-support-annotations Bug: 69161825 Change-Id: I07d3ea892cb7f048d056b1a32705acce3ae3e77f --- Android.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Android.mk b/Android.mk index 76e429b9..07f17e68 100644 --- a/Android.mk +++ b/Android.mk @@ -50,10 +50,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ icu4j-usbtuner \ lib-exoplayer \ lib-exoplayer-v2 \ - lib-exoplayer-v2-ext-ffmpeg + lib-exoplayer-v2-ext-ffmpeg \ + android-support-annotations LOCAL_STATIC_ANDROID_LIBRARIES := \ - android-support-annotations \ android-support-compat \ android-support-core-ui \ android-support-tv-provider \ -- cgit v1.2.3 From 91074e9bb05a32d6a1a7cc1b3720ccbe59fe73b9 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 13 Nov 2017 20:31:25 +0000 Subject: Fix import of android-support-annotations Bug: 69161825 Change-Id: I5942ec4efd5aadb71d0ab765170d274c52d6a332 --- common/Android.mk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/Android.mk b/common/Android.mk index ff19392e..7d674a0c 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -13,8 +13,10 @@ LOCAL_USE_AAPT2 := true LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_JAVA_LIBRARIES := \ + android-support-annotations + LOCAL_SHARED_ANDROID_LIBRARIES := \ - android-support-annotations \ android-support-compat \ android-support-core-ui \ android-support-v7-recyclerview \ -- cgit v1.2.3 From e506854dbc4919bf79ca4c705a04d6e225704d3e Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Fri, 17 Nov 2017 11:24:39 -0800 Subject: @IntDef is switching from long to int values This cast will be necessary when the @IntDef annotation values changes type from long to int, and the cast is safe because the value is itself not used at all (this is a source retention annotation, and the static analyzer is using the field reference itself, not the value -- that's the whole point of this annotation.) Test: The compiler Change-Id: I5f66e03b62670c4b73ec0339fe63f55a1a76e5d5 --- src/com/android/tv/data/PreviewDataManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java index 01a58520..6311a870 100644 --- a/src/com/android/tv/data/PreviewDataManager.java +++ b/src/com/android/tv/data/PreviewDataManager.java @@ -60,7 +60,7 @@ public class PreviewDataManager { * Invalid preview channel ID. */ public static final long INVALID_PREVIEW_CHANNEL_ID = -1; - @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) + @IntDef({(int)TYPE_DEFAULT_PREVIEW_CHANNEL, (int)TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) @Retention(RetentionPolicy.SOURCE) public @interface PreviewChannelType{} -- cgit v1.2.3 From eb190dc7fdd9e349f50084b10c655c135befdcfb Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Wed, 13 Dec 2017 13:14:07 +0000 Subject: Add android.test.runner.stubs dependency In preparation for the removal of the non-junit classes in the android.test.runner library from the android.jar this adds a dependency on android.test.runner.stubs to ensure this code will continue to compile. The following change descriptions were generated automatically and so may be a little repetitive. They are provided to give the reviewer enough information to check the comments match what has actually been changed and check the reasoning behind the changes. * tests/unit/Android.mk Added 'android.test.runner.stubs' to LOCAL_JAVA_LIBRARIES because TVUnitTests's will needs them in order to compile once its classes are removed from the system_current SDK on which it currently depends. Bug: 30188076 Test: make checkbuild Change-Id: I6de67f924f16d7450098ecf19c21d5ce2e0c73fd --- tests/unit/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk index 3632fe94..cb3af303 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -12,6 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target \ tv-test-common \ +LOCAL_JAVA_LIBRARIES := android.test.runner.stubs LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res -- cgit v1.2.3 From 1726129279b2e7f363a41434582a397d28d7ca8e Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 15 Dec 2017 07:22:38 +0000 Subject: Add android.test.base/stubs dependency In preparation for the removal of the non-junit classes in the android.test.base library from the android.jar this adds a dependency on android.test.base/stubs to ensure this code will continue to compile. The following change descriptions were generated automatically and so may be a little repetitive. They are provided to give the reviewer enough information to check the comments match what has actually been changed and check the reasoning behind the changes. * tests/func/Android.mk Added 'android.test.base.stubs' to LOCAL_JAVA_LIBRARIES because TVFuncTests's will need them in order to compile once its classes are removed from the current SDK on which it currently depends. * tests/jank/Android.mk Added 'android.test.base.stubs' to LOCAL_JAVA_LIBRARIES because TVJankTests's will need them in order to compile once its classes are removed from the current SDK on which it currently depends. * tests/unit/Android.mk Added 'android.test.base.stubs' to LOCAL_JAVA_LIBRARIES because TVUnitTests's will need them in order to compile once its classes are removed from the system_current SDK on which it currently depends. Bug: 30188076 Test: make dist Change-Id: Iae562276a1d46996993a4c3b7c1952f7403e71d2 --- tests/func/Android.mk | 2 ++ tests/jank/Android.mk | 2 ++ tests/unit/Android.mk | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/func/Android.mk b/tests/func/Android.mk index e89ba25b..ea763a2a 100644 --- a/tests/func/Android.mk +++ b/tests/func/Android.mk @@ -14,6 +14,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ tv-test-common \ ub-uiautomator \ +LOCAL_JAVA_LIBRARIES := android.test.base.stubs + LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_SDK_VERSION := current diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk index b71ea1b7..aafe9118 100644 --- a/tests/jank/Android.mk +++ b/tests/jank/Android.mk @@ -15,6 +15,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ ub-janktesthelper \ ub-uiautomator \ +LOCAL_JAVA_LIBRARIES := android.test.base.stubs + LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_SDK_VERSION := current diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk index cb3af303..109a47bc 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -12,7 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target \ tv-test-common \ -LOCAL_JAVA_LIBRARIES := android.test.runner.stubs +LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res -- cgit v1.2.3 From 40147c1b15f05a896aed4a9e277aca9430610134 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 15 Dec 2017 16:17:05 +0000 Subject: Add android.test.mock/stubs dependency In preparation for the removal of the android.test.mock classes from the android.jar this adds dependencies on android.test.mock/stubs to ensure this code will continue to compile. The following change descriptions were generated automatically and so may be a little repetitive. They are provided to give the reviewer enough information to check the comments match what has actually been changed and check the reasoning behind the changes. * tests/unit/Android.mk Added 'android.test.mock.stubs' to LOCAL_JAVA_LIBRARIES because TVUnitTests's will need them in order to compile once its classes are removed from the system_current SDK on which it currently depends. Bug: 30188076 Test: make dist Change-Id: I24cce1fbd27753c18808bd6828386cde4b1b1e98 --- tests/unit/Android.mk | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk index 109a47bc..c6ecd532 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -12,7 +12,11 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target \ tv-test-common \ -LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs +LOCAL_JAVA_LIBRARIES := \ + android.test.runner.stubs \ + android.test.base.stubs \ + android.test.mock.stubs \ + LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res -- cgit v1.2.3 From 95961816a768da387f0b5523cf4363ace2044089 Mon Sep 17 00:00:00 2001 From: Nick Chalko Date: Fri, 22 Dec 2017 12:03:00 -0800 Subject: Format all java files. Change-Id: I238348d30afadf4d4ced9ca37be7de13d70fb5d0 Bug: 69474774 --- .../com/android/tv/common/AutoCloseableUtils.java | 6 +- .../android/tv/common/BooleanSystemProperty.java | 16 +- .../src/com/android/tv/common/CollectionUtils.java | 34 +- .../com/android/tv/common/MemoryManageable.java | 9 +- .../android/tv/common/SharedPreferencesUtils.java | 34 +- .../com/android/tv/common/SoftPreconditions.java | 46 +- .../com/android/tv/common/TvCommonConstants.java | 35 +- .../src/com/android/tv/common/TvCommonUtils.java | 6 +- .../android/tv/common/TvContentRatingCache.java | 33 +- common/src/com/android/tv/common/WeakHandler.java | 9 +- .../tv/common/annotation/UsedByReflection.java | 3 +- .../android/tv/common/feature/CommonFeatures.java | 13 +- .../android/tv/common/feature/EngOnlyFeature.java | 7 +- .../src/com/android/tv/common/feature/Feature.java | 13 +- .../android/tv/common/feature/FeatureUtils.java | 59 +- .../android/tv/common/feature/GServiceFeature.java | 5 +- .../tv/common/feature/PackageVersionFeature.java | 7 +- .../android/tv/common/feature/PropertyFeature.java | 10 +- common/src/com/android/tv/common/feature/Sdk.java | 4 +- .../common/feature/SharedPreferencesFeature.java | 15 +- .../tv/common/feature/SystemAppFeature.java | 6 +- .../android/tv/common/feature/TestableFeature.java | 26 +- .../tv/common/recording/RecordingCapability.java | 88 +- .../tv/common/ui/setup/OnActionClickListener.java | 8 +- .../tv/common/ui/setup/SetupActionHelper.java | 51 +- .../android/tv/common/ui/setup/SetupActivity.java | 49 +- .../android/tv/common/ui/setup/SetupFragment.java | 128 ++- .../common/ui/setup/SetupGuidedStepFragment.java | 48 +- .../tv/common/ui/setup/SetupMultiPaneFragment.java | 57 +- .../ui/setup/animation/FadeAndShortSlide.java | 127 ++- .../ui/setup/animation/SetupAnimationHelper.java | 96 +- .../animation/TranslationAnimationCreator.java | 27 +- .../classes/core/src/com/ibm/icu/text/SCSU.java | 505 ++++++--- .../src/com/ibm/icu/text/UnicodeDecompressor.java | 1174 ++++++++++++-------- src/com/android/exoplayer/MediaFormatUtil.java | 48 +- .../android/exoplayer/MediaSoftwareCodecUtil.java | 73 +- src/com/android/exoplayer/text/SubtitleView.java | 31 +- .../exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java | 3 +- .../exoplayer2/ext/ffmpeg/FfmpegLibrary.java | 23 +- src/com/android/tv/ApplicationSingletons.java | 4 +- src/com/android/tv/AudioManagerHelper.java | 50 +- src/com/android/tv/ChannelTuner.java | 104 +- src/com/android/tv/Features.java | 63 +- src/com/android/tv/InputSessionManager.java | 280 ++--- src/com/android/tv/LauncherActivity.java | 36 +- src/com/android/tv/MainActivity.java | 1067 ++++++++++-------- src/com/android/tv/MainActivityWrapper.java | 54 +- src/com/android/tv/MediaSessionWrapper.java | 100 +- src/com/android/tv/SelectInputActivity.java | 45 +- src/com/android/tv/SetupPassthroughActivity.java | 95 +- src/com/android/tv/TimeShiftManager.java | 576 +++++----- src/com/android/tv/TvActivity.java | 1 - src/com/android/tv/TvApplication.java | 243 ++-- src/com/android/tv/TvOptionsManager.java | 52 +- src/com/android/tv/analytics/Analytics.java | 14 +- .../android/tv/analytics/ConfigurationInfo.java | 4 +- src/com/android/tv/analytics/HasTrackerLabel.java | 4 +- .../tv/analytics/SendChannelStatusRunnable.java | 74 +- .../tv/analytics/SendConfigInfoRunnable.java | 10 +- src/com/android/tv/analytics/StubAnalytics.java | 7 +- src/com/android/tv/analytics/StubTracker.java | 71 +- src/com/android/tv/analytics/Tracker.java | 60 +- src/com/android/tv/config/ConfigKeys.java | 8 +- .../android/tv/config/DefaultConfigManager.java | 13 +- src/com/android/tv/config/RemoteConfig.java | 20 +- src/com/android/tv/config/RemoteConfigFeature.java | 5 +- src/com/android/tv/customization/CustomAction.java | 23 +- .../tv/customization/TvCustomizationManager.java | 90 +- src/com/android/tv/data/BaseProgram.java | 144 +-- src/com/android/tv/data/Channel.java | 236 ++-- src/com/android/tv/data/ChannelDataManager.java | 298 +++-- src/com/android/tv/data/ChannelLogoFetcher.java | 50 +- src/com/android/tv/data/ChannelNumber.java | 34 +- src/com/android/tv/data/DisplayMode.java | 7 +- src/com/android/tv/data/GenreItems.java | 23 +- src/com/android/tv/data/InternalDataUtils.java | 34 +- src/com/android/tv/data/Lineup.java | 57 +- .../tv/data/OnCurrentProgramUpdatedListener.java | 4 +- src/com/android/tv/data/ParcelableList.java | 40 +- src/com/android/tv/data/PreviewDataManager.java | 192 ++-- src/com/android/tv/data/PreviewProgramContent.java | 95 +- src/com/android/tv/data/Program.java | 312 +++--- src/com/android/tv/data/ProgramDataManager.java | 306 ++--- src/com/android/tv/data/StreamInfo.java | 13 +- src/com/android/tv/data/TvInputNewComparator.java | 6 +- src/com/android/tv/data/WatchedHistoryManager.java | 92 +- src/com/android/tv/data/epg/EpgFetchHelper.java | 96 +- src/com/android/tv/data/epg/EpgFetcher.java | 135 +-- src/com/android/tv/data/epg/EpgReader.java | 20 +- src/com/android/tv/data/epg/StubEpgReader.java | 13 +- .../tv/dialog/DvrHistoryDialogFragment.java | 118 +- .../tv/dialog/FullscreenDialogFragment.java | 25 +- .../android/tv/dialog/HalfSizedDialogFragment.java | 48 +- src/com/android/tv/dialog/PinDialogFragment.java | 321 +++--- .../tv/dialog/RecentlyWatchedDialogFragment.java | 115 +- .../tv/dialog/SafeDismissDialogFragment.java | 12 +- src/com/android/tv/dialog/WebDialogFragment.java | 20 +- src/com/android/tv/dvr/BaseDvrDataManager.java | 84 +- src/com/android/tv/dvr/DvrDataManager.java | 149 +-- src/com/android/tv/dvr/DvrDataManagerImpl.java | 402 ++++--- src/com/android/tv/dvr/DvrManager.java | 370 +++--- src/com/android/tv/dvr/DvrScheduleManager.java | 584 +++++----- .../android/tv/dvr/DvrStorageStatusManager.java | 109 +- .../android/tv/dvr/DvrWatchedPositionManager.java | 59 +- src/com/android/tv/dvr/WritableDvrDataManager.java | 33 +- src/com/android/tv/dvr/data/IdGenerator.java | 20 +- src/com/android/tv/dvr/data/RecordedProgram.java | 469 ++++---- .../android/tv/dvr/data/ScheduledRecording.java | 502 ++++----- .../android/tv/dvr/data/SeasonEpisodeNumber.java | 27 +- src/com/android/tv/dvr/data/SeriesInfo.java | 28 +- src/com/android/tv/dvr/data/SeriesRecording.java | 399 ++++--- .../android/tv/dvr/provider/AsyncDvrDbTask.java | 58 +- src/com/android/tv/dvr/provider/DvrContract.java | 67 +- .../android/tv/dvr/provider/DvrDatabaseHelper.java | 264 +++-- src/com/android/tv/dvr/provider/DvrDbSync.java | 187 ++-- .../tv/dvr/provider/EpisodicProgramLoadTask.java | 150 +-- .../android/tv/dvr/recorder/ConflictChecker.java | 85 +- .../tv/dvr/recorder/DvrRecordingService.java | 46 +- .../tv/dvr/recorder/DvrStartRecordingReceiver.java | 5 +- .../tv/dvr/recorder/InputTaskScheduler.java | 160 +-- .../tv/dvr/recorder/RecordingScheduler.java | 99 +- src/com/android/tv/dvr/recorder/RecordingTask.java | 290 ++--- .../tv/dvr/recorder/ScheduledProgramReaper.java | 11 +- .../tv/dvr/recorder/SeriesRecordingScheduler.java | 249 +++-- src/com/android/tv/dvr/ui/BigArguments.java | 20 +- .../ui/ChangeImageTransformWithScaledParent.java | 24 +- .../tv/dvr/ui/DvrAlreadyRecordedFragment.java | 43 +- .../tv/dvr/ui/DvrAlreadyScheduledFragment.java | 53 +- .../ui/DvrChannelRecordDurationOptionFragment.java | 51 +- src/com/android/tv/dvr/ui/DvrConflictFragment.java | 155 +-- .../android/tv/dvr/ui/DvrGuidedActionsStylist.java | 15 +- .../android/tv/dvr/ui/DvrGuidedStepFragment.java | 85 +- .../tv/dvr/ui/DvrHalfSizedDialogFragment.java | 54 +- .../dvr/ui/DvrInsufficientSpaceErrorFragment.java | 79 +- .../tv/dvr/ui/DvrMissingStorageErrorFragment.java | 24 +- .../tv/dvr/ui/DvrPrioritySettingsFragment.java | 76 +- src/com/android/tv/dvr/ui/DvrScheduleFragment.java | 104 +- .../tv/dvr/ui/DvrSeriesDeletionActivity.java | 9 +- .../tv/dvr/ui/DvrSeriesDeletionFragment.java | 118 +- .../dvr/ui/DvrSeriesScheduledDialogActivity.java | 13 +- .../tv/dvr/ui/DvrSeriesScheduledFragment.java | 115 +- .../tv/dvr/ui/DvrSeriesSettingsActivity.java | 28 +- .../tv/dvr/ui/DvrSeriesSettingsFragment.java | 133 ++- .../tv/dvr/ui/DvrStopRecordingFragment.java | 53 +- .../ui/DvrStopSeriesRecordingDialogFragment.java | 9 +- .../tv/dvr/ui/DvrStopSeriesRecordingFragment.java | 35 +- src/com/android/tv/dvr/ui/DvrUiHelper.java | 502 +++++---- src/com/android/tv/dvr/ui/FadeBackground.java | 17 +- src/com/android/tv/dvr/ui/SortedArrayAdapter.java | 34 +- .../tv/dvr/ui/TrackedGuidedStepFragment.java | 1 - .../tv/dvr/ui/browse/ActionPresenterSelector.java | 34 +- .../ui/browse/CurrentRecordingDetailsFragment.java | 22 +- .../android/tv/dvr/ui/browse/DetailsContent.java | 135 +-- .../tv/dvr/ui/browse/DetailsContentPresenter.java | 188 ++-- .../dvr/ui/browse/DetailsViewBackgroundHelper.java | 16 +- .../tv/dvr/ui/browse/DvrBrowseActivity.java | 7 +- .../tv/dvr/ui/browse/DvrBrowseFragment.java | 241 ++-- .../tv/dvr/ui/browse/DvrDetailsActivity.java | 39 +- .../tv/dvr/ui/browse/DvrDetailsFragment.java | 141 +-- .../android/tv/dvr/ui/browse/DvrItemPresenter.java | 41 +- .../tv/dvr/ui/browse/DvrListRowPresenter.java | 1 - .../tv/dvr/ui/browse/FullScheduleCardHolder.java | 10 +- .../dvr/ui/browse/FullSchedulesCardPresenter.java | 33 +- .../ui/browse/RecordedProgramDetailsFragment.java | 67 +- .../tv/dvr/ui/browse/RecordedProgramPresenter.java | 51 +- .../tv/dvr/ui/browse/RecordingCardView.java | 90 +- .../tv/dvr/ui/browse/RecordingDetailsFragment.java | 21 +- .../browse/ScheduledRecordingDetailsFragment.java | 25 +- .../dvr/ui/browse/ScheduledRecordingPresenter.java | 90 +- .../ui/browse/SeriesRecordingDetailsFragment.java | 100 +- .../tv/dvr/ui/browse/SeriesRecordingPresenter.java | 39 +- .../tv/dvr/ui/list/BaseDvrSchedulesFragment.java | 77 +- .../tv/dvr/ui/list/DvrSchedulesActivity.java | 43 +- .../tv/dvr/ui/list/DvrSchedulesFocusView.java | 11 +- .../tv/dvr/ui/list/DvrSchedulesFragment.java | 11 +- .../tv/dvr/ui/list/DvrSeriesSchedulesFragment.java | 112 +- .../android/tv/dvr/ui/list/EpisodicProgramRow.java | 19 +- src/com/android/tv/dvr/ui/list/ScheduleRow.java | 99 +- .../android/tv/dvr/ui/list/ScheduleRowAdapter.java | 168 ++- .../tv/dvr/ui/list/ScheduleRowPresenter.java | 567 +++++----- .../android/tv/dvr/ui/list/SchedulesHeaderRow.java | 63 +- .../dvr/ui/list/SchedulesHeaderRowPresenter.java | 196 ++-- .../tv/dvr/ui/list/SeriesScheduleRowAdapter.java | 61 +- .../tv/dvr/ui/list/SeriesScheduleRowPresenter.java | 71 +- .../tv/dvr/ui/playback/DvrPlaybackActivity.java | 15 +- .../dvr/ui/playback/DvrPlaybackCardPresenter.java | 14 +- .../dvr/ui/playback/DvrPlaybackControlHelper.java | 132 +-- .../ui/playback/DvrPlaybackMediaSessionHelper.java | 161 +-- .../ui/playback/DvrPlaybackOverlayFragment.java | 184 +-- .../dvr/ui/playback/DvrPlaybackSideFragment.java | 59 +- src/com/android/tv/dvr/ui/playback/DvrPlayer.java | 361 +++--- src/com/android/tv/experiments/ExperimentFlag.java | 16 +- src/com/android/tv/experiments/Experiments.java | 11 +- src/com/android/tv/guide/GenreListAdapter.java | 59 +- src/com/android/tv/guide/GuideUtils.java | 42 +- src/com/android/tv/guide/ProgramGrid.java | 40 +- src/com/android/tv/guide/ProgramGuide.java | 669 ++++++----- src/com/android/tv/guide/ProgramItemView.java | 320 +++--- src/com/android/tv/guide/ProgramListAdapter.java | 12 +- src/com/android/tv/guide/ProgramManager.java | 355 +++--- src/com/android/tv/guide/ProgramRow.java | 127 +-- src/com/android/tv/guide/ProgramTableAdapter.java | 276 +++-- src/com/android/tv/guide/TimeListAdapter.java | 13 +- src/com/android/tv/guide/TimelineGridView.java | 17 +- src/com/android/tv/guide/TimelineRow.java | 13 +- .../android/tv/license/LicenseDialogFragment.java | 1 - .../android/tv/license/LicenseSideFragment.java | 2 - src/com/android/tv/license/LicenseUtils.java | 17 +- src/com/android/tv/license/Licenses.java | 3 +- src/com/android/tv/menu/ActionCardView.java | 7 +- src/com/android/tv/menu/AppLinkCardView.java | 114 +- src/com/android/tv/menu/BaseCardView.java | 132 ++- src/com/android/tv/menu/ChannelCardView.java | 25 +- .../android/tv/menu/ChannelsPosterPrefetcher.java | 50 +- src/com/android/tv/menu/ChannelsRow.java | 53 +- src/com/android/tv/menu/ChannelsRowAdapter.java | 111 +- src/com/android/tv/menu/ChannelsRowItem.java | 32 +- .../tv/menu/CustomizableOptionsRowAdapter.java | 11 +- src/com/android/tv/menu/IMenuView.java | 23 +- src/com/android/tv/menu/ItemListRow.java | 22 +- src/com/android/tv/menu/ItemListRowView.java | 47 +- src/com/android/tv/menu/Menu.java | 117 +- src/com/android/tv/menu/MenuAction.java | 51 +- src/com/android/tv/menu/MenuLayoutManager.java | 536 +++++---- src/com/android/tv/menu/MenuRow.java | 72 +- src/com/android/tv/menu/MenuRowFactory.java | 57 +- src/com/android/tv/menu/MenuRowView.java | 63 +- src/com/android/tv/menu/MenuUpdater.java | 76 +- src/com/android/tv/menu/MenuView.java | 64 +- src/com/android/tv/menu/OptionsRowAdapter.java | 47 +- .../android/tv/menu/PartnerOptionsRowAdapter.java | 5 +- src/com/android/tv/menu/PlayControlsButton.java | 64 +- src/com/android/tv/menu/PlayControlsRow.java | 13 +- src/com/android/tv/menu/PlayControlsRowView.java | 451 ++++---- src/com/android/tv/menu/PlaybackProgressBar.java | 22 +- src/com/android/tv/menu/SimpleCardView.java | 4 +- src/com/android/tv/menu/TvOptionsRowAdapter.java | 32 +- .../android/tv/onboarding/NewSourcesFragment.java | 34 +- .../android/tv/onboarding/OnboardingActivity.java | 147 +-- .../tv/onboarding/SetupSourcesFragment.java | 215 ++-- src/com/android/tv/onboarding/WelcomeFragment.java | 1113 ++++++++++--------- .../tv/parental/ContentRatingLevelPolicy.java | 22 +- .../android/tv/parental/ContentRatingSystem.java | 87 +- .../android/tv/parental/ContentRatingsManager.java | 16 +- .../android/tv/parental/ContentRatingsParser.java | 158 ++- .../tv/parental/ParentalControlSettings.java | 90 +- src/com/android/tv/perf/EventNames.java | 10 +- src/com/android/tv/perf/PerformanceMonitor.java | 5 +- .../tv/receiver/AudioCapabilitiesReceiver.java | 24 +- .../android/tv/receiver/BootCompletedReceiver.java | 14 +- src/com/android/tv/receiver/GlobalKeyReceiver.java | 12 +- .../tv/receiver/PackageIntentsReceiver.java | 5 +- .../tv/recommendation/ChannelPreviewUpdater.java | 136 ++- .../android/tv/recommendation/ChannelRecord.java | 3 - .../recommendation/FavoriteChannelEvaluator.java | 5 +- .../tv/recommendation/NotificationService.java | 221 ++-- .../tv/recommendation/RecentChannelEvaluator.java | 11 +- .../recommendation/RecommendationDataManager.java | 283 +++-- src/com/android/tv/recommendation/Recommender.java | 79 +- .../RecordedProgramPreviewUpdater.java | 100 +- .../tv/recommendation/RoutineWatchEvaluator.java | 88 +- src/com/android/tv/search/DataManagerSearch.java | 128 ++- src/com/android/tv/search/LocalSearchProvider.java | 116 +- .../tv/search/ProgramGuideSearchFragment.java | 168 +-- src/com/android/tv/search/SearchInterface.java | 11 +- src/com/android/tv/search/TvProviderSearch.java | 277 +++-- src/com/android/tv/setup/SystemSetupActivity.java | 96 +- .../android/tv/tuner/ChannelScanFileParser.java | 37 +- src/com/android/tv/tuner/DvbDeviceAccessor.java | 31 +- src/com/android/tv/tuner/DvbTunerHal.java | 24 +- src/com/android/tv/tuner/TunerHal.java | 82 +- src/com/android/tv/tuner/TunerInputController.java | 120 +- .../android/tv/tuner/TunerPreferenceProvider.java | 57 +- src/com/android/tv/tuner/TunerPreferences.java | 130 +-- src/com/android/tv/tuner/cc/CaptionLayout.java | 17 +- .../android/tv/tuner/cc/CaptionTrackRenderer.java | 14 +- .../android/tv/tuner/cc/CaptionWindowLayout.java | 122 +- src/com/android/tv/tuner/cc/Cea708Parser.java | 638 ++++++----- src/com/android/tv/tuner/data/Cea708Data.java | 83 +- src/com/android/tv/tuner/data/PsiData.java | 19 +- src/com/android/tv/tuner/data/PsipData.java | 161 ++- src/com/android/tv/tuner/data/TunerChannel.java | 162 +-- .../tuner/exoplayer/Cea708TextTrackRenderer.java | 69 +- .../exoplayer/ExoPlayerExtractorsFactory.java | 12 +- .../tuner/exoplayer/ExoPlayerSampleExtractor.java | 264 +++-- .../tv/tuner/exoplayer/FileSampleExtractor.java | 29 +- .../android/tv/tuner/exoplayer/MpegTsPlayer.java | 232 ++-- .../tv/tuner/exoplayer/MpegTsRendererBuilder.java | 36 +- .../tv/tuner/exoplayer/MpegTsSampleExtractor.java | 92 +- .../tv/tuner/exoplayer/MpegTsSampleSource.java | 5 +- .../tuner/exoplayer/MpegTsVideoTrackRenderer.java | 52 +- .../tv/tuner/exoplayer/SampleExtractor.java | 83 +- .../tv/tuner/exoplayer/audio/AudioClock.java | 43 +- .../tv/tuner/exoplayer/audio/AudioDecoder.java | 1 - .../tuner/exoplayer/audio/AudioTrackMonitor.java | 37 +- .../tuner/exoplayer/audio/AudioTrackWrapper.java | 10 +- .../exoplayer/audio/MediaCodecAudioDecoder.java | 2 - .../audio/MpegTsDefaultAudioTrackRenderer.java | 178 +-- .../audio/MpegTsMediaCodecAudioTrackRenderer.java | 22 +- .../tv/tuner/exoplayer/buffer/BufferManager.java | 206 ++-- .../tuner/exoplayer/buffer/DvrStorageManager.java | 43 +- .../exoplayer/buffer/RecordingSampleBuffer.java | 86 +- .../tv/tuner/exoplayer/buffer/SampleChunk.java | 133 ++- .../exoplayer/buffer/SampleChunkIoHelper.java | 101 +- .../tv/tuner/exoplayer/buffer/SamplePool.java | 18 +- .../tv/tuner/exoplayer/buffer/SampleQueue.java | 5 +- .../tuner/exoplayer/buffer/SimpleSampleBuffer.java | 21 +- .../exoplayer/buffer/TrickplayStorageManager.java | 82 +- .../exoplayer/ffmpeg/FfmpegDecoderClient.java | 78 +- .../exoplayer/ffmpeg/FfmpegDecoderService.java | 11 +- src/com/android/tv/tuner/layout/ScaledLayout.java | 124 ++- .../tv/tuner/setup/ConnectionTypeFragment.java | 27 +- .../android/tv/tuner/setup/PostalCodeFragment.java | 21 +- src/com/android/tv/tuner/setup/ScanFragment.java | 167 +-- .../android/tv/tuner/setup/ScanResultFragment.java | 42 +- .../android/tv/tuner/setup/TunerSetupActivity.java | 191 ++-- .../android/tv/tuner/setup/WelcomeFragment.java | 37 +- .../android/tv/tuner/source/FileTsStreamer.java | 63 +- src/com/android/tv/tuner/source/TsDataSource.java | 13 +- .../tv/tuner/source/TsDataSourceManager.java | 47 +- .../android/tv/tuner/source/TsStreamWriter.java | 33 +- src/com/android/tv/tuner/source/TsStreamer.java | 13 +- .../android/tv/tuner/source/TunerTsStreamer.java | 72 +- .../tv/tuner/source/TunerTsStreamerManager.java | 63 +- src/com/android/tv/tuner/ts/SectionParser.java | 664 ++++++++--- src/com/android/tv/tuner/ts/TsParser.java | 335 +++--- .../tv/tuner/tvinput/ChannelDataManager.java | 345 +++--- .../android/tv/tuner/tvinput/EventDetector.java | 311 +++--- .../tv/tuner/tvinput/FileSourceEventDetector.java | 280 ++--- .../tv/tuner/tvinput/PlaybackBufferListener.java | 8 +- src/com/android/tv/tuner/tvinput/TunerDebug.java | 15 +- .../tv/tuner/tvinput/TunerRecordingSession.java | 13 +- .../tuner/tvinput/TunerRecordingSessionWorker.java | 317 +++--- src/com/android/tv/tuner/tvinput/TunerSession.java | 167 +-- .../tv/tuner/tvinput/TunerSessionWorker.java | 1110 +++++++++--------- .../tuner/tvinput/TunerStorageCleanUpService.java | 33 +- .../tv/tuner/tvinput/TunerTvInputService.java | 22 +- src/com/android/tv/tuner/util/ByteArrayBuffer.java | 19 +- src/com/android/tv/tuner/util/ConvertUtils.java | 6 +- .../android/tv/tuner/util/GlobalSettingsUtils.java | 6 +- src/com/android/tv/tuner/util/Ints.java | 4 +- src/com/android/tv/tuner/util/PostalCodeUtils.java | 18 +- src/com/android/tv/tuner/util/StatusTextUtils.java | 64 +- .../tv/tuner/util/SystemPropertiesProxy.java | 26 +- .../android/tv/tuner/util/TisConfiguration.java | 6 +- .../android/tv/tuner/util/TunerInputInfoUtils.java | 16 +- src/com/android/tv/ui/AppLayerTvView.java | 15 +- src/com/android/tv/ui/BlockScreenView.java | 97 +- src/com/android/tv/ui/ChannelBannerView.java | 315 +++--- src/com/android/tv/ui/DialogUtils.java | 34 +- src/com/android/tv/ui/FullscreenDialogView.java | 99 +- .../tv/ui/GuidedActionsStylistWithDivider.java | 14 +- src/com/android/tv/ui/InputBannerView.java | 34 +- src/com/android/tv/ui/IntroView.java | 18 +- src/com/android/tv/ui/KeypadChannelSwitchView.java | 186 ++-- .../tv/ui/OnRepeatedKeyInterceptListener.java | 26 +- src/com/android/tv/ui/SelectInputView.java | 196 ++-- src/com/android/tv/ui/TunableTvView.java | 842 +++++++------- src/com/android/tv/ui/TvOverlayManager.java | 696 ++++++------ src/com/android/tv/ui/TvTransitionManager.java | 150 +-- src/com/android/tv/ui/TvViewUiManager.java | 314 +++--- src/com/android/tv/ui/ViewUtils.java | 53 +- src/com/android/tv/ui/sidepanel/ActionItem.java | 3 +- .../android/tv/ui/sidepanel/ChannelCheckItem.java | 49 +- src/com/android/tv/ui/sidepanel/CheckBoxItem.java | 16 +- .../tv/ui/sidepanel/ClosedCaptionFragment.java | 54 +- .../tv/ui/sidepanel/CompoundButtonItem.java | 11 +- .../ui/sidepanel/CustomizeChannelListFragment.java | 168 +-- .../tv/ui/sidepanel/DeveloperOptionFragment.java | 73 +- .../tv/ui/sidepanel/DisplayModeFragment.java | 2 - src/com/android/tv/ui/sidepanel/DividerItem.java | 11 +- src/com/android/tv/ui/sidepanel/Item.java | 23 +- .../tv/ui/sidepanel/MultiAudioFragment.java | 10 +- .../android/tv/ui/sidepanel/RadioButtonItem.java | 2 +- .../android/tv/ui/sidepanel/SettingsFragment.java | 162 +-- src/com/android/tv/ui/sidepanel/SideFragment.java | 43 +- .../tv/ui/sidepanel/SideFragmentManager.java | 108 +- .../android/tv/ui/sidepanel/SimpleActionItem.java | 7 +- src/com/android/tv/ui/sidepanel/SubMenuItem.java | 1 - src/com/android/tv/ui/sidepanel/SwitchItem.java | 6 +- .../parentalcontrols/ChannelsBlockedFragment.java | 95 +- .../parentalcontrols/ParentalControlsFragment.java | 179 +-- .../ProgramRestrictionsFragment.java | 64 +- .../parentalcontrols/RatingSystemsFragment.java | 39 +- .../parentalcontrols/RatingsFragment.java | 90 +- .../parentalcontrols/SubRatingsFragment.java | 25 +- src/com/android/tv/util/AccountHelper.java | 29 +- src/com/android/tv/util/AsyncDbTask.java | 161 +-- src/com/android/tv/util/BitmapUtils.java | 100 +- src/com/android/tv/util/CaptionSettings.java | 13 +- src/com/android/tv/util/Clock.java | 41 +- src/com/android/tv/util/CompositeComparator.java | 4 +- src/com/android/tv/util/Debug.java | 26 +- src/com/android/tv/util/DurationTimer.java | 25 +- src/com/android/tv/util/Filter.java | 8 +- src/com/android/tv/util/ImageCache.java | 68 +- src/com/android/tv/util/ImageLoader.java | 200 ++-- src/com/android/tv/util/LocationUtils.java | 52 +- src/com/android/tv/util/MainThreadExecutor.java | 19 +- src/com/android/tv/util/MultiLongSparseArray.java | 21 +- src/com/android/tv/util/NamedThreadFactory.java | 5 +- src/com/android/tv/util/NetworkTrafficTags.java | 1 - src/com/android/tv/util/NetworkUtils.java | 11 +- src/com/android/tv/util/OnboardingUtils.java | 43 +- src/com/android/tv/util/Partner.java | 3 +- src/com/android/tv/util/PermissionUtils.java | 28 +- src/com/android/tv/util/RecurringRunner.java | 37 +- src/com/android/tv/util/SetupUtils.java | 208 ++-- src/com/android/tv/util/StringUtils.java | 10 +- src/com/android/tv/util/SystemProperties.java | 51 +- src/com/android/tv/util/TimeShiftUtils.java | 18 +- src/com/android/tv/util/ToastUtils.java | 9 +- src/com/android/tv/util/TvInputManagerHelper.java | 344 +++--- src/com/android/tv/util/TvSettings.java | 126 ++- src/com/android/tv/util/TvTrackInfoUtils.java | 50 +- src/com/android/tv/util/TvUriMatcher.java | 31 +- src/com/android/tv/util/Utils.java | 384 +++---- src/com/android/tv/util/ViewCache.java | 37 +- .../src/com/android/tv/input/TunerHelper.java | 23 +- .../src/com/android/tv/testing/ChannelInfo.java | 157 ++- .../src/com/android/tv/testing/ChannelUtils.java | 17 +- .../com/android/tv/testing/ComparableTester.java | 48 +- .../com/android/tv/testing/ComparatorTester.java | 55 +- .../src/com/android/tv/testing/Constants.java | 35 +- .../src/com/android/tv/testing/FakeClock.java | 14 +- .../src/com/android/tv/testing/ProgramInfo.java | 152 +-- .../src/com/android/tv/testing/ProgramUtils.java | 19 +- .../tv/testing/TvContentRatingConstants.java | 16 +- tests/common/src/com/android/tv/testing/Utils.java | 25 +- .../android/tv/testing/dvr/RecordingTestUtils.java | 36 +- .../android/tv/testing/testinput/ChannelState.java | 33 +- .../tv/testing/testinput/ChannelStateData.java | 31 +- .../testinput/TestInputControlConnection.java | 11 +- .../testing/testinput/TestInputControlUtils.java | 14 +- .../tv/testing/testinput/TvTestInputConstants.java | 11 +- .../tv/testing/uihelper/BaseUiDeviceHelper.java | 4 +- .../android/tv/testing/uihelper/ByResource.java | 7 +- .../com/android/tv/testing/uihelper/Constants.java | 3 +- .../android/tv/testing/uihelper/DialogHelper.java | 9 +- .../uihelper/LiveChannelsUiDeviceHelper.java | 26 +- .../android/tv/testing/uihelper/MenuHelper.java | 89 +- .../tv/testing/uihelper/SidePanelHelper.java | 11 +- .../tv/testing/uihelper/UiDeviceAsserts.java | 73 +- .../android/tv/testing/uihelper/UiDeviceUtils.java | 49 +- .../tv/testing/uihelper/UiObject2Asserts.java | 29 +- .../tv/testing/uihelper/UiObject2Utils.java | 7 +- .../android/tv/tests/ui/ChannelBannerViewTest.java | 6 +- .../android/tv/tests/ui/ChannelSourcesTest.java | 17 +- .../android/tv/tests/ui/LiveChannelsAppTest.java | 29 +- .../android/tv/tests/ui/LiveChannelsTestCase.java | 19 +- .../android/tv/tests/ui/ParentalControlsTest.java | 50 +- .../tv/tests/ui/PlayControlsRowViewTest.java | 18 +- .../com/android/tv/tests/ui/ProgramGuideTest.java | 8 +- .../src/com/android/tv/tests/ui/TimeoutTest.java | 16 +- .../android/tv/tests/ui/dvr/DvrLibraryTest.java | 140 ++- .../CustomizeChannelListFragmentTest.java | 24 +- .../com/android/tv/testinput/TestInputControl.java | 22 +- .../tv/testinput/TestInputControlService.java | 4 +- .../android/tv/testinput/TestTvInputService.java | 154 +-- .../tv/testinput/TestTvInputSetupActivity.java | 44 +- .../instrument/TestSetupInstrumentation.java | 39 +- .../tv/tests/jank/ChannelZappingJankTest.java | 21 +- .../tv/tests/jank/LiveChannelsTestCase.java | 10 +- .../com/android/tv/tests/jank/MenuJankTest.java | 16 +- .../tv/tests/jank/ProgramGuideJankTest.java | 39 +- .../jank/src/com/android/tv/tests/jank/Utils.java | 9 +- .../com/android/tv/BaseMainActivityTestCase.java | 45 +- .../android/tv/CurrentPositionMediatorTest.java | 5 +- tests/unit/src/com/android/tv/FeaturesTest.java | 5 +- .../unit/src/com/android/tv/MainActivityTest.java | 32 +- .../src/com/android/tv/TimeShiftManagerTest.java | 34 +- .../android/tv/data/ChannelDataManagerTest.java | 197 ++-- .../src/com/android/tv/data/ChannelNumberTest.java | 30 +- .../unit/src/com/android/tv/data/ChannelTest.java | 284 +++-- .../src/com/android/tv/data/GenreItemTest.java | 5 +- .../android/tv/data/ProgramDataManagerTest.java | 153 +-- .../unit/src/com/android/tv/data/ProgramTest.java | 94 +- .../android/tv/data/TvInputNewComparatorTest.java | 78 +- .../android/tv/data/WatchedHistoryManagerTest.java | 18 +- .../com/android/tv/dvr/BaseDvrDataManagerTest.java | 25 +- .../com/android/tv/dvr/DvrDataManagerImplTest.java | 45 +- .../android/tv/dvr/DvrDataManagerInMemoryImpl.java | 45 +- .../com/android/tv/dvr/DvrScheduleManagerTest.java | 722 +++++++----- .../com/android/tv/dvr/ScheduledRecordingTest.java | 44 +- .../android/tv/dvr/data/SeriesRecordingTest.java | 69 +- .../com/android/tv/dvr/provider/DvrDbSyncTest.java | 79 +- .../dvr/provider/EpisodicProgramLoadTaskTest.java | 62 +- .../tv/dvr/recorder/DvrRecordingServiceTest.java | 12 +- .../tv/dvr/recorder/InputTaskSchedulerTest.java | 128 ++- .../android/tv/dvr/recorder/RecordingTaskTest.java | 48 +- .../dvr/recorder/ScheduledProgramReaperTest.java | 51 +- .../com/android/tv/dvr/recorder/SchedulerTest.java | 54 +- .../dvr/recorder/SeriesRecordingSchedulerTest.java | 85 +- .../android/tv/dvr/ui/SortedArrayAdapterTest.java | 28 +- .../android/tv/experiments/ExperimentsTest.java | 20 +- tests/unit/src/com/android/tv/menu/MenuTest.java | 41 +- .../android/tv/menu/TvOptionsRowAdapterTest.java | 55 +- .../tv/recommendation/ChannelRecordTest.java | 16 +- .../tv/recommendation/EvaluatorTestCase.java | 70 +- .../FavoriteChannelEvaluatorTest.java | 52 +- .../recommendation/RecentChannelEvaluatorTest.java | 32 +- .../tv/recommendation/RecommendationUtils.java | 88 +- .../android/tv/recommendation/RecommenderTest.java | 174 +-- .../recommendation/RoutineWatchEvaluatorTest.java | 153 ++- .../android/tv/search/LocalSearchProviderTest.java | 37 +- .../src/com/android/tv/tests/TvActivityTest.java | 2 - .../src/com/android/tv/util/ImageCacheTest.java | 20 +- .../android/tv/util/MockApplicationSingletons.java | 5 +- .../android/tv/util/MultiLongSparseArrayTest.java | 20 +- .../com/android/tv/util/ScaledBitmapInfoTest.java | 31 +- tests/unit/src/com/android/tv/util/TestUtils.java | 166 ++- .../android/tv/util/TvInputManagerHelperTest.java | 254 +++-- .../com/android/tv/util/TvTrackInfoUtilsTest.java | 31 +- .../tv/util/UtilsTest_GetDurationString.java | 415 +++++-- .../tv/util/UtilsTest_GetMultiAudioString.java | 69 +- .../android/tv/util/UtilsTest_IsInGivenDay.java | 25 +- 516 files changed, 25677 insertions(+), 22614 deletions(-) diff --git a/common/src/com/android/tv/common/AutoCloseableUtils.java b/common/src/com/android/tv/common/AutoCloseableUtils.java index ad364cc4..03d7bd9a 100644 --- a/common/src/com/android/tv/common/AutoCloseableUtils.java +++ b/common/src/com/android/tv/common/AutoCloseableUtils.java @@ -18,13 +18,11 @@ package com.android.tv.common; import android.util.Log; -/** - * Static utilities for AutoCloseable. - */ +/** Static utilities for AutoCloseable. */ public class AutoCloseableUtils { private static final String TAG = "AutoCloseableUtils"; - private AutoCloseableUtils() { } + private AutoCloseableUtils() {} public static void closeQuietly(AutoCloseable closeable) { try { diff --git a/common/src/com/android/tv/common/BooleanSystemProperty.java b/common/src/com/android/tv/common/BooleanSystemProperty.java index 21e575a1..5436524f 100644 --- a/common/src/com/android/tv/common/BooleanSystemProperty.java +++ b/common/src/com/android/tv/common/BooleanSystemProperty.java @@ -17,7 +17,6 @@ package com.android.tv.common; import android.util.Log; - import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -25,16 +24,15 @@ import java.util.List; /** * Lazy loaded boolean system property. * - *

Set with adb shell setprop key value where: - * Values 'n', 'no', '0', 'false' or 'off' are considered false. - * Values 'y', 'yes', '1', 'true' or 'on' are considered true. - * (case sensitive). See Set with adb shell setprop key value where: Values 'n', 'no', + * '0', 'false' or 'off' are considered false. Values 'y', 'yes', '1', 'true' or 'on' are considered + * true. (case sensitive). See android.os.SystemProperties.getBoolean. */ public class BooleanSystemProperty { - private final static String TAG = "BooleanSystemProperty"; - private final static boolean DEBUG = false; + private static final String TAG = "BooleanSystemProperty"; + private static final boolean DEBUG = false; private static final List ALL_PROPERTIES = new ArrayList<>(); private final boolean mDefaultValue; private final String mKey; @@ -78,7 +76,7 @@ public class BooleanSystemProperty { } /** - * Clears the cached value. The next call to getValue will check {@code + * Clears the cached value. The next call to getValue will check {@code * android.os.SystemProperties}. */ public void reset() { @@ -88,7 +86,7 @@ public class BooleanSystemProperty { /** * Returns the value of the system property. * - *

If the value is cached get the value from {@code android.os.SystemProperties} with the + *

If the value is cached get the value from {@code android.os.SystemProperties} with the * default set in the constructor. */ public boolean getValue() { diff --git a/common/src/com/android/tv/common/CollectionUtils.java b/common/src/com/android/tv/common/CollectionUtils.java index 300ad8f2..79f6a9e0 100644 --- a/common/src/com/android/tv/common/CollectionUtils.java +++ b/common/src/com/android/tv/common/CollectionUtils.java @@ -23,16 +23,14 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -/** - * Static utilities for collections - */ +/** Static utilities for collections */ public class CollectionUtils { /** * Returns an array with the arrays concatenated together. * - * @see Stackoverflow answer by - * Joachim Sauer + * @see Stackoverflow answer by Joachim Sauer */ public static T[] concatAll(T[] first, T[]... rest) { int totalLength = first.length; @@ -50,12 +48,12 @@ public class CollectionUtils { /** * Unions the two collections and returns the unified list. - *

- * The elements is not compared with hashcode() or equals(). Comparator is used for the equality - * check. + * + *

The elements is not compared with hashcode() or equals(). Comparator is used for the + * equality check. */ - public static List union(Collection originals, Collection toAdds, - Comparator comparator) { + public static List union( + Collection originals, Collection toAdds, Comparator comparator) { List result = new ArrayList<>(originals); Collections.sort(result, comparator); List resultToAdd = new ArrayList<>(); @@ -68,11 +66,9 @@ public class CollectionUtils { return result; } - /** - * Subtracts the elements from the original collection. - */ - public static List subtract(Collection originals, T[] toSubtracts, - Comparator comparator) { + /** Subtracts the elements from the original collection. */ + public static List subtract( + Collection originals, T[] toSubtracts, Comparator comparator) { List result = new ArrayList<>(originals); Collections.sort(result, comparator); for (T toSubtract : toSubtracts) { @@ -84,11 +80,9 @@ public class CollectionUtils { return result; } - /** - * Returns {@code true} if the two specified collections have common elements. - */ - public static boolean containsAny(Collection c1, Collection c2, - Comparator comparator) { + /** Returns {@code true} if the two specified collections have common elements. */ + public static boolean containsAny( + Collection c1, Collection c2, Comparator comparator) { List contains = new ArrayList<>(c1); Collections.sort(contains, comparator); for (T iterate : c2) { diff --git a/common/src/com/android/tv/common/MemoryManageable.java b/common/src/com/android/tv/common/MemoryManageable.java index 0cb36103..f782ccde 100644 --- a/common/src/com/android/tv/common/MemoryManageable.java +++ b/common/src/com/android/tv/common/MemoryManageable.java @@ -17,13 +17,10 @@ package com.android.tv.common; /** - * Interface for the fine-grained memory management. - * The class which wants to release memory based on the system constraints should inherit - * this interface and implement {@link #performTrimMemory}. + * Interface for the fine-grained memory management. The class which wants to release memory based + * on the system constraints should inherit this interface and implement {@link #performTrimMemory}. */ public interface MemoryManageable { - /** - * For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}. - */ + /** For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}. */ void performTrimMemory(int level); } diff --git a/common/src/com/android/tv/common/SharedPreferencesUtils.java b/common/src/com/android/tv/common/SharedPreferencesUtils.java index 140c4e6f..18f5d7bb 100644 --- a/common/src/com/android/tv/common/SharedPreferencesUtils.java +++ b/common/src/com/android/tv/common/SharedPreferencesUtils.java @@ -20,9 +20,7 @@ import android.content.Context; import android.os.AsyncTask; import android.preference.PreferenceManager; -/** - * Static utilities for {@link android.content.SharedPreferences} - */ +/** Static utilities for {@link android.content.SharedPreferences} */ public final class SharedPreferencesUtils { // Note that changing the preference name will reset the preference values. public static final String SHARED_PREF_FEATURES = "sharePreferencesFeatures"; @@ -30,8 +28,7 @@ public final class SharedPreferencesUtils { public static final String SHARED_PREF_WATCHED_HISTORY = "watched_history_shared_preference"; public static final String SHARED_PREF_DVR_WATCHED_POSITION = "dvr_watched_position_shared_preference"; - public static final String SHARED_PREF_AUDIO_CAPABILITIES = - "com.android.tv.audio_capabilities"; + public static final String SHARED_PREF_AUDIO_CAPABILITIES = "com.android.tv.audio_capabilities"; public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner"; public static final String SHARED_PREF_EPG = "epg_preferences"; public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings"; @@ -39,15 +36,16 @@ public final class SharedPreferencesUtils { public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris"; /** Stores the UI related settings */ public static final String SHARED_PREF_UI_SETTINGS = "ui_settings"; + public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms"; private static boolean sInitializeCalled; /** - * {@link android.content.SharedPreferences} loads the preference file when - * {@link Context#getSharedPreferences(String, int)} is called for the first time. - * Call {@link Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR - * due to the file loading. + * {@link android.content.SharedPreferences} loads the preference file when {@link + * Context#getSharedPreferences(String, int)} is called for the first time. Call {@link + * Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR due to the + * file loading. */ public static synchronized void initialize(final Context context, final Runnable postTask) { if (!sInitializeCalled) { @@ -59,15 +57,15 @@ public final class SharedPreferencesUtils { context.getSharedPreferences(SHARED_PREF_FEATURES, Context.MODE_PRIVATE); context.getSharedPreferences(SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); context.getSharedPreferences(SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_DVR_WATCHED_POSITION, - Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_AUDIO_CAPABILITIES, - Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_RECURRING_RUNNER, - Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_SERIES_RECORDINGS, - Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); return null; } @@ -80,5 +78,5 @@ public final class SharedPreferencesUtils { } } - private SharedPreferencesUtils() { } + private SharedPreferencesUtils() {} } diff --git a/common/src/com/android/tv/common/SoftPreconditions.java b/common/src/com/android/tv/common/SoftPreconditions.java index 823c42ff..9fe0137b 100644 --- a/common/src/com/android/tv/common/SoftPreconditions.java +++ b/common/src/com/android/tv/common/SoftPreconditions.java @@ -19,15 +19,14 @@ package com.android.tv.common; import android.content.Context; import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.feature.Feature; /** - * Simple static methods to be called at the start of your own methods to verify - * correct arguments and state. + * Simple static methods to be called at the start of your own methods to verify correct arguments + * and state. * - *

{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and - * logs a warning when it is false. + *

{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and logs a + * warning when it is false. * *

This is based on com.android.internal.util.Preconditions. */ @@ -35,12 +34,11 @@ public final class SoftPreconditions { private static final String TAG = "SoftPreconditions"; /** - * Throws or logs if an expression involving the parameter of the calling - * method is not true. + * Throws or logs if an expression involving the parameter of the calling method is not true. * * @param expression a boolean expression - * @param tag Used to identify the source of a log message. It usually - * identifies the class or activity where the log call occurs. + * @param tag Used to identify the source of a log message. It usually identifies the class or + * activity where the log call occurs. * @param msg The message you would like logged. * @return the evaluation result of the boolean expression * @throws IllegalArgumentException if {@code expression} is true @@ -53,8 +51,7 @@ public final class SoftPreconditions { } /** - * Throws or logs if an expression involving the parameter of the calling - * method is not true. + * Throws or logs if an expression involving the parameter of the calling method is not true. * * @param expression a boolean expression * @return the evaluation result of the boolean expression @@ -69,8 +66,8 @@ public final class SoftPreconditions { * Throws or logs if an and object is null. * * @param reference an object reference - * @param tag Used to identify the source of a log message. It usually - * identifies the class or activity where the log call occurs. + * @param tag Used to identify the source of a log message. It usually identifies the class or + * activity where the log call occurs. * @param msg The message you would like logged. * @return true if the object is null * @throws NullPointerException if {@code reference} is null @@ -94,12 +91,12 @@ public final class SoftPreconditions { } /** - * Throws or logs if an expression involving the state of the calling - * instance, but not involving any parameters to the calling method is not true. + * Throws or logs if an expression involving the state of the calling instance, but not + * involving any parameters to the calling method is not true. * * @param expression a boolean expression - * @param tag Used to identify the source of a log message. It usually - * identifies the class or activity where the log call occurs. + * @param tag Used to identify the source of a log message. It usually identifies the class or + * activity where the log call occurs. * @param msg The message you would like logged. * @return the evaluation result of the boolean expression * @throws IllegalStateException if {@code expression} is true @@ -112,8 +109,8 @@ public final class SoftPreconditions { } /** - * Throws or logs if an expression involving the state of the calling - * instance, but not involving any parameters to the calling method is not true. + * Throws or logs if an expression involving the state of the calling instance, but not + * involving any parameters to the calling method is not true. * * @param expression a boolean expression * @return the evaluation result of the boolean expression @@ -129,8 +126,8 @@ public final class SoftPreconditions { * * @param context an android context * @param feature the required feature - * @param tag used to identify the source of a log message. It usually - * identifies the class or activity where the log call occurs + * @param tag used to identify the source of a log message. It usually identifies the class or + * activity where the log call occurs * @throws IllegalStateException if {@code feature} is not enabled */ public static void checkFeatureEnabled(Context context, Feature feature, String tag) { @@ -140,8 +137,8 @@ public final class SoftPreconditions { /** * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning. * - * @param tag Used to identify the source of a log message. It usually - * identifies the class or activity where the log call occurs. + * @param tag Used to identify the source of a log message. It usually identifies the class or + * activity where the log call occurs. * @param msg The message you would like logged * @param e The exception to wrap with a RuntimeException when thrown. */ @@ -166,6 +163,5 @@ public final class SoftPreconditions { } } - private SoftPreconditions() { - } + private SoftPreconditions() {} } diff --git a/common/src/com/android/tv/common/TvCommonConstants.java b/common/src/com/android/tv/common/TvCommonConstants.java index 9824497e..0c64a346 100644 --- a/common/src/com/android/tv/common/TvCommonConstants.java +++ b/common/src/com/android/tv/common/TvCommonConstants.java @@ -17,52 +17,45 @@ package com.android.tv.common; import android.media.tv.TvInputInfo; -import android.os.Build; -/** - * Constants for common use in TV app and tests. - */ +/** Constants for common use in TV app and tests. */ public final class TvCommonConstants { - /** - * A constant for the key of the extra data for the app linking intent. - */ + /** 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. + * 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}. + * A constant of the key to indicate a TV input ID for the intent action {@link + * INTENT_ACTION_INPUT_SETUP}. * *

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}. + * A constant of the key for intent to launch actual TV input setup activity used with {@link + * INTENT_ACTION_INPUT_SETUP}. * *

Value type: Intent (Parcelable) */ - public static final String EXTRA_SETUP_INTENT = - "com.android.tv.extra.SETUP_INTENT"; + 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}. + * A constant of the key to indicate an Activity launch intent for the intent action {@link + * INTENT_ACTION_INPUT_SETUP}. * *

Value type: Intent (Parcelable) */ public static final String EXTRA_ACTIVITY_AFTER_COMPLETION = "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION"; - private TvCommonConstants() { - } + private TvCommonConstants() {} } diff --git a/common/src/com/android/tv/common/TvCommonUtils.java b/common/src/com/android/tv/common/TvCommonUtils.java index c391ad24..2172e1c4 100644 --- a/common/src/com/android/tv/common/TvCommonUtils.java +++ b/common/src/com/android/tv/common/TvCommonUtils.java @@ -19,13 +19,11 @@ package com.android.tv.common; import android.content.Intent; import android.media.tv.TvInputInfo; -/** - * Util class for common use in TV app and inputs. - */ +/** Util class for common use in TV app and inputs. */ public final class TvCommonUtils { private static Boolean sRunningInTest; - private TvCommonUtils() { } + private TvCommonUtils() {} /** * Returns an intent to start the setup activity for the TV input using {@link diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java index 8b3c06f1..5f91ee3e 100644 --- a/common/src/com/android/tv/common/TvContentRatingCache.java +++ b/common/src/com/android/tv/common/TvContentRatingCache.java @@ -22,7 +22,6 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -31,13 +30,11 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -/** - * TvContentRating cache. - */ +/** TvContentRating cache. */ public final class TvContentRatingCache implements MemoryManageable { - private final static String TAG = "TvContentRatings"; + private static final String TAG = "TvContentRatings"; - private final static TvContentRatingCache INSTANCE = new TvContentRatingCache(); + private static final TvContentRatingCache INSTANCE = new TvContentRatingCache(); public static TvContentRatingCache getInstance() { return INSTANCE; @@ -48,8 +45,8 @@ public final class TvContentRatingCache implements MemoryManageable { /** * Returns an array TvContentRatings from a string of comma separated set of rating strings - * creating each from {@link TvContentRating#unflattenFromString(String)} if needed. - * Returns {@code null} if the string is empty or contains no valid ratings. + * creating each from {@link TvContentRating#unflattenFromString(String)} if needed. Returns + * {@code null} if the string is empty or contains no valid ratings. */ @Nullable public synchronized TvContentRating[] getRatings(String commaSeparatedRatings) { @@ -60,8 +57,8 @@ public final class TvContentRatingCache implements MemoryManageable { if (mRatingsMultiMap.containsKey(commaSeparatedRatings)) { tvContentRatings = mRatingsMultiMap.get(commaSeparatedRatings); } else { - String normalizedRatings = TextUtils - .join(",", getSortedSetFromCsv(commaSeparatedRatings)); + String normalizedRatings = + TextUtils.join(",", getSortedSetFromCsv(commaSeparatedRatings)); if (mRatingsMultiMap.containsKey(normalizedRatings)) { tvContentRatings = mRatingsMultiMap.get(normalizedRatings); } else { @@ -76,9 +73,7 @@ public final class TvContentRatingCache implements MemoryManageable { return tvContentRatings; } - /** - * Returns a sorted array of TvContentRatings from a comma separated string of ratings. - */ + /** Returns a sorted array of TvContentRatings from a comma separated string of ratings. */ @VisibleForTesting static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) { if (TextUtils.isEmpty(commaSeparatedRatings)) { @@ -93,8 +88,9 @@ public final class TvContentRatingCache implements MemoryManageable { Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e); } } - return contentRatings.size() == 0 ? - null : contentRatings.toArray(new TvContentRating[contentRatings.size()]); + return contentRatings.size() == 0 + ? null + : contentRatings.toArray(new TvContentRating[contentRatings.size()]); } private static Set getSortedSetFromCsv(String commaSeparatedRatings) { @@ -118,8 +114,8 @@ public final class TvContentRatingCache implements MemoryManageable { } /** - * Returns a string of each flattened content rating, sorted and concatenated together - * with a comma. + * Returns a string of each flattened content rating, sorted and concatenated together with a + * comma. */ public static String contentRatingsToString(TvContentRating[] contentRatings) { if (contentRatings == null || contentRatings.length == 0) { @@ -141,6 +137,5 @@ public final class TvContentRatingCache implements MemoryManageable { mRatingsMultiMap.clear(); } - private TvContentRatingCache() { - } + private TvContentRatingCache() {} } diff --git a/common/src/com/android/tv/common/WeakHandler.java b/common/src/com/android/tv/common/WeakHandler.java index 188f20ec..dbdd4769 100644 --- a/common/src/com/android/tv/common/WeakHandler.java +++ b/common/src/com/android/tv/common/WeakHandler.java @@ -20,7 +20,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.NonNull; - import java.lang.ref.WeakReference; /** @@ -37,7 +36,7 @@ public abstract class WeakHandler extends Handler { private final WeakReference mRef; /** - * Constructs a new handler with a weak reference to the given referent using the provided + * Constructs a new handler with a weak reference to the given referent using the provided * Looper instead of the default one. * * @param looper The looper, must not be null. @@ -57,9 +56,7 @@ public abstract class WeakHandler extends Handler { mRef = new WeakReference<>(ref); } - /** - * Calls {@link #handleMessage(Message, Object)} if the WeakReference is not cleared. - */ + /** Calls {@link #handleMessage(Message, Object)} if the WeakReference is not cleared. */ @Override public final void handleMessage(Message msg) { T referent = mRef.get(); @@ -74,7 +71,7 @@ public abstract class WeakHandler extends Handler { * *

If the WeakReference is cleared this method will no longer be called. * - * @param msg the message to handle + * @param msg the message to handle * @param referent the referent. Guaranteed to be non null. */ protected abstract void handleMessage(Message msg, @NonNull T referent); diff --git a/common/src/com/android/tv/common/annotation/UsedByReflection.java b/common/src/com/android/tv/common/annotation/UsedByReflection.java index 5a4517f7..7e0ac31a 100644 --- a/common/src/com/android/tv/common/annotation/UsedByReflection.java +++ b/common/src/com/android/tv/common/annotation/UsedByReflection.java @@ -25,5 +25,4 @@ import java.lang.annotation.Retention; * directly. */ @Retention(SOURCE) -public @interface UsedByReflection { -} +public @interface UsedByReflection {} diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java index 62c88ead..792e59c6 100644 --- a/common/src/com/android/tv/common/feature/CommonFeatures.java +++ b/common/src/com/android/tv/common/feature/CommonFeatures.java @@ -30,15 +30,15 @@ public class CommonFeatures { * *

See go/atv-dvr-onepager * - * DVR API is introduced in N, it only works when app runs as a system app. + *

DVR API is introduced in N, it only works when app runs as a system app. */ - public static final TestableFeature DVR = createTestableFeature( - AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE)); + public static final TestableFeature DVR = + createTestableFeature(AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE)); /** * ENABLE_RECORDING_REGARDLESS_OF_STORAGE_STATUS * - * Enables dvr recording regardless of storage status. + *

Enables dvr recording regardless of storage status. */ public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE = new PropertyFeature("force_recording_until_no_space", false); @@ -46,9 +46,8 @@ public class CommonFeatures { /** * USE_SW_CODEC_FOR_SD * - * Prefer software based codec for SD channels. + *

Prefer software based codec for SD channels. */ public static final Feature USE_SW_CODEC_FOR_SD = - new PropertyFeature("use_sw_codec_for_sd", false - ); + new PropertyFeature("use_sw_codec_for_sd", false); } diff --git a/common/src/com/android/tv/common/feature/EngOnlyFeature.java b/common/src/com/android/tv/common/feature/EngOnlyFeature.java index 9fc39d9f..5feb5481 100644 --- a/common/src/com/android/tv/common/feature/EngOnlyFeature.java +++ b/common/src/com/android/tv/common/feature/EngOnlyFeature.java @@ -17,16 +17,13 @@ package com.android.tv.common.feature; import android.content.Context; - import com.android.tv.common.BuildConfig; -/** - * A feature that is only available on {@link BuildConfig#ENG} builds. - */ +/** A feature that is only available on {@link BuildConfig#ENG} builds. */ public final class EngOnlyFeature implements Feature { public static final Feature ENG_ONLY_FEATURE = new EngOnlyFeature(); - private EngOnlyFeature() { } + private EngOnlyFeature() {} @Override public boolean isEnabled(Context context) { diff --git a/common/src/com/android/tv/common/feature/Feature.java b/common/src/com/android/tv/common/feature/Feature.java index b3a8336c..fd5625e5 100644 --- a/common/src/com/android/tv/common/feature/Feature.java +++ b/common/src/com/android/tv/common/feature/Feature.java @@ -23,16 +23,15 @@ import android.content.Context; * launched. * *

Expected usage is: + * *

{@code
- *     if (MY_FEATURE.isEnabled(context) {
- *         showNewCoolUi();
- *     } else{
- *         showOldBoringUi();
- *     }
+ * if (MY_FEATURE.isEnabled(context) {
+ *     showNewCoolUi();
+ * } else{
+ *     showOldBoringUi();
+ * }
  * }
*/ public interface Feature { boolean isEnabled(Context context); - - } diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java index f60b2048..bbaa2107 100644 --- a/common/src/com/android/tv/common/feature/FeatureUtils.java +++ b/common/src/com/android/tv/common/feature/FeatureUtils.java @@ -17,12 +17,9 @@ package com.android.tv.common.feature; import android.content.Context; - import java.util.Arrays; -/** - * Static utilities for features. - */ +/** Static utilities for features. */ public class FeatureUtils { /** @@ -47,7 +44,6 @@ public class FeatureUtils { return "or(" + Arrays.asList(features) + ")"; } }; - } /** @@ -74,36 +70,33 @@ public class FeatureUtils { }; } - /** - * A feature that is always enabled. - */ - public static final Feature ON = new Feature() { - @Override - public boolean isEnabled(Context context) { - return true; - } + /** A feature that is always enabled. */ + public static final Feature ON = + new Feature() { + @Override + public boolean isEnabled(Context context) { + return true; + } - @Override - public String toString() { - return "on"; - } - }; + @Override + public String toString() { + return "on"; + } + }; - /** - * A feature that is always disabled. - */ - public static final Feature OFF = new Feature() { - @Override - public boolean isEnabled(Context context) { - return false; - } + /** A feature that is always disabled. */ + public static final Feature OFF = + new Feature() { + @Override + public boolean isEnabled(Context context) { + return false; + } - @Override - public String toString() { - return "off"; - } - }; + @Override + public String toString() { + return "off"; + } + }; - private FeatureUtils() { - } + private FeatureUtils() {} } diff --git a/common/src/com/android/tv/common/feature/GServiceFeature.java b/common/src/com/android/tv/common/feature/GServiceFeature.java index 9e6e11a6..1d7d1156 100644 --- a/common/src/com/android/tv/common/feature/GServiceFeature.java +++ b/common/src/com/android/tv/common/feature/GServiceFeature.java @@ -18,10 +18,7 @@ package com.android.tv.common.feature; import android.content.Context; - -/** - * A feature controlled by a GServices flag. - */ +/** A feature controlled by a GServices flag. */ public class GServiceFeature implements Feature { private static final String LIVECHANNELS_PREFIX = "livechannels:"; private final String mKey; diff --git a/common/src/com/android/tv/common/feature/PackageVersionFeature.java b/common/src/com/android/tv/common/feature/PackageVersionFeature.java index 7f615d31..f41a14a0 100644 --- a/common/src/com/android/tv/common/feature/PackageVersionFeature.java +++ b/common/src/com/android/tv/common/feature/PackageVersionFeature.java @@ -49,7 +49,10 @@ public class PackageVersionFeature implements Feature { @Override public String toString() { - return "PackageVersionFeature[packageName=" + mPackageName + ",requiredVersion=" - + mRequiredVersionCode + "]"; + return "PackageVersionFeature[packageName=" + + mPackageName + + ",requiredVersion=" + + mRequiredVersionCode + + "]"; } } diff --git a/common/src/com/android/tv/common/feature/PropertyFeature.java b/common/src/com/android/tv/common/feature/PropertyFeature.java index fdcffa04..33227bdf 100644 --- a/common/src/com/android/tv/common/feature/PropertyFeature.java +++ b/common/src/com/android/tv/common/feature/PropertyFeature.java @@ -17,7 +17,6 @@ package com.android.tv.common.feature; import android.content.Context; - import com.android.tv.common.BooleanSystemProperty; /** @@ -31,7 +30,7 @@ public final class PropertyFeature implements Feature { /** * Create System Property backed feature. * - * @param key the system property key. Length must be <= 31 characters. + * @param key the system property key. Length must be <= 31 characters. * @param defaultValue the value to return if the property is undefined or empty. */ public PropertyFeature(String key, boolean defaultValue) { @@ -39,8 +38,11 @@ public final class PropertyFeature implements Feature { // Since Features are initialized at startup and the keys are static go ahead and kill // the application. throw new IllegalArgumentException( - "Property keys have a max length of 31 characters but '" + key + "' is " + key - .length() + " characters."); + "Property keys have a max length of 31 characters but '" + + key + + "' is " + + key.length() + + " characters."); } mProperty = new BooleanSystemProperty(key, defaultValue); } diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java index 9f99a64f..b6ab04a9 100644 --- a/common/src/com/android/tv/common/feature/Sdk.java +++ b/common/src/com/android/tv/common/feature/Sdk.java @@ -19,9 +19,7 @@ package com.android.tv.common.feature; import android.content.Context; import android.os.Build; -/** - * Holder for SDK version features - */ +/** Holder for SDK version features */ public class Sdk { public static final Feature AT_LEAST_N = new Feature() { diff --git a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java index 881f53d6..d4ec81ae 100644 --- a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java +++ b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java @@ -21,9 +21,7 @@ import android.content.SharedPreferences; import android.util.Log; import com.android.tv.common.SharedPreferencesUtils; -/** - * Feature controlled by shared preferences. - */ +/** Feature controlled by shared preferences. */ public final class SharedPreferencesFeature implements Feature { private static final String TAG = "SharedPrefFeature"; private static final boolean DEBUG = false; @@ -53,8 +51,9 @@ public final class SharedPreferencesFeature implements Feature { return false; } if (mSharedPreferences == null) { - mSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); + mSharedPreferences = + context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); mEnabled = mSharedPreferences.getBoolean(mKey, mDefaultValue); } if (DEBUG) Log.d(TAG, mKey + " is " + mEnabled); @@ -69,14 +68,14 @@ public final class SharedPreferencesFeature implements Feature { public void setEnabled(Context context, boolean enable) { if (DEBUG) Log.d(TAG, mKey + " is set to " + enable); if (mSharedPreferences == null) { - mSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); + mSharedPreferences = + context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); mEnabled = enable; mSharedPreferences.edit().putBoolean(mKey, enable).apply(); } else if (mEnabled != enable) { mEnabled = enable; mSharedPreferences.edit().putBoolean(mKey, enable).apply(); } - } } diff --git a/common/src/com/android/tv/common/feature/SystemAppFeature.java b/common/src/com/android/tv/common/feature/SystemAppFeature.java index 79fd32f3..1dd3b8dd 100644 --- a/common/src/com/android/tv/common/feature/SystemAppFeature.java +++ b/common/src/com/android/tv/common/feature/SystemAppFeature.java @@ -19,13 +19,11 @@ package com.android.tv.common.feature; import android.content.Context; import android.content.pm.ApplicationInfo; -/** - * A feature that is for system App. - */ +/** A feature that is for system App. */ public final class SystemAppFeature implements Feature { public static final Feature SYSTEM_APP_FEATURE = new SystemAppFeature(); - private SystemAppFeature() { } + private SystemAppFeature() {} @Override public boolean isEnabled(Context context) { diff --git a/common/src/com/android/tv/common/feature/TestableFeature.java b/common/src/com/android/tv/common/feature/TestableFeature.java index d7e707a1..c37da766 100644 --- a/common/src/com/android/tv/common/feature/TestableFeature.java +++ b/common/src/com/android/tv/common/feature/TestableFeature.java @@ -19,33 +19,28 @@ package com.android.tv.common.feature; import android.content.Context; import android.support.annotation.VisibleForTesting; import android.util.Log; - import com.android.tv.common.TvCommonUtils; /** * When run in a test harness this feature can be turned on or off, overriding the normal value. * - *

Warning making a feature testable will cause the code to stay in the APK and - * could leak unreleased features. + *

Warning making a feature testable will cause the code to stay in the APK and could leak + * unreleased features. */ public class TestableFeature implements Feature { - private final static String TAG = "TestableFeature"; - private final static String DETAIL_MESSAGE - = "TestableFeatures should only be changed in tests."; + private static final String TAG = "TestableFeature"; + private static final String DETAIL_MESSAGE = + "TestableFeatures should only be changed in tests."; private final Feature mDelegate; private Boolean mTestValue = null; - /** - * Creates testable feature. - */ + /** Creates testable feature. */ public static TestableFeature createTestableFeature(Feature delegate) { return new TestableFeature(delegate); } - /** - * Creates testable feature with initial value. - */ + /** Creates testable feature with initial value. */ public static TestableFeature createTestableFeature(Feature delegate, Boolean initialValue) { return new TestableFeature(delegate, initialValue); } @@ -62,8 +57,7 @@ public class TestableFeature implements Feature { @VisibleForTesting public void enableForTest() { if (!TvCommonUtils.isRunningInTest()) { - Log.e(TAG, "Not enabling for test:" + this, - new IllegalStateException(DETAIL_MESSAGE)); + Log.e(TAG, "Not enabling for test:" + this, new IllegalStateException(DETAIL_MESSAGE)); } else { mTestValue = true; } @@ -72,7 +66,9 @@ public class TestableFeature implements Feature { @VisibleForTesting public void disableForTests() { if (!TvCommonUtils.isRunningInTest()) { - Log.e(TAG, "Not disabling for test: " + this, + Log.e( + TAG, + "Not disabling for test: " + this, new IllegalStateException(DETAIL_MESSAGE)); } else { mTestValue = false; diff --git a/common/src/com/android/tv/common/recording/RecordingCapability.java b/common/src/com/android/tv/common/recording/RecordingCapability.java index 266fd271..9fef527c 100644 --- a/common/src/com/android/tv/common/recording/RecordingCapability.java +++ b/common/src/com/android/tv/common/recording/RecordingCapability.java @@ -18,16 +18,11 @@ package com.android.tv.common.recording; import android.os.Parcel; import android.os.Parcelable; - import java.util.Objects; -/** - * Static representation of the recording capability of a TvInputService. - */ -public final class RecordingCapability implements Parcelable{ - /** - * The inputId this capability represents. - */ +/** Static representation of the recording capability of a TvInputService. */ +public final class RecordingCapability implements Parcelable { + /** The inputId this capability represents. */ public final String inputId; /** @@ -40,8 +35,8 @@ public final class RecordingCapability implements Parcelable{ /** * The max number concurrent session that play a stream. * - *

This is often limited by the number of decoders available. - * The count includes both playing live TV and playing a recorded stream. + *

This is often limited by the number of decoders available. The count includes both playing + * live TV and playing a recorded stream. */ public final int maxConcurrentPlayingSessions; @@ -52,13 +47,14 @@ public final class RecordingCapability implements Parcelable{ */ public final int maxConcurrentSessionsOfAllTypes; - /** - * True if a tuned session can support recording and playback from the same resource. - */ + /** True if a tuned session can support recording and playback from the same resource. */ public final boolean playbackWhileRecording; - private RecordingCapability(String inputId, int maxConcurrentTunedSessions, - int maxConcurrentPlayingSessions, int maxConcurrentSessionsOfAllTypes, + private RecordingCapability( + String inputId, + int maxConcurrentTunedSessions, + int maxConcurrentPlayingSessions, + int maxConcurrentSessionsOfAllTypes, boolean playbackWhileRecording) { this.inputId = inputId; this.maxConcurrentTunedSessions = maxConcurrentTunedSessions; @@ -93,12 +89,12 @@ public final class RecordingCapability implements Parcelable{ return false; } RecordingCapability that = (RecordingCapability) o; - return Objects.equals(maxConcurrentTunedSessions, that.maxConcurrentTunedSessions) && - Objects.equals(maxConcurrentPlayingSessions, that.maxConcurrentPlayingSessions) && - Objects.equals(maxConcurrentSessionsOfAllTypes, - that.maxConcurrentSessionsOfAllTypes) && - Objects.equals(playbackWhileRecording, that.playbackWhileRecording) && - Objects.equals(inputId, that.inputId); + return Objects.equals(maxConcurrentTunedSessions, that.maxConcurrentTunedSessions) + && Objects.equals(maxConcurrentPlayingSessions, that.maxConcurrentPlayingSessions) + && Objects.equals( + maxConcurrentSessionsOfAllTypes, that.maxConcurrentSessionsOfAllTypes) + && Objects.equals(playbackWhileRecording, that.playbackWhileRecording) + && Objects.equals(inputId, that.inputId); } @Override @@ -108,13 +104,19 @@ public final class RecordingCapability implements Parcelable{ @Override public String toString() { - return "RecordingCapability{" + - "inputId='" + inputId + '\'' + - ", maxConcurrentTunedSessions=" + maxConcurrentTunedSessions + - ", maxConcurrentPlayingSessions=" + maxConcurrentPlayingSessions + - ", maxConcurrentSessionsOfAllTypes=" + maxConcurrentSessionsOfAllTypes + - ", playbackWhileRecording=" + playbackWhileRecording + - '}'; + return "RecordingCapability{" + + "inputId='" + + inputId + + '\'' + + ", maxConcurrentTunedSessions=" + + maxConcurrentTunedSessions + + ", maxConcurrentPlayingSessions=" + + maxConcurrentPlayingSessions + + ", maxConcurrentSessionsOfAllTypes=" + + maxConcurrentSessionsOfAllTypes + + ", playbackWhileRecording=" + + playbackWhileRecording + + '}'; } @Override @@ -122,17 +124,18 @@ public final class RecordingCapability implements Parcelable{ return 0; } - public static final Creator CREATOR = new Creator() { - @Override - public RecordingCapability createFromParcel(Parcel in) { - return new RecordingCapability(in); - } + public static final Creator CREATOR = + new Creator() { + @Override + public RecordingCapability createFromParcel(Parcel in) { + return new RecordingCapability(in); + } - @Override - public RecordingCapability[] newArray(int size) { - return new RecordingCapability[size]; - } - }; + @Override + public RecordingCapability[] newArray(int size) { + return new RecordingCapability[size]; + } + }; public static Builder builder() { return new Builder(); @@ -171,11 +174,12 @@ public final class RecordingCapability implements Parcelable{ } public RecordingCapability build() { - return new RecordingCapability(mInputId, mMaxConcurrentTunedSessions, - mMaxConcurrentPlayingSessions, mMaxConcurrentSessionsOfAllTypes, + return new RecordingCapability( + mInputId, + mMaxConcurrentTunedSessions, + mMaxConcurrentPlayingSessions, + mMaxConcurrentSessionsOfAllTypes, mPlaybackWhileRecording); } } } - - diff --git a/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java index 392d489f..6f088c0b 100644 --- a/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java +++ b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java @@ -18,14 +18,12 @@ package com.android.tv.common.ui.setup; import android.os.Bundle; -/** - * A listener for the action click. - */ +/** A listener for the action click. */ public interface OnActionClickListener { /** * Called when the action is clicked. - *

- * The method should return {@code true} if the action is handled, otherwise {@code false}. + * + *

The method should return {@code true} if the action is handled, otherwise {@code false}. * * @param category The action category. * @param id The action id. diff --git a/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java index 7ee06faf..8a7dbd70 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java +++ b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java @@ -22,46 +22,45 @@ import android.util.Log; import android.view.View; import android.view.View.OnClickListener; -/** - * Helper class for the execution in the fragment. - */ +/** Helper class for the execution in the fragment. */ public class SetupActionHelper { private static final String TAG = "SetupActionHelper"; - /** - * Executes the action. - */ + /** Executes the action. */ public static boolean onActionClick(Fragment fragment, String category, int actionId) { return onActionClick(fragment, category, actionId, null); } - /** - * Executes the action. - */ - public static boolean onActionClick(Fragment fragment, String category, int actionId, - Bundle params) { + /** Executes the action. */ + public static boolean onActionClick( + Fragment fragment, String category, int actionId, Bundle params) { if (fragment.getActivity() instanceof OnActionClickListener) { - return ((OnActionClickListener) fragment.getActivity()).onActionClick(category, - actionId, params); + return ((OnActionClickListener) fragment.getActivity()) + .onActionClick(category, actionId, params); } - Log.e(TAG, "Activity can't handle the action: {category=" + category + ", actionId=" - + actionId + ", params=" + params + "}"); + Log.e( + TAG, + "Activity can't handle the action: {category=" + + category + + ", actionId=" + + actionId + + ", params=" + + params + + "}"); return false; } - /** - * Creates an {@link OnClickListener} to handle the action. - */ - public static OnClickListener createOnClickListenerForAction(Fragment fragment, String category, - int actionId, Bundle params) { + /** Creates an {@link OnClickListener} to handle the action. */ + public static OnClickListener createOnClickListenerForAction( + Fragment fragment, String category, int actionId, Bundle params) { return new OnActionClickListenerForAction(fragment, category, actionId, params); } /** * The {@link OnClickListener} for the view. - *

- * Note that this class should be used only for the views in the {@code mFragment} to avoid the - * leak of mFragment. + * + *

Note that this class should be used only for the views in the {@code mFragment} to avoid + * the leak of mFragment. */ private static class OnActionClickListenerForAction implements OnClickListener { private final Fragment mFragment; @@ -69,8 +68,8 @@ public class SetupActionHelper { private final int mActionId; private final Bundle mParams; - OnActionClickListenerForAction(Fragment fragment, String category, int actionId, - Bundle params) { + OnActionClickListenerForAction( + Fragment fragment, String category, int actionId, Bundle params) { mFragment = fragment; mCategory = category; mActionId = actionId; @@ -83,5 +82,5 @@ public class SetupActionHelper { } } - private SetupActionHelper() { } + private SetupActionHelper() {} } diff --git a/common/src/com/android/tv/common/ui/setup/SetupActivity.java b/common/src/com/android/tv/common/ui/setup/SetupActivity.java index 2b381a6e..67418ce0 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupActivity.java +++ b/common/src/com/android/tv/common/ui/setup/SetupActivity.java @@ -28,7 +28,6 @@ import android.transition.Transition; import android.transition.TransitionInflater; import android.view.View; import android.view.ViewTreeObserver.OnPreDrawListener; - import com.android.tv.common.R; import com.android.tv.common.WeakHandler; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; @@ -51,23 +50,28 @@ public abstract class SetupActivity extends Activity implements OnActionClickLis super.onCreate(savedInstanceState); SetupAnimationHelper.initialize(this); setContentView(R.layout.activity_setup); - mFragmentTransitionDuration = getResources().getInteger( - R.integer.setup_fragment_transition_duration); + mFragmentTransitionDuration = + getResources().getInteger(R.integer.setup_fragment_transition_duration); // Show initial fragment only when the saved state is not restored, because the last // fragment is restored if savesInstanceState is not null. if (savedInstanceState == null) { // This is the workaround to show the first fragment with delay to show the fragment // enter transition. See http://b/26255145 - getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener( - new OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getWindow().getDecorView().getViewTreeObserver() - .removeOnPreDrawListener(this); - showInitialFragment(); - return true; - } - }); + getWindow() + .getDecorView() + .getViewTreeObserver() + .addOnPreDrawListener( + new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getWindow() + .getDecorView() + .getViewTreeObserver() + .removeOnPreDrawListener(this); + showInitialFragment(); + return true; + } + }); } else { mShowInitialFragment = false; } @@ -76,8 +80,8 @@ public abstract class SetupActivity extends Activity implements OnActionClickLis /** * The inherited class should provide the initial fragment to show. * - *

If this method returns {@code null} during {@link #onCreate}, then call - * {@link #showInitialFragment} explicitly later with non null initial fragment. + *

If this method returns {@code null} during {@link #onCreate}, then call {@link + * #showInitialFragment} explicitly later with non null initial fragment. */ protected abstract Fragment onCreateInitialFragment(); @@ -98,16 +102,15 @@ public abstract class SetupActivity extends Activity implements OnActionClickLis } } - /** - * Shows the given fragment. - */ + /** Shows the given fragment. */ protected FragmentTransaction showFragment(Fragment fragment, boolean addToBackStack) { FragmentTransaction ft = getFragmentManager().beginTransaction(); if (fragment instanceof SetupFragment) { int[] sharedElements = ((SetupFragment) fragment).getSharedElementIds(); if (sharedElements != null && sharedElements.length > 0) { - Transition sharedTransition = TransitionInflater.from(this) - .inflateTransition(R.transition.transition_action_background); + Transition sharedTransition = + TransitionInflater.from(this) + .inflateTransition(R.transition.transition_action_background); sharedTransition.setDuration(getSharedElementTransitionDuration()); SetupAnimationHelper.applyAnimationTimeScale(sharedTransition); fragment.setSharedElementEnterTransition(sharedTransition); @@ -143,9 +146,9 @@ public abstract class SetupActivity extends Activity implements OnActionClickLis /** * Override this method if the inherited class wants to handle the action. - *

- * The override method should return {@code true} if the action is handled, otherwise - * {@code false}. + * + *

The override method should return {@code true} if the action is handled, otherwise {@code + * false}. */ protected boolean executeAction(String category, int actionId, Bundle params) { return false; diff --git a/common/src/com/android/tv/common/ui/setup/SetupFragment.java b/common/src/com/android/tv/common/ui/setup/SetupFragment.java index d2b9d7c8..7d47548d 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupFragment.java @@ -26,22 +26,25 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; - import com.android.tv.common.ui.setup.animation.FadeAndShortSlide; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * A fragment which slides when it is entering/exiting. - */ +/** A fragment which slides when it is entering/exiting. */ public abstract class SetupFragment extends Fragment { @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {FRAGMENT_ENTER_TRANSITION, FRAGMENT_EXIT_TRANSITION, - FRAGMENT_REENTER_TRANSITION, FRAGMENT_RETURN_TRANSITION}) + @IntDef( + flag = true, + value = { + FRAGMENT_ENTER_TRANSITION, + FRAGMENT_EXIT_TRANSITION, + FRAGMENT_REENTER_TRANSITION, + FRAGMENT_RETURN_TRANSITION + } + ) public @interface FragmentTransitionType {} + public static final int FRAGMENT_ENTER_TRANSITION = 0x01; public static final int FRAGMENT_EXIT_TRANSITION = FRAGMENT_ENTER_TRANSITION << 1; public static final int FRAGMENT_REENTER_TRANSITION = FRAGMENT_ENTER_TRANSITION << 2; @@ -49,50 +52,50 @@ public abstract class SetupFragment extends Fragment { private boolean mEnterTransitionRunning; - private final TransitionListener mTransitionListener = new TransitionListener() { - @Override - public void onTransitionStart(Transition transition) { - mEnterTransitionRunning = true; - } + private final TransitionListener mTransitionListener = + new TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + mEnterTransitionRunning = true; + } - @Override - public void onTransitionEnd(Transition transition) { - mEnterTransitionRunning = false; - onEnterTransitionEnd(); - } + @Override + public void onTransitionEnd(Transition transition) { + mEnterTransitionRunning = false; + onEnterTransitionEnd(); + } - @Override - public void onTransitionCancel(Transition transition) { } + @Override + public void onTransitionCancel(Transition transition) {} - @Override - public void onTransitionPause(Transition transition) { } + @Override + public void onTransitionPause(Transition transition) {} - @Override - public void onTransitionResume(Transition transition) { } - }; + @Override + public void onTransitionResume(Transition transition) {} + }; - /** - * Returns {@code true} if the enter/reenter transition is running. - */ + /** Returns {@code true} if the enter/reenter transition is running. */ protected boolean isEnterTransitionRunning() { return mEnterTransitionRunning; } - /** - * Called when the enter/reenter transition ends. - */ - protected void onEnterTransitionEnd() { } + /** Called when the enter/reenter transition ends. */ + protected void onEnterTransitionEnd() {} public SetupFragment() { setAllowEnterTransitionOverlap(false); setAllowReturnTransitionOverlap(false); - enableFragmentTransition(FRAGMENT_ENTER_TRANSITION | FRAGMENT_EXIT_TRANSITION - | FRAGMENT_REENTER_TRANSITION | FRAGMENT_RETURN_TRANSITION); + enableFragmentTransition( + FRAGMENT_ENTER_TRANSITION + | FRAGMENT_EXIT_TRANSITION + | FRAGMENT_REENTER_TRANSITION + | FRAGMENT_RETURN_TRANSITION); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(getLayoutResourceId(), container, false); // After the transition animation, we need to request the focus. If not, this fragment // doesn't have the focus. @@ -100,18 +103,17 @@ public abstract class SetupFragment extends Fragment { return view; } - /** - * Returns the layout resource ID for this fragment. - */ - abstract protected int getLayoutResourceId(); + /** Returns the layout resource ID for this fragment. */ + protected abstract int getLayoutResourceId(); protected void setOnClickAction(View view, final String category, final int actionId) { - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - onActionClick(category, actionId); - } - }); + view.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + onActionClick(category, actionId); + } + }); } protected boolean onActionClick(String category, int actionId) { @@ -141,24 +143,22 @@ public abstract class SetupFragment extends Fragment { /** * Enables fragment transition according to the given {@code mask}. * - * @param mask This value is the combination of {@link #FRAGMENT_ENTER_TRANSITION}, - * {@link #FRAGMENT_EXIT_TRANSITION}, {@link #FRAGMENT_REENTER_TRANSITION}, and - * {@link #FRAGMENT_RETURN_TRANSITION}. + * @param mask This value is the combination of {@link #FRAGMENT_ENTER_TRANSITION}, {@link + * #FRAGMENT_EXIT_TRANSITION}, {@link #FRAGMENT_REENTER_TRANSITION}, and {@link + * #FRAGMENT_RETURN_TRANSITION}. */ public void enableFragmentTransition(@FragmentTransitionType int mask) { - setEnterTransition((mask & FRAGMENT_ENTER_TRANSITION) == 0 ? null - : createTransition(Gravity.END)); - setExitTransition((mask & FRAGMENT_EXIT_TRANSITION) == 0 ? null - : createTransition(Gravity.START)); - setReenterTransition((mask & FRAGMENT_REENTER_TRANSITION) == 0 ? null - : createTransition(Gravity.START)); - setReturnTransition((mask & FRAGMENT_RETURN_TRANSITION) == 0 ? null - : createTransition(Gravity.END)); + setEnterTransition( + (mask & FRAGMENT_ENTER_TRANSITION) == 0 ? null : createTransition(Gravity.END)); + setExitTransition( + (mask & FRAGMENT_EXIT_TRANSITION) == 0 ? null : createTransition(Gravity.START)); + setReenterTransition( + (mask & FRAGMENT_REENTER_TRANSITION) == 0 ? null : createTransition(Gravity.START)); + setReturnTransition( + (mask & FRAGMENT_RETURN_TRANSITION) == 0 ? null : createTransition(Gravity.END)); } - /** - * Sets the transition with the given {@code slidEdge}. - */ + /** Sets the transition with the given {@code slidEdge}. */ public void setFragmentTransition(@FragmentTransitionType int transitionType, int slideEdge) { switch (transitionType) { case FRAGMENT_ENTER_TRANSITION: @@ -184,9 +184,7 @@ public abstract class SetupFragment extends Fragment { .build(); } - /** - * Changes the move distance of the transitions to short distance. - */ + /** Changes the move distance of the transitions to short distance. */ public void setShortDistance(@FragmentTransitionType int mask) { if ((mask & FRAGMENT_ENTER_TRANSITION) != 0) { Transition transition = getEnterTransition(); @@ -218,7 +216,7 @@ public abstract class SetupFragment extends Fragment { * Returns the ID's of the view's whose descendants will perform delayed move. * * @see com.android.tv.common.ui.setup.animation.SetupAnimationHelper.TransitionBuilder - * #setParentIdsForDelay + * #setParentIdsForDelay */ protected int[] getParentIdsForDelay() { return null; @@ -228,7 +226,7 @@ public abstract class SetupFragment extends Fragment { * Sets the ID's of the views which will not be included in the transition. * * @see com.android.tv.common.ui.setup.animation.SetupAnimationHelper.TransitionBuilder - * #setExcludeIds + * #setExcludeIds */ protected int[] getExcludedTargetIds() { return null; diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java index 7bce9c27..7a649285 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java @@ -26,12 +26,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; - import com.android.tv.common.R; -/** - * A fragment for channel source info/setup. - */ +/** A fragment for channel source info/setup. */ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { /** * Key of the argument which indicate whether the parent of this fragment has three panes. @@ -41,22 +38,26 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { public static final String KEY_THREE_PANE = "key_three_pane"; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); Bundle arguments = getArguments(); view.findViewById(android.support.v17.leanback.R.id.action_fragment_root) .setPadding(0, 0, 0, 0); - LinearLayout.LayoutParams guidanceLayoutParams = (LinearLayout.LayoutParams) - view.findViewById(android.support.v17.leanback.R.id.content_fragment) - .getLayoutParams(); + LinearLayout.LayoutParams guidanceLayoutParams = + (LinearLayout.LayoutParams) + view.findViewById(android.support.v17.leanback.R.id.content_fragment) + .getLayoutParams(); guidanceLayoutParams.weight = 0; if (arguments != null && arguments.getBoolean(KEY_THREE_PANE, false)) { // Content fragment. - guidanceLayoutParams.width = getResources().getDimensionPixelOffset( - R.dimen.setup_guidedstep_guidance_section_width_3pane); - int doneButtonWidth = getResources().getDimensionPixelOffset( - R.dimen.setup_done_button_container_width); + guidanceLayoutParams.width = + getResources() + .getDimensionPixelOffset( + R.dimen.setup_guidedstep_guidance_section_width_3pane); + int doneButtonWidth = + getResources() + .getDimensionPixelOffset(R.dimen.setup_done_button_container_width); // Guided actions list View list = view.findViewById(android.support.v17.leanback.R.id.guidedactions_list); MarginLayoutParams marginLayoutParams = (MarginLayoutParams) list.getLayoutParams(); @@ -69,13 +70,16 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { } } else { // Content fragment. - guidanceLayoutParams.width = getResources().getDimensionPixelOffset( - R.dimen.setup_guidedstep_guidance_section_width_2pane); + guidanceLayoutParams.width = + getResources() + .getDimensionPixelOffset( + R.dimen.setup_guidedstep_guidance_section_width_2pane); } // gridView Alignment VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView(); - int offset = getResources().getDimensionPixelOffset( - R.dimen.setup_guidedactions_selector_margin_top); + int offset = + getResources() + .getDimensionPixelOffset(R.dimen.setup_guidedactions_selector_margin_top); gridView.setWindowAlignmentOffset(offset); gridView.setWindowAlignmentOffsetPercent(0); gridView.setItemAlignmentOffsetPercent(0); @@ -83,8 +87,8 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { .setTransitionGroup(false); // Needed for the shared element transition. // content_frame is defined in leanback. - ViewGroup group = (ViewGroup) view.findViewById( - android.support.v17.leanback.R.id.content_frame); + ViewGroup group = + (ViewGroup) view.findViewById(android.support.v17.leanback.R.id.content_frame); group.setClipChildren(false); group.setClipToPadding(false); return view; @@ -94,8 +98,8 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { public GuidanceStylist onCreateGuidanceStylist() { return new GuidanceStylist() { @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Guidance guidance) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Guidance guidance) { View view = super.onCreateView(inflater, container, guidance); if (guidance.getIconDrawable() == null) { // Icon view should not take up space when we don't use image. @@ -106,7 +110,7 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { }; } - abstract protected String getActionCategory(); + protected abstract String getActionCategory(); @Override public void onGuidedActionClicked(GuidedAction action) { diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java index f5c2bf26..e05a40c8 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java @@ -23,12 +23,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; - import com.android.tv.common.R; -/** - * A fragment for channel source info/setup. - */ +/** A fragment for channel source info/setup. */ public abstract class SetupMultiPaneFragment extends SetupFragment { private static final String TAG = "SetupMultiPaneFragment"; private static final boolean DEBUG = false; @@ -39,18 +36,29 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { private static final String CONTENT_FRAGMENT_TAG = "content_fragment"; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (DEBUG) { - Log.d(TAG, "onCreateView(" + inflater + ", " + container + ", " + savedInstanceState - + ")"); + Log.d( + TAG, + "onCreateView(" + + inflater + + ", " + + container + + ", " + + savedInstanceState + + ")"); } View view = super.onCreateView(inflater, container, savedInstanceState); if (savedInstanceState == null) { SetupGuidedStepFragment contentFragment = onCreateContentFragment(); - getChildFragmentManager().beginTransaction() - .replace(R.id.guided_step_fragment_container, contentFragment, - CONTENT_FRAGMENT_TAG).commit(); + getChildFragmentManager() + .beginTransaction() + .replace( + R.id.guided_step_fragment_container, + contentFragment, + CONTENT_FRAGMENT_TAG) + .commit(); } if (needsDoneButton()) { setOnClickAction(view.findViewById(R.id.button_done), getActionCategory(), ACTION_DONE); @@ -65,12 +73,12 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) { ((MarginLayoutParams) doneButtonContainer.getLayoutParams()).rightMargin = - -getResources().getDimensionPixelOffset( - R.dimen.setup_done_button_container_width); + -getResources() + .getDimensionPixelOffset(R.dimen.setup_done_button_container_width); } else { ((MarginLayoutParams) doneButtonContainer.getLayoutParams()).leftMargin = - -getResources().getDimensionPixelOffset( - R.dimen.setup_done_button_container_width); + -getResources() + .getDimensionPixelOffset(R.dimen.setup_done_button_container_width); } view.findViewById(R.id.button_done).setFocusable(false); } @@ -82,15 +90,15 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { return R.layout.fragment_setup_multi_pane; } - abstract protected SetupGuidedStepFragment onCreateContentFragment(); + protected abstract SetupGuidedStepFragment onCreateContentFragment(); @Nullable protected SetupGuidedStepFragment getContentFragment() { - return (SetupGuidedStepFragment) getChildFragmentManager() - .findFragmentByTag(CONTENT_FRAGMENT_TAG); + return (SetupGuidedStepFragment) + getChildFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); } - abstract protected String getActionCategory(); + protected abstract String getActionCategory(); protected boolean needsDoneButton() { return true; @@ -102,13 +110,16 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { @Override protected int[] getParentIdsForDelay() { - return new int[] {android.support.v17.leanback.R.id.content_fragment, - android.support.v17.leanback.R.id.guidedactions_list}; + return new int[] { + android.support.v17.leanback.R.id.content_fragment, + android.support.v17.leanback.R.id.guidedactions_list + }; } @Override public int[] getSharedElementIds() { - return new int[] {android.support.v17.leanback.R.id.action_fragment_background, - R.id.done_button_container}; + return new int[] { + android.support.v17.leanback.R.id.action_fragment_background, R.id.done_button_container + }; } } diff --git a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java index e1a8e60c..60ffb70f 100644 --- a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java +++ b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java @@ -29,15 +29,12 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -/** - * Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734) - */ +/** Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734) */ public class FadeAndShortSlide extends Visibility { private static final TimeInterpolator APPEAR_INTERPOLATOR = new DecelerateInterpolator(); private static final TimeInterpolator DISAPPEAR_INTERPOLATOR = new AccelerateInterpolator(); @@ -48,39 +45,45 @@ public class FadeAndShortSlide extends Visibility { private static final int DEFAULT_DISTANCE = 200; - private static abstract class CalculateSlide { + private abstract static class CalculateSlide { /** Returns the translation value for view when it goes out of the scene */ - public abstract float getGoneX(ViewGroup sceneRoot, View view, int[] position, - int distance); + public abstract float getGoneX( + ViewGroup sceneRoot, View view, int[] position, int distance); } - private static final CalculateSlide sCalculateStart = new CalculateSlide() { - @Override - public float getGoneX(ViewGroup sceneRoot, View view, int[] position, int distance) { - final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - final float x; - if (isRtl) { - x = view.getTranslationX() + distance; - } else { - x = view.getTranslationX() - distance; - } - return x; - } - }; - - private static final CalculateSlide sCalculateEnd = new CalculateSlide() { - @Override - public float getGoneX(ViewGroup sceneRoot, View view, int[] position, int distance) { - final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - final float x; - if (isRtl) { - x = view.getTranslationX() - distance; - } else { - x = view.getTranslationX() + distance; - } - return x; - } - }; + private static final CalculateSlide sCalculateStart = + new CalculateSlide() { + @Override + public float getGoneX( + ViewGroup sceneRoot, View view, int[] position, int distance) { + final boolean isRtl = + sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final float x; + if (isRtl) { + x = view.getTranslationX() + distance; + } else { + x = view.getTranslationX() - distance; + } + return x; + } + }; + + private static final CalculateSlide sCalculateEnd = + new CalculateSlide() { + @Override + public float getGoneX( + ViewGroup sceneRoot, View view, int[] position, int distance) { + final boolean isRtl = + sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final float x; + if (isRtl) { + x = view.getTranslationX() - distance; + } else { + x = view.getTranslationX() + distance; + } + return x; + } + }; private static final ViewPositionComparator sViewPositionComparator = new ViewPositionComparator(); @@ -131,9 +134,10 @@ public class FadeAndShortSlide extends Visibility { getTransitionTargets((ViewGroup) parentForDelay, transitionTargets); sViewPositionComparator.mParentForDelay = parentForDelay; sViewPositionComparator.mIsLtr = view.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - sViewPositionComparator.mToLeft = sViewPositionComparator.mIsLtr - ? mSlideEdge == (appear ? Gravity.END : Gravity.START) - : mSlideEdge == (appear ? Gravity.START : Gravity.END); + sViewPositionComparator.mToLeft = + sViewPositionComparator.mIsLtr + ? mSlideEdge == (appear ? Gravity.END : Gravity.START) + : mSlideEdge == (appear ? Gravity.START : Gravity.END); Collections.sort(transitionTargets, sViewPositionComparator); return transitionTargets.indexOf(view); } @@ -180,8 +184,8 @@ public class FadeAndShortSlide extends Visibility { captureValues(transitionValues); int delayIndex = getDelayOrder(transitionValues.view, false); if (delayIndex > 0) { - transitionValues.values.put(PROPNAME_DELAY, - delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); + transitionValues.values.put( + PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); } } @@ -192,8 +196,8 @@ public class FadeAndShortSlide extends Visibility { captureValues(transitionValues); int delayIndex = getDelayOrder(transitionValues.view, true); if (delayIndex > 0) { - transitionValues.values.put(PROPNAME_DELAY, - delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); + transitionValues.values.put( + PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS); } } @@ -212,7 +216,10 @@ public class FadeAndShortSlide extends Visibility { } @Override - public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, + public Animator onAppear( + ViewGroup sceneRoot, + View view, + TransitionValues startValues, TransitionValues endValues) { if (endValues == null) { return null; @@ -221,15 +228,16 @@ public class FadeAndShortSlide extends Visibility { int left = position[0]; float endX = view.getTranslationX(); float startX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance); - final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues, - left, startX, endX, APPEAR_INTERPOLATOR, this); + final Animator slideAnimator = + TranslationAnimationCreator.createAnimation( + view, endValues, left, startX, endX, APPEAR_INTERPOLATOR, this); if (slideAnimator == null) { return null; } mFade.setInterpolator(APPEAR_INTERPOLATOR); final AnimatorSet set = new AnimatorSet(); set.play(slideAnimator).with(mFade.onAppear(sceneRoot, view, startValues, endValues)); - Long delay = (Long ) endValues.values.get(PROPNAME_DELAY); + Long delay = (Long) endValues.values.get(PROPNAME_DELAY); if (delay != null) { set.setStartDelay(delay); } @@ -237,7 +245,10 @@ public class FadeAndShortSlide extends Visibility { } @Override - public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues, + public Animator onDisappear( + ViewGroup sceneRoot, + final View view, + TransitionValues startValues, TransitionValues endValues) { if (startValues == null) { return null; @@ -246,8 +257,9 @@ public class FadeAndShortSlide extends Visibility { int left = position[0]; float startX = view.getTranslationX(); float endX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance); - final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, - startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this); + final Animator slideAnimator = + TranslationAnimationCreator.createAnimation( + view, startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this); if (slideAnimator == null) { // slideAnimator is null if startX == endX return null; } @@ -257,13 +269,14 @@ public class FadeAndShortSlide extends Visibility { if (fadeAnimator == null) { return null; } - fadeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - fadeAnimator.removeListener(this); - view.setAlpha(0.0f); - } - }); + fadeAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + fadeAnimator.removeListener(this); + view.setAlpha(0.0f); + } + }); final AnimatorSet set = new AnimatorSet(); set.play(slideAnimator).with(fadeAnimator); @@ -300,9 +313,7 @@ public class FadeAndShortSlide extends Visibility { return super.setDuration(scaledDuration); } - /** - * Sets the moving distance in pixel. - */ + /** Sets the moving distance in pixel. */ public void setDistance(int distance) { mDistance = distance; } diff --git a/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java b/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java index d98138a2..2a913c6e 100644 --- a/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java +++ b/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java @@ -27,12 +27,9 @@ import android.transition.TransitionSet; import android.view.Gravity; import android.view.View; import android.widget.ImageView; - import com.android.tv.common.R; -/** - * A helper class for setup animation. - */ +/** A helper class for setup animation. */ public final class SetupAnimationHelper { public static final long DELAY_BETWEEN_SIBLINGS_MS = applyAnimationTimeScale(33); @@ -43,21 +40,21 @@ public final class SetupAnimationHelper { private static int sFragmentTransitionLongDistance; private static int sFragmentTransitionShortDistance; - private SetupAnimationHelper() { } + private SetupAnimationHelper() {} - /** - * Load initial parameters. This method should be called before using this class. - */ + /** Load initial parameters. This method should be called before using this class. */ public static void initialize(Context context) { if (sInitialized) { return; } - sFragmentTransitionDuration = context.getResources() - .getInteger(R.integer.setup_fragment_transition_duration); - sFragmentTransitionLongDistance = context.getResources() - .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance); - sFragmentTransitionShortDistance = context.getResources() - .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance); + sFragmentTransitionDuration = + context.getResources().getInteger(R.integer.setup_fragment_transition_duration); + sFragmentTransitionLongDistance = + context.getResources() + .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance); + sFragmentTransitionShortDistance = + context.getResources() + .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance); sInitialized = true; } @@ -88,9 +85,7 @@ public final class SetupAnimationHelper { return this; } - /** - * Sets the duration of the transition. - */ + /** Sets the duration of the transition. */ public TransitionBuilder setDuration(long duration) { mDuration = duration; return this; @@ -106,17 +101,13 @@ public final class SetupAnimationHelper { return this; } - /** - * Sets the ID's of the views which will not be included in the transition. - */ + /** Sets the ID's of the views which will not be included in the transition. */ public TransitionBuilder setExcludeIds(int[] excludeIds) { mExcludeIds = excludeIds; return this; } - /** - * Builds and returns the {@link android.transition.Transition}. - */ + /** Builds and returns the {@link android.transition.Transition}. */ public Transition build() { FadeAndShortSlide transition = new FadeAndShortSlide(mSlideEdge, mParentIdForDelay); transition.setDistance(mDistance); @@ -130,25 +121,19 @@ public final class SetupAnimationHelper { } } - /** - * Changes the move distance of the {@code transition} to long distance. - */ + /** Changes the move distance of the {@code transition} to long distance. */ public static void setLongDistance(FadeAndShortSlide transition) { checkInitialized(); transition.setDistance(sFragmentTransitionLongDistance); } - /** - * Changes the move distance of the {@code transition} to short distance. - */ + /** Changes the move distance of the {@code transition} to short distance. */ public static void setShortDistance(FadeAndShortSlide transition) { checkInitialized(); transition.setDistance(sFragmentTransitionShortDistance); } - /** - * Applies the animation scale to the given {@code animator}. - */ + /** Applies the animation scale to the given {@code animator}. */ public static Animator applyAnimationTimeScale(Animator animator) { if (animator instanceof AnimatorSet) { for (Animator child : ((AnimatorSet) animator).getChildAnimations()) { @@ -162,9 +147,7 @@ public final class SetupAnimationHelper { return animator; } - /** - * Applies the animation scale to the given {@code transition}. - */ + /** Applies the animation scale to the given {@code transition}. */ public static Transition applyAnimationTimeScale(Transition transition) { if (transition instanceof TransitionSet) { TransitionSet set = (TransitionSet) transition; @@ -180,9 +163,7 @@ public final class SetupAnimationHelper { return transition; } - /** - * Applies the animation scale to the given {@code time}. - */ + /** Applies the animation scale to the given {@code time}. */ public static long applyAnimationTimeScale(long time) { return (long) (time * ANIMATION_TIME_SCALE); } @@ -197,23 +178,25 @@ public final class SetupAnimationHelper { } /** - * Returns an animator which animates the source image of the {@link ImageView} with start delay. + * Returns an animator which animates the source image of the {@link ImageView} with start + * delay. * *

The frame rate is 60 fps. */ - public static ObjectAnimator createFrameAnimatorWithDelay(ImageView imageView, int[] frames, - long startDelay) { + public static ObjectAnimator createFrameAnimatorWithDelay( + ImageView imageView, int[] frames, long startDelay) { ObjectAnimator animator = ObjectAnimator.ofInt(imageView, "imageResource", frames); // Make it 60 fps. animator.setDuration(frames.length * 1000 / 60); animator.setInterpolator(null); animator.setStartDelay(startDelay); - animator.setEvaluator(new TypeEvaluator() { - @Override - public Integer evaluate(float fraction, Integer startValue, Integer endValue) { - return startValue; - } - }); + animator.setEvaluator( + new TypeEvaluator() { + @Override + public Integer evaluate(float fraction, Integer startValue, Integer endValue) { + return startValue; + } + }); return animator; } @@ -223,19 +206,20 @@ public final class SetupAnimationHelper { * @param view The view which will be animated. * @param duration The duration of the animation. * @param makeVisibleAfterAnimation If {@code true}, the view will become visible after the - * animation ends. + * animation ends. */ - public static Animator createFadeOutAnimator(final View view, long duration, - boolean makeVisibleAfterAnimation) { + public static Animator createFadeOutAnimator( + final View view, long duration, boolean makeVisibleAfterAnimation) { ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f).setDuration(duration); if (makeVisibleAfterAnimation) { - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setAlpha(1.0f); - } - }); + animator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1.0f); + } + }); } return animator; } diff --git a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java index 7705f7a7..38fccbbe 100644 --- a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java +++ b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java @@ -5,18 +5,16 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Path; +import android.support.v17.leanback.R; import android.transition.Transition; import android.transition.TransitionValues; import android.view.View; -import android.support.v17.leanback.R; - /** * This class is used by Slide and Explode to create an animator that goes from the start position * to the end position. It takes into account the canceled position so that it will not blink out or - * shift suddenly when the transition is interrupted. - * The original class is android.support.v17.leanback.transition.TranslationAnimationCreator which - * is hidden. + * shift suddenly when the transition is interrupted. The original class is + * android.support.v17.leanback.transition.TranslationAnimationCreator which is hidden. */ // Copied from android.support.v17.leanback.transition.TransltaionAnimationCreator class TranslationAnimationCreator { @@ -31,11 +29,16 @@ class TranslationAnimationCreator { * @param endX The end translation x of view * @param interpolator The interpolator to use with this animator. * @return An animator that moves from (startX, startY) to (endX, endY) unless there was a - * previous interruption, in which case it moves from the current position to (endX, - * endY). + * previous interruption, in which case it moves from the current position to (endX, endY). */ - static Animator createAnimation(View view, TransitionValues values, int viewPosX, float startX, - float endX, TimeInterpolator interpolator, Transition transition) { + static Animator createAnimation( + View view, + TransitionValues values, + int viewPosX, + float startX, + float endX, + TimeInterpolator interpolator, + Transition transition) { float terminalX = view.getTranslationX(); Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition); if (startPosition != null) { @@ -74,8 +77,8 @@ class TranslationAnimationCreator { private float mPausedX; private final float mTerminalX; - private TransitionPositionListener(View movingView, View viewInHierarchy, int startX, - float terminalX) { + private TransitionPositionListener( + View movingView, View viewInHierarchy, int startX, float terminalX) { mMovingView = movingView; mViewInHierarchy = viewInHierarchy; mStartX = startX - Math.round(mMovingView.getTranslationX()); @@ -123,6 +126,4 @@ class TranslationAnimationCreator { @Override public void onTransitionResume(Transition transition) {} } - } - diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java index 39196fe3..a257c1c6 100644 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java +++ b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java @@ -10,177 +10,396 @@ package com.ibm.icu.text; /** - * An interface defining constants for the Standard Compression Scheme - * for Unicode (SCSU) as outlined in Unicode Technical - * Report #6. + * An interface defining constants for the Standard Compression Scheme for Unicode (SCSU) as + * outlined in Unicode Technical Report #6. * * @author Stephen F. Booth * @version 1.1 05 Aug 99 * @version 1.0 26 Jul 99 */ -interface SCSU -{ - //========================== +interface SCSU { + // ========================== // Generic window shift - //========================== - final static int COMPRESSIONOFFSET = 0x80; + // ========================== + static final int COMPRESSIONOFFSET = 0x80; - //========================== + // ========================== // Number of windows - //========================== - final static int NUMWINDOWS = 8; - final static int NUMSTATICWINDOWS = 8; + // ========================== + static final int NUMWINDOWS = 8; + static final int NUMSTATICWINDOWS = 8; - //========================== + // ========================== // Indicates a window index is invalid - //========================== - final static int INVALIDWINDOW = -1; + // ========================== + static final int INVALIDWINDOW = -1; - //========================== + // ========================== // Indicates a character doesn't exist in input (past end of buffer) - //========================== - final static int INVALIDCHAR = -1; + // ========================== + static final int INVALIDCHAR = -1; - //========================== + // ========================== // Compression modes - //========================== - final static int SINGLEBYTEMODE = 0; - final static int UNICODEMODE = 1; + // ========================== + static final int SINGLEBYTEMODE = 0; + static final int UNICODEMODE = 1; - //========================== + // ========================== // Maximum value for a window's index - //========================== - final static int MAXINDEX = 0xFF; + // ========================== + static final int MAXINDEX = 0xFF; - //========================== + // ========================== // Reserved index value (characters belongs to first block) - //========================== - final static int RESERVEDINDEX = 0x00; + // ========================== + static final int RESERVEDINDEX = 0x00; - //========================== + // ========================== // Indices for scripts which cross a half-block boundary - //========================== - final static int LATININDEX = 0xF9; - final static int IPAEXTENSIONINDEX = 0xFA; - final static int GREEKINDEX = 0xFB; - final static int ARMENIANINDEX = 0xFC; - final static int HIRAGANAINDEX = 0xFD; - final static int KATAKANAINDEX = 0xFE; - final static int HALFWIDTHKATAKANAINDEX = 0xFF; - - //========================== + // ========================== + static final int LATININDEX = 0xF9; + static final int IPAEXTENSIONINDEX = 0xFA; + static final int GREEKINDEX = 0xFB; + static final int ARMENIANINDEX = 0xFC; + static final int HIRAGANAINDEX = 0xFD; + static final int KATAKANAINDEX = 0xFE; + static final int HALFWIDTHKATAKANAINDEX = 0xFF; + + // ========================== // Single-byte mode tags - //========================== - final static int SDEFINEX = 0x0B; - final static int SRESERVED = 0x0C; // reserved value - final static int SQUOTEU = 0x0E; - final static int SCHANGEU = 0x0F; - - final static int SQUOTE0 = 0x01; - final static int SQUOTE1 = 0x02; - final static int SQUOTE2 = 0x03; - final static int SQUOTE3 = 0x04; - final static int SQUOTE4 = 0x05; - final static int SQUOTE5 = 0x06; - final static int SQUOTE6 = 0x07; - final static int SQUOTE7 = 0x08; - - final static int SCHANGE0 = 0x10; - final static int SCHANGE1 = 0x11; - final static int SCHANGE2 = 0x12; - final static int SCHANGE3 = 0x13; - final static int SCHANGE4 = 0x14; - final static int SCHANGE5 = 0x15; - final static int SCHANGE6 = 0x16; - final static int SCHANGE7 = 0x17; - - final static int SDEFINE0 = 0x18; - final static int SDEFINE1 = 0x19; - final static int SDEFINE2 = 0x1A; - final static int SDEFINE3 = 0x1B; - final static int SDEFINE4 = 0x1C; - final static int SDEFINE5 = 0x1D; - final static int SDEFINE6 = 0x1E; - final static int SDEFINE7 = 0x1F; - - //========================== + // ========================== + static final int SDEFINEX = 0x0B; + static final int SRESERVED = 0x0C; // reserved value + static final int SQUOTEU = 0x0E; + static final int SCHANGEU = 0x0F; + + static final int SQUOTE0 = 0x01; + static final int SQUOTE1 = 0x02; + static final int SQUOTE2 = 0x03; + static final int SQUOTE3 = 0x04; + static final int SQUOTE4 = 0x05; + static final int SQUOTE5 = 0x06; + static final int SQUOTE6 = 0x07; + static final int SQUOTE7 = 0x08; + + static final int SCHANGE0 = 0x10; + static final int SCHANGE1 = 0x11; + static final int SCHANGE2 = 0x12; + static final int SCHANGE3 = 0x13; + static final int SCHANGE4 = 0x14; + static final int SCHANGE5 = 0x15; + static final int SCHANGE6 = 0x16; + static final int SCHANGE7 = 0x17; + + static final int SDEFINE0 = 0x18; + static final int SDEFINE1 = 0x19; + static final int SDEFINE2 = 0x1A; + static final int SDEFINE3 = 0x1B; + static final int SDEFINE4 = 0x1C; + static final int SDEFINE5 = 0x1D; + static final int SDEFINE6 = 0x1E; + static final int SDEFINE7 = 0x1F; + + // ========================== // Unicode mode tags - //========================== - final static int UCHANGE0 = 0xE0; - final static int UCHANGE1 = 0xE1; - final static int UCHANGE2 = 0xE2; - final static int UCHANGE3 = 0xE3; - final static int UCHANGE4 = 0xE4; - final static int UCHANGE5 = 0xE5; - final static int UCHANGE6 = 0xE6; - final static int UCHANGE7 = 0xE7; - - final static int UDEFINE0 = 0xE8; - final static int UDEFINE1 = 0xE9; - final static int UDEFINE2 = 0xEA; - final static int UDEFINE3 = 0xEB; - final static int UDEFINE4 = 0xEC; - final static int UDEFINE5 = 0xED; - final static int UDEFINE6 = 0xEE; - final static int UDEFINE7 = 0xEF; - - final static int UQUOTEU = 0xF0; - final static int UDEFINEX = 0xF1; - final static int URESERVED = 0xF2; // reserved value - - - //========================== + // ========================== + static final int UCHANGE0 = 0xE0; + static final int UCHANGE1 = 0xE1; + static final int UCHANGE2 = 0xE2; + static final int UCHANGE3 = 0xE3; + static final int UCHANGE4 = 0xE4; + static final int UCHANGE5 = 0xE5; + static final int UCHANGE6 = 0xE6; + static final int UCHANGE7 = 0xE7; + + static final int UDEFINE0 = 0xE8; + static final int UDEFINE1 = 0xE9; + static final int UDEFINE2 = 0xEA; + static final int UDEFINE3 = 0xEB; + static final int UDEFINE4 = 0xEC; + static final int UDEFINE5 = 0xED; + static final int UDEFINE6 = 0xEE; + static final int UDEFINE7 = 0xEF; + + static final int UQUOTEU = 0xF0; + static final int UDEFINEX = 0xF1; + static final int URESERVED = 0xF2; // reserved value + + // ========================== // Class variables - //========================== + // ========================== /** For window offset mapping */ - final static int [] sOffsetTable = { + static final int[] sOffsetTable = { // table generated by CompressionTableGenerator - 0x0, 0x80, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x400, - 0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780, 0x800, 0x880, - 0x900, 0x980, 0xa00, 0xa80, 0xb00, 0xb80, 0xc00, 0xc80, 0xd00, - 0xd80, 0xe00, 0xe80, 0xf00, 0xf80, 0x1000, 0x1080, 0x1100, - 0x1180, 0x1200, 0x1280, 0x1300, 0x1380, 0x1400, 0x1480, - 0x1500, 0x1580, 0x1600, 0x1680, 0x1700, 0x1780, 0x1800, - 0x1880, 0x1900, 0x1980, 0x1a00, 0x1a80, 0x1b00, 0x1b80, - 0x1c00, 0x1c80, 0x1d00, 0x1d80, 0x1e00, 0x1e80, 0x1f00, - 0x1f80, 0x2000, 0x2080, 0x2100, 0x2180, 0x2200, 0x2280, - 0x2300, 0x2380, 0x2400, 0x2480, 0x2500, 0x2580, 0x2600, - 0x2680, 0x2700, 0x2780, 0x2800, 0x2880, 0x2900, 0x2980, - 0x2a00, 0x2a80, 0x2b00, 0x2b80, 0x2c00, 0x2c80, 0x2d00, - 0x2d80, 0x2e00, 0x2e80, 0x2f00, 0x2f80, 0x3000, 0x3080, - 0x3100, 0x3180, 0x3200, 0x3280, 0x3300, 0x3380, 0xe000, - 0xe080, 0xe100, 0xe180, 0xe200, 0xe280, 0xe300, 0xe380, - 0xe400, 0xe480, 0xe500, 0xe580, 0xe600, 0xe680, 0xe700, - 0xe780, 0xe800, 0xe880, 0xe900, 0xe980, 0xea00, 0xea80, - 0xeb00, 0xeb80, 0xec00, 0xec80, 0xed00, 0xed80, 0xee00, - 0xee80, 0xef00, 0xef80, 0xf000, 0xf080, 0xf100, 0xf180, - 0xf200, 0xf280, 0xf300, 0xf380, 0xf400, 0xf480, 0xf500, - 0xf580, 0xf600, 0xf680, 0xf700, 0xf780, 0xf800, 0xf880, - 0xf900, 0xf980, 0xfa00, 0xfa80, 0xfb00, 0xfb80, 0xfc00, - 0xfc80, 0xfd00, 0xfd80, 0xfe00, 0xfe80, 0xff00, 0xff80, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x250, 0x370, - 0x530, 0x3040, 0x30a0, 0xff60 + 0x0, + 0x80, + 0x100, + 0x180, + 0x200, + 0x280, + 0x300, + 0x380, + 0x400, + 0x480, + 0x500, + 0x580, + 0x600, + 0x680, + 0x700, + 0x780, + 0x800, + 0x880, + 0x900, + 0x980, + 0xa00, + 0xa80, + 0xb00, + 0xb80, + 0xc00, + 0xc80, + 0xd00, + 0xd80, + 0xe00, + 0xe80, + 0xf00, + 0xf80, + 0x1000, + 0x1080, + 0x1100, + 0x1180, + 0x1200, + 0x1280, + 0x1300, + 0x1380, + 0x1400, + 0x1480, + 0x1500, + 0x1580, + 0x1600, + 0x1680, + 0x1700, + 0x1780, + 0x1800, + 0x1880, + 0x1900, + 0x1980, + 0x1a00, + 0x1a80, + 0x1b00, + 0x1b80, + 0x1c00, + 0x1c80, + 0x1d00, + 0x1d80, + 0x1e00, + 0x1e80, + 0x1f00, + 0x1f80, + 0x2000, + 0x2080, + 0x2100, + 0x2180, + 0x2200, + 0x2280, + 0x2300, + 0x2380, + 0x2400, + 0x2480, + 0x2500, + 0x2580, + 0x2600, + 0x2680, + 0x2700, + 0x2780, + 0x2800, + 0x2880, + 0x2900, + 0x2980, + 0x2a00, + 0x2a80, + 0x2b00, + 0x2b80, + 0x2c00, + 0x2c80, + 0x2d00, + 0x2d80, + 0x2e00, + 0x2e80, + 0x2f00, + 0x2f80, + 0x3000, + 0x3080, + 0x3100, + 0x3180, + 0x3200, + 0x3280, + 0x3300, + 0x3380, + 0xe000, + 0xe080, + 0xe100, + 0xe180, + 0xe200, + 0xe280, + 0xe300, + 0xe380, + 0xe400, + 0xe480, + 0xe500, + 0xe580, + 0xe600, + 0xe680, + 0xe700, + 0xe780, + 0xe800, + 0xe880, + 0xe900, + 0xe980, + 0xea00, + 0xea80, + 0xeb00, + 0xeb80, + 0xec00, + 0xec80, + 0xed00, + 0xed80, + 0xee00, + 0xee80, + 0xef00, + 0xef80, + 0xf000, + 0xf080, + 0xf100, + 0xf180, + 0xf200, + 0xf280, + 0xf300, + 0xf380, + 0xf400, + 0xf480, + 0xf500, + 0xf580, + 0xf600, + 0xf680, + 0xf700, + 0xf780, + 0xf800, + 0xf880, + 0xf900, + 0xf980, + 0xfa00, + 0xfa80, + 0xfb00, + 0xfb80, + 0xfc00, + 0xfc80, + 0xfd00, + 0xfd80, + 0xfe00, + 0xfe80, + 0xff00, + 0xff80, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0xc0, + 0x250, + 0x370, + 0x530, + 0x3040, + 0x30a0, + 0xff60 }; /** Static compression window offsets */ - final static int [] sOffsets = { - 0x0000, // for quoting single-byte mode tags - 0x0080, // Latin-1 Supplement - 0x0100, // Latin Extended-A - 0x0300, // Combining Diacritical Marks - 0x2000, // General Punctuation - 0x2080, // Curency Symbols - 0x2100, // Letterlike Symbols and Number Forms - 0x3000 // CJK Symbols and Punctuation + static final int[] sOffsets = { + 0x0000, // for quoting single-byte mode tags + 0x0080, // Latin-1 Supplement + 0x0100, // Latin Extended-A + 0x0300, // Combining Diacritical Marks + 0x2000, // General Punctuation + 0x2080, // Curency Symbols + 0x2100, // Letterlike Symbols and Number Forms + 0x3000 // CJK Symbols and Punctuation }; - } - diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java index 6789469f..6e2a2d71 100644 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java +++ b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java @@ -10,116 +10,110 @@ package com.ibm.icu.text; /** -* A decompression engine implementing the Standard Compression Scheme -* for Unicode (SCSU) as outlined in Unicode Technical -* Report #6. -* -*

USAGE

-* -*

The static methods on UnicodeDecompressor may be used in a -* straightforward manner to decompress simple strings:

-* -*
-*  byte [] compressed = ... ; // get compressed bytes from somewhere
-*  String result = UnicodeDecompressor.decompress(compressed);
-* 
-* -*

The static methods have a fairly large memory footprint. -* For finer-grained control over memory usage, -* UnicodeDecompressor offers more powerful APIs allowing -* iterative decompression:

-* -*
-*  // Decompress an array "bytes" of length "len" using a buffer of 512 chars
-*  // to the Writer "out"
-*
-*  UnicodeDecompressor myDecompressor         = new UnicodeDecompressor();
-*  final static int    BUFSIZE                = 512;
-*  char []             charBuffer             = new char [ BUFSIZE ];
-*  int                 charsWritten           = 0;
-*  int []              bytesRead              = new int [1];
-*  int                 totalBytesDecompressed = 0;
-*  int                 totalCharsWritten      = 0;
-*
-*  do {
-*    // do the decompression
-*    charsWritten = myDecompressor.decompress(bytes, totalBytesDecompressed, 
-*                                             len, bytesRead,
-*                                             charBuffer, 0, BUFSIZE);
-*
-*    // do something with the current set of chars
-*    out.write(charBuffer, 0, charsWritten);
-*
-*    // update the no. of bytes decompressed
-*    totalBytesDecompressed += bytesRead[0];
-*
-*    // update the no. of chars written
-*    totalCharsWritten += charsWritten;
-*
-*  } while(totalBytesDecompressed < len);
-*
-*  myDecompressor.reset(); // reuse decompressor
-* 
-* -*

Decompression is performed according to the standard set forth in -* Unicode Technical -* Report #6

-* -* @see UnicodeCompressor -* -* @author Stephen F. Booth -* @stable ICU 2.4 -*/ -public final class UnicodeDecompressor implements SCSU -{ - //========================== + * A decompression engine implementing the Standard Compression Scheme for Unicode (SCSU) as + * outlined in Unicode Technical Report #6. + * + *

USAGE + * + *

The static methods on UnicodeDecompressor may be used in a straightforward manner to + * decompress simple strings: + * + *

+ *  byte [] compressed = ... ; // get compressed bytes from somewhere
+ *  String result = UnicodeDecompressor.decompress(compressed);
+ * 
+ * + *

The static methods have a fairly large memory footprint. For finer-grained control over memory + * usage, UnicodeDecompressor offers more powerful APIs allowing iterative decompression: + * + *

+ *  // Decompress an array "bytes" of length "len" using a buffer of 512 chars
+ *  // to the Writer "out"
+ *
+ *  UnicodeDecompressor myDecompressor         = new UnicodeDecompressor();
+ *  final static int    BUFSIZE                = 512;
+ *  char []             charBuffer             = new char [ BUFSIZE ];
+ *  int                 charsWritten           = 0;
+ *  int []              bytesRead              = new int [1];
+ *  int                 totalBytesDecompressed = 0;
+ *  int                 totalCharsWritten      = 0;
+ *
+ *  do {
+ *    // do the decompression
+ *    charsWritten = myDecompressor.decompress(bytes, totalBytesDecompressed,
+ *                                             len, bytesRead,
+ *                                             charBuffer, 0, BUFSIZE);
+ *
+ *    // do something with the current set of chars
+ *    out.write(charBuffer, 0, charsWritten);
+ *
+ *    // update the no. of bytes decompressed
+ *    totalBytesDecompressed += bytesRead[0];
+ *
+ *    // update the no. of chars written
+ *    totalCharsWritten += charsWritten;
+ *
+ *  } while(totalBytesDecompressed < len);
+ *
+ *  myDecompressor.reset(); // reuse decompressor
+ * 
+ * + *

Decompression is performed according to the standard set forth in Unicode Technical Report #6 + * + * @see UnicodeCompressor + * @author Stephen F. Booth + * @stable ICU 2.4 + */ +public final class UnicodeDecompressor implements SCSU { + // ========================== // Instance variables - //========================== - + // ========================== + /** Alias to current dynamic window */ - private int fCurrentWindow = 0; + private int fCurrentWindow = 0; /** Dynamic compression window offsets */ - private int [] fOffsets = new int [ NUMWINDOWS ]; + private int[] fOffsets = new int[NUMWINDOWS]; /** Current compression mode */ - private int fMode = SINGLEBYTEMODE; + private int fMode = SINGLEBYTEMODE; /** Size of our internal buffer */ - private final static int BUFSIZE = 3; + private static final int BUFSIZE = 3; /** Internal buffer for saving state */ - private byte [] fBuffer = new byte [BUFSIZE]; + private byte[] fBuffer = new byte[BUFSIZE]; /** Number of characters in our internal buffer */ - private int fBufferLength = 0; - + private int fBufferLength = 0; /** - * Create a UnicodeDecompressor. - * Sets all windows to their default values. + * Create a UnicodeDecompressor. Sets all windows to their default values. + * * @see #reset * @stable ICU 2.4 */ - public UnicodeDecompressor(){ - reset(); // initialize to defaults + public UnicodeDecompressor() { + reset(); // initialize to defaults } /** * Decompress a byte array into a String. + * * @param buffer The byte array to decompress. * @return A String containing the decompressed characters. * @see #decompress(byte [], int, int) * @stable ICU 2.4 */ - public static String decompress(byte [] buffer){ - char [] buf = decompress(buffer, 0, buffer.length); + public static String decompress(byte[] buffer) { + char[] buf = decompress(buffer, 0, buffer.length); return new String(buf); } /** * Decompress a byte array into a Unicode character array. + * * @param buffer The byte array to decompress. * @param start The start of the byte run to decompress. * @param limit The limit of the byte run to decompress. @@ -127,433 +121,675 @@ public final class UnicodeDecompressor implements SCSU * @see #decompress(byte []) * @stable ICU 2.4 */ - public static char [] decompress(byte [] buffer, int start, int limit) { + public static char[] decompress(byte[] buffer, int start, int limit) { UnicodeDecompressor comp = new UnicodeDecompressor(); - + // use a buffer we know will never overflow // in the worst case, each byte will decompress // to a surrogate pair (buffer must be at least 2 chars) int len = Math.max(2, 2 * (limit - start)); - char [] temp = new char [len]; - - int charCount = comp.decompress(buffer, start, limit, null, - temp, 0, len); - - char [] result = new char [charCount]; + char[] temp = new char[len]; + + int charCount = comp.decompress(buffer, start, limit, null, temp, 0, len); + + char[] result = new char[charCount]; System.arraycopy(temp, 0, result, 0, charCount); return result; } - + /** * Decompress a byte array into a Unicode character array. * - * This function will either completely fill the output buffer, - * or consume the entire input. + *

This function will either completely fill the output buffer, or consume the entire input. * * @param byteBuffer The byte buffer to decompress. * @param byteBufferStart The start of the byte run to decompress. * @param byteBufferLimit The limit of the byte run to decompress. - * @param bytesRead A one-element array. If not null, on return - * the number of bytes read from byteBuffer. - * @param charBuffer A buffer to receive the decompressed data. - * This buffer must be at minimum two characters in size. - * @param charBufferStart The starting offset to which to write - * decompressed data. - * @param charBufferLimit The limiting offset for writing - * decompressed data. + * @param bytesRead A one-element array. If not null, on return the number of bytes read from + * byteBuffer. + * @param charBuffer A buffer to receive the decompressed data. This buffer must be at minimum + * two characters in size. + * @param charBufferStart The starting offset to which to write decompressed data. + * @param charBufferLimit The limiting offset for writing decompressed data. * @return The number of Unicode characters written to charBuffer. * @stable ICU 2.4 */ - public int decompress(byte [] byteBuffer, - int byteBufferStart, - int byteBufferLimit, - int [] bytesRead, - char [] charBuffer, - int charBufferStart, - int charBufferLimit) - { - // the current position in the source byte buffer - int bytePos = byteBufferStart; - - // the current position in the target char buffer - int ucPos = charBufferStart; - - // the current byte from the source buffer - int aByte = 0x00; - - - // charBuffer must be at least 2 chars in size - if(charBuffer.length < 2 || (charBufferLimit - charBufferStart) < 2) - throw new IllegalArgumentException("charBuffer.length < 2"); - - // if our internal buffer isn't empty, flush its contents - // to the output buffer before doing any more decompression - if(fBufferLength > 0) { - - int newBytes = 0; + public int decompress( + byte[] byteBuffer, + int byteBufferStart, + int byteBufferLimit, + int[] bytesRead, + char[] charBuffer, + int charBufferStart, + int charBufferLimit) { + // the current position in the source byte buffer + int bytePos = byteBufferStart; + + // the current position in the target char buffer + int ucPos = charBufferStart; - // fill the buffer completely, to guarantee one full character - if(fBufferLength != BUFSIZE) { - newBytes = fBuffer.length - fBufferLength; + // the current byte from the source buffer + int aByte = 0x00; - // verify there are newBytes bytes in byteBuffer - if(byteBufferLimit - byteBufferStart < newBytes) - newBytes = byteBufferLimit - byteBufferStart; + // charBuffer must be at least 2 chars in size + if (charBuffer.length < 2 || (charBufferLimit - charBufferStart) < 2) + throw new IllegalArgumentException("charBuffer.length < 2"); - System.arraycopy(byteBuffer, byteBufferStart, - fBuffer, fBufferLength, newBytes); - } + // if our internal buffer isn't empty, flush its contents + // to the output buffer before doing any more decompression + if (fBufferLength > 0) { - // reset buffer length to 0 before recursive call - fBufferLength = 0; + int newBytes = 0; - // call self recursively to decompress the buffer - int count = decompress(fBuffer, 0, fBuffer.length, null, - charBuffer, charBufferStart, - charBufferLimit); + // fill the buffer completely, to guarantee one full character + if (fBufferLength != BUFSIZE) { + newBytes = fBuffer.length - fBufferLength; - // update the positions into the arrays - ucPos += count; - bytePos += newBytes; - } + // verify there are newBytes bytes in byteBuffer + if (byteBufferLimit - byteBufferStart < newBytes) + newBytes = byteBufferLimit - byteBufferStart; - // the main decompression loop - mainLoop: - while(bytePos < byteBufferLimit && ucPos < charBufferLimit) { - switch(fMode) { - case SINGLEBYTEMODE: - // single-byte mode decompression loop - singleByteModeLoop: - while(bytePos < byteBufferLimit && ucPos < charBufferLimit) { - aByte = byteBuffer[bytePos++] & 0xFF; - switch(aByte) { - // All bytes from 0x80 through 0xFF are remapped - // to chars or surrogate pairs according to the - // currently active window - case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: - case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: - case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: - case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: - case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: - case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: - case 0x9E: case 0x9F: case 0xA0: case 0xA1: case 0xA2: - case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: - case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: - case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: - case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: - case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: - case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0: - case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: - case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: - case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: - case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: - case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: - case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: - case 0xDF: case 0xE0: case 0xE1: case 0xE2: case 0xE3: - case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: - case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: - case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: - case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: - case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: - case 0xFD: case 0xFE: case 0xFF: - // For offsets <= 0xFFFF, convert to a single char - // by adding the window's offset and subtracting - // the generic compression offset - if(fOffsets[ fCurrentWindow ] <= 0xFFFF) { - charBuffer[ucPos++] = (char) - (aByte + fOffsets[ fCurrentWindow ] - - COMPRESSIONOFFSET); - } - // For offsets > 0x10000, convert to a surrogate pair by - // normBase = window's offset - 0x10000 - // high surr. = 0xD800 + (normBase >> 10) - // low surr. = 0xDC00 + (normBase & 0x3FF) + (byte & 0x7F) - else { - // make sure there is enough room to write - // both characters - // if not, save state and break out - if((ucPos + 1) >= charBufferLimit) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - int normalizedBase = fOffsets[ fCurrentWindow ] - - 0x10000; - charBuffer[ucPos++] = (char) - (0xD800 + (normalizedBase >> 10)); - charBuffer[ucPos++] = (char) - (0xDC00 + (normalizedBase & 0x3FF)+(aByte & 0x7F)); - } - break; - - // bytes from 0x20 through 0x7F are treated as ASCII and - // are remapped to chars by padding the high byte - // (this is the same as quoting from static window 0) - // NUL (0x00), HT (0x09), CR (0x0A), LF (0x0D) - // are treated as ASCII as well - case 0x00: case 0x09: case 0x0A: case 0x0D: - case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: - case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: - case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: - case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: - case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: - case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: - case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: - case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: - case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: - case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: - case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: - case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: - case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: - case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: - case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: - case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: - case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: - case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: - case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: - case 0x7F: - charBuffer[ucPos++] = (char) aByte; - break; - - // quote unicode - case SQUOTEU: - // verify we have two bytes following tag - // if not, save state and break out - if( (bytePos + 1) >= byteBufferLimit ) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++]; - charBuffer[ucPos++] = (char) - (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; - - // switch to Unicode mode - case SCHANGEU: - fMode = UNICODEMODE; - break singleByteModeLoop; - //break; - - // handle all quote tags - case SQUOTE0: case SQUOTE1: case SQUOTE2: case SQUOTE3: - case SQUOTE4: case SQUOTE5: case SQUOTE6: case SQUOTE7: - // verify there is a byte following the tag - // if not, save state and break out - if(bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - // if the byte is in the range 0x00 - 0x7F, use - // static window n otherwise, use dynamic window n - int dByte = byteBuffer[bytePos++] & 0xFF; - charBuffer[ucPos++] = (char) - (dByte+ (dByte >= 0x00 && dByte < 0x80 - ? sOffsets[aByte - SQUOTE0] - : (fOffsets[aByte - SQUOTE0] - - COMPRESSIONOFFSET))); - break; - - // handle all change tags - case SCHANGE0: case SCHANGE1: case SCHANGE2: case SCHANGE3: - case SCHANGE4: case SCHANGE5: case SCHANGE6: case SCHANGE7: - fCurrentWindow = aByte - SCHANGE0; - break; - - // handle all define tags - case SDEFINE0: case SDEFINE1: case SDEFINE2: case SDEFINE3: - case SDEFINE4: case SDEFINE5: case SDEFINE6: case SDEFINE7: - // verify there is a byte following the tag - // if not, save state and break out - if(bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - fCurrentWindow = aByte - SDEFINE0; - fOffsets[fCurrentWindow] = - sOffsetTable[byteBuffer[bytePos++] & 0xFF]; - break; - - // handle define extended tag - case SDEFINEX: - // verify we have two bytes following tag - // if not, save state and break out - if((bytePos + 1) >= byteBufferLimit ) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++] & 0xFF; - fCurrentWindow = (aByte & 0xE0) >> 5; - fOffsets[fCurrentWindow] = 0x10000 + - (0x80 * (((aByte & 0x1F) << 8) - | (byteBuffer[bytePos++] & 0xFF))); - break; - - // reserved, shouldn't happen - case SRESERVED: - break; - - } // end switch - } // end while - break; - - case UNICODEMODE: - // unicode mode decompression loop - unicodeModeLoop: - while(bytePos < byteBufferLimit && ucPos < charBufferLimit) { - aByte = byteBuffer[bytePos++] & 0xFF; - switch(aByte) { - // handle all define tags - case UDEFINE0: case UDEFINE1: case UDEFINE2: case UDEFINE3: - case UDEFINE4: case UDEFINE5: case UDEFINE6: case UDEFINE7: - // verify there is a byte following tag - // if not, save state and break out - if(bytePos >= byteBufferLimit ) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - fCurrentWindow = aByte - UDEFINE0; - fOffsets[fCurrentWindow] = - sOffsetTable[byteBuffer[bytePos++] & 0xFF]; - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - //break; - - // handle define extended tag - case UDEFINEX: - // verify we have two bytes following tag - // if not, save state and break out - if((bytePos + 1) >= byteBufferLimit ) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++] & 0xFF; - fCurrentWindow = (aByte & 0xE0) >> 5; - fOffsets[fCurrentWindow] = 0x10000 + - (0x80 * (((aByte & 0x1F) << 8) - | (byteBuffer[bytePos++] & 0xFF))); - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - //break; - - // handle all change tags - case UCHANGE0: case UCHANGE1: case UCHANGE2: case UCHANGE3: - case UCHANGE4: case UCHANGE5: case UCHANGE6: case UCHANGE7: - fCurrentWindow = aByte - UCHANGE0; - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - //break; - - // quote unicode - case UQUOTEU: - // verify we have two bytes following tag - // if not, save state and break out - if(bytePos >= byteBufferLimit - 1) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++]; - charBuffer[ucPos++] = (char) - (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; - - default: - // verify there is a byte following tag - // if not, save state and break out - if(bytePos >= byteBufferLimit ) { - --bytePos; - System.arraycopy(byteBuffer, bytePos, - fBuffer, 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; + System.arraycopy(byteBuffer, byteBufferStart, fBuffer, fBufferLength, newBytes); } - charBuffer[ucPos++] = (char) - (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; + // reset buffer length to 0 before recursive call + fBufferLength = 0; + + // call self recursively to decompress the buffer + int count = + decompress( + fBuffer, + 0, + fBuffer.length, + null, + charBuffer, + charBufferStart, + charBufferLimit); + + // update the positions into the arrays + ucPos += count; + bytePos += newBytes; + } - } // end switch + // the main decompression loop + mainLoop: + while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { + switch (fMode) { + case SINGLEBYTEMODE: + // single-byte mode decompression loop + singleByteModeLoop: + while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { + aByte = byteBuffer[bytePos++] & 0xFF; + switch (aByte) { + // All bytes from 0x80 through 0xFF are remapped + // to chars or surrogate pairs according to the + // currently active window + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + // For offsets <= 0xFFFF, convert to a single char + // by adding the window's offset and subtracting + // the generic compression offset + if (fOffsets[fCurrentWindow] <= 0xFFFF) { + charBuffer[ucPos++] = + (char) + (aByte + + fOffsets[fCurrentWindow] + - COMPRESSIONOFFSET); + } + // For offsets > 0x10000, convert to a surrogate pair by + // normBase = window's offset - 0x10000 + // high surr. = 0xD800 + (normBase >> 10) + // low surr. = 0xDC00 + (normBase & 0x3FF) + (byte & 0x7F) + else { + // make sure there is enough room to write + // both characters + // if not, save state and break out + if ((ucPos + 1) >= charBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + int normalizedBase = fOffsets[fCurrentWindow] - 0x10000; + charBuffer[ucPos++] = (char) (0xD800 + (normalizedBase >> 10)); + charBuffer[ucPos++] = + (char) + (0xDC00 + + (normalizedBase & 0x3FF) + + (aByte & 0x7F)); + } + break; + + // bytes from 0x20 through 0x7F are treated as ASCII and + // are remapped to chars by padding the high byte + // (this is the same as quoting from static window 0) + // NUL (0x00), HT (0x09), CR (0x0A), LF (0x0D) + // are treated as ASCII as well + case 0x00: + case 0x09: + case 0x0A: + case 0x0D: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + charBuffer[ucPos++] = (char) aByte; + break; + + // quote unicode + case SQUOTEU: + // verify we have two bytes following tag + // if not, save state and break out + if ((bytePos + 1) >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + aByte = byteBuffer[bytePos++]; + charBuffer[ucPos++] = + (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); + break; + + // switch to Unicode mode + case SCHANGEU: + fMode = UNICODEMODE; + break singleByteModeLoop; + // break; + + // handle all quote tags + case SQUOTE0: + case SQUOTE1: + case SQUOTE2: + case SQUOTE3: + case SQUOTE4: + case SQUOTE5: + case SQUOTE6: + case SQUOTE7: + // verify there is a byte following the tag + // if not, save state and break out + if (bytePos >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + // if the byte is in the range 0x00 - 0x7F, use + // static window n otherwise, use dynamic window n + int dByte = byteBuffer[bytePos++] & 0xFF; + charBuffer[ucPos++] = + (char) + (dByte + + (dByte >= 0x00 && dByte < 0x80 + ? sOffsets[aByte - SQUOTE0] + : (fOffsets[aByte - SQUOTE0] + - COMPRESSIONOFFSET))); + break; + + // handle all change tags + case SCHANGE0: + case SCHANGE1: + case SCHANGE2: + case SCHANGE3: + case SCHANGE4: + case SCHANGE5: + case SCHANGE6: + case SCHANGE7: + fCurrentWindow = aByte - SCHANGE0; + break; + + // handle all define tags + case SDEFINE0: + case SDEFINE1: + case SDEFINE2: + case SDEFINE3: + case SDEFINE4: + case SDEFINE5: + case SDEFINE6: + case SDEFINE7: + // verify there is a byte following the tag + // if not, save state and break out + if (bytePos >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + fCurrentWindow = aByte - SDEFINE0; + fOffsets[fCurrentWindow] = + sOffsetTable[byteBuffer[bytePos++] & 0xFF]; + break; + + // handle define extended tag + case SDEFINEX: + // verify we have two bytes following tag + // if not, save state and break out + if ((bytePos + 1) >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + aByte = byteBuffer[bytePos++] & 0xFF; + fCurrentWindow = (aByte & 0xE0) >> 5; + fOffsets[fCurrentWindow] = + 0x10000 + + (0x80 + * (((aByte & 0x1F) << 8) + | (byteBuffer[bytePos++] & 0xFF))); + break; + + // reserved, shouldn't happen + case SRESERVED: + break; + } // end switch + } // end while + break; + + case UNICODEMODE: + // unicode mode decompression loop + unicodeModeLoop: + while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { + aByte = byteBuffer[bytePos++] & 0xFF; + switch (aByte) { + // handle all define tags + case UDEFINE0: + case UDEFINE1: + case UDEFINE2: + case UDEFINE3: + case UDEFINE4: + case UDEFINE5: + case UDEFINE6: + case UDEFINE7: + // verify there is a byte following tag + // if not, save state and break out + if (bytePos >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + fCurrentWindow = aByte - UDEFINE0; + fOffsets[fCurrentWindow] = + sOffsetTable[byteBuffer[bytePos++] & 0xFF]; + fMode = SINGLEBYTEMODE; + break unicodeModeLoop; + // break; + + // handle define extended tag + case UDEFINEX: + // verify we have two bytes following tag + // if not, save state and break out + if ((bytePos + 1) >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + aByte = byteBuffer[bytePos++] & 0xFF; + fCurrentWindow = (aByte & 0xE0) >> 5; + fOffsets[fCurrentWindow] = + 0x10000 + + (0x80 + * (((aByte & 0x1F) << 8) + | (byteBuffer[bytePos++] & 0xFF))); + fMode = SINGLEBYTEMODE; + break unicodeModeLoop; + // break; + + // handle all change tags + case UCHANGE0: + case UCHANGE1: + case UCHANGE2: + case UCHANGE3: + case UCHANGE4: + case UCHANGE5: + case UCHANGE6: + case UCHANGE7: + fCurrentWindow = aByte - UCHANGE0; + fMode = SINGLEBYTEMODE; + break unicodeModeLoop; + // break; + + // quote unicode + case UQUOTEU: + // verify we have two bytes following tag + // if not, save state and break out + if (bytePos >= byteBufferLimit - 1) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + aByte = byteBuffer[bytePos++]; + charBuffer[ucPos++] = + (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); + break; + + default: + // verify there is a byte following tag + // if not, save state and break out + if (bytePos >= byteBufferLimit) { + --bytePos; + System.arraycopy( + byteBuffer, + bytePos, + fBuffer, + 0, + byteBufferLimit - bytePos); + fBufferLength = byteBufferLimit - bytePos; + bytePos += fBufferLength; + break mainLoop; + } + + charBuffer[ucPos++] = + (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); + break; + } // end switch + } // end while + break; + } // end switch( fMode ) } // end while - break; - - } // end switch( fMode ) - } // end while // fill in output parameter - if(bytesRead != null) - bytesRead [0] = (bytePos - byteBufferStart); + if (bytesRead != null) bytesRead[0] = (bytePos - byteBufferStart); // return # of chars written - return (ucPos - charBufferStart); + return (ucPos - charBufferStart); } - /** - * Reset the decompressor to its initial state. + /** + * Reset the decompressor to its initial state. + * * @stable ICU 2.4 */ - public void reset() - { + public void reset() { // reset dynamic windows - fOffsets[0] = 0x0080; // Latin-1 - fOffsets[1] = 0x00C0; // Latin-1 Supplement + Latin Extended-A - fOffsets[2] = 0x0400; // Cyrillic - fOffsets[3] = 0x0600; // Arabic - fOffsets[4] = 0x0900; // Devanagari - fOffsets[5] = 0x3040; // Hiragana - fOffsets[6] = 0x30A0; // Katakana - fOffsets[7] = 0xFF00; // Fullwidth ASCII - - - fCurrentWindow = 0; // Make current window Latin-1 - fMode = SINGLEBYTEMODE; // Always start in single-byte mode - fBufferLength = 0; // Empty buffer + fOffsets[0] = 0x0080; // Latin-1 + fOffsets[1] = 0x00C0; // Latin-1 Supplement + Latin Extended-A + fOffsets[2] = 0x0400; // Cyrillic + fOffsets[3] = 0x0600; // Arabic + fOffsets[4] = 0x0900; // Devanagari + fOffsets[5] = 0x3040; // Hiragana + fOffsets[6] = 0x30A0; // Katakana + fOffsets[7] = 0xFF00; // Fullwidth ASCII + + fCurrentWindow = 0; // Make current window Latin-1 + fMode = SINGLEBYTEMODE; // Always start in single-byte mode + fBufferLength = 0; // Empty buffer } } diff --git a/src/com/android/exoplayer/MediaFormatUtil.java b/src/com/android/exoplayer/MediaFormatUtil.java index d7a981f6..151c6dd5 100644 --- a/src/com/android/exoplayer/MediaFormatUtil.java +++ b/src/com/android/exoplayer/MediaFormatUtil.java @@ -16,9 +16,7 @@ package com.google.android.exoplayer; import android.support.annotation.Nullable; - import com.google.android.exoplayer.util.MimeTypes; - import java.nio.ByteBuffer; import java.util.ArrayList; @@ -26,9 +24,9 @@ import java.util.ArrayList; public class MediaFormatUtil { /** - * Creates {@link MediaFormat} from {@link android.media.MediaFormat}. - * Since {@link com.google.android.exoplayer.TrackRenderer} uses {@link MediaFormat}, - * {@link android.media.MediaFormat} should be converted to be used with ExoPlayer. + * Creates {@link MediaFormat} from {@link android.media.MediaFormat}. Since {@link + * com.google.android.exoplayer.TrackRenderer} uses {@link MediaFormat}, {@link + * android.media.MediaFormat} should be converted to be used with ExoPlayer. */ public static MediaFormat createMediaFormat(android.media.MediaFormat format) { String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); @@ -51,15 +49,36 @@ public class MediaFormatUtil { initializationData.add(data); buffer.flip(); } - long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) - ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; - int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT - : MediaFormat.NO_VALUE; - MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, - maxInputSize, durationUs, width, height, rotationDegrees, MediaFormat.NO_VALUE, - channelCount, sampleRate, language, MediaFormat.OFFSET_SAMPLE_RELATIVE, - initializationData, false, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, pcmEncoding, - encoderDelay, encoderPadding, null, MediaFormat.NO_VALUE); + long durationUs = + format.containsKey(android.media.MediaFormat.KEY_DURATION) + ? format.getLong(android.media.MediaFormat.KEY_DURATION) + : C.UNKNOWN_TIME_US; + int pcmEncoding = + MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : MediaFormat.NO_VALUE; + MediaFormat mediaFormat = + new MediaFormat( + null, + mimeType, + MediaFormat.NO_VALUE, + maxInputSize, + durationUs, + width, + height, + rotationDegrees, + MediaFormat.NO_VALUE, + channelCount, + sampleRate, + language, + MediaFormat.OFFSET_SAMPLE_RELATIVE, + initializationData, + false, + MediaFormat.NO_VALUE, + MediaFormat.NO_VALUE, + pcmEncoding, + encoderDelay, + encoderPadding, + null, + MediaFormat.NO_VALUE); mediaFormat.setFrameworkFormatV16(format); return mediaFormat; } @@ -72,5 +91,4 @@ public class MediaFormatUtil { private static int getOptionalIntegerV16(android.media.MediaFormat format, String key) { return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE; } - } diff --git a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java b/src/com/android/exoplayer/MediaSoftwareCodecUtil.java index 8c2509d4..cf74f106 100644 --- a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java +++ b/src/com/android/exoplayer/MediaSoftwareCodecUtil.java @@ -21,9 +21,7 @@ import android.media.MediaCodecList; import android.text.TextUtils; import android.util.Log; import android.util.Pair; - import com.google.android.exoplayer.util.MimeTypes; - import java.util.HashMap; /** @@ -35,8 +33,8 @@ public class MediaSoftwareCodecUtil { /** * Thrown when an error occurs querying the device for its underlying media capabilities. - *

- * Such failures are not expected in normal operation and are normally temporary (e.g. if the + * + *

Such failures are not expected in normal operation and are normally temporary (e.g. if the * mediaserver process has crashed and is yet to restart). */ public static class DecoderQueryException extends Exception { @@ -44,15 +42,12 @@ public class MediaSoftwareCodecUtil { private DecoderQueryException(Throwable cause) { super("Failed to query underlying media codecs", cause); } - } private static final HashMap> sSwCodecs = new HashMap<>(); - /** - * Gets information about the software decoder that will be used for a given mime type. - */ + /** Gets information about the software decoder that will be used for a given mime type. */ public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure) throws DecoderQueryException { // TODO: Add a test for this method. @@ -64,11 +59,10 @@ public class MediaSoftwareCodecUtil { return new DecoderInfo(info.first, info.second); } - /** - * Returns the name of the software decoder and its capabilities for the given mimeType. - */ + /** Returns the name of the software decoder and its capabilities for the given mimeType. */ private static synchronized Pair - getMediaSoftwareCodecInfo(String mimeType, boolean secure) throws DecoderQueryException { + getMediaSoftwareCodecInfo(String mimeType, boolean secure) + throws DecoderQueryException { CodecKey key = new CodecKey(mimeType, secure); if (sSwCodecs.containsKey(key)) { return sSwCodecs.get(key); @@ -81,8 +75,12 @@ public class MediaSoftwareCodecUtil { mediaCodecList = new MediaCodecListCompatV16(); codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); if (codecInfo != null) { - Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType - + ". Assuming: " + codecInfo.first); + Log.w( + TAG, + "MediaCodecList API didn't list secure decoder for: " + + mimeType + + ". Assuming: " + + codecInfo.first); } } return codecInfo; @@ -108,29 +106,33 @@ public class MediaSoftwareCodecUtil { for (int i = 0; i < numberOfCodecs; i++) { MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); - if (!info.isEncoder() && codecName.startsWith("OMX.google.") + if (!info.isEncoder() + && codecName.startsWith("OMX.google.") && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { String[] supportedTypes = info.getSupportedTypes(); for (String supportedType : supportedTypes) { if (supportedType.equalsIgnoreCase(mimeType)) { MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(supportedType); - boolean secure = mediaCodecList.isSecurePlaybackSupported( - key.mimeType, capabilities); + boolean secure = + mediaCodecList.isSecurePlaybackSupported( + key.mimeType, capabilities); if (!secureDecodersExplicit) { // Cache variants for both insecure and (if we think it's supported) // secure playback. - sSwCodecs.put(key.secure ? new CodecKey(mimeType, false) : key, + sSwCodecs.put( + key.secure ? new CodecKey(mimeType, false) : key, Pair.create(codecName, capabilities)); if (secure) { - sSwCodecs.put(key.secure ? key : new CodecKey(mimeType, true), + sSwCodecs.put( + key.secure ? key : new CodecKey(mimeType, true), Pair.create(codecName + ".secure", capabilities)); } } else { // Only cache this variant. If both insecure and secure decoders are // available, they should both be listed separately. sSwCodecs.put( - key.secure == secure ? key: new CodecKey(mimeType, secure), + key.secure == secure ? key : new CodecKey(mimeType, secure), Pair.create(codecName, capabilities)); } if (sSwCodecs.containsKey(key)) { @@ -146,9 +148,7 @@ public class MediaSoftwareCodecUtil { private interface MediaCodecListCompat { - /** - * Returns the number of codecs in the list. - */ + /** Returns the number of codecs in the list. */ int getCodecCount(); /** @@ -158,19 +158,16 @@ public class MediaSoftwareCodecUtil { */ MediaCodecInfo getCodecInfoAt(int index); - /** - * Returns whether secure decoders are explicitly listed, if present. - */ + /** Returns whether secure decoders are explicitly listed, if present. */ boolean secureDecodersExplicit(); /** - * Returns true if secure playback is supported for the given - * {@link android.media.MediaCodecInfo.CodecCapabilities}, which should - * have been obtained from a {@link MediaCodecInfo} obtained from this list. + * Returns true if secure playback is supported for the given {@link + * android.media.MediaCodecInfo.CodecCapabilities}, which should have been obtained from a + * {@link MediaCodecInfo} obtained from this list. */ - boolean isSecurePlaybackSupported(String mimeType, - MediaCodecInfo.CodecCapabilities capabilities); - + boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities); } @TargetApi(21) @@ -202,8 +199,8 @@ public class MediaSoftwareCodecUtil { } @Override - public boolean isSecurePlaybackSupported(String mimeType, - MediaCodecInfo.CodecCapabilities capabilities) { + public boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { return capabilities.isFeatureSupported( MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); } @@ -213,7 +210,6 @@ public class MediaSoftwareCodecUtil { mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); } } - } @SuppressWarnings("deprecation") @@ -235,13 +231,12 @@ public class MediaSoftwareCodecUtil { } @Override - public boolean isSecurePlaybackSupported(String mimeType, - MediaCodecInfo.CodecCapabilities capabilities) { + public boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { // Secure decoders weren't explicitly listed prior to API level 21. We assume that // a secure H264 decoder exists. return MimeTypes.VIDEO_H264.equals(mimeType); } - } private static final class CodecKey { @@ -274,7 +269,5 @@ public class MediaSoftwareCodecUtil { CodecKey other = (CodecKey) obj; return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; } - } - } diff --git a/src/com/android/exoplayer/text/SubtitleView.java b/src/com/android/exoplayer/text/SubtitleView.java index 37926eda..e930ef2d 100644 --- a/src/com/android/exoplayer/text/SubtitleView.java +++ b/src/com/android/exoplayer/text/SubtitleView.java @@ -32,27 +32,20 @@ import android.text.TextPaint; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; - import com.google.android.exoplayer.util.Util; - import java.util.ArrayList; /** - * Since this class does not exist in recent version of ExoPlayer and used by - * {@link com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from - * older version of ExoPlayer. - * A view for rendering a single caption. + * Since this class does not exist in recent version of ExoPlayer and used by {@link + * com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from older version of + * ExoPlayer. A view for rendering a single caption. */ @Deprecated public class SubtitleView extends View { - /** - * Ratio of inner padding to font size. - */ + /** Ratio of inner padding to font size. */ private static final float INNER_PADDING_RATIO = 0.125f; - /** - * Temporary rectangle used for computing line bounds. - */ + /** Temporary rectangle used for computing line bounds. */ private final RectF mLineBounds = new RectF(); // Styled dimensions. @@ -93,8 +86,12 @@ public class SubtitleView extends View { public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - int[] viewAttr = {android.R.attr.text, android.R.attr.textSize, - android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; + int[] viewAttr = { + android.R.attr.text, + android.R.attr.textSize, + android.R.attr.lineSpacingExtra, + android.R.attr.lineSpacingMultiplier + }; TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0); CharSequence text = a.getText(0); int textSize = a.getDimensionPixelSize(1, 15); @@ -250,8 +247,9 @@ public class SubtitleView extends View { mHasMeasurements = true; mLastMeasuredWidth = maxWidth; - mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, - mSpacingMult, mSpacingAdd, true); + mLayout = + new StaticLayout( + mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true); return true; } @@ -320,5 +318,4 @@ public class SubtitleView extends View { textPaint.setShadowLayer(0, 0, 0, 0); c.restoreToCount(saveCount); } - } diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java index 2b7817dc..321e19da 100644 --- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java +++ b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java @@ -17,11 +17,10 @@ package com.google.android.exoplayer2.ext.ffmpeg; import android.content.Context; import android.content.pm.PackageManager; +import com.android.tv.common.SoftPreconditions; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.util.MimeTypes; -import com.android.tv.common.SoftPreconditions; - import java.nio.ByteBuffer; /** diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java index daa77340..a33d4020 100644 --- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java +++ b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java @@ -19,9 +19,8 @@ import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.MimeTypes; /** - * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2 - * in order to support mp2 decoder. - * Configures and queries the underlying native library. + * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2 in + * order to support mp2 decoder. Configures and queries the underlying native library. */ public final class FfmpegLibrary { @@ -39,23 +38,17 @@ public final class FfmpegLibrary { LOADER.setLibraries(libraries); } - /** - * Returns whether the underlying library is available, loading it if necessary. - */ + /** Returns whether the underlying library is available, loading it if necessary. */ public static boolean isAvailable() { return LOADER.isAvailable(); } - /** - * Returns the version of the underlying library if available, or null otherwise. - */ + /** Returns the version of the underlying library if available, or null otherwise. */ public static String getVersion() { return isAvailable() ? ffmpegGetVersion() : null; } - /** - * Returns whether the underlying library supports the specified MIME type. - */ + /** Returns whether the underlying library supports the specified MIME type. */ public static boolean supportsFormat(String mimeType) { if (!isAvailable()) { return false; @@ -64,9 +57,7 @@ public final class FfmpegLibrary { return codecName != null && ffmpegHasDecoder(codecName); } - /** - * Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. - */ + /** Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. */ /* package */ static String getCodecName(String mimeType) { switch (mimeType) { case MimeTypes.AUDIO_MPEG_L2: @@ -79,6 +70,6 @@ public final class FfmpegLibrary { } private static native String ffmpegGetVersion(); - private static native boolean ffmpegHasDecoder(String codecName); + private static native boolean ffmpegHasDecoder(String codecName); } diff --git a/src/com/android/tv/ApplicationSingletons.java b/src/com/android/tv/ApplicationSingletons.java index ac7d4c4a..f9eaf58e 100644 --- a/src/com/android/tv/ApplicationSingletons.java +++ b/src/com/android/tv/ApplicationSingletons.java @@ -32,9 +32,7 @@ import com.android.tv.perf.PerformanceMonitor; import com.android.tv.util.AccountHelper; import com.android.tv.util.TvInputManagerHelper; -/** - * Interface with getters for application scoped singletons. - */ +/** Interface with getters for application scoped singletons. */ public interface ApplicationSingletons { Analytics getAnalytics(); diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java index 4fca06ac..f4bfdb9a 100644 --- a/src/com/android/tv/AudioManagerHelper.java +++ b/src/com/android/tv/AudioManagerHelper.java @@ -4,13 +4,10 @@ import android.app.Activity; import android.content.Context; import android.media.AudioManager; import android.os.Build; - import com.android.tv.receiver.AudioCapabilitiesReceiver; import com.android.tv.ui.TunableTvView; -/** - * A helper class to help {@link MainActivity} to handle audio-related stuffs. - */ +/** A helper class to help {@link MainActivity} to handle audio-related stuffs. */ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { private static final float AUDIO_MAX_VOLUME = 1.0f; private static final float AUDIO_MIN_VOLUME = 0.0f; @@ -28,20 +25,22 @@ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { mActivity = activity; mTvView = tvView; mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); - mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(activity, - new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() { - @Override - public void onAc3PassthroughCapabilityChange(boolean capability) { - mAc3PassthroughSupported = capability; - } - }); + mAudioCapabilitiesReceiver = + new AudioCapabilitiesReceiver( + activity, + new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() { + @Override + public void onAc3PassthroughCapabilityChange(boolean capability) { + mAc3PassthroughSupported = capability; + } + }); mAudioCapabilitiesReceiver.register(); } /** - * Sets suitable volume to {@link TunableTvView} according to the current audio focus. - * If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP - * mode, this method will finish the activity. + * Sets suitable volume to {@link TunableTvView} according to the current audio focus. If the + * focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP mode, this + * method will finish the activity. */ void setVolumeByAudioFocusStatus() { if (mTvView.isPlaying()) { @@ -71,31 +70,28 @@ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { * returned result. */ void requestAudioFocus() { - int result = mAudioManager.requestAudioFocus(this, - AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ? - AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS; + int result = + mAudioManager.requestAudioFocus( + this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + mAudioFocusStatus = + (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) + ? AudioManager.AUDIOFOCUS_GAIN + : AudioManager.AUDIOFOCUS_LOSS; setVolumeByAudioFocusStatus(); } - /** - * Abandons audio focus. - */ + /** Abandons audio focus. */ void abandonAudioFocus() { mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; mAudioManager.abandonAudioFocus(this); } - /** - * Returns {@code true} if the device supports AC3 pass-through. - */ + /** Returns {@code true} if the device supports AC3 pass-through. */ boolean isAc3PassthroughSupported() { return mAc3PassthroughSupported; } - /** - * Release the resources the helper class may occupied. - */ + /** Release the resources the helper class may occupied. */ void release() { mAudioCapabilitiesReceiver.unregister(); } diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java index faa27bbd..c6fb329c 100644 --- a/src/com/android/tv/ChannelTuner.java +++ b/src/com/android/tv/ChannelTuner.java @@ -24,12 +24,10 @@ import android.support.annotation.MainThread; 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.util.TvInputManagerHelper; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -57,11 +55,9 @@ public class ChannelTuner { private final Handler mHandler = new Handler(); private final ChannelDataManager mChannelDataManager; private final Set mListeners = new ArraySet<>(); - @Nullable - private Channel mCurrentChannel; + @Nullable private Channel mCurrentChannel; private final TvInputManagerHelper mInputManager; - @Nullable - private TvInputInfo mCurrentChannelInputInfo; + @Nullable private TvInputInfo mCurrentChannelInputInfo; private final ChannelDataManager.Listener mChannelDataManagerListener = new ChannelDataManager.Listener() { @@ -86,16 +82,14 @@ public class ChannelTuner { l.onBrowsableChannelListChanged(); } } - }; + }; public ChannelTuner(ChannelDataManager channelDataManager, TvInputManagerHelper inputManager) { mChannelDataManager = channelDataManager; mInputManager = inputManager; } - /** - * Starts ChannelTuner. It cannot be called twice before calling {@link #stop}. - */ + /** Starts ChannelTuner. It cannot be called twice before calling {@link #stop}. */ public void start() { if (mStarted) { throw new IllegalStateException("start is called twice"); @@ -103,18 +97,17 @@ public class ChannelTuner { mStarted = true; mChannelDataManager.addListener(mChannelDataManagerListener); if (mChannelDataManager.isDbLoadFinished()) { - mHandler.post(new Runnable() { - @Override - public void run() { - mChannelDataManagerListener.onLoadFinished(); - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + mChannelDataManagerListener.onLoadFinished(); + } + }); } } - /** - * Stops ChannelTuner. - */ + /** Stops ChannelTuner. */ public void stop() { if (!mStarted) { return; @@ -130,30 +123,22 @@ public class ChannelTuner { mChannelDataManagerLoaded = false; } - /** - * Returns true, if all the channels are loaded. - */ + /** Returns true, if all the channels are loaded. */ public boolean areAllChannelsLoaded() { return mChannelDataManagerLoaded; } - /** - * Returns browsable channel lists. - */ + /** Returns browsable channel lists. */ public List getBrowsableChannelList() { return Collections.unmodifiableList(mBrowsableChannels); } - /** - * Returns the number of browsable channels. - */ + /** Returns the number of browsable channels. */ public int getBrowsableChannelCount() { return mBrowsableChannels.size(); } - /** - * Returns the current channel. - */ + /** Returns the current channel. */ @Nullable public Channel getCurrentChannel() { return mCurrentChannel; @@ -169,16 +154,12 @@ public class ChannelTuner { mCurrentChannel = currentChannel; } - /** - * Returns the current channel's ID. - */ + /** Returns the current channel's ID. */ public long getCurrentChannelId() { return mCurrentChannel != null ? mCurrentChannel.getId() : Channel.INVALID_ID; } - /** - * Returns the current channel's URI - */ + /** Returns the current channel's URI */ public Uri getCurrentChannelUri() { if (mCurrentChannel == null) { return null; @@ -190,17 +171,13 @@ public class ChannelTuner { } } - /** - * Returns the current {@link TvInputInfo}. - */ + /** Returns the current {@link TvInputInfo}. */ @Nullable public TvInputInfo getCurrentInputInfo() { return mCurrentChannelInputInfo; } - /** - * Returns true, if the current channel is for a passthrough TV input. - */ + /** Returns true, if the current channel is for a passthrough TV input. */ public boolean isCurrentChannelPassthrough() { return mCurrentChannel != null && mCurrentChannel.isPassthrough(); } @@ -208,8 +185,8 @@ public class ChannelTuner { /** * Moves the current channel to the next (or previous) browsable channel. * - * @return true, if the channel is changed to the adjacent channel. If there is no - * browsable channel, it returns false. + * @return true, if the channel is changed to the adjacent channel. If there is no browsable + * channel, it returns false. */ public boolean moveToAdjacentBrowsableChannel(boolean up) { Channel channel = getAdjacentBrowsableChannel(up); @@ -221,8 +198,8 @@ public class ChannelTuner { } /** - * Returns a next browsable channel. It doesn't change the current channel unlike - * {@link #moveToAdjacentBrowsableChannel}. + * Returns a next browsable channel. It doesn't change the current channel unlike {@link + * #moveToAdjacentBrowsableChannel}. */ public Channel getAdjacentBrowsableChannel(boolean up) { if (isCurrentChannelPassthrough() || getBrowsableChannelCount() == 0) { @@ -240,8 +217,7 @@ public class ChannelTuner { } int size = mChannels.size(); for (int i = 0; i < size; ++i) { - int nextChannelIndex = up ? channelIndex + 1 + i - : channelIndex - 1 - i + size; + int nextChannelIndex = up ? channelIndex + 1 + i : channelIndex - 1 - i + size; if (nextChannelIndex >= size) { nextChannelIndex -= size; } @@ -289,7 +265,7 @@ public class ChannelTuner { * as a browsable channel. * * @return true, the channel change is success. But, if the channel doesn't exist, the channel - * change will be failed and it will return false. + * change will be failed and it will return false. */ public boolean moveToChannel(Channel channel) { if (channel == null) { @@ -308,43 +284,29 @@ public class ChannelTuner { return false; } - /** - * Resets the current channel to {@code null}. - */ + /** Resets the current channel to {@code null}. */ public void resetCurrentChannel() { setCurrentChannelAndNotify(null); } - /** - * Adds {@link Listener}. - */ + /** Adds {@link Listener}. */ public void addListener(Listener listener) { mListeners.add(listener); } - /** - * Removes {@link Listener}. - */ + /** Removes {@link Listener}. */ public void removeListener(Listener listener) { mListeners.remove(listener); } public interface Listener { - /** - * Called when all the channels are loaded. - */ + /** Called when all the channels are loaded. */ void onLoadFinished(); - /** - * Called when the browsable channel list is changed. - */ + /** Called when the browsable channel list is changed. */ void onBrowsableChannelListChanged(); - /** - * Called when the current channel is removed. - */ + /** Called when the current channel is removed. */ void onCurrentChannelUnavailable(Channel channel); - /** - * Called when the current channel is changed. - */ + /** Called when the current channel is changed. */ void onChannelChanged(Channel previousChannel, Channel currentChannel); } diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java index 2052f2e7..3f8e56a8 100644 --- a/src/com/android/tv/Features.java +++ b/src/com/android/tv/Features.java @@ -16,30 +16,26 @@ package com.android.tv; +import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; +import static com.android.tv.common.feature.FeatureUtils.AND; +import static com.android.tv.common.feature.FeatureUtils.OFF; +import static com.android.tv.common.feature.FeatureUtils.ON; +import static com.android.tv.common.feature.FeatureUtils.OR; + import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.feature.Feature; import com.android.tv.common.feature.GServiceFeature; import com.android.tv.common.feature.PropertyFeature; -import com.android.tv.config.RemoteConfig; import com.android.tv.experiments.Experiments; import com.android.tv.util.LocationUtils; import com.android.tv.util.PermissionUtils; import com.android.tv.util.Utils; - import java.util.Locale; -import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; -import static com.android.tv.common.feature.FeatureUtils.AND; -import static com.android.tv.common.feature.FeatureUtils.OFF; -import static com.android.tv.common.feature.FeatureUtils.ON; -import static com.android.tv.common.feature.FeatureUtils.OR; - /** * List of {@link Feature} for the Live TV App. * @@ -78,14 +74,13 @@ public final class Features { } // This is special handling just for USB Tuner. - // It does not require any N API's but relies on a improvements in N for AC3 support + // It does not require any N API's but relies on a improvements in N for AC3 + // support return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; } }; - /** - * Use network tuner if it is available and there is no other tuner types. - */ + /** Use network tuner if it is available and there is no other tuner types. */ public static final Feature NETWORK_TUNER = new Feature() { @Override @@ -97,23 +92,24 @@ public final class Features { // Network tuner will be enabled for developers. return true; } - return Locale.US.getCountry().equalsIgnoreCase( - LocationUtils.getCurrentCountry(context)); + return Locale.US + .getCountry() + .equalsIgnoreCase(LocationUtils.getCurrentCountry(context)); } }; private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide"; - /** - * A flag which indicates that LC app is unhidden even when there is no input. - */ + /** A flag which indicates that LC app is unhidden even when there is no input. */ public static final Feature UNHIDE = - OR(new GServiceFeature(GSERVICE_KEY_UNHIDE, false), new Feature() { - @Override - public boolean isEnabled(Context context) { - // If LC app runs as non-system app, we unhide the app. - return !PermissionUtils.hasAccessAllEpg(context); - } - }); + OR( + new GServiceFeature(GSERVICE_KEY_UNHIDE, false), + new Feature() { + @Override + public boolean isEnabled(Context context) { + // If LC app runs as non-system app, we unhide the app. + return !PermissionUtils.hasAccessAllEpg(context); + } + }); public static final Feature PICTURE_IN_PICTURE = new Feature() { @@ -162,9 +158,7 @@ public final class Features { /** Show postal code fragment before channel scan. */ public static final Feature ENABLE_CLOUD_EPG_REGION = new Feature() { - private final String[] SUPPORTED_REGIONS = { - }; - + private final String[] SUPPORTED_REGIONS = {}; @Override public boolean isEnabled(Context context) { @@ -186,19 +180,14 @@ public final class Features { /** Enable a conflict dialog between currently watched channel and upcoming recording. */ public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF; - /** - * Use input blacklist to disable partner's tuner input. - */ + /** Use input blacklist to disable partner's tuner input. */ public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON; - /** - * Enable Dvb parsers and listeners. - */ + /** Enable Dvb parsers and listeners. */ public static final Feature ENABLE_FILE_DVB = OFF; @VisibleForTesting public static final Feature TEST_FEATURE = new PropertyFeature("test_feature", false); - private Features() { - } + private Features() {} } diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java index 2978f409..709ed4a4 100644 --- a/src/com/android/tv/InputSessionManager.java +++ b/src/com/android/tv/InputSessionManager.java @@ -36,28 +36,27 @@ 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.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnTuneListener; import com.android.tv.util.TvInputManagerHelper; - import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** - * Manages input sessions. - * Responsible for: + * Manages input sessions. Responsible for: + * *

    - *
  • Manage {@link TvView} sessions and recording sessions
  • - *
  • Manage capabilities (conflict)
  • + *
  • Manage {@link TvView} sessions and recording sessions + *
  • Manage capabilities (conflict) *
- *

- * As TvView's methods should be called on the main thread and the {@link RecordingSession} should - * look at the state of the {@link TvViewSession} when it calls the framework methods, the framework - * calls in RecordingSession are made on the main thread not to introduce the multi-thread problems. + * + *

As TvView's methods should be called on the main thread and the {@link RecordingSession} + * should look at the state of the {@link TvViewSession} when it calls the framework methods, the + * framework calls in RecordingSession are made on the main thread not to introduce the multi-thread + * problems. */ @TargetApi(Build.VERSION_CODES.N) public class InputSessionManager { @@ -82,22 +81,20 @@ public class InputSessionManager { /** * Creates the session for {@link TvView}. - *

- * Do not call {@link TvView#setCallback} after the session is created. + * + *

Do not call {@link TvView#setCallback} after the session is created. */ @MainThread @NonNull - public TvViewSession createTvViewSession(TvView tvView, TunableTvView tunableTvView, - TvInputCallback callback) { + public TvViewSession createTvViewSession( + TvView tvView, TunableTvView tunableTvView, TvInputCallback callback) { TvViewSession session = new TvViewSession(tvView, tunableTvView, callback); mTvViewSessions.add(session); if (DEBUG) Log.d(TAG, "TvView session created: " + session); return session; } - /** - * Releases the {@link TvView} session. - */ + /** Releases the {@link TvView} session. */ @MainThread public void releaseTvViewSession(TvViewSession session) { mTvViewSessions.remove(session); @@ -105,12 +102,14 @@ public class InputSessionManager { if (DEBUG) Log.d(TAG, "TvView session released: " + session); } - /** - * Creates the session for recording. - */ + /** Creates the session for recording. */ @NonNull - public RecordingSession createRecordingSession(String inputId, String tag, - RecordingCallback callback, Handler handler, long endTimeMs) { + public RecordingSession createRecordingSession( + String inputId, + String tag, + RecordingCallback callback, + Handler handler, + long endTimeMs) { RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs); mRecordingSessions.add(session); if (DEBUG) Log.d(TAG, "Recording session created: " + session); @@ -120,9 +119,7 @@ public class InputSessionManager { return session; } - /** - * Releases the recording session. - */ + /** Releases the recording session. */ public void releaseRecordingSession(RecordingSession session) { mRecordingSessions.remove(session); session.release(); @@ -132,17 +129,13 @@ public class InputSessionManager { } } - /** - * Adds the {@link OnTvViewChannelChangeListener}. - */ + /** Adds the {@link OnTvViewChannelChangeListener}. */ @MainThread public void addOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) { mOnTvViewChannelChangeListeners.add(listener); } - /** - * Removes the {@link OnTvViewChannelChangeListener}. - */ + /** Removes the {@link OnTvViewChannelChangeListener}. */ @MainThread public void removeOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) { mOnTvViewChannelChangeListeners.remove(listener); @@ -176,9 +169,7 @@ public class InputSessionManager { return null; } - /** - * Retruns the earliest end time of recording sessions in progress of the certain TV input. - */ + /** Retruns the earliest end time of recording sessions in progress of the certain TV input. */ @MainThread public Long getEarliestRecordingSessionEndTimeMs(String inputId) { long timeMs = Long.MAX_VALUE; @@ -240,8 +231,8 @@ public class InputSessionManager { /** * The session for {@link TvView}. - *

- * The methods which create or release session for the TV input should be called through this + * + *

The methods which create or release session for the TV input should be called through this * session. */ @MainThread @@ -261,31 +252,32 @@ public class InputSessionManager { mTvView = tvView; mTunableTvView = tunableTvView; mCallback = callback; - mTvView.setCallback(new DelegateTvInputCallback(mCallback) { - @Override - public void onConnectionFailed(String inputId) { - if (DEBUG) Log.d(TAG, "TvViewSession: connection failed"); - mTuned = false; - mNeedToBeRetuned = false; - super.onConnectionFailed(inputId); - notifyTvViewChannelChange(null); - } + mTvView.setCallback( + new DelegateTvInputCallback(mCallback) { + @Override + public void onConnectionFailed(String inputId) { + if (DEBUG) Log.d(TAG, "TvViewSession: connection failed"); + mTuned = false; + mNeedToBeRetuned = false; + super.onConnectionFailed(inputId); + notifyTvViewChannelChange(null); + } - @Override - public void onDisconnected(String inputId) { - if (DEBUG) Log.d(TAG, "TvViewSession: disconnected"); - mTuned = false; - mNeedToBeRetuned = false; - super.onDisconnected(inputId); - notifyTvViewChannelChange(null); - } - }); + @Override + public void onDisconnected(String inputId) { + if (DEBUG) Log.d(TAG, "TvViewSession: disconnected"); + mTuned = false; + mNeedToBeRetuned = false; + super.onDisconnected(inputId); + notifyTvViewChannelChange(null); + } + }); } /** * Tunes to the channel. - *

- * As this is called only for the warming up, there's no need to be retuned. + * + *

As this is called only for the warming up, there's no need to be retuned. */ public void tune(String inputId, Uri channelUri) { if (DEBUG) { @@ -299,13 +291,22 @@ public class InputSessionManager { notifyTvViewChannelChange(channelUri); } - /** - * Tunes to the channel. - */ + /** Tunes to the channel. */ public void tune(Channel channel, Bundle params, OnTuneListener listener) { if (DEBUG) { - Log.d(TAG, "tune: {session=" + this + ", channel=" + channel + ", params=" + params - + ", listener=" + listener + ", mTuned=" + mTuned + "}"); + Log.d( + TAG, + "tune: {session=" + + this + + ", channel=" + + channel + + ", params=" + + params + + ", listener=" + + listener + + ", mTuned=" + + mTuned + + "}"); } mChannel = channel; mInputId = channel.getInputId(); @@ -313,8 +314,10 @@ public class InputSessionManager { mParams = params; mOnTuneListener = listener; TvInputInfo input = mInputManager.getTvInputInfo(mInputId); - if (input == null || (input.canRecord() && !isTunedForRecording(mChannelUri) - && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) { + if (input == null + || (input.canRecord() + && !isTunedForRecording(mChannelUri) + && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) { if (DEBUG) { if (input == null) { Log.d(TAG, "Can't find input for input ID: " + mInputId); @@ -354,9 +357,7 @@ public class InputSessionManager { notifyTvViewChannelChange(null); } - /** - * Resets this TvView. - */ + /** Resets this TvView. */ public void reset() { if (DEBUG) Log.d(TAG, "Reset TvView session"); mTuned = false; @@ -366,8 +367,8 @@ public class InputSessionManager { } void resetByRecording() { - mCallback.onVideoUnavailable(mInputId, - TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE); + mCallback.onVideoUnavailable( + mInputId, TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE); if (mTuned) { if (DEBUG) Log.d(TAG, "Reset TvView session by recording"); mTunableTvView.resetByRecording(); @@ -379,8 +380,8 @@ public class InputSessionManager { /** * The session for recording. - *

- * The caller is responsible for releasing the session when the error occurs. + * + *

The caller is responsible for releasing the session when the error occurs. */ public class RecordingSession { private final String mInputId; @@ -391,8 +392,12 @@ public class InputSessionManager { private TvRecordingClient mClient; private boolean mTuned; - RecordingSession(String inputId, String tag, RecordingCallback callback, - Handler handler, long endTimeMs) { + RecordingSession( + String inputId, + String tag, + RecordingCallback callback, + Handler handler, + long endTimeMs) { mInputId = inputId; mCallback = callback; mHandler = handler; @@ -402,83 +407,90 @@ public class InputSessionManager { void release() { if (DEBUG) Log.d(TAG, "Release of recording session requested."); - runOnHandler(mMainThreadHandler, new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Releasing of recording session."); - mTuned = false; - mClient.release(); - mClient = null; - for (TvViewSession session : mTvViewSessions) { - if (DEBUG) { - Log.d(TAG, "Finding TvView sessions for retune: {tuned=" - + session.mTuned + ", inputId=" + session.mInputId - + ", session=" + session + "}"); - } - if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) { - session.retune(); - break; + runOnHandler( + mMainThreadHandler, + new Runnable() { + @Override + public void run() { + if (DEBUG) Log.d(TAG, "Releasing of recording session."); + mTuned = false; + mClient.release(); + mClient = null; + for (TvViewSession session : mTvViewSessions) { + if (DEBUG) { + Log.d( + TAG, + "Finding TvView sessions for retune: {tuned=" + + session.mTuned + + ", inputId=" + + session.mInputId + + ", session=" + + session + + "}"); + } + if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) { + session.retune(); + break; + } + } } - } - } - }); + }); } - /** - * Tunes to the channel for recording. - */ + /** Tunes to the channel for recording. */ public void tune(String inputId, Uri channelUri) { - runOnHandler(mMainThreadHandler, new Runnable() { - @Override - public void run() { - int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId); - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - if (input == null || !input.canRecord() - || input.getTunerCount() <= tunedRecordingSessionCount) { - runOnHandler(mHandler, new Runnable() { - @Override - public void run() { - mCallback.onConnectionFailed(inputId); + runOnHandler( + mMainThreadHandler, + new Runnable() { + @Override + public void run() { + int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId); + TvInputInfo input = mInputManager.getTvInputInfo(inputId); + if (input == null + || !input.canRecord() + || input.getTunerCount() <= tunedRecordingSessionCount) { + runOnHandler( + mHandler, + new Runnable() { + @Override + public void run() { + mCallback.onConnectionFailed(inputId); + } + }); + return; } - }); - return; - } - mTuned = true; - int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId); - if (!isTunedForTvView(channelUri) && tunedTuneSessionCount > 0 - && tunedRecordingSessionCount + tunedTuneSessionCount - >= input.getTunerCount()) { - for (TvViewSession session : mTvViewSessions) { - if (session.mTuned && Objects.equals(session.mInputId, inputId) - && !isTunedForRecording(session.mChannelUri)) { - session.resetByRecording(); - break; + mTuned = true; + int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId); + if (!isTunedForTvView(channelUri) + && tunedTuneSessionCount > 0 + && tunedRecordingSessionCount + tunedTuneSessionCount + >= input.getTunerCount()) { + for (TvViewSession session : mTvViewSessions) { + if (session.mTuned + && Objects.equals(session.mInputId, inputId) + && !isTunedForRecording(session.mChannelUri)) { + session.resetByRecording(); + break; + } + } } + mChannelUri = channelUri; + mClient.tune(inputId, channelUri); } - } - mChannelUri = channelUri; - mClient.tune(inputId, channelUri); - } - }); + }); } - /** - * Starts recording. - */ + /** Starts recording. */ public void startRecording(Uri programHintUri) { mClient.startRecording(programHintUri); } - /** - * Stops recording. - */ + /** Stops recording. */ public void stopRecording() { mClient.stopRecording(); } - /** - * Sets recording session's ending time. - */ + /** Sets recording session's ending time. */ public void setEndTimeMs(long endTimeMs) { mEndTimeMs = endTimeMs; } @@ -555,9 +567,7 @@ public class InputSessionManager { } } - /** - * Called when the {@link TvView} channel is changed. - */ + /** Called when the {@link TvView} channel is changed. */ public interface OnTvViewChannelChangeListener { void onTvViewChannelChange(@Nullable Uri channelUri); } diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java index e03952da..545d49b1 100644 --- a/src/com/android/tv/LauncherActivity.java +++ b/src/com/android/tv/LauncherActivity.java @@ -26,17 +26,16 @@ import android.util.Log; /** * An activity to launch a new activity. * - *

In the case when {@link MainActivity} starts a new activity using - * {@link Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is - * terminated if the new activity crashes. That's because the {@link android.app.ActivityManager} - * terminates the activity which is just below the crashed activity in the activity stack. To avoid - * this, we need to locate an additional activity between these activities in the activity stack. + *

In the case when {@link MainActivity} starts a new activity using {@link + * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is terminated if + * the new activity crashes. That's because the {@link android.app.ActivityManager} terminates the + * activity which is just below the crashed activity in the activity stack. To avoid this, we need + * to locate an additional activity between these activities in the activity stack. */ public class LauncherActivity extends Activity { private static final String TAG = "LauncherActivity"; - public static final String ERROR_MESSAGE - = "com.android.tv.LauncherActivity.ErrorMessage"; + public static final String ERROR_MESSAGE = "com.android.tv.LauncherActivity.ErrorMessage"; private static final int REQUEST_CODE_DEFAULT = 0; private static final int REQUEST_START_ACTIVITY = 100; @@ -45,14 +44,12 @@ public class LauncherActivity extends Activity { private static final String EXTRA_REQUEST_RESULT = "com.android.tv.LauncherActivity.REQUEST_RESULT"; - /** - * Starts an activity by calling {@link Activity#startActivity}. - */ + /** Starts an activity by calling {@link Activity#startActivity}. */ public static void startActivitySafe(Activity baseActivity, Intent intentToLaunch) { // To avoid the app termination when the new activity crashes, LauncherActivity should be // started by calling startActivityForResult(). - baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, false), - REQUEST_CODE_DEFAULT); + baseActivity.startActivityForResult( + createIntent(baseActivity, intentToLaunch, false), REQUEST_CODE_DEFAULT); } /** @@ -60,19 +57,19 @@ public class LauncherActivity extends Activity { * *

Note: {@code requestCode} should not be 0. The value is reserved for internal use. */ - public static void startActivityForResultSafe(Activity baseActivity, Intent intentToLaunch, - int requestCode) { + public static void startActivityForResultSafe( + Activity baseActivity, Intent intentToLaunch, int requestCode) { if (requestCode == REQUEST_CODE_DEFAULT) { throw new IllegalArgumentException("requestCode should not be 0."); } // To avoid the app termination when the new activity crashes, LauncherActivity should be // started by calling startActivityForResult(). - baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, true), - requestCode); + baseActivity.startActivityForResult( + createIntent(baseActivity, intentToLaunch, true), requestCode); } - private static Intent createIntent(Context context, Intent intentToLaunch, - boolean requestResult) { + private static Intent createIntent( + Context context, Intent intentToLaunch, boolean requestResult) { Intent intent = new Intent(context, LauncherActivity.class); intent.putExtra(EXTRA_INTENT, intentToLaunch); if (requestResult) { @@ -98,8 +95,7 @@ public class LauncherActivity extends Activity { } } catch (ActivityNotFoundException e) { Log.w(TAG, "Activity not found for " + intent); - intent.putExtra(ERROR_MESSAGE, - getResources().getString(R.string.msg_missing_app)); + intent.putExtra(ERROR_MESSAGE, getResources().getString(R.string.msg_missing_app)); setResult(Activity.RESULT_CANCELED, intent); finish(); } diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index ed5f79a1..427d562a 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -62,7 +62,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.Toast; - import com.android.tv.analytics.SendChannelStatusRunnable; import com.android.tv.analytics.SendConfigInfoRunnable; import com.android.tv.analytics.Tracker; @@ -124,7 +123,6 @@ import com.android.tv.ui.sidepanel.MultiAudioFragment; import com.android.tv.ui.sidepanel.SettingsFragment; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment; -import com.android.tv.util.AccountHelper; import com.android.tv.util.CaptionSettings; import com.android.tv.util.Debug; import com.android.tv.util.DurationTimer; @@ -139,7 +137,6 @@ import com.android.tv.util.TvSettings; import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; import com.android.tv.util.ViewCache; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -150,17 +147,20 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; -/** - * The main activity for the Live TV app. - */ +/** The main activity for the Live TV app. */ public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener { private static final String TAG = "MainActivity"; private static final boolean DEBUG = false; @Retention(RetentionPolicy.SOURCE) - @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED, - KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY}) + @IntDef({ + KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, + KEY_EVENT_HANDLER_RESULT_NOT_HANDLED, + KEY_EVENT_HANDLER_RESULT_HANDLED, + KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY + }) public @interface KeyHandlerResultType {} + public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0; public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1; public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2; @@ -171,7 +171,6 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private static final float FRAME_RATE_FOR_FILM = 23.976f; private static final float FRAME_RATE_EPSILON = 0.1f; - private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1; private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; @@ -196,8 +195,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW); } - private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter(); + static { SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF); @@ -244,10 +243,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private View mContentView; private TunableTvView mTvView; private Bundle mTuneParams; - @Nullable - private Uri mInitChannelUri; - @Nullable - private String mParentInputIdWhenScreenOff; + @Nullable private Uri mInitChannelUri; + @Nullable private String mParentInputIdWhenScreenOff; private boolean mScreenOffIntentReceived; private boolean mShowProgramGuide; private boolean mShowSelectInputView; @@ -302,68 +299,74 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private final Handler mHandler = new MainActivityHandler(this); private final Set mOnActionClickListeners = new ArraySet<>(); - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Intent.ACTION_SCREEN_OFF: - if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); - // We need to stop TvView, when the screen is turned off. If not and TIS uses - // MediaPlayer, a device may not go to the sleep mode and audio can be heard, - // because MediaPlayer keeps playing media by its wake lock. - mScreenOffIntentReceived = true; - markCurrentChannelDuringScreenOff(); - stopAll(true); - break; - case Intent.ACTION_SCREEN_ON: - if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); - if (!mActivityResumed && mVisibleBehind) { - // ACTION_SCREEN_ON is usually called after onResume. But, if media is - // played under launcher with requestVisibleBehind(true), onResume will - // not be called. In this case, we need to resume TvView explicitly. - resumeTvIfNeeded(); - } - break; - case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED: - if (DEBUG) Log.d(TAG, "Received parental control settings change"); - applyParentalControlSettings(); - checkChannelLockNeeded(mTvView, null); - break; - case Intent.ACTION_TIME_CHANGED: - // Re-tune the current channel to prevent incorrect behavior of trick-play. - // See: b/37393628 - if (mChannelTuner.getCurrentChannel() != null) { - tune(true); + private final BroadcastReceiver mBroadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case Intent.ACTION_SCREEN_OFF: + if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); + // We need to stop TvView, when the screen is turned off. If not and TIS + // uses + // MediaPlayer, a device may not go to the sleep mode and audio can be + // heard, + // because MediaPlayer keeps playing media by its wake lock. + mScreenOffIntentReceived = true; + markCurrentChannelDuringScreenOff(); + stopAll(true); + break; + case Intent.ACTION_SCREEN_ON: + if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); + if (!mActivityResumed && mVisibleBehind) { + // ACTION_SCREEN_ON is usually called after onResume. But, if media + // is + // played under launcher with requestVisibleBehind(true), onResume + // will + // not be called. In this case, we need to resume TvView explicitly. + resumeTvIfNeeded(); + } + break; + case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED: + if (DEBUG) Log.d(TAG, "Received parental control settings change"); + applyParentalControlSettings(); + checkChannelLockNeeded(mTvView, null); + break; + case Intent.ACTION_TIME_CHANGED: + // Re-tune the current channel to prevent incorrect behavior of + // trick-play. + // See: b/37393628 + if (mChannelTuner.getCurrentChannel() != null) { + tune(true); + } + break; } - break; - } - } - }; + } + }; private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener = new OnCurrentProgramUpdatedListener() { - @Override - public void onCurrentProgramUpdated(long channelId, Program program) { - // Do not update channel banner by this notification - // when the time shifting is available. - if (mTimeShiftManager.isAvailable()) { - return; - } - Channel channel = mTvView.getCurrentChannel(); - if (channel != null && channel.getId() == channelId) { - mOverlayManager.updateChannelBannerAndShowIfNeeded( - TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program); - } - } - }; + @Override + public void onCurrentProgramUpdated(long channelId, Program program) { + // Do not update channel banner by this notification + // when the time shifting is available. + if (mTimeShiftManager.isAvailable()) { + return; + } + Channel channel = mTvView.getCurrentChannel(); + if (channel != null && channel.getId() == channelId) { + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); + mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program); + } + } + }; private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() { @Override public void onLoadFinished() { - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "MainActivity.mChannelTunerListener.onLoadFinished"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("MainActivity.mChannelTunerListener.onLoadFinished"); SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable(); if (mActivityResumed) { resumeTvIfNeeded(); @@ -389,30 +392,33 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP public void onChannelChanged(Channel previousChannel, Channel currentChannel) {} }; - private final Runnable mRestoreMainViewRunnable = new Runnable() { - @Override - public void run() { - restoreMainTvView(); - } - }; + private final Runnable mRestoreMainViewRunnable = + new Runnable() { + @Override + public void run() { + restoreMainTvView(); + } + }; private ProgramGuideSearchFragment mSearchFragment; - private final TvInputCallback mTvInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId) - && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) { - Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this); - startActivity(intent); - TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false); - SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId); - } - } - }; + private final TvInputCallback mTvInputCallback = + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + if (Features.TUNER.isEnabled(MainActivity.this) + && mTunerInputId.equals(inputId) + && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) { + Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this); + startActivity(intent); + TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false); + SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId); + } + } + }; private void applyParentalControlSettings() { - boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings() - .isParentalControlsEnabled(); + boolean parentalControlEnabled = + mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled(); mTvView.onParentalControlChanged(parentalControlEnabled); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately(); @@ -430,7 +436,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP startUpDebugTimer.start(); } startUpDebugTimer.log("MainActivity.onCreate"); - if (DEBUG) Log.d(TAG,"onCreate()"); + if (DEBUG) Log.d(TAG, "onCreate()"); TvApplication.setCurrentRunningProcess(this, true); super.onCreate(savedInstanceState); ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this); @@ -446,12 +452,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // In API 23, TvContract.isChannelUriForPassthroughInput is hidden. boolean isPassthroughInput = TvContract.isChannelUriForPassthroughInput(getIntent().getData()); - boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction()) - && isPassthroughInput; - boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished() - && mChannelDataManager.getChannelCount() <= 0; + boolean tuneToPassthroughInput = + Intent.ACTION_VIEW.equals(getIntent().getAction()) && isPassthroughInput; + boolean channelLoadedAndNoChannelAvailable = + mChannelDataManager.isDbLoadFinished() + && mChannelDataManager.getChannelCount() <= 0; if ((OnboardingUtils.isFirstRunWithCurrentVersion(this) - || channelLoadedAndNoChannelAvailable) + || channelLoadedAndNoChannelAvailable) && !tuneToPassthroughInput && !TvCommonUtils.isRunningInTest()) { startOnboardingActivity(); @@ -462,32 +469,33 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mTvInputManagerHelper = tvApplication.getTvInputManagerHelper(); mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view); mTvView.initialize(mProgramDataManager, mTvInputManagerHelper); - mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() { - @Override - public boolean onUnhandledInputEvent(InputEvent event) { - if (isKeyEventBlocked()) { - return true; - } - if (event instanceof KeyEvent) { - KeyEvent keyEvent = (KeyEvent) event; - if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) { - if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) { + mTvView.setOnUnhandledInputEventListener( + new OnUnhandledInputEventListener() { + @Override + public boolean onUnhandledInputEvent(InputEvent event) { + if (isKeyEventBlocked()) { return true; } + if (event instanceof KeyEvent) { + KeyEvent keyEvent = (KeyEvent) event; + if (keyEvent.getAction() == KeyEvent.ACTION_DOWN + && keyEvent.isLongPress()) { + if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) { + return true; + } + } + if (keyEvent.getAction() == KeyEvent.ACTION_UP) { + return onKeyUp(keyEvent.getKeyCode(), keyEvent); + } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + return onKeyDown(keyEvent.getKeyCode(), keyEvent); + } + } + return false; } - if (keyEvent.getAction() == KeyEvent.ACTION_UP) { - return onKeyUp(keyEvent.getKeyCode(), keyEvent); - } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { - return onKeyDown(keyEvent.getKeyCode(), keyEvent); - } - } - return false; - } - }); + }); long channelId = Utils.getLastWatchedChannelId(this); String inputId = Utils.getLastWatchedTunerInputId(this); - if (!isPassthroughInput && inputId != null - && channelId != Channel.INVALID_ID) { + if (!isPassthroughInput && inputId != null && channelId != Channel.INVALID_ID) { mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId)); } @@ -500,8 +508,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mTvInputManagerHelper.addCallback(mTvInputCallback); } mTunerInputId = TunerTvInputService.getInputId(this); - mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID, - mOnCurrentProgramUpdatedListener); + mProgramDataManager.addOnCurrentProgramUpdatedListener( + Channel.INVALID_ID, mOnCurrentProgramUpdatedListener); mProgramDataManager.setPrefetchEnabled(true); mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper); mChannelTuner.addListener(mChannelTunerListener); @@ -512,88 +520,119 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (CommonFeatures.DVR.isEnabled(this)) { mDvrManager = tvApplication.getDvrManager(); } - mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker, - new OnCurrentProgramUpdatedListener() { - @Override - public void onCurrentProgramUpdated(long channelId, Program program) { - mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(), - program); - switch (mTimeShiftManager.getLastActionId()) { - case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND: - case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD: - case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS: - case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT: - mOverlayManager.updateChannelBannerAndShowIfNeeded( - TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); - break; - case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE: - case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY: - default: - mOverlayManager.updateChannelBannerAndShowIfNeeded( - TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); - break; - } - } - }); + mTimeShiftManager = + new TimeShiftManager( + this, + mTvView, + mProgramDataManager, + mTracker, + new OnCurrentProgramUpdatedListener() { + @Override + public void onCurrentProgramUpdated(long channelId, Program program) { + mMediaSessionWrapper.update( + mTvView.isBlocked(), getCurrentChannel(), program); + switch (mTimeShiftManager.getLastActionId()) { + case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND: + case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD: + case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS: + case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT: + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager + .UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW); + break; + case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE: + case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY: + default: + mOverlayManager.updateChannelBannerAndShowIfNeeded( + TvOverlayManager + .UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO); + break; + } + } + }); DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); mDefaultRefreshRate = display.getRefreshRate(); if (!PermissionUtils.hasAccessWatchedHistory(this)) { - WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager( - getApplicationContext()); + WatchedHistoryManager watchedHistoryManager = + new WatchedHistoryManager(getApplicationContext()); watchedHistoryManager.start(); mTvView.setWatchedHistoryManager(watchedHistoryManager); } - mTvViewUiManager = new TvViewUiManager(this, mTvView, - (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager); + mTvViewUiManager = + new TvViewUiManager( + this, + mTvView, + (FrameLayout) findViewById(android.R.id.content), + mTvOptionsManager); mContentView = findViewById(android.R.id.content); ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container); - ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate( - R.layout.channel_banner, sceneContainer, false); - KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView) - getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false); - InputBannerView inputBannerView = (InputBannerView) getLayoutInflater() - .inflate(R.layout.input_banner, sceneContainer, false); - SelectInputView selectInputView = (SelectInputView) getLayoutInflater() - .inflate(R.layout.select_input, sceneContainer, false); - selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() { - @Override - public void onTunerInputSelected() { - Channel currentChannel = mChannelTuner.getCurrentChannel(); - if (currentChannel != null && !currentChannel.isPassthrough()) { - hideOverlays(); - } else { - tuneToLastWatchedChannelForTunerInput(); - } - } + ChannelBannerView channelBannerView = + (ChannelBannerView) + getLayoutInflater().inflate(R.layout.channel_banner, sceneContainer, false); + KeypadChannelSwitchView keypadChannelSwitchView = + (KeypadChannelSwitchView) + getLayoutInflater() + .inflate(R.layout.keypad_channel_switch, sceneContainer, false); + InputBannerView inputBannerView = + (InputBannerView) + getLayoutInflater().inflate(R.layout.input_banner, sceneContainer, false); + SelectInputView selectInputView = + (SelectInputView) + getLayoutInflater().inflate(R.layout.select_input, sceneContainer, false); + selectInputView.setOnInputSelectedCallback( + new OnInputSelectedCallback() { + @Override + public void onTunerInputSelected() { + Channel currentChannel = mChannelTuner.getCurrentChannel(); + if (currentChannel != null && !currentChannel.isPassthrough()) { + hideOverlays(); + } else { + tuneToLastWatchedChannelForTunerInput(); + } + } - @Override - public void onPassthroughInputSelected(@NonNull TvInputInfo input) { - Channel currentChannel = mChannelTuner.getCurrentChannel(); - String currentInputId = currentChannel == null ? null : currentChannel.getInputId(); - if (TextUtils.equals(input.getId(), currentInputId)) { - hideOverlays(); - } else { - tuneToChannel(Channel.createPassthroughChannel(input.getId())); - } - } + @Override + public void onPassthroughInputSelected(@NonNull TvInputInfo input) { + Channel currentChannel = mChannelTuner.getCurrentChannel(); + String currentInputId = + currentChannel == null ? null : currentChannel.getInputId(); + if (TextUtils.equals(input.getId(), currentInputId)) { + hideOverlays(); + } else { + tuneToChannel(Channel.createPassthroughChannel(input.getId())); + } + } - private void hideOverlays() { - getOverlayManager().hideOverlays( - TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); - } - }); + private void hideOverlays() { + getOverlayManager() + .hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + } + }); mSearchFragment = new ProgramGuideSearchFragment(); - mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager, - keypadChannelSwitchView, channelBannerView, inputBannerView, - selectInputView, sceneContainer, mSearchFragment); + mOverlayManager = + new TvOverlayManager( + this, + mChannelTuner, + mTvView, + mTvOptionsManager, + keypadChannelSwitchView, + channelBannerView, + inputBannerView, + selectInputView, + sceneContainer, + mSearchFragment); mAudioManagerHelper = new AudioManagerHelper(this, mTvView); mMediaSessionWrapper = new MediaSessionWrapper(this); @@ -606,11 +645,16 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mAccessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE); - mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1), - new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null); + mSendConfigInfoRecurringRunner = + new RecurringRunner( + this, + TimeUnit.DAYS.toMillis(1), + new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), + null); mSendConfigInfoRecurringRunner.start(); - mChannelStatusRecurringRunner = SendChannelStatusRunnable - .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager); + mChannelStatusRecurringRunner = + SendChannelStatusRunnable.startChannelStatusRecurringRunner( + this, mTracker, mChannelDataManager); // To avoid not updating Rating systems when changing language. mTvInputManagerHelper.getContentRatingsManager().update(); @@ -632,13 +676,14 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); float density = getResources().getDisplayMetrics().density; - mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density), + mTvViewUiManager.onConfigurationChanged( + (int) (newConfig.screenWidthDp * density), (int) (newConfig.screenHeightDp * density)); } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Start reload of dependent data @@ -650,14 +695,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP finish(); startActivity(intent); } else { - Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, - Toast.LENGTH_LONG).show(); + Toast.makeText( + this, + R.string.msg_read_tv_listing_permission_denied, + Toast.LENGTH_LONG) + .show(); finish(); } } } - @BlockScreenType private int getDesiredBlockScreenType() { + @BlockScreenType + private int getDesiredBlockScreenType() { if (!mActivityResumed) { return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI; } @@ -689,7 +738,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override protected void onNewIntent(Intent intent) { - if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent); + if (DEBUG) Log.d(TAG, "onNewIntent(): " + intent); if (mOverlayManager == null) { // It's called before onCreate. The intent will be handled at onCreate. b/30725058 return; @@ -705,7 +754,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override protected void onStart() { TimerEvent timer = mPerformanceMonitor.startTimer(); - if (DEBUG) Log.d(TAG,"onStart()"); + if (DEBUG) Log.d(TAG, "onStart()"); super.onStart(); mScreenOffIntentReceived = false; mActivityStarted = true; @@ -734,8 +783,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP super.onResume(); if (!PermissionUtils.hasAccessAllEpg(this) && checkSelfPermission(PERMISSION_READ_TV_LISTINGS) - != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS}, + != PackageManager.PERMISSION_GRANTED) { + requestPermissions( + new String[] {PERMISSION_READ_TV_LISTINGS}, PERMISSIONS_REQUEST_READ_TV_LISTINGS); } mTracker.sendScreenView(SCREEN_NAME); @@ -755,15 +805,16 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP Set failedScheduledRecordingInfoSet = Utils.getFailedScheduledRecordingInfoSet(getApplicationContext()); if (Utils.hasRecordingFailedReason( - getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE) + getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE) && !failedScheduledRecordingInfoSet.isEmpty()) { - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this, - failedScheduledRecordingInfoSet); - } - }); + runAfterAttachedToWindow( + new Runnable() { + @Override + public void run() { + DvrUiHelper.showDvrInsufficientSpaceErrorDialog( + MainActivity.this, failedScheduledRecordingInfoSet); + } + }); } if (mChannelTuner.areAllChannelsLoaded()) { @@ -779,28 +830,35 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mInputToSetUp = null; } else if (mShowProgramGuide) { mShowProgramGuide = false; - mHandler.post(new Runnable() { - // This will delay the start of the animation until after the Live Channel app is - // shown. Without this the animation is completed before it is actually visible on - // the screen. - @Override - public void run() { - mOverlayManager.showProgramGuide(); - } - }); + mHandler.post( + new Runnable() { + // This will delay the start of the animation until after the Live Channel + // app is + // shown. Without this the animation is completed before it is actually + // visible on + // the screen. + @Override + public void run() { + mOverlayManager.showProgramGuide(); + } + }); } else if (mShowSelectInputView) { mShowSelectInputView = false; - mHandler.post(new Runnable() { - // mShowSelectInputView is true when the activity is started/resumed because the - // TV_INPUT button was pressed in a different app. - // This will delay the start of the animation until after the Live Channel app is - // shown. Without this the animation is completed before it is actually visible on - // the screen. - @Override - public void run() { - mOverlayManager.showSelectInputView(); - } - }); + mHandler.post( + new Runnable() { + // mShowSelectInputView is true when the activity is started/resumed because + // the + // TV_INPUT button was pressed in a different app. + // This will delay the start of the animation until after the Live Channel + // app is + // shown. Without this the animation is completed before it is actually + // visible on + // the screen. + @Override + public void run() { + mOverlayManager.showSelectInputView(); + } + }); } if (mDvrConflictChecker != null) { mDvrConflictChecker.start(); @@ -832,16 +890,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP super.onPause(); } - /** - * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet. - */ + /** Returns true if {@link #onResume} is called and {@link #onPause} is not called yet. */ public boolean isActivityResumed() { return mActivityResumed; } - /** - * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet. - */ + /** Returns true if {@link #onStart} is called and {@link #onStop} is not called yet. */ public boolean isActivityStarted() { return mActivityStarted; } @@ -867,7 +921,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mTvView.unblockContent(unblockedRating); break; case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN: - mOverlayManager.getSideFragmentManager() + mOverlayManager + .getSideFragmentManager() .show(new ParentalControlsFragment(), false); // Pass through. case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN: @@ -881,7 +936,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private void resumeTvIfNeeded() { if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()"); - if (!mTvView.isPlaying() || mInitChannelUri != null + if (!mTvView.isPlaying() + || mInitChannelUri != null || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) { if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) { // The target input may not be ready yet, especially, just after screen on. @@ -917,9 +973,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // is playing, we stop the passthrough TV input. stopTv(); } - SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri) - || mChannelTuner.areAllChannelsLoaded(), - TAG, "startTV assumes that ChannelDataManager is already loaded."); + SoftPreconditions.checkState( + TvContract.isChannelUriForPassthroughInput(channelUri) + || mChannelTuner.areAllChannelsLoaded(), + TAG, + "startTV assumes that ChannelDataManager is already loaded."); if (mTvView.isPlaying()) { // TV has already started. if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) { @@ -952,8 +1010,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP Channel channel = mChannelDataManager.getChannel(channelId); if (channel == null || !mChannelTuner.moveToChannel(channel)) { mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0)); - Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. " - + "The first channel will be tuned to."); + Log.w( + TAG, + "The requested channel (id=" + + channelId + + ") doesn't exist. " + + "The first channel will be tuned to."); } } } @@ -985,9 +1047,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP super.onStop(); } - /** - * Handles screen off to keep the current channel for next screen on. - */ + /** Handles screen off to keep the current channel for next screen on. */ private void markCurrentChannelDuringScreenOff() { mInitChannelUri = mChannelTuner.getCurrentChannelUri(); if (mChannelTuner.isCurrentChannelPassthrough()) { @@ -1038,16 +1098,23 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); } catch (ActivityNotFoundException e) { mInputIdUnderSetup = null; - Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity, - input.loadLabel(this)), Toast.LENGTH_SHORT).show(); + Toast.makeText( + this, + getString( + R.string.msg_unable_to_start_setup_activity, + input.loadLabel(this)), + Toast.LENGTH_SHORT) + .show(); return; } if (calledByPopup) { - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + mOverlayManager.hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); } else { - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY); + mOverlayManager.hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY); } } @@ -1060,8 +1127,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP try { startActivitySafe(intent); } catch (ActivityNotFoundException e) { - Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings), - Toast.LENGTH_SHORT).show(); + Toast.makeText( + this, + getString(R.string.msg_unable_to_start_system_captioning_settings), + Toast.LENGTH_SHORT) + .show(); } } @@ -1085,16 +1155,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return mTimeShiftManager; } - /** - * Returns the instance of {@link TvOverlayManager}. - */ + /** Returns the instance of {@link TvOverlayManager}. */ public TvOverlayManager getOverlayManager() { return mOverlayManager; } - /** - * Returns the {@link ConflictChecker}. - */ + /** Returns the {@link ConflictChecker}. */ @Nullable public ConflictChecker getDvrConflictChecker() { return mDvrConflictChecker; @@ -1109,9 +1175,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } /** - * Returns the current program which the user is watching right now.

+ * Returns the current program which the user is watching right now. * - * It might be a live program. If the time shifting is available, it can be a past program, too. + *

It might be a live program. If the time shifting is available, it can be a past program, + * too. */ public Program getCurrentProgram() { if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) { @@ -1122,9 +1189,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } /** - * Returns the current playing time in milliseconds.

+ * Returns the current playing time in milliseconds. * - * If the time shifting is available, the time is the playing position of the program, + *

If the time shifting is available, the time is the playing position of the program, * otherwise, the system current time. */ public long getCurrentPlayingPosition() { @@ -1161,9 +1228,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP LauncherActivity.startActivityForResultSafe(this, intent, requestCode); } - /** - * Show settings fragment. - */ + /** Show settings fragment. */ public void showSettingsFragment() { if (!mChannelTuner.areAllChannelsLoaded()) { // Show ChannelSourcesFragment only if all the channels are loaded. @@ -1180,8 +1245,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP * It is called when shrunken TvView is desired, such as EditChannelFragment and * ChannelsLockedFragment. */ - public void startShrunkenTvView(boolean showLockedChannelsTemporarily, - boolean willMainViewBeTunerInput) { + public void startShrunkenTvView( + boolean showLockedChannelsTemporarily, boolean willMainViewBeTunerInput) { mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel(); mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser; mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel; @@ -1214,19 +1279,21 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel. if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) { final Channel channel = returnChannel; - Runnable tuneAction = new Runnable() { - @Override - public void run() { - tuneToChannel(channel); - if (mChannelBeforeShrunkenTvView == null - || !mChannelBeforeShrunkenTvView.equals(channel)) { - Utils.setLastWatchedChannel(MainActivity.this, channel); - } - mIsCompletingShrunkenTvView = false; - mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser; - mTvView.setBlockScreenType(getDesiredBlockScreenType()); - } - }; + Runnable tuneAction = + new Runnable() { + @Override + public void run() { + tuneToChannel(channel); + if (mChannelBeforeShrunkenTvView == null + || !mChannelBeforeShrunkenTvView.equals(channel)) { + Utils.setLastWatchedChannel(MainActivity.this, channel); + } + mIsCompletingShrunkenTvView = false; + mIsCurrentChannelUnblockedByUser = + mWasChannelUnblockedBeforeShrunkenByUser; + mTvView.setBlockScreenType(getDesiredBlockScreenType()); + } + }; mTvViewUiManager.fadeOutTvView(tuneAction); // Will automatically fade-in when video becomes available. } else { @@ -1247,7 +1314,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP */ public boolean isScreenBlockedByResourceConflictOrParentalControl() { return mTvView.getVideoUnavailableReason() - == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked(); + == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE + || mTvView.isBlocked(); } @Override @@ -1257,8 +1325,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); String text; if (count > 0) { - text = getResources().getQuantityString(R.plurals.msg_channel_added, - count, count); + text = + getResources() + .getQuantityString(R.plurals.msg_channel_added, count, count); } else { text = getString(R.string.msg_no_channel_added); } @@ -1325,16 +1394,15 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event); } - /** - * Notifies the key input focus is changed to the TV view. - */ + /** Notifies the key input focus is changed to the TV view. */ public void updateKeyInputFocus() { - mHandler.post(new Runnable() { - @Override - public void run() { - mTvView.setBlockScreenType(getDesiredBlockScreenType()); - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + mTvView.setBlockScreenType(getDesiredBlockScreenType()); + } + }); } // It should be called before onResume. @@ -1360,12 +1428,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) { - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - mOverlayManager.showSetupFragment(); - } - }); + runAfterAttachedToWindow( + new Runnable() { + @Override + public void run() { + mOverlayManager.showSetupFragment(); + } + }); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { Uri uri = intent.getData(); if (Utils.isProgramsUri(uri)) { @@ -1388,8 +1457,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } if ((!Utils.isChannelUriForOneChannel(mInitChannelUri) && !Utils.isChannelUriForInput(mInitChannelUri))) { - Log.w(TAG, "Malformed channel uri " + mInitChannelUri - + " tuning to default instead"); + Log.w( + TAG, + "Malformed channel uri " + mInitChannelUri + " tuning to default instead"); mInitChannelUri = null; return true; } @@ -1419,16 +1489,20 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP String inputId = mInitChannelUri.getQueryParameter("input"); long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId); if (channelId == Channel.INVALID_ID) { - String[] projection = { Channels._ID }; + String[] projection = {Channels._ID}; long time = System.currentTimeMillis(); - try (Cursor cursor = getContentResolver().query(uri, projection, - null, null, null)) { + try (Cursor cursor = + getContentResolver().query(uri, projection, null, null, null)) { if (cursor != null && cursor.moveToNext()) { channelId = cursor.getLong(0); } } - Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity queries DB for " - + "last channel check (" + (System.currentTimeMillis() - time) + "ms)"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log( + "MainActivity queries DB for " + + "last channel check (" + + (System.currentTimeMillis() - time) + + "ms)"); } if (channelId == Channel.INVALID_ID) { // Couldn't find any channel probably because the input hasn't been set up. @@ -1462,7 +1536,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mAudioManagerHelper.abandonAudioFocus(); mMediaSessionWrapper.setPlaybackState(false); } - TvApplication.getSingletons(this).getMainActivityWrapper() + TvApplication.getSingletons(this) + .getMainActivityWrapper() .notifyCurrentChannelChange(this, null); mChannelTuner.resetCurrentChannel(); mTunePending = false; @@ -1473,9 +1548,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS); } - /** - * Says {@code text} when accessibility is turned on. - */ + /** Says {@code text} when accessibility is turned on. */ private void sendAccessibilityText(String text) { if (mAccessibilityManager.isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain(); @@ -1532,27 +1605,31 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return; } if (mTvInputManagerHelper.getTunerTvInputSize() == 1) { - mOverlayManager.getSideFragmentManager().show( - new CustomizeChannelListFragment()); + mOverlayManager + .getSideFragmentManager() + .show(new CustomizeChannelListFragment()); } else { mOverlayManager.showSetupFragment(); } return; } - if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment + if (!TvCommonUtils.isRunningInTest() + && mShowNewSourcesFragment && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { // Show new channel sources fragment. - runAfterAttachedToWindow(new Runnable() { - @Override - public void run() { - mOverlayManager.runAfterOverlaysAreClosed(new Runnable() { + runAfterAttachedToWindow( + new Runnable() { @Override public void run() { - mOverlayManager.showNewSourcesFragment(); + mOverlayManager.runAfterOverlaysAreClosed( + new Runnable() { + @Override + public void run() { + mOverlayManager.showNewSourcesFragment(); + } + }); } }); - } - }); } setupUtils.onTuned(); if (mTuneParams != null) { @@ -1571,9 +1648,11 @@ 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() ? - Utils.loadLabel(this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId())) - : channel.getDisplayText()); + sendAccessibilityText( + !mChannelTuner.isCurrentChannelPassthrough() + ? Utils.loadLabel( + this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId())) + : channel.getDisplayText()); boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener); mOnTuneListener.onTune(channel, isUnderShrunkenTvView()); @@ -1592,7 +1671,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP addToRecentChannels(channel.getId()); } Utils.setLastWatchedChannel(this, channel); - TvApplication.getSingletons(this).getMainActivityWrapper() + TvApplication.getSingletons(this) + .getMainActivityWrapper() .notifyCurrentChannelChange(this, channel); } // We have to provide channel here instead of using TvView's channel, because TvView's @@ -1619,34 +1699,42 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // If the activity is paused shortly, runnable may not be called because all the fragments // should be closed when the activity is paused. private void runAfterAttachedToWindow(final Runnable runnable) { - final Runnable runOnlyIfActivityIsResumed = new Runnable() { - @Override - public void run() { - if (mActivityResumed) { - runnable.run(); - } - } - }; + final Runnable runOnlyIfActivityIsResumed = + new Runnable() { + @Override + public void run() { + if (mActivityResumed) { + runnable.run(); + } + } + }; if (mContentView.isAttachedToWindow()) { mHandler.post(runOnlyIfActivityIsResumed); } else { - mContentView.getViewTreeObserver().addOnWindowAttachListener( - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - mContentView.getViewTreeObserver().removeOnWindowAttachListener(this); - mHandler.post(runOnlyIfActivityIsResumed); - } + mContentView + .getViewTreeObserver() + .addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + mContentView + .getViewTreeObserver() + .removeOnWindowAttachListener(this); + mHandler.post(runOnlyIfActivityIsResumed); + } - @Override - public void onWindowDetached() { } - }); + @Override + public void onWindowDetached() {} + }); } } boolean isNowPlayingProgram(Channel channel, Program program) { - return program == null ? (channel != null && getCurrentProgram() == null - && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram()); + return program == null + ? (channel != null + && getCurrentProgram() == null + && channel.equals(getCurrentChannel())) + : program.equals(getCurrentProgram()); } private void addToRecentChannels(long channelId) { @@ -1659,9 +1747,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mOverlayManager.getMenu().onRecentChannelsChanged(); } - /** - * Returns the recently tuned channels. - */ + /** Returns the recently tuned channels. */ public ArrayDeque getRecentChannels() { return mRecentChannels; } @@ -1694,9 +1780,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } } - /** - * Hide the overlays when tuning to a channel from the menu (e.g. Channels). - */ + /** Hide the overlays when tuning to a channel from the menu (e.g. Channels). */ public void hideOverlaysForTune() { mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); } @@ -1712,8 +1796,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP setPreferredRefreshRate(mDefaultRefreshRate); mIsFilmModeSet = false; } else if (!mIsFilmModeSet && is24Fps) { - DisplayManager displayManager = (DisplayManager) getSystemService( - Context.DISPLAY_SERVICE); + DisplayManager displayManager = + (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); float[] refreshRates = display.getSupportedRefreshRates(); @@ -1745,8 +1829,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP String id = TvSettings.getMultiAudioId(this); String language = TvSettings.getMultiAudioLanguage(this); int channelCount = TvSettings.getMultiAudioChannelCount(this); - TvTrackInfo bestTrack = TvTrackInfoUtils - .getBestTrackInfo(tracks, id, language, channelCount); + TvTrackInfo bestTrack = + TvTrackInfoUtils.getBestTrackInfo(tracks, id, language, channelCount); if (bestTrack != null) { String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO); if (!bestTrack.getId().equals(selectedTrack)) { @@ -1787,8 +1871,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mTvOptionsManager.onClosedCaptionsChanged(track, i); } if (DEBUG) { - Log.d(TAG, "Subtitle Track Selected {id=" + track.getId() - + ", language=" + track.getLanguage() + "}"); + Log.d( + TAG, + "Subtitle Track Selected {id=" + + track.getId() + + ", language=" + + track.getLanguage() + + "}"); } return; } else if (alternativeTrack == null) { @@ -1801,12 +1890,17 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (!alternativeTrack.getId().equals(selectedTrackId)) { selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex); } else { - mTvOptionsManager - .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex); + mTvOptionsManager.onClosedCaptionsChanged( + alternativeTrack, alternativeTrackIndex); } if (DEBUG) { - Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId() - + ", language=" + alternativeTrack.getLanguage() + "}"); + Log.d( + TAG, + "Subtitle Track Selected {id=" + + alternativeTrack.getId() + + ", language=" + + alternativeTrack.getLanguage() + + "}"); } return; } @@ -1820,8 +1914,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } public void showProgramGuideSearchFragment() { - getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment) - .addToBackStack(null).commit(); + getFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, mSearchFragment) + .addToBackStack(null) + .commit(); } @Override @@ -1911,8 +2008,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP && mChannelTuner.getBrowsableChannelCount() > 0) { // message sending should be done before moving channel, because we use the // existence of message to decide if users are switching channel. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED, - System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_CHANNEL_UP_PRESSED, System.currentTimeMillis()), + CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); moveToAdjacentChannel(true, false); mTracker.sendChannelUp(); } @@ -1923,8 +2022,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP && mChannelTuner.getBrowsableChannelCount() > 0) { // message sending should be done before moving channel, because we use the // existence of message to decide if users are switching channel. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED, - System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_CHANNEL_DOWN_PRESSED, System.currentTimeMillis()), + CHANNEL_CHANGE_INITIAL_DELAY_MILLIS); moveToAdjacentChannel(false, false); mTracker.sendChannelDown(); } @@ -2009,9 +2110,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mTvView.isVideoOrAudioAvailable() && mTvView.getVideoUnavailableReason() - == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) { - DvrUiHelper.startSchedulesActivityForTuneConflict(this, - mChannelTuner.getCurrentChannel()); + == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) { + DvrUiHelper.startSchedulesActivityForTuneConflict( + this, mChannelTuner.getCurrentChannel()); return true; } if (!PermissionUtils.hasModifyParentalControls(this)) { @@ -2019,16 +2120,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } PinDialogFragment dialog = null; if (mTvView.isScreenBlocked()) { - dialog = PinDialogFragment - .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL); + dialog = + PinDialogFragment.create( + PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL); } else if (mTvView.isContentBlocked()) { - dialog = PinDialogFragment - .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, + dialog = + PinDialogFragment.create( + PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM, mTvView.getBlockedContentRating().flattenToString()); } if (dialog != null) { - mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog, - false); + mOverlayManager.showDialogFragment( + PinDialogFragment.DIALOG_TAG, dialog, false); } return true; case KeyEvent.KEYCODE_WINDOW: @@ -2065,66 +2168,81 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP break; } // Pass through. - case KeyEvent.KEYCODE_CAPTIONS: { - mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); - return true; - } + case KeyEvent.KEYCODE_CAPTIONS: + { + mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); + return true; + } case KeyEvent.KEYCODE_A: if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { break; } // Pass through. - case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { - mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); - return true; - } - case KeyEvent.KEYCODE_INFO: { - mOverlayManager.showBanner(); - return true; - } + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: + { + mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); + return true; + } + case KeyEvent.KEYCODE_INFO: + { + mOverlayManager.showBanner(); + return true; + } case KeyEvent.KEYCODE_MEDIA_RECORD: - case KeyEvent.KEYCODE_V: { - Channel currentChannel = getCurrentChannel(); - if (currentChannel != null && mDvrManager != null) { - boolean isRecording = - mDvrManager.getCurrentRecording(currentChannel.getId()) != null; - if (!isRecording) { - if (!mDvrManager.isChannelRecordable(currentChannel)) { - Toast.makeText(this, R.string.dvr_msg_cannot_record_program, - Toast.LENGTH_SHORT).show(); + case KeyEvent.KEYCODE_V: + { + Channel currentChannel = getCurrentChannel(); + if (currentChannel != null && mDvrManager != null) { + boolean isRecording = + mDvrManager.getCurrentRecording(currentChannel.getId()) != null; + if (!isRecording) { + if (!mDvrManager.isChannelRecordable(currentChannel)) { + Toast.makeText( + this, + R.string.dvr_msg_cannot_record_program, + Toast.LENGTH_SHORT) + .show(); + } else { + Program program = + mProgramDataManager.getCurrentProgram( + currentChannel.getId()); + DvrUiHelper.checkStorageStatusAndShowErrorMessage( + this, + currentChannel.getInputId(), + new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingCurrentProgram( + MainActivity.this, + currentChannel, + program, + false); + } + }); + } } else { - Program program = mProgramDataManager - .getCurrentProgram(currentChannel.getId()); - DvrUiHelper.checkStorageStatusAndShowErrorMessage(this, - currentChannel.getInputId(), new Runnable() { + DvrUiHelper.showStopRecordingDialog( + this, + currentChannel.getId(), + DvrStopRecordingFragment.REASON_USER_STOP, + new HalfSizedDialogFragment.OnActionClickListener() { @Override - public void run() { - DvrUiHelper.requestRecordingCurrentProgram( - MainActivity.this, - currentChannel, program, false); + public void onActionClick(long actionId) { + if (actionId + == DvrStopRecordingFragment.ACTION_STOP) { + ScheduledRecording currentRecording = + mDvrManager.getCurrentRecording( + currentChannel.getId()); + if (currentRecording != null) { + mDvrManager.stopRecording(currentRecording); + } + } } }); } - } else { - DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(), - DvrStopRecordingFragment.REASON_USER_STOP, - new HalfSizedDialogFragment.OnActionClickListener() { - @Override - public void onActionClick(long actionId) { - if (actionId == DvrStopRecordingFragment.ACTION_STOP) { - ScheduledRecording currentRecording = - mDvrManager.getCurrentRecording( - currentChannel.getId()); - if (currentRecording != null) { - mDvrManager.stopRecording(currentRecording); - } - } - } - }); } + return true; } - return true; - } } } if (keyCode == KeyEvent.KEYCODE_WINDOW) { @@ -2197,12 +2315,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // be shown during PIP stack resizing, because UI and its animation is stuck during // PIP resizing. mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); - mHandler.post(new Runnable() { - @Override - public void run() { - MainActivity.super.enterPictureInPictureMode(); - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + MainActivity.super.enterPictureInPictureMode(); + } + }); } @Override @@ -2248,7 +2367,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } if (isKeyEventBlocked()) { if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK - || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) { + || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) + && mNeedShowBackKeyGuide) { // KeyEvent.KEYCODE_BUTTON_B is also used like the back button. Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show(); mNeedShowBackKeyGuide = false; @@ -2296,24 +2416,23 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } /** - * This method just moves the channel in the channel map and updates the channel banner, - * but doesn't actually tune to the channel. - * The caller of this method should call {@link #tune} in the end. + * This method just moves the channel in the channel map and updates the channel banner, but + * doesn't actually tune to the channel. The caller of this method should call {@link #tune} in + * the end. * * @param channelUp {@code true} for channel up, and {@code false} for channel down. * @param fastTuning {@code true} if fast tuning is requested. */ private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) { if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) { - mOverlayManager.updateChannelBannerAndShowIfNeeded(fastTuning ? - TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST - : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); + mOverlayManager.updateChannelBannerAndShowIfNeeded( + fastTuning + ? TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST + : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); } } - /** - * Set the main TV view which holds HDMI-CEC active source based on the sound mode - */ + /** Set the main TV view which holds HDMI-CEC active source based on the sound mode */ private void restoreMainTvView() { mTvView.setMain(); } @@ -2355,8 +2474,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private void selectTrack(int type, TvTrackInfo track, int trackIndex) { mTvView.selectTrack(type, track == null ? null : track.getId()); if (type == TvTrackInfo.TYPE_AUDIO) { - mTvOptionsManager.onMultiAudioChanged(track == null ? null : - Utils.getMultiAudioString(this, track, false)); + mTvOptionsManager.onMultiAudioChanged( + track == null ? null : Utils.getMultiAudioString(this, track, false)); } else if (type == TvTrackInfo.TYPE_SUBTITLE) { mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex); } @@ -2428,50 +2547,38 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return; case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: default: - Toast.makeText(this, R.string.msg_channel_unavailable_unknown, - Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.msg_channel_unavailable_unknown, Toast.LENGTH_SHORT) + .show(); break; } } - /** - * Returns {@code true} if some overlay UI will be shown when the activity is resumed. - */ + /** Returns {@code true} if some overlay UI will be shown when the activity is resumed. */ public boolean willShowOverlayUiWhenResume() { return mInputToSetUp != null || mShowProgramGuide || mShowSelectInputView; } - /** - * Returns the current parental control settings. - */ + /** Returns the current parental control settings. */ public ParentalControlSettings getParentalControlSettings() { return mTvInputManagerHelper.getParentalControlSettings(); } - /** - * Returns a ContentRatingsManager instance. - */ + /** Returns a ContentRatingsManager instance. */ public ContentRatingsManager getContentRatingsManager() { return mTvInputManagerHelper.getContentRatingsManager(); } - /** - * Returns the current captioning settings. - */ + /** Returns the current captioning settings. */ public CaptionSettings getCaptionSettings() { return mCaptionSettings; } - /** - * Adds the {@link OnActionClickListener}. - */ + /** Adds the {@link OnActionClickListener}. */ public void addOnActionClickListener(OnActionClickListener listener) { mOnActionClickListeners.add(listener); } - /** - * Removes the {@link OnActionClickListener}. - */ + /** Removes the {@link OnActionClickListener}. */ public void removeOnActionClickListener(OnActionClickListener listener) { mOnActionClickListeners.remove(listener); } @@ -2505,16 +2612,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } mLazyInitialized = true; // Running initialization. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (mActivityStarted) { - initAnimations(); - initSideFragments(); - initMenuItemViews(); - } - } - }, LAZY_INITIALIZATION_DELAY); + mHandler.postDelayed( + new Runnable() { + @Override + public void run() { + if (mActivityStarted) { + initAnimations(); + initSideFragments(); + initMenuItemViews(); + } + } + }, + LAZY_INITIALIZATION_DELAY); } private void initAnimations() { @@ -2594,15 +2703,17 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (mTvView.isFadedOut()) { mTvView.removeFadeEffect(); } - Toast.makeText(MainActivity.this, R.string.msg_channel_unavailable_unknown, - Toast.LENGTH_SHORT).show(); + Toast.makeText( + MainActivity.this, + R.string.msg_channel_unavailable_unknown, + Toast.LENGTH_SHORT) + .show(); } @Override public void onStreamInfoChanged(StreamInfo info) { if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) { - mTracker.sendChannelTuneTime(info.getCurrentChannel(), - mTuneDurationTimer.reset()); + mTracker.sendChannelTuneTime(info.getCurrentChannel(), mTuneDurationTimer.reset()); } if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) { mOverlayManager.updateChannelBannerAndShowIfNeeded( @@ -2628,11 +2739,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (channel == null) { return; } - Channel currentChannel = - mChannelDataManager.getChannel(ContentUris.parseId(channel)); + Channel currentChannel = mChannelDataManager.getChannel(ContentUris.parseId(channel)); if (currentChannel == null) { - Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI " - + channel); + Log.e( + TAG, + "onChannelRetuned is called but can't find a channel with the URI " + + channel); return; } if (isChannelChangeKeyDownReceived()) { @@ -2647,15 +2759,16 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override public void onContentBlocked() { - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "MainActivity.MyOnTuneListener.onContentBlocked removes timer"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("MainActivity.MyOnTuneListener.onContentBlocked removes timer"); Debug.removeTimer(Debug.TAG_START_UP_TIMER); mTuneDurationTimer.reset(); TvContentRating rating = mTvView.getBlockedContentRating(); // When tuneTo was called while TV view was shrunken, if the channel id is the same // with the channel watched before shrunken, we allow the rating which was allowed // before. - if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken + if (mWasUnderShrunkenTvView + && mUnlockAllowedRatingBeforeShrunken && mChannelBeforeShrunkenTvView.equals(mChannel) && rating.equals(mAllowedRatingBeforeShrunken)) { mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView(); diff --git a/src/com/android/tv/MainActivityWrapper.java b/src/com/android/tv/MainActivityWrapper.java index 5af5079f..6a995cf1 100644 --- a/src/com/android/tv/MainActivityWrapper.java +++ b/src/com/android/tv/MainActivityWrapper.java @@ -20,14 +20,12 @@ 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 java.util.Set; /** - * A wrapper for safely getting the current {@link MainActivity}. - * Note that this class is not thread-safe. All the public methods should be called on main thread. + * A wrapper for safely getting the current {@link MainActivity}. Note that this class is not + * thread-safe. All the public methods should be called on main thread. */ @MainThread public final class MainActivityWrapper { @@ -36,39 +34,31 @@ public final class MainActivityWrapper { private final Set mListeners = new ArraySet<>(); /** - * Returns the current main activity. - * WARNING do not keep a reference to MainActivity, leaking activities is expensive. + * Returns the current main activity. WARNING do not keep a reference to MainActivity, + * leaking activities is expensive. */ MainActivity getMainActivity() { return mActivity; } - /** - * Checks if the given {@code activity} is the current main activity. - */ + /** Checks if the given {@code activity} is the current main activity. */ boolean isCurrent(MainActivity activity) { return activity != null && mActivity == activity; } - /** - * Sets the currently created main activity instance. - */ + /** Sets the currently created main activity instance. */ public void onMainActivityCreated(@NonNull MainActivity activity) { mActivity = activity; } - /** - * Unsets the main activity instance. - */ + /** Unsets the main activity instance. */ public void onMainActivityDestroyed(@NonNull MainActivity activity) { if (mActivity == activity) { mActivity = null; } } - /** - * Notifies the current channel change. - */ + /** Notifies the current channel change. */ void notifyCurrentChannelChange(@NonNull MainActivity caller, @Nullable Channel channel) { if (mActivity == caller) { for (OnCurrentChannelChangeListener listener : mListeners) { @@ -77,48 +67,34 @@ public final class MainActivityWrapper { } } - /** - * Checks if the main activity is created. - */ + /** Checks if the main activity is created. */ public boolean isCreated() { return mActivity != null; } - /** - * Checks if the main activity is started. - */ + /** Checks if the main activity is started. */ public boolean isStarted() { return mActivity != null && mActivity.isActivityStarted(); } - /** - * Checks if the main activity is resumed. - */ + /** Checks if the main activity is resumed. */ public boolean isResumed() { return mActivity != null && mActivity.isActivityResumed(); } - /** - * Adds OnCurrentChannelChangeListener. - */ + /** Adds OnCurrentChannelChangeListener. */ public void addOnCurrentChannelChangeListener(OnCurrentChannelChangeListener listener) { mListeners.add(listener); } - /** - * Removes OnCurrentChannelChangeListener. - */ + /** Removes OnCurrentChannelChangeListener. */ public void removeOnCurrentChannelChangeListener(OnCurrentChannelChangeListener listener) { mListeners.remove(listener); } - /** - * Listener for the current channel change in main activity. - */ + /** Listener for the current channel change in main activity. */ public interface OnCurrentChannelChangeListener { - /** - * Called when the current channel changes. - */ + /** Called when the current channel changes. */ void onCurrentChannelChange(@Nullable Channel channel); } } diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java index da6ad2a4..2cc898c3 100644 --- a/src/com/android/tv/MediaSessionWrapper.java +++ b/src/com/android/tv/MediaSessionWrapper.java @@ -29,7 +29,6 @@ import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.util.ImageLoader; @@ -41,12 +40,20 @@ import com.android.tv.util.Utils; */ class MediaSessionWrapper { private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession"; - private static PlaybackState MEDIA_SESSION_STATE_PLAYING = new PlaybackState.Builder() - .setState(PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f) - .build(); - private static PlaybackState MEDIA_SESSION_STATE_STOPPED = new PlaybackState.Builder() - .setState(PlaybackState.STATE_STOPPED, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f) - .build(); + private static PlaybackState MEDIA_SESSION_STATE_PLAYING = + new PlaybackState.Builder() + .setState( + PlaybackState.STATE_PLAYING, + PlaybackState.PLAYBACK_POSITION_UNKNOWN, + 1.0f) + .build(); + private static PlaybackState MEDIA_SESSION_STATE_STOPPED = + new PlaybackState.Builder() + .setState( + PlaybackState.STATE_STOPPED, + PlaybackState.PLAYBACK_POSITION_UNKNOWN, + 0.0f) + .build(); private final Context mContext; private final MediaSession mMediaSession; @@ -56,19 +63,21 @@ class MediaSessionWrapper { MediaSessionWrapper(Context context) { mContext = context; mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG); - mMediaSession.setCallback(new MediaSession.Callback() { - @Override - public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { - // Consume the media button event here. Should not send it to other apps. - return true; - } - }); - mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | - MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); - mNowPlayingCardWidth = mContext.getResources().getDimensionPixelSize( - R.dimen.notif_card_img_max_width); - mNowPlayingCardHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.notif_card_img_height); + mMediaSession.setCallback( + new MediaSession.Callback() { + @Override + public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { + // Consume the media button event here. Should not send it to other apps. + return true; + } + }); + mMediaSession.setFlags( + MediaSession.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mNowPlayingCardWidth = + mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); + mNowPlayingCardHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); } /** @@ -90,8 +99,8 @@ class MediaSessionWrapper { /** * Updates media session according to the current TV playback status. * - * @param blocked {@code true} if the current channel is blocked, either by user settings or - * the current program's content ratings. + * @param blocked {@code true} if the current channel is blocked, either by user settings or the + * current program's content ratings. * @param currentChannel The currently playing channel. * @param currentProgram The currently playing program. */ @@ -103,10 +112,12 @@ class MediaSessionWrapper { // If the channel is blocked, display a lock and a short text on the Now Playing Card if (blocked) { - Bitmap art = BitmapFactory.decodeResource(mContext.getResources(), - R.drawable.ic_message_lock_preview); - updateMediaMetadata(mContext.getResources() - .getString(R.string.channel_banner_locked_channel_title), art); + Bitmap art = + BitmapFactory.decodeResource( + mContext.getResources(), R.drawable.ic_message_lock_preview); + updateMediaMetadata( + mContext.getResources().getString(R.string.channel_banner_locked_channel_title), + art); setPlaybackState(true); return; } @@ -139,22 +150,32 @@ class MediaSessionWrapper { private String getChannelName(Channel channel) { if (channel.isPassthrough()) { - TvInputInfo input = TvApplication.getSingletons(mContext).getTvInputManagerHelper() - .getTvInputInfo(channel.getInputId()); + TvInputInfo input = + TvApplication.getSingletons(mContext) + .getTvInputManagerHelper() + .getTvInputInfo(channel.getInputId()); return Utils.loadLabel(mContext, input); } else { return channel.getDisplayName(); } } - private void updatePosterArt(Channel currentChannel, Program currentProgram, - String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) { + private void updatePosterArt( + Channel currentChannel, + Program currentProgram, + String cardTitleText, + @Nullable Bitmap posterArt, + @Nullable String posterArtUri) { if (posterArt != null) { updateMediaMetadata(cardTitleText, posterArt); } else if (posterArtUri != null) { - ImageLoader.loadBitmap(mContext, posterArtUri, mNowPlayingCardWidth, - mNowPlayingCardHeight, new ProgramPosterArtCallback(this, currentChannel, - currentProgram, cardTitleText)); + ImageLoader.loadBitmap( + mContext, + posterArtUri, + mNowPlayingCardWidth, + mNowPlayingCardHeight, + new ProgramPosterArtCallback( + this, currentChannel, currentProgram, cardTitleText)); } else { updateMediaMetadata(cardTitleText, R.drawable.default_now_card); } @@ -176,7 +197,7 @@ class MediaSessionWrapper { } private void updateMediaMetadata(final String title, final int imageResId) { - new AsyncTask () { + new AsyncTask() { @Override protected Void doInBackground(Void... arg0) { MediaMetadata.Builder builder = new MediaMetadata.Builder(); @@ -192,14 +213,17 @@ class MediaSessionWrapper { }.execute(); } - private static class ProgramPosterArtCallback extends - ImageLoader.ImageLoaderCallback { + private static class ProgramPosterArtCallback + extends ImageLoader.ImageLoaderCallback { private final Channel mChannel; private final Program mProgram; private final String mCardTitleText; - ProgramPosterArtCallback(MediaSessionWrapper sessionWrapper, Channel channel, - Program program, String cardTitleText) { + ProgramPosterArtCallback( + MediaSessionWrapper sessionWrapper, + Channel channel, + Program program, + String cardTitleText) { super(sessionWrapper); mChannel = channel; mProgram = program; diff --git a/src/com/android/tv/SelectInputActivity.java b/src/com/android/tv/SelectInputActivity.java index c68a1ad0..4487fbe2 100644 --- a/src/com/android/tv/SelectInputActivity.java +++ b/src/com/android/tv/SelectInputActivity.java @@ -23,15 +23,12 @@ 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.ui.SelectInputView; import com.android.tv.ui.SelectInputView.OnInputSelectedCallback; import com.android.tv.util.Utils; -/** - * An activity to select input. - */ +/** An activity to select input. */ public class SelectInputActivity extends Activity { private SelectInputView mSelectInputView; @@ -41,25 +38,31 @@ public class SelectInputActivity extends Activity { ((TvApplication) getApplicationContext()).setSelectInputActivity(this); setContentView(R.layout.activity_select_input); mSelectInputView = (SelectInputView) findViewById(R.id.scene_transition_common); - mSelectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() { - @Override - public void onTunerInputSelected() { - startTvWithChannel(TvContract.Channels.CONTENT_URI); - } + mSelectInputView.setOnInputSelectedCallback( + new OnInputSelectedCallback() { + @Override + public void onTunerInputSelected() { + startTvWithChannel(TvContract.Channels.CONTENT_URI); + } - @Override - public void onPassthroughInputSelected(TvInputInfo input) { - startTvWithChannel(TvContract.buildChannelUriForPassthroughInput(input.getId())); - } + @Override + public void onPassthroughInputSelected(TvInputInfo input) { + startTvWithChannel( + TvContract.buildChannelUriForPassthroughInput(input.getId())); + } - private void startTvWithChannel(Uri channelUri) { - Intent intent = new Intent(Intent.ACTION_VIEW, channelUri, - SelectInputActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - } - }); + private void startTvWithChannel(Uri channelUri) { + Intent intent = + new Intent( + Intent.ACTION_VIEW, + channelUri, + SelectInputActivity.this, + MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } + }); String channelUriString = Utils.getLastWatchedChannelUri(this); if (channelUriString != null) { Uri channelUri = Uri.parse(channelUriString); diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java index f0f54413..d1158682 100644 --- a/src/com/android/tv/SetupPassthroughActivity.java +++ b/src/com/android/tv/SetupPassthroughActivity.java @@ -26,7 +26,6 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.MainThread; import android.util.Log; - import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonConstants; import com.android.tv.data.ChannelDataManager; @@ -36,13 +35,12 @@ import com.android.tv.experiments.Experiments; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.concurrent.TimeUnit; /** * An activity to launch a TV input setup activity. * - *

After setup activity is finished, all channels will be browsable. + *

After setup activity is finished, all channels will be browsable. */ public class SetupPassthroughActivity extends Activity { private static final String TAG = "SetupPassthroughAct"; @@ -65,10 +63,10 @@ public class SetupPassthroughActivity extends Activity { Intent intent = getIntent(); String inputId = intent.getStringExtra(TvCommonConstants.EXTRA_INPUT_ID); mTvInputInfo = inputManager.getTvInputInfo(inputId); - mActivityAfterCompletion = intent.getParcelableExtra( - TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); - boolean needToFetchEpg = Utils.isInternalTvInput(this, mTvInputInfo.getId()) - && Experiments.CLOUD_EPG.get(); + mActivityAfterCompletion = + intent.getParcelableExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); + boolean needToFetchEpg = + Utils.isInternalTvInput(this, mTvInputInfo.getId()) && Experiments.CLOUD_EPG.get(); if (needToFetchEpg) { // In case when the activity is restored, this flag should be restored as well. mEpgFetcherDuringScan = true; @@ -121,8 +119,8 @@ public class SetupPassthroughActivity extends Activity { sScanTimeoutMonitor.stopMonitoring(); } // Note: It's not guaranteed that this method is always called after scanning. - boolean setupComplete = requestCode == REQUEST_START_SETUP_ACTIVITY - && resultCode == Activity.RESULT_OK; + boolean setupComplete = + requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK; // Tells EpgFetcher that channel source setup is finished. if (mEpgFetcherDuringScan) { EpgFetcher.getInstance(this).onChannelScanFinished(); @@ -132,25 +130,28 @@ public class SetupPassthroughActivity extends Activity { finish(); return; } - SetupUtils.getInstance(this).onTvInputSetupFinished(mTvInputInfo.getId(), new Runnable() { - @Override - public void run() { - if (mActivityAfterCompletion != null) { - try { - startActivity(mActivityAfterCompletion); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity launch failed", e); - } - } - setResult(resultCode, data); - finish(); - } - }); + SetupUtils.getInstance(this) + .onTvInputSetupFinished( + mTvInputInfo.getId(), + new Runnable() { + @Override + public void run() { + if (mActivityAfterCompletion != null) { + try { + startActivity(mActivityAfterCompletion); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity launch failed", e); + } + } + setResult(resultCode, data); + finish(); + } + }); } /** - * Monitors the scan progress and notifies the timeout of the scanning. - * The purpose of this monitor is to call EpgFetcher.onChannelScanFinished() in case when + * Monitors the scan progress and notifies the timeout of the scanning. The purpose of this + * monitor is to call EpgFetcher.onChannelScanFinished() in case when * SetupPassthroughActivity.onActivityResult() is not called properly. b/36008534 */ @MainThread @@ -161,28 +162,32 @@ public class SetupPassthroughActivity extends Activity { private final Context mContext; private final ChannelDataManager mChannelDataManager; private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final Runnable mScanTimeoutRunnable = new Runnable() { - @Override - public void run() { - Log.w(TAG, "No channels has been added for a while." + - " The scan might have finished unexpectedly."); - onScanTimedOut(); - } - }; - private final Listener mChannelDataManagerListener = new Listener() { - @Override - public void onLoadFinished() { - setupTimer(); - } + private final Runnable mScanTimeoutRunnable = + new Runnable() { + @Override + public void run() { + Log.w( + TAG, + "No channels has been added for a while." + + " The scan might have finished unexpectedly."); + onScanTimedOut(); + } + }; + private final Listener mChannelDataManagerListener = + new Listener() { + @Override + public void onLoadFinished() { + setupTimer(); + } - @Override - public void onChannelListUpdated() { - setupTimer(); - } + @Override + public void onChannelListUpdated() { + setupTimer(); + } - @Override - public void onChannelBrowsableChanged() { } - }; + @Override + public void onChannelBrowsableChanged() {} + }; private boolean mStarted; private ScanTimeoutMonitor(Context context) { diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java index 70885936..513fe3cd 100644 --- a/src/com/android/tv/TimeShiftManager.java +++ b/src/com/android/tv/TimeShiftManager.java @@ -27,7 +27,6 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.util.Range; - import com.android.tv.analytics.Tracker; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; @@ -40,7 +39,6 @@ import com.android.tv.ui.TunableTvView.TimeShiftListener; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.TimeShiftUtils; import com.android.tv.util.Utils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -53,11 +51,11 @@ import java.util.Queue; import java.util.concurrent.TimeUnit; /** - * A class which manages the time shift feature in Live TV. It consists of two parts. - * {@link PlayController} controls the playback such as play/pause, rewind and fast-forward using - * {@link TunableTvView} which communicates with TvInputService through - * {@link android.media.tv.TvInputService.Session}. - * {@link ProgramManager} loads programs of the current channel in the background. + * A class which manages the time shift feature in Live TV. It consists of two parts. {@link + * PlayController} controls the playback such as play/pause, rewind and fast-forward using {@link + * TunableTvView} which communicates with TvInputService through {@link + * android.media.tv.TvInputService.Session}. {@link ProgramManager} loads programs of the current + * channel in the background. */ public class TimeShiftManager { private static final String TAG = "TimeShiftManager"; @@ -66,12 +64,14 @@ public class TimeShiftManager { @Retention(RetentionPolicy.SOURCE) @IntDef({PLAY_STATUS_PAUSED, PLAY_STATUS_PLAYING}) public @interface PlayStatus {} - public static final int PLAY_STATUS_PAUSED = 0; + + public static final int PLAY_STATUS_PAUSED = 0; public static final int PLAY_STATUS_PLAYING = 1; @Retention(RetentionPolicy.SOURCE) @IntDef({PLAY_SPEED_1X, PLAY_SPEED_2X, PLAY_SPEED_3X, PLAY_SPEED_4X, PLAY_SPEED_5X}) - public @interface PlaySpeed{} + public @interface PlaySpeed {} + public static final int PLAY_SPEED_1X = 1; public static final int PLAY_SPEED_2X = 2; public static final int PLAY_SPEED_3X = 3; @@ -80,15 +80,25 @@ public class TimeShiftManager { @Retention(RetentionPolicy.SOURCE) @IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD}) - public @interface PlayDirection{} - public static final int PLAY_DIRECTION_FORWARD = 0; + public @interface PlayDirection {} + + public static final int PLAY_DIRECTION_FORWARD = 0; public static final int PLAY_DIRECTION_BACKWARD = 1; @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {TIME_SHIFT_ACTION_ID_PLAY, TIME_SHIFT_ACTION_ID_PAUSE, - TIME_SHIFT_ACTION_ID_REWIND, TIME_SHIFT_ACTION_ID_FAST_FORWARD, - TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT}) - public @interface TimeShiftActionId{} + @IntDef( + flag = true, + value = { + TIME_SHIFT_ACTION_ID_PLAY, + TIME_SHIFT_ACTION_ID_PAUSE, + TIME_SHIFT_ACTION_ID_REWIND, + TIME_SHIFT_ACTION_ID_FAST_FORWARD, + TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, + TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT + } + ) + public @interface TimeShiftActionId {} + public static final int TIME_SHIFT_ACTION_ID_PLAY = 1; public static final int TIME_SHIFT_ACTION_ID_PAUSE = 1 << 1; public static final int TIME_SHIFT_ACTION_ID_REWIND = 1 << 2; @@ -100,8 +110,7 @@ public class TimeShiftManager { private static final int MSG_PREFETCH_PROGRAM = 1001; private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1); private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30); - @VisibleForTesting - static final long INVALID_TIME = -1; + @VisibleForTesting static final long INVALID_TIME = -1; static final long CURRENT_TIME = -2; private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1); private static final long PREFETCH_DURATION_FOR_NEXT = TimeUnit.HOURS.toMillis(2); @@ -109,57 +118,57 @@ public class TimeShiftManager { private static final long ALLOWED_START_TIME_OFFSET = TimeUnit.DAYS.toMillis(14); private static final long TWO_WEEKS_MS = TimeUnit.DAYS.toMillis(14); - @VisibleForTesting - static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3); + @VisibleForTesting static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3); /** * If the user presses the {@link android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS} button within * this threshold from the program start time, the play position moves to the start of the - * previous program. - * Otherwise, the play position moves to the start of the current program. + * previous program. Otherwise, the play position moves to the start of the current program. * This value is specified in the UX document. */ private static final long PROGRAM_START_TIME_THRESHOLD = TimeUnit.SECONDS.toMillis(3); /** * If the current position enters within this range from the recording start time, rewind action - * and jump to previous action is disabled. - * Similarly, if the current position enters within this range from the current system time, - * fast forward action and jump to next action is disabled. - * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least. + * and jump to previous action is disabled. Similarly, if the current position enters within + * this range from the current system time, fast forward action and jump to next action is + * disabled. It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at + * least. */ private static final long DISABLE_ACTION_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL; /** * If the current position goes out of this range from the recording start time, rewind action - * and jump to previous action is enabled. - * Similarly, if the current position goes out of this range from the current system time, - * fast forward action and jump to next action is enabled. - * Enable threshold and disable threshold must be different because the current position - * does not have the continuous value. It changes every one second. + * and jump to previous action is enabled. Similarly, if the current position goes out of this + * range from the current system time, fast forward action and jump to next action is enabled. + * Enable threshold and disable threshold must be different because the current position does + * not have the continuous value. It changes every one second. */ private static final long ENABLE_ACTION_THRESHOLD = DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL; /** - * The current position sent from TIS can not be exactly the same as the current system time - * due to the elapsed time to pass the message from TIS to Live TV. - * So the boundary threshold is necessary. - * The same goes for the recording start time. - * It's the same {@link #REQUEST_CURRENT_POSITION_INTERVAL}. + * The current position sent from TIS can not be exactly the same as the current system time due + * to the elapsed time to pass the message from TIS to Live TV. So the boundary threshold is + * necessary. The same goes for the recording start time. It's the same {@link + * #REQUEST_CURRENT_POSITION_INTERVAL}. */ private static final long RECORDING_BOUNDARY_THRESHOLD = REQUEST_CURRENT_POSITION_INTERVAL; private final PlayController mPlayController; private final ProgramManager mProgramManager; private final Tracker mTracker; + @VisibleForTesting final CurrentPositionMediator mCurrentPositionMediator = new CurrentPositionMediator(); private Listener mListener; private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener; - private int mEnabledActionIds = TIME_SHIFT_ACTION_ID_PLAY | TIME_SHIFT_ACTION_ID_PAUSE - | TIME_SHIFT_ACTION_ID_REWIND | TIME_SHIFT_ACTION_ID_FAST_FORWARD - | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT; - @TimeShiftActionId - private int mLastActionId = 0; + private int mEnabledActionIds = + TIME_SHIFT_ACTION_ID_PLAY + | TIME_SHIFT_ACTION_ID_PAUSE + | TIME_SHIFT_ACTION_ID_REWIND + | TIME_SHIFT_ACTION_ID_FAST_FORWARD + | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS + | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT; + @TimeShiftActionId private int mLastActionId = 0; private final Context mContext; @@ -169,8 +178,11 @@ public class TimeShiftManager { private final Handler mHandler = new TimeShiftHandler(this); - public TimeShiftManager(Context context, TunableTvView tvView, - ProgramDataManager programDataManager, Tracker tracker, + public TimeShiftManager( + Context context, + TunableTvView tvView, + ProgramDataManager programDataManager, + Tracker tracker, OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) { mContext = context; mPlayController = new PlayController(tvView); @@ -179,23 +191,17 @@ public class TimeShiftManager { mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener; } - /** - * Sets a listener which will receive events from this class. - */ + /** Sets a listener which will receive events from this class. */ public void setListener(Listener listener) { mListener = listener; } - /** - * Checks if the trick play is available for the current channel. - */ + /** Checks if the trick play is available for the current channel. */ public boolean isAvailable() { return mPlayController.mAvailable; } - /** - * Returns the current time position in milliseconds. - */ + /** Returns the current time position in milliseconds. */ public long getCurrentPositionMs() { return mCurrentPositionMediator.mCurrentPositionMs; } @@ -204,18 +210,15 @@ public class TimeShiftManager { mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs); } - /** - * Returns the start time of the recording in milliseconds. - */ + /** Returns the start time of the recording in milliseconds. */ public long getRecordStartTimeMs() { long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime(); - return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME + return oldestProgramStartTime == INVALID_TIME + ? INVALID_TIME : mPlayController.mRecordStartTimeMs; } - /** - * Returns the end time of the recording in milliseconds. - */ + /** Returns the end time of the recording in milliseconds. */ public long getRecordEndTimeMs() { if (mPlayController.mRecordEndTimeMs == CURRENT_TIME) { return System.currentTimeMillis(); @@ -264,9 +267,9 @@ public class TimeShiftManager { } /** - * Plays the media in backward direction. The playback speed is increased by 1x each time - * this is called. The range of the speed is from 2x to 5x. - * If the playing position is considered the same as the record start time, it does nothing + * Plays the media in backward direction. The playback speed is increased by 1x each time this + * is called. The range of the speed is from 2x to 5x. If the playing position is considered the + * same as the record start time, it does nothing * * @throws IllegalStateException if the trick play is not available. */ @@ -281,9 +284,9 @@ public class TimeShiftManager { } /** - * Plays the media in forward direction. The playback speed is increased by 1x each time - * this is called. The range of the speed is from 2x to 5x. - * If the playing position is the same as the current time, it does nothing. + * Plays the media in forward direction. The playback speed is increased by 1x each time this is + * called. The range of the speed is from 2x to 5x. If the playing position is the same as the + * current time, it does nothing. * * @throws IllegalStateException if the trick play is not available. */ @@ -298,11 +301,10 @@ public class TimeShiftManager { } /** - * Jumps to the start of the current program. - * If the currently playing position is within 3 seconds - * (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes to - * the start of the previous program if exists. - * If the playing position is the same as the record start time, it does nothing. + * Jumps to the start of the current program. If the currently playing position is within 3 + * seconds (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes + * to the start of the previous program if exists. If the playing position is the same as the + * record start time, it does nothing. * * @throws IllegalStateException if the trick play is not available. */ @@ -310,8 +312,9 @@ public class TimeShiftManager { if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) { return; } - Program program = mProgramManager.getProgramAt( - mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD); + Program program = + mProgramManager.getProgramAt( + mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD); if (program == null) { return; } @@ -325,9 +328,9 @@ public class TimeShiftManager { } /** - * Jumps to the start of the next program if exists. - * If there's no next program, it jumps to the current system time and shows the live TV. - * If the playing position is considered the same as the current time, it does nothing. + * Jumps to the start of the next program if exists. If there's no next program, it jumps to the + * current system time and shows the live TV. If the playing position is considered the same as + * the current time, it does nothing. * * @throws IllegalStateException if the trick play is not available. */ @@ -335,8 +338,8 @@ public class TimeShiftManager { if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) { return; } - Program currentProgram = mProgramManager.getProgramAt( - mCurrentPositionMediator.mCurrentPositionMs); + Program currentProgram = + mProgramManager.getProgramAt(mCurrentPositionMediator.mCurrentPositionMs); if (currentProgram == null) { return; } @@ -362,10 +365,9 @@ public class TimeShiftManager { updateActions(); } - /** - * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING. - */ - @PlayStatus public int getPlayStatus() { + /** Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING. */ + @PlayStatus + public int getPlayStatus() { return mPlayController.mPlayStatus; } @@ -373,27 +375,26 @@ public class TimeShiftManager { * Returns the displayed playback speed. The value is one of PLAY_SPEED_1X, PLAY_SPEED_2X, * PLAY_SPEED_3X, PLAY_SPEED_4X and PLAY_SPEED_5X. */ - @PlaySpeed public int getDisplayedPlaySpeed() { + @PlaySpeed + public int getDisplayedPlaySpeed() { return mPlayController.mDisplayedPlaySpeed; } /** * Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD. */ - @PlayDirection public int getPlayDirection() { + @PlayDirection + public int getPlayDirection() { return mPlayController.mPlayDirection; } - /** - * Returns the ID of the last action.. - */ - @TimeShiftActionId public int getLastActionId() { + /** Returns the ID of the last action.. */ + @TimeShiftActionId + public int getLastActionId() { return mLastActionId; } - /** - * Enables or disables the time-shift actions. - */ + /** Enables or disables the time-shift actions. */ @VisibleForTesting void enableAction(@TimeShiftActionId int actionId, boolean enable) { int oldEnabledActionIds = mEnabledActionIds; @@ -402,8 +403,7 @@ public class TimeShiftManager { } else { mEnabledActionIds &= ~actionId; } - if (mNotificationEnabled && mListener != null - && oldEnabledActionIds != mEnabledActionIds) { + if (mNotificationEnabled && mListener != null && oldEnabledActionIds != mEnabledActionIds) { mListener.onActionEnabledChanged(actionId, enable); } } @@ -417,17 +417,22 @@ public class TimeShiftManager { enableAction(TIME_SHIFT_ACTION_ID_PLAY, true); enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true); // Rewind action and jump to previous action. - long threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND) - ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD; - boolean enabled = mCurrentPositionMediator.mCurrentPositionMs - - mPlayController.mRecordStartTimeMs > threshold; + long threshold = + isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND) + ? DISABLE_ACTION_THRESHOLD + : ENABLE_ACTION_THRESHOLD; + boolean enabled = + mCurrentPositionMediator.mCurrentPositionMs - mPlayController.mRecordStartTimeMs + > threshold; enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled); enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled); // Fast forward action and jump to next action - threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD) - ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD; - enabled = getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs - > threshold; + threshold = + isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD) + ? DISABLE_ACTION_THRESHOLD + : ENABLE_ACTION_THRESHOLD; + enabled = + getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs > threshold; enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled); enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled); } else { @@ -453,8 +458,8 @@ public class TimeShiftManager { if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) { Channel channel = mPlayController.getCurrentChannel(); if (channel != null) { - mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(), - mCurrentProgram); + mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated( + channel.getId(), mCurrentProgram); mPlayController.onCurrentProgramChanged(); } } @@ -472,16 +477,12 @@ public class TimeShiftManager { && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X; } - /** - * Checks if the trick play is available and it's playback status is paused. - */ + /** Checks if the trick play is available and it's playback status is paused. */ public boolean isPaused() { return mPlayController.mAvailable && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED; } - /** - * Returns the program which airs at the given time. - */ + /** Returns the program which airs at the given time. */ @NonNull public Program getProgramAt(long timeMs) { Program program = mProgramManager.getProgramAt(timeMs); @@ -495,8 +496,10 @@ public class TimeShiftManager { void onAvailabilityChanged() { mCurrentPositionMediator.initialize(mPlayController.mRecordStartTimeMs); - mProgramManager.onAvailabilityChanged(mPlayController.mAvailable, - mPlayController.getCurrentChannel(), mPlayController.mRecordStartTimeMs); + mProgramManager.onAvailabilityChanged( + mPlayController.mAvailable, + mPlayController.getCurrentChannel(), + mPlayController.mRecordStartTimeMs); updateActions(); // Availability change notification should be always sent // even if mNotificationEnabled is false. @@ -507,8 +510,8 @@ public class TimeShiftManager { void onRecordTimeRangeChanged() { if (mPlayController.mAvailable) { - mProgramManager.onRecordTimeRangeChanged(mPlayController.mRecordStartTimeMs, - mPlayController.mRecordEndTimeMs); + mProgramManager.onRecordTimeRangeChanged( + mPlayController.mRecordStartTimeMs, mPlayController.mRecordEndTimeMs); } updateActions(); if (mNotificationEnabled && mListener != null) { @@ -538,10 +541,10 @@ public class TimeShiftManager { } /** - * Returns the current program which airs right now.

+ * Returns the current program which airs right now. * - * If the program is a dummy program, which means there's no program information, - * returns {@code null}. + *

If the program is a dummy program, which means there's no program information, returns + * {@code null}. */ @Nullable public Program getCurrentProgram() { @@ -558,8 +561,10 @@ public class TimeShiftManager { long durationMs = (getCurrentProgram() == null ? 0 : getCurrentProgram().getDurationMillis()); if (mPlayController.mDisplayedPlaySpeed > PLAY_SPEED_5X) { - Log.w(TAG, "Unknown displayed play speed is chosen : " - + mPlayController.mDisplayedPlaySpeed); + Log.w( + TAG, + "Unknown displayed play speed is chosen : " + + mPlayController.mDisplayedPlaySpeed); return TimeShiftUtils.getMaxPlaybackSpeed(durationMs); } else { return TimeShiftUtils.getPlaybackSpeed( @@ -568,9 +573,7 @@ public class TimeShiftManager { } } - /** - * A class which controls the trick play. - */ + /** A class which controls the trick play. */ private class PlayController { private final TunableTvView mTvView; @@ -585,69 +588,87 @@ public class TimeShiftManager { private boolean mAvailable; /** - * Indicates that the trick play is not playing the current time position. - * It is set true when {@link PlayController#pause}, {@link PlayController#rewind}, - * {@link PlayController#fastForward} and {@link PlayController#seekTo} - * is called. - * If it is true, the current time is equal to System.currentTimeMillis(). + * Indicates that the trick play is not playing the current time position. It is set true + * when {@link PlayController#pause}, {@link PlayController#rewind}, {@link + * PlayController#fastForward} and {@link PlayController#seekTo} is called. If it is true, + * the current time is equal to System.currentTimeMillis(). */ private boolean mIsPlayOffsetChanged; PlayController(TunableTvView tvView) { mTvView = tvView; - mTvView.setTimeShiftListener(new TimeShiftListener() { - @Override - public void onAvailabilityChanged() { - if (DEBUG) { - Log.d(TAG, "onAvailabilityChanged(available=" - + mTvView.isTimeShiftAvailable() + ")"); - } - PlayController.this.onAvailabilityChanged(); - } + mTvView.setTimeShiftListener( + new TimeShiftListener() { + @Override + public void onAvailabilityChanged() { + if (DEBUG) { + Log.d( + TAG, + "onAvailabilityChanged(available=" + + mTvView.isTimeShiftAvailable() + + ")"); + } + PlayController.this.onAvailabilityChanged(); + } - @Override - public void onRecordStartTimeChanged(long recordStartTimeMs) { - if (!SoftPreconditions.checkState(mAvailable, TAG, - "Trick play is not available.")) { - return; - } - if (recordStartTimeMs < mAvailablityChangedTimeMs - ALLOWED_START_TIME_OFFSET) { - Log.e(TAG, "The start time is too earlier than the time of availability: {" - + "startTime: " + recordStartTimeMs + ", availability: " - + mAvailablityChangedTimeMs); - return; - } - if (recordStartTimeMs > System.currentTimeMillis()) { - // The time reported by TvInputService might not consistent with system - // clock,, use system's current time instead. - Log.e(TAG, "The start time should not be earlier than the current time, " - + "reset the start time to the system's current time: {" - + "startTime: " + recordStartTimeMs + ", current time: " - + System.currentTimeMillis()); - recordStartTimeMs = System.currentTimeMillis(); - } - if (mRecordStartTimeMs == recordStartTimeMs) { - return; - } - mRecordStartTimeMs = recordStartTimeMs; - TimeShiftManager.this.onRecordTimeRangeChanged(); - - // According to the UX guidelines, the stream should be resumed if the - // recording buffer fills up while paused, which means that the current time - // position is the same as or before the recording start time. - // But, for this application and the TIS, it's an erroneous and confusing - // situation if the current time position is before the recording start time. - // So, we recommend the TIS to keep the current time position greater than or - // equal to the recording start time. - // And here, we assume that the buffer is full if the current time position - // is nearly equal to the recording start time. - if (mPlayStatus == PLAY_STATUS_PAUSED && - getCurrentPositionMs() - mRecordStartTimeMs - < RECORDING_BOUNDARY_THRESHOLD) { - TimeShiftManager.this.play(); - } - } - }); + @Override + public void onRecordStartTimeChanged(long recordStartTimeMs) { + if (!SoftPreconditions.checkState( + mAvailable, TAG, "Trick play is not available.")) { + return; + } + if (recordStartTimeMs + < mAvailablityChangedTimeMs - ALLOWED_START_TIME_OFFSET) { + Log.e( + TAG, + "The start time is too earlier than the time of availability: {" + + "startTime: " + + recordStartTimeMs + + ", availability: " + + mAvailablityChangedTimeMs); + return; + } + if (recordStartTimeMs > System.currentTimeMillis()) { + // The time reported by TvInputService might not consistent with + // system + // clock,, use system's current time instead. + Log.e( + TAG, + "The start time should not be earlier than the current time, " + + "reset the start time to the system's current time: {" + + "startTime: " + + recordStartTimeMs + + ", current time: " + + System.currentTimeMillis()); + recordStartTimeMs = System.currentTimeMillis(); + } + if (mRecordStartTimeMs == recordStartTimeMs) { + return; + } + mRecordStartTimeMs = recordStartTimeMs; + TimeShiftManager.this.onRecordTimeRangeChanged(); + + // According to the UX guidelines, the stream should be resumed if the + // recording buffer fills up while paused, which means that the current + // time + // position is the same as or before the recording start time. + // But, for this application and the TIS, it's an erroneous and + // confusing + // situation if the current time position is before the recording start + // time. + // So, we recommend the TIS to keep the current time position greater + // than or + // equal to the recording start time. + // And here, we assume that the buffer is full if the current time + // position + // is nearly equal to the recording start time. + if (mPlayStatus == PLAY_STATUS_PAUSED + && getCurrentPositionMs() - mRecordStartTimeMs + < RECORDING_BOUNDARY_THRESHOLD) { + TimeShiftManager.this.play(); + } + } + }); } void onAvailabilityChanged() { @@ -672,8 +693,8 @@ public class TimeShiftManager { mRecordEndTimeMs = CURRENT_TIME; // When the media availability message has come. mPlayController.setPlayStatus(PLAY_STATUS_PLAYING); - mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, - REQUEST_CURRENT_POSITION_INTERVAL); + mHandler.sendEmptyMessageDelayed( + MSG_GET_CURRENT_POSITION, REQUEST_CURRENT_POSITION_INTERVAL); } else { mAvailablityChangedTimeMs = INVALID_TIME; mIsPlayOffsetChanged = false; @@ -688,11 +709,14 @@ public class TimeShiftManager { void handleGetCurrentPosition() { if (mIsPlayOffsetChanged) { - long currentTimeMs = mRecordEndTimeMs == CURRENT_TIME ? System.currentTimeMillis() - : mRecordEndTimeMs; - long currentPositionMs = Math.max( - Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs), - mRecordStartTimeMs); + long currentTimeMs = + mRecordEndTimeMs == CURRENT_TIME + ? System.currentTimeMillis() + : mRecordEndTimeMs; + long currentPositionMs = + Math.max( + Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs), + mRecordStartTimeMs); boolean isCurrentTime = currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD; long newCurrentPositionMs; @@ -708,8 +732,8 @@ public class TimeShiftManager { } } else { newCurrentPositionMs = currentPositionMs; - boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs - < RECORDING_BOUNDARY_THRESHOLD; + boolean isRecordStartTime = + currentPositionMs - mRecordStartTimeMs < RECORDING_BOUNDARY_THRESHOLD; if (isRecordStartTime && isRewinding()) { TimeShiftManager.this.play(); } @@ -721,8 +745,8 @@ public class TimeShiftManager { } // Need to send message here just in case there is no or invalid response // for the current time position request from TIS. - mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION, - REQUEST_CURRENT_POSITION_INTERVAL); + mHandler.sendEmptyMessageDelayed( + MSG_GET_CURRENT_POSITION, REQUEST_CURRENT_POSITION_INTERVAL); } void play() { @@ -777,12 +801,13 @@ public class TimeShiftManager { mIsPlayOffsetChanged = true; } - /** - * Moves to the specified time. - */ + /** Moves to the specified time. */ void seekTo(long timeMs) { - mTvView.timeshiftSeekTo(Math.min(mRecordEndTimeMs == CURRENT_TIME - ? System.currentTimeMillis() : mRecordEndTimeMs, + mTvView.timeshiftSeekTo( + Math.min( + mRecordEndTimeMs == CURRENT_TIME + ? System.currentTimeMillis() + : mRecordEndTimeMs, Math.max(mRecordStartTimeMs, timeMs))); mIsPlayOffsetChanged = true; } @@ -853,8 +878,15 @@ public class TimeShiftManager { void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) { if (DEBUG) { - Log.d(TAG, "onAvailabilityChanged(" + available + "+," + channel + ", " - + currentPositionMs + ")"); + Log.d( + TAG, + "onAvailabilityChanged(" + + available + + "+," + + channel + + ", " + + currentPositionMs + + ")"); } mProgramLoadQueue.clear(); @@ -875,12 +907,14 @@ public class TimeShiftManager { mPrograms.add(program); prefetchStartTimeMs = program.getEndTimeUtcMillis(); } else { - prefetchStartTimeMs = Utils.floorTime(currentPositionMs, - MAX_DUMMY_PROGRAM_DURATION); + prefetchStartTimeMs = + Utils.floorTime(currentPositionMs, MAX_DUMMY_PROGRAM_DURATION); } // Create dummy program - mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs, - currentPositionMs + PREFETCH_DURATION_FOR_NEXT)); + mPrograms.addAll( + createDummyPrograms( + prefetchStartTimeMs, + currentPositionMs + PREFETCH_DURATION_FOR_NEXT)); schedulePrefetchPrograms(); TimeShiftManager.this.onProgramInfoChanged(); } @@ -895,8 +929,9 @@ public class TimeShiftManager { } long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION); - long fetchEndTimeMs = Utils.ceilTime(endTimeMs + PREFETCH_DURATION_FOR_NEXT, - MAX_DUMMY_PROGRAM_DURATION); + long fetchEndTimeMs = + Utils.ceilTime( + endTimeMs + PREFETCH_DURATION_FOR_NEXT, MAX_DUMMY_PROGRAM_DURATION); removeOutdatedPrograms(fetchStartTimeMs); boolean needToLoad = addDummyPrograms(fetchStartTimeMs, fetchEndTimeMs); if (needToLoad) { @@ -934,16 +969,16 @@ public class TimeShiftManager { Range next = mProgramLoadQueue.poll(); // Extend next to include any overlapping Ranges. Iterator> i = mProgramLoadQueue.iterator(); - while(i.hasNext()) { + while (i.hasNext()) { Range r = i.next(); - if(next.contains(r.getLower()) || next.contains(r.getUpper())){ + if (next.contains(r.getLower()) || next.contains(r.getUpper())) { i.remove(); next = next.extend(r); } } if (mChannel != null) { - mProgramLoadTask = new LoadProgramsForCurrentChannelTask( - mContext.getContentResolver(), next); + mProgramLoadTask = + new LoadProgramsForCurrentChannelTask(mContext.getContentResolver(), next); mProgramLoadTask.executeOnDbThread(); } } @@ -969,10 +1004,12 @@ public class TimeShiftManager { if (!firstProgram.isValid()) { // Already the firstProgram is dummy. mPrograms.remove(0); - mPrograms.addAll(0, + mPrograms.addAll( + 0, createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis())); } else { - mPrograms.addAll(0, + mPrograms.addAll( + 0, createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis())); } added = true; @@ -1055,9 +1092,12 @@ public class TimeShiftManager { // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most // for a dummy program. private List createDummyPrograms(long startTimeMs, long endTimeMs) { - SoftPreconditions.checkArgument(endTimeMs - startTimeMs <= TWO_WEEKS_MS, TAG, + SoftPreconditions.checkArgument( + endTimeMs - startTimeMs <= TWO_WEEKS_MS, + TAG, "createDummyProgram: long duration of dummy programs are requested (" - + Utils.toTimeString(startTimeMs) + ", " + + Utils.toTimeString(startTimeMs) + + ", " + Utils.toTimeString(endTimeMs)); if (startTimeMs >= endTimeMs) { return Collections.emptyList(); @@ -1066,17 +1106,19 @@ public class TimeShiftManager { long start = startTimeMs; long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION); while (end < endTimeMs) { - programs.add(new Program.Builder() - .setStartTimeUtcMillis(start) - .setEndTimeUtcMillis(end) - .build()); + programs.add( + new Program.Builder() + .setStartTimeUtcMillis(start) + .setEndTimeUtcMillis(end) + .build()); start = end; end += MAX_DUMMY_PROGRAM_DURATION; } - programs.add(new Program.Builder() - .setStartTimeUtcMillis(start) - .setEndTimeUtcMillis(endTimeMs) - .build()); + programs.add( + new Program.Builder() + .setStartTimeUtcMillis(start) + .setEndTimeUtcMillis(endTimeMs) + .build()); return programs; } @@ -1093,7 +1135,7 @@ public class TimeShiftManager { if (program.getStartTimeUtcMillis() > timeMs) { return getProgramAt(timeMs, start, mid - 1); } else if (program.getEndTimeUtcMillis() <= timeMs) { - return getProgramAt(timeMs, mid+1, end); + return getProgramAt(timeMs, mid + 1, end); } else { return program; } @@ -1125,8 +1167,10 @@ public class TimeShiftManager { if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram); final long delay; if (lastValidProgram != null) { - delay = lastValidProgram.getEndTimeUtcMillis() - - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis(); + delay = + lastValidProgram.getEndTimeUtcMillis() + - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END + - System.currentTimeMillis(); } else { // Since there might not be any program data delay the retry 5 seconds, // then 30 seconds then 5 minutes @@ -1145,7 +1189,8 @@ public class TimeShiftManager { break; } if (DEBUG) { - Log.d(TAG, + Log.d( + TAG, "No last valid program. Already tried " + mEmptyFetchCount + " times"); } } @@ -1165,8 +1210,13 @@ public class TimeShiftManager { long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT; if (startTimeMs <= endTimeMs) { if (DEBUG) { - Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs) - + ", endTime=" + Utils.toTimeString(endTimeMs) + "}"); + Log.d( + TAG, + "Prefetch task starts: {startTime=" + + Utils.toTimeString(startTimeMs) + + ", endTime=" + + Utils.toTimeString(endTimeMs) + + "}"); } mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs)); } @@ -1176,20 +1226,24 @@ public class TimeShiftManager { private class LoadProgramsForCurrentChannelTask extends AsyncDbTask.LoadProgramsForChannelTask { - LoadProgramsForCurrentChannelTask(ContentResolver contentResolver, - Range period) { + LoadProgramsForCurrentChannelTask(ContentResolver contentResolver, Range period) { super(contentResolver, mChannel.getId(), period); } @Override protected void onPostExecute(List programs) { if (DEBUG) { - Log.d(TAG, "Programs are loaded {channelId=" + mChannelId + - ", from=" + Utils.toTimeString(mPeriod.getLower()) + - ", to=" + Utils.toTimeString(mPeriod.getUpper()) + - "}"); + Log.d( + TAG, + "Programs are loaded {channelId=" + + mChannelId + + ", from=" + + Utils.toTimeString(mPeriod.getLower()) + + ", to=" + + Utils.toTimeString(mPeriod.getUpper()) + + "}"); } - //remove pending tasks that are fully satisfied by this query. + // remove pending tasks that are fully satisfied by this query. Iterator> it = mProgramLoadQueue.iterator(); while (it.hasNext()) { Range r = it.next(); @@ -1207,14 +1261,14 @@ public class TimeShiftManager { return; } mEmptyFetchCount = 0; - if(!mPrograms.isEmpty()) { + if (!mPrograms.isEmpty()) { removeDummyPrograms(); removeOverlappedPrograms(programs); Program loadedProgram = programs.get(0); for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) { Program program = mPrograms.get(i); - while (program.getStartTimeUtcMillis() > loadedProgram - .getStartTimeUtcMillis()) { + while (program.getStartTimeUtcMillis() + > loadedProgram.getStartTimeUtcMillis()) { mPrograms.add(i++, loadedProgram); programs.remove(0); if (programs.isEmpty()) { @@ -1234,10 +1288,15 @@ public class TimeShiftManager { @Override protected void onCancelled(List programs) { if (DEBUG) { - Log.d(TAG, "Program loading has been canceled {channelId=" + (mChannel == null - ? "null" : mChannelId) + ", from=" + Utils - .toTimeString(mPeriod.getLower()) + ", to=" + Utils - .toTimeString(mPeriod.getUpper()) + "}"); + Log.d( + TAG, + "Program loading has been canceled {channelId=" + + (mChannel == null ? "null" : mChannelId) + + ", from=" + + Utils.toTimeString(mPeriod.getLower()) + + ", to=" + + Utils.toTimeString(mPeriod.getUpper()) + + "}"); } startNextLoadingIfNeeded(); } @@ -1247,12 +1306,13 @@ public class TimeShiftManager { mProgramLoadTask = null; } // Need to post to handler, because the task is still running. - mHandler.post(new Runnable() { - @Override - public void run() { - startTaskIfNeeded(); - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + startTaskIfNeeded(); + } + }); } boolean overlaps(Queue> programLoadQueue) { @@ -1299,11 +1359,11 @@ public class TimeShiftManager { } else { if (getPlayStatus() == PLAY_STATUS_PLAYING) { if (getPlayDirection() == PLAY_DIRECTION_FORWARD) { - mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs) - * getPlaybackSpeed(); + mCurrentPositionMs += + (currentTimeMs - mSeekRequestTimeMs) * getPlaybackSpeed(); } else { - mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs) - * getPlaybackSpeed(); + mCurrentPositionMs -= + (currentTimeMs - mSeekRequestTimeMs) * getPlaybackSpeed(); } } TimeShiftManager.this.onCurrentPositionChanged(); @@ -1311,9 +1371,7 @@ public class TimeShiftManager { } } - /** - * The listener used to receive the events by the time-shift manager - */ + /** The listener used to receive the events by the time-shift manager */ public interface Listener { /** * Called when the availability of the time-shift for the current channel has been changed. @@ -1323,31 +1381,23 @@ public class TimeShiftManager { void onAvailabilityChanged(); /** - * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and - * {@link #PLAY_STATUS_PAUSED} + * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and {@link + * #PLAY_STATUS_PAUSED} * * @param status The new play state. */ void onPlayStatusChanged(int status); - /** - * Called when the recordStartTime has been changed. - */ + /** Called when the recordStartTime has been changed. */ void onRecordTimeRangeChanged(); - /** - * Called when the current position is changed. - */ + /** Called when the current position is changed. */ void onCurrentPositionChanged(); - /** - * Called when the program information is updated. - */ + /** Called when the program information is updated. */ void onProgramInfoChanged(); - /** - * Called when an action becomes enabled or disabled. - */ + /** Called when an action becomes enabled or disabled. */ void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled); } diff --git a/src/com/android/tv/TvActivity.java b/src/com/android/tv/TvActivity.java index 9a1cea55..aa0f0e8c 100644 --- a/src/com/android/tv/TvActivity.java +++ b/src/com/android/tv/TvActivity.java @@ -18,7 +18,6 @@ package com.android.tv; import android.app.Activity; import android.content.Intent; - import com.android.tv.util.Utils; public class TvActivity extends Activity { diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java index 0c7c0fd1..549ab6d8 100644 --- a/src/com/android/tv/TvApplication.java +++ b/src/com/android/tv/TvApplication.java @@ -22,7 +22,6 @@ import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -39,10 +38,8 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; - import com.android.tv.analytics.Analytics; import com.android.tv.analytics.StubAnalytics; -import com.android.tv.analytics.StubAnalytics; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; import com.android.tv.common.SharedPreferencesUtils; @@ -81,7 +78,6 @@ import com.android.tv.util.SetupUtils; import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.List; public class TvApplication extends Application implements ApplicationSingletons { @@ -93,16 +89,16 @@ public class TvApplication extends Application implements ApplicationSingletons * An instance of {@link ApplicationSingletons}. Note that this can be set directly only for the * test purpose. */ - @VisibleForTesting - public static ApplicationSingletons sAppSingletons; + @VisibleForTesting public static ApplicationSingletons sAppSingletons; /** - * Broadcast Action: The user has updated LC to a new version that supports tuner input. - * {@link com.android.tv.tuner.TunerInputController} will recevice this intent to check - * the existence of tuner input when the new version is first launched. + * Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link + * com.android.tv.tuner.TunerInputController} will recevice this intent to check the existence + * of tuner input when the new version is first launched. */ public static final String ACTION_APPLICATION_FIRST_LAUNCHED = "com.android.tv.action.APPLICATION_FIRST_LAUNCHED"; + private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch"; private RemoteConfig mRemoteConfig; @@ -123,8 +119,7 @@ public class TvApplication extends Application implements ApplicationSingletons private DvrStorageStatusManager mDvrStorageStatusManager; private DvrWatchedPositionManager mDvrWatchedPositionManager; private RecordingScheduler mRecordingScheduler; - @Nullable - private InputSessionManager mInputSessionManager; + @Nullable private InputSessionManager mInputSessionManager; private AccountHelper mAccountHelper; // When this variable is null, we don't know in which process TvApplication runs. private Boolean mRunningInMainProcess; @@ -139,14 +134,16 @@ public class TvApplication extends Application implements ApplicationSingletons } Debug.getTimer(Debug.TAG_START_UP_TIMER).start(); Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start TvApplication.onCreate"); - SharedPreferencesUtils.initialize(this, new Runnable() { - @Override - public void run() { - if (mRunningInMainProcess != null && mRunningInMainProcess) { - checkTunerServiceOnFirstLaunch(); - } - } - }); + SharedPreferencesUtils.initialize( + this, + new Runnable() { + @Override + public void run() { + if (mRunningInMainProcess != null && mRunningInMainProcess) { + checkTunerServiceOnFirstLaunch(); + } + } + }); // TunerPreferences is used to enable/disable the tuner input even when TUNER feature is // disabled. TunerPreferences.initialize(this); @@ -184,7 +181,6 @@ public class TvApplication extends Application implements ApplicationSingletons // initialized here before Activity.onCreate() is called. SetupAnimationHelper.initialize(this); - Log.i(TAG, "Started Live TV " + mVersionName); Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate"); getPerformanceMonitor().stopTimer(sAppStartTimer, EventNames.APPLICATION_ONCREATE); @@ -195,8 +191,8 @@ public class TvApplication extends Application implements ApplicationSingletons SoftPreconditions.checkState(isMainProcess == mRunningInMainProcess); return; } - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "start TvApplication.setCurrentRunningProcess"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("start TvApplication.setCurrentRunningProcess"); mRunningInMainProcess = isMainProcess; if (CommonFeatures.DVR.isEnabled(this)) { mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess); @@ -210,21 +206,27 @@ public class TvApplication extends Application implements ApplicationSingletons } }.execute(); if (mRunningInMainProcess) { - getTvInputManagerHelper().addCallback(new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - if (Features.TUNER.isEnabled(TvApplication.this) && TextUtils.equals(inputId, - TunerTvInputService.getInputId(TvApplication.this))) { - TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this); - } - handleInputCountChanged(); - } - - @Override - public void onInputRemoved(String inputId) { - handleInputCountChanged(); - } - }); + getTvInputManagerHelper() + .addCallback( + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + if (Features.TUNER.isEnabled(TvApplication.this) + && TextUtils.equals( + inputId, + TunerTvInputService.getInputId( + TvApplication.this))) { + TunerInputInfoUtils.updateTunerInputInfo( + TvApplication.this); + } + handleInputCountChanged(); + } + + @Override + public void onInputRemoved(String inputId) { + handleInputCountChanged(); + } + }); if (Features.TUNER.isEnabled(this)) { // If the tuner input service is added before the app is started, we need to // handle it here. @@ -242,13 +244,14 @@ public class TvApplication extends Application implements ApplicationSingletons .updatePreviewDataForRecordedPrograms(); } } - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "finish TvApplication.setCurrentRunningProcess"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("finish TvApplication.setCurrentRunningProcess"); } private void checkTunerServiceOnFirstLaunch() { - SharedPreferences sharedPreferences = this.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); + SharedPreferences sharedPreferences = + this.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true); if (isFirstLaunch) { if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!"); @@ -259,34 +262,26 @@ public class TvApplication extends Application implements ApplicationSingletons } } - /** - * Returns the {@link DvrManager}. - */ + /** Returns the {@link DvrManager}. */ @Override public DvrManager getDvrManager() { return mDvrManager; } - /** - * Returns the {@link DvrScheduleManager}. - */ + /** Returns the {@link DvrScheduleManager}. */ @Override public DvrScheduleManager getDvrScheduleManager() { return mDvrScheduleManager; } - /** - * Returns the {@link RecordingScheduler}. - */ + /** Returns the {@link RecordingScheduler}. */ @Override @Nullable public RecordingScheduler getRecordingScheduler() { return mRecordingScheduler; } - /** - * Returns the {@link DvrWatchedPositionManager}. - */ + /** Returns the {@link DvrWatchedPositionManager}. */ @Override public DvrWatchedPositionManager getDvrWatchedPositionManager() { if (mDvrWatchedPositionManager == null) { @@ -304,25 +299,19 @@ public class TvApplication extends Application implements ApplicationSingletons return mInputSessionManager; } - /** - * Returns the {@link Analytics}. - */ + /** Returns the {@link Analytics}. */ @Override public Analytics getAnalytics() { return mAnalytics; } - /** - * Returns the default tracker. - */ + /** Returns the default tracker. */ @Override public Tracker getTracker() { return mTracker; } - /** - * Returns {@link ChannelDataManager}. - */ + /** Returns {@link ChannelDataManager}. */ @Override public ChannelDataManager getChannelDataManager() { if (mChannelDataManager == null) { @@ -337,23 +326,22 @@ public class TvApplication extends Application implements ApplicationSingletons return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished(); } - /** - * Returns {@link ProgramDataManager}. - */ + /** Returns {@link ProgramDataManager}. */ @Override public ProgramDataManager getProgramDataManager() { if (mProgramDataManager != null) { return mProgramDataManager; } - Utils.runInMainThreadAndWait(new Runnable() { - @Override - public void run() { - if (mProgramDataManager == null) { - mProgramDataManager = new ProgramDataManager(TvApplication.this); - mProgramDataManager.start(); - } - } - }); + Utils.runInMainThreadAndWait( + new Runnable() { + @Override + public void run() { + if (mProgramDataManager == null) { + mProgramDataManager = new ProgramDataManager(TvApplication.this); + mProgramDataManager.start(); + } + } + }); return mProgramDataManager; } @@ -362,9 +350,7 @@ public class TvApplication extends Application implements ApplicationSingletons return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished(); } - /** - * Returns {@link PreviewDataManager}. - */ + /** Returns {@link PreviewDataManager}. */ @TargetApi(Build.VERSION_CODES.O) @Override public PreviewDataManager getPreviewDataManager() { @@ -375,9 +361,7 @@ public class TvApplication extends Application implements ApplicationSingletons return mPreviewDataManager; } - /** - * Returns {@link DvrDataManager}. - */ + /** Returns {@link DvrDataManager}. */ @TargetApi(Build.VERSION_CODES.N) @Override public DvrDataManager getDvrDataManager() { @@ -389,18 +373,14 @@ public class TvApplication extends Application implements ApplicationSingletons return mDvrDataManager; } - /** - * Returns {@link DvrStorageStatusManager}. - */ + /** Returns {@link DvrStorageStatusManager}. */ @TargetApi(Build.VERSION_CODES.N) @Override public DvrStorageStatusManager getDvrStorageStatusManager() { return mDvrStorageStatusManager; } - /** - * Returns {@link TvInputManagerHelper}. - */ + /** Returns {@link TvInputManagerHelper}. */ @Override public TvInputManagerHelper getTvInputManagerHelper() { if (mTvInputManagerHelper == null) { @@ -410,17 +390,13 @@ public class TvApplication extends Application implements ApplicationSingletons return mTvInputManagerHelper; } - /** - * Returns the main activity information. - */ + /** Returns the main activity information. */ @Override public MainActivityWrapper getMainActivityWrapper() { return mMainActivityWrapper; } - /** - * Returns the {@link AccountHelper}. - */ + /** Returns the {@link AccountHelper}. */ @Override public AccountHelper getAccountHelper() { if (mAccountHelper == null) { @@ -452,8 +428,8 @@ public class TvApplication extends Application implements ApplicationSingletons } /** - * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in - * {@link SelectInputActivity#onDestroy}. + * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link + * SelectInputActivity#onDestroy}. */ public void setSelectInputActivity(SelectInputActivity activity) { mSelectInputActivity = activity; @@ -467,18 +443,14 @@ public class TvApplication extends Application implements ApplicationSingletons } } - /** - * Handles the global key KEYCODE_TV. - */ + /** Handles the global key KEYCODE_TV. */ public void handleTvKey() { if (!mMainActivityWrapper.isResumed()) { startMainActivity(null); } } - /** - * Handles the global key KEYCODE_TV_INPUT. - */ + /** Handles the global key KEYCODE_TV_INPUT. */ public void handleTvInputKey() { TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); List tvInputs = tvInputManager.getTvInputList(); @@ -497,22 +469,25 @@ public class TvApplication extends Application implements ApplicationSingletons if (inputCount < 2) { return; } - Activity activityToHandle = mMainActivityWrapper.isResumed() - ? mMainActivityWrapper.getMainActivity() : mSelectInputActivity; + Activity activityToHandle = + mMainActivityWrapper.isResumed() + ? mMainActivityWrapper.getMainActivity() + : mSelectInputActivity; if (activityToHandle != null) { // If startActivity is called, MainActivity.onPause is unnecessarily called. To // prevent it, MainActivity.dispatchKeyEvent is directly called. activityToHandle.dispatchKeyEvent( new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT)); - activityToHandle.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_TV_INPUT)); + activityToHandle.dispatchKeyEvent( + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT)); } else if (mMainActivityWrapper.isStarted()) { Bundle extras = new Bundle(); extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT); startMainActivity(extras); } else { - startActivity(new Intent(this, SelectInputActivity.class).setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK)); + startActivity( + new Intent(this, SelectInputActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } } @@ -520,8 +495,8 @@ public class TvApplication extends Application implements ApplicationSingletons // The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent // sent to the root activity. Having said that, we should be fine here since such an intent // does not carry any important user data. - Intent intent = new Intent(this, MainActivity.class) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = + new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (extras != null) { intent.putExtras(extras); } @@ -538,35 +513,37 @@ public class TvApplication extends Application implements ApplicationSingletons } /** - * Checks the input counts and enable/disable TvActivity. Also updates the input list in - * {@link SetupUtils}. + * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link + * SetupUtils}. */ public void handleInputCountChanged() { handleInputCountChanged(false, false, false); } /** - * Checks the input counts and enable/disable TvActivity. Also updates the input list in - * {@link SetupUtils}. + * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link + * SetupUtils}. * - * @param calledByTunerServiceChanged true if it is called when TunerTvInputService - * is enabled or disabled. + * @param calledByTunerServiceChanged true if it is called when TunerTvInputService is enabled + * or disabled. * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true. - * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts - * by default. But, if dontKillApp is true, the app won't restart. + * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by + * default. But, if dontKillApp is true, the app won't restart. */ - public void handleInputCountChanged(boolean calledByTunerServiceChanged, - boolean tunerServiceEnabled, boolean dontKillApp) { + public void handleInputCountChanged( + boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) { TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); - boolean enable = (calledByTunerServiceChanged && tunerServiceEnabled) - || Features.UNHIDE.isEnabled(TvApplication.this); + boolean enable = + (calledByTunerServiceChanged && tunerServiceEnabled) + || Features.UNHIDE.isEnabled(TvApplication.this); if (!enable) { List inputs = inputManager.getTvInputList(); boolean skipTunerInputCheck = false; // Enable the TvActivity only if there is at least one tuner type input. if (!skipTunerInputCheck) { for (TvInputInfo input : inputs) { - if (calledByTunerServiceChanged && !tunerServiceEnabled + if (calledByTunerServiceChanged + && !tunerServiceEnabled && TunerTvInputService.getInputId(this).equals(input.getId())) { continue; } @@ -580,19 +557,19 @@ public class TvApplication extends Application implements ApplicationSingletons } PackageManager packageManager = getPackageManager(); ComponentName name = new ComponentName(this, TvActivity.class); - int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : - PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + int newState = + enable + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; if (packageManager.getComponentEnabledSetting(name) != newState) { - packageManager.setComponentEnabledSetting(name, newState, - dontKillApp ? PackageManager.DONT_KILL_APP : 0); + packageManager.setComponentEnabledSetting( + name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0); Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV."); } SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager); } - /** - * Returns the @{@link ApplicationSingletons} using the application context. - */ + /** Returns the @{@link ApplicationSingletons} using the application context. */ public static ApplicationSingletons getSingletons(Context context) { // No need to be "synchronized" because this doesn't create any instance. if (sAppSingletons == null) { @@ -602,12 +579,12 @@ public class TvApplication extends Application implements ApplicationSingletons } /** - * Sets true, if TvApplication is running on the main process. If TvApplication runs on - * tuner process or other process, it sets false. + * Sets true, if TvApplication is running on the main process. If TvApplication runs on tuner + * process or other process, it sets false. * - * Note: it should be called at the beginning of Service.onCreate Activity.onCreate, or - * BroadcastReceiver.onCreate. When it is firstly called after launch, it runs process - * specific initializations. + *

Note: it should be called at the beginning of Service.onCreate Activity.onCreate, or + * BroadcastReceiver.onCreate. When it is firstly called after launch, it runs process specific + * initializations. */ public static void setCurrentRunningProcess(Context context, boolean isMainProcess) { // TODO(b/63064354) TvApplication should not have to know if it is "the main process" diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java index 493e039c..4e0636ff 100644 --- a/src/com/android/tv/TvOptionsManager.java +++ b/src/com/android/tv/TvOptionsManager.java @@ -20,9 +20,7 @@ import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.annotation.IntDef; import android.util.SparseArray; - import com.android.tv.data.DisplayMode; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Locale; @@ -33,9 +31,17 @@ import java.util.Locale; */ public class TvOptionsManager { @Retention(RetentionPolicy.SOURCE) - @IntDef({OPTION_CLOSED_CAPTIONS, OPTION_DISPLAY_MODE, OPTION_SYSTEMWIDE_PIP, OPTION_MULTI_AUDIO, - OPTION_MORE_CHANNELS, OPTION_DEVELOPER, OPTION_SETTINGS}) + @IntDef({ + OPTION_CLOSED_CAPTIONS, + OPTION_DISPLAY_MODE, + OPTION_SYSTEMWIDE_PIP, + OPTION_MULTI_AUDIO, + OPTION_MORE_CHANNELS, + OPTION_DEVELOPER, + OPTION_SETTINGS + }) public @interface OptionType {} + public static final int OPTION_CLOSED_CAPTIONS = 0; public static final int OPTION_DISPLAY_MODE = 1; public static final int OPTION_SYSTEMWIDE_PIP = 2; @@ -57,6 +63,7 @@ public class TvOptionsManager { /** * Returns a suitable displayed string for the given option type under current settings. + * * @param option the type of option, should be one of {@link OptionType}. */ public String getOptionString(@OptionType int option) { @@ -67,8 +74,9 @@ public class TvOptionsManager { } return new Locale(mClosedCaptionsLanguage).getDisplayName(); case OPTION_DISPLAY_MODE: - return ((MainActivity) mContext).getTvViewUiManager() - .isDisplayModeAvailable(mDisplayMode) + return ((MainActivity) mContext) + .getTvViewUiManager() + .isDisplayModeAvailable(mDisplayMode) ? DisplayMode.getLabel(mDisplayMode, mContext) : DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext); case OPTION_MULTI_AUDIO: @@ -77,27 +85,25 @@ public class TvOptionsManager { return ""; } - /** - * Handles changing selection of closed caption. - */ + /** Handles changing selection of closed caption. */ public void onClosedCaptionsChanged(TvTrackInfo track, int trackIndex) { - mClosedCaptionsLanguage = (track == null) ? - null : (track.getLanguage() != null) ? track.getLanguage() - : mContext.getString(R.string.closed_caption_unknown_language, trackIndex + 1); + mClosedCaptionsLanguage = + (track == null) + ? null + : (track.getLanguage() != null) + ? track.getLanguage() + : mContext.getString( + R.string.closed_caption_unknown_language, trackIndex + 1); notifyOptionChanged(OPTION_CLOSED_CAPTIONS); } - /** - * Handles changing selection of display mode. - */ + /** Handles changing selection of display mode. */ public void onDisplayModeChanged(int displayMode) { mDisplayMode = displayMode; notifyOptionChanged(OPTION_DISPLAY_MODE); } - /** - * Handles changing selection of multi-audio. - */ + /** Handles changing selection of multi-audio. */ public void onMultiAudioChanged(String multiAudio) { mMultiAudio = multiAudio; notifyOptionChanged(OPTION_MULTI_AUDIO); @@ -110,17 +116,13 @@ public class TvOptionsManager { } } - /** - * Sets listeners to changes of the given option type. - */ + /** Sets listeners to changes of the given option type. */ public void setOptionChangedListener(int option, OptionChangedListener listener) { mOptionChangedListeners.put(option, listener); } - /** - * An interface used to monitor option changes. - */ + /** An interface used to monitor option changes. */ public interface OptionChangedListener { void onOptionChanged(@OptionType int optionType, String newString); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/analytics/Analytics.java b/src/com/android/tv/analytics/Analytics.java index 27085de7..e0626423 100644 --- a/src/com/android/tv/analytics/Analytics.java +++ b/src/com/android/tv/analytics/Analytics.java @@ -16,21 +16,17 @@ package com.android.tv.analytics; -/** - * Provides Trackers used for user activity analysis. - */ +/** Provides Trackers used for user activity analysis. */ public interface Analytics { Tracker getDefaultTracker(); - /** - * Returns whether the state of the application-level opt is on. - */ + /** Returns whether the state of the application-level opt is on. */ boolean isAppOptOut(); /** - * Sets or resets the application-level opt out flag. If set, no hits will be sent. - * The value of this flag will not persist across application starts. The - * correct value should thus be set in application initialization code. + * Sets or resets the application-level opt out flag. If set, no hits will be sent. The value of + * this flag will not persist across application starts. The correct value should thus be + * set in application initialization code. * * @param optOut {@code true} if application-level opt out should be enforced. */ diff --git a/src/com/android/tv/analytics/ConfigurationInfo.java b/src/com/android/tv/analytics/ConfigurationInfo.java index 41e8baeb..b6bfc5aa 100644 --- a/src/com/android/tv/analytics/ConfigurationInfo.java +++ b/src/com/android/tv/analytics/ConfigurationInfo.java @@ -16,9 +16,7 @@ package com.android.tv.analytics; -/** - * Data useful for tracking that doesn't change often. - */ +/** Data useful for tracking that doesn't change often. */ public class ConfigurationInfo { public final int systemInputCount; public final int nonSystemInputCount; diff --git a/src/com/android/tv/analytics/HasTrackerLabel.java b/src/com/android/tv/analytics/HasTrackerLabel.java index 566e5f1a..04896850 100644 --- a/src/com/android/tv/analytics/HasTrackerLabel.java +++ b/src/com/android/tv/analytics/HasTrackerLabel.java @@ -23,8 +23,6 @@ package com.android.tv.analytics; */ public interface HasTrackerLabel { - /** - * Returns the label. - */ + /** Returns the label. */ String getTrackerLabel(); } diff --git a/src/com/android/tv/analytics/SendChannelStatusRunnable.java b/src/com/android/tv/analytics/SendChannelStatusRunnable.java index b5b5805c..601e82f7 100644 --- a/src/com/android/tv/analytics/SendChannelStatusRunnable.java +++ b/src/com/android/tv/analytics/SendChannelStatusRunnable.java @@ -20,11 +20,9 @@ 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.util.RecurringRunner; - import java.util.List; import java.util.concurrent.TimeUnit; @@ -32,53 +30,63 @@ import java.util.concurrent.TimeUnit; * Periodically sends analytics data with the channel count. * *

- *

This should only be started from a user activity - * like {@link com.android.tv.MainActivity}. + * + *

This should only be started from a user activity like {@link com.android.tv.MainActivity}. */ @MainThread public class SendChannelStatusRunnable implements Runnable { private static final long SEND_CHANNEL_STATUS_INTERVAL_MS = TimeUnit.DAYS.toMillis(1); - public static RecurringRunner startChannelStatusRecurringRunner(Context context, - Tracker tracker, ChannelDataManager channelDataManager) { + public static RecurringRunner startChannelStatusRecurringRunner( + Context context, Tracker tracker, ChannelDataManager channelDataManager) { - final SendChannelStatusRunnable sendChannelStatusRunnable = new SendChannelStatusRunnable( - channelDataManager, tracker); + final SendChannelStatusRunnable sendChannelStatusRunnable = + new SendChannelStatusRunnable(channelDataManager, tracker); - Runnable onStopRunnable = new Runnable() { - @Override - public void run() { - sendChannelStatusRunnable.setDbLoadListener(null); - } - }; - final RecurringRunner recurringRunner = new RecurringRunner(context, - SEND_CHANNEL_STATUS_INTERVAL_MS, sendChannelStatusRunnable, onStopRunnable); + Runnable onStopRunnable = + new Runnable() { + @Override + public void run() { + sendChannelStatusRunnable.setDbLoadListener(null); + } + }; + final RecurringRunner recurringRunner = + new RecurringRunner( + context, + SEND_CHANNEL_STATUS_INTERVAL_MS, + sendChannelStatusRunnable, + onStopRunnable); if (channelDataManager.isDbLoadFinished()) { sendChannelStatusRunnable.setDbLoadListener(null); recurringRunner.start(); } else { - //Start the recurring runnable after the channel DB is finished loading. - sendChannelStatusRunnable.setDbLoadListener(new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - // This is called inside an iterator of Listeners so the remove step is done - // via a post on the main thread - new Handler(Looper.getMainLooper()).post(new Runnable() { + // Start the recurring runnable after the channel DB is finished loading. + sendChannelStatusRunnable.setDbLoadListener( + new ChannelDataManager.Listener() { @Override - public void run() { - sendChannelStatusRunnable.setDbLoadListener(null); + public void onLoadFinished() { + // This is called inside an iterator of Listeners so the remove step is + // done + // via a post on the main thread + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + sendChannelStatusRunnable.setDbLoadListener( + null); + } + }); + recurringRunner.start(); } - }); - recurringRunner.start(); - } - @Override - public void onChannelListUpdated() { } + @Override + public void onChannelListUpdated() {} - @Override - public void onChannelBrowsableChanged() { } - }); + @Override + public void onChannelBrowsableChanged() {} + }); } return recurringRunner; } diff --git a/src/com/android/tv/analytics/SendConfigInfoRunnable.java b/src/com/android/tv/analytics/SendConfigInfoRunnable.java index 41392a6d..d4674086 100644 --- a/src/com/android/tv/analytics/SendConfigInfoRunnable.java +++ b/src/com/android/tv/analytics/SendConfigInfoRunnable.java @@ -17,14 +17,10 @@ package com.android.tv.analytics; import android.media.tv.TvInputInfo; - import com.android.tv.util.TvInputManagerHelper; - import java.util.List; -/** - * Sends ConfigurationInfo once a day. - */ +/** Sends ConfigurationInfo once a day. */ public class SendConfigInfoRunnable implements Runnable { private final Tracker mTracker; private final TvInputManagerHelper mTvInputManagerHelper; @@ -46,8 +42,8 @@ public class SendConfigInfoRunnable implements Runnable { nonSystemInputCount++; } } - ConfigurationInfo configurationInfo = new ConfigurationInfo(systemInputCount, - nonSystemInputCount); + ConfigurationInfo configurationInfo = + new ConfigurationInfo(systemInputCount, nonSystemInputCount); mTracker.sendConfigurationInfo(configurationInfo); } } diff --git a/src/com/android/tv/analytics/StubAnalytics.java b/src/com/android/tv/analytics/StubAnalytics.java index 99c10d94..2c58b9b8 100644 --- a/src/com/android/tv/analytics/StubAnalytics.java +++ b/src/com/android/tv/analytics/StubAnalytics.java @@ -19,9 +19,7 @@ package com.android.tv.analytics; import android.app.Application; import android.content.Context; -/** - * An implementation of {@link Analytics} that returns a {@link StubTracker}. - */ +/** An implementation of {@link Analytics} that returns a {@link StubTracker}. */ public final class StubAnalytics implements Analytics { public static StubAnalytics getInstance(Application application) { return new StubAnalytics(application); @@ -30,8 +28,7 @@ public final class StubAnalytics implements Analytics { private final Tracker mTracker = new StubTracker(); private boolean mOptOut = true; - private StubAnalytics(Context context) { - } + private StubAnalytics(Context context) {} @Override public Tracker getDefaultTracker() { diff --git a/src/com/android/tv/analytics/StubTracker.java b/src/com/android/tv/analytics/StubTracker.java index 6e64ebca..4a926d58 100644 --- a/src/com/android/tv/analytics/StubTracker.java +++ b/src/com/android/tv/analytics/StubTracker.java @@ -17,111 +17,108 @@ package com.android.tv.analytics; import android.support.annotation.VisibleForTesting; - import com.android.tv.TimeShiftManager; import com.android.tv.data.Channel; -/** - * A implementation of Tracker that does nothing. - */ +/** A implementation of Tracker that does nothing. */ @VisibleForTesting public class StubTracker implements Tracker { @Override - public void sendChannelCount(int browsableChannelCount, int totalChannelCount) { } + public void sendChannelCount(int browsableChannelCount, int totalChannelCount) {} @Override - public void sendConfigurationInfo(ConfigurationInfo info) { } + public void sendConfigurationInfo(ConfigurationInfo info) {} @Override - public void sendMainStart() { } + public void sendMainStart() {} @Override - public void sendMainStop(long durationMs) { } + public void sendMainStop(long durationMs) {} @Override - public void sendScreenView(String screenName) { } + public void sendScreenView(String screenName) {} @Override - public void sendChannelViewStart(Channel channel, boolean tunedByRecommendation) { } + public void sendChannelViewStart(Channel channel, boolean tunedByRecommendation) {} @Override - public void sendChannelTuneTime(Channel channel, long durationMs) { } + public void sendChannelTuneTime(Channel channel, long durationMs) {} @Override - public void sendChannelViewStop(Channel channel, long durationMs) { } + public void sendChannelViewStop(Channel channel, long durationMs) {} @Override - public void sendChannelUp() { } + public void sendChannelUp() {} @Override - public void sendChannelDown() { } + public void sendChannelDown() {} @Override - public void sendShowMenu() { } + public void sendShowMenu() {} @Override - public void sendHideMenu(long durationMs) { } + public void sendHideMenu(long durationMs) {} @Override - public void sendMenuClicked(String label) { } + public void sendMenuClicked(String label) {} @Override - public void sendMenuClicked(int labelResId) { } + public void sendMenuClicked(int labelResId) {} @Override - public void sendShowEpg() { } + public void sendShowEpg() {} @Override - public void sendEpgItemClicked() { } + public void sendEpgItemClicked() {} @Override - public void sendHideEpg(long durationMs) { } + public void sendHideEpg(long durationMs) {} @Override - public void sendShowChannelSwitch() { } + public void sendShowChannelSwitch() {} @Override - public void sendHideChannelSwitch(long durationMs) { } + public void sendHideChannelSwitch(long durationMs) {} @Override - public void sendChannelNumberInput() { } + public void sendChannelNumberInput() {} @Override - public void sendChannelInputNavigated() { } + public void sendChannelInputNavigated() {} @Override - public void sendChannelNumberItemClicked() { } + public void sendChannelNumberItemClicked() {} @Override - public void sendChannelNumberItemChosenByTimeout() { } + public void sendChannelNumberItemChosenByTimeout() {} @Override - public void sendChannelVideoUnavailable(Channel channel, int reason) { } + public void sendChannelVideoUnavailable(Channel channel, int reason) {} @Override - public void sendAc3PassthroughCapabilities(boolean isSupported) { } + public void sendAc3PassthroughCapabilities(boolean isSupported) {} @Override - public void sendInputConnectionFailure(String inputId) { } + public void sendInputConnectionFailure(String inputId) {} @Override - public void sendInputDisconnected(String inputId) { } + public void sendInputDisconnected(String inputId) {} @Override - public void sendShowInputSelection() { } + public void sendShowInputSelection() {} @Override - public void sendHideInputSelection(long durationMs) { } + public void sendHideInputSelection(long durationMs) {} @Override - public void sendInputSelected(String inputLabel) { } + public void sendInputSelected(String inputLabel) {} @Override - public void sendShowSidePanel(HasTrackerLabel trackerLabel) { } + public void sendShowSidePanel(HasTrackerLabel trackerLabel) {} @Override - public void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs) { } + public void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs) {} @Override - public void sendTimeShiftAction(@TimeShiftManager.TimeShiftActionId int actionId) { } + public void sendTimeShiftAction(@TimeShiftManager.TimeShiftActionId int actionId) {} } diff --git a/src/com/android/tv/analytics/Tracker.java b/src/com/android/tv/analytics/Tracker.java index 291fc9ce..8c72e3b4 100644 --- a/src/com/android/tv/analytics/Tracker.java +++ b/src/com/android/tv/analytics/Tracker.java @@ -19,9 +19,7 @@ package com.android.tv.analytics; import com.android.tv.TimeShiftManager; import com.android.tv.data.Channel; -/** - * Interface for sending user activity for analysis. - */ +/** Interface for sending user activity for analysis. */ public interface Tracker { /** @@ -45,9 +43,7 @@ public interface Tracker { */ void sendConfigurationInfo(ConfigurationInfo info); - /** - * Sends tracking information for starting the MainActivity. - */ + /** Sends tracking information for starting the MainActivity. */ void sendMainStart(); /** @@ -55,11 +51,9 @@ public interface Tracker { * * @param durationMs The time main activity was "started" in milliseconds. */ - void sendMainStop( long durationMs); + void sendMainStop(long durationMs); - /** - * Sets the screen name and sends a ScreenView hit. - */ + /** Sets the screen name and sends a ScreenView hit. */ void sendScreenView(String screenName); /** @@ -86,19 +80,13 @@ public interface Tracker { */ void sendChannelViewStop(Channel channel, long durationMs); - /** - * Sends tracking information for pressing channel up. - */ + /** Sends tracking information for pressing channel up. */ void sendChannelUp(); - /** - * Sends tracking information for pressing channel down. - */ + /** Sends tracking information for pressing channel down. */ void sendChannelDown(); - /** - * Sends tracking information for showing the main menu. - */ + /** Sends tracking information for showing the main menu. */ void sendShowMenu(); /** @@ -126,14 +114,10 @@ public interface Tracker { */ void sendMenuClicked(int labelResId); - /** - * Sends tracking information for showing the Electronic Program Guide (EPG). - */ + /** Sends tracking information for showing the Electronic Program Guide (EPG). */ void sendShowEpg(); - /** - * Sends tracking information for clicking an Electronic Program Guide (EPG) item. - */ + /** Sends tracking information for clicking an Electronic Program Guide (EPG) item. */ void sendEpgItemClicked(); /** @@ -143,9 +127,7 @@ public interface Tracker { */ void sendHideEpg(long durationMs); - /** - * Sends tracking information for showing the channel switch view. - */ + /** Sends tracking information for showing the channel switch view. */ void sendShowChannelSwitch(); /** @@ -155,9 +137,7 @@ public interface Tracker { */ void sendHideChannelSwitch(long durationMs); - /** - * Sends tracking for each channel number or delimiter pressed. - */ + /** Sends tracking for each channel number or delimiter pressed. */ void sendChannelNumberInput(); /** @@ -167,19 +147,13 @@ public interface Tracker { */ void sendChannelInputNavigated(); - /** - * Sends tracking for channel clicked. - */ + /** Sends tracking for channel clicked. */ void sendChannelNumberItemClicked(); - /** - * Sends tracking for channel chosen (tuned) because the channel switch view timed out. - */ + /** Sends tracking for channel chosen (tuned) because the channel switch view timed out. */ void sendChannelNumberItemChosenByTimeout(); - /** - * Sends tracking for the reason video is unavailable on a channel. - */ + /** Sends tracking for the reason video is unavailable on a channel. */ void sendChannelVideoUnavailable(Channel channel, int reason); /** @@ -191,6 +165,7 @@ public interface Tracker { /** * Sends tracking for input a connection failure. + * *

WARNING callers must ensure no PII is included in the inputId. * * @param inputId the input the failure happened on @@ -199,15 +174,14 @@ public interface Tracker { /** * Sends tracking for input disconnected. + * *

WARNING callers must ensure no PII is included in the inputId. * * @param inputId the input the failure happened on */ void sendInputDisconnected(String inputId); - /** - * Sends tracking information for showing the input selection view. - */ + /** Sends tracking information for showing the input selection view. */ void sendShowInputSelection(); /** diff --git a/src/com/android/tv/config/ConfigKeys.java b/src/com/android/tv/config/ConfigKeys.java index 7df033d2..135017ae 100644 --- a/src/com/android/tv/config/ConfigKeys.java +++ b/src/com/android/tv/config/ConfigKeys.java @@ -16,12 +16,8 @@ package com.android.tv.config; -/** - * Static list of config keys. - */ +/** Static list of config keys. */ public final class ConfigKeys { - - private ConfigKeys() { - } + private ConfigKeys() {} } diff --git a/src/com/android/tv/config/DefaultConfigManager.java b/src/com/android/tv/config/DefaultConfigManager.java index bbabc6d4..4d754d1f 100644 --- a/src/com/android/tv/config/DefaultConfigManager.java +++ b/src/com/android/tv/config/DefaultConfigManager.java @@ -18,11 +18,10 @@ package com.android.tv.config; import android.content.Context; -/** - * Stub Remote Config. - */ +/** Stub Remote Config. */ public class DefaultConfigManager { public static final long DEFAULT_LONG_VALUE = 0; + public static DefaultConfigManager createInstance(Context context) { return new DefaultConfigManager(); } @@ -35,9 +34,7 @@ public class DefaultConfigManager { private static class StubRemoteConfig implements RemoteConfig { @Override - public void fetch(OnRemoteConfigUpdatedListener listener) { - - } + public void fetch(OnRemoteConfigUpdatedListener listener) {} @Override public String getString(String key) { @@ -55,7 +52,3 @@ public class DefaultConfigManager { } } } - - - - diff --git a/src/com/android/tv/config/RemoteConfig.java b/src/com/android/tv/config/RemoteConfig.java index f7ae87e7..d72a1f3f 100644 --- a/src/com/android/tv/config/RemoteConfig.java +++ b/src/com/android/tv/config/RemoteConfig.java @@ -19,31 +19,23 @@ package com.android.tv.config; /** * Manages Live TV Configuration, allowing remote updates. * - *

This is a thin wrapper around - * Firebase Remote Config + *

This is a thin wrapper around Firebase Remote Config */ public interface RemoteConfig { - /** - * Notified on successful completion of a {@link #fetch)} - */ + /** Notified on successful completion of a {@link #fetch)} */ interface OnRemoteConfigUpdatedListener { void onRemoteConfigUpdated(); } - /** - * Starts a fetch and notifies {@code listener} on successful completion. - */ + /** Starts a fetch and notifies {@code listener} on successful completion. */ void fetch(OnRemoteConfigUpdatedListener listener); - /** - * Gets value as a string corresponding to the specified key. - */ + /** Gets value as a string corresponding to the specified key. */ String getString(String key); - /** - * Gets value as a boolean corresponding to the specified key. - */ + /** Gets value as a boolean corresponding to the specified key. */ boolean getBoolean(String key); /** Gets value as a long corresponding to the specified key. */ diff --git a/src/com/android/tv/config/RemoteConfigFeature.java b/src/com/android/tv/config/RemoteConfigFeature.java index 502e6a9c..c22446be 100644 --- a/src/com/android/tv/config/RemoteConfigFeature.java +++ b/src/com/android/tv/config/RemoteConfigFeature.java @@ -17,13 +17,10 @@ package com.android.tv.config; import android.content.Context; - import com.android.tv.TvApplication; import com.android.tv.common.feature.Feature; -/** - * A {@link Feature} controlled by a {@link RemoteConfig} boolean. - */ +/** A {@link Feature} controlled by a {@link RemoteConfig} boolean. */ public class RemoteConfigFeature implements Feature { private final String mKey; diff --git a/src/com/android/tv/customization/CustomAction.java b/src/com/android/tv/customization/CustomAction.java index b8f4695b..77a5ae5e 100644 --- a/src/com/android/tv/customization/CustomAction.java +++ b/src/com/android/tv/customization/CustomAction.java @@ -20,10 +20,7 @@ import android.content.Intent; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; -/** - * Describes a custom option defined in customization package. - * This will be added to main menu. - */ +/** Describes a custom option defined in customization package. This will be added to main menu. */ public class CustomAction implements Comparable { private static final int POSITION_THRESHOLD = 100; @@ -40,9 +37,9 @@ public class CustomAction implements Comparable { } /** - * Returns if this option comes before the existing items. - * Note that custom options can only be placed at the front or back. - * (i.e. cannot be added in the middle of existing options.) + * Returns if this option comes before the existing items. Note that custom options can only be + * placed at the front or back. (i.e. cannot be added in the middle of existing options.) + * * @return {@code true} if it goes to the beginning. {@code false} if it goes to the end. */ public boolean isFront() { @@ -54,23 +51,17 @@ public class CustomAction implements Comparable { return mPositionPriority - another.mPositionPriority; } - /** - * Returns title. - */ + /** Returns title. */ public String getTitle() { return mTitle; } - /** - * Returns icon drawable. - */ + /** Returns icon drawable. */ public Drawable getIconDrawable() { return mIconDrawable; } - /** - * Returns intent to launch when this option is clicked. - */ + /** Returns intent to launch when this option is clicked. */ public Intent getIntent() { return mIntent; } diff --git a/src/com/android/tv/customization/TvCustomizationManager.java b/src/com/android/tv/customization/TvCustomizationManager.java index ed6b98ca..7d21c6d2 100644 --- a/src/com/android/tv/customization/TvCustomizationManager.java +++ b/src/com/android/tv/customization/TvCustomizationManager.java @@ -27,7 +27,6 @@ import android.graphics.drawable.Drawable; import android.support.annotation.IntDef; import android.text.TextUtils; import android.util.Log; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -41,33 +40,30 @@ public class TvCustomizationManager { private static final boolean DEBUG = false; private static final String[] CUSTOMIZE_PERMISSIONS = { - "com.android.tv.permission.CUSTOMIZE_TV_APP" + "com.android.tv.permission.CUSTOMIZE_TV_APP" }; - private static final String CATEGORY_TV_CUSTOMIZATION = - "com.android.tv.category"; + private static final String CATEGORY_TV_CUSTOMIZATION = "com.android.tv.category"; - /** - * Row IDs to share customized actions. - * Only rows listed below can have customized action. - */ + /** Row IDs to share customized actions. Only rows listed below can have customized action. */ public static final String ID_OPTIONS_ROW = "options_row"; + public static final String ID_PARTNER_ROW = "partner_row"; @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE}) @Retention(RetentionPolicy.SOURCE) public @interface TRICKPLAY_MODE {} + public static final int TRICKPLAY_MODE_ENABLED = 0; public static final int TRICKPLAY_MODE_DISABLED = 1; public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2; private static final String[] TRICKPLAY_MODE_STRINGS = { - "enabled", - "disabled", - "use_external_storage_only" + "enabled", "disabled", "use_external_storage_only" }; private static final HashMap INTENT_CATEGORY_TO_ROW_ID; + static { INTENT_CATEGORY_TO_ROW_ID = new HashMap<>(); INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW); @@ -108,10 +104,14 @@ public class TvCustomizationManager { sHasLinuxDvbBuiltInTuner = false; } else { try { - Resources res = context.getPackageManager() - .getResourcesForApplication(sCustomizationPackage); - int resId = res.getIdentifier(RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, - RES_TYPE_BOOLEAN, sCustomizationPackage); + Resources res = + context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = + res.getIdentifier( + RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, + RES_TYPE_BOOLEAN, + sCustomizationPackage); sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId); } catch (NameNotFoundException e) { sHasLinuxDvbBuiltInTuner = false; @@ -128,10 +128,12 @@ public class TvCustomizationManager { } else { try { String customization = null; - Resources res = context.getPackageManager() - .getResourcesForApplication(sCustomizationPackage); - int resId = res.getIdentifier(RES_ID_TRICKPLAY_MODE, - RES_TYPE_STRING, sCustomizationPackage); + Resources res = + context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = + res.getIdentifier( + RES_ID_TRICKPLAY_MODE, RES_TYPE_STRING, sCustomizationPackage); customization = resId == 0 ? null : res.getString(resId); sTrickplayMode = TRICKPLAY_MODE_ENABLED; if (customization != null) { @@ -152,17 +154,15 @@ public class TvCustomizationManager { private static String getCustomizationPackageName(Context context) { if (sCustomizationPackage == null) { - List packageInfos = context.getPackageManager() - .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); + List packageInfos = + context.getPackageManager() + .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName; } return sCustomizationPackage; } - /** - * Initialize TV customization options. - * Run this API only on the main thread. - */ + /** Initialize TV customization options. Run this API only on the main thread. */ public void initialize() { if (mInitialized) { return; @@ -181,14 +181,21 @@ public class TvCustomizationManager { Intent customOptionIntent = new Intent(Intent.ACTION_MAIN); customOptionIntent.addCategory(intentCategory); - List activities = pm.queryIntentActivities(customOptionIntent, - PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER - | PackageManager.GET_META_DATA); + List activities = + pm.queryIntentActivities( + customOptionIntent, + PackageManager.GET_RECEIVERS + | PackageManager.GET_RESOLVED_FILTER + | PackageManager.GET_META_DATA); for (ResolveInfo info : activities) { String packageName = info.activityInfo.packageName; if (!TextUtils.equals(packageName, sCustomizationPackage)) { - Log.w(TAG, "A customization package " + sCustomizationPackage - + " already exist. Ignoring " + packageName); + Log.w( + TAG, + "A customization package " + + sCustomizationPackage + + " already exist. Ignoring " + + packageName); continue; } @@ -217,8 +224,14 @@ public class TvCustomizationManager { Log.d(TAG, "Dumping custom actions"); for (String id : mRowIdToCustomActionsMap.keySet()) { for (CustomAction action : mRowIdToCustomActionsMap.get(id)) { - Log.d(TAG, "Custom row rowId=" + id + " title=" + action.getTitle() - + " class=" + action.getIntent()); + Log.d( + TAG, + "Custom row rowId=" + + id + + " title=" + + action.getTitle() + + " class=" + + action.getIntent()); } } Log.d(TAG, "Dumping custom actions - end of dump"); @@ -228,7 +241,7 @@ public class TvCustomizationManager { /** * Returns custom actions for given row id. * - * Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW. + *

Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW. */ public List getCustomActions(String rowId) { return mRowIdToCustomActionsMap.get(rowId); @@ -238,23 +251,20 @@ public class TvCustomizationManager { mPartnerRowTitle = null; Resources res; try { - res = mContext.getPackageManager() - .getResourcesForApplication(sCustomizationPackage); + res = mContext.getPackageManager().getResourcesForApplication(sCustomizationPackage); } catch (NameNotFoundException e) { Log.w(TAG, "Could not get resources for package " + sCustomizationPackage); return; } - int resId = res.getIdentifier( - RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); + int resId = + res.getIdentifier(RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); if (resId != 0) { mPartnerRowTitle = res.getString(resId); } if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]"); } - /** - * Returns partner row title. - */ + /** Returns partner row title. */ public String getPartnerRowTitle() { return mPartnerRowTitle; } diff --git a/src/com/android/tv/data/BaseProgram.java b/src/com/android/tv/data/BaseProgram.java index 4e36c80a..6cb682b8 100644 --- a/src/com/android/tv/data/BaseProgram.java +++ b/src/com/android/tv/data/BaseProgram.java @@ -20,14 +20,12 @@ import android.content.Context; import android.media.tv.TvContentRating; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.R; - import java.util.Comparator; /** - * Base class for {@link com.android.tv.data.Program} and - * {@link com.android.tv.dvr.data.RecordedProgram}. + * Base class for {@link com.android.tv.data.Program} and {@link + * com.android.tv.dvr.data.RecordedProgram}. */ public abstract class BaseProgram { /** @@ -35,8 +33,7 @@ public abstract class BaseProgram { * If a program's season or episode number is null, it will be consider "smaller" than programs * with season or episode numbers. */ - public static final Comparator EPISODE_COMPARATOR = - new EpisodeComparator(false); + public static final Comparator EPISODE_COMPARATOR = new EpisodeComparator(false); /** * Comparator used to compare {@link BaseProgram} according to its season and episodes number @@ -58,8 +55,7 @@ public abstract class BaseProgram { if (lhs == rhs) { return 0; } - int seasonNumberCompare = - numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber()); + int seasonNumberCompare = numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber()); if (seasonNumberCompare != 0) { return mReversedSeason ? -seasonNumberCompare : seasonNumberCompare; } else { @@ -68,9 +64,7 @@ public abstract class BaseProgram { } } - /** - * Compares two strings represent season numbers or episode numbers of programs. - */ + /** Compares two strings represent season numbers or episode numbers of programs. */ public static int numberCompare(String s1, String s2) { if (s1 == s2) { return 0; @@ -88,121 +82,87 @@ public abstract class BaseProgram { } } - /** - * Returns ID of the program. - */ - abstract public long getId(); + /** Returns ID of the program. */ + public abstract long getId(); - /** - * Returns the title of the program. - */ - abstract public String getTitle(); + /** Returns the title of the program. */ + public abstract String getTitle(); - /** - * Returns the episode title. - */ - abstract public String getEpisodeTitle(); + /** Returns the episode title. */ + public abstract String getEpisodeTitle(); - /** - * Returns the displayed title of the program episode. - */ + /** Returns the displayed title of the program episode. */ public String getEpisodeDisplayTitle(Context context) { if (!TextUtils.isEmpty(getEpisodeNumber())) { String episodeTitle = getEpisodeTitle() == null ? "" : getEpisodeTitle(); if (TextUtils.equals(getSeasonNumber(), "0")) { // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_title_format_no_season_number), - getEpisodeNumber(), episodeTitle); + return String.format( + context.getResources() + .getString(R.string.display_episode_title_format_no_season_number), + getEpisodeNumber(), + episodeTitle); } else { - return String.format(context.getResources().getString( - R.string.display_episode_title_format), - getSeasonNumber(), getEpisodeNumber(), episodeTitle); + return String.format( + context.getResources().getString(R.string.display_episode_title_format), + getSeasonNumber(), + getEpisodeNumber(), + episodeTitle); } } return getEpisodeTitle(); } - /** - * Returns the description of the program. - */ - abstract public String getDescription(); + /** Returns the description of the program. */ + public abstract String getDescription(); - /** - * Returns the long description of the program. - */ - abstract public String getLongDescription(); + /** Returns the long description of the program. */ + public abstract String getLongDescription(); - /** - * Returns the start time of the program in Milliseconds. - */ - abstract public long getStartTimeUtcMillis(); + /** Returns the start time of the program in Milliseconds. */ + public abstract long getStartTimeUtcMillis(); - /** - * Returns the end time of the program in Milliseconds. - */ - abstract public long getEndTimeUtcMillis(); + /** Returns the end time of the program in Milliseconds. */ + public abstract long getEndTimeUtcMillis(); - /** - * Returns the duration of the program in Milliseconds. - */ - abstract public long getDurationMillis(); + /** Returns the duration of the program in Milliseconds. */ + public abstract long getDurationMillis(); - /** - * Returns the series ID. - */ - abstract public String getSeriesId(); + /** Returns the series ID. */ + public abstract String getSeriesId(); - /** - * Returns the season number. - */ - abstract public String getSeasonNumber(); + /** Returns the season number. */ + public abstract String getSeasonNumber(); - /** - * Returns the episode number. - */ - abstract public String getEpisodeNumber(); + /** Returns the episode number. */ + public abstract String getEpisodeNumber(); - /** - * Returns URI of the program's poster. - */ - abstract public String getPosterArtUri(); + /** Returns URI of the program's poster. */ + public abstract String getPosterArtUri(); - /** - * Returns URI of the program's thumbnail. - */ - abstract public String getThumbnailUri(); + /** Returns URI of the program's thumbnail. */ + public abstract String getThumbnailUri(); - /** - * Returns the array of the ID's of the canonical genres. - */ - abstract public int[] getCanonicalGenreIds(); + /** Returns the array of the ID's of the canonical genres. */ + public abstract int[] getCanonicalGenreIds(); /** Returns the array of content ratings. */ @Nullable - abstract public TvContentRating[] getContentRatings(); + public abstract TvContentRating[] getContentRatings(); - /** - * Returns channel's ID of the program. - */ - abstract public long getChannelId(); + /** Returns channel's ID of the program. */ + public abstract long getChannelId(); - /** - * Returns if the program is valid. - */ - abstract public boolean isValid(); + /** Returns if the program is valid. */ + public abstract boolean isValid(); - /** - * Checks whether the program is episodic or not. - */ + /** Checks whether the program is episodic or not. */ public boolean isEpisodic() { return getSeriesId() != null; } - /** - * Generates the series ID for the other inputs than the tuner TV input. - */ + /** Generates the series ID for the other inputs than the tuner TV input. */ public static String generateSeriesId(String packageName, String title) { return packageName + "/" + title; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java index 4a391ae7..eda188e4 100644 --- a/src/com/android/tv/data/Channel.java +++ b/src/com/android/tv/data/Channel.java @@ -28,21 +28,17 @@ import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.TvCommonConstants; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.net.URISyntaxException; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Objects; -/** - * A convenience class to create and insert channel entries into the database. - */ +/** A convenience class to create and insert channel entries into the database. */ public final class Channel { private static final String TAG = "Channel"; @@ -51,15 +47,14 @@ public final class Channel { public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2; public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3; - /** - * Compares the channel numbers of channels which belong to the same input. - */ - public static final Comparator CHANNEL_NUMBER_COMPARATOR = new Comparator() { - @Override - public int compare(Channel lhs, Channel rhs) { - return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); - } - }; + /** Compares the channel numbers of channels which belong to the same input. */ + public static final Comparator CHANNEL_NUMBER_COMPARATOR = + new Comparator() { + @Override + public int compare(Channel lhs, Channel rhs) { + return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); + } + }; /** * When a TIS doesn't provide any information about app link, and it doesn't have a leanback @@ -67,8 +62,8 @@ public final class Channel { */ 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. + * 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; /** @@ -81,36 +76,33 @@ public final class Channel { private static final String INVALID_PACKAGE_NAME = "packageName"; public static final String[] PROJECTION = { - // Columns must match what is read in Channel.fromCursor() - TvContract.Channels._ID, - TvContract.Channels.COLUMN_PACKAGE_NAME, - TvContract.Channels.COLUMN_INPUT_ID, - TvContract.Channels.COLUMN_TYPE, - TvContract.Channels.COLUMN_DISPLAY_NUMBER, - TvContract.Channels.COLUMN_DISPLAY_NAME, - TvContract.Channels.COLUMN_DESCRIPTION, - TvContract.Channels.COLUMN_VIDEO_FORMAT, - TvContract.Channels.COLUMN_BROWSABLE, - TvContract.Channels.COLUMN_SEARCHABLE, - TvContract.Channels.COLUMN_LOCKED, - TvContract.Channels.COLUMN_APP_LINK_TEXT, - TvContract.Channels.COLUMN_APP_LINK_COLOR, - TvContract.Channels.COLUMN_APP_LINK_ICON_URI, - TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI, - TvContract.Channels.COLUMN_APP_LINK_INTENT_URI, - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input + // Columns must match what is read in Channel.fromCursor() + TvContract.Channels._ID, + TvContract.Channels.COLUMN_PACKAGE_NAME, + TvContract.Channels.COLUMN_INPUT_ID, + TvContract.Channels.COLUMN_TYPE, + TvContract.Channels.COLUMN_DISPLAY_NUMBER, + TvContract.Channels.COLUMN_DISPLAY_NAME, + TvContract.Channels.COLUMN_DESCRIPTION, + TvContract.Channels.COLUMN_VIDEO_FORMAT, + TvContract.Channels.COLUMN_BROWSABLE, + TvContract.Channels.COLUMN_SEARCHABLE, + TvContract.Channels.COLUMN_LOCKED, + TvContract.Channels.COLUMN_APP_LINK_TEXT, + TvContract.Channels.COLUMN_APP_LINK_COLOR, + TvContract.Channels.COLUMN_APP_LINK_ICON_URI, + TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI, + TvContract.Channels.COLUMN_APP_LINK_INTENT_URI, + TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input }; - /** - * Channel number delimiter between major and minor parts. - */ + /** Channel number delimiter between major and minor parts. */ public static final char CHANNEL_NUMBER_DELIMITER = '-'; /** * Creates {@code Channel} object from cursor. * *

The query that created the cursor MUST use {@link #PROJECTION} - * */ public static Channel fromCursor(Cursor cursor) { // Columns read must match the order of {@link #PROJECTION} @@ -138,15 +130,14 @@ public final class Channel { return channel; } - /** - * Replaces the channel number separator with dash('-'). - */ + /** Replaces the channel number separator with dash('-'). */ public static String normalizeDisplayNumber(String string) { if (!TextUtils.isEmpty(string)) { int length = string.length(); for (int i = 0; i < length; i++) { char c = string.charAt(i); - if (c == '.' || Character.isWhitespace(c) + if (c == '.' + || Character.isWhitespace(c) || Character.getType(c) == Character.DASH_PUNCTUATION) { StringBuilder sb = new StringBuilder(string); sb.setCharAt(i, CHANNEL_NUMBER_DELIMITER); @@ -233,11 +224,12 @@ public final class Channel { } /** - * Gets identification text for displaying or debugging. - * It's made from Channels' display number plus their display name. + * Gets identification text for displaying or debugging. It's made from Channels' display number + * plus their display name. */ public String getDisplayText() { - return TextUtils.isEmpty(mDisplayName) ? mDisplayNumber + return TextUtils.isEmpty(mDisplayName) + ? mDisplayNumber : mDisplayNumber + " " + mDisplayName; } @@ -261,9 +253,7 @@ public final class Channel { return mAppLinkIntentUri; } - /** - * Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. - */ + /** Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. */ public String getLogoUri() { return mLogoUri; } @@ -272,16 +262,12 @@ public final class Channel { return mRecordingProhibited; } - /** - * Checks whether this channel is physical tuner channel or not. - */ + /** Checks whether this channel is physical tuner channel or not. */ public boolean isPhysicalTunerChannel() { return !TextUtils.isEmpty(mType) && !TvContract.Channels.TYPE_OTHER.equals(mType); } - /** - * Checks if two channels equal by checking ids. - */ + /** Checks if two channels equal by checking ids. */ @Override public boolean equals(Object o) { if (!(o instanceof Channel)) { @@ -289,7 +275,8 @@ public final class Channel { } Channel other = (Channel) o; // All pass-through TV channels have INVALID_ID value for mId. - return mId == other.mId && TextUtils.equals(mInputId, other.mInputId) + return mId == other.mId + && TextUtils.equals(mInputId, other.mInputId) && mIsPassthrough == other.mIsPassthrough; } @@ -319,9 +306,7 @@ public final class Channel { mLocked = locked; } - /** - * Sets channel logo uri which is got from cloud. - */ + /** Sets channel logo uri which is got from cloud. */ public void setLogoUri(String logoUri) { mLogoUri = logoUri; } @@ -353,20 +338,35 @@ public final class Channel { @Override public String toString() { return "Channel{" - + "id=" + mId - + ", packageName=" + mPackageName - + ", inputId=" + mInputId - + ", type=" + mType - + ", displayNumber=" + mDisplayNumber - + ", displayName=" + mDisplayName - + ", description=" + mDescription - + ", videoFormat=" + mVideoFormat - + ", isPassthrough=" + mIsPassthrough - + ", browsable=" + mBrowsable - + ", searchable=" + mSearchable - + ", locked=" + mLocked - + ", appLinkText=" + mAppLinkText - + ", recordingProhibited=" + mRecordingProhibited + "}"; + + "id=" + + mId + + ", packageName=" + + mPackageName + + ", inputId=" + + mInputId + + ", type=" + + mType + + ", displayNumber=" + + mDisplayNumber + + ", displayName=" + + mDisplayName + + ", description=" + + mDescription + + ", videoFormat=" + + mVideoFormat + + ", isPassthrough=" + + mIsPassthrough + + ", browsable=" + + mBrowsable + + ", searchable=" + + mSearchable + + ", locked=" + + mLocked + + ", appLinkText=" + + mAppLinkText + + ", recordingProhibited=" + + mRecordingProhibited + + "}"; } void copyFrom(Channel other) { @@ -396,9 +396,7 @@ public final class Channel { mChannelLogoExist = other.mChannelLogoExist; } - /** - * Creates a channel for a passthrough TV input. - */ + /** Creates a channel for a passthrough TV input. */ public static Channel createPassthroughChannel(Uri uri) { if (!TvContract.isChannelUriForPassthroughInput(uri)) { throw new IllegalArgumentException("URI is not a passthrough channel URI"); @@ -407,27 +405,19 @@ public final class Channel { return createPassthroughChannel(inputId); } - /** - * Creates a channel for a passthrough TV input with {@code inputId}. - */ + /** Creates a channel for a passthrough TV input with {@code inputId}. */ public static Channel createPassthroughChannel(String inputId) { - return new Builder() - .setInputId(inputId) - .setPassthrough(true) - .build(); + return new Builder().setInputId(inputId).setPassthrough(true).build(); } - /** - * Checks whether the channel is valid or not. - */ + /** Checks whether the channel is valid or not. */ public static boolean isValid(Channel channel) { return channel != null && (channel.mId != INVALID_ID || channel.mIsPassthrough); } /** - * Builder class for {@code Channel}. - * Suppress using this outside of ChannelDataManager - * so Channels could be managed by ChannelDataManager. + * Builder class for {@code Channel}. Suppress using this outside of ChannelDataManager so + * Channels could be managed by ChannelDataManager. */ public static final class Builder { private final Channel mChannel; @@ -555,9 +545,7 @@ public final class Channel { } } - /** - * Prefetches the images for this channel. - */ + /** Prefetches the images for this channel. */ public void prefetchImage(Context context, int type, int maxWidth, int maxHeight) { String uriString = getImageUriString(type); if (!TextUtils.isEmpty(uriString)) { @@ -566,47 +554,48 @@ public final class Channel { } /** - * Loads the bitmap of this channel and returns it via {@code callback}. - * The loaded bitmap will be cached and resized with given params. - *

- * Note that it may directly call {@code callback} if the bitmap is already loaded. + * Loads the bitmap of this channel and returns it via {@code callback}. The loaded bitmap will + * be cached and resized with given params. + * + *

Note that it may directly call {@code callback} if the bitmap is already loaded. * * @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}. + * @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}. * @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. */ @UiThread - public void loadBitmap(Context context, final int type, int maxWidth, int maxHeight, + public void loadBitmap( + Context context, + final int type, + int maxWidth, + int maxHeight, ImageLoader.ImageLoaderCallback callback) { String uriString = getImageUriString(type); ImageLoader.loadBitmap(context, uriString, maxWidth, maxHeight, callback); } /** - * Sets if the channel logo exists. This method should be only called from - * {@link ChannelDataManager}. + * Sets if the channel logo exists. This method should be only called from {@link + * ChannelDataManager}. */ void setChannelLogoExist(boolean exist) { mChannelLogoExist = exist; } - /** - * Returns if channel logo exists. - */ + /** Returns if channel logo exists. */ public boolean channelLogoExists() { return mChannelLogoExist; } /** - * 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 #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. */ public int getAppLinkType(Context context) { if (mAppLinkType == APP_LINK_TYPE_NOT_SET) { @@ -616,8 +605,8 @@ 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}. + * Returns the app link intent for this channel. If the type of app link is {@link + * #APP_LINK_TYPE_NONE}, it returns {@code null}. */ public Intent getAppLinkIntent(Context context) { if (mAppLinkType == APP_LINK_TYPE_NOT_SET) { @@ -635,8 +624,8 @@ public final class Channel { Intent intent = Intent.parseUri(mAppLinkIntentUri, Intent.URI_INTENT_SCHEME); if (intent.resolveActivityInfo(pm, 0) != null) { mAppLinkIntent = intent; - mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, - getUri().toString()); + mAppLinkIntent.putExtra( + TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); mAppLinkType = APP_LINK_TYPE_CHANNEL; return; } else { @@ -652,8 +641,8 @@ public final class Channel { } mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName); if (mAppLinkIntent != null) { - mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, - getUri().toString()); + mAppLinkIntent.putExtra( + TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); mAppLinkType = APP_LINK_TYPE_APP; } } @@ -699,8 +688,10 @@ public final class Channel { // Compare the input labels. String lhsLabel = getInputLabelForChannel(lhs); String rhsLabel = getInputLabelForChannel(rhs); - int result = lhsLabel == null ? (rhsLabel == null ? 0 : 1) : rhsLabel == null ? -1 - : lhsLabel.compareTo(rhsLabel); + int result = + lhsLabel == null + ? (rhsLabel == null ? 0 : 1) + : rhsLabel == null ? -1 : lhsLabel.compareTo(rhsLabel); if (result != 0) { return result; } @@ -712,8 +703,13 @@ public final class Channel { // Compare the channel numbers if both channels belong to the same input. result = ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); if (mDetectDuplicatesEnabled && result == 0) { - Log.w(TAG, "Duplicate channels detected! - \"" - + lhs.getDisplayText() + "\" and \"" + rhs.getDisplayText() + "\""); + Log.w( + TAG, + "Duplicate channels detected! - \"" + + lhs.getDisplayText() + + "\" and \"" + + rhs.getDisplayText() + + "\""); } return result; } @@ -733,4 +729,4 @@ public final class Channel { return label; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java index 6f93fbd1..e4d1cd85 100644 --- a/src/com/android/tv/data/ChannelDataManager.java +++ b/src/com/android/tv/data/ChannelDataManager.java @@ -38,7 +38,6 @@ import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Log; import android.util.MutableInt; - import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; @@ -46,7 +45,6 @@ import com.android.tv.util.AsyncDbTask; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -58,11 +56,10 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; /** - * The class to manage channel data. - * Basic features: reading channel list and each channel's current program, and updating - * the values of {@link Channels#COLUMN_BROWSABLE}, {@link Channels#COLUMN_LOCKED}. - * This class is not thread-safe and under an assumption that its public methods are called in - * only the main thread. + * The class to manage channel data. Basic features: reading channel list and each channel's current + * program, and updating the values of {@link Channels#COLUMN_BROWSABLE}, {@link + * Channels#COLUMN_LOCKED}. This class is not thread-safe and under an assumption that its public + * methods are called in only the main thread. */ @AnyThread public class ChannelDataManager { @@ -81,7 +78,7 @@ public class ChannelDataManager { private final Set mListeners = new CopyOnWriteArraySet<>(); // Use container class to support multi-thread safety. This value can be set only on the main // thread. - volatile private UnmodifiableChannelData mData = new UnmodifiableChannelData(); + private volatile UnmodifiableChannelData mData = new UnmodifiableChannelData(); private final Channel.DefaultComparator mChannelComparator; private final Handler mHandler; @@ -93,53 +90,54 @@ public class ChannelDataManager { private final boolean mStoreBrowsableInSharedPreferences; private final SharedPreferences mBrowsableSharedPreferences; - private final TvInputCallback mTvInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - boolean channelAdded = false; - ChannelData data = new ChannelData(mData); - for (ChannelWrapper channel : mData.channelWrapperMap.values()) { - if (channel.mChannel.getInputId().equals(inputId)) { - channel.mInputRemoved = false; - addChannel(data, channel.mChannel); - channelAdded = true; + private final TvInputCallback mTvInputCallback = + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + boolean channelAdded = false; + ChannelData data = new ChannelData(mData); + for (ChannelWrapper channel : mData.channelWrapperMap.values()) { + if (channel.mChannel.getInputId().equals(inputId)) { + channel.mInputRemoved = false; + addChannel(data, channel.mChannel); + channelAdded = true; + } + } + if (channelAdded) { + Collections.sort(data.channels, mChannelComparator); + mData = new UnmodifiableChannelData(data); + notifyChannelListUpdated(); + } } - } - if (channelAdded) { - Collections.sort(data.channels, mChannelComparator); - mData = new UnmodifiableChannelData(data); - notifyChannelListUpdated(); - } - } - @Override - public void onInputRemoved(String inputId) { - boolean channelRemoved = false; - ArrayList removedChannels = new ArrayList<>(); - for (ChannelWrapper channel : mData.channelWrapperMap.values()) { - if (channel.mChannel.getInputId().equals(inputId)) { - channel.mInputRemoved = true; - channelRemoved = true; - removedChannels.add(channel); - } - } - if (channelRemoved) { - ChannelData data = new ChannelData(); - data.channelWrapperMap.putAll(mData.channelWrapperMap); - for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) { - if (!channelWrapper.mInputRemoved) { - addChannel(data, channelWrapper.mChannel); + @Override + public void onInputRemoved(String inputId) { + boolean channelRemoved = false; + ArrayList removedChannels = new ArrayList<>(); + for (ChannelWrapper channel : mData.channelWrapperMap.values()) { + if (channel.mChannel.getInputId().equals(inputId)) { + channel.mInputRemoved = true; + channelRemoved = true; + removedChannels.add(channel); + } + } + if (channelRemoved) { + ChannelData data = new ChannelData(); + data.channelWrapperMap.putAll(mData.channelWrapperMap); + for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) { + if (!channelWrapper.mInputRemoved) { + addChannel(data, channelWrapper.mChannel); + } + } + Collections.sort(data.channels, mChannelComparator); + mData = new UnmodifiableChannelData(data); + notifyChannelListUpdated(); + for (ChannelWrapper channel : removedChannels) { + channel.notifyChannelRemoved(); + } } } - Collections.sort(data.channels, mChannelComparator); - mData = new UnmodifiableChannelData(data); - notifyChannelListUpdated(); - for (ChannelWrapper channel : removedChannels) { - channel.notifyChannelRemoved(); - } - } - } - }; + }; @MainThread public ChannelDataManager(Context context, TvInputManagerHelper inputManager) { @@ -148,8 +146,8 @@ public class ChannelDataManager { @MainThread @VisibleForTesting - ChannelDataManager(Context context, TvInputManagerHelper inputManager, - ContentResolver contentResolver) { + ChannelDataManager( + Context context, TvInputManagerHelper inputManager, ContentResolver contentResolver) { mContext = context; mInputManager = inputManager; mContentResolver = contentResolver; @@ -157,17 +155,19 @@ public class ChannelDataManager { // Detect duplicate channels while sorting. mChannelComparator.setDetectDuplicatesEnabled(true); mHandler = new ChannelDataManagerHandler(this); - mChannelObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) { - mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS); - } - } - }; + mChannelObserver = + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) { + mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS); + } + } + }; mStoreBrowsableInSharedPreferences = !PermissionUtils.hasAccessAllEpg(mContext); - mBrowsableSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); + mBrowsableSharedPreferences = + context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); } @VisibleForTesting @@ -175,9 +175,7 @@ public class ChannelDataManager { return mChannelObserver; } - /** - * Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called. - */ + /** Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called. */ @MainThread public void start() { if (mStarted) { @@ -187,8 +185,8 @@ public class ChannelDataManager { // Should be called directly instead of posting MSG_UPDATE_CHANNELS message to the handler. // If not, other DB tasks can be executed before channel loading. handleUpdateChannels(); - mContentResolver.registerContentObserver(TvContract.Channels.CONTENT_URI, true, - mChannelObserver); + mContentResolver.registerContentObserver( + TvContract.Channels.CONTENT_URI, true, mChannelObserver); mInputManager.addCallback(mTvInputCallback); } @@ -218,9 +216,7 @@ public class ChannelDataManager { applyUpdatedValuesToDb(); } - /** - * Adds a {@link Listener}. - */ + /** Adds a {@link Listener}. */ public void addListener(Listener listener) { if (DEBUG) Log.d(TAG, "addListener " + listener); SoftPreconditions.checkNotNull(listener); @@ -229,9 +225,7 @@ public class ChannelDataManager { } } - /** - * Removes a {@link Listener}. - */ + /** Removes a {@link Listener}. */ public void removeListener(Listener listener) { if (DEBUG) Log.d(TAG, "removeListener " + listener); SoftPreconditions.checkNotNull(listener); @@ -252,8 +246,8 @@ public class ChannelDataManager { } /** - * Removes a {@link ChannelListener} for a specific channel with the channel ID - * {@code channelId}. + * Removes a {@link ChannelListener} for a specific channel with the channel ID {@code + * channelId}. */ public void removeChannelListener(Long channelId, ChannelListener listener) { ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); @@ -263,30 +257,22 @@ public class ChannelDataManager { channelWrapper.removeListener(listener); } - /** - * Checks whether data is ready. - */ + /** Checks whether data is ready. */ public boolean isDbLoadFinished() { return mDbLoadFinished; } - /** - * Returns the number of channels. - */ + /** Returns the number of channels. */ public int getChannelCount() { return mData.channels.size(); } - /** - * Returns a list of channels. - */ + /** Returns a list of channels. */ public List getChannelList() { return new ArrayList<>(mData.channels); } - /** - * Returns a list of browsable channels. - */ + /** Returns a list of browsable channels. */ public List getBrowsableChannelList() { List channels = new ArrayList<>(); for (Channel channel : mData.channels) { @@ -329,9 +315,7 @@ public class ChannelDataManager { return true; } - /** - * Gets the channel with the channel ID {@code channelId}. - */ + /** Gets the channel with the channel ID {@code channelId}. */ public Channel getChannel(Long channelId) { ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null || channelWrapper.mInputRemoved) { @@ -340,9 +324,7 @@ public class ChannelDataManager { return channelWrapper.mChannel; } - /** - * The value change will be applied to DB when applyPendingDbOperation is called. - */ + /** The value change will be applied to DB when applyPendingDbOperation is called. */ public void updateBrowsable(Long channelId, boolean browsable) { updateBrowsable(channelId, browsable, false); } @@ -351,12 +333,12 @@ public class ChannelDataManager { * The value change will be applied to DB when applyPendingDbOperation is called. * * @param skipNotifyChannelBrowsableChanged If it's true, {@link Listener - * #onChannelBrowsableChanged()} is not called, when this method is called. - * {@link #notifyChannelBrowsableChanged} should be directly called, once browsable - * update is completed. + * #onChannelBrowsableChanged()} is not called, when this method is called. {@link + * #notifyChannelBrowsableChanged} should be directly called, once browsable update is + * completed. */ - public void updateBrowsable(Long channelId, boolean browsable, - boolean skipNotifyChannelBrowsableChanged) { + public void updateBrowsable( + Long channelId, boolean browsable, boolean skipNotifyChannelBrowsableChanged) { ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { return; @@ -396,10 +378,7 @@ public class ChannelDataManager { } } - /** - * Updates channels from DB. Once the update is done, {@code postRunnable} will - * be called. - */ + /** Updates channels from DB. Once the update is done, {@code postRunnable} will be called. */ public void updateChannels(Runnable postRunnable) { if (mChannelsUpdateTask != null) { mChannelsUpdateTask.cancel(true); @@ -411,9 +390,7 @@ public class ChannelDataManager { } } - /** - * The value change will be applied to DB when applyPendingDbOperation is called. - */ + /** The value change will be applied to DB when applyPendingDbOperation is called. */ public void updateLocked(Long channelId, boolean locked) { ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId); if (channelWrapper == null) { @@ -430,10 +407,7 @@ public class ChannelDataManager { } } - /** - * Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked} - * to DB. - */ + /** Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked} to DB. */ public void applyUpdatedValuesToDb() { ChannelData data = mData; ArrayList browsableIds = new ArrayList<>(); @@ -493,11 +467,17 @@ public class ChannelDataManager { } mLockedUpdateChannelIds.clear(); if (DEBUG) { - Log.d(TAG, "applyUpdatedValuesToDb" - + "\n browsableIds size:" + browsableIds.size() - + "\n unbrowsableIds size:" + unbrowsableIds.size() - + "\n lockedIds size:" + lockedIds.size() - + "\n unlockedIds size:" + unlockedIds.size()); + Log.d( + TAG, + "applyUpdatedValuesToDb" + + "\n browsableIds size:" + + browsableIds.size() + + "\n unbrowsableIds size:" + + unbrowsableIds.size() + + "\n lockedIds size:" + + lockedIds.size() + + "\n unlockedIds size:" + + unlockedIds.size()); } } @@ -527,48 +507,34 @@ public class ChannelDataManager { mChannelsUpdateTask.executeOnDbThread(); } - /** - * Reloads channel data. - */ + /** Reloads channel data. */ public void reload() { if (mDbLoadFinished && !mHandler.hasMessages(MSG_UPDATE_CHANNELS)) { mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS); } } - /** - * A listener for ChannelDataManager. The callbacks are called on the main thread. - */ + /** A listener for ChannelDataManager. The callbacks are called on the main thread. */ public interface Listener { - /** - * Called when data load is finished. - */ + /** Called when data load is finished. */ void onLoadFinished(); /** - * Called when channels are added, deleted, or updated. But, when browsable is changed, - * it won't be called. Instead, {@link #onChannelBrowsableChanged} will be called. + * Called when channels are added, deleted, or updated. But, when browsable is changed, it + * won't be called. Instead, {@link #onChannelBrowsableChanged} will be called. */ void onChannelListUpdated(); - /** - * Called when browsable of channels are changed. - */ + /** Called when browsable of channels are changed. */ void onChannelBrowsableChanged(); } - /** - * A listener for individual channel change. The callbacks are called on the main thread. - */ + /** A listener for individual channel change. The callbacks are called on the main thread. */ public interface ChannelListener { - /** - * Called when the channel has been removed in DB. - */ + /** Called when the channel has been removed in DB. */ void onChannelRemoved(Channel channel); - /** - * Called when values of the channel has been changed. - */ + /** Called when values of the channel has been changed. */ void onChannelUpdated(Channel channel); } @@ -616,8 +582,10 @@ public class ChannelDataManager { @Override protected Boolean doInBackground(Void... params) { - try (AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor( - TvContract.buildChannelLogoUri(mChannel.getId()), "r")) { + try (AssetFileDescriptor f = + mContext.getContentResolver() + .openAssetFileDescriptor( + TvContract.buildChannelLogoUri(mChannel.getId()), "r")) { return true; } catch (SQLiteException | IOException | NullPointerException e) { // File not found or asset file not found. @@ -663,8 +631,8 @@ public class ChannelDataManager { for (Channel channel : channels) { if (mStoreBrowsableInSharedPreferences) { String browsableKey = getBrowsableKey(channel); - channel.setBrowsable(mBrowsableSharedPreferences.getBoolean(browsableKey, - false)); + channel.setBrowsable( + mBrowsableSharedPreferences.getBoolean(browsableKey, false)); deletedBrowsableMap.remove(browsableKey); } long channelId = channel.getId(); @@ -698,7 +666,8 @@ public class ChannelDataManager { } } } - if (mStoreBrowsableInSharedPreferences && !deletedBrowsableMap.isEmpty() + if (mStoreBrowsableInSharedPreferences + && !deletedBrowsableMap.isEmpty() && PermissionUtils.hasReadTvListings(mContext)) { // If hasReadTvListings(mContext) is false, the given channel list would // empty. In this case, we skip the browsable data clean up process. @@ -745,24 +714,26 @@ public class ChannelDataManager { } /** - * Updates a column {@code columnName} of DB table {@code uri} with the value - * {@code columnValue}. The selective rows in the ID list {@code ids} will be updated. - * The DB operations will run on {@link AsyncDbTask#getExecutor()}. + * Updates a column {@code columnName} of DB table {@code uri} with the value {@code + * columnValue}. The selective rows in the ID list {@code ids} will be updated. The DB + * operations will run on {@link AsyncDbTask#getExecutor()}. */ private void updateOneColumnValue( final String columnName, final int columnValue, final List ids) { if (!PermissionUtils.hasAccessAllEpg(mContext)) { return; } - AsyncDbTask.executeOnDbThread(new Runnable() { - @Override - public void run() { - String selection = Utils.buildSelectionForIds(Channels._ID, ids); - ContentValues values = new ContentValues(); - values.put(columnName, columnValue); - mContentResolver.update(TvContract.Channels.CONTENT_URI, values, selection, null); - } - }); + AsyncDbTask.executeOnDbThread( + new Runnable() { + @Override + public void run() { + String selection = Utils.buildSelectionForIds(Channels._ID, ids); + ContentValues values = new ContentValues(); + values.put(columnName, columnValue); + mContentResolver.update( + TvContract.Channels.CONTENT_URI, values, selection, null); + } + }); } private String getBrowsableKey(Channel channel) { @@ -784,9 +755,8 @@ public class ChannelDataManager { } /** - * Container class which includes channel data that needs to be synced. This class is - * modifiable and used for changing channel data. - * e.g. TvInputCallback, or AsyncDbTask.onPostExecute. + * Container class which includes channel data that needs to be synced. This class is modifiable + * and used for changing channel data. e.g. TvInputCallback, or AsyncDbTask.onPostExecute. */ @MainThread private static class ChannelData { @@ -806,8 +776,10 @@ public class ChannelDataManager { channels = new ArrayList<>(data.channels); } - ChannelData(Map channelWrapperMap, - Map channelCountMap, List channels) { + ChannelData( + Map channelWrapperMap, + Map channelCountMap, + List channels) { this.channelWrapperMap = channelWrapperMap; this.channelCountMap = channelCountMap; this.channels = channels; @@ -818,13 +790,15 @@ public class ChannelDataManager { @MainThread private static class UnmodifiableChannelData extends ChannelData { UnmodifiableChannelData() { - super(Collections.unmodifiableMap(new HashMap<>()), + super( + Collections.unmodifiableMap(new HashMap<>()), Collections.unmodifiableMap(new HashMap<>()), Collections.unmodifiableList(new ArrayList<>())); } UnmodifiableChannelData(ChannelData data) { - super(Collections.unmodifiableMap(data.channelWrapperMap), + super( + Collections.unmodifiableMap(data.channelWrapperMap), Collections.unmodifiableMap(data.channelCountMap), Collections.unmodifiableList(data.channels)); } diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java index 132cab7a..2dc43102 100644 --- a/src/com/android/tv/data/ChannelLogoFetcher.java +++ b/src/com/android/tv/data/ChannelLogoFetcher.java @@ -28,17 +28,15 @@ import android.os.RemoteException; import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.util.BitmapUtils; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; import com.android.tv.util.PermissionUtils; - import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Map; import java.util.List; +import java.util.Map; /** * Fetches channel logos from the cloud into the database. It's for the channels which have no logos @@ -54,12 +52,11 @@ public class ChannelLogoFetcher { private static FetchLogoTask sFetchTask; /** - * Fetches the channel logos from the cloud data and insert them into TvProvider. - * The previous task is canceled and a new task starts. + * Fetches the channel logos from the cloud data and insert them into TvProvider. The previous + * task is canceled and a new task starts. */ @MainThread - public static void startFetchingChannelLogos( - Context context, List channels) { + public static void startFetchingChannelLogos(Context context, List channels) { if (!PermissionUtils.hasAccessAllEpg(context)) { // TODO: support this feature for non-system LC app. b/23939816 return; @@ -76,8 +73,7 @@ public class ChannelLogoFetcher { sFetchTask.execute(); } - private ChannelLogoFetcher() { - } + private ChannelLogoFetcher() {} private static final class FetchLogoTask extends AsyncTask { private final Context mContext; @@ -105,8 +101,8 @@ public class ChannelLogoFetcher { Context.MODE_PRIVATE); SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); Map uncheckedChannels = sharedPreferences.getAll(); - boolean isFirstTimeFetchChannelLogo = sharedPreferences.getBoolean( - PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, true); + boolean isFirstTimeFetchChannelLogo = + sharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, true); // Iterating channels. for (Channel channel : mChannels) { String channelIdString = Long.toString(channel.getId()); @@ -117,7 +113,7 @@ public class ChannelLogoFetcher { sharedPreferencesEditor.putString(channelIdString, channel.getLogoUri()); } else if (TextUtils.isEmpty(channel.getLogoUri()) && (!TextUtils.isEmpty(storedChannelLogoUri) - || isFirstTimeFetchChannelLogo)) { + || isFirstTimeFetchChannelLogo)) { channelsToRemove.add(channel); sharedPreferencesEditor.remove(channelIdString); } @@ -136,11 +132,18 @@ public class ChannelLogoFetcher { } // Downloads the channel logo. String logoUri = channel.getLogoUri(); - ScaledBitmapInfo bitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString( - mContext, logoUri, Integer.MAX_VALUE, Integer.MAX_VALUE); + ScaledBitmapInfo bitmapInfo = + BitmapUtils.decodeSampledBitmapFromUriString( + mContext, logoUri, Integer.MAX_VALUE, Integer.MAX_VALUE); if (bitmapInfo == null) { - Log.e(TAG, "Failed to load bitmap. {channelName=" + channel.getDisplayName() - + ", " + "logoUri=" + logoUri + "}"); + Log.e( + TAG, + "Failed to load bitmap. {channelName=" + + channel.getDisplayName() + + ", " + + "logoUri=" + + logoUri + + "}"); continue; } if (isCancelled()) { @@ -160,8 +163,13 @@ public class ChannelLogoFetcher { continue; } if (DEBUG) { - Log.d(TAG, "Inserting logo file to DB succeeded. {from=" + logoUri + ", to=" - + dstLogoUri + "}"); + Log.d( + TAG, + "Inserting logo file to DB succeeded. {from=" + + logoUri + + ", to=" + + dstLogoUri + + "}"); } } @@ -171,8 +179,10 @@ public class ChannelLogoFetcher { if (!channelsToRemove.isEmpty()) { ArrayList ops = new ArrayList<>(); for (Channel channel : channelsToRemove) { - ops.add(ContentProviderOperation.newDelete( - TvContract.buildChannelLogoUri(channel.getId())).build()); + ops.add( + ContentProviderOperation.newDelete( + TvContract.buildChannelLogoUri(channel.getId())) + .build()); } try { mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java index 29054aa5..63f8a972 100644 --- a/src/com/android/tv/data/ChannelNumber.java +++ b/src/com/android/tv/data/ChannelNumber.java @@ -19,18 +19,17 @@ package com.android.tv.data; import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.KeyEvent; - import com.android.tv.util.StringUtils; - import java.util.Objects; -/** - * A convenience class to handle channel number. - */ +/** A convenience class to handle channel number. */ public final class ChannelNumber implements Comparable { private static final int[] CHANNEL_DELIMITER_KEYCODES = { - KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_PERIOD, - KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_SPACE + KeyEvent.KEYCODE_MINUS, + KeyEvent.KEYCODE_NUMPAD_SUBTRACT, + KeyEvent.KEYCODE_PERIOD, + KeyEvent.KEYCODE_NUMPAD_DOT, + KeyEvent.KEYCODE_SPACE }; /** The major part of the channel number. */ @@ -68,8 +67,7 @@ public final class ChannelNumber implements Comparable { int minor = hasDelimiter ? Integer.parseInt(minorNumber) : 0; int opponentMajor = Integer.parseInt(another.majorNumber); - int opponentMinor = another.hasDelimiter - ? Integer.parseInt(another.minorNumber) : 0; + int opponentMinor = another.hasDelimiter ? Integer.parseInt(another.minorNumber) : 0; if (major == opponentMajor) { return minor - opponentMinor; } @@ -103,10 +101,10 @@ public final class ChannelNumber implements Comparable { /** * Returns the ChannelNumber instance. - *

- * Note that all the channel number argument should be normalized by - * {@link Channel#normalizeDisplayNumber}. The channels retrieved from - * {@link ChannelDataManager} are already normalized. + * + *

Note that all the channel number argument should be normalized by {@link + * Channel#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} are + * already normalized. */ public static ChannelNumber parseChannelNumber(String number) { if (number == null) { @@ -134,10 +132,10 @@ public final class ChannelNumber implements Comparable { /** * Compares the channel numbers. - *

- * Note that all the channel number arguments should be normalized by - * {@link Channel#normalizeDisplayNumber}. The channels retrieved from - * {@link ChannelDataManager} are already normalized. + * + *

Note that all the channel number arguments should be normalized by {@link + * Channel#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} are + * already normalized. */ public static int compare(String lhs, String rhs) { ChannelNumber lhsNumber = parseChannelNumber(lhs); @@ -156,7 +154,7 @@ public final class ChannelNumber implements Comparable { private static boolean isInteger(String string) { try { Integer.parseInt(string); - } catch(NumberFormatException | NullPointerException e) { + } catch (NumberFormatException | NullPointerException e) { return false; } return true; diff --git a/src/com/android/tv/data/DisplayMode.java b/src/com/android/tv/data/DisplayMode.java index ccba5480..dbcca010 100644 --- a/src/com/android/tv/data/DisplayMode.java +++ b/src/com/android/tv/data/DisplayMode.java @@ -17,7 +17,6 @@ package com.android.tv.data; import android.content.Context; - import com.android.tv.R; public class DisplayMode { @@ -28,12 +27,10 @@ public class DisplayMode { public static final int MODE_ZOOM = 2; public static final int SIZE_OF_RATIO_TYPES = MODE_ZOOM + 1; - /** - * Constant to indicate that any mode is not set yet. - */ + /** Constant to indicate that any mode is not set yet. */ public static final int MODE_NOT_DEFINED = -1; - private DisplayMode() { } + private DisplayMode() {} public static String getLabel(int mode, Context context) { return context.getResources().getStringArray(R.array.display_mode_labels)[mode]; diff --git a/src/com/android/tv/data/GenreItems.java b/src/com/android/tv/data/GenreItems.java index b12fd1aa..dfecb63c 100644 --- a/src/com/android/tv/data/GenreItems.java +++ b/src/com/android/tv/data/GenreItems.java @@ -18,13 +18,10 @@ package com.android.tv.data; import android.content.Context; import android.media.tv.TvContract.Programs.Genres; - import com.android.tv.R; public class GenreItems { - /** - * Genre ID indicating all channels. - */ + /** Genre ID indicating all channels. */ public static final int ID_ALL_CHANNELS = 0; private static final String[] CANONICAL_GENRES = { @@ -48,11 +45,9 @@ public class GenreItems { Genres.TECH_SCIENCE }; - private GenreItems() { } + private GenreItems() {} - /** - * Returns array of all genre labels. - */ + /** Returns array of all genre labels. */ public static String[] getLabels(Context context) { String[] items = context.getResources().getStringArray(R.array.genre_labels); if (items.length != CANONICAL_GENRES.length) { @@ -61,16 +56,14 @@ public class GenreItems { return items; } - /** - * Returns the number of genres including all channels. - */ + /** Returns the number of genres including all channels. */ public static int getGenreCount() { return CANONICAL_GENRES.length; } /** - * Returns the canonical genre for the given id. - * If the id is invalid, {@code null} will be returned instead. + * Returns the canonical genre for the given id. If the id is invalid, {@code null} will be + * returned instead. */ public static String getCanonicalGenre(int id) { if (id < 0 || id >= CANONICAL_GENRES.length) { @@ -80,8 +73,8 @@ public class GenreItems { } /** - * Returns id for the given canonical genre. - * If the genre is invalid, {@link #ID_ALL_CHANNELS} will be returned instead. + * Returns id for the given canonical genre. If the genre is invalid, {@link #ID_ALL_CHANNELS} + * will be returned instead. */ public static int getId(String canonicalGenre) { if (canonicalGenre == null) { diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java index e33ca18f..4c30d395 100644 --- a/src/com/android/tv/data/InternalDataUtils.java +++ b/src/com/android/tv/data/InternalDataUtils.java @@ -19,10 +19,8 @@ package com.android.tv.data; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; - import com.android.tv.data.Program.CriticScore; import com.android.tv.dvr.data.RecordedProgram; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -31,22 +29,22 @@ import java.io.ObjectOutputStream; import java.util.List; /** - * A utility class to parse and store data from the - * {@link android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the - * {@link android.media.tv.TvContract.Programs}. + * A utility class to parse and store data from the {@link + * android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the {@link + * android.media.tv.TvContract.Programs}. */ public final class InternalDataUtils { private static final boolean DEBUG = false; private static final String TAG = "InternalDataUtils"; private InternalDataUtils() { - //do nothing + // do nothing } /** * Deserializes a byte array into objects to be stored in the Program class. * - *

Series ID and critic scores are loaded from the bytes. + *

Series ID and critic scores are loaded from the bytes. * * @param bytes the bytes to be deserialized * @param builder the builder for the Program class @@ -70,6 +68,7 @@ public final class InternalDataUtils { /** * Convenience method for converting relevant data in Program class to a serialized blob type * for storage in internal_provider_data field. + * * @param program the program which contains the objects to be serialized * @return serialized blob-type data */ @@ -83,8 +82,10 @@ public final class InternalDataUtils { return bos.toByteArray(); } } catch (IOException e) { - Log.e(TAG, "Could not serialize internal provider contents for program: " - + program.getTitle()); + Log.e( + TAG, + "Could not serialize internal provider contents for program: " + + program.getTitle()); } return null; } @@ -92,13 +93,13 @@ public final class InternalDataUtils { /** * Deserializes a byte array into objects to be stored in the RecordedProgram class. * - *

Series ID is loaded from the bytes. + *

Series ID is loaded from the bytes. * * @param bytes the bytes to be deserialized * @param builder the builder for the RecordedProgram class */ - public static void deserializeInternalProviderData(byte[] bytes, - RecordedProgram.Builder builder) { + public static void deserializeInternalProviderData( + byte[] bytes, RecordedProgram.Builder builder) { if (bytes == null || bytes.length == 0) { return; } @@ -115,6 +116,7 @@ public final class InternalDataUtils { /** * Serializes relevant objects in {@link android.media.tv.TvContract.Programs} to byte array. + * * @return the serialized byte array */ public static byte[] serializeInternalProviderData(RecordedProgram program) { @@ -125,9 +127,11 @@ public final class InternalDataUtils { return bos.toByteArray(); } } catch (IOException e) { - Log.e(TAG, "Could not serialize internal provider contents for program: " - + program.getTitle()); + Log.e( + TAG, + "Could not serialize internal provider contents for program: " + + program.getTitle()); } return null; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/data/Lineup.java b/src/com/android/tv/data/Lineup.java index d0e9d7ba..0f11c1cc 100644 --- a/src/com/android/tv/data/Lineup.java +++ b/src/com/android/tv/data/Lineup.java @@ -17,74 +17,59 @@ package com.android.tv.data; import android.support.annotation.IntDef; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * A class that represents a lineup. - */ +/** A class that represents a lineup. */ public class Lineup { - /** - * The ID of this lineup. - */ + /** The ID of this lineup. */ public final String id; - /** - * The type associated with this lineup. - */ + /** The type associated with this lineup. */ public final int type; - /** - * The human readable name associated with this lineup. - */ + /** The human readable name associated with this lineup. */ public final String name; /** - * Location this lineup can be found. - * This is a human readable description of a geographic location. + * Location this lineup can be found. This is a human readable description of a geographic + * location. */ public final String location; @Retention(RetentionPolicy.SOURCE) - @IntDef({LINEUP_CABLE, LINEUP_SATELLITE, LINEUP_BROADCAST_DIGITAL, LINEUP_BROADCAST_ANALOG, - LINEUP_IPTV, LINEUP_MVPD}) + @IntDef({ + LINEUP_CABLE, + LINEUP_SATELLITE, + LINEUP_BROADCAST_DIGITAL, + LINEUP_BROADCAST_ANALOG, + LINEUP_IPTV, + LINEUP_MVPD + }) public @interface LineupType {} - /** - * Lineup type for cable. - */ + /** Lineup type for cable. */ public static final int LINEUP_CABLE = 0; - /** - * Lineup type for satelite. - */ + /** Lineup type for satelite. */ public static final int LINEUP_SATELLITE = 1; - /** - * Lineup type for broadcast digital. - */ + /** Lineup type for broadcast digital. */ public static final int LINEUP_BROADCAST_DIGITAL = 2; - /** - * Lineup type for broadcast analog. - */ + /** Lineup type for broadcast analog. */ public static final int LINEUP_BROADCAST_ANALOG = 3; - /** - * Lineup type for IPTV. - */ + /** Lineup type for IPTV. */ public static final int LINEUP_IPTV = 4; /** * Indicates the lineup is either satelite, cable or IPTV but we are not sure which specific * type. - */ + */ public static final int LINEUP_MVPD = 5; - /** - * Creates a lineup. - */ + /** Creates a lineup. */ public Lineup(String id, int type, String name, String location) { this.id = id; this.type = type; diff --git a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java index 77b6c9b8..edb33556 100644 --- a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java +++ b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java @@ -17,8 +17,6 @@ package com.android.tv.data; public interface OnCurrentProgramUpdatedListener { - /** - * Called when the current program is updated. - */ + /** Called when the current program is updated. */ void onCurrentProgramUpdated(long channelId, Program program); } diff --git a/src/com/android/tv/data/ParcelableList.java b/src/com/android/tv/data/ParcelableList.java index 78f444e4..1c1f5f29 100644 --- a/src/com/android/tv/data/ParcelableList.java +++ b/src/com/android/tv/data/ParcelableList.java @@ -18,18 +18,13 @@ package com.android.tv.data; import android.os.Parcel; import android.os.Parcelable; - import java.util.ArrayList; import java.util.Collection; import java.util.List; -/** - * A convenience class for the list of {@link Parcelable}s. - */ +/** A convenience class for the list of {@link Parcelable}s. */ public final class ParcelableList implements Parcelable { - /** - * Create instance from {@link Parcel}. - */ + /** Create instance from {@link Parcel}. */ public static ParcelableList fromParcel(Parcel in) { ParcelableList list = new ParcelableList(); int length = in.readInt(); @@ -41,32 +36,29 @@ public final class ParcelableList implements Parcelable { return list; } - /** - * A creator for {@link ParcelableList}. - */ - public static final Creator CREATOR = new Creator() { - @Override - public ParcelableList createFromParcel(Parcel in) { - return ParcelableList.fromParcel(in); - } + /** A creator for {@link ParcelableList}. */ + public static final Creator CREATOR = + new Creator() { + @Override + public ParcelableList createFromParcel(Parcel in) { + return ParcelableList.fromParcel(in); + } - @Override - public ParcelableList[] newArray(int size) { - return new ParcelableList[size]; - } - }; + @Override + public ParcelableList[] newArray(int size) { + return new ParcelableList[size]; + } + }; private final List mList = new ArrayList<>(); - private ParcelableList() { } + private ParcelableList() {} public ParcelableList(Collection initialList) { mList.addAll(initialList); } - /** - * Returns the list. - */ + /** Returns the list. */ public List getList() { return new ArrayList(mList); } diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java index 6311a870..b103a5d7 100644 --- a/src/com/android/tv/data/PreviewDataManager.java +++ b/src/com/android/tv/data/PreviewDataManager.java @@ -35,10 +35,8 @@ import android.support.media.tv.ChannelLogoUtils; import android.support.media.tv.PreviewProgram; import android.util.Log; import android.util.Pair; - import com.android.tv.R; import com.android.tv.util.PermissionUtils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; @@ -46,9 +44,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * Class to manage the preview data. - */ +/** Class to manage the preview data. */ @TargetApi(Build.VERSION_CODES.O) @MainThread public class PreviewDataManager { @@ -56,21 +52,16 @@ public class PreviewDataManager { // STOPSHIP: set it to false. private static final boolean DEBUG = true; - /** - * Invalid preview channel ID. - */ + /** Invalid preview channel ID. */ public static final long INVALID_PREVIEW_CHANNEL_ID = -1; - @IntDef({(int)TYPE_DEFAULT_PREVIEW_CHANNEL, (int)TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) + + @IntDef({(int) TYPE_DEFAULT_PREVIEW_CHANNEL, (int) TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) @Retention(RetentionPolicy.SOURCE) - public @interface PreviewChannelType{} + public @interface PreviewChannelType {} - /** - * Type of default preview channel - */ + /** Type of default preview channel */ public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1; - /** - * Type of recorded program channel - */ + /** Type of recorded program channel */ public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2; private final Context mContext; @@ -80,8 +71,7 @@ public class PreviewDataManager { private final Set mPreviewDataListeners = new CopyOnWriteArraySet<>(); private QueryPreviewDataTask mQueryPreviewTask; - private final Map mCreatePreviewChannelTasks = - new HashMap<>(); + private final Map mCreatePreviewChannelTasks = new HashMap<>(); private final Map mUpdatePreviewProgramTasks = new HashMap<>(); private final int mPreviewChannelLogoWidth; @@ -90,15 +80,13 @@ public class PreviewDataManager { public PreviewDataManager(Context context) { mContext = context.getApplicationContext(); mContentResolver = context.getContentResolver(); - mPreviewChannelLogoWidth = mContext.getResources().getDimensionPixelSize( - R.dimen.preview_channel_logo_width); - mPreviewChannelLogoHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.preview_channel_logo_height); + mPreviewChannelLogoWidth = + mContext.getResources().getDimensionPixelSize(R.dimen.preview_channel_logo_width); + mPreviewChannelLogoHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.preview_channel_logo_height); } - /** - * Starts the preview data manager. - */ + /** Starts the preview data manager. */ public void start() { if (mQueryPreviewTask == null) { mQueryPreviewTask = new QueryPreviewDataTask(); @@ -106,19 +94,17 @@ public class PreviewDataManager { } } - /** - * Stops the preview data manager. - */ + /** Stops the preview data manager. */ public void stop() { if (mQueryPreviewTask != null) { mQueryPreviewTask.cancel(true); } - for (CreatePreviewChannelTask createPreviewChannelTask - : mCreatePreviewChannelTasks.values()) { + for (CreatePreviewChannelTask createPreviewChannelTask : + mCreatePreviewChannelTasks.values()) { createPreviewChannelTask.cancel(true); } - for (UpdatePreviewProgramTask updatePreviewProgramTask - : mUpdatePreviewProgramTasks.values()) { + for (UpdatePreviewProgramTask updatePreviewProgramTask : + mUpdatePreviewProgramTasks.values()) { updatePreviewProgramTask.cancel(true); } @@ -127,31 +113,26 @@ public class PreviewDataManager { mUpdatePreviewProgramTasks.clear(); } - /** - * Gets preview channel ID from the preview channel type. - */ + /** Gets preview channel ID from the preview channel type. */ public @PreviewChannelType long getPreviewChannelId(long previewChannelType) { return mPreviewData.getPreviewChannelId(previewChannelType); } - /** - * Creates default preview channel. - */ + /** Creates default preview channel. */ public void createDefaultPreviewChannel( OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) { createPreviewChannel(TYPE_DEFAULT_PREVIEW_CHANNEL, onPreviewChannelCreationResultListener); } - /** - * Creates a preview channel for specific channel type. - */ - public void createPreviewChannel(@PreviewChannelType long previewChannelType, + /** Creates a preview channel for specific channel type. */ + public void createPreviewChannel( + @PreviewChannelType long previewChannelType, OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) { CreatePreviewChannelTask currentRunningCreateTask = mCreatePreviewChannelTasks.get(previewChannelType); if (currentRunningCreateTask == null) { - CreatePreviewChannelTask createPreviewChannelTask = new CreatePreviewChannelTask( - previewChannelType); + CreatePreviewChannelTask createPreviewChannelTask = + new CreatePreviewChannelTask(previewChannelType); createPreviewChannelTask.addOnPreviewChannelCreationResultListener( onPreviewChannelCreationResultListener); createPreviewChannelTask.execute(); @@ -162,32 +143,26 @@ public class PreviewDataManager { } } - /** - * Returns {@code true} if the preview data is loaded. - */ + /** Returns {@code true} if the preview data is loaded. */ public boolean isLoadFinished() { return mLoadFinished; } - /** - * Adds listener. - */ + /** Adds listener. */ public void addListener(PreviewDataListener previewDataListener) { mPreviewDataListeners.add(previewDataListener); } - /** - * Removes listener. - */ + /** Removes listener. */ public void removeListener(PreviewDataListener previewDataListener) { mPreviewDataListeners.remove(previewDataListener); } - /** - * Updates the preview programs table for a specific preview channel. - */ - public void updatePreviewProgramsForChannel(long previewChannelId, - Set programs, PreviewDataListener previewDataListener) { + /** Updates the preview programs table for a specific preview channel. */ + public void updatePreviewProgramsForChannel( + long previewChannelId, + Set programs, + PreviewDataListener previewDataListener) { UpdatePreviewProgramTask currentRunningUpdateTask = mUpdatePreviewProgramTasks.get(previewChannelId); if (currentRunningUpdateTask != null @@ -215,22 +190,19 @@ public class PreviewDataManager { } public interface PreviewDataListener { - /** - * Called when the preview data is loaded. - */ + /** Called when the preview data is loaded. */ void onPreviewDataLoadFinished(); - /** - * Called when the preview data is updated. - */ + /** Called when the preview data is updated. */ void onPreviewDataUpdateFinished(); } public interface OnPreviewChannelCreationResultListener { /** * Called when the creation of preview channel is finished. - * @param createdPreviewChannelId The preview channel ID if created successfully, - * otherwise it's {@value #INVALID_PREVIEW_CHANNEL_ID}. + * + * @param createdPreviewChannelId The preview channel ID if created successfully, otherwise + * it's {@value #INVALID_PREVIEW_CHANNEL_ID}. */ void onPreviewChannelCreationResult(long createdPreviewChannelId); } @@ -352,9 +324,11 @@ public class PreviewDataManager { if (DEBUG) Log.d(TAG, "CreatePreviewChannelTask.doInBackground"); long previewChannelId; try { - Uri channelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI, - PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType) - .toContentValues()); + Uri channelUri = + mContentResolver.insert( + TvContract.Channels.CONTENT_URI, + PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType) + .toContentValues()); if (channelUri != null) { previewChannelId = ContentUris.parseId(channelUri); } else { @@ -367,9 +341,14 @@ public class PreviewDataManager { } Drawable appIcon = mContext.getApplicationInfo().loadIcon(mContext.getPackageManager()); if (appIcon != null && appIcon instanceof BitmapDrawable) { - ChannelLogoUtils.storeChannelLogo(mContext, previewChannelId, - Bitmap.createScaledBitmap(((BitmapDrawable) appIcon).getBitmap(), - mPreviewChannelLogoWidth, mPreviewChannelLogoHeight, false)); + ChannelLogoUtils.storeChannelLogo( + mContext, + previewChannelId, + Bitmap.createScaledBitmap( + ((BitmapDrawable) appIcon).getBitmap(), + mPreviewChannelLogoWidth, + mPreviewChannelLogoHeight, + false)); } return previewChannelId; } @@ -380,8 +359,8 @@ public class PreviewDataManager { if (result != INVALID_PREVIEW_CHANNEL_ID) { mPreviewData.addPreviewChannelId(mPreviewChannelType, result); } - for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener - : mOnPreviewChannelCreationResultListeners) { + for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener : + mOnPreviewChannelCreationResultListeners) { onPreviewChannelCreationResultListener.onPreviewChannelCreationResult(result); } mCreatePreviewChannelTasks.remove(mPreviewChannelType); @@ -389,8 +368,8 @@ public class PreviewDataManager { } /** - * Updates the whole data which belongs to the package in preview programs table for a - * specific preview channel with a set of {@link PreviewProgramContent}. + * Updates the whole data which belongs to the package in preview programs table for a specific + * preview channel with a set of {@link PreviewProgramContent}. */ private final class UpdatePreviewProgramTask extends AsyncTask { private long mPreviewChannelId; @@ -398,15 +377,15 @@ public class PreviewDataManager { private Map mCurrentProgramId2PreviewProgramId; private Set mPreviewDataListeners = new CopyOnWriteArraySet<>(); - public UpdatePreviewProgramTask(long previewChannelId, - Set programs) { + public UpdatePreviewProgramTask( + long previewChannelId, Set programs) { mPreviewChannelId = previewChannelId; mPrograms = programs; if (mPreviewData.getPreviewProgramIds(previewChannelId) == null) { mCurrentProgramId2PreviewProgramId = new HashMap<>(); } else { - mCurrentProgramId2PreviewProgramId = new HashMap<>( - mPreviewData.getPreviewProgramIds(previewChannelId)); + mCurrentProgramId2PreviewProgramId = + new HashMap<>(mPreviewData.getPreviewProgramIds(previewChannelId)); } } @@ -440,14 +419,22 @@ public class PreviewDataManager { } Long existingPreviewProgramId = uncheckedPrograms.remove(program.getId()); if (existingPreviewProgramId != null) { - if (DEBUG) Log.d(TAG, "Preview program " + existingPreviewProgramId + " " + - "already exists for program " + program.getId()); + if (DEBUG) + Log.d( + TAG, + "Preview program " + + existingPreviewProgramId + + " " + + "already exists for program " + + program.getId()); continue; } try { - Uri programUri = mContentResolver.insert(TvContract.PreviewPrograms.CONTENT_URI, - PreviewDataUtils.createPreviewProgramFromContent(program) - .toContentValues()); + Uri programUri = + mContentResolver.insert( + TvContract.PreviewPrograms.CONTENT_URI, + PreviewDataUtils.createPreviewProgramFromContent(program) + .toContentValues()); if (programUri != null) { long previewProgramId = ContentUris.parseId(programUri); mCurrentProgramId2PreviewProgramId.put(program.getId(), previewProgramId); @@ -466,8 +453,10 @@ public class PreviewDataManager { } try { if (DEBUG) Log.d(TAG, "Remove preview program " + uncheckedPrograms.get(key)); - mContentResolver.delete(TvContract.buildPreviewProgramUri( - uncheckedPrograms.get(key)), null, null); + mContentResolver.delete( + TvContract.buildPreviewProgramUri(uncheckedPrograms.get(key)), + null, + null); mCurrentProgramId2PreviewProgramId.remove(key); } catch (Exception e) { Log.e(TAG, "Fail to remove preview program " + uncheckedPrograms.get(key)); @@ -493,9 +482,7 @@ public class PreviewDataManager { } } - /** - * Class to store the query result of preview data. - */ + /** Class to store the query result of preview data. */ private static final class PreviewData { private Map mPreviewChannelType2Id = new HashMap<>(); private Map> mProgramId2PreviewProgramId = new HashMap<>(); @@ -565,13 +552,9 @@ public class PreviewDataManager { } } - /** - * A utils class for preview data. - */ - public final static class PreviewDataUtils { - /** - * Creates a preview channel. - */ + /** A utils class for preview data. */ + public static final class PreviewDataUtils { + /** Creates a preview channel. */ public static android.support.media.tv.Channel createPreviewChannel( Context context, @PreviewChannelType long previewChannelType) { if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) { @@ -590,7 +573,7 @@ public class PreviewDataManager { context.getApplicationInfo().loadDescription(context.getPackageManager()); builder.setType(TvContract.Channels.TYPE_PREVIEW) .setDisplayName(appLabel == null ? null : appLabel.toString()) - .setDescription(appDescription == null ? null : appDescription.toString()) + .setDescription(appDescription == null ? null : appDescription.toString()) .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI) .setInternalProviderFlag1(previewChannelType); return builder.build(); @@ -601,16 +584,15 @@ public class PreviewDataManager { android.support.media.tv.Channel.Builder builder = new android.support.media.tv.Channel.Builder(); builder.setType(TvContract.Channels.TYPE_PREVIEW) - .setDisplayName(context.getResources().getString( - R.string.recorded_programs_preview_channel)) + .setDisplayName( + context.getResources() + .getString(R.string.recorded_programs_preview_channel)) .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI) .setInternalProviderFlag1(previewChannelType); return builder.build(); } - /** - * Creates a preview program. - */ + /** Creates a preview program. */ public static PreviewProgram createPreviewProgramFromContent( PreviewProgramContent program) { PreviewProgram.Builder builder = new PreviewProgram.Builder(); @@ -626,9 +608,7 @@ public class PreviewDataManager { return builder.build(); } - /** - * Appends query parameters to a Uri. - */ + /** Appends query parameters to a Uri. */ public static Uri addQueryParamToUri(Uri uri, Pair param) { return uri.buildUpon().appendQueryParameter(param.first, param.second).build(); } diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java index 39f5051d..845ca9d4 100644 --- a/src/com/android/tv/data/PreviewProgramContent.java +++ b/src/com/android/tv/data/PreviewProgramContent.java @@ -21,17 +21,13 @@ import android.media.tv.TvContract; import android.net.Uri; import android.text.TextUtils; import android.util.Pair; - import com.android.tv.TvApplication; import com.android.tv.dvr.data.RecordedProgram; - import java.util.Objects; -/** - * A class to store the content of preview programs. - */ +/** A class to store the content of preview programs. */ public class PreviewProgramContent { - private final static String PARAM_INPUT = "input"; + private static final String PARAM_INPUT = "input"; private long mId; private long mPreviewChannelId; @@ -43,13 +39,13 @@ public class PreviewProgramContent { private Uri mIntentUri; private Uri mPreviewVideoUri; - /** - * Create preview program content from {@link Program} - */ - public static PreviewProgramContent createFromProgram(Context context, - long previewChannelId, Program program) { - Channel channel = TvApplication.getSingletons(context).getChannelDataManager() - .getChannel(program.getChannelId()); + /** Create preview program content from {@link Program} */ + public static PreviewProgramContent createFromProgram( + Context context, long previewChannelId, Program program) { + Channel channel = + TvApplication.getSingletons(context) + .getChannelDataManager() + .getChannel(program.getChannelId()); if (channel == null) { return null; } @@ -60,22 +56,25 @@ public class PreviewProgramContent { .setType(TvContract.PreviewPrograms.TYPE_CHANNEL) .setLive(true) .setTitle(program.getTitle()) - .setDescription(!TextUtils.isEmpty(channelDisplayName) - ? channelDisplayName : channel.getDisplayNumber()) + .setDescription( + !TextUtils.isEmpty(channelDisplayName) + ? channelDisplayName + : channel.getDisplayNumber()) .setPosterArtUri(Uri.parse(program.getPosterArtUri())) .setIntentUri(channel.getUri()) - .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri( - channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId()))) + .setPreviewVideoUri( + PreviewDataManager.PreviewDataUtils.addQueryParamToUri( + channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId()))) .build(); } - /** - * Create preview program content from {@link RecordedProgram} - */ + /** Create preview program content from {@link RecordedProgram} */ public static PreviewProgramContent createFromRecordedProgram( Context context, long previewChannelId, RecordedProgram recordedProgram) { - Channel channel = TvApplication.getSingletons(context).getChannelDataManager() - .getChannel(recordedProgram.getChannelId()); + Channel channel = + TvApplication.getSingletons(context) + .getChannelDataManager() + .getChannel(recordedProgram.getChannelId()); String channelDisplayName = null; if (channel != null) { channelDisplayName = channel.getDisplayName(); @@ -89,12 +88,14 @@ public class PreviewProgramContent { .setDescription(channelDisplayName != null ? channelDisplayName : "") .setPosterArtUri(Uri.parse(recordedProgram.getPosterArtUri())) .setIntentUri(recordedProgramUri) - .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri( - recordedProgramUri, new Pair<>(PARAM_INPUT, recordedProgram.getInputId()))) + .setPreviewVideoUri( + PreviewDataManager.PreviewDataUtils.addQueryParamToUri( + recordedProgramUri, + new Pair<>(PARAM_INPUT, recordedProgram.getInputId()))) .build(); } - private PreviewProgramContent() { } + private PreviewProgramContent() {} public void copyFrom(PreviewProgramContent other) { if (this == other) { @@ -119,58 +120,42 @@ public class PreviewProgramContent { return mId; } - /** - * Returns the preview channel id which the preview program belongs to. - */ + /** Returns the preview channel id which the preview program belongs to. */ public long getPreviewChannelId() { return mPreviewChannelId; } - /** - * Returns the type of the preview program. - */ + /** Returns the type of the preview program. */ public int getType() { return mType; } - /** - * Returns whether the preview program is live or not. - */ + /** Returns whether the preview program is live or not. */ public boolean getLive() { return mLive; } - /** - * Returns the title of the preview program. - */ + /** Returns the title of the preview program. */ public String getTitle() { return mTitle; } - /** - * Returns the description of the preview program. - */ + /** Returns the description of the preview program. */ public String getDescription() { return mDescription; } - /** - * Returns the poster art uri of the preview program. - */ + /** Returns the poster art uri of the preview program. */ public Uri getPosterArtUri() { return mPosterArtUri; } - /** - * Returns the intent uri of the preview program. - */ + /** Returns the intent uri of the preview program. */ public Uri getIntentUri() { return mIntentUri; } - /** - * Returns the preview video uri of the preview program. - */ + /** Returns the preview video uri of the preview program. */ public Uri getPreviewVideoUri() { return mPreviewVideoUri; } @@ -194,8 +179,16 @@ public class PreviewProgramContent { @Override public int hashCode() { - return Objects.hash(mId, mPreviewChannelId, mType, mLive, mTitle, mDescription, - mPosterArtUri, mIntentUri, mPreviewVideoUri); + return Objects.hash( + mId, + mPreviewChannelId, + mType, + mLive, + mTitle, + mDescription, + mPosterArtUri, + mIntentUri, + mPreviewVideoUri); } public static final class Builder { diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java index 071c7024..f47a3a06 100644 --- a/src/com/android/tv/data/Program.java +++ b/src/com/android/tv/data/Program.java @@ -32,59 +32,54 @@ import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.BuildConfig; import com.android.tv.common.CollectionUtils; import com.android.tv.common.TvContentRatingCache; import com.android.tv.util.ImageLoader; import com.android.tv.util.Utils; - import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; -/** - * A convenience class to create and insert program information entries into the database. - */ +/** A convenience class to create and insert program information entries into the database. */ public final class Program extends BaseProgram implements Comparable, Parcelable { private static final boolean DEBUG = false; private static final boolean DEBUG_DUMP_DESCRIPTION = false; private static final String TAG = "Program"; private static final String[] PROJECTION_BASE = { - // Columns must match what is read in Program.fromCursor() - TvContract.Programs._ID, - TvContract.Programs.COLUMN_PACKAGE_NAME, - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_EPISODE_TITLE, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_LONG_DESCRIPTION, - TvContract.Programs.COLUMN_POSTER_ART_URI, - TvContract.Programs.COLUMN_THUMBNAIL_URI, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_VIDEO_WIDTH, - TvContract.Programs.COLUMN_VIDEO_HEIGHT, - TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA + // Columns must match what is read in Program.fromCursor() + TvContract.Programs._ID, + TvContract.Programs.COLUMN_PACKAGE_NAME, + TvContract.Programs.COLUMN_CHANNEL_ID, + TvContract.Programs.COLUMN_TITLE, + TvContract.Programs.COLUMN_EPISODE_TITLE, + TvContract.Programs.COLUMN_SHORT_DESCRIPTION, + TvContract.Programs.COLUMN_LONG_DESCRIPTION, + TvContract.Programs.COLUMN_POSTER_ART_URI, + TvContract.Programs.COLUMN_THUMBNAIL_URI, + TvContract.Programs.COLUMN_CANONICAL_GENRE, + TvContract.Programs.COLUMN_CONTENT_RATING, + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_VIDEO_WIDTH, + TvContract.Programs.COLUMN_VIDEO_HEIGHT, + TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA }; // Columns which is deprecated in NYC @SuppressWarnings("deprecation") private static final String[] PROJECTION_DEPRECATED_IN_NYC = { - TvContract.Programs.COLUMN_SEASON_NUMBER, - TvContract.Programs.COLUMN_EPISODE_NUMBER + TvContract.Programs.COLUMN_SEASON_NUMBER, TvContract.Programs.COLUMN_EPISODE_NUMBER }; private static final String[] PROJECTION_ADDED_IN_NYC = { - TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_SEASON_TITLE, - TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_RECORDING_PROHIBITED + TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, + TvContract.Programs.COLUMN_SEASON_TITLE, + TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + TvContract.Programs.COLUMN_RECORDING_PROHIBITED }; public static final String[] PROJECTION = createProjection(); @@ -97,9 +92,7 @@ public final class Program extends BaseProgram implements Comparable, P : PROJECTION_DEPRECATED_IN_NYC); } - /** - * Returns the column index for {@code column}, -1 if the column doesn't exist. - */ + /** Returns the column index for {@code column}, -1 if the column doesn't exist. */ public static int getColumnIndex(String column) { for (int i = 0; i < PROJECTION.length; ++i) { if (PROJECTION[i].equals(column)) { @@ -183,17 +176,18 @@ public final class Program extends BaseProgram implements Comparable, P return program; } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public Program createFromParcel(Parcel in) { - return Program.fromParcel(in); - } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Program createFromParcel(Parcel in) { + return Program.fromParcel(in); + } - @Override - public Program[] newArray(int size) { - return new Program[size]; - } - }; + @Override + public Program[] newArray(int size) { + return new Program[size]; + } + }; private long mId; private String mPackageName; @@ -225,9 +219,7 @@ public final class Program extends BaseProgram implements Comparable, P return mId; } - /** - * Returns the package name of this program. - */ + /** Returns the package name of this program. */ public String getPackageName() { return mPackageName; } @@ -236,17 +228,13 @@ public final class Program extends BaseProgram implements Comparable, P return mChannelId; } - /** - * Returns {@code true} if this program is valid or {@code false} otherwise. - */ + /** Returns {@code true} if this program is valid or {@code false} otherwise. */ @Override public boolean isValid() { return mChannelId >= 0; } - /** - * Returns {@code true} if the program is valid and {@code false} otherwise. - */ + /** Returns {@code true} if the program is valid and {@code false} otherwise. */ public static boolean isValid(Program program) { return program != null && program.isValid(); } @@ -256,17 +244,13 @@ public final class Program extends BaseProgram implements Comparable, P return mTitle; } - /** - * Returns the series ID. - */ + /** Returns the series ID. */ @Override public String getSeriesId() { return mSeriesId; } - /** - * Returns the episode title. - */ + /** Returns the episode title. */ @Override public String getEpisodeTitle() { return mEpisodeTitle; @@ -292,9 +276,7 @@ public final class Program extends BaseProgram implements Comparable, P return mEndTimeUtcMillis; } - /** - * Returns the program duration. - */ + /** Returns the program duration. */ @Override public long getDurationMillis() { return mEndTimeUtcMillis - mStartTimeUtcMillis; @@ -318,9 +300,7 @@ public final class Program extends BaseProgram implements Comparable, P return mVideoHeight; } - /** - * Returns the list of Critic Scores for this program - */ + /** Returns the list of Critic Scores for this program */ @Nullable public List getCriticScores() { return mCriticScores; @@ -342,17 +322,12 @@ public final class Program extends BaseProgram implements Comparable, P return mThumbnailUri; } - /** - * Returns {@code true} if the recording of this program is prohibited. - */ + /** Returns {@code true} if the recording of this program is prohibited. */ public boolean isRecordingProhibited() { return mRecordingProhibited; } - /** - * Returns array of canonical genres for this program. - * This is expected to be called rarely. - */ + /** Returns array of canonical genres for this program. This is expected to be called rarely. */ @Nullable public String[] getCanonicalGenres() { if (mCanonicalGenreIds == null) { @@ -365,17 +340,13 @@ public final class Program extends BaseProgram implements Comparable, P return genres; } - /** - * Returns array of canonical genre ID's for this program. - */ + /** Returns array of canonical genre ID's for this program. */ @Override public int[] getCanonicalGenreIds() { return mCanonicalGenreIds; } - /** - * Returns if this program has the genre. - */ + /** Returns if this program has the genre. */ public boolean hasGenre(int genreId) { if (genreId == GenreItems.ID_ALL_CHANNELS) { return true; @@ -393,10 +364,24 @@ public final class Program extends BaseProgram implements Comparable, P @Override public int hashCode() { // Hash with all the properties because program ID can be invalid for the dummy programs. - return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis, - mTitle, mSeriesId, mEpisodeTitle, mDescription, mLongDescription, mVideoWidth, - mVideoHeight, mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings), - Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mSeasonTitle, mEpisodeNumber, + return Objects.hash( + mChannelId, + mStartTimeUtcMillis, + mEndTimeUtcMillis, + mTitle, + mSeriesId, + mEpisodeTitle, + mDescription, + mLongDescription, + mVideoWidth, + mVideoHeight, + mPosterArtUri, + mThumbnailUri, + Arrays.hashCode(mContentRatings), + Arrays.hashCode(mCanonicalGenreIds), + mSeasonNumber, + mSeasonTitle, + mEpisodeNumber, mRecordingProhibited); } @@ -436,28 +421,47 @@ public final class Program extends BaseProgram implements Comparable, P @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("Program[").append(mId) - .append("]{channelId=").append(mChannelId) - .append(", packageName=").append(mPackageName) - .append(", title=").append(mTitle) - .append(", seriesId=").append(mSeriesId) - .append(", episodeTitle=").append(mEpisodeTitle) - .append(", seasonNumber=").append(mSeasonNumber) - .append(", seasonTitle=").append(mSeasonTitle) - .append(", episodeNumber=").append(mEpisodeNumber) - .append(", startTimeUtcSec=").append(Utils.toTimeString(mStartTimeUtcMillis)) - .append(", endTimeUtcSec=").append(Utils.toTimeString(mEndTimeUtcMillis)) - .append(", videoWidth=").append(mVideoWidth) - .append(", videoHeight=").append(mVideoHeight) + builder.append("Program[") + .append(mId) + .append("]{channelId=") + .append(mChannelId) + .append(", packageName=") + .append(mPackageName) + .append(", title=") + .append(mTitle) + .append(", seriesId=") + .append(mSeriesId) + .append(", episodeTitle=") + .append(mEpisodeTitle) + .append(", seasonNumber=") + .append(mSeasonNumber) + .append(", seasonTitle=") + .append(mSeasonTitle) + .append(", episodeNumber=") + .append(mEpisodeNumber) + .append(", startTimeUtcSec=") + .append(Utils.toTimeString(mStartTimeUtcMillis)) + .append(", endTimeUtcSec=") + .append(Utils.toTimeString(mEndTimeUtcMillis)) + .append(", videoWidth=") + .append(mVideoWidth) + .append(", videoHeight=") + .append(mVideoHeight) .append(", contentRatings=") .append(TvContentRatingCache.contentRatingsToString(mContentRatings)) - .append(", posterArtUri=").append(mPosterArtUri) - .append(", thumbnailUri=").append(mThumbnailUri) - .append(", canonicalGenres=").append(Arrays.toString(mCanonicalGenreIds)) - .append(", recordingProhibited=").append(mRecordingProhibited); + .append(", posterArtUri=") + .append(mPosterArtUri) + .append(", thumbnailUri=") + .append(mThumbnailUri) + .append(", canonicalGenres=") + .append(Arrays.toString(mCanonicalGenreIds)) + .append(", recordingProhibited=") + .append(mRecordingProhibited); if (DEBUG_DUMP_DESCRIPTION) { - builder.append(", description=").append(mDescription) - .append(", longDescription=").append(mLongDescription); + builder.append(", description=") + .append(mDescription) + .append(", longDescription=") + .append(mLongDescription); } return builder.append("}").toString(); } @@ -474,9 +478,13 @@ public final class Program extends BaseProgram implements Comparable, P putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle()); putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, + putValue( + values, + TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, program.getSeasonNumber()); - putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + putValue( + values, + TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, program.getEpisodeNumber()); } else { putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber()); @@ -488,17 +496,23 @@ public final class Program extends BaseProgram implements Comparable, P putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri()); String[] canonicalGenres = program.getCanonicalGenres(); if (canonicalGenres != null && canonicalGenres.length > 0) { - putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, + putValue( + values, + TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(canonicalGenres)); } else { putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, ""); } - putValue(values, Programs.COLUMN_CONTENT_RATING, + putValue( + values, + Programs.COLUMN_CONTENT_RATING, TvContentRatingCache.contentRatingsToString(program.getContentRatings())); - values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - program.getStartTimeUtcMillis()); + values.put( + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, program.getStartTimeUtcMillis()); values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis()); - putValue(values, TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, + putValue( + values, + TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, InternalDataUtils.serializeInternalProviderData(program)); return values; } @@ -547,15 +561,11 @@ public final class Program extends BaseProgram implements Comparable, P mRecordingProhibited = other.mRecordingProhibited; } - /** - * A Builder for the Program class - */ + /** A Builder for the Program class */ public static final class Builder { private final Program mProgram; - /** - * Creates a Builder for this Program class - */ + /** Creates a Builder for this Program class */ public Builder() { mProgram = new Program(); // Fill initial data. @@ -574,8 +584,9 @@ public final class Program extends BaseProgram implements Comparable, P } /** - * Creates a builder for this Program class - * by setting default values equivalent to another Program + * Creates a builder for this Program class by setting default values equivalent to another + * Program + * * @param other the program to be copied */ @VisibleForTesting @@ -586,6 +597,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the ID of this program + * * @param id the ID * @return a reference to this object */ @@ -596,16 +608,18 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the package name for this program + * * @param packageName the package name * @return a reference to this object */ - public Builder setPackageName(String packageName){ + public Builder setPackageName(String packageName) { mProgram.mPackageName = packageName; return this; } /** * Sets the channel ID for this program + * * @param channelId the channel ID * @return a reference to this object */ @@ -616,6 +630,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the program title + * * @param title the title * @return a reference to this object */ @@ -626,6 +641,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the series ID. + * * @param seriesId the series ID * @return a reference to this object */ @@ -636,6 +652,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the episode title if this is a series program + * * @param episodeTitle the episode title * @return a reference to this object */ @@ -646,6 +663,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the season number if this is a series program + * * @param seasonNumber the season number * @return a reference to this object */ @@ -654,9 +672,9 @@ public final class Program extends BaseProgram implements Comparable, P return this; } - /** * Sets the season title if this is a series program + * * @param seasonTitle the season title * @return a reference to this object */ @@ -667,6 +685,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the episode number if this is a series program + * * @param episodeNumber the episode number * @return a reference to this object */ @@ -677,6 +696,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the start time of this program + * * @param startTimeUtcMillis the start time in UTC milliseconds * @return a reference to this object */ @@ -687,6 +707,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the end time of this program + * * @param endTimeUtcMillis the end time in UTC milliseconds * @return a reference to this object */ @@ -697,6 +718,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets a description + * * @param description the description * @return a reference to this object */ @@ -707,6 +729,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets a long description + * * @param longDescription the long description * @return a reference to this object */ @@ -717,6 +740,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Defines the video width of this program + * * @param width * @return a reference to this object */ @@ -727,6 +751,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Defines the video height of this program + * * @param height * @return a reference to this object */ @@ -737,6 +762,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the content ratings for this program + * * @param contentRatings the content ratings * @return a reference to this object */ @@ -747,6 +773,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the poster art URI + * * @param posterArtUri the poster art URI * @return a reference to this object */ @@ -757,6 +784,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the thumbnail URI + * * @param thumbnailUri the thumbnail URI * @return a reference to this object */ @@ -767,6 +795,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the canonical genres by id + * * @param genres the genres * @return a reference to this object */ @@ -777,6 +806,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the recording prohibited flag + * * @param recordingProhibited recording prohibited flag * @return a reference to this object */ @@ -787,6 +817,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Adds a critic score + * * @param criticScore the critic score * @return a reference to this object */ @@ -802,6 +833,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Sets the critic scores + * * @param criticScores the critic scores * @return a reference to this objects */ @@ -812,6 +844,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Returns a reference to the Program object being constructed + * * @return the Program object constructed */ public Program build() { @@ -831,7 +864,9 @@ public final class Program extends BaseProgram implements Comparable, P } /** - * Prefetches the program poster art.

+ * Prefetches the program poster art. + * + *

*/ public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) { if (mPosterArtUri == null) { @@ -842,13 +877,17 @@ public final class Program extends BaseProgram implements Comparable, P /** * Loads the program poster art and returns it via {@code callback}. - *

- * Note that it may directly call {@code callback} if the program poster art already is loaded. + * + *

Note that it may directly call {@code callback} if the program poster art already is + * loaded. * * @return {@code true} if the load is complete and the callback is executed. */ @UiThread - public boolean loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, + public boolean loadPosterArt( + Context context, + int posterArtWidth, + int posterArtHeight, ImageLoader.ImageLoaderCallback callback) { if (mPosterArtUri == null) { return false; @@ -861,12 +900,18 @@ public final class Program extends BaseProgram implements Comparable, P if (p1 == null || p2 == null) { return false; } - boolean isDuplicate = p1.getChannelId() == p2.getChannelId() - && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis() - && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis(); + boolean isDuplicate = + p1.getChannelId() == p2.getChannelId() + && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis() + && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis(); if (DEBUG && BuildConfig.ENG && isDuplicate) { - Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \"" - + p2.getTitle() + "\""); + Log.w( + TAG, + "Duplicate programs detected! - \"" + + p1.getTitle() + + "\" and \"" + + p2.getTitle() + + "\""); } return isDuplicate; } @@ -906,21 +951,13 @@ public final class Program extends BaseProgram implements Comparable, P out.writeByte((byte) (mRecordingProhibited ? 1 : 0)); } - /** - * Holds one type of critic score and its source. - */ + /** Holds one type of critic score and its source. */ public static final class CriticScore implements Serializable, Parcelable { - /** - * The source of the rating. - */ + /** The source of the rating. */ public final String source; - /** - * The score. - */ + /** The score. */ public final String score; - /** - * The url of the logo image - */ + /** The url of the logo image */ public final String logoUrl; public static final Parcelable.Creator CREATOR = @@ -929,7 +966,7 @@ public final class Program extends BaseProgram implements Comparable, P public CriticScore createFromParcel(Parcel in) { String source = in.readString(); String score = in.readString(); - String logoUri = in.readString(); + String logoUri = in.readString(); return new CriticScore(source, score, logoUri); } @@ -941,6 +978,7 @@ public final class Program extends BaseProgram implements Comparable, P /** * Constructor for this class. + * * @param source the source of the rating * @param score the score */ diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java index 8cb5e74a..639ac99a 100644 --- a/src/com/android/tv/data/ProgramDataManager.java +++ b/src/com/android/tv/data/ProgramDataManager.java @@ -33,14 +33,12 @@ import android.util.ArraySet; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; - import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.Clock; import com.android.tv.util.MultiLongSparseArray; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -65,18 +63,20 @@ public class ProgramDataManager implements MemoryManageable { private static final long PROGRAM_PREFETCH_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5); // TODO: need to optimize consecutive DB updates. private static final long CURRENT_PROGRAM_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5); - @VisibleForTesting - static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30); - @VisibleForTesting - static final long PROGRAM_GUIDE_MAX_TIME_RANGE = TimeUnit.DAYS.toMillis(2); + @VisibleForTesting static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30); + @VisibleForTesting static final long PROGRAM_GUIDE_MAX_TIME_RANGE = TimeUnit.DAYS.toMillis(2); // TODO: Use TvContract constants, once they become public. private static final String PARAM_START_TIME = "start_time"; private static final String PARAM_END_TIME = "end_time"; // COLUMN_CHANNEL_ID, COLUMN_END_TIME_UTC_MILLIS are added to detect duplicated programs. // Duplicated programs are always consecutive by the sorting order. - private static final String SORT_BY_TIME = Programs.COLUMN_START_TIME_UTC_MILLIS + ", " - + Programs.COLUMN_CHANNEL_ID + ", " + Programs.COLUMN_END_TIME_UTC_MILLIS; + private static final String SORT_BY_TIME = + Programs.COLUMN_START_TIME_UTC_MILLIS + + ", " + + Programs.COLUMN_CHANNEL_ID + + ", " + + Programs.COLUMN_END_TIME_UTC_MILLIS; private static final int MSG_UPDATE_CURRENT_PROGRAMS = 1000; private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001; @@ -122,24 +122,27 @@ public class ProgramDataManager implements MemoryManageable { mClock = time; mContentResolver = contentResolver; mHandler = new MyHandler(looper); - mProgramObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) { - mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS); - } - if (isProgramUpdatePaused()) { - return; - } - if (mPrefetchEnabled) { - // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be quite long - // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message - // and send MSG_UPDATE_PREFETCH_PROGRAM again. - mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM); - mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM); - } - } - }; + mProgramObserver = + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) { + mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS); + } + if (isProgramUpdatePaused()) { + return; + } + if (mPrefetchEnabled) { + // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be + // quite long + // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing + // message + // and send MSG_UPDATE_PREFETCH_PROGRAM again. + mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM); + mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM); + } + } + }; mProgramPrefetchUpdateWaitMs = PROGRAM_PREFETCH_UPDATE_WAIT_MS; } @@ -149,18 +152,16 @@ public class ProgramDataManager implements MemoryManageable { } /** - * Set the program prefetch update wait which gives the delay to query all programs from DB - * to prevent from too frequent DB queries. - * Default value is {@link #PROGRAM_PREFETCH_UPDATE_WAIT_MS} + * Set the program prefetch update wait which gives the delay to query all programs from DB to + * prevent from too frequent DB queries. Default value is {@link + * #PROGRAM_PREFETCH_UPDATE_WAIT_MS} */ @VisibleForTesting void setProgramPrefetchUpdateWait(long programPrefetchUpdateWaitMs) { mProgramPrefetchUpdateWaitMs = programPrefetchUpdateWaitMs; } - /** - * Starts the manager. - */ + /** Starts the manager. */ public void start() { if (mStarted) { return; @@ -172,8 +173,7 @@ public class ProgramDataManager implements MemoryManageable { if (mPrefetchEnabled) { mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM); } - mContentResolver.registerContentObserver(Programs.CONTENT_URI, - true, mProgramObserver); + mContentResolver.registerContentObserver(Programs.CONTENT_URI, true, mProgramObserver); } /** @@ -214,9 +214,7 @@ public class ProgramDataManager implements MemoryManageable { return new ArrayList<>(mChannelIdCurrentProgramMap.values()); } - /** - * Reloads program data. - */ + /** Reloads program data. */ public void reload() { if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) { mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS); @@ -226,35 +224,27 @@ public class ProgramDataManager implements MemoryManageable { } } - /** - * A listener interface to receive notification on program data retrieval from DB. - */ + /** A listener interface to receive notification on program data retrieval from DB. */ public interface Listener { /** - * Called when a Program data is now available through getProgram() - * after the DB operation is done which wasn't before. - * This would be called only if fetched data is around the selected program. - **/ + * Called when a Program data is now available through getProgram() after the DB operation + * is done which wasn't before. This would be called only if fetched data is around the + * selected program. + */ void onProgramUpdated(); } - /** - * Adds the {@link Listener}. - */ + /** Adds the {@link Listener}. */ public void addListener(Listener listener) { mListeners.add(listener); } - /** - * Removes the {@link Listener}. - */ + /** Removes the {@link Listener}. */ public void removeListener(Listener listener) { mListeners.remove(listener); } - /** - * Enables or Disables program prefetch. - */ + /** Enables or Disables program prefetch. */ public void setPrefetchEnabled(boolean enable) { if (mPrefetchEnabled == enable) { return; @@ -276,10 +266,10 @@ public class ProgramDataManager implements MemoryManageable { /** * Returns the programs for the given channel which ends after the given start time. * - *

Prefetch should be enabled to call it. + *

Prefetch should be enabled to call it. * * @return {@link List} with Programs. It may includes dummy program if the entry needs DB - * operations to get. + * operations to get. */ public List getPrograms(long channelId, long startTime) { SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled."); @@ -321,38 +311,38 @@ public class ProgramDataManager implements MemoryManageable { * Adds the listener to be notified if current program is updated for a channel. * * @param channelId A channel ID to get notified. If it's {@link Channel#INVALID_ID}, the - * listener would be called whenever a current program is updated. + * listener would be called whenever a current program is updated. */ public void addOnCurrentProgramUpdatedListener( long channelId, OnCurrentProgramUpdatedListener listener) { - mChannelId2ProgramUpdatedListeners - .put(channelId, listener); + mChannelId2ProgramUpdatedListeners.put(channelId, listener); } /** - * Removes the listener previously added by - * {@link #addOnCurrentProgramUpdatedListener(long, OnCurrentProgramUpdatedListener)}. + * Removes the listener previously added by {@link #addOnCurrentProgramUpdatedListener(long, + * OnCurrentProgramUpdatedListener)}. */ public void removeOnCurrentProgramUpdatedListener( long channelId, OnCurrentProgramUpdatedListener listener) { - mChannelId2ProgramUpdatedListeners - .remove(channelId, listener); + mChannelId2ProgramUpdatedListeners.remove(channelId, listener); } private void notifyCurrentProgramUpdate(long channelId, Program program) { - for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners - .get(channelId)) { + for (OnCurrentProgramUpdatedListener listener : + mChannelId2ProgramUpdatedListeners.get(channelId)) { listener.onCurrentProgramUpdated(channelId, program); } - for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners - .get(Channel.INVALID_ID)) { + for (OnCurrentProgramUpdatedListener listener : + mChannelId2ProgramUpdatedListeners.get(Channel.INVALID_ID)) { listener.onCurrentProgramUpdated(channelId, program); } } private void updateCurrentProgram(long channelId, Program program) { - Program previousProgram = program == null ? mChannelIdCurrentProgramMap.remove(channelId) - : mChannelIdCurrentProgramMap.put(channelId, program); + Program previousProgram = + program == null + ? mChannelIdCurrentProgramMap.remove(channelId) + : mChannelIdCurrentProgramMap.put(channelId, program); if (!Objects.equals(program, previousProgram)) { if (mPrefetchEnabled) { removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program); @@ -362,14 +352,17 @@ public class ProgramDataManager implements MemoryManageable { long delayedTime; if (program == null) { - delayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS - + (long) (Math.random() * (PERIODIC_PROGRAM_UPDATE_MAX_MS - - PERIODIC_PROGRAM_UPDATE_MIN_MS)); + delayedTime = + PERIODIC_PROGRAM_UPDATE_MIN_MS + + (long) + (Math.random() + * (PERIODIC_PROGRAM_UPDATE_MAX_MS + - PERIODIC_PROGRAM_UPDATE_MIN_MS)); } else { delayedTime = program.getEndTimeUtcMillis() - mClock.currentTimeMillis(); } - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_UPDATE_ONE_CURRENT_PROGRAM, channelId), delayedTime); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_UPDATE_ONE_CURRENT_PROGRAM, channelId), delayedTime); } private void removePreviousProgramsAndUpdateCurrentProgramInCache( @@ -391,27 +384,29 @@ public class ProgramDataManager implements MemoryManageable { continue; } - if (cachedProgram.getEndTimeUtcMillis() <= currentProgram - .getStartTimeUtcMillis()) { + if (cachedProgram.getEndTimeUtcMillis() <= currentProgram.getStartTimeUtcMillis()) { // Keep the programs that ends earlier than current program // but later than mPrefetchTimeRangeStartMs. continue; } // Update dummy program around current program if any. - if (cachedProgram.getStartTimeUtcMillis() < currentProgram - .getStartTimeUtcMillis()) { + if (cachedProgram.getStartTimeUtcMillis() < currentProgram.getStartTimeUtcMillis()) { // The dummy program starts earlier than the current program. Adjust its end time. - i.set(createDummyProgram(cachedProgram.getStartTimeUtcMillis(), - currentProgram.getStartTimeUtcMillis())); + i.set( + createDummyProgram( + cachedProgram.getStartTimeUtcMillis(), + currentProgram.getStartTimeUtcMillis())); i.add(currentProgram); } else { i.set(currentProgram); } if (currentProgram.getEndTimeUtcMillis() < cachedProgram.getEndTimeUtcMillis()) { // The dummy program ends later than the current program. Adjust its start time. - i.add(createDummyProgram(currentProgram.getEndTimeUtcMillis(), - cachedProgram.getEndTimeUtcMillis())); + i.add( + createDummyProgram( + currentProgram.getEndTimeUtcMillis(), + cachedProgram.getEndTimeUtcMillis())); } break; } @@ -425,8 +420,8 @@ public class ProgramDataManager implements MemoryManageable { private void handleUpdateCurrentPrograms() { if (mProgramsUpdateTask != null) { - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_CURRENT_PROGRAMS, - CURRENT_PROGRAM_UPDATE_WAIT_MS); + mHandler.sendEmptyMessageDelayed( + MSG_UPDATE_CURRENT_PROGRAMS, CURRENT_PROGRAM_UPDATE_WAIT_MS); return; } clearTask(mProgramUpdateTaskMap); @@ -444,8 +439,8 @@ public class ProgramDataManager implements MemoryManageable { public ProgramsPrefetchTask() { long time = mClock.currentTimeMillis(); - mStartTimeMs = Utils - .floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS); + mStartTimeMs = + Utils.floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS); mEndTimeMs = mStartTimeMs + PROGRAM_GUIDE_MAX_TIME_RANGE; mSuccess = false; } @@ -454,12 +449,19 @@ public class ProgramDataManager implements MemoryManageable { protected Map> doInBackground(Void... params) { Map> programMap = new HashMap<>(); if (DEBUG) { - Log.d(TAG, "Starts programs prefetch. " + Utils.toTimeString(mStartTimeMs) + "-" - + Utils.toTimeString(mEndTimeMs)); + Log.d( + TAG, + "Starts programs prefetch. " + + Utils.toTimeString(mStartTimeMs) + + "-" + + Utils.toTimeString(mEndTimeMs)); } - Uri uri = Programs.CONTENT_URI.buildUpon() - .appendQueryParameter(PARAM_START_TIME, String.valueOf(mStartTimeMs)) - .appendQueryParameter(PARAM_END_TIME, String.valueOf(mEndTimeMs)).build(); + Uri uri = + Programs.CONTENT_URI + .buildUpon() + .appendQueryParameter(PARAM_START_TIME, String.valueOf(mStartTimeMs)) + .appendQueryParameter(PARAM_END_TIME, String.valueOf(mEndTimeMs)) + .build(); final int RETRY_COUNT = 3; Program lastReadProgram = null; for (int retryCount = RETRY_COUNT; retryCount > 0; retryCount--) { @@ -467,8 +469,8 @@ public class ProgramDataManager implements MemoryManageable { return null; } programMap.clear(); - try (Cursor c = mContentResolver.query(uri, Program.PROJECTION, null, null, - SORT_BY_TIME)) { + try (Cursor c = + mContentResolver.query(uri, Program.PROJECTION, null, null, SORT_BY_TIME)) { if (c == null) { continue; } @@ -527,14 +529,16 @@ public class ProgramDataManager implements MemoryManageable { long currentTime = mClock.currentTimeMillis(); mLastPrefetchTaskRunMs = currentTime; nextMessageDelayedTime = - Utils.floorTime(mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS, - PROGRAM_GUIDE_SNAP_TIME_MS) - currentTime; + Utils.floorTime( + mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS, + PROGRAM_GUIDE_SNAP_TIME_MS) + - currentTime; } else { nextMessageDelayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS; } if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) { - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM, - nextMessageDelayedTime); + mHandler.sendEmptyMessageDelayed( + MSG_UPDATE_PREFETCH_PROGRAM, nextMessageDelayedTime); } } } @@ -547,10 +551,17 @@ public class ProgramDataManager implements MemoryManageable { private class ProgramsUpdateTask extends AsyncDbTask.AsyncQueryTask> { public ProgramsUpdateTask(ContentResolver contentResolver, long time) { - super(contentResolver, Programs.CONTENT_URI.buildUpon() + super( + contentResolver, + Programs.CONTENT_URI + .buildUpon() .appendQueryParameter(PARAM_START_TIME, String.valueOf(time)) - .appendQueryParameter(PARAM_END_TIME, String.valueOf(time)).build(), - Program.PROJECTION, null, null, SORT_BY_TIME); + .appendQueryParameter(PARAM_END_TIME, String.valueOf(time)) + .build(), + Program.PROJECTION, + null, + null, + SORT_BY_TIME); } @Override @@ -604,10 +615,16 @@ public class ProgramDataManager implements MemoryManageable { private class UpdateCurrentProgramForChannelTask extends AsyncDbTask.AsyncQueryTask { private final long mChannelId; - private UpdateCurrentProgramForChannelTask(ContentResolver contentResolver, long channelId, - long time) { - super(contentResolver, TvContract.buildProgramsUriForChannel(channelId, time, time), - Program.PROJECTION, null, null, SORT_BY_TIME); + + private UpdateCurrentProgramForChannelTask( + ContentResolver contentResolver, long channelId, long time) { + super( + contentResolver, + TvContract.buildProgramsUriForChannel(channelId, time, time), + Program.PROJECTION, + null, + null, + SORT_BY_TIME); mChannelId = channelId; } @@ -638,48 +655,53 @@ public class ProgramDataManager implements MemoryManageable { case MSG_UPDATE_CURRENT_PROGRAMS: handleUpdateCurrentPrograms(); break; - case MSG_UPDATE_ONE_CURRENT_PROGRAM: { - long channelId = (Long) msg.obj; - UpdateCurrentProgramForChannelTask oldTask = mProgramUpdateTaskMap - .get(channelId); - if (oldTask != null) { - oldTask.cancel(true); - } - UpdateCurrentProgramForChannelTask - task = new UpdateCurrentProgramForChannelTask( - mContentResolver, channelId, mClock.currentTimeMillis()); - mProgramUpdateTaskMap.put(channelId, task); - task.executeOnDbThread(); - break; - } - case MSG_UPDATE_PREFETCH_PROGRAM: { - if (isProgramUpdatePaused()) { - return; - } - if (mProgramsPrefetchTask != null) { - mHandler.sendEmptyMessageDelayed(msg.what, mProgramPrefetchUpdateWaitMs); - return; + case MSG_UPDATE_ONE_CURRENT_PROGRAM: + { + long channelId = (Long) msg.obj; + UpdateCurrentProgramForChannelTask oldTask = + mProgramUpdateTaskMap.get(channelId); + if (oldTask != null) { + oldTask.cancel(true); + } + UpdateCurrentProgramForChannelTask task = + new UpdateCurrentProgramForChannelTask( + mContentResolver, channelId, mClock.currentTimeMillis()); + mProgramUpdateTaskMap.put(channelId, task); + task.executeOnDbThread(); + break; } - long delayMillis = mLastPrefetchTaskRunMs + mProgramPrefetchUpdateWaitMs - - mClock.currentTimeMillis(); - if (delayMillis > 0) { - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM, delayMillis); - } else { - mProgramsPrefetchTask = new ProgramsPrefetchTask(); - mProgramsPrefetchTask.executeOnDbThread(); + case MSG_UPDATE_PREFETCH_PROGRAM: + { + if (isProgramUpdatePaused()) { + return; + } + if (mProgramsPrefetchTask != null) { + mHandler.sendEmptyMessageDelayed( + msg.what, mProgramPrefetchUpdateWaitMs); + return; + } + long delayMillis = + mLastPrefetchTaskRunMs + + mProgramPrefetchUpdateWaitMs + - mClock.currentTimeMillis(); + if (delayMillis > 0) { + mHandler.sendEmptyMessageDelayed( + MSG_UPDATE_PREFETCH_PROGRAM, delayMillis); + } else { + mProgramsPrefetchTask = new ProgramsPrefetchTask(); + mProgramsPrefetchTask.executeOnDbThread(); + } + break; } - break; - } } } } /** - * Pause program update. - * Updating program data will result in UI refresh, - * but UI is fragile to handle it so we'd better disable it for a while. + * Pause program update. Updating program data will result in UI refresh, but UI is fragile to + * handle it so we'd better disable it for a while. * - *

Prefetch should be enabled to call it. + *

Prefetch should be enabled to call it. */ public void setPauseProgramUpdate(boolean pauseProgramUpdate) { SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled."); @@ -700,11 +722,10 @@ public class ProgramDataManager implements MemoryManageable { } /** - * Sets program data prefetch time range. - * Any program data that ends before the start time will be removed from the cache later. - * Note that there's no limit for end time. + * Sets program data prefetch time range. Any program data that ends before the start time will + * be removed from the cache later. Note that there's no limit for end time. * - *

Prefetch should be enabled to call it. + *

Prefetch should be enabled to call it. */ public void setPrefetchTimeRange(long startTimeMs) { SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled."); @@ -736,7 +757,8 @@ public class ProgramDataManager implements MemoryManageable { return new Program.Builder() .setChannelId(Channel.INVALID_ID) .setStartTimeUtcMillis(startTimeMs) - .setEndTimeUtcMillis(endTimeMs).build(); + .setEndTimeUtcMillis(endTimeMs) + .build(); } @Override diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java index 709863cf..1f84235d 100644 --- a/src/com/android/tv/data/StreamInfo.java +++ b/src/com/android/tv/data/StreamInfo.java @@ -28,19 +28,26 @@ public interface StreamInfo { int AUDIO_CHANNEL_COUNT_UNKNOWN = 0; Channel getCurrentChannel(); + TvContentRating getBlockedContentRating(); int getVideoWidth(); + int getVideoHeight(); + float getVideoFrameRate(); + float getVideoDisplayAspectRatio(); + int getVideoDefinitionLevel(); + int getAudioChannelCount(); + boolean hasClosedCaption(); + boolean isVideoAvailable(); - /** - * Returns true, if video or audio is available. - */ + /** Returns true, if video or audio is available. */ boolean isVideoOrAudioAvailable(); + int getVideoUnavailableReason(); } diff --git a/src/com/android/tv/data/TvInputNewComparator.java b/src/com/android/tv/data/TvInputNewComparator.java index acc3e38a..effca970 100644 --- a/src/com/android/tv/data/TvInputNewComparator.java +++ b/src/com/android/tv/data/TvInputNewComparator.java @@ -17,15 +17,11 @@ package com.android.tv.data; import android.media.tv.TvInputInfo; - import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; - import java.util.Comparator; -/** - * Compares TV input such that the new input comes first. - */ +/** Compares TV input such that the new input comes first. */ public class TvInputNewComparator implements Comparator { private final SetupUtils mSetupUtils; private final TvInputManagerHelper mInputManager; diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java index 3edd7b1a..8c9756b0 100644 --- a/src/com/android/tv/data/WatchedHistoryManager.java +++ b/src/com/android/tv/data/WatchedHistoryManager.java @@ -12,9 +12,7 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; - import com.android.tv.common.SharedPreferencesUtils; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -25,13 +23,14 @@ import java.util.concurrent.TimeUnit; /** * A class to manage watched history. * - *

When there is no access to watched table of TvProvider, - * this class is used to build up watched history and to compute recent channels. + *

When there is no access to watched table of TvProvider, this class is used to build up watched + * history and to compute recent channels. + * *

Note that this class is not thread safe. Please use this on one thread. */ public class WatchedHistoryManager { - private final static String TAG = "WatchedHistoryManager"; - private final static boolean DEBUG = false; + private static final String TAG = "WatchedHistoryManager"; + private static final boolean DEBUG = false; private static final int MAX_HISTORY_SIZE = 10000; private static final String PREF_KEY_LAST_INDEX = "last_index"; @@ -47,8 +46,8 @@ public class WatchedHistoryManager { new OnSharedPreferenceChangeListener() { @Override @MainThread - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { + public void onSharedPreferenceChanged( + SharedPreferences sharedPreferences, String key) { if (key.equals(PREF_KEY_LAST_INDEX)) { final long lastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1); if (lastIndex <= mLastIndex) { @@ -57,23 +56,26 @@ public class WatchedHistoryManager { // onSharedPreferenceChanged is always called in a main thread. // onNewRecordAdded will be called in the same thread as the thread // which created this instance. - mHandler.post(new Runnable() { - @Override - public void run() { - for (long i = mLastIndex + 1; i <= lastIndex; ++i) { - WatchedRecord record = decode( - mSharedPreferences.getString(getSharedPreferencesKey(i), - null)); - if (record != null) { - mWatchedHistory.add(record); - if (mListener != null) { - mListener.onNewRecordAdded(record); + mHandler.post( + new Runnable() { + @Override + public void run() { + for (long i = mLastIndex + 1; i <= lastIndex; ++i) { + WatchedRecord record = + decode( + mSharedPreferences.getString( + getSharedPreferencesKey(i), + null)); + if (record != null) { + mWatchedHistory.add(record); + if (mListener != null) { + mListener.onNewRecordAdded(record); + } + } } + mLastIndex = lastIndex; } - } - mLastIndex = lastIndex; - } - }); + }); } } }; @@ -94,9 +96,7 @@ public class WatchedHistoryManager { mHandler = new Handler(); } - /** - * Starts the manager. It loads history data from {@link SharedPreferences}. - */ + /** Starts the manager. It loads history data from {@link SharedPreferences}. */ public void start() { if (mStarted) { return; @@ -123,22 +123,22 @@ public class WatchedHistoryManager { @WorkerThread private void loadWatchedHistory() { - mSharedPreferences = mContext.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); + mSharedPreferences = + mContext.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); mLastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1); if (mLastIndex >= 0 && mLastIndex < mMaxHistorySize) { for (int i = 0; i <= mLastIndex; ++i) { WatchedRecord record = - decode(mSharedPreferences.getString(getSharedPreferencesKey(i), - null)); + decode(mSharedPreferences.getString(getSharedPreferencesKey(i), null)); if (record != null) { mWatchedHistory.add(record); } } } else if (mLastIndex >= mMaxHistorySize) { for (long i = mLastIndex - mMaxHistorySize + 1; i <= mLastIndex; ++i) { - WatchedRecord record = decode(mSharedPreferences.getString( - getSharedPreferencesKey(i), null)); + WatchedRecord record = + decode(mSharedPreferences.getString(getSharedPreferencesKey(i), null)); if (record != null) { mWatchedHistory.add(record); } @@ -173,9 +173,7 @@ public class WatchedHistoryManager { return mLoaded; } - /** - * Logs the record of the watched channel. - */ + /** Logs the record of the watched channel. */ public void logChannelViewStop(Channel channel, long endTime, long duration) { if (duration < MIN_DURATION_MS) { return; @@ -185,7 +183,8 @@ public class WatchedHistoryManager { if (DEBUG) Log.d(TAG, "Log a watched record. " + record); mWatchedHistory.add(record); ++mLastIndex; - mSharedPreferences.edit() + mSharedPreferences + .edit() .putString(getSharedPreferencesKey(mLastIndex), encode(record)) .putLong(PREF_KEY_LAST_INDEX, mLastIndex) .apply(); @@ -197,16 +196,14 @@ public class WatchedHistoryManager { } } - /** - * Sets {@link Listener}. - */ + /** Sets {@link Listener}. */ public void setListener(Listener listener) { mListener = listener; } /** - * Returns watched history in the ascending order of time. In other words, the first element - * is the oldest and the last element is the latest record. + * Returns watched history in the ascending order of time. In other words, the first element is + * the oldest and the last element is the latest record. */ @NonNull public List getWatchedHistory() { @@ -242,8 +239,12 @@ public class WatchedHistoryManager { @Override public String toString() { - return "WatchedRecord: id=" + channelId + ",watchedStartTime=" + watchedStartTime - + ",duration=" + duration; + return "WatchedRecord: id=" + + channelId + + ",watchedStartTime=" + + watchedStartTime + + ",duration=" + + duration; } @Override @@ -281,10 +282,9 @@ public class WatchedHistoryManager { } public interface Listener { - /** - * Called when history is loaded. - */ + /** Called when history is loaded. */ void onLoadFinished(); + void onNewRecordAdded(WatchedRecord watchedRecord); } } diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java index 5693c877..89d5f494 100644 --- a/src/com/android/tv/data/epg/EpgFetchHelper.java +++ b/src/com/android/tv/data/epg/EpgFetchHelper.java @@ -27,9 +27,7 @@ import android.preference.PreferenceManager; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; - import com.android.tv.data.Program; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -53,7 +51,7 @@ class EpgFetchHelper { private static long sLastEpgUpdatedTimestamp = -1; private static String sLastLineupId; - private EpgFetchHelper() { } + private EpgFetchHelper() {} /** * Updates newly fetched EPG data for the given channel to local providers. The method will @@ -61,7 +59,7 @@ class EpgFetchHelper { * of that channel in the database one by one. It will update the matched old program, or insert * the new program if there is no matching program can be found in the database and at the same * time remove those old programs which conflicts with the inserted one. - + * * @param channelId the target channel ID. * @param fetchedPrograms the newly fetched program data. * @return {@code true} if new program data are successfully updated. Otherwise {@code false}. @@ -82,8 +80,10 @@ class EpgFetchHelper { // or insert new program if there is no matching program in the database. ArrayList ops = new ArrayList<>(); while (newProgramsIndex < fetchedProgramsCount) { - Program oldProgram = oldProgramsIndex < oldPrograms.size() - ? oldPrograms.get(oldProgramsIndex) : null; + Program oldProgram = + oldProgramsIndex < oldPrograms.size() + ? oldPrograms.get(oldProgramsIndex) + : null; Program newProgram = fetchedPrograms.get(newProgramsIndex); boolean addNewProgram = false; if (oldProgram != null) { @@ -95,18 +95,20 @@ class EpgFetchHelper { // Partial match. Update the old program with the new one. // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There // could be application specific settings which belong to the old program. - ops.add(ContentProviderOperation.newUpdate( - TvContract.buildProgramUri(oldProgram.getId())) - .withValues(Program.toContentValues(newProgram)) - .build()); + ops.add( + ContentProviderOperation.newUpdate( + TvContract.buildProgramUri(oldProgram.getId())) + .withValues(Program.toContentValues(newProgram)) + .build()); oldProgramsIndex++; newProgramsIndex++; } else if (oldProgram.getEndTimeUtcMillis() < newProgram.getEndTimeUtcMillis()) { // No match. Remove the old program first to see if the next program in // {@code oldPrograms} partially matches the new program. - ops.add(ContentProviderOperation.newDelete( - TvContract.buildProgramUri(oldProgram.getId())) - .build()); + ops.add( + ContentProviderOperation.newDelete( + TvContract.buildProgramUri(oldProgram.getId())) + .build()); oldProgramsIndex++; } else { // No match. The new program does not match any of the old programs. Insert @@ -120,10 +122,10 @@ class EpgFetchHelper { newProgramsIndex++; } if (addNewProgram) { - ops.add(ContentProviderOperation - .newInsert(Programs.CONTENT_URI) - .withValues(Program.toContentValues(newProgram)) - .build()); + ops.add( + ContentProviderOperation.newInsert(Programs.CONTENT_URI) + .withValues(Program.toContentValues(newProgram)) + .build()); } // Throttle the batch operation not to cause TransactionTooLargeException. if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) { @@ -150,11 +152,17 @@ class EpgFetchHelper { return updated; } - private static List queryPrograms(Context context, long channelId, - long startTimeMs, long endTimeMs) { - try (Cursor c = context.getContentResolver().query( - TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs), - Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) { + private static List queryPrograms( + Context context, long channelId, long startTimeMs, long endTimeMs) { + try (Cursor c = + context.getContentResolver() + .query( + TvContract.buildProgramsUriForChannel( + channelId, startTimeMs, endTimeMs), + Program.PROJECTION, + null, + null, + Programs.COLUMN_START_TIME_UTC_MILLIS)) { if (c == null) { return Collections.emptyList(); } @@ -167,8 +175,8 @@ class EpgFetchHelper { } /** - * Returns {@code true} if the {@code oldProgram} needs to be updated with the - * {@code newProgram}. + * Returns {@code true} if the {@code oldProgram} needs to be updated with the {@code + * newProgram}. */ private static boolean hasSameTitleAndOverlap(Program oldProgram, Program newProgram) { // NOTE: Here, we update the old program if it has the same title and overlaps with the @@ -186,24 +194,25 @@ class EpgFetchHelper { * every time when it needs to fetch EPG data. */ @WorkerThread - synchronized static void setLastLineupId(Context context, String lineupId) { + static synchronized void setLastLineupId(Context context, String lineupId) { if (DEBUG) { if (lineupId == null) { Log.d(TAG, "Clear stored lineup id: " + sLastLineupId); } } sLastLineupId = lineupId; - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(KEY_LAST_LINEUP_ID, lineupId).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(KEY_LAST_LINEUP_ID, lineupId) + .apply(); } - /** - * Gets the last known lineup ID from shared preferences. - */ - synchronized static String getLastLineupId(Context context) { + /** Gets the last known lineup ID from shared preferences. */ + static synchronized String getLastLineupId(Context context) { if (sLastLineupId == null) { - sLastLineupId = PreferenceManager.getDefaultSharedPreferences(context) - .getString(KEY_LAST_LINEUP_ID, null); + sLastLineupId = + PreferenceManager.getDefaultSharedPreferences(context) + .getString(KEY_LAST_LINEUP_ID, null); } if (DEBUG) Log.d(TAG, "Last lineup is " + sLastLineupId); return sLastLineupId; @@ -214,20 +223,21 @@ class EpgFetchHelper { * out-dated, it's not necessary for EPG fetcher to fetch EPG again. */ @WorkerThread - synchronized static void setLastEpgUpdatedTimestamp(Context context, long timestamp) { + static synchronized void setLastEpgUpdatedTimestamp(Context context, long timestamp) { sLastEpgUpdatedTimestamp = timestamp; - PreferenceManager.getDefaultSharedPreferences(context).edit().putLong( - KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp) + .apply(); } - /** - * Gets the last updated timestamp of EPG data. - */ - synchronized static long getLastEpgUpdatedTimestamp(Context context) { + /** Gets the last updated timestamp of EPG data. */ + static synchronized long getLastEpgUpdatedTimestamp(Context context) { if (sLastEpgUpdatedTimestamp < 0) { - sLastEpgUpdatedTimestamp = PreferenceManager.getDefaultSharedPreferences(context) - .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0); + sLastEpgUpdatedTimestamp = + PreferenceManager.getDefaultSharedPreferences(context) + .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0); } return sLastEpgUpdatedTimestamp; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java index 24f8b826..b10bdc1b 100644 --- a/src/com/android/tv/data/epg/EpgFetcher.java +++ b/src/com/android/tv/data/epg/EpgFetcher.java @@ -35,7 +35,6 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.Features; import com.android.tv.TvApplication; @@ -54,7 +53,6 @@ import com.android.tv.tuner.util.PostalCodeUtils; import com.android.tv.util.LocationUtils; import com.android.tv.util.NetworkTrafficTags; import com.android.tv.util.Utils; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -140,8 +138,9 @@ public class EpgFetcher { mEpgReader = createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext)); int remoteInteval = - (int) RemoteConfigUtils.getRemoteConfig( - context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR); + (int) + RemoteConfigUtils.getRemoteConfig( + context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR); mRoutineIntervalMs = remoteInteval < 0 ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR) @@ -152,8 +151,8 @@ public class EpgFetcher { /** * Starts the routine service of EPG fetching. It use {@link JobScheduler} to schedule the EPG - * fetching routine. The EPG fetching routine will be started roughly every 4 hours, unless - * the channel scanning of tuner input is started. + * fetching routine. The EPG fetching routine will be started roughly every 4 hours, unless the + * channel scanning of tuner input is started. */ @MainThread public void startRoutineService() { @@ -177,8 +176,8 @@ public class EpgFetcher { } /** - * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated - * by routine fetching service due to various reasons. + * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated by + * routine fetching service due to various reasons. */ @MainThread public void fetchImmediatelyIfNeeded() { @@ -203,33 +202,30 @@ public class EpgFetcher { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - /** - * Fetches EPG immediately. - */ + /** Fetches EPG immediately. */ @MainThread public void fetchImmediately() { if (!mChannelDataManager.isDbLoadFinished()) { - mChannelDataManager.addListener(new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - mChannelDataManager.removeListener(this); - executeFetchTaskIfPossible(null, null); - } + mChannelDataManager.addListener( + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + executeFetchTaskIfPossible(null, null); + } - @Override - public void onChannelListUpdated() { } + @Override + public void onChannelListUpdated() {} - @Override - public void onChannelBrowsableChanged() { } - }); + @Override + public void onChannelBrowsableChanged() {} + }); } else { executeFetchTaskIfPossible(null, null); } } - /** - * Notifies EPG fetch service that channel scanning is started. - */ + /** Notifies EPG fetch service that channel scanning is started. */ @MainThread public void onChannelScanStarted() { if (mScanStarted || !Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { @@ -248,9 +244,7 @@ public class EpgFetcher { Log.i(TAG, "EPG fetching on channel scanning started."); } - /** - * Notifies EPG fetch service that channel scanning is finished. - */ + /** Notifies EPG fetch service that channel scanning is finished. */ @MainThread public void onChannelScanFinished() { if (!mScanStarted) { @@ -285,8 +279,10 @@ public class EpgFetcher { private boolean checkFetchPrerequisite() { if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job."); if (!Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { - Log.i(TAG, "Cannot start routine service: country not supported: " - + LocationUtils.getCurrentCountry(mContext)); + Log.i( + TAG, + "Cannot start routine service: country not supported: " + + LocationUtils.getCurrentCountry(mContext)); return false; } if (mFetchTask != null) { @@ -312,8 +308,10 @@ public class EpgFetcher { @MainThread private int getTunerChannelCount() { - for (TvInputInfo input : TvApplication.getSingletons(mContext) - .getTvInputManagerHelper().getTvInputInfos(true, true)) { + for (TvInputInfo input : + TvApplication.getSingletons(mContext) + .getTvInputManagerHelper() + .getTvInputInfos(true, true)) { String inputId = input.getId(); if (Utils.isInternalTvInput(mContext, inputId)) { return mChannelDataManager.getChannelCountForInput(inputId); @@ -369,7 +367,8 @@ public class EpgFetcher { } // Updates possible lineups if necessary. SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset."); - if (postalCodeChanged || forceUpdatePossibleLineups + if (postalCodeChanged + || forceUpdatePossibleLineups || EpgFetchHelper.getLastLineupId(mContext) == null) { // To prevent main thread being blocked, though theoretically it should not happen. List possibleLineups = @@ -415,8 +414,9 @@ public class EpgFetcher { continue; } Collections.sort(programs); - Log.i(TAG, "Batch fetched " + programs.size() + " programs for channel " - + entry.getKey()); + Log.i( + TAG, + "Batch fetched " + programs.size() + " programs for channel " + entry.getKey()); EpgFetchHelper.updateEpgData(mContext, entry.getKey(), programs); } } @@ -473,21 +473,23 @@ public class EpgFetcher { @Override public boolean onStartJob(JobParameters params) { if (!mEpgFetcher.mChannelDataManager.isDbLoadFinished()) { - mEpgFetcher.mChannelDataManager.addListener(new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - mEpgFetcher.mChannelDataManager.removeListener(this); - if (!mEpgFetcher.executeFetchTaskIfPossible(EpgFetchService.this, params)) { - jobFinished(params, false); - } - } - - @Override - public void onChannelListUpdated() { } - - @Override - public void onChannelBrowsableChanged() { } - }); + mEpgFetcher.mChannelDataManager.addListener( + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mEpgFetcher.mChannelDataManager.removeListener(this); + if (!mEpgFetcher.executeFetchTaskIfPossible( + EpgFetchService.this, params)) { + jobFinished(params, false); + } + } + + @Override + public void onChannelListUpdated() {} + + @Override + public void onChannelBrowsableChanged() {} + }); return true; } else { return mEpgFetcher.executeFetchTaskIfPossible(this, params); @@ -585,7 +587,8 @@ public class EpgFetcher { @Override protected void onPostExecute(Integer failureReason) { mFetchTask = null; - if (failureReason == null || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED + if (failureReason == null + || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED || failureReason == REASON_NO_NEW_EPG) { jobFinished(false); } else { @@ -622,9 +625,11 @@ public class EpgFetcher { if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()"); if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - Message.obtain(FetchDuringScanHandler.this, - MSG_CHANNEL_UPDATED_DURING_SCAN, new ArrayList<>( - mChannelDataManager.getChannelList())).sendToTarget(); + Message.obtain( + FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, + new ArrayList<>(mChannelDataManager.getChannelList())) + .sendToTarget(); } } @@ -633,9 +638,11 @@ public class EpgFetcher { if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()"); if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - Message.obtain(FetchDuringScanHandler.this, - MSG_CHANNEL_UPDATED_DURING_SCAN, - mChannelDataManager.getChannelList()).sendToTarget(); + Message.obtain( + FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, + mChannelDataManager.getChannelList()) + .sendToTarget(); } } @@ -724,12 +731,14 @@ public class EpgFetcher { // Clear timestamp to make routine service start right away. EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0); Log.i(TAG, "EPG Fetching during channel scanning finished."); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - fetchImmediately(); - } - }); + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + fetchImmediately(); + } + }); } } } diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java index c5aeca27..d10a852c 100644 --- a/src/com/android/tv/data/epg/EpgReader.java +++ b/src/com/android/tv/data/epg/EpgReader.java @@ -19,28 +19,22 @@ 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.dvr.data.SeriesInfo; - import java.util.List; import java.util.Map; -/** - * An interface used to retrieve the EPG data. This class should be used in worker thread. - */ +/** An interface used to retrieve the EPG data. This class should be used in worker thread. */ @WorkerThread public interface EpgReader { - /** - * Checks if the reader is available. - */ + /** Checks if the reader is available. */ boolean isAvailable(); /** - * Returns the timestamp of the current EPG. - * The format should be YYYYMMDDHHmmSS as a long value. ex) 20160308141500 + * Returns the timestamp of the current EPG. The format should be YYYYMMDDHHmmSS as a long + * value. ex) 20160308141500 */ long getEpgTimestamp(); @@ -66,9 +60,7 @@ public interface EpgReader { /** Pre-loads and caches channels for a given lineup. */ void preloadChannels(@NonNull String lineupId); - /** - * Clears cached channels for a given lineup. - */ + /** Clears cached channels for a given lineup. */ @AnyThread void clearCachedChannels(@NonNull String lineupId); @@ -88,4 +80,4 @@ public interface EpgReader { /** Returns the series information for the given series ID. */ SeriesInfo getSeriesInfo(@NonNull String seriesId); -} \ No newline at end of file +} diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java index ab6935ad..49409a1d 100644 --- a/src/com/android/tv/data/epg/StubEpgReader.java +++ b/src/com/android/tv/data/epg/StubEpgReader.java @@ -17,23 +17,18 @@ 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.dvr.data.SeriesInfo; - import java.util.Collections; import java.util.List; import java.util.Map; -/** - * A stub class to read EPG. - */ -public class StubEpgReader implements EpgReader{ - public StubEpgReader(@SuppressWarnings("unused") Context context) { - } +/** A stub class to read EPG. */ +public class StubEpgReader implements EpgReader { + public StubEpgReader(@SuppressWarnings("unused") Context context) {} @Override public boolean isAvailable() { @@ -89,4 +84,4 @@ public class StubEpgReader implements EpgReader{ public SeriesInfo getSeriesInfo(@NonNull String seriesId) { return null; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java index d686e6e6..442a663d 100644 --- a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java +++ b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java @@ -30,7 +30,6 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -41,13 +40,10 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; -/** - * Displays the DVR history. - */ +/** Displays the DVR history. */ @TargetApi(VERSION_CODES.N) public class DvrHistoryDialogFragment extends SafeDismissDialogFragment { public static final String DIALOG_TAG = DvrHistoryDialogFragment.class.getSimpleName(); @@ -67,60 +63,78 @@ public class DvrHistoryDialogFragment extends SafeDismissDialogFragment { } mSchedules.sort(ScheduledRecording.START_TIME_COMPARATOR.reversed()); LayoutInflater inflater = LayoutInflater.from(getContext()); - ArrayAdapter adapter = new ArrayAdapter(getContext(), - R.layout.list_item_dvr_history, ScheduledRecording.toArray(mSchedules)) { - @NonNull - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view = inflater.inflate(R.layout.list_item_dvr_history, parent, false); - ScheduledRecording schedule = mSchedules.get(position); - setText(view, R.id.state, getStateString(schedule.getState())); - setText(view, R.id.schedule_time, getRecordingTimeText(schedule)); - setText(view, R.id.program_title, DvrUiHelper.getStyledTitleWithEpisodeNumber( - getContext(), schedule, 0)); - setText(view, R.id.channel_name, getChannelNameText(schedule)); - return view; - } + ArrayAdapter adapter = + new ArrayAdapter( + getContext(), + R.layout.list_item_dvr_history, + ScheduledRecording.toArray(mSchedules)) { + @NonNull + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = inflater.inflate(R.layout.list_item_dvr_history, parent, false); + ScheduledRecording schedule = mSchedules.get(position); + setText(view, R.id.state, getStateString(schedule.getState())); + setText(view, R.id.schedule_time, getRecordingTimeText(schedule)); + setText( + view, + R.id.program_title, + DvrUiHelper.getStyledTitleWithEpisodeNumber( + getContext(), schedule, 0)); + setText(view, R.id.channel_name, getChannelNameText(schedule)); + return view; + } - private void setText(View view, int id, CharSequence text) { - ((TextView) view.findViewById(id)).setText(text); - } + private void setText(View view, int id, CharSequence text) { + ((TextView) view.findViewById(id)).setText(text); + } - private void setText(View view, int id, int text) { - ((TextView) view.findViewById(id)).setText(text); - } + private void setText(View view, int id, int text) { + ((TextView) view.findViewById(id)).setText(text); + } - @SuppressLint("SwitchIntDef") - private int getStateString(@RecordingState int state) { - switch (state) { - case ScheduledRecording.STATE_RECORDING_CLIPPED: - return R.string.dvr_history_dialog_state_clip; - case ScheduledRecording.STATE_RECORDING_FAILED: - return R.string.dvr_history_dialog_state_fail; - case ScheduledRecording.STATE_RECORDING_FINISHED: - return R.string.dvr_history_dialog_state_success; - default: - break; - } - return 0; - } + @SuppressLint("SwitchIntDef") + private int getStateString(@RecordingState int state) { + switch (state) { + case ScheduledRecording.STATE_RECORDING_CLIPPED: + return R.string.dvr_history_dialog_state_clip; + case ScheduledRecording.STATE_RECORDING_FAILED: + return R.string.dvr_history_dialog_state_fail; + case ScheduledRecording.STATE_RECORDING_FINISHED: + return R.string.dvr_history_dialog_state_success; + default: + break; + } + return 0; + } - private String getChannelNameText(ScheduledRecording schedule) { - Channel channel = channelDataManager.getChannel(schedule.getChannelId()); - return channel == null ? null : - TextUtils.isEmpty(channel.getDisplayName()) ? channel.getDisplayNumber() : - channel.getDisplayName().trim() + " " + channel.getDisplayNumber(); - } + private String getChannelNameText(ScheduledRecording schedule) { + Channel channel = channelDataManager.getChannel(schedule.getChannelId()); + return channel == null + ? null + : TextUtils.isEmpty(channel.getDisplayName()) + ? channel.getDisplayNumber() + : channel.getDisplayName().trim() + + " " + + channel.getDisplayNumber(); + } - private String getRecordingTimeText(ScheduledRecording schedule) { - return Utils.getDurationString(getContext(), schedule.getStartTimeMs(), - schedule.getEndTimeMs(), true, true, true, 0); - } - }; + private String getRecordingTimeText(ScheduledRecording schedule) { + return Utils.getDurationString( + getContext(), + schedule.getStartTimeMs(), + schedule.getEndTimeMs(), + true, + true, + true, + 0); + } + }; ListView listView = new ListView(getActivity()); listView.setAdapter(adapter); - return new AlertDialog.Builder(getActivity()).setTitle(R.string.dvr_history_dialog_title) - .setView(listView).create(); + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.dvr_history_dialog_title) + .setView(listView) + .create(); } @Override diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java index d00422a7..53adb308 100644 --- a/src/com/android/tv/dialog/FullscreenDialogFragment.java +++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java @@ -23,21 +23,18 @@ import android.os.Bundle; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; - import com.android.tv.MainActivity; import com.android.tv.R; -/** - * Dialog fragment with full screen. - */ +/** Dialog fragment with full screen. */ public class FullscreenDialogFragment extends SafeDismissDialogFragment { public static final String DIALOG_TAG = FullscreenDialogFragment.class.getSimpleName(); public static final String VIEW_LAYOUT_ID = "viewLayoutId"; public static final String TRACKER_LABEL = "trackerLabel"; /** - * Creates a FullscreenDialogFragment. View class of viewLayoutResId should - * implement {@link DialogView}. + * Creates a FullscreenDialogFragment. View class of viewLayoutResId should implement {@link + * DialogView}. */ public static FullscreenDialogFragment newInstance(int viewLayoutResId, String trackerLabel) { FullscreenDialogFragment f = new FullscreenDialogFragment(); @@ -100,21 +97,13 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment { } } - /** - * Interface for the view of {@link FullscreenDialogFragment}. - */ + /** Interface for the view of {@link FullscreenDialogFragment}. */ public interface DialogView { - /** - * Called after the view is inflated and attached to the dialog. - */ + /** Called after the view is inflated and attached to the dialog. */ void initialize(MainActivity activity, Dialog dialog); - /** - * Called when a back key is pressed. - */ + /** Called when a back key is pressed. */ void onBackPressed(); - /** - * Called when {@link DialogFragment#onDestroy} is called. - */ + /** Called when {@link DialogFragment#onDestroy} is called. */ void onDestroy(); } } diff --git a/src/com/android/tv/dialog/HalfSizedDialogFragment.java b/src/com/android/tv/dialog/HalfSizedDialogFragment.java index 315c6a93..aed75655 100644 --- a/src/com/android/tv/dialog/HalfSizedDialogFragment.java +++ b/src/com/android/tv/dialog/HalfSizedDialogFragment.java @@ -24,9 +24,7 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; - import java.util.concurrent.TimeUnit; public class HalfSizedDialogFragment extends SafeDismissDialogFragment { @@ -38,16 +36,17 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment { private OnActionClickListener mOnActionClickListener; private Handler mHandler = new Handler(); - private Runnable mAutoDismisser = new Runnable() { - @Override - public void run() { - dismiss(); - } - }; + private Runnable mAutoDismisser = + new Runnable() { + @Override + public void run() { + dismiss(); + } + }; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.halfsized_dialog, container, false); } @@ -76,13 +75,14 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { - public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) { - mHandler.removeCallbacks(mAutoDismisser); - mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); - return false; - } - }); + dialog.setOnKeyListener( + new DialogInterface.OnKeyListener() { + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) { + mHandler.removeCallbacks(mAutoDismisser); + mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS); + return false; + } + }); return dialog; } @@ -98,26 +98,24 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment { /** * Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog - * will be automatically closed when it's paused to prevent the fragment being re-created by - * the framework, which will result the listener being forgotten. + * will be automatically closed when it's paused to prevent the fragment being re-created by the + * framework, which will result the listener being forgotten. */ public void setOnActionClickListener(OnActionClickListener listener) { mOnActionClickListener = listener; } - /** - * Returns {@link OnActionClickListener} for sub-classes or any inner fragments. - */ + /** Returns {@link OnActionClickListener} for sub-classes or any inner fragments. */ protected OnActionClickListener getOnActionClickListener() { return mOnActionClickListener; } /** * An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments - * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier - * of the action user clicked. + * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier of + * the action user clicked. */ public interface OnActionClickListener { void onActionClick(long actionId); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java index d5c154da..ccc3a983 100644 --- a/src/com/android/tv/dialog/PinDialogFragment.java +++ b/src/com/android/tv/dialog/PinDialogFragment.java @@ -44,7 +44,6 @@ import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -54,33 +53,25 @@ public class PinDialogFragment extends SafeDismissDialogFragment { private static final String TAG = "PinDialogFragment"; private static final boolean DEBUG = true; - /** - * PIN code dialog for unlock channel - */ + /** PIN code dialog for unlock channel */ public static final int PIN_DIALOG_TYPE_UNLOCK_CHANNEL = 0; /** - * PIN code dialog for unlock content. - * Only difference between {@code PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title. + * PIN code dialog for unlock content. Only difference between {@code + * PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title. */ public static final int PIN_DIALOG_TYPE_UNLOCK_PROGRAM = 1; - /** - * PIN code dialog for change parental control settings - */ + /** PIN code dialog for change parental control settings */ public static final int PIN_DIALOG_TYPE_ENTER_PIN = 2; - /** - * PIN code dialog for set new PIN - */ + /** PIN code dialog for set new PIN */ public static final int PIN_DIALOG_TYPE_NEW_PIN = 3; // PIN code dialog for checking old PIN. This is internal only. private static final int PIN_DIALOG_TYPE_OLD_PIN = 4; - /** - * PIN code dialog for unlocking DVR playback - */ + /** PIN code dialog for unlocking DVR playback */ public static final int PIN_DIALOG_TYPE_UNLOCK_DVR = 5; private static final int MAX_WRONG_PIN_COUNT = 5; @@ -94,7 +85,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment { public static final String DIALOG_TAG = PinDialogFragment.class.getName(); private static final int NUMBER_PICKERS_RES_ID[] = { - R.id.first, R.id.second, R.id.third, R.id.fourth }; + R.id.first, R.id.second, R.id.third, R.id.fourth + }; private int mType; private int mRequestType; @@ -164,15 +156,16 @@ public class PinDialogFragment extends SafeDismissDialogFragment { // So apply view size to window after the DialogFragment.onStart() where dialog is shown. Dialog dlg = getDialog(); if (dlg != null) { - dlg.getWindow().setLayout( - getResources().getDimensionPixelSize(R.dimen.pin_dialog_width), - LayoutParams.WRAP_CONTENT); + dlg.getWindow() + .setLayout( + getResources().getDimensionPixelSize(R.dimen.pin_dialog_width), + LayoutParams.WRAP_CONTENT); } } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.pin_dialog, container, false); mWrongPinView = (TextView) v.findViewById(R.id.wrong_pin); @@ -234,12 +227,13 @@ public class PinDialogFragment extends SafeDismissDialogFragment { return v; } - private final Runnable mUpdateEnterPinRunnable = new Runnable() { - @Override - public void run() { - updateWrongPin(); - } - }; + private final Runnable mUpdateEnterPinRunnable = + new Runnable() { + @Override + public void run() { + updateWrongPin(); + } + }; private void updateWrongPin() { if (getActivity() == null) { @@ -257,8 +251,12 @@ public class PinDialogFragment extends SafeDismissDialogFragment { } else { mEnterPinView.setVisibility(View.INVISIBLE); mWrongPinView.setVisibility(View.VISIBLE); - mWrongPinView.setText(getResources().getQuantityString(R.plurals.pin_enter_countdown, - remainingSeconds, remainingSeconds)); + mWrongPinView.setText( + getResources() + .getQuantityString( + R.plurals.pin_enter_countdown, + remainingSeconds, + remainingSeconds)); mHandler.postDelayed(mUpdateEnterPinRunnable, 1000); } } @@ -280,8 +278,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment { if (DEBUG) Log.d(TAG, "onDismiss: mPinChecked=" + mPinChecked); SoftPreconditions.checkState(getActivity() instanceof OnPinCheckedListener); if (!mDismissSilently && getActivity() instanceof OnPinCheckedListener) { - ((OnPinCheckedListener) getActivity()).onPinChecked( - mPinChecked, mRequestType, mRatingString); + ((OnPinCheckedListener) getActivity()) + .onPinChecked(mPinChecked, mRequestType, mRatingString); } mDismissSilently = false; } @@ -391,7 +389,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment { R.id.previous_number, R.id.current_number, R.id.next_number, - R.id.next2_number }; + R.id.next2_number + }; private static final int CURRENT_NUMBER_VIEW_INDEX = 2; private static final int NOT_INITIALIZED = Integer.MIN_VALUE; @@ -436,8 +435,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment { this(context, attrs, defStyleAttr, 0); } - public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public PinNumberPicker( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); View view = inflate(context, R.layout.pin_number_picker, this); mNumberViewHolder = view.findViewById(R.id.number_view_holder); @@ -447,118 +446,149 @@ public class PinDialogFragment extends SafeDismissDialogFragment { mNumberViews[i] = (TextView) view.findViewById(NUMBER_VIEWS_RES_ID[i]); } Resources resources = context.getResources(); - mNumberViewHeight = resources.getDimensionPixelSize( - R.dimen.pin_number_picker_text_view_height); - - mNumberViewHolder.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - updateFocus(true); - } - }); - - mNumberViewHolder.setOnKeyListener(new OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: { - if (mCancelAnimation) { - mScrollAnimatorSet.end(); + mNumberViewHeight = + resources.getDimensionPixelSize(R.dimen.pin_number_picker_text_view_height); + + mNumberViewHolder.setOnFocusChangeListener( + new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + updateFocus(true); + } + }); + + mNumberViewHolder.setOnKeyListener( + new OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + { + if (mCancelAnimation) { + mScrollAnimatorSet.end(); + } + if (!mScrollAnimatorSet.isRunning()) { + mCancelAnimation = false; + if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + mNextValue = + adjustValueInValidRange( + mCurrentValue + 1); + startScrollAnimation(true); + } else { + mNextValue = + adjustValueInValidRange( + mCurrentValue - 1); + startScrollAnimation(false); + } + } + return true; + } } - if (!mScrollAnimatorSet.isRunning()) { - mCancelAnimation = false; - if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { - mNextValue = adjustValueInValidRange(mCurrentValue + 1); - startScrollAnimation(true); - } else { - mNextValue = adjustValueInValidRange(mCurrentValue - 1); - startScrollAnimation(false); - } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + { + mCancelAnimation = true; + return true; + } } - return true; - } - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: { - mCancelAnimation = true; - return true; } + return false; } - } - return false; - } - }); + }); mNumberViewHolder.setScrollY(mNumberViewHeight); mFocusGainAnimator = new AnimatorSet(); mFocusGainAnimator.playTogether( - ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1], - "alpha", 0f, sAlphaForAdjacentNumber), - ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX], - "alpha", sAlphaForFocusedNumber, 0f), - ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1], - "alpha", 0f, sAlphaForAdjacentNumber), + ObjectAnimator.ofFloat( + mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1], + "alpha", + 0f, + sAlphaForAdjacentNumber), + ObjectAnimator.ofFloat( + mNumberViews[CURRENT_NUMBER_VIEW_INDEX], + "alpha", + sAlphaForFocusedNumber, + 0f), + ObjectAnimator.ofFloat( + mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1], + "alpha", + 0f, + sAlphaForAdjacentNumber), ObjectAnimator.ofFloat(mBackgroundView, "alpha", 0f, 1f)); - mFocusGainAnimator.setDuration(context.getResources().getInteger( - android.R.integer.config_shortAnimTime)); - mFocusGainAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText(mBackgroundView.getText()); - mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(sAlphaForFocusedNumber); - mBackgroundView.setText(""); - } - }); + mFocusGainAnimator.setDuration( + context.getResources().getInteger(android.R.integer.config_shortAnimTime)); + mFocusGainAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText( + mBackgroundView.getText()); + mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha( + sAlphaForFocusedNumber); + mBackgroundView.setText(""); + } + }); mFocusLossAnimator = new AnimatorSet(); mFocusLossAnimator.playTogether( - ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1], - "alpha", sAlphaForAdjacentNumber, 0f), - ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1], - "alpha", sAlphaForAdjacentNumber, 0f), + ObjectAnimator.ofFloat( + mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1], + "alpha", + sAlphaForAdjacentNumber, + 0f), + ObjectAnimator.ofFloat( + mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1], + "alpha", + sAlphaForAdjacentNumber, + 0f), ObjectAnimator.ofFloat(mBackgroundView, "alpha", 1f, 0f)); - mFocusLossAnimator.setDuration(context.getResources().getInteger( - android.R.integer.config_shortAnimTime)); + mFocusLossAnimator.setDuration( + context.getResources().getInteger(android.R.integer.config_shortAnimTime)); mScrollAnimatorSet = new AnimatorSet(); - mScrollAnimatorSet.setDuration(context.getResources().getInteger( - R.integer.pin_number_scroll_duration)); - mScrollAnimatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Set mCurrent value when scroll animation is finished. - mCurrentValue = mNextValue; - updateText(); - mNumberViewHolder.setScrollY(mNumberViewHeight); - mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(sAlphaForAdjacentNumber); - mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(sAlphaForFocusedNumber); - mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(sAlphaForAdjacentNumber); - } - }); + mScrollAnimatorSet.setDuration( + context.getResources().getInteger(R.integer.pin_number_scroll_duration)); + mScrollAnimatorSet.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Set mCurrent value when scroll animation is finished. + mCurrentValue = mNextValue; + updateText(); + mNumberViewHolder.setScrollY(mNumberViewHeight); + mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha( + sAlphaForAdjacentNumber); + mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha( + sAlphaForFocusedNumber); + mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha( + sAlphaForAdjacentNumber); + } + }); } static void loadResources(Context context) { if (sFocusedNumberEnterAnimator == null) { TypedValue outValue = new TypedValue(); - context.getResources().getValue( - R.dimen.pin_alpha_for_focused_number, outValue, true); + context.getResources() + .getValue(R.dimen.pin_alpha_for_focused_number, outValue, true); sAlphaForFocusedNumber = outValue.getFloat(); - context.getResources().getValue( - R.dimen.pin_alpha_for_adjacent_number, outValue, true); + context.getResources() + .getValue(R.dimen.pin_alpha_for_adjacent_number, outValue, true); sAlphaForAdjacentNumber = outValue.getFloat(); - sFocusedNumberEnterAnimator = AnimatorInflater.loadAnimator(context, - R.animator.pin_focused_number_enter); - sFocusedNumberExitAnimator = AnimatorInflater.loadAnimator(context, - R.animator.pin_focused_number_exit); - sAdjacentNumberEnterAnimator = AnimatorInflater.loadAnimator(context, - R.animator.pin_adjacent_number_enter); - sAdjacentNumberExitAnimator = AnimatorInflater.loadAnimator(context, - R.animator.pin_adjacent_number_exit); + sFocusedNumberEnterAnimator = + AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_enter); + sFocusedNumberExitAnimator = + AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_exit); + sAdjacentNumberEnterAnimator = + AnimatorInflater.loadAnimator( + context, R.animator.pin_adjacent_number_enter); + sAdjacentNumberExitAnimator = + AnimatorInflater.loadAnimator(context, R.animator.pin_adjacent_number_exit); } } @@ -588,15 +618,16 @@ public class PinDialogFragment extends SafeDismissDialogFragment { void startScrollAnimation(boolean scrollUp) { mFocusGainAnimator.end(); mFocusLossAnimator.end(); - final ValueAnimator scrollAnimator = ValueAnimator.ofInt( - 0, scrollUp ? mNumberViewHeight : -mNumberViewHeight); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mNumberViewHolder.setScrollY(value + mNumberViewHeight); - } - }); + final ValueAnimator scrollAnimator = + ValueAnimator.ofInt(0, scrollUp ? mNumberViewHeight : -mNumberViewHeight); + scrollAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mNumberViewHolder.setScrollY(value + mNumberViewHeight); + } + }); scrollAnimator.setDuration( getResources().getInteger(R.integer.pin_number_scroll_duration)); @@ -612,9 +643,12 @@ public class PinDialogFragment extends SafeDismissDialogFragment { sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]); } - mScrollAnimatorSet.playTogether(scrollAnimator, - sAdjacentNumberExitAnimator, sFocusedNumberExitAnimator, - sFocusedNumberEnterAnimator, sAdjacentNumberEnterAnimator); + mScrollAnimatorSet.playTogether( + scrollAnimator, + sAdjacentNumberExitAnimator, + sFocusedNumberExitAnimator, + sFocusedNumberEnterAnimator, + sAdjacentNumberEnterAnimator); mScrollAnimatorSet.start(); } @@ -688,8 +722,10 @@ public class PinDialogFragment extends SafeDismissDialogFragment { // In order to show the text change animation, keep the text of // mNumberViews[CURRENT_NUMBER_VIEW_INDEX]. } else { - mNumberViews[i].setText(String.valueOf(adjustValueInValidRange( - mCurrentValue - CURRENT_NUMBER_VIEW_INDEX + i))); + mNumberViews[i].setText( + String.valueOf( + adjustValueInValidRange( + mCurrentValue - CURRENT_NUMBER_VIEW_INDEX + i))); } } } @@ -698,10 +734,11 @@ public class PinDialogFragment extends SafeDismissDialogFragment { private int adjustValueInValidRange(int value) { int interval = mMaxValue - mMinValue + 1; if (value < mMinValue - interval || value > mMaxValue + interval) { - throw new IllegalArgumentException("The value( " + value - + ") is too small or too big to adjust"); + throw new IllegalArgumentException( + "The value( " + value + ") is too small or too big to adjust"); } - return (value < mMinValue) ? value + interval + return (value < mMinValue) + ? value + interval : (value > mMaxValue) ? value - interval : value; } } @@ -714,8 +751,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment { /** * Called when {@link PinDialogFragment} is dismissed. * - * @param checked {@code true} if the pin code entered is checked to be correct, - * otherwise {@code false}. + * @param checked {@code true} if the pin code entered is checked to be correct, otherwise + * {@code false}. * @param type The dialog type regarding to what pin entering is for. * @param rating The target rating to unblock for. */ diff --git a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java index 1875f411..84fddbe3 100644 --- a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java +++ b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java @@ -29,17 +29,14 @@ import android.view.View; import android.widget.ListView; 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; -/** - * Displays the watch history - */ -public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment implements - LoaderManager.LoaderCallbacks { +/** Displays the watch history */ +public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment + implements LoaderManager.LoaderCallbacks { public static final String DIALOG_TAG = RecentlyWatchedDialogFragment.class.getSimpleName(); private static final String EMPTY_STRING = ""; @@ -55,47 +52,57 @@ public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment imp ((MainActivity) getActivity()).getChannelDataManager(); String[] from = { - TvContract.WatchedPrograms._ID, - TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, - TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, - TvContract.WatchedPrograms.COLUMN_TITLE }; + TvContract.WatchedPrograms._ID, + TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, + TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, + TvContract.WatchedPrograms.COLUMN_TITLE + }; int[] to = { - R.id.watched_program_id, - R.id.watched_program_channel_id, - R.id.watched_program_watch_time, - R.id.watched_program_title}; - mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_item_watched_program, null, - from, to, 0); - mAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { - @Override - public boolean setViewValue(View view, Cursor cursor, int columnIndex) { - String name = cursor.getColumnName(columnIndex); - if (TvContract.WatchedPrograms.COLUMN_CHANNEL_ID.equals(name)) { - long channelId = cursor.getLong(columnIndex); - ((TextView) view).setText(String.valueOf(channelId)); - // Update display number - String displayNumber; - Channel channel = dataChannelManager.getChannel(channelId); - if (channel == null) { - displayNumber = EMPTY_STRING; - } else { - displayNumber = channel.getDisplayNumber(); + R.id.watched_program_id, + R.id.watched_program_channel_id, + R.id.watched_program_watch_time, + R.id.watched_program_title + }; + mAdapter = + new SimpleCursorAdapter( + getActivity(), R.layout.list_item_watched_program, null, from, to, 0); + mAdapter.setViewBinder( + new SimpleCursorAdapter.ViewBinder() { + @Override + public boolean setViewValue(View view, Cursor cursor, int columnIndex) { + String name = cursor.getColumnName(columnIndex); + if (TvContract.WatchedPrograms.COLUMN_CHANNEL_ID.equals(name)) { + long channelId = cursor.getLong(columnIndex); + ((TextView) view).setText(String.valueOf(channelId)); + // Update display number + String displayNumber; + Channel channel = dataChannelManager.getChannel(channelId); + if (channel == null) { + displayNumber = EMPTY_STRING; + } else { + displayNumber = channel.getDisplayNumber(); + } + TextView displayNumberView = + ((TextView) + ((View) view.getParent()) + .findViewById( + R.id.watched_program_channel_display_number)); + displayNumberView.setText(displayNumber); + return true; + } else if (TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + .equals(name)) { + long time = cursor.getLong(columnIndex); + CharSequence timeString = + DateUtils.getRelativeTimeSpanString( + time, + System.currentTimeMillis(), + DateUtils.SECOND_IN_MILLIS); + ((TextView) view).setText(String.valueOf(timeString)); + return true; + } + return false; } - TextView displayNumberView = ((TextView) ((View) view.getParent()) - .findViewById(R.id.watched_program_channel_display_number)); - displayNumberView.setText(displayNumber); - return true; - } else if (TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS.equals( - name)) { - long time = cursor.getLong(columnIndex); - CharSequence timeString = DateUtils.getRelativeTimeSpanString(time, - System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS); - ((TextView) view).setText(String.valueOf(timeString)); - return true; - } - return false; - } - }); + }); ListView listView = new ListView(getActivity()); listView.setAdapter(mAdapter); @@ -121,12 +128,18 @@ public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment imp @Override public Loader onCreateLoader(int id, Bundle args) { String[] projection = { - TvContract.WatchedPrograms._ID, - TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, - TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, - TvContract.WatchedPrograms.COLUMN_TITLE }; - return new CursorLoader(getActivity(), TvContract.WatchedPrograms.CONTENT_URI, projection, - null, null, TvContract.WatchedPrograms._ID + " DESC"); + TvContract.WatchedPrograms._ID, + TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, + TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, + TvContract.WatchedPrograms.COLUMN_TITLE + }; + return new CursorLoader( + getActivity(), + TvContract.WatchedPrograms.CONTENT_URI, + projection, + null, + null, + TvContract.WatchedPrograms._ID + " DESC"); } @Override diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java index e3390b0a..18460cb6 100644 --- a/src/com/android/tv/dialog/SafeDismissDialogFragment.java +++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java @@ -18,17 +18,13 @@ package com.android.tv.dialog; import android.app.Activity; import android.app.DialogFragment; - import com.android.tv.MainActivity; import com.android.tv.TvApplication; import com.android.tv.analytics.HasTrackerLabel; import com.android.tv.analytics.Tracker; -/** - * Provides the safe dismiss feature regardless of the DialogFragment's life cycle. - */ -public abstract class SafeDismissDialogFragment extends DialogFragment - implements HasTrackerLabel { +/** Provides the safe dismiss feature regardless of the DialogFragment's life cycle. */ +public abstract class SafeDismissDialogFragment extends DialogFragment implements HasTrackerLabel { private MainActivity mActivity; private boolean mAttached = false; private boolean mDismissPending = false; @@ -69,9 +65,7 @@ public abstract class SafeDismissDialogFragment extends DialogFragment mTracker = null; } - /** - * Dismiss safely regardless of the DialogFragment's life cycle. - */ + /** Dismiss safely regardless of the DialogFragment's life cycle. */ @Override public void dismiss() { if (!mAttached) { diff --git a/src/com/android/tv/dialog/WebDialogFragment.java b/src/com/android/tv/dialog/WebDialogFragment.java index 171a256b..5266f760 100644 --- a/src/com/android/tv/dialog/WebDialogFragment.java +++ b/src/com/android/tv/dialog/WebDialogFragment.java @@ -28,9 +28,7 @@ import android.view.ViewGroup.LayoutParams; import android.webkit.WebView; import android.webkit.WebViewClient; -/** - * A DialogFragment that shows a web view. - */ +/** A DialogFragment that shows a web view. */ public class WebDialogFragment extends SafeDismissDialogFragment { private static final String TAG = "WebDialogFragment"; private static final String URL = "URL"; @@ -43,11 +41,11 @@ public class WebDialogFragment extends SafeDismissDialogFragment { /** * Create a new WebDialogFragment to show a particular web page. * - * @param url The URL of the content to show. + * @param url The URL of the content to show. * @param title Optional title for the dialog. */ - public static WebDialogFragment newInstance(String url, @Nullable String title, - String trackerLabel) { + public static WebDialogFragment newInstance( + String url, @Nullable String title, String trackerLabel) { WebDialogFragment f = new WebDialogFragment(); Bundle args = new Bundle(); args.putString(URL, url); @@ -62,15 +60,17 @@ public class WebDialogFragment extends SafeDismissDialogFragment { super.onCreate(savedInstanceState); String title = getArguments().getString(TITLE); mTrackerLabel = getArguments().getString(TRACKER_LABEL); - int style = TextUtils.isEmpty(title) ? DialogFragment.STYLE_NO_TITLE - : DialogFragment.STYLE_NORMAL; + int style = + TextUtils.isEmpty(title) + ? DialogFragment.STYLE_NO_TITLE + : DialogFragment.STYLE_NORMAL; setStyle(style, 0); } @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { String title = getArguments().getString(TITLE); getDialog().setTitle(title); diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index a8637449..342e4b21 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -23,7 +23,6 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.util.ArraySet; import android.util.Log; - import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.dvr.data.RecordedProgram; @@ -31,7 +30,6 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Clock; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -42,14 +40,12 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * Base implementation of @{link DataManagerInternal}. - */ +/** Base implementation of @{link DataManagerInternal}. */ @MainThread @TargetApi(Build.VERSION_CODES.N) public abstract class BaseDvrDataManager implements WritableDvrDataManager { - private final static String TAG = "BaseDvrDataManager"; - private final static boolean DEBUG = false; + private static final String TAG = "BaseDvrDataManager"; + private static final boolean DEBUG = false; protected final Clock mClock; private final Set mOnDvrScheduleLoadFinishedListeners = @@ -129,8 +125,8 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } /** - * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()} - * for each listener. + * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()} for each + * listener. */ protected final void notifyRecordedProgramLoadFinished() { for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) { @@ -139,10 +135,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link RecordedProgramListener#onRecordedProgramsAdded} - * for each listener. - */ + /** Calls {@link RecordedProgramListener#onRecordedProgramsAdded} for each listener. */ protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) { for (RecordedProgramListener l : mRecordedProgramListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms)); @@ -150,10 +143,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link RecordedProgramListener#onRecordedProgramsChanged} - * for each listener. - */ + /** Calls {@link RecordedProgramListener#onRecordedProgramsChanged} for each listener. */ protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) { for (RecordedProgramListener l : mRecordedProgramListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms)); @@ -161,10 +151,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link RecordedProgramListener#onRecordedProgramsRemoved} - * for each listener. - */ + /** Calls {@link RecordedProgramListener#onRecordedProgramsRemoved} for each listener. */ protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { for (RecordedProgramListener l : mRecordedProgramListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms)); @@ -172,10 +159,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link SeriesRecordingListener#onSeriesRecordingAdded} - * for each listener. - */ + /** Calls {@link SeriesRecordingListener#onSeriesRecordingAdded} for each listener. */ protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) { for (SeriesRecordingListener l : mSeriesRecordingListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(seriesRecordings)); @@ -183,10 +167,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved} - * for each listener. - */ + /** Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved} for each listener. */ protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) { for (SeriesRecordingListener l : mSeriesRecordingListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings)); @@ -194,11 +175,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls - * {@link SeriesRecordingListener#onSeriesRecordingChanged} - * for each listener. - */ + /** Calls {@link SeriesRecordingListener#onSeriesRecordingChanged} for each listener. */ protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) { for (SeriesRecordingListener l : mSeriesRecordingListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings)); @@ -206,10 +183,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} - * for each listener. - */ + /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */ protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) { for (ScheduledRecordingListener l : mScheduledRecordingListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(scheduledRecording)); @@ -217,10 +191,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } } - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} - * for each listener. - */ + /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */ protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) { for (ScheduledRecordingListener l : mScheduledRecordingListeners) { if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording)); @@ -229,9 +200,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } /** - * Calls - * {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} - * for each listener. + * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener. */ protected final void notifyScheduledRecordingStatusChanged( ScheduledRecording... scheduledRecording) { @@ -257,28 +226,29 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { @Override public List getAvailableScheduledRecordings() { - return filterEndTimeIsPast(getRecordingsWithState( - ScheduledRecording.STATE_RECORDING_IN_PROGRESS, - ScheduledRecording.STATE_RECORDING_NOT_STARTED)); + return filterEndTimeIsPast( + getRecordingsWithState( + ScheduledRecording.STATE_RECORDING_IN_PROGRESS, + ScheduledRecording.STATE_RECORDING_NOT_STARTED)); } @Override public List getStartedRecordings() { - return filterEndTimeIsPast(getRecordingsWithState( - ScheduledRecording.STATE_RECORDING_IN_PROGRESS)); + return filterEndTimeIsPast( + getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)); } @Override public List getNonStartedScheduledRecordings() { - return filterEndTimeIsPast(getRecordingsWithState( - ScheduledRecording.STATE_RECORDING_NOT_STARTED)); + return filterEndTimeIsPast( + getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)); } @Override public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) { if (scheduledRecording.getState() != newState) { - updateScheduledRecording(ScheduledRecording.buildFrom(scheduledRecording) - .setState(newState).build()); + updateScheduledRecording( + ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build()); } } @@ -300,9 +270,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { return mDeletedScheduleMap; } - /** - * Returns the schedules whose state is contained by states. - */ + /** Returns the schedules whose state is contained by states. */ protected abstract List getRecordingsWithState(int... states); @Override @@ -357,5 +325,5 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } @Override - public void forgetStorage(String inputId) { } + public void forgetStorage(String inputId) {} } diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java index 6d400b82..4ec0acfc 100644 --- a/src/com/android/tv/dvr/DvrDataManager.java +++ b/src/com/android/tv/dvr/DvrDataManager.java @@ -20,48 +20,36 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Range; - import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.data.SeriesRecording; - import java.util.Collection; import java.util.List; -/** - * Read only data manager. - */ +/** Read only data manager. */ @MainThread public interface DvrDataManager { long NEXT_START_TIME_NOT_FOUND = -1; boolean isInitialized(); - /** - * Returns {@code true} if the schedules were loaded, otherwise {@code false}. - */ + /** Returns {@code true} if the schedules were loaded, otherwise {@code false}. */ boolean isDvrScheduleLoadFinished(); - /** - * Returns {@code true} if the recorded programs were loaded, otherwise {@code false}. - */ + /** Returns {@code true} if the recorded programs were loaded, otherwise {@code false}. */ boolean isRecordedProgramLoadFinished(); - /** - * Returns past recordings. - */ + /** Returns past recordings. */ List getRecordedPrograms(); - /** - * Returns past recorded programs in the given series. - */ + /** Returns past recorded programs in the given series. */ List getRecordedPrograms(long seriesRecordingId); /** * Returns all {@link ScheduledRecording} regardless of state. - *

- * The result doesn't contain the deleted schedules. + * + *

The result doesn't contain the deleted schedules. */ List getAllScheduledRecordings(); @@ -71,29 +59,21 @@ public interface DvrDataManager { */ List getAvailableScheduledRecordings(); - /** - * Returns started recordings that expired. - */ + /** Returns started recordings that expired. */ List getStartedRecordings(); - /** - * Returns scheduled but not started recordings that have not expired. - */ + /** Returns scheduled but not started recordings that have not expired. */ List getNonStartedScheduledRecordings(); - /** - * Returns series recordings. - */ + /** Returns series recordings. */ List getSeriesRecordings(); - /** - * Returns series recordings from the given input. - */ + /** Returns series recordings from the given input. */ List getSeriesRecordings(String inputId); /** - * Returns the next start time after {@code time} or {@link #NEXT_START_TIME_NOT_FOUND} - * if none is found. + * Returns the next start time after {@code time} or {@link #NEXT_START_TIME_NOT_FOUND} if none + * is found. * * @param time time milliseconds */ @@ -103,73 +83,48 @@ public interface DvrDataManager { * Returns a list of the schedules with a overlap with the given time period inclusive and with * the given state. * - *

A recording overlaps with a period when - * {@code recording.getStartTime() <= period.getUpper() && - * recording.getEndTime() >= period.getLower()}. + *

A recording overlaps with a period when {@code recording.getStartTime() <= + * period.getUpper() && recording.getEndTime() >= period.getLower()}. * * @param period a time period in milliseconds. * @param state the state of the schedule. */ List getScheduledRecordings(Range period, @RecordingState int state); - /** - * Returns a list of the schedules in the given series. - */ + /** Returns a list of the schedules in the given series. */ List getScheduledRecordings(long seriesRecordingId); - /** - * Returns a list of the schedules from the given input. - */ + /** Returns a list of the schedules from the given input. */ List getScheduledRecordings(String inputId); - /** - * Add a {@link OnDvrScheduleLoadFinishedListener}. - */ + /** Add a {@link OnDvrScheduleLoadFinishedListener}. */ void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener); - /** - * Remove a {@link OnDvrScheduleLoadFinishedListener}. - */ + /** Remove a {@link OnDvrScheduleLoadFinishedListener}. */ void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener); - /** - * Add a {@link OnRecordedProgramLoadFinishedListener}. - */ + /** Add a {@link OnRecordedProgramLoadFinishedListener}. */ void addRecordedProgramLoadFinishedListener(OnRecordedProgramLoadFinishedListener listener); - /** - * Remove a {@link OnRecordedProgramLoadFinishedListener}. - */ + /** Remove a {@link OnRecordedProgramLoadFinishedListener}. */ void removeRecordedProgramLoadFinishedListener(OnRecordedProgramLoadFinishedListener listener); - /** - * Add a {@link ScheduledRecordingListener}. - */ + /** Add a {@link ScheduledRecordingListener}. */ void addScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener); - /** - * Remove a {@link ScheduledRecordingListener}. - */ + /** Remove a {@link ScheduledRecordingListener}. */ void removeScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener); - /** - * Add a {@link RecordedProgramListener}. - */ + /** Add a {@link RecordedProgramListener}. */ void addRecordedProgramListener(RecordedProgramListener listener); - /** - * Remove a {@link RecordedProgramListener}. - */ + /** Remove a {@link RecordedProgramListener}. */ void removeRecordedProgramListener(RecordedProgramListener listener); - /** - * Add a {@link ScheduledRecordingListener}. - */ + /** Add a {@link ScheduledRecordingListener}. */ void addSeriesRecordingListener(SeriesRecordingListener seriesRecordingListener); - /** - * Remove a {@link ScheduledRecordingListener}. - */ + /** Remove a {@link ScheduledRecordingListener}. */ void removeSeriesRecordingListener(SeriesRecordingListener seriesRecordingListener); /** @@ -178,65 +133,47 @@ public interface DvrDataManager { @Nullable ScheduledRecording getScheduledRecording(long recordingId); - /** - * Returns the scheduled recording program with the given programId or null if is not found. - */ + /** Returns the scheduled recording program with the given programId or null if is not found. */ @Nullable ScheduledRecording getScheduledRecordingForProgramId(long programId); - /** - * Returns the recorded program with the given recordingId or null if is not found. - */ + /** Returns the recorded program with the given recordingId or null if is not found. */ @Nullable RecordedProgram getRecordedProgram(long recordingId); - /** - * Returns the series recording with the given seriesId or null if is not found. - */ + /** Returns the series recording with the given seriesId or null if is not found. */ @Nullable SeriesRecording getSeriesRecording(long seriesRecordingId); - /** - * Returns the series recording with the given series ID or {@code null} if not found. - */ + /** Returns the series recording with the given series ID or {@code null} if not found. */ @Nullable SeriesRecording getSeriesRecording(String seriesId); - /** - * Returns the schedules which are marked deleted. - */ + /** Returns the schedules which are marked deleted. */ Collection getDeletedSchedules(); - /** - * Returns the program IDs which is not allowed to make a schedule automatically. - */ + /** Returns the program IDs which is not allowed to make a schedule automatically. */ @NonNull Collection getDisallowedProgramIds(); /** - * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains - * any available schedules or recorded programs, and it's status is - * {@link SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings. + * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains any + * available schedules or recorded programs, and it's status is {@link + * SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings. */ void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds); - /** - * Listens for the DVR schedules loading finished. - */ + /** Listens for the DVR schedules loading finished. */ interface OnDvrScheduleLoadFinishedListener { void onDvrScheduleLoadFinished(); } - /** - * Listens for the recorded program loading finished. - */ + /** Listens for the recorded program loading finished. */ interface OnRecordedProgramLoadFinishedListener { void onRecordedProgramLoadFinished(); } - /** - * Listens for changes to {@link ScheduledRecording}s. - */ + /** Listens for changes to {@link ScheduledRecording}s. */ interface ScheduledRecordingListener { void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings); @@ -250,9 +187,7 @@ public interface DvrDataManager { void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings); } - /** - * Listens for changes to {@link SeriesRecording}s. - */ + /** Listens for changes to {@link SeriesRecording}s. */ interface SeriesRecordingListener { void onSeriesRecordingAdded(SeriesRecording... seriesRecordings); @@ -261,9 +196,7 @@ public interface DvrDataManager { void onSeriesRecordingChanged(SeriesRecording... seriesRecordings); } - /** - * Listens for changes to {@link RecordedProgram}s. - */ + /** Listens for changes to {@link RecordedProgram}s. */ interface RecordedProgramListener { void onRecordedProgramsAdded(RecordedProgram... recordedPrograms); diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index 6094ca72..17ea63a0 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -38,7 +38,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Range; - import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener; @@ -64,7 +63,6 @@ import com.android.tv.util.Filter; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.TvUriMatcher; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -74,9 +72,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.Set; -/** - * DVR Data manager to handle recordings and schedules. - */ +/** DVR Data manager to handle recordings and schedules. */ @MainThread @TargetApi(Build.VERSION_CODES.N) public class DvrDataManagerImpl extends BaseDvrDataManager { @@ -98,21 +94,21 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private final HashMap mSeriesRecordingsForRemovedInput = new HashMap<>(); private final Context mContext; - private final ContentObserver mContentObserver = new ContentObserver(new Handler( - Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } - @Override - public void onChange(boolean selfChange, final @Nullable Uri uri) { - RecordedProgramsQueryTask task = new RecordedProgramsQueryTask( - mContext.getContentResolver(), uri); - task.executeOnDbThread(); - mPendingTasks.add(task); - } - }; + @Override + public void onChange(boolean selfChange, final @Nullable Uri uri) { + RecordedProgramsQueryTask task = + new RecordedProgramsQueryTask(mContext.getContentResolver(), uri); + task.executeOnDbThread(); + mPendingTasks.add(task); + } + }; private boolean mDvrLoadFinished; private boolean mRecordedProgramLoadFinished; @@ -120,23 +116,24 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private DvrDbSync mDbSync; private DvrStorageStatusManager mStorageStatusManager; - private final TvInputCallback mInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - if (DEBUG) Log.d(TAG, "onInputAdded " + inputId); - if (!isInputAvailable(inputId)) { - if (DEBUG) Log.d(TAG, "Not available for recording"); - return; - } - unhideInput(inputId); - } + private final TvInputCallback mInputCallback = + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + if (DEBUG) Log.d(TAG, "onInputAdded " + inputId); + if (!isInputAvailable(inputId)) { + if (DEBUG) Log.d(TAG, "Not available for recording"); + return; + } + unhideInput(inputId); + } - @Override - public void onInputRemoved(String inputId) { - if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); - hideInput(inputId); - } - }; + @Override + public void onInputRemoved(String inputId) { + if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); + hideInput(inputId); + } + }; private final OnStorageMountChangedListener mStorageMountChangedListener = new OnStorageMountChangedListener() { @@ -154,8 +151,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } }; - private static List moveElements(HashMap from, HashMap to, - Filter filter) { + private static List moveElements( + HashMap from, HashMap to, Filter filter) { List moved = new ArrayList<>(); Iterator> iter = from.entrySet().iterator(); while (iter.hasNext()) { @@ -179,112 +176,123 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { public void start() { mInputManager.addCallback(mInputCallback); mStorageStatusManager.addListener(mStorageMountChangedListener); - AsyncDvrQuerySeriesRecordingTask dvrQuerySeriesRecordingTask - = new AsyncDvrQuerySeriesRecordingTask(mContext) { - @Override - protected void onCancelled(List seriesRecordings) { - mPendingTasks.remove(this); - } + AsyncDvrQuerySeriesRecordingTask dvrQuerySeriesRecordingTask = + new AsyncDvrQuerySeriesRecordingTask(mContext) { + @Override + protected void onCancelled(List seriesRecordings) { + mPendingTasks.remove(this); + } - @Override - protected void onPostExecute(List seriesRecordings) { - mPendingTasks.remove(this); - long maxId = 0; - HashSet seriesIds = new HashSet<>(); - for (SeriesRecording r : seriesRecordings) { - if (SoftPreconditions.checkState(!seriesIds.contains(r.getSeriesId()), TAG, - "Skip loading series recording with duplicate series ID: " + r)) { - seriesIds.add(r.getSeriesId()); - if (isInputAvailable(r.getInputId())) { - mSeriesRecordings.put(r.getId(), r); - mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); - } else { - mSeriesRecordingsForRemovedInput.put(r.getId(), r); + @Override + protected void onPostExecute(List seriesRecordings) { + mPendingTasks.remove(this); + long maxId = 0; + HashSet seriesIds = new HashSet<>(); + for (SeriesRecording r : seriesRecordings) { + if (SoftPreconditions.checkState( + !seriesIds.contains(r.getSeriesId()), + TAG, + "Skip loading series recording with duplicate series ID: " + + r)) { + seriesIds.add(r.getSeriesId()); + if (isInputAvailable(r.getInputId())) { + mSeriesRecordings.put(r.getId(), r); + mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); + } else { + mSeriesRecordingsForRemovedInput.put(r.getId(), r); + } + } + if (maxId < r.getId()) { + maxId = r.getId(); + } } + IdGenerator.SERIES_RECORDING.setMaxId(maxId); } - if (maxId < r.getId()) { - maxId = r.getId(); - } - } - IdGenerator.SERIES_RECORDING.setMaxId(maxId); - } - }; + }; dvrQuerySeriesRecordingTask.executeOnDbThread(); mPendingTasks.add(dvrQuerySeriesRecordingTask); - AsyncDvrQueryScheduleTask dvrQueryScheduleTask - = new AsyncDvrQueryScheduleTask(mContext) { - @Override - protected void onCancelled(List scheduledRecordings) { - mPendingTasks.remove(this); - } + AsyncDvrQueryScheduleTask dvrQueryScheduleTask = + new AsyncDvrQueryScheduleTask(mContext) { + @Override + protected void onCancelled(List scheduledRecordings) { + mPendingTasks.remove(this); + } - @SuppressLint("SwitchIntDef") - @Override - protected void onPostExecute(List result) { - mPendingTasks.remove(this); - long maxId = 0; - List seriesRecordingsToAdd = new ArrayList<>(); - List toUpdate = new ArrayList<>(); - List toDelete = new ArrayList<>(); - for (ScheduledRecording r : result) { - if (!isInputAvailable(r.getInputId())) { - mScheduledRecordingsForRemovedInput.put(r.getId(), r); - } else if (r.getState() == ScheduledRecording.STATE_RECORDING_DELETED) { - getDeletedScheduleMap().put(r.getProgramId(), r); - } else { - mScheduledRecordings.put(r.getId(), r); - if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) { - mProgramId2ScheduledRecordings.put(r.getProgramId(), r); - } - // Adjust the state of the schedules before DB loading is finished. - switch (r.getState()) { - case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: - if (r.getEndTimeMs() <= mClock.currentTimeMillis()) { - toUpdate.add(ScheduledRecording.buildFrom(r) - .setState(ScheduledRecording.STATE_RECORDING_FAILED) - .build()); - } else { - toUpdate.add(ScheduledRecording.buildFrom(r) - .setState( - ScheduledRecording.STATE_RECORDING_NOT_STARTED) - .build()); + @SuppressLint("SwitchIntDef") + @Override + protected void onPostExecute(List result) { + mPendingTasks.remove(this); + long maxId = 0; + List seriesRecordingsToAdd = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + List toDelete = new ArrayList<>(); + for (ScheduledRecording r : result) { + if (!isInputAvailable(r.getInputId())) { + mScheduledRecordingsForRemovedInput.put(r.getId(), r); + } else if (r.getState() == ScheduledRecording.STATE_RECORDING_DELETED) { + getDeletedScheduleMap().put(r.getProgramId(), r); + } else { + mScheduledRecordings.put(r.getId(), r); + if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) { + mProgramId2ScheduledRecordings.put(r.getProgramId(), r); } - break; - case ScheduledRecording.STATE_RECORDING_NOT_STARTED: - if (r.getEndTimeMs() <= mClock.currentTimeMillis()) { - toUpdate.add(ScheduledRecording.buildFrom(r) - .setState(ScheduledRecording.STATE_RECORDING_FAILED) - .build()); + // Adjust the state of the schedules before DB loading is finished. + switch (r.getState()) { + case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: + if (r.getEndTimeMs() <= mClock.currentTimeMillis()) { + toUpdate.add( + ScheduledRecording.buildFrom(r) + .setState( + ScheduledRecording + .STATE_RECORDING_FAILED) + .build()); + } else { + toUpdate.add( + ScheduledRecording.buildFrom(r) + .setState( + ScheduledRecording + .STATE_RECORDING_NOT_STARTED) + .build()); + } + break; + case ScheduledRecording.STATE_RECORDING_NOT_STARTED: + if (r.getEndTimeMs() <= mClock.currentTimeMillis()) { + toUpdate.add( + ScheduledRecording.buildFrom(r) + .setState( + ScheduledRecording + .STATE_RECORDING_FAILED) + .build()); + } + break; + case ScheduledRecording.STATE_RECORDING_CANCELED: + toDelete.add(r); + break; } - break; - case ScheduledRecording.STATE_RECORDING_CANCELED: - toDelete.add(r); - break; + } + if (maxId < r.getId()) { + maxId = r.getId(); + } + } + if (!toUpdate.isEmpty()) { + updateScheduledRecording(ScheduledRecording.toArray(toUpdate)); + } + if (!toDelete.isEmpty()) { + removeScheduledRecording(ScheduledRecording.toArray(toDelete)); + } + IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId); + if (mRecordedProgramLoadFinished) { + validateSeriesRecordings(); + } + mDvrLoadFinished = true; + notifyDvrScheduleLoadFinished(); + if (isInitialized()) { + mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this); + mDbSync.start(); + SeriesRecordingScheduler.getInstance(mContext).start(); } } - if (maxId < r.getId()) { - maxId = r.getId(); - } - } - if (!toUpdate.isEmpty()) { - updateScheduledRecording(ScheduledRecording.toArray(toUpdate)); - } - if (!toDelete.isEmpty()) { - removeScheduledRecording(ScheduledRecording.toArray(toDelete)); - } - IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId); - if (mRecordedProgramLoadFinished) { - validateSeriesRecordings(); - } - mDvrLoadFinished = true; - notifyDvrScheduleLoadFinished(); - if (isInitialized()) { - mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this); - mDbSync.start(); - SeriesRecordingScheduler.getInstance(mContext).start(); - } - } - }; + }; dvrQueryScheduleTask.executeOnDbThread(); mPendingTasks.add(dvrQueryScheduleTask); RecordedProgramsQueryTask mRecordedProgramQueryTask = @@ -341,8 +349,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { mRecordedProgramsForRemovedInput.clear(); notifyRecordedProgramsRemoved(RecordedProgram.toArray(oldRecordedPrograms)); } else { - HashMap oldRecordedPrograms - = new HashMap<>(mRecordedPrograms); + HashMap oldRecordedPrograms = + new HashMap<>(mRecordedPrograms); mRecordedPrograms.clear(); mRecordedProgramsForRemovedInput.clear(); List addedRecordedPrograms = new ArrayList<>(); @@ -492,7 +500,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } @VisibleForTesting - static long getNextStartTimeAfter(List scheduledRecordings, long startTime) { + static long getNextStartTimeAfter( + List scheduledRecordings, long startTime) { int start = 0; int end = scheduledRecordings.size() - 1; while (start <= end) { @@ -503,13 +512,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { end = mid - 1; } } - return start < scheduledRecordings.size() ? scheduledRecordings.get(start).getStartTimeMs() + return start < scheduledRecordings.size() + ? scheduledRecordings.get(start).getStartTimeMs() : NEXT_START_TIME_NOT_FOUND; } @Override - public List getScheduledRecordings(Range period, - @RecordingState int state) { + public List getScheduledRecordings( + Range period, @RecordingState int state) { List result = new ArrayList<>(); for (ScheduledRecording r : mScheduledRecordings.values()) { if (r.isOverLapping(period) && r.getState() == state) { @@ -595,8 +605,12 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { r.setId(IdGenerator.SERIES_RECORDING.newId()); mSeriesRecordings.put(r.getId(), r); SeriesRecording previousSeries = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); - SoftPreconditions.checkArgument(previousSeries == null, TAG, "Attempt to add series" - + " recording with the duplicate series ID: " + r.getSeriesId()); + SoftPreconditions.checkArgument( + previousSeries == null, + TAG, + "Attempt to add series" + + " recording with the duplicate series ID: " + + r.getSeriesId()); } if (mDvrLoadFinished) { notifySeriesRecordingAdded(seriesRecordings); @@ -620,20 +634,23 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { mProgramId2ScheduledRecordings.remove(r.getProgramId()); if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET && (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { seriesRecordingIdsToCheck.add(r.getSeriesRecordingId()); } boolean isScheduleForRemovedInput = mScheduledRecordingsForRemovedInput.remove(r.getProgramId()) != null; // If it belongs to the series recording and it's not started yet, just mark delete // instead of deleting it. - if (!isScheduleForRemovedInput && !forceRemove + if (!isScheduleForRemovedInput + && !forceRemove && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET && (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || r.getState() == ScheduledRecording.STATE_RECORDING_CANCELED)) { + || r.getState() == ScheduledRecording.STATE_RECORDING_CANCELED)) { SoftPreconditions.checkState(r.getProgramId() != ScheduledRecording.ID_NOT_SET); - ScheduledRecording deleted = ScheduledRecording.buildFrom(r) - .setState(ScheduledRecording.STATE_RECORDING_DELETED).build(); + ScheduledRecording deleted = + ScheduledRecording.buildFrom(r) + .setState(ScheduledRecording.STATE_RECORDING_DELETED) + .build(); getDeletedScheduleMap().put(deleted.getProgramId(), deleted); schedulesNotToDelete.add(deleted); } else { @@ -655,12 +672,12 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } if (!schedulesToDelete.isEmpty()) { - new AsyncDeleteScheduleTask(mContext).executeOnDbThread( - ScheduledRecording.toArray(schedulesToDelete)); + new AsyncDeleteScheduleTask(mContext) + .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete)); } if (!schedulesNotToDelete.isEmpty()) { - new AsyncUpdateScheduleTask(mContext).executeOnDbThread( - ScheduledRecording.toArray(schedulesNotToDelete)); + new AsyncUpdateScheduleTask(mContext) + .executeOnDbThread(ScheduledRecording.toArray(schedulesNotToDelete)); } } @@ -680,8 +697,10 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { toDelete.add(r); } else { - toUpdate.add(ScheduledRecording.buildFrom(r) - .setSeriesRecordingId(SeriesRecording.ID_NOT_SET).build()); + toUpdate.add( + ScheduledRecording.buildFrom(r) + .setSeriesRecordingId(SeriesRecording.ID_NOT_SET) + .build()); } } } @@ -709,7 +728,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { List toUpdate = new ArrayList<>(); Set seriesRecordingIdsToCheck = new HashSet<>(); for (ScheduledRecording r : schedules) { - if (!SoftPreconditions.checkState(mScheduledRecordings.containsKey(r.getId()), TAG, + if (!SoftPreconditions.checkState( + mScheduledRecordings.containsKey(r.getId()), + TAG, "Recording not found for: " + r)) { continue; } @@ -720,8 +741,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { long programId = r.getProgramId(); if (oldScheduledRecording.getProgramId() != programId && oldScheduledRecording.getProgramId() != ScheduledRecording.ID_NOT_SET) { - ScheduledRecording oldValueForProgramId = mProgramId2ScheduledRecordings - .get(oldScheduledRecording.getProgramId()); + ScheduledRecording oldValueForProgramId = + mProgramId2ScheduledRecordings.get(oldScheduledRecording.getProgramId()); if (oldValueForProgramId.getId() == r.getId()) { // Only remove the old ScheduledRecording if it has the same ID as the new one. mProgramId2ScheduledRecordings.remove(oldScheduledRecording.getProgramId()); @@ -755,14 +776,16 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { @Override public void updateSeriesRecording(final SeriesRecording... seriesRecordings) { for (SeriesRecording r : seriesRecordings) { - if (!SoftPreconditions.checkArgument(mSeriesRecordings.containsKey(r.getId()), TAG, + if (!SoftPreconditions.checkArgument( + mSeriesRecordings.containsKey(r.getId()), + TAG, "Non Existing Series ID: " + r)) { continue; } SeriesRecording old1 = mSeriesRecordings.put(r.getId(), r); SeriesRecording old2 = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); - SoftPreconditions.checkArgument(old1.equals(old2), TAG, "Series ID cannot be" - + " updated: " + r); + SoftPreconditions.checkArgument( + old1.equals(old2), TAG, "Series ID cannot be" + " updated: " + r); } if (mDvrLoadFinished) { notifySeriesRecordingChanged(seriesRecordings); @@ -784,8 +807,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } if (!schedulesToDelete.isEmpty()) { - new AsyncDeleteScheduleTask(mContext).executeOnDbThread( - ScheduledRecording.toArray(schedulesToDelete)); + new AsyncDeleteScheduleTask(mContext) + .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete)); } } @@ -805,15 +828,17 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } if (!schedulesToDelete.isEmpty()) { - new AsyncDeleteScheduleTask(mContext).executeOnDbThread( - ScheduledRecording.toArray(schedulesToDelete)); + new AsyncDeleteScheduleTask(mContext) + .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete)); } } private void unhideInput(String inputId) { if (DEBUG) Log.d(TAG, "unhideInput " + inputId); List movedSchedules = - moveElements(mScheduledRecordingsForRemovedInput, mScheduledRecordings, + moveElements( + mScheduledRecordingsForRemovedInput, + mScheduledRecordings, new Filter() { @Override public boolean filter(ScheduledRecording r) { @@ -821,7 +846,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } }); List movedRecordedPrograms = - moveElements(mRecordedProgramsForRemovedInput, mRecordedPrograms, + moveElements( + mRecordedProgramsForRemovedInput, + mRecordedPrograms, new Filter() { @Override public boolean filter(RecordedProgram r) { @@ -830,7 +857,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { }); List removedSeriesRecordings = new ArrayList<>(); List movedSeriesRecordings = - moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings, + moveElements( + mSeriesRecordingsForRemovedInput, + mSeriesRecordings, new Filter() { @Override public boolean filter(SeriesRecording r) { @@ -856,8 +885,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { for (SeriesRecording r : removedSeriesRecordings) { mSeriesRecordingsForRemovedInput.remove(r.getId()); } - new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread( - SeriesRecording.toArray(removedSeriesRecordings)); + new AsyncDeleteSeriesRecordingTask(mContext) + .executeOnDbThread(SeriesRecording.toArray(removedSeriesRecordings)); // Notify after all the data are moved. if (!movedSchedules.isEmpty()) { notifyScheduledRecordingAdded(ScheduledRecording.toArray(movedSchedules)); @@ -873,7 +902,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private void hideInput(String inputId) { if (DEBUG) Log.d(TAG, "hideInput " + inputId); List movedSchedules = - moveElements(mScheduledRecordings, mScheduledRecordingsForRemovedInput, + moveElements( + mScheduledRecordings, + mScheduledRecordingsForRemovedInput, new Filter() { @Override public boolean filter(ScheduledRecording r) { @@ -881,7 +912,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } }); List movedSeriesRecordings = - moveElements(mSeriesRecordings, mSeriesRecordingsForRemovedInput, + moveElements( + mSeriesRecordings, + mSeriesRecordingsForRemovedInput, new Filter() { @Override public boolean filter(SeriesRecording r) { @@ -889,7 +922,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } }); List movedRecordedPrograms = - moveElements(mRecordedPrograms, mRecordedProgramsForRemovedInput, + moveElements( + mRecordedPrograms, + mRecordedProgramsForRemovedInput, new Filter() { @Override public boolean filter(RecordedProgram r) { @@ -931,7 +966,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { public void forgetStorage(String inputId) { List schedulesToDelete = new ArrayList<>(); for (Iterator i = - mScheduledRecordingsForRemovedInput.values().iterator(); i.hasNext(); ) { + mScheduledRecordingsForRemovedInput.values().iterator(); + i.hasNext(); ) { ScheduledRecording r = i.next(); if (inputId.equals(r.getInputId())) { schedulesToDelete.add(r); @@ -939,32 +975,34 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { } } List seriesRecordingsToDelete = new ArrayList<>(); - for (Iterator i = - mSeriesRecordingsForRemovedInput.values().iterator(); i.hasNext(); ) { + for (Iterator i = mSeriesRecordingsForRemovedInput.values().iterator(); + i.hasNext(); ) { SeriesRecording r = i.next(); if (inputId.equals(r.getInputId())) { seriesRecordingsToDelete.add(r); i.remove(); } } - for (Iterator i = - mRecordedProgramsForRemovedInput.values().iterator(); i.hasNext(); ) { + for (Iterator i = mRecordedProgramsForRemovedInput.values().iterator(); + i.hasNext(); ) { if (inputId.equals(i.next().getInputId())) { i.remove(); } } - new AsyncDeleteScheduleTask(mContext).executeOnDbThread( - ScheduledRecording.toArray(schedulesToDelete)); - new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread( - SeriesRecording.toArray(seriesRecordingsToDelete)); + new AsyncDeleteScheduleTask(mContext) + .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete)); + new AsyncDeleteSeriesRecordingTask(mContext) + .executeOnDbThread(SeriesRecording.toArray(seriesRecordingsToDelete)); new AsyncDbTask() { @Override protected Void doInBackground(Void... params) { ContentResolver resolver = mContext.getContentResolver(); - String args[] = { inputId }; + String args[] = {inputId}; try { - resolver.delete(RecordedPrograms.CONTENT_URI, - RecordedPrograms.COLUMN_INPUT_ID + " = ?", args); + resolver.delete( + RecordedPrograms.CONTENT_URI, + RecordedPrograms.COLUMN_INPUT_ID + " = ?", + args); } catch (SQLiteException e) { Log.e(TAG, "Failed to delete recorded programs for inputId: " + inputId, e); } diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java index d222003d..50751d95 100644 --- a/src/com/android/tv/dvr/DvrManager.java +++ b/src/com/android/tv/dvr/DvrManager.java @@ -36,7 +36,6 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -51,7 +50,6 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.Utils; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -103,37 +101,41 @@ public class DvrManager { }); } if (!mScheduleManager.isInitialized()) { - mScheduleManager.addOnInitializeListener(new OnInitializeListener() { + mScheduleManager.addOnInitializeListener( + new OnInitializeListener() { + @Override + public void onInitialize() { + mScheduleManager.removeOnInitializeListener(this); + if (mDataManager.isInitialized() + && mScheduleManager.isInitialized()) { + createSeriesRecordingsForRecordedProgramsIfNeeded( + mDataManager.getRecordedPrograms()); + } + } + }); + } + } + mDataManager.addRecordedProgramListener( + new RecordedProgramListener() { @Override - public void onInitialize() { - mScheduleManager.removeOnInitializeListener(this); - if (mDataManager.isInitialized() && mScheduleManager.isInitialized()) { - createSeriesRecordingsForRecordedProgramsIfNeeded( - mDataManager.getRecordedPrograms()); + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + if (!mDataManager.isInitialized() || !mScheduleManager.isInitialized()) { + return; + } + for (RecordedProgram recordedProgram : recordedPrograms) { + createSeriesRecordingForRecordedProgramIfNeeded(recordedProgram); } } - }); - } - } - mDataManager.addRecordedProgramListener(new RecordedProgramListener() { - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - if (!mDataManager.isInitialized() || !mScheduleManager.isInitialized()) { - return; - } - for (RecordedProgram recordedProgram : recordedPrograms) { - createSeriesRecordingForRecordedProgramIfNeeded(recordedProgram); - } - } - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { } + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {} - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - // Removing series recording is handled in the SeriesRecordingDetailsFragment. - } - }); + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + // Removing series recording is handled in the + // SeriesRecordingDetailsFragment. + } + }); } private void createSeriesRecordingsForRecordedProgramsIfNeeded( @@ -153,33 +155,38 @@ public class DvrManager { } } - /** - * Schedules a recording for {@code program}. - */ + /** Schedules a recording for {@code program}. */ public ScheduledRecording addSchedule(Program program) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return null; } SeriesRecording seriesRecording = getSeriesRecording(program); - return addSchedule(program, seriesRecording == null - ? mScheduleManager.suggestNewPriority() - : seriesRecording.getPriority()); + return addSchedule( + program, + seriesRecording == null + ? mScheduleManager.suggestNewPriority() + : seriesRecording.getPriority()); } /** - * Schedules a recording for {@code program} with the highest priority so that the schedule - * can be recorded. + * Schedules a recording for {@code program} with the highest priority so that the schedule can + * be recorded. */ public ScheduledRecording addScheduleWithHighestPriority(Program program) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return null; } SeriesRecording seriesRecording = getSeriesRecording(program); - return addSchedule(program, seriesRecording == null - ? mScheduleManager.suggestNewPriority() - : mScheduleManager.suggestHighestPriority(seriesRecording.getInputId(), - new Range(program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()), - seriesRecording.getPriority())); + return addSchedule( + program, + seriesRecording == null + ? mScheduleManager.suggestNewPriority() + : mScheduleManager.suggestHighestPriority( + seriesRecording.getInputId(), + new Range( + program.getStartTimeUtcMillis(), + program.getEndTimeUtcMillis()), + seriesRecording.getPriority())); } private ScheduledRecording addSchedule(Program program, long priority) { @@ -190,21 +197,28 @@ public class DvrManager { } ScheduledRecording schedule; SeriesRecording seriesRecording = getSeriesRecording(program); - schedule = createScheduledRecordingBuilder(input.getId(), program) - .setPriority(priority) - .setSeriesRecordingId(seriesRecording == null ? SeriesRecording.ID_NOT_SET - : seriesRecording.getId()) - .build(); + schedule = + createScheduledRecordingBuilder(input.getId(), program) + .setPriority(priority) + .setSeriesRecordingId( + seriesRecording == null + ? SeriesRecording.ID_NOT_SET + : seriesRecording.getId()) + .build(); mDataManager.addScheduledRecording(schedule); return schedule; } - /** - * Adds a recording schedule with a time range. - */ + /** Adds a recording schedule with a time range. */ public void addSchedule(Channel channel, long startTime, long endTime) { - Log.i(TAG, "Adding scheduled recording of channel " + channel + " starting at " + - Utils.toTimeString(startTime) + " and ending at " + Utils.toTimeString(endTime)); + Log.i( + TAG, + "Adding scheduled recording of channel " + + channel + + " starting at " + + Utils.toTimeString(startTime) + + " and ending at " + + Utils.toTimeString(endTime)); if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return; } @@ -216,9 +230,7 @@ public class DvrManager { addScheduleInternal(input.getId(), channel.getId(), startTime, endTime); } - /** - * Adds the schedule. - */ + /** Adds the schedule. */ public void addSchedule(ScheduledRecording schedule) { if (mDataManager.isDvrScheduleLoadFinished()) { mDataManager.addScheduledRecording(schedule); @@ -226,19 +238,23 @@ public class DvrManager { } private void addScheduleInternal(String inputId, long channelId, long startTime, long endTime) { - mDataManager.addScheduledRecording(ScheduledRecording - .builder(inputId, channelId, startTime, endTime) - .setPriority(mScheduleManager.suggestNewPriority()) - .build()); + mDataManager.addScheduledRecording( + ScheduledRecording.builder(inputId, channelId, startTime, endTime) + .setPriority(mScheduleManager.suggestNewPriority()) + .build()); } - /** - * Adds a new series recording and schedules for the programs with the initial state. - */ - public SeriesRecording addSeriesRecording(Program selectedProgram, - List programsToSchedule, @SeriesRecording.SeriesState int initialState) { - Log.i(TAG, "Adding series recording for program " + selectedProgram + ", and schedules: " - + programsToSchedule); + /** Adds a new series recording and schedules for the programs with the initial state. */ + public SeriesRecording addSeriesRecording( + Program selectedProgram, + List programsToSchedule, + @SeriesRecording.SeriesState int initialState) { + Log.i( + TAG, + "Adding series recording for program " + + selectedProgram + + ", and schedules: " + + programsToSchedule); if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { return null; } @@ -247,10 +263,11 @@ public class DvrManager { Log.e(TAG, "Can't find input for program: " + selectedProgram); return null; } - SeriesRecording seriesRecording = SeriesRecording.builder(input.getId(), selectedProgram) - .setPriority(mScheduleManager.suggestNewSeriesPriority()) - .setState(initialState) - .build(); + SeriesRecording seriesRecording = + SeriesRecording.builder(input.getId(), selectedProgram) + .setPriority(mScheduleManager.suggestNewSeriesPriority()) + .setState(initialState) + .build(); mDataManager.addSeriesRecording(seriesRecording); // The schedules for the recorded programs should be added not to create the schedule the // duplicate episodes. @@ -279,9 +296,11 @@ public class DvrManager { // Duplicate schedules can exist, but they will be deleted in a few days. And it's // also guaranteed that the schedules don't belong to any series recordings because // there are no more than one series recordings which have the same program title. - toAdd.add(ScheduledRecording.builder(recordedProgram) - .setPriority(series.getPriority()) - .setSeriesRecordingId(series.getId()).build()); + toAdd.add( + ScheduledRecording.builder(recordedProgram) + .setPriority(series.getPriority()) + .setSeriesRecordingId(series.getId()) + .build()); } } if (!toAdd.isEmpty()) { @@ -291,11 +310,11 @@ public class DvrManager { /** * Adds {@link ScheduledRecording}s for the series recording. - *

- * This method doesn't add the series recording. + * + *

This method doesn't add the series recording. */ - public void addScheduleToSeriesRecording(SeriesRecording series, - List programsToSchedule) { + public void addScheduleToSeriesRecording( + SeriesRecording series, List programsToSchedule) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return; } @@ -311,18 +330,20 @@ public class DvrManager { mDataManager.getScheduledRecordingForProgramId(program.getId()); if (scheduleWithSameProgram != null) { if (scheduleWithSameProgram.isNotStarted()) { - ScheduledRecording r = ScheduledRecording.buildFrom(scheduleWithSameProgram) - .setSeriesRecordingId(series.getId()) - .build(); + ScheduledRecording r = + ScheduledRecording.buildFrom(scheduleWithSameProgram) + .setSeriesRecordingId(series.getId()) + .build(); if (!r.equals(scheduleWithSameProgram)) { toUpdate.add(r); } } } else { - toAdd.add(createScheduledRecordingBuilder(input.getId(), program) - .setPriority(series.getPriority()) - .setSeriesRecordingId(series.getId()) - .build()); + toAdd.add( + createScheduledRecordingBuilder(input.getId(), program) + .setPriority(series.getPriority()) + .setSeriesRecordingId(series.getId()) + .build()); } } if (!toAdd.isEmpty()) { @@ -333,9 +354,7 @@ public class DvrManager { } } - /** - * Updates the series recording. - */ + /** Updates the series recording. */ public void updateSeriesRecording(SeriesRecording series) { if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { SeriesRecording previousSeries = mDataManager.getSeriesRecording(series.getId()); @@ -344,7 +363,7 @@ public class DvrManager { // schedules will be added by SeriesRecordingScheduler or by SeriesSettingsFragment. if (previousSeries.getChannelOption() != series.getChannelOption() || (previousSeries.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE - && previousSeries.getChannelId() != series.getChannelId())) { + && previousSeries.getChannelId() != series.getChannelId())) { List schedules = mDataManager.getScheduledRecordings(series.getId()); List schedulesToRemove = new ArrayList<>(); @@ -365,20 +384,21 @@ public class DvrManager { schedulesToRemove.add(deletedSchedule); } } - mDataManager.removeScheduledRecording(true, - ScheduledRecording.toArray(schedulesToRemove)); + mDataManager.removeScheduledRecording( + true, ScheduledRecording.toArray(schedulesToRemove)); } } mDataManager.updateSeriesRecording(series); - if (previousSeries == null - || previousSeries.getPriority() != series.getPriority()) { + if (previousSeries == null || previousSeries.getPriority() != series.getPriority()) { long priority = series.getPriority(); List schedulesToUpdate = new ArrayList<>(); - for (ScheduledRecording schedule - : mDataManager.getScheduledRecordings(series.getId())) { + for (ScheduledRecording schedule : + mDataManager.getScheduledRecordings(series.getId())) { if (schedule.isNotStarted() || schedule.isInProgress()) { - schedulesToUpdate.add(ScheduledRecording.buildFrom(schedule) - .setPriority(priority).build()); + schedulesToUpdate.add( + ScheduledRecording.buildFrom(schedule) + .setPriority(priority) + .build()); } } if (!schedulesToUpdate.isEmpty()) { @@ -411,28 +431,26 @@ public class DvrManager { mDataManager.removeSeriesRecording(series); } - /** - * Stops the currently recorded program - */ + /** Stops the currently recorded program */ public void stopRecording(final ScheduledRecording recording) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return; } synchronized (mListener) { for (final Entry entry : mListener.entrySet()) { - entry.getValue().post(new Runnable() { - @Override - public void run() { - entry.getKey().onStopRecordingRequested(recording); - } - }); + entry.getValue() + .post( + new Runnable() { + @Override + public void run() { + entry.getKey().onStopRecordingRequested(recording); + } + }); } } } - /** - * Removes scheduled recordings or an existing recordings. - */ + /** Removes scheduled recordings or an existing recordings. */ public void removeScheduledRecording(ScheduledRecording... schedules) { Log.i(TAG, "Removing " + Arrays.asList(schedules)); if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { @@ -447,9 +465,7 @@ public class DvrManager { } } - /** - * Removes scheduled recordings without changing to the DELETED state. - */ + /** Removes scheduled recordings without changing to the DELETED state. */ public void forceRemoveScheduledRecording(ScheduledRecording... schedules) { Log.i(TAG, "Force removing " + Arrays.asList(schedules)); if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { @@ -464,9 +480,7 @@ public class DvrManager { } } - /** - * Removes the recorded program. It deletes the file if possible. - */ + /** Removes the recorded program. It deletes the file if possible. */ public void removeRecordedProgram(Uri recordedProgramUri) { if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { return; @@ -474,9 +488,7 @@ public class DvrManager { removeRecordedProgram(ContentUris.parseId(recordedProgramUri)); } - /** - * Removes the recorded program. It deletes the file if possible. - */ + /** Removes the recorded program. It deletes the file if possible. */ public void removeRecordedProgram(long recordedProgramId) { if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { return; @@ -487,9 +499,7 @@ public class DvrManager { } } - /** - * Removes the recorded program. It deletes the file if possible. - */ + /** Removes the recorded program. It deletes the file if possible. */ public void removeRecordedProgram(final RecordedProgram recordedProgram) { if (!SoftPreconditions.checkState(mDataManager.isInitialized())) { return; @@ -556,9 +566,7 @@ public class DvrManager { }.executeOnDbThread(); } - /** - * Updates the scheduled recording. - */ + /** Updates the scheduled recording. */ public void updateScheduledRecording(ScheduledRecording recording) { if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { mDataManager.updateScheduledRecording(recording); @@ -566,8 +574,8 @@ public class DvrManager { } /** - * Returns priority ordered list of all scheduled recordings that will not be recorded if - * this program is. + * Returns priority ordered list of all scheduled recordings that will not be recorded if this + * program is. * * @see DvrScheduleManager#getConflictingSchedules(Program) */ @@ -579,13 +587,13 @@ public class DvrManager { } /** - * Returns priority ordered list of all scheduled recordings that will not be recorded if - * this channel is. + * Returns priority ordered list of all scheduled recordings that will not be recorded if this + * channel is. * * @see DvrScheduleManager#getConflictingSchedules(long, long, long) */ - public List getConflictingSchedules(long channelId, long startTimeMs, - long endTimeMs) { + public List getConflictingSchedules( + long channelId, long startTimeMs, long endTimeMs) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return Collections.emptyList(); } @@ -595,8 +603,8 @@ public class DvrManager { /** * Checks if the schedule is conflicting. * - *

Note that the {@code schedule} should be the existing one. If not, this returns - * {@code false}. + *

Note that the {@code schedule} should be the existing one. If not, this returns {@code + * false}. */ public boolean isConflicting(ScheduledRecording schedule) { return schedule != null @@ -605,8 +613,8 @@ public class DvrManager { } /** - * Returns priority ordered list of all scheduled recording that will not be recorded if - * this channel is tuned to. + * Returns priority ordered list of all scheduled recording that will not be recorded if this + * channel is tuned to. * * @see DvrScheduleManager#getConflictingSchedulesForTune */ @@ -617,22 +625,18 @@ public class DvrManager { return mScheduleManager.getConflictingSchedulesForTune(channelId); } - /** - * Sets the highest priority to the schedule. - */ + /** Sets the highest priority to the schedule. */ public void setHighestPriority(ScheduledRecording schedule) { if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { long newPriority = mScheduleManager.suggestHighestPriority(schedule); if (newPriority != schedule.getPriority()) { - mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule) - .setPriority(newPriority).build()); + mDataManager.updateScheduledRecording( + ScheduledRecording.buildFrom(schedule).setPriority(newPriority).build()); } } } - /** - * Suggests the higher priority than the schedules which overlap with {@code schedule}. - */ + /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */ public long suggestHighestPriority(ScheduledRecording schedule) { if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { return mScheduleManager.suggestHighestPriority(schedule); @@ -642,9 +646,9 @@ public class DvrManager { /** * Returns {@code true} if the channel can be recorded. - *

- * Note that this method doesn't check the conflict of the schedule or available tuners. - * This can be called from the UI before the schedules are loaded. + * + *

Note that this method doesn't check the conflict of the schedule or available tuners. This + * can be called from the UI before the schedules are loaded. */ public boolean isChannelRecordable(Channel channel) { if (!mDataManager.isDvrScheduleLoadFinished() || channel == null) { @@ -661,23 +665,27 @@ public class DvrManager { if (!info.canRecord()) { return false; } - Program program = TvApplication.getSingletons(mAppContext).getProgramDataManager() - .getCurrentProgram(channel.getId()); + Program program = + TvApplication.getSingletons(mAppContext) + .getProgramDataManager() + .getCurrentProgram(channel.getId()); return program == null || !program.isRecordingProhibited(); } /** * Returns {@code true} if the program can be recorded. - *

- * Note that this method doesn't check the conflict of the schedule or available tuners. - * This can be called from the UI before the schedules are loaded. + * + *

Note that this method doesn't check the conflict of the schedule or available tuners. This + * can be called from the UI before the schedules are loaded. */ public boolean isProgramRecordable(Program program) { if (!mDataManager.isInitialized()) { return false; } - Channel channel = TvApplication.getSingletons(mAppContext).getChannelDataManager() - .getChannel(program.getChannelId()); + Channel channel = + TvApplication.getSingletons(mAppContext) + .getChannelDataManager() + .getChannel(program.getChannelId()); if (channel == null || channel.isRecordingProhibited()) { return false; } @@ -691,8 +699,8 @@ public class DvrManager { /** * Returns the current recording for the channel. - *

- * This can be called from the UI before the schedules are loaded. + * + *

This can be called from the UI before the schedules are loaded. */ public ScheduledRecording getCurrentRecording(long channelId) { if (!mDataManager.isDvrScheduleLoadFinished()) { @@ -707,8 +715,8 @@ public class DvrManager { } /** - * Returns schedules which is available (i.e., isNotStarted or isInProgress) and belongs to - * the series recording {@code seriesRecordingId}. + * Returns schedules which is available (i.e., isNotStarted or isInProgress) and belongs to the + * series recording {@code seriesRecordingId}. */ public List getAvailableScheduledRecording(long seriesRecordingId) { if (!mDataManager.isDvrScheduleLoadFinished()) { @@ -723,9 +731,7 @@ public class DvrManager { return schedules; } - /** - * Returns the series recording related to the program. - */ + /** Returns the series recording related to the program. */ @Nullable public SeriesRecording getSeriesRecording(Program program) { if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) { @@ -735,8 +741,8 @@ public class DvrManager { } /** - * Returns if there are valid items. Valid item contains {@link RecordedProgram}, - * available {@link ScheduledRecording} and {@link SeriesRecording}. + * Returns if there are valid items. Valid item contains {@link RecordedProgram}, available + * {@link ScheduledRecording} and {@link SeriesRecording}. */ public boolean hasValidItems() { return !(mDataManager.getRecordedPrograms().isEmpty() @@ -768,8 +774,8 @@ public class DvrManager { * Returns ScheduledRecording.builder based on {@code program}. If program is already started, * recording started time is clipped to the current time. */ - private ScheduledRecording.Builder createScheduledRecordingBuilder(String inputId, - Program program) { + private ScheduledRecording.Builder createScheduledRecordingBuilder( + String inputId, Program program) { ScheduledRecording.Builder builder = ScheduledRecording.builder(inputId, program); long time = System.currentTimeMillis(); if (program.getStartTimeUtcMillis() < time && time < program.getEndTimeUtcMillis()) { @@ -778,13 +784,13 @@ public class DvrManager { return builder; } - /** - * Returns a schedule which matches to the given episode. - */ - public ScheduledRecording getScheduledRecording(String title, String seasonNumber, - String episodeNumber) { - if (!SoftPreconditions.checkState(mDataManager.isInitialized()) || title == null - || seasonNumber == null || episodeNumber == null) { + /** Returns a schedule which matches to the given episode. */ + public ScheduledRecording getScheduledRecording( + String title, String seasonNumber, String episodeNumber) { + if (!SoftPreconditions.checkState(mDataManager.isInitialized()) + || title == null + || seasonNumber == null + || episodeNumber == null) { return null; } for (ScheduledRecording r : mDataManager.getAllScheduledRecordings()) { @@ -797,13 +803,13 @@ public class DvrManager { return null; } - /** - * Returns a recorded program which is the same episode as the given {@code program}. - */ - public RecordedProgram getRecordedProgram(String title, String seasonNumber, - String episodeNumber) { - if (!SoftPreconditions.checkState(mDataManager.isInitialized()) || title == null - || seasonNumber == null || episodeNumber == null) { + /** Returns a recorded program which is the same episode as the given {@code program}. */ + public RecordedProgram getRecordedProgram( + String title, String seasonNumber, String episodeNumber) { + if (!SoftPreconditions.checkState(mDataManager.isInitialized()) + || title == null + || seasonNumber == null + || episodeNumber == null) { return null; } for (RecordedProgram r : mDataManager.getRecordedPrograms()) { @@ -820,7 +826,8 @@ public class DvrManager { @WorkerThread private void removeRecordedData(Uri dataUri) { try { - if (dataUri != null && ContentResolver.SCHEME_FILE.equals(dataUri.getScheme()) + if (dataUri != null + && ContentResolver.SCHEME_FILE.equals(dataUri.getScheme()) && dataUri.getPath() != null) { File recordedProgramPath = new File(dataUri.getPath()); if (!recordedProgramPath.exists()) { @@ -834,16 +841,19 @@ public class DvrManager { } } catch (SecurityException e) { if (DEBUG) { - Log.d(TAG, "To delete this recorded program, please manually delete video data at" - + "\nadb shell rm -rf " + dataUri); + Log.d( + TAG, + "To delete this recorded program, please manually delete video data at" + + "\nadb shell rm -rf " + + dataUri); } } } /** * Remove all the records related to the input. - *

- * Note that this should be called after the input was removed. + * + *

Note that this should be called after the input was removed. */ public void forgetStorage(String inputId) { if (mDataManager.isInitialized()) { diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java index b72117aa..62f93c8b 100644 --- a/src/com/android/tv/dvr/DvrScheduleManager.java +++ b/src/com/android/tv/dvr/DvrScheduleManager.java @@ -25,7 +25,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Range; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -34,12 +33,11 @@ import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.recorder.InputTaskScheduler; -import com.android.tv.util.CompositeComparator; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.recorder.InputTaskScheduler; +import com.android.tv.util.CompositeComparator; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -50,21 +48,15 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * A class to manage the schedules. - */ +/** A class to manage the schedules. */ @TargetApi(Build.VERSION_CODES.N) @MainThread public class DvrScheduleManager { private static final String TAG = "DvrScheduleManager"; - /** - * The default priority of scheduled recording. - */ + /** The default priority of scheduled recording. */ public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; - /** - * The default priority of series recording. - */ + /** The default priority of series recording. */ public static final long DEFAULT_SERIES_PRIORITY = DEFAULT_PRIORITY >> 1; // The new priority will have the offset from the existing one. private static final long PRIORITY_OFFSET = 1024; @@ -119,146 +111,151 @@ public class DvrScheduleManager { } }); } - ScheduledRecordingListener scheduledRecordingListener = new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - if (!mInitialized) { - return; - } - for (ScheduledRecording schedule : scheduledRecordings) { - if (!schedule.isNotStarted() && !schedule.isInProgress()) { - continue; - } - TvInputInfo input = Utils - .getTvInputInfoForInputId(mContext, schedule.getInputId()); - if (!SoftPreconditions.checkArgument(input != null, TAG, - "Input was removed for : " + schedule)) { - // Input removed. - mInputScheduleMap.remove(schedule.getInputId()); - mInputConflictInfoMap.remove(schedule.getInputId()); - continue; - } - String inputId = input.getId(); - List schedules = mInputScheduleMap.get(inputId); - if (schedules == null) { - schedules = new ArrayList<>(); - mInputScheduleMap.put(inputId, schedules); + ScheduledRecordingListener scheduledRecordingListener = + new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded( + ScheduledRecording... scheduledRecordings) { + if (!mInitialized) { + return; + } + for (ScheduledRecording schedule : scheduledRecordings) { + if (!schedule.isNotStarted() && !schedule.isInProgress()) { + continue; + } + TvInputInfo input = + Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); + if (!SoftPreconditions.checkArgument( + input != null, TAG, "Input was removed for : " + schedule)) { + // Input removed. + mInputScheduleMap.remove(schedule.getInputId()); + mInputConflictInfoMap.remove(schedule.getInputId()); + continue; + } + String inputId = input.getId(); + List schedules = mInputScheduleMap.get(inputId); + if (schedules == null) { + schedules = new ArrayList<>(); + mInputScheduleMap.put(inputId, schedules); + } + schedules.add(schedule); + } + onSchedulesChanged(); + notifyScheduledRecordingAdded(scheduledRecordings); } - schedules.add(schedule); - } - onSchedulesChanged(); - notifyScheduledRecordingAdded(scheduledRecordings); - } - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - if (!mInitialized) { - return; - } - for (ScheduledRecording schedule : scheduledRecordings) { - TvInputInfo input = Utils - .getTvInputInfoForInputId(mContext, schedule.getInputId()); - if (input == null) { - // Input removed. - mInputScheduleMap.remove(schedule.getInputId()); - mInputConflictInfoMap.remove(schedule.getInputId()); - continue; - } - String inputId = input.getId(); - List schedules = mInputScheduleMap.get(inputId); - if (schedules != null) { - schedules.remove(schedule); - if (schedules.isEmpty()) { - mInputScheduleMap.remove(inputId); + @Override + public void onScheduledRecordingRemoved( + ScheduledRecording... scheduledRecordings) { + if (!mInitialized) { + return; } - } - Map conflictInfo = mInputConflictInfoMap.get(inputId); - if (conflictInfo != null) { - conflictInfo.remove(schedule.getId()); - if (conflictInfo.isEmpty()) { - mInputConflictInfoMap.remove(inputId); + for (ScheduledRecording schedule : scheduledRecordings) { + TvInputInfo input = + Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); + if (input == null) { + // Input removed. + mInputScheduleMap.remove(schedule.getInputId()); + mInputConflictInfoMap.remove(schedule.getInputId()); + continue; + } + String inputId = input.getId(); + List schedules = mInputScheduleMap.get(inputId); + if (schedules != null) { + schedules.remove(schedule); + if (schedules.isEmpty()) { + mInputScheduleMap.remove(inputId); + } + } + Map conflictInfo = + mInputConflictInfoMap.get(inputId); + if (conflictInfo != null) { + conflictInfo.remove(schedule.getId()); + if (conflictInfo.isEmpty()) { + mInputConflictInfoMap.remove(inputId); + } + } } + onSchedulesChanged(); + notifyScheduledRecordingRemoved(scheduledRecordings); } - } - onSchedulesChanged(); - notifyScheduledRecordingRemoved(scheduledRecordings); - } - @Override - public void onScheduledRecordingStatusChanged( - ScheduledRecording... scheduledRecordings) { - if (!mInitialized) { - return; - } - for (ScheduledRecording schedule : scheduledRecordings) { - TvInputInfo input = Utils - .getTvInputInfoForInputId(mContext, schedule.getInputId()); - if (!SoftPreconditions.checkArgument(input != null, TAG, - "Input was removed for : " + schedule)) { - // Input removed. - mInputScheduleMap.remove(schedule.getInputId()); - mInputConflictInfoMap.remove(schedule.getInputId()); - continue; - } - String inputId = input.getId(); - List schedules = mInputScheduleMap.get(inputId); - if (schedules == null) { - schedules = new ArrayList<>(); - mInputScheduleMap.put(inputId, schedules); - } - // Compare ID because ScheduledRecording.equals() doesn't work if the state - // is changed. - for (Iterator i = schedules.iterator(); i.hasNext(); ) { - if (i.next().getId() == schedule.getId()) { - i.remove(); - break; + @Override + public void onScheduledRecordingStatusChanged( + ScheduledRecording... scheduledRecordings) { + if (!mInitialized) { + return; } - } - if (schedule.isNotStarted() || schedule.isInProgress()) { - schedules.add(schedule); - } - if (schedules.isEmpty()) { - mInputScheduleMap.remove(inputId); - } - // Update conflict list as well - Map conflictInfo = mInputConflictInfoMap.get(inputId); - if (conflictInfo != null) { - ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId()); - if (oldConflictInfo != null) { - oldConflictInfo.schedule = schedule; + for (ScheduledRecording schedule : scheduledRecordings) { + TvInputInfo input = + Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); + if (!SoftPreconditions.checkArgument( + input != null, TAG, "Input was removed for : " + schedule)) { + // Input removed. + mInputScheduleMap.remove(schedule.getInputId()); + mInputConflictInfoMap.remove(schedule.getInputId()); + continue; + } + String inputId = input.getId(); + List schedules = mInputScheduleMap.get(inputId); + if (schedules == null) { + schedules = new ArrayList<>(); + mInputScheduleMap.put(inputId, schedules); + } + // Compare ID because ScheduledRecording.equals() doesn't work if the + // state + // is changed. + for (Iterator i = schedules.iterator(); + i.hasNext(); ) { + if (i.next().getId() == schedule.getId()) { + i.remove(); + break; + } + } + if (schedule.isNotStarted() || schedule.isInProgress()) { + schedules.add(schedule); + } + if (schedules.isEmpty()) { + mInputScheduleMap.remove(inputId); + } + // Update conflict list as well + Map conflictInfo = + mInputConflictInfoMap.get(inputId); + if (conflictInfo != null) { + ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId()); + if (oldConflictInfo != null) { + oldConflictInfo.schedule = schedule; + } + } } + onSchedulesChanged(); + notifyScheduledRecordingStatusChanged(scheduledRecordings); } - } - onSchedulesChanged(); - notifyScheduledRecordingStatusChanged(scheduledRecordings); - } - }; + }; mDataManager.addScheduledRecordingListener(scheduledRecordingListener); - ChannelDataManager.Listener channelDataManagerListener = new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) { - buildData(); - } - } + ChannelDataManager.Listener channelDataManagerListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) { + buildData(); + } + } - @Override - public void onChannelListUpdated() { - if (mDataManager.isDvrScheduleLoadFinished()) { - buildData(); - } - } + @Override + public void onChannelListUpdated() { + if (mDataManager.isDvrScheduleLoadFinished()) { + buildData(); + } + } - @Override - public void onChannelBrowsableChanged() { - } - }; + @Override + public void onChannelBrowsableChanged() {} + }; mChannelDataManager.addListener(channelDataManagerListener); } - /** - * Returns the started recordings for the given input. - */ + /** Returns the started recordings for the given input. */ private List getStartedRecordings(String inputId) { if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { return Collections.emptyList(); @@ -337,39 +334,29 @@ public class DvrScheduleManager { } } - /** - * Returns {@code true} if this class has been initialized. - */ + /** Returns {@code true} if this class has been initialized. */ public boolean isInitialized() { return mInitialized; } - /** - * Adds a {@link ScheduledRecordingListener}. - */ + /** Adds a {@link ScheduledRecordingListener}. */ public final void addScheduledRecordingListener(ScheduledRecordingListener listener) { mScheduledRecordingListeners.add(listener); } - /** - * Removes a {@link ScheduledRecordingListener}. - */ + /** Removes a {@link ScheduledRecordingListener}. */ public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) { mScheduledRecordingListeners.remove(listener); } - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. - */ + /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */ private void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { for (ScheduledRecordingListener l : mScheduledRecordingListeners) { l.onScheduledRecordingAdded(scheduledRecordings); } } - /** - * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. - */ + /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */ private void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { for (ScheduledRecordingListener l : mScheduledRecordingListeners) { l.onScheduledRecordingRemoved(scheduledRecordings); @@ -385,48 +372,36 @@ public class DvrScheduleManager { } } - /** - * Adds a {@link OnInitializeListener}. - */ + /** Adds a {@link OnInitializeListener}. */ public final void addOnInitializeListener(OnInitializeListener listener) { mOnInitializeListeners.add(listener); } - /** - * Removes a {@link OnInitializeListener}. - */ + /** Removes a {@link OnInitializeListener}. */ public final void removeOnInitializeListener(OnInitializeListener listener) { mOnInitializeListeners.remove(listener); } - /** - * Calls {@link OnInitializeListener#onInitialize} for each listener. - */ + /** Calls {@link OnInitializeListener#onInitialize} for each listener. */ private void notifyInitialize() { for (OnInitializeListener l : mOnInitializeListeners) { l.onInitialize(); } } - /** - * Adds a {@link OnConflictStateChangeListener}. - */ + /** Adds a {@link OnConflictStateChangeListener}. */ public final void addOnConflictStateChangeListener(OnConflictStateChangeListener listener) { mOnConflictStateChangeListeners.add(listener); } - /** - * Removes a {@link OnConflictStateChangeListener}. - */ + /** Removes a {@link OnConflictStateChangeListener}. */ public final void removeOnConflictStateChangeListener(OnConflictStateChangeListener listener) { mOnConflictStateChangeListeners.remove(listener); } - /** - * Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener. - */ - private void notifyConflictStateChange(boolean conflict, - ScheduledRecording... scheduledRecordings) { + /** Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener. */ + private void notifyConflictStateChange( + boolean conflict, ScheduledRecording... scheduledRecordings) { for (OnConflictStateChangeListener l : mOnConflictStateChangeListeners) { l.onConflictStateChange(conflict, scheduledRecordings); } @@ -434,8 +409,8 @@ public class DvrScheduleManager { /** * Returns the priority for the program if it is recorded. - *

- * The recording will have the higher priority than the existing ones. + * + *

The recording will have the higher priority than the existing ones. */ public long suggestNewPriority() { if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { @@ -454,9 +429,7 @@ public class DvrScheduleManager { return highestPriority + PRIORITY_OFFSET; } - /** - * Suggests the higher priority than the schedules which overlap with {@code schedule}. - */ + /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */ public long suggestHighestPriority(ScheduledRecording schedule) { List schedules = mInputScheduleMap.get(schedule.getInputId()); if (schedules == null) { @@ -464,7 +437,8 @@ public class DvrScheduleManager { } long highestPriority = Long.MIN_VALUE; for (ScheduledRecording r : schedules) { - if (!r.equals(schedule) && r.isOverLapping(schedule) + if (!r.equals(schedule) + && r.isOverLapping(schedule) && r.getPriority() > highestPriority) { highestPriority = r.getPriority(); } @@ -475,9 +449,7 @@ public class DvrScheduleManager { return highestPriority + PRIORITY_OFFSET; } - /** - * Suggests the higher priority than the schedules which overlap with {@code schedule}. - */ + /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */ public long suggestHighestPriority(String inputId, Range peroid, long basePriority) { List schedules = mInputScheduleMap.get(inputId); if (schedules == null) { @@ -497,8 +469,8 @@ public class DvrScheduleManager { /** * Returns the priority for a series recording. - *

- * The recording will have the higher priority than the existing series. + * + *

The recording will have the higher priority than the existing series. */ public long suggestNewSeriesPriority() { if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { @@ -510,7 +482,7 @@ public class DvrScheduleManager { /** * Returns the priority for a series recording by order of series recording priority. * - * Higher order will have higher priority. + *

Higher order will have higher priority. */ public static long suggestSeriesPriority(int order) { return DEFAULT_SERIES_PRIORITY + order * PRIORITY_OFFSET; @@ -527,21 +499,23 @@ public class DvrScheduleManager { } /** - * Returns a sorted list of all scheduled recordings that will not be recorded if - * this program is going to be recorded, with their priorities in decending order. - *

- * An empty list means there is no conflicts. If there is conflict, a priority higher than + * Returns a sorted list of all scheduled recordings that will not be recorded if this program + * is going to be recorded, with their priorities in decending order. + * + *

An empty list means there is no conflicts. If there is conflict, a priority higher than * the first recording in the returned list should be assigned to the new schedule of this * program to guarantee the program would be completely recorded. */ public List getConflictingSchedules(Program program) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); - SoftPreconditions.checkState(Program.isValid(program), TAG, - "Program is invalid: " + program); SoftPreconditions.checkState( - program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), TAG, + Program.isValid(program), TAG, "Program is invalid: " + program); + SoftPreconditions.checkState( + program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), + TAG, "Program duration is empty: " + program); - if (!mInitialized || !Program.isValid(program) + if (!mInitialized + || !Program.isValid(program) || program.getStartTimeUtcMillis() >= program.getEndTimeUtcMillis()) { return Collections.emptyList(); } @@ -549,17 +523,19 @@ public class DvrScheduleManager { if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { return Collections.emptyList(); } - return getConflictingSchedules(input, Collections.singletonList( - ScheduledRecording.builder(input.getId(), program) - .setPriority(suggestHighestPriority()) - .build())); + return getConflictingSchedules( + input, + Collections.singletonList( + ScheduledRecording.builder(input.getId(), program) + .setPriority(suggestHighestPriority()) + .build())); } /** * Returns list of all conflicting scheduled recordings for the given {@code seriesRecording} * recording. - *

- * Any empty list means there is no conflicts. + * + *

Any empty list means there is no conflicts. */ public List getConflictingSchedules(SeriesRecording seriesRecording) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); @@ -571,8 +547,8 @@ public class DvrScheduleManager { if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { return Collections.emptyList(); } - List scheduledRecordingForSeries = mDataManager.getScheduledRecordings( - seriesRecording.getId()); + List scheduledRecordingForSeries = + mDataManager.getScheduledRecordings(seriesRecording.getId()); List availableScheduledRecordingForSeries = new ArrayList<>(); for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) { if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) { @@ -586,15 +562,15 @@ public class DvrScheduleManager { } /** - * Returns a sorted list of all scheduled recordings that will not be recorded if - * this channel is going to be recorded, with their priority in decending order. - *

- * An empty list means there is no conflicts. If there is conflict, a priority higher than + * Returns a sorted list of all scheduled recordings that will not be recorded if this channel + * is going to be recorded, with their priority in decending order. + * + *

An empty list means there is no conflicts. If there is conflict, a priority higher than * the first recording in the returned list should be assigned to the new schedule of this * channel to guarantee the channel would be completely recorded in the designated time range. */ - public List getConflictingSchedules(long channelId, long startTimeMs, - long endTimeMs) { + public List getConflictingSchedules( + long channelId, long startTimeMs, long endTimeMs) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); SoftPreconditions.checkState(startTimeMs < endTimeMs, TAG, "Recording duration is empty."); @@ -605,10 +581,12 @@ public class DvrScheduleManager { if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { return Collections.emptyList(); } - return getConflictingSchedules(input, Collections.singletonList( - ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs) - .setPriority(suggestHighestPriority()) - .build())); + return getConflictingSchedules( + input, + Collections.singletonList( + ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs) + .setPriority(suggestHighestPriority()) + .build())); } /** @@ -633,14 +611,14 @@ public class DvrScheduleManager { /** * Checks if the schedule is conflicting. * - *

Note that the {@code schedule} should be the existing one. If not, this returns - * {@code false}. + *

Note that the {@code schedule} should be the existing one. If not, this returns {@code + * false}. */ public boolean isConflicting(ScheduledRecording schedule) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); - SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : " - + schedule.getChannelId()); + SoftPreconditions.checkState( + input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId()); if (!mInitialized || input == null) { return false; } @@ -651,15 +629,15 @@ public class DvrScheduleManager { /** * Checks if the schedule is partially conflicting, i.e., part of the scheduled program might be * recorded even if the priority of the schedule is not raised. - *

- * If the given schedule is not conflicting or is totally conflicting, i.e., cannot be recorded - * at all, this method returns {@code false} in both cases. + * + *

If the given schedule is not conflicting or is totally conflicting, i.e., cannot be + * recorded at all, this method returns {@code false} in both cases. */ public boolean isPartiallyConflicting(@NonNull ScheduledRecording schedule) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); - SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : " - + schedule.getChannelId()); + SoftPreconditions.checkState( + input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId()); if (!mInitialized || input == null) { return false; } @@ -672,27 +650,35 @@ public class DvrScheduleManager { } /** - * Returns priority ordered list of all scheduled recordings that will not be recorded if - * this channel is tuned to. + * Returns priority ordered list of all scheduled recordings that will not be recorded if this + * channel is tuned to. */ public List getConflictingSchedulesForTune(long channelId) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); - SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: " - + channelId); + SoftPreconditions.checkState( + input != null, TAG, "Can't find input for channel ID: " + channelId); if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { return Collections.emptyList(); } - return getConflictingSchedulesForTune(input.getId(), channelId, System.currentTimeMillis(), - suggestHighestPriority(), getStartedRecordings(input.getId()), + return getConflictingSchedulesForTune( + input.getId(), + channelId, + System.currentTimeMillis(), + suggestHighestPriority(), + getStartedRecordings(input.getId()), input.getTunerCount()); } @VisibleForTesting - public static List getConflictingSchedulesForTune(String inputId, - long channelId, long currentTimeMs, long newPriority, - List startedRecordings, int tunerCount) { + public static List getConflictingSchedulesForTune( + String inputId, + long channelId, + long currentTimeMs, + long newPriority, + List startedRecordings, + int tunerCount) { boolean channelFound = false; for (ScheduledRecording schedule : startedRecordings) { if (schedule.getChannelId() == channelId) { @@ -704,10 +690,10 @@ public class DvrScheduleManager { if (!channelFound) { // The current channel is not being recorded. schedules = new ArrayList<>(startedRecordings); - schedules.add(ScheduledRecording - .builder(inputId, channelId, currentTimeMs, currentTimeMs + 1) - .setPriority(newPriority) - .build()); + schedules.add( + ScheduledRecording.builder(inputId, channelId, currentTimeMs, currentTimeMs + 1) + .setPriority(newPriority) + .build()); } else { schedules = startedRecordings; } @@ -715,17 +701,17 @@ public class DvrScheduleManager { } /** - * Returns priority ordered list of all scheduled recordings that will not be recorded if - * the user keeps watching this channel. - *

- * Note that if the user keeps watching the channel, the channel can be recorded. + * Returns priority ordered list of all scheduled recordings that will not be recorded if the + * user keeps watching this channel. + * + *

Note that if the user keeps watching the channel, the channel can be recorded. */ public List getConflictingSchedulesForWatching(long channelId) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); - SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: " - + channelId); + SoftPreconditions.checkState( + input != null, TAG, "Can't find input for channel ID: " + channelId); if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { return Collections.emptyList(); } @@ -733,12 +719,17 @@ public class DvrScheduleManager { if (schedules == null || schedules.isEmpty()) { return Collections.emptyList(); } - return getConflictingSchedulesForWatching(input.getId(), channelId, - System.currentTimeMillis(), suggestNewPriority(), schedules, input.getTunerCount()); + return getConflictingSchedulesForWatching( + input.getId(), + channelId, + System.currentTimeMillis(), + suggestNewPriority(), + schedules, + input.getTunerCount()); } - private List getConflictingSchedules(TvInputInfo input, - List schedulesToAdd) { + private List getConflictingSchedules( + TvInputInfo input, List schedulesToAdd) { SoftPreconditions.checkNotNull(input); if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { return Collections.emptyList(); @@ -751,9 +742,13 @@ public class DvrScheduleManager { } @VisibleForTesting - static List getConflictingSchedulesForWatching(String inputId, - long channelId, long currentTimeMs, long newPriority, - @NonNull List schedules, int tunerCount) { + static List getConflictingSchedulesForWatching( + String inputId, + long channelId, + long currentTimeMs, + long newPriority, + @NonNull List schedules, + int tunerCount) { List schedulesToCheck = new ArrayList<>(schedules); List schedulesSameChannel = new ArrayList<>(); for (ScheduledRecording schedule : schedules) { @@ -763,10 +758,10 @@ public class DvrScheduleManager { } } // Assume that the user will watch the current channel forever. - schedulesToCheck.add(ScheduledRecording - .builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE) - .setPriority(newPriority) - .build()); + schedulesToCheck.add( + ScheduledRecording.builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE) + .setPriority(newPriority) + .build()); List result = new ArrayList<>(); result.addAll(getConflictingSchedules(schedulesSameChannel, 1)); result.addAll(getConflictingSchedules(schedulesToCheck, tunerCount)); @@ -775,8 +770,10 @@ public class DvrScheduleManager { } @VisibleForTesting - static List getConflictingSchedules(List schedulesToAdd, - List currentSchedules, int tunerCount) { + static List getConflictingSchedules( + List schedulesToAdd, + List currentSchedules, + int tunerCount) { List schedulesToCheck = new ArrayList<>(currentSchedules); // When the duplicate schedule is to be added, remove the current duplicate recording. for (Iterator iter = schedulesToCheck.iterator(); iter.hasNext(); ) { @@ -805,9 +802,7 @@ public class DvrScheduleManager { return getConflictingSchedules(schedulesToCheck, tunerCount, ranges); } - /** - * Returns all conflicting scheduled recordings for the given schedules and count of tuner. - */ + /** Returns all conflicting scheduled recordings for the given schedules and count of tuner. */ public static List getConflictingSchedules( List schedules, int tunerCount) { return getConflictingSchedules(schedules, tunerCount, null); @@ -825,21 +820,21 @@ public class DvrScheduleManager { } @VisibleForTesting - static List getConflictingSchedulesInfo(List schedules, - int tunerCount) { + static List getConflictingSchedulesInfo( + List schedules, int tunerCount) { return getConflictingSchedulesInfo(schedules, tunerCount, null); } /** * This is the core method to calculate all the conflicting schedules (in given periods). - *

- * Note that this method will ignore duplicated schedules with a same hash code. (Please refer - * to {@link ScheduledRecording#hashCode}.) + * + *

Note that this method will ignore duplicated schedules with a same hash code. (Please + * refer to {@link ScheduledRecording#hashCode}.) * * @return A {@link HashMap} from {@link ScheduledRecording} to {@link Boolean}. The boolean - * value denotes if the scheduled recording is partially conflicting, i.e., is possible - * to be partially recorded under the given schedules and tuner count {@code true}, - * or not {@code false}. + * value denotes if the scheduled recording is partially conflicting, i.e., is possible to + * be partially recorded under the given schedules and tuner count {@code true}, or not + * {@code false}. */ private static List getConflictingSchedulesInfo( List schedules, int tunerCount, List> periods) { @@ -886,14 +881,19 @@ public class DvrScheduleManager { if (earliestEndTime < schedule.getEndTimeMs()) { // The schedule can starts when other recording ends even though it's // clipped. - ScheduledRecording modifiedSchedule = ScheduledRecording.buildFrom(schedule) - .setStartTimeMs(earliestEndTime).build(); + ScheduledRecording modifiedSchedule = + ScheduledRecording.buildFrom(schedule) + .setStartTimeMs(earliestEndTime) + .build(); ScheduledRecording originalSchedule = modified2OriginalSchedules.getOrDefault(schedule, schedule); modified2OriginalSchedules.put(modifiedSchedule, originalSchedule); - int insertPosition = Collections.binarySearch(schedulesToCheck, - modifiedSchedule, - ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); + int insertPosition = + Collections.binarySearch( + schedulesToCheck, + modifiedSchedule, + ScheduledRecording + .START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); if (insertPosition >= 0) { schedulesToCheck.add(insertPosition, modifiedSchedule); } else { @@ -921,17 +921,19 @@ public class DvrScheduleManager { } } List result = new ArrayList<>(conflicts.values()); - Collections.sort(result, new Comparator() { - @Override - public int compare(ConflictInfo lhs, ConflictInfo rhs) { - return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule); - } - }); + Collections.sort( + result, + new Comparator() { + @Override + public int compare(ConflictInfo lhs, ConflictInfo rhs) { + return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule); + } + }); return result; } - private static void removeFinishedRecordings(List recordings, - long currentTimeMs) { + private static void removeFinishedRecordings( + List recordings, long currentTimeMs) { for (Iterator iter = recordings.iterator(); iter.hasNext(); ) { if (iter.next().getEndTimeMs() <= currentTimeMs) { iter.remove(); @@ -939,11 +941,9 @@ public class DvrScheduleManager { } } - /** - * @see InputTaskScheduler#getReplacableTask - */ - private static ScheduledRecording findReplaceableRecording(List recordings, - ScheduledRecording schedule) { + /** @see InputTaskScheduler#getReplacableTask */ + private static ScheduledRecording findReplaceableRecording( + List recordings, ScheduledRecording schedule) { // Returns the recording with the following priority. // 1. The recording with the lowest priority is returned. // 2. If the priorities are the same, the recording which finishes early is returned. @@ -980,30 +980,24 @@ public class DvrScheduleManager { } } - /** - * A listener which is notified the initialization of schedule manager. - */ + /** A listener which is notified the initialization of schedule manager. */ public interface OnInitializeListener { - /** - * Called when the schedule manager has been initialized. - */ + /** Called when the schedule manager has been initialized. */ void onInitialize(); } - /** - * A listener which is notified the conflict state change of the schedules. - */ + /** A listener which is notified the conflict state change of the schedules. */ public interface OnConflictStateChangeListener { /** * Called when the conflicting schedules change. - *

- * Note that this can be called before - * {@link ScheduledRecordingListener#onScheduledRecordingAdded} is called. + * + *

Note that this can be called before {@link + * ScheduledRecordingListener#onScheduledRecordingAdded} is called. * * @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise - * {@code false}. + * {@code false}. * @param schedules the schedules */ void onConflictStateChange(boolean conflict, ScheduledRecording... schedules); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java index 2d41d732..a2f4bda8 100644 --- a/src/com/android/tv/dvr/DvrStorageStatusManager.java +++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java @@ -36,14 +36,11 @@ import android.support.annotation.AnyThread; import android.support.annotation.IntDef; import android.support.annotation.WorkerThread; import android.util.Log; - import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; @@ -54,52 +51,44 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * Signals DVR storage status change such as plugging/unplugging. - */ +/** Signals DVR storage status change such as plugging/unplugging. */ public class DvrStorageStatusManager { private static final String TAG = "DvrStorageStatusManager"; private static final boolean DEBUG = false; - /** - * Minimum storage size to support DVR - */ + /** Minimum storage size to support DVR */ public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB - private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES - = 10 * 1024 * 1024 * 1024L; // 10GB + + private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES = + 10 * 1024 * 1024 * 1024L; // 10GB private static final String RECORDING_DATA_SUB_PATH = "/recording"; private static final String[] PROJECTION = { - TvContract.RecordedPrograms._ID, - TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI + TvContract.RecordedPrograms._ID, + TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, + TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI }; - private final static int BATCH_OPERATION_COUNT = 100; - - @IntDef({STORAGE_STATUS_OK, STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL, - STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, STORAGE_STATUS_MISSING}) + private static final int BATCH_OPERATION_COUNT = 100; + + @IntDef({ + STORAGE_STATUS_OK, + STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL, + STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, + STORAGE_STATUS_MISSING + }) @Retention(RetentionPolicy.SOURCE) - public @interface StorageStatus { - } + public @interface StorageStatus {} - /** - * Current storage is OK to record a program. - */ + /** Current storage is OK to record a program. */ public static final int STORAGE_STATUS_OK = 0; - /** - * Current storage's total capacity is smaller than DVR requirement. - */ + /** Current storage's total capacity is smaller than DVR requirement. */ public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1; - /** - * Current storage's free space is insufficient to record programs. - */ + /** Current storage's free space is insufficient to record programs. */ public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2; - /** - * Current storage is missing. - */ + /** Current storage is missing. */ public static final int STORAGE_STATUS_MISSING = 3; private final Context mContext; @@ -142,8 +131,8 @@ public class DvrStorageStatusManager { /** * Listener for DVR storage status change. * - * @param storageMounted {@code true} when DVR possible storage is mounted, - * {@code false} otherwise. + * @param storageMounted {@code true} when DVR possible storage is mounted, {@code false} + * otherwise. */ void onStorageMountChanged(boolean storageMounted); } @@ -205,24 +194,17 @@ public class DvrStorageStatusManager { mOnStorageMountChangedListeners.add(listener); } - /** - * Removes the current listener. - */ + /** Removes the current listener. */ public void removeListener(OnStorageMountChangedListener listener) { mOnStorageMountChangedListeners.remove(listener); } - /** - * Returns true if a storage is mounted. - */ + /** Returns true if a storage is mounted. */ public boolean isStorageMounted() { return mMountedStorageStatus.mStorageMounted; } - /** - * Returns the path to DVR recording data directory. - * This can take for a while sometimes. - */ + /** Returns the path to DVR recording data directory. This can take for a while sometimes. */ @WorkerThread public File getRecordingRootDataDirectory() { SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper()); @@ -294,8 +276,7 @@ public class DvrStorageStatusManager { storageMountedDir = null; } } - return new MountedStorageStatus( - storageMounted, storageMountedDir, storageMountedCapacity); + return new MountedStorageStatus(storageMounted, storageMountedDir, storageMountedCapacity); } private class CleanUpDbTask extends AsyncTask { @@ -318,11 +299,14 @@ public class DvrStorageStatusManager { if (ops == null || ops.isEmpty()) { return null; } - Log.i(TAG, "New device storage mounted. # of recordings to be forgotten : " - + ops.size()); - for (int i = 0 ; i < ops.size() && !isCancelled() ; i += BATCH_OPERATION_COUNT) { - int toIndex = (i + BATCH_OPERATION_COUNT) > ops.size() - ? ops.size() : (i + BATCH_OPERATION_COUNT); + Log.i( + TAG, + "New device storage mounted. # of recordings to be forgotten : " + ops.size()); + for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) { + int toIndex = + (i + BATCH_OPERATION_COUNT) > ops.size() + ? ops.size() + : (i + BATCH_OPERATION_COUNT); ArrayList batchOps = new ArrayList<>(ops.subList(i, toIndex)); try { @@ -359,14 +343,19 @@ public class DvrStorageStatusManager { private List getDeleteOps() { List ops = new ArrayList<>(); - try (Cursor c = mContentResolver.query( - TvContract.RecordedPrograms.CONTENT_URI, PROJECTION, null, null, null)) { + try (Cursor c = + mContentResolver.query( + TvContract.RecordedPrograms.CONTENT_URI, + PROJECTION, + null, + null, + null)) { if (c == null) { return null; } while (c.moveToNext()) { - @DvrStorageStatusManager.StorageStatus int storageStatus = - getDvrStorageStatus(); + @DvrStorageStatusManager.StorageStatus + int storageStatus = getDvrStorageStatus(); if (isCancelled() || storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { ops.clear(); @@ -380,14 +369,18 @@ public class DvrStorageStatusManager { } Uri dataUri = Uri.parse(dataUriString); if (!Utils.isInBundledPackageSet(packageName) - || dataUri == null || dataUri.getPath() == null + || dataUri == null + || dataUri.getPath() == null || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { continue; } File recordedProgramDir = new File(dataUri.getPath()); if (!recordedProgramDir.exists()) { - ops.add(ContentProviderOperation.newDelete( - TvContract.buildRecordedProgramUri(Long.parseLong(id))).build()); + ops.add( + ContentProviderOperation.newDelete( + TvContract.buildRecordedProgramUri( + Long.parseLong(id))) + .build()); } } return ops; diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java index da6ddb1a..7da2bfc9 100644 --- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java +++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java @@ -20,10 +20,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.media.tv.TvInputManager; import android.support.annotation.IntDef; - import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.dvr.data.RecordedProgram; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -33,8 +31,8 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; /** - * A class to manage DVR watched state. - * It will remember and provides previous watched position of DVR playback. + * A class to manage DVR watched state. It will remember and provides previous watched position of + * DVR playback. */ public class DvrWatchedPositionManager { private SharedPreferences mWatchedPositions; @@ -49,55 +47,46 @@ public class DvrWatchedPositionManager { @Retention(RetentionPolicy.SOURCE) @IntDef({DVR_WATCHED_STATUS_NEW, DVR_WATCHED_STATUS_WATCHING, DVR_WATCHED_STATUS_WATCHED}) public @interface DvrWatchedStatus {} - /** - * The status indicates the recorded program has not been watched at all. - */ + /** The status indicates the recorded program has not been watched at all. */ public static final int DVR_WATCHED_STATUS_NEW = 0; - /** - * The status indicates the recorded program is being watched. - */ + /** The status indicates the recorded program is being watched. */ public static final int DVR_WATCHED_STATUS_WATCHING = 1; - /** - * The status indicates the recorded program was completely watched. - */ + /** The status indicates the recorded program was completely watched. */ public static final int DVR_WATCHED_STATUS_WATCHED = 2; public DvrWatchedPositionManager(Context context) { - mWatchedPositions = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE); + mWatchedPositions = + context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION, + Context.MODE_PRIVATE); } - /** - * Sets the watched position of the give program. - */ + /** Sets the watched position of the give program. */ public void setWatchedPosition(long recordedProgramId, long positionMs) { mWatchedPositions.edit().putLong(Long.toString(recordedProgramId), positionMs).apply(); notifyWatchedPositionChanged(recordedProgramId, positionMs); } - /** - * Gets the watched position of the give program. - */ + /** Gets the watched position of the give program. */ public long getWatchedPosition(long recordedProgramId) { - return mWatchedPositions.getLong(Long.toString(recordedProgramId), - TvInputManager.TIME_SHIFT_INVALID_TIME); + return mWatchedPositions.getLong( + Long.toString(recordedProgramId), TvInputManager.TIME_SHIFT_INVALID_TIME); } - @DvrWatchedStatus public int getWatchedStatus(RecordedProgram recordedProgram) { + @DvrWatchedStatus + public int getWatchedStatus(RecordedProgram recordedProgram) { long watchedPosition = getWatchedPosition(recordedProgram.getId()); if (watchedPosition == TvInputManager.TIME_SHIFT_INVALID_TIME) { return DVR_WATCHED_STATUS_NEW; - } else if (watchedPosition > recordedProgram - .getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) { + } else if (watchedPosition + > recordedProgram.getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) { return DVR_WATCHED_STATUS_WATCHED; } else { return DVR_WATCHED_STATUS_WATCHING; } } - /** - * Adds {@link WatchedPositionChangedListener}. - */ + /** Adds {@link WatchedPositionChangedListener}. */ public void addListener(WatchedPositionChangedListener listener, long recordedProgramId) { if (recordedProgramId == RecordedProgram.ID_NOT_SET) { return; @@ -110,18 +99,14 @@ public class DvrWatchedPositionManager { listenerSet.add(listener); } - /** - * Removes {@link WatchedPositionChangedListener}. - */ + /** Removes {@link WatchedPositionChangedListener}. */ public void removeListener(WatchedPositionChangedListener listener) { for (long recordedProgramId : new ArrayList<>(mListeners.keySet())) { removeListener(listener, recordedProgramId); } } - /** - * Removes {@link WatchedPositionChangedListener}. - */ + /** Removes {@link WatchedPositionChangedListener}. */ public void removeListener(WatchedPositionChangedListener listener, long recordedProgramId) { Set listenerSet = mListeners.get(recordedProgramId); if (listenerSet == null) { @@ -144,9 +129,7 @@ public class DvrWatchedPositionManager { } public interface WatchedPositionChangedListener { - /** - * Called when the watched position of some program is changed. - */ + /** Called when the watched position of some program is changed. */ void onWatchedPositionChanged(long recordedProgramId, long positionMs); } } diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java index 129ba153..059b0a6d 100644 --- a/src/com/android/tv/dvr/WritableDvrDataManager.java +++ b/src/com/android/tv/dvr/WritableDvrDataManager.java @@ -17,7 +17,6 @@ package com.android.tv.dvr; import android.support.annotation.MainThread; - import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.data.SeriesRecording; @@ -30,19 +29,13 @@ import com.android.tv.dvr.data.SeriesRecording; */ @MainThread public interface WritableDvrDataManager extends DvrDataManager { - /** - * Adds new recordings. - */ + /** Adds new recordings. */ void addScheduledRecording(ScheduledRecording... scheduledRecordings); - /** - * Adds new series recordings. - */ + /** Adds new series recordings. */ void addSeriesRecording(SeriesRecording... seriesRecordings); - /** - * Removes recordings. - */ + /** Removes recordings. */ void removeScheduledRecording(ScheduledRecording... scheduledRecordings); /** @@ -51,30 +44,22 @@ public interface WritableDvrDataManager extends DvrDataManager { */ void removeScheduledRecording(boolean forceRemove, ScheduledRecording... scheduledRecordings); - /** - * Removes series recordings. - */ + /** Removes series recordings. */ void removeSeriesRecording(SeriesRecording... seasonSchedules); - /** - * Updates existing recordings. - */ + /** Updates existing recordings. */ void updateScheduledRecording(ScheduledRecording... scheduledRecordings); - /** - * Updates existing series recordings. - */ + /** Updates existing series recordings. */ void updateSeriesRecording(SeriesRecording... seriesRecordings); - /** - * Changes the state of the recording. - */ + /** Changes the state of the recording. */ void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState); /** * Remove all the records related to the input. - *

- * Note that this should be called after the input was removed. + * + *

Note that this should be called after the input was removed. */ void forgetStorage(String inputId); } diff --git a/src/com/android/tv/dvr/data/IdGenerator.java b/src/com/android/tv/dvr/data/IdGenerator.java index 2ade1dad..496651ba 100644 --- a/src/com/android/tv/dvr/data/IdGenerator.java +++ b/src/com/android/tv/dvr/data/IdGenerator.java @@ -18,32 +18,22 @@ package com.android.tv.dvr.data; import java.util.concurrent.atomic.AtomicLong; -/** - * A class which generate the ID which increases sequentially. - */ +/** A class which generate the ID which increases sequentially. */ public class IdGenerator { - /** - * ID generator for the scheduled recording. - */ + /** ID generator for the scheduled recording. */ public static final IdGenerator SCHEDULED_RECORDING = new IdGenerator(); - /** - * ID generator for the series recording. - */ + /** ID generator for the series recording. */ public static final IdGenerator SERIES_RECORDING = new IdGenerator(); private final AtomicLong mMaxId = new AtomicLong(0); - /** - * Sets the new maximum ID. - */ + /** Sets the new maximum ID. */ public void setMaxId(long maxId) { mMaxId.set(maxId); } - /** - * Returns the new ID which is greater than the existing maximum ID by 1. - */ + /** Returns the new ID which is greater than the existing maximum ID by 1. */ public long newId() { return mMaxId.incrementAndGet(); } diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java index 2e953a52..18841ae5 100644 --- a/src/com/android/tv/dvr/data/RecordedProgram.java +++ b/src/com/android/tv/dvr/data/RecordedProgram.java @@ -28,98 +28,96 @@ import android.net.Uri; import android.os.Build; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.common.R; import com.android.tv.common.TvContentRatingCache; import com.android.tv.data.BaseProgram; import com.android.tv.data.GenreItems; import com.android.tv.data.InternalDataUtils; import com.android.tv.util.Utils; - import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.TimeUnit; -/** - * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. - */ +/** Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. */ @TargetApi(Build.VERSION_CODES.N) public class RecordedProgram extends BaseProgram { public static final int ID_NOT_SET = -1; - public final static String[] PROJECTION = { - // These are in exactly the order listed in RecordedPrograms - RecordedPrograms._ID, - RecordedPrograms.COLUMN_PACKAGE_NAME, - RecordedPrograms.COLUMN_INPUT_ID, - RecordedPrograms.COLUMN_CHANNEL_ID, - RecordedPrograms.COLUMN_TITLE, - RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, - RecordedPrograms.COLUMN_SEASON_TITLE, - RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, - RecordedPrograms.COLUMN_EPISODE_TITLE, - RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_BROADCAST_GENRE, - RecordedPrograms.COLUMN_CANONICAL_GENRE, - RecordedPrograms.COLUMN_SHORT_DESCRIPTION, - RecordedPrograms.COLUMN_LONG_DESCRIPTION, - RecordedPrograms.COLUMN_VIDEO_WIDTH, - RecordedPrograms.COLUMN_VIDEO_HEIGHT, - RecordedPrograms.COLUMN_AUDIO_LANGUAGE, - RecordedPrograms.COLUMN_CONTENT_RATING, - RecordedPrograms.COLUMN_POSTER_ART_URI, - RecordedPrograms.COLUMN_THUMBNAIL_URI, - RecordedPrograms.COLUMN_SEARCHABLE, - RecordedPrograms.COLUMN_RECORDING_DATA_URI, - RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, - RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, - RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, - RecordedPrograms.COLUMN_VERSION_NUMBER, - RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + public static final String[] PROJECTION = { + // These are in exactly the order listed in RecordedPrograms + RecordedPrograms._ID, + RecordedPrograms.COLUMN_PACKAGE_NAME, + RecordedPrograms.COLUMN_INPUT_ID, + RecordedPrograms.COLUMN_CHANNEL_ID, + RecordedPrograms.COLUMN_TITLE, + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_SEASON_TITLE, + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_EPISODE_TITLE, + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_BROADCAST_GENRE, + RecordedPrograms.COLUMN_CANONICAL_GENRE, + RecordedPrograms.COLUMN_SHORT_DESCRIPTION, + RecordedPrograms.COLUMN_LONG_DESCRIPTION, + RecordedPrograms.COLUMN_VIDEO_WIDTH, + RecordedPrograms.COLUMN_VIDEO_HEIGHT, + RecordedPrograms.COLUMN_AUDIO_LANGUAGE, + RecordedPrograms.COLUMN_CONTENT_RATING, + RecordedPrograms.COLUMN_POSTER_ART_URI, + RecordedPrograms.COLUMN_THUMBNAIL_URI, + RecordedPrograms.COLUMN_SEARCHABLE, + RecordedPrograms.COLUMN_RECORDING_DATA_URI, + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + RecordedPrograms.COLUMN_VERSION_NUMBER, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, }; public static RecordedProgram fromCursor(Cursor cursor) { int index = 0; - Builder builder = builder() - .setId(cursor.getLong(index++)) - .setPackageName(cursor.getString(index++)) - .setInputId(cursor.getString(index++)) - .setChannelId(cursor.getLong(index++)) - .setTitle(cursor.getString(index++)) - .setSeasonNumber(cursor.getString(index++)) - .setSeasonTitle(cursor.getString(index++)) - .setEpisodeNumber(cursor.getString(index++)) - .setEpisodeTitle(cursor.getString(index++)) - .setStartTimeUtcMillis(cursor.getLong(index++)) - .setEndTimeUtcMillis(cursor.getLong(index++)) - .setBroadcastGenres(cursor.getString(index++)) - .setCanonicalGenres(cursor.getString(index++)) - .setShortDescription(cursor.getString(index++)) - .setLongDescription(cursor.getString(index++)) - .setVideoWidth(cursor.getInt(index++)) - .setVideoHeight(cursor.getInt(index++)) - .setAudioLanguage(cursor.getString(index++)) - .setContentRatings( - TvContentRatingCache.getInstance().getRatings(cursor.getString(index++))) - .setPosterArtUri(cursor.getString(index++)) - .setThumbnailUri(cursor.getString(index++)) - .setSearchable(cursor.getInt(index++) == 1) - .setDataUri(cursor.getString(index++)) - .setDataBytes(cursor.getLong(index++)) - .setDurationMillis(cursor.getLong(index++)) - .setExpireTimeUtcMillis(cursor.getLong(index++)) - .setInternalProviderFlag1(cursor.getInt(index++)) - .setInternalProviderFlag2(cursor.getInt(index++)) - .setInternalProviderFlag3(cursor.getInt(index++)) - .setInternalProviderFlag4(cursor.getInt(index++)) - .setVersionNumber(cursor.getInt(index++)); + Builder builder = + builder() + .setId(cursor.getLong(index++)) + .setPackageName(cursor.getString(index++)) + .setInputId(cursor.getString(index++)) + .setChannelId(cursor.getLong(index++)) + .setTitle(cursor.getString(index++)) + .setSeasonNumber(cursor.getString(index++)) + .setSeasonTitle(cursor.getString(index++)) + .setEpisodeNumber(cursor.getString(index++)) + .setEpisodeTitle(cursor.getString(index++)) + .setStartTimeUtcMillis(cursor.getLong(index++)) + .setEndTimeUtcMillis(cursor.getLong(index++)) + .setBroadcastGenres(cursor.getString(index++)) + .setCanonicalGenres(cursor.getString(index++)) + .setShortDescription(cursor.getString(index++)) + .setLongDescription(cursor.getString(index++)) + .setVideoWidth(cursor.getInt(index++)) + .setVideoHeight(cursor.getInt(index++)) + .setAudioLanguage(cursor.getString(index++)) + .setContentRatings( + TvContentRatingCache.getInstance() + .getRatings(cursor.getString(index++))) + .setPosterArtUri(cursor.getString(index++)) + .setThumbnailUri(cursor.getString(index++)) + .setSearchable(cursor.getInt(index++) == 1) + .setDataUri(cursor.getString(index++)) + .setDataBytes(cursor.getLong(index++)) + .setDurationMillis(cursor.getLong(index++)) + .setExpireTimeUtcMillis(cursor.getLong(index++)) + .setInternalProviderFlag1(cursor.getInt(index++)) + .setInternalProviderFlag2(cursor.getInt(index++)) + .setInternalProviderFlag3(cursor.getInt(index++)) + .setInternalProviderFlag4(cursor.getInt(index++)) + .setVersionNumber(cursor.getInt(index++)); if (Utils.isInBundledPackageSet(builder.mPackageName)) { InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); } @@ -138,12 +136,14 @@ public class RecordedProgram extends BaseProgram { values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle); values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber); values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle); - values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, - recordedProgram.mStartTimeUtcMillis); + values.put( + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, recordedProgram.mStartTimeUtcMillis); values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis); - values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, + values.put( + RecordedPrograms.COLUMN_BROADCAST_GENRE, safeEncode(recordedProgram.mBroadcastGenres)); - values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, + values.put( + RecordedPrograms.COLUMN_CANONICAL_GENRE, safeEncode(recordedProgram.mCanonicalGenres)); values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription); values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription); @@ -158,33 +158,40 @@ public class RecordedProgram extends BaseProgram { values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight); } values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage); - values.put(RecordedPrograms.COLUMN_CONTENT_RATING, + values.put( + RecordedPrograms.COLUMN_CONTENT_RATING, TvContentRatingCache.contentRatingsToString(recordedProgram.mContentRatings)); values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri); values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri); values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0); - values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, - safeToString(recordedProgram.mDataUri)); + values.put( + RecordedPrograms.COLUMN_RECORDING_DATA_URI, safeToString(recordedProgram.mDataUri)); values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes); - values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, - recordedProgram.mDurationMillis); - values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, + values.put( + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, recordedProgram.mDurationMillis); + values.put( + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, recordedProgram.mExpireTimeUtcMillis); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + values.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, InternalDataUtils.serializeInternalProviderData(recordedProgram)); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + values.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, recordedProgram.mInternalProviderFlag1); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + values.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, recordedProgram.mInternalProviderFlag2); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + values.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, recordedProgram.mInternalProviderFlag3); - values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + values.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, recordedProgram.mInternalProviderFlag4); values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber); return values; } - public static class Builder{ + public static class Builder { private long mId = ID_NOT_SET; private String mPackageName; private String mInputId; @@ -414,18 +421,45 @@ public class RecordedProgram extends BaseProgram { // If series ID is not set, generate it for the episodic program of other TV input. setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle)); } - return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId, - mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis, - mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription, - mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRatings, - mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes, - mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1, - mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4, + return new RecordedProgram( + mId, + mPackageName, + mInputId, + mChannelId, + mTitle, + mSeriesId, + mSeasonNumber, + mSeasonTitle, + mEpisodeNumber, + mEpisodeTitle, + mStartTimeUtcMillis, + mEndTimeUtcMillis, + mBroadcastGenres, + mCanonicalGenres, + mShortDescription, + mLongDescription, + mVideoWidth, + mVideoHeight, + mAudioLanguage, + mContentRatings, + mPosterArtUri, + mThumbnailUri, + mSearchable, + mDataUri, + mDataBytes, + mDurationMillis, + mExpireTimeUtcMillis, + mInternalProviderFlag1, + mInternalProviderFlag2, + mInternalProviderFlag3, + mInternalProviderFlag4, mVersionNumber); } } - public static Builder builder() { return new Builder(); } + public static Builder builder() { + return new Builder(); + } public static Builder buildFrom(RecordedProgram orig) { return builder() @@ -470,7 +504,7 @@ public class RecordedProgram extends BaseProgram { } return Long.compare(lhs.mId, rhs.mId); } - }; + }; private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); @@ -507,15 +541,38 @@ public class RecordedProgram extends BaseProgram { private final int mInternalProviderFlag4; private final int mVersionNumber; - private RecordedProgram(long id, String packageName, String inputId, long channelId, - String title, String seriesId, String seasonNumber, String seasonTitle, - String episodeNumber, String episodeTitle, long startTimeUtcMillis, - long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres, - String shortDescription, String longDescription, int videoWidth, int videoHeight, - String audioLanguage, TvContentRating[] contentRatings, String posterArtUri, - String thumbnailUri, boolean searchable, Uri dataUri, long dataBytes, - long durationMillis, long expireTimeUtcMillis, int internalProviderFlag1, - int internalProviderFlag2, int internalProviderFlag3, int internalProviderFlag4, + private RecordedProgram( + long id, + String packageName, + String inputId, + long channelId, + String title, + String seriesId, + String seasonNumber, + String seasonTitle, + String episodeNumber, + String episodeTitle, + long startTimeUtcMillis, + long endTimeUtcMillis, + String[] broadcastGenres, + String[] canonicalGenres, + String shortDescription, + String longDescription, + int videoWidth, + int videoHeight, + String audioLanguage, + TvContentRating[] contentRatings, + String posterArtUri, + String thumbnailUri, + boolean searchable, + Uri dataUri, + long dataBytes, + long durationMillis, + long expireTimeUtcMillis, + int internalProviderFlag1, + int internalProviderFlag2, + int internalProviderFlag3, + int internalProviderFlag4, int versionNumber) { mId = id; mPackageName = packageName; @@ -564,9 +621,7 @@ public class RecordedProgram extends BaseProgram { return mCanonicalGenres; } - /** - * Returns array of canonical genre ID's for this recorded program. - */ + /** Returns array of canonical genre ID's for this recorded program. */ @Override public int[] getCanonicalGenreIds() { if (mCanonicalGenres == null) { @@ -623,11 +678,15 @@ public class RecordedProgram extends BaseProgram { if (!TextUtils.isEmpty(mEpisodeNumber)) { if (TextUtils.equals(mSeasonNumber, "0")) { // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_number_format_no_season_number), mEpisodeNumber); + return String.format( + context.getResources() + .getString(R.string.display_episode_number_format_no_season_number), + mEpisodeNumber); } else { - return String.format(context.getResources().getString( - R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber); + return String.format( + context.getResources().getString(R.string.display_episode_number_format), + mSeasonNumber, + mEpisodeNumber); } } return null; @@ -734,9 +793,7 @@ public class RecordedProgram extends BaseProgram { return mVideoWidth; } - /** - * Checks whether the recording has been clipped or not. - */ + /** Checks whether the recording has been clipped or not. */ public boolean isClipped() { return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS; } @@ -746,40 +803,38 @@ public class RecordedProgram extends BaseProgram { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RecordedProgram that = (RecordedProgram) o; - return Objects.equals(mId, that.mId) && - Objects.equals(mChannelId, that.mChannelId) && - Objects.equals(mSeriesId, that.mSeriesId) && - Objects.equals(mSeasonNumber, that.mSeasonNumber) && - Objects.equals(mSeasonTitle, that.mSeasonTitle) && - Objects.equals(mEpisodeNumber, that.mEpisodeNumber) && - Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) && - Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) && - Objects.equals(mVideoWidth, that.mVideoWidth) && - Objects.equals(mVideoHeight, that.mVideoHeight) && - Objects.equals(mSearchable, that.mSearchable) && - Objects.equals(mDataBytes, that.mDataBytes) && - Objects.equals(mDurationMillis, that.mDurationMillis) && - Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) && - Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) && - Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) && - Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) && - Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) && - Objects.equals(mVersionNumber, that.mVersionNumber) && - Objects.equals(mTitle, that.mTitle) && - Objects.equals(mEpisodeTitle, that.mEpisodeTitle) && - Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) && - Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) && - Objects.equals(mShortDescription, that.mShortDescription) && - Objects.equals(mLongDescription, that.mLongDescription) && - Objects.equals(mAudioLanguage, that.mAudioLanguage) && - Arrays.equals(mContentRatings, that.mContentRatings) && - Objects.equals(mPosterArtUri, that.mPosterArtUri) && - Objects.equals(mThumbnailUri, that.mThumbnailUri); - } - - /** - * Hashes based on the ID. - */ + return Objects.equals(mId, that.mId) + && Objects.equals(mChannelId, that.mChannelId) + && Objects.equals(mSeriesId, that.mSeriesId) + && Objects.equals(mSeasonNumber, that.mSeasonNumber) + && Objects.equals(mSeasonTitle, that.mSeasonTitle) + && Objects.equals(mEpisodeNumber, that.mEpisodeNumber) + && Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) + && Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) + && Objects.equals(mVideoWidth, that.mVideoWidth) + && Objects.equals(mVideoHeight, that.mVideoHeight) + && Objects.equals(mSearchable, that.mSearchable) + && Objects.equals(mDataBytes, that.mDataBytes) + && Objects.equals(mDurationMillis, that.mDurationMillis) + && Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) + && Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) + && Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) + && Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) + && Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) + && Objects.equals(mVersionNumber, that.mVersionNumber) + && Objects.equals(mTitle, that.mTitle) + && Objects.equals(mEpisodeTitle, that.mEpisodeTitle) + && Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) + && Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) + && Objects.equals(mShortDescription, that.mShortDescription) + && Objects.equals(mLongDescription, that.mLongDescription) + && Objects.equals(mAudioLanguage, that.mAudioLanguage) + && Arrays.equals(mContentRatings, that.mContentRatings) + && Objects.equals(mPosterArtUri, that.mPosterArtUri) + && Objects.equals(mThumbnailUri, that.mThumbnailUri); + } + + /** Hashes based on the ID. */ @Override public int hashCode() { return Objects.hash(mId); @@ -788,42 +843,80 @@ public class RecordedProgram extends BaseProgram { @Override public String toString() { return "RecordedProgram" - + "[" + mId + - "]{ mPackageName=" + mPackageName + - ", mInputId='" + mInputId + '\'' + - ", mChannelId='" + mChannelId + '\'' + - ", mTitle='" + mTitle + '\'' + - ", mSeriesId='" + mSeriesId + '\'' + - ", mEpisodeNumber=" + mEpisodeNumber + - ", mEpisodeTitle='" + mEpisodeTitle + '\'' + - ", mStartTimeUtcMillis=" + mStartTimeUtcMillis + - ", mEndTimeUtcMillis=" + mEndTimeUtcMillis + - ", mBroadcastGenres=" + - (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") + - ", mCanonicalGenres=" + - (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") + - ", mShortDescription='" + mShortDescription + '\'' + - ", mLongDescription='" + mLongDescription + '\'' + - ", mVideoHeight=" + mVideoHeight + - ", mVideoWidth=" + mVideoWidth + - ", mAudioLanguage='" + mAudioLanguage + '\'' + - ", mContentRatings='" + - TvContentRatingCache.contentRatingsToString(mContentRatings) + '\'' + - ", mPosterArtUri=" + mPosterArtUri + - ", mThumbnailUri=" + mThumbnailUri + - ", mSearchable=" + mSearchable + - ", mDataUri=" + mDataUri + - ", mDataBytes=" + mDataBytes + - ", mDurationMillis=" + mDurationMillis + - ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis + - ", mInternalProviderFlag1=" + mInternalProviderFlag1 + - ", mInternalProviderFlag2=" + mInternalProviderFlag2 + - ", mInternalProviderFlag3=" + mInternalProviderFlag3 + - ", mInternalProviderFlag4=" + mInternalProviderFlag4 + - ", mSeasonNumber=" + mSeasonNumber + - ", mSeasonTitle=" + mSeasonTitle + - ", mVersionNumber=" + mVersionNumber + - '}'; + + "[" + + mId + + "]{ mPackageName=" + + mPackageName + + ", mInputId='" + + mInputId + + '\'' + + ", mChannelId='" + + mChannelId + + '\'' + + ", mTitle='" + + mTitle + + '\'' + + ", mSeriesId='" + + mSeriesId + + '\'' + + ", mEpisodeNumber=" + + mEpisodeNumber + + ", mEpisodeTitle='" + + mEpisodeTitle + + '\'' + + ", mStartTimeUtcMillis=" + + mStartTimeUtcMillis + + ", mEndTimeUtcMillis=" + + mEndTimeUtcMillis + + ", mBroadcastGenres=" + + (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") + + ", mCanonicalGenres=" + + (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") + + ", mShortDescription='" + + mShortDescription + + '\'' + + ", mLongDescription='" + + mLongDescription + + '\'' + + ", mVideoHeight=" + + mVideoHeight + + ", mVideoWidth=" + + mVideoWidth + + ", mAudioLanguage='" + + mAudioLanguage + + '\'' + + ", mContentRatings='" + + TvContentRatingCache.contentRatingsToString(mContentRatings) + + '\'' + + ", mPosterArtUri=" + + mPosterArtUri + + ", mThumbnailUri=" + + mThumbnailUri + + ", mSearchable=" + + mSearchable + + ", mDataUri=" + + mDataUri + + ", mDataBytes=" + + mDataBytes + + ", mDurationMillis=" + + mDurationMillis + + ", mExpireTimeUtcMillis=" + + mExpireTimeUtcMillis + + ", mInternalProviderFlag1=" + + mInternalProviderFlag1 + + ", mInternalProviderFlag2=" + + mInternalProviderFlag2 + + ", mInternalProviderFlag3=" + + mInternalProviderFlag3 + + ", mInternalProviderFlag4=" + + mInternalProviderFlag4 + + ", mSeasonNumber=" + + mSeasonNumber + + ", mSeasonTitle=" + + mSeasonTitle + + ", mVersionNumber=" + + mVersionNumber + + '}'; } @Nullable @@ -836,9 +929,7 @@ public class RecordedProgram extends BaseProgram { return genres == null ? null : TvContract.Programs.Genres.encode(genres); } - /** - * Returns an array containing all of the elements in the list. - */ + /** Returns an array containing all of the elements in the list. */ public static RecordedProgram[] toArray(Collection recordedPrograms) { return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]); } diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java index 5d11c0f3..7de37ebc 100644 --- a/src/com/android/tv/dvr/data/ScheduledRecording.java +++ b/src/com/android/tv/dvr/data/ScheduledRecording.java @@ -24,7 +24,6 @@ import android.os.Parcelable; import android.support.annotation.IntDef; import android.text.TextUtils; import android.util.Range; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -34,89 +33,75 @@ import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.util.CompositeComparator; import com.android.tv.util.Utils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; import java.util.Comparator; import java.util.Objects; -/** - * A data class for one recording contents. - */ +/** A data class for one recording contents. */ public final class ScheduledRecording implements Parcelable { private static final String TAG = "ScheduledRecording"; - /** - * Indicates that the ID is not assigned yet. - */ + /** Indicates that the ID is not assigned yet. */ public static final long ID_NOT_SET = 0; - /** - * The default priority of the recording. - */ + /** The default priority of the recording. */ public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; - /** - * Compares the start time in ascending order. - */ - public static final Comparator START_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs); - } - }; - - /** - * Compares the end time in ascending order. - */ - public static final Comparator END_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs); - } - }; - - /** - * Compares ID in ascending order. The schedule with the larger ID was created later. - */ - public static final Comparator ID_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mId, rhs.mId); - } - }; - - /** - * Compares the priority in ascending order. - */ - public static final Comparator PRIORITY_COMPARATOR - = new Comparator() { - @Override - public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { - return Long.compare(lhs.mPriority, rhs.mPriority); - } - }; + /** Compares the start time in ascending order. */ + public static final Comparator START_TIME_COMPARATOR = + new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs); + } + }; + + /** Compares the end time in ascending order. */ + public static final Comparator END_TIME_COMPARATOR = + new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs); + } + }; + + /** Compares ID in ascending order. The schedule with the larger ID was created later. */ + public static final Comparator ID_COMPARATOR = + new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mId, rhs.mId); + } + }; + + /** Compares the priority in ascending order. */ + public static final Comparator PRIORITY_COMPARATOR = + new Comparator() { + @Override + public int compare(ScheduledRecording lhs, ScheduledRecording rhs) { + return Long.compare(lhs.mPriority, rhs.mPriority); + } + }; /** * Compares start time in ascending order and then priority in descending order and then ID in * descending order. */ - public static final Comparator START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR - = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(), - ID_COMPARATOR.reversed()); + public static final Comparator START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR = + new CompositeComparator<>( + START_TIME_COMPARATOR, + PRIORITY_COMPARATOR.reversed(), + ID_COMPARATOR.reversed()); - /** - * Builds scheduled recordings from programs. - */ + /** Builds scheduled recordings from programs. */ public static Builder builder(String inputId, Program p) { return new Builder() .setInputId(inputId) .setChannelId(p.getChannelId()) - .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis()) + .setStartTimeMs(p.getStartTimeUtcMillis()) + .setEndTimeMs(p.getEndTimeUtcMillis()) .setProgramId(p.getId()) .setProgramTitle(p.getTitle()) .setSeasonNumber(p.getSeasonNumber()) @@ -138,9 +123,7 @@ public final class ScheduledRecording implements Parcelable { .setType(TYPE_TIMED); } - /** - * Creates a new Builder with the values set from the {@link RecordedProgram}. - */ + /** Creates a new Builder with the values set from the {@link RecordedProgram}. */ public static Builder builder(RecordedProgram p) { boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle()); return new Builder() @@ -180,7 +163,7 @@ public final class ScheduledRecording implements Parcelable { private @RecordingState int mState; private long mSeriesRecordingId = ID_NOT_SET; - private Builder() { } + private Builder() {} public Builder setId(long id) { mId = id; @@ -273,16 +256,29 @@ public final class ScheduledRecording implements Parcelable { } public ScheduledRecording build() { - return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId, - mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, - mEpisodeTitle, mProgramDescription, mProgramLongDescription, - mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId); - } - } - - /** - * Creates {@link Builder} object from the given original {@code Recording}. - */ + return new ScheduledRecording( + mId, + mPriority, + mInputId, + mChannelId, + mProgramId, + mProgramTitle, + mType, + mStartTimeMs, + mEndTimeMs, + mSeasonNumber, + mEpisodeNumber, + mEpisodeTitle, + mProgramDescription, + mProgramLongDescription, + mProgramPosterArtUri, + mProgramThumbnailUri, + mState, + mSeriesRecordingId); + } + } + + /** Creates {@link Builder} object from the given original {@code Recording}. */ public static Builder buildFrom(ScheduledRecording orig) { return new Builder() .setId(orig.mId) @@ -301,14 +297,22 @@ public final class ScheduledRecording implements Parcelable { .setProgramLongDescription(orig.getProgramLongDescription()) .setProgramPosterArtUri(orig.getProgramPosterArtUri()) .setProgramThumbnailUri(orig.getProgramThumbnailUri()) - .setState(orig.mState).setType(orig.mType); + .setState(orig.mState) + .setType(orig.mType); } @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED, - STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED, - STATE_RECORDING_CANCELED}) + @IntDef({ + STATE_RECORDING_NOT_STARTED, + STATE_RECORDING_IN_PROGRESS, + STATE_RECORDING_FINISHED, + STATE_RECORDING_FAILED, + STATE_RECORDING_CLIPPED, + STATE_RECORDING_DELETED, + STATE_RECORDING_CANCELED + }) public @interface RecordingState {} + public static final int STATE_RECORDING_NOT_STARTED = 0; public static final int STATE_RECORDING_IN_PROGRESS = 1; public static final int STATE_RECORDING_FINISHED = 2; @@ -320,45 +324,40 @@ public final class ScheduledRecording implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_TIMED, TYPE_PROGRAM}) public @interface RecordingType {} - /** - * Record with given time range. - */ + /** Record with given time range. */ public static final int TYPE_TIMED = 1; - /** - * Record with a given program. - */ + /** Record with a given program. */ public static final int TYPE_PROGRAM = 2; @RecordingType private final int mType; /** - * Use this projection if you want to create {@link ScheduledRecording} object using - * {@link #fromCursor}. + * Use this projection if you want to create {@link ScheduledRecording} object using {@link + * #fromCursor}. */ public static final String[] PROJECTION = { - // Columns must match what is read in #fromCursor - Schedules._ID, - Schedules.COLUMN_PRIORITY, - Schedules.COLUMN_TYPE, - Schedules.COLUMN_INPUT_ID, - Schedules.COLUMN_CHANNEL_ID, - Schedules.COLUMN_PROGRAM_ID, - Schedules.COLUMN_PROGRAM_TITLE, - Schedules.COLUMN_START_TIME_UTC_MILLIS, - Schedules.COLUMN_END_TIME_UTC_MILLIS, - Schedules.COLUMN_SEASON_NUMBER, - Schedules.COLUMN_EPISODE_NUMBER, - Schedules.COLUMN_EPISODE_TITLE, - Schedules.COLUMN_PROGRAM_DESCRIPTION, - Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, - Schedules.COLUMN_PROGRAM_POST_ART_URI, - Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, - Schedules.COLUMN_STATE, - Schedules.COLUMN_SERIES_RECORDING_ID}; + // Columns must match what is read in #fromCursor + Schedules._ID, + Schedules.COLUMN_PRIORITY, + Schedules.COLUMN_TYPE, + Schedules.COLUMN_INPUT_ID, + Schedules.COLUMN_CHANNEL_ID, + Schedules.COLUMN_PROGRAM_ID, + Schedules.COLUMN_PROGRAM_TITLE, + Schedules.COLUMN_START_TIME_UTC_MILLIS, + Schedules.COLUMN_END_TIME_UTC_MILLIS, + Schedules.COLUMN_SEASON_NUMBER, + Schedules.COLUMN_EPISODE_NUMBER, + Schedules.COLUMN_EPISODE_TITLE, + Schedules.COLUMN_PROGRAM_DESCRIPTION, + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, + Schedules.COLUMN_PROGRAM_POST_ART_URI, + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, + Schedules.COLUMN_STATE, + Schedules.COLUMN_SERIES_RECORDING_ID + }; - /** - * Creates {@link ScheduledRecording} object from the given {@link Cursor}. - */ + /** Creates {@link ScheduledRecording} object from the given {@link Cursor}. */ public static ScheduledRecording fromCursor(Cursor c) { int index = -1; return new Builder() @@ -437,36 +436,33 @@ public final class ScheduledRecording implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public ScheduledRecording createFromParcel(Parcel in) { - return ScheduledRecording.fromParcel(in); - } - - @Override - public ScheduledRecording[] newArray(int size) { - return new ScheduledRecording[size]; - } - }; - - /** - * The ID internal to Live TV - */ + @Override + public ScheduledRecording createFromParcel(Parcel in) { + return ScheduledRecording.fromParcel(in); + } + + @Override + public ScheduledRecording[] newArray(int size) { + return new ScheduledRecording[size]; + } + }; + + /** The ID internal to Live TV */ private long mId; /** * The priority of this recording. * - *

The highest number is recorded first. If there is a tie in priority then the higher id + *

The highest number is recorded first. If there is a tie in priority then the higher id * wins. */ private final long mPriority; private final String mInputId; private final long mChannelId; - /** - * Optional id of the associated program. - */ + /** Optional id of the associated program. */ private final long mProgramId; + private final String mProgramTitle; private final long mStartTimeMs; @@ -481,11 +477,25 @@ public final class ScheduledRecording implements Parcelable { @RecordingState private final int mState; private final long mSeriesRecordingId; - private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId, - String programTitle, @RecordingType int type, long startTime, long endTime, - String seasonNumber, String episodeNumber, String episodeTitle, - String programDescription, String programLongDescription, String programPosterArtUri, - String programThumbnailUri, @RecordingState int state, long seriesRecordingId) { + private ScheduledRecording( + long id, + long priority, + String inputId, + long channelId, + long programId, + String programTitle, + @RecordingType int type, + long startTime, + long endTime, + String seasonNumber, + String episodeNumber, + String episodeTitle, + String programDescription, + String programLongDescription, + String programPosterArtUri, + String programThumbnailUri, + @RecordingState int state, + long seriesRecordingId) { mId = id; mPriority = priority; mInputId = inputId; @@ -507,125 +517,96 @@ public final class ScheduledRecording implements Parcelable { } /** - * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and - * {@link #TYPE_TIMED}. + * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and {@link + * #TYPE_TIMED}. */ @RecordingType public int getType() { return mType; } - /** - * Returns schedules' input id. - */ + /** Returns schedules' input id. */ public String getInputId() { return mInputId; } - /** - * Returns recorded {@link Channel}. - */ + /** Returns recorded {@link Channel}. */ public long getChannelId() { return mChannelId; } - /** - * Return the optional program id - */ + /** Return the optional program id */ public long getProgramId() { return mProgramId; } - /** - * Return the optional program Title - */ + /** Return the optional program Title */ public String getProgramTitle() { return mProgramTitle; } - /** - * Returns started time. - */ + /** Returns started time. */ public long getStartTimeMs() { return mStartTimeMs; } - /** - * Returns ended time. - */ + /** Returns ended time. */ public long getEndTimeMs() { return mEndTimeMs; } - /** - * Returns the season number. - */ + /** Returns the season number. */ public String getSeasonNumber() { return mSeasonNumber; } - /** - * Returns the episode number. - */ + /** Returns the episode number. */ public String getEpisodeNumber() { return mEpisodeNumber; } - /** - * Returns the episode title. - */ + /** Returns the episode title. */ public String getEpisodeTitle() { return mEpisodeTitle; } - /** - * Returns the description of program. - */ + /** Returns the description of program. */ public String getProgramDescription() { return mProgramDescription; } - /** - * Returns the long description of program. - */ + /** Returns the long description of program. */ public String getProgramLongDescription() { return mProgramLongDescription; } - /** - * Returns the poster uri of program. - */ + /** Returns the poster uri of program. */ public String getProgramPosterArtUri() { return mProgramPosterArtUri; } - /** - * Returns the thumb nail uri of program. - */ + /** Returns the thumb nail uri of program. */ public String getProgramThumbnailUri() { return mProgramThumbnailUri; } - /** - * Returns duration. - */ + /** Returns duration. */ public long getDuration() { return mEndTimeMs - mStartTimeMs; } /** - * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED}, - * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, - * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and - * {@link #STATE_RECORDING_DELETED}. + * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED}, {@link + * #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, {@link + * #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and {@link + * #STATE_RECORDING_DELETED}. */ - @RecordingState public int getState() { + @RecordingState + public int getState() { return mState; } - /** - * Returns the ID of the {@link SeriesRecording} including this schedule. - */ + /** Returns the ID of the {@link SeriesRecording} including this schedule. */ public long getSeriesRecordingId() { return mSeriesRecordingId; } @@ -634,9 +615,7 @@ public final class ScheduledRecording implements Parcelable { return mId; } - /** - * Sets the ID; - */ + /** Sets the ID; */ public void setId(long id) { mId = id; } @@ -645,21 +624,23 @@ public final class ScheduledRecording implements Parcelable { return mPriority; } - /** - * Returns season number, episode number and episode title for display. - */ + /** Returns season number, episode number and episode title for display. */ public String getEpisodeDisplayTitle(Context context) { if (!TextUtils.isEmpty(mEpisodeNumber)) { String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; if (TextUtils.equals(mSeasonNumber, "0")) { // Do not show "S0: ". - return String.format(context.getResources().getString( - R.string.display_episode_title_format_no_season_number), - mEpisodeNumber, episodeTitle); + return String.format( + context.getResources() + .getString(R.string.display_episode_title_format_no_season_number), + mEpisodeNumber, + episodeTitle); } else { - return String.format(context.getResources().getString( - R.string.display_episode_title_format), - mSeasonNumber, mEpisodeNumber, episodeTitle); + return String.format( + context.getResources().getString(R.string.display_episode_title_format), + mSeasonNumber, + mEpisodeNumber, + episodeTitle); } } return mEpisodeTitle; @@ -673,15 +654,14 @@ public final class ScheduledRecording implements Parcelable { if (!TextUtils.isEmpty(mProgramTitle)) { return mProgramTitle; } - Channel channel = TvApplication.getSingletons(context).getChannelDataManager() - .getChannel(mChannelId); - return channel != null ? channel.getDisplayName() + Channel channel = + TvApplication.getSingletons(context).getChannelDataManager().getChannel(mChannelId); + return channel != null + ? channel.getDisplayName() : context.getString(R.string.no_program_information); } - /** - * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. - */ + /** Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. */ private static @RecordingType int recordingType(String type) { switch (type) { case Schedules.TYPE_TIMED: @@ -694,9 +674,7 @@ public final class ScheduledRecording implements Parcelable { } } - /** - * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}. - */ + /** Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}. */ private static String recordingType(@RecordingType int type) { switch (type) { case TYPE_TIMED: @@ -710,8 +688,8 @@ public final class ScheduledRecording implements Parcelable { } /** - * Converts a string to a @RecordingState int, defaulting to - * {@link #STATE_RECORDING_NOT_STARTED}. + * Converts a string to a @RecordingState int, defaulting to {@link + * #STATE_RECORDING_NOT_STARTED}. */ private static @RecordingState int recordingState(String state) { switch (state) { @@ -736,8 +714,8 @@ public final class ScheduledRecording implements Parcelable { } /** - * Converts a @RecordingState int to string, defaulting to - * {@link Schedules#STATE_RECORDING_NOT_STARTED}. + * Converts a @RecordingState int to string, defaulting to {@link + * Schedules#STATE_RECORDING_NOT_STARTED}. */ private static String recordingState(@RecordingState int state) { switch (state) { @@ -761,41 +739,61 @@ public final class ScheduledRecording implements Parcelable { } } - /** - * Checks if the {@code period} overlaps with the recording time. - */ + /** Checks if the {@code period} overlaps with the recording time. */ public boolean isOverLapping(Range period) { return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower(); } - /** - * Checks if the {@code schedule} overlaps with this schedule. - */ + /** Checks if the {@code schedule} overlaps with this schedule. */ public boolean isOverLapping(ScheduledRecording schedule) { return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs(); } @Override public String toString() { - return "ScheduledRecording[" + mId + return "ScheduledRecording[" + + mId + "]" - + "(inputId=" + mInputId - + ",channelId=" + mChannelId - + ",programId=" + mProgramId - + ",programTitle=" + mProgramTitle - + ",type=" + mType - + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")" - + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")" - + ",seasonNumber=" + mSeasonNumber - + ",episodeNumber=" + mEpisodeNumber - + ",episodeTitle=" + mEpisodeTitle - + ",programDescription=" + mProgramDescription - + ",programLongDescription=" + mProgramLongDescription - + ",programPosterArtUri=" + mProgramPosterArtUri - + ",programThumbnailUri=" + mProgramThumbnailUri - + ",state=" + mState - + ",priority=" + mPriority - + ",seriesRecordingId=" + mSeriesRecordingId + + "(inputId=" + + mInputId + + ",channelId=" + + mChannelId + + ",programId=" + + mProgramId + + ",programTitle=" + + mProgramTitle + + ",type=" + + mType + + ",startTime=" + + Utils.toIsoDateTimeString(mStartTimeMs) + + "(" + + mStartTimeMs + + ")" + + ",endTime=" + + Utils.toIsoDateTimeString(mEndTimeMs) + + "(" + + mEndTimeMs + + ")" + + ",seasonNumber=" + + mSeasonNumber + + ",episodeNumber=" + + mEpisodeNumber + + ",episodeTitle=" + + mEpisodeTitle + + ",programDescription=" + + mProgramDescription + + ",programLongDescription=" + + mProgramLongDescription + + ",programPosterArtUri=" + + mProgramPosterArtUri + + ",programThumbnailUri=" + + mProgramThumbnailUri + + ",state=" + + mState + + ",priority=" + + mPriority + + ",seriesRecordingId=" + + mSeriesRecordingId + ")"; } @@ -826,16 +824,12 @@ public final class ScheduledRecording implements Parcelable { out.writeLong(mSeriesRecordingId); } - /** - * Returns {@code true} if the recording is not started yet, otherwise @{code false}. - */ + /** Returns {@code true} if the recording is not started yet, otherwise @{code false}. */ public boolean isNotStarted() { return mState == STATE_RECORDING_NOT_STARTED; } - /** - * Returns {@code true} if the recording is in progress, otherwise @{code false}. - */ + /** Returns {@code true} if the recording is in progress, otherwise @{code false}. */ public boolean isInProgress() { return mState == STATE_RECORDING_IN_PROGRESS; } @@ -867,15 +861,27 @@ public final class ScheduledRecording implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType, - mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle, - mProgramDescription, mProgramLongDescription, mProgramPosterArtUri, - mProgramThumbnailUri, mState, mSeriesRecordingId); - } - - /** - * Returns an array containing all of the elements in the list. - */ + return Objects.hash( + mId, + mPriority, + mChannelId, + mProgramId, + mProgramTitle, + mType, + mStartTimeMs, + mEndTimeMs, + mSeasonNumber, + mEpisodeNumber, + mEpisodeTitle, + mProgramDescription, + mProgramLongDescription, + mProgramPosterArtUri, + mProgramThumbnailUri, + mState, + mSeriesRecordingId); + } + + /** Returns an array containing all of the elements in the list. */ public static ScheduledRecording[] toArray(Collection schedules) { return schedules.toArray(new ScheduledRecording[schedules.size()]); } diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java index 89533dbb..c697451a 100644 --- a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java +++ b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java @@ -17,20 +17,15 @@ package com.android.tv.dvr.data; import android.text.TextUtils; - import java.util.Objects; -/** - * A plain java object which includes the season/episode number for the series recording. - */ +/** A plain java object which includes the season/episode number for the series recording. */ public class SeasonEpisodeNumber { public final long seriesRecordingId; public final String seasonNumber; public final String episodeNumber; - /** - * Creates a new Builder with the values set from an existing {@link ScheduledRecording}. - */ + /** Creates a new Builder with the values set from an existing {@link ScheduledRecording}. */ public SeasonEpisodeNumber(ScheduledRecording r) { this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()); } @@ -47,7 +42,8 @@ public class SeasonEpisodeNumber { return true; } if (!(o instanceof SeasonEpisodeNumber) - || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) { + || TextUtils.isEmpty(seasonNumber) + || TextUtils.isEmpty(episodeNumber)) { return false; } SeasonEpisodeNumber that = (SeasonEpisodeNumber) o; @@ -63,10 +59,13 @@ public class SeasonEpisodeNumber { @Override public String toString() { - return "SeasonEpisodeNumber{" + - "seriesRecordingId=" + seriesRecordingId + - ", seasonNumber='" + seasonNumber + - ", episodeNumber=" + episodeNumber + - '}'; + return "SeasonEpisodeNumber{" + + "seriesRecordingId=" + + seriesRecordingId + + ", seasonNumber='" + + seasonNumber + + ", episodeNumber=" + + episodeNumber + + '}'; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/data/SeriesInfo.java b/src/com/android/tv/dvr/data/SeriesInfo.java index a0dec4a4..aa622c3d 100644 --- a/src/com/android/tv/dvr/data/SeriesInfo.java +++ b/src/com/android/tv/dvr/data/SeriesInfo.java @@ -16,9 +16,7 @@ package com.android.tv.dvr.data; -/** - * Series information. - */ +/** Series information. */ public class SeriesInfo { private final String mId; private final String mTitle; @@ -28,8 +26,14 @@ public class SeriesInfo { private final String mPosterUri; private final String mPhotoUri; - public SeriesInfo(String id, String title, String description, String longDescription, - int[] canonicalGenreIds, String posterUri, String photoUri) { + public SeriesInfo( + String id, + String title, + String description, + String longDescription, + int[] canonicalGenreIds, + String posterUri, + String photoUri) { this.mId = id; this.mTitle = title; this.mDescription = description; @@ -39,37 +43,37 @@ public class SeriesInfo { this.mPhotoUri = photoUri; } - /** Returns the ID. **/ + /** Returns the ID. * */ public String getId() { return mId; } - /** Returns the title. **/ + /** Returns the title. * */ public String getTitle() { return mTitle; } - /** Returns the description. **/ + /** Returns the description. * */ public String getDescription() { return mDescription; } - /** Returns the description. **/ + /** Returns the description. * */ public String getLongDescription() { return mLongDescription; } - /** Returns the canonical genre IDs. **/ + /** Returns the canonical genre IDs. * */ public int[] getCanonicalGenreIds() { return mCanonicalGenreIds; } - /** Returns the poster URI. **/ + /** Returns the poster URI. * */ public String getPosterUri() { return mPosterUri; } - /** Returns the photo URI. **/ + /** Returns the photo URI. * */ public String getPhotoUri() { return mPhotoUri; } diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java index 822e7320..1fd1cea3 100644 --- a/src/com/android/tv/dvr/data/SeriesRecording.java +++ b/src/com/android/tv/dvr/data/SeriesRecording.java @@ -21,15 +21,12 @@ import android.database.Cursor; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.IntDef; -import android.support.annotation.VisibleForTesting; import android.text.TextUtils; - import com.android.tv.data.BaseProgram; import com.android.tv.data.Program; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; import com.android.tv.util.Utils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -40,68 +37,55 @@ import java.util.Objects; /** * Schedules the recording of a Series of Programs. * - *

- * Contains the data needed to create new ScheduleRecordings as the programs become available in + *

Contains the data needed to create new ScheduleRecordings as the programs become available in * the EPG. */ public class SeriesRecording implements Parcelable { - /** - * Indicates that the ID is not assigned yet. - */ + /** Indicates that the ID is not assigned yet. */ public static final long ID_NOT_SET = 0; - /** - * The default priority of this recording. - */ + /** The default priority of this recording. */ public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL}) + @IntDef( + flag = true, + value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL} + ) public @interface ChannelOption {} - /** - * An option which indicates that the episodes in one channel are recorded. - */ + /** An option which indicates that the episodes in one channel are recorded. */ public static final int OPTION_CHANNEL_ONE = 0; - /** - * An option which indicates that the episodes in all the channels are recorded. - */ + /** An option which indicates that the episodes in all the channels are recorded. */ public static final int OPTION_CHANNEL_ALL = 1; @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED}) + @IntDef( + flag = true, + value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED} + ) public @interface SeriesState {} - /** - * The state indicates that the series recording is a normal one. - */ + /** The state indicates that the series recording is a normal one. */ public static final int STATE_SERIES_NORMAL = 0; - /** - * The state indicates that the series recording is stopped. - */ + /** The state indicates that the series recording is stopped. */ public static final int STATE_SERIES_STOPPED = 1; - /** - * Compare priority in descending order. - */ + /** Compare priority in descending order. */ public static final Comparator PRIORITY_COMPARATOR = new Comparator() { - @Override - public int compare(SeriesRecording lhs, SeriesRecording rhs) { - int value = Long.compare(rhs.mPriority, lhs.mPriority); - if (value == 0) { - // New recording has the higher priority. - value = Long.compare(rhs.mId, lhs.mId); - } - return value; - } - }; + @Override + public int compare(SeriesRecording lhs, SeriesRecording rhs) { + int value = Long.compare(rhs.mPriority, lhs.mPriority); + if (value == 0) { + // New recording has the higher priority. + value = Long.compare(rhs.mId, lhs.mId); + } + return value; + } + }; - /** - * Compare ID in ascending order. - */ + /** Compare ID in ascending order. */ public static final Comparator ID_COMPARATOR = new Comparator() { @Override @@ -126,9 +110,7 @@ public class SeriesRecording implements Parcelable { .setPhotoUri(p.getThumbnailUri()); } - /** - * Creates a new Builder with the values set from an existing {@link SeriesRecording}. - */ + /** Creates a new Builder with the values set from an existing {@link SeriesRecording}. */ public static Builder buildFrom(SeriesRecording r) { return new Builder() .setId(r.mId) @@ -149,30 +131,28 @@ public class SeriesRecording implements Parcelable { } /** - * Use this projection if you want to create {@link SeriesRecording} object using - * {@link #fromCursor}. + * Use this projection if you want to create {@link SeriesRecording} object using {@link + * #fromCursor}. */ public static final String[] PROJECTION = { - // Columns must match what is read in fromCursor() - SeriesRecordings._ID, - SeriesRecordings.COLUMN_INPUT_ID, - SeriesRecordings.COLUMN_CHANNEL_ID, - SeriesRecordings.COLUMN_PRIORITY, - SeriesRecordings.COLUMN_TITLE, - SeriesRecordings.COLUMN_SHORT_DESCRIPTION, - SeriesRecordings.COLUMN_LONG_DESCRIPTION, - SeriesRecordings.COLUMN_SERIES_ID, - SeriesRecordings.COLUMN_START_FROM_EPISODE, - SeriesRecordings.COLUMN_START_FROM_SEASON, - SeriesRecordings.COLUMN_CHANNEL_OPTION, - SeriesRecordings.COLUMN_CANONICAL_GENRE, - SeriesRecordings.COLUMN_POSTER_URI, - SeriesRecordings.COLUMN_PHOTO_URI, - SeriesRecordings.COLUMN_STATE + // Columns must match what is read in fromCursor() + SeriesRecordings._ID, + SeriesRecordings.COLUMN_INPUT_ID, + SeriesRecordings.COLUMN_CHANNEL_ID, + SeriesRecordings.COLUMN_PRIORITY, + SeriesRecordings.COLUMN_TITLE, + SeriesRecordings.COLUMN_SHORT_DESCRIPTION, + SeriesRecordings.COLUMN_LONG_DESCRIPTION, + SeriesRecordings.COLUMN_SERIES_ID, + SeriesRecordings.COLUMN_START_FROM_EPISODE, + SeriesRecordings.COLUMN_START_FROM_SEASON, + SeriesRecordings.COLUMN_CHANNEL_OPTION, + SeriesRecordings.COLUMN_CANONICAL_GENRE, + SeriesRecordings.COLUMN_POSTER_URI, + SeriesRecordings.COLUMN_PHOTO_URI, + SeriesRecordings.COLUMN_STATE }; - /** - * Creates {@link SeriesRecording} object from the given {@link Cursor}. - */ + /** Creates {@link SeriesRecording} object from the given {@link Cursor}. */ public static SeriesRecording fromCursor(Cursor c) { int index = -1; return new Builder() @@ -195,8 +175,8 @@ public class SeriesRecording implements Parcelable { } /** - * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings} - * and the values from {@code r}. + * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings} and + * the values from {@code r}. */ public static ContentValues toContentValues(SeriesRecording r) { ContentValues values = new ContentValues(); @@ -214,9 +194,9 @@ public class SeriesRecording implements Parcelable { values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId()); values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode()); values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason()); - values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION, - channelOption(r.getChannelOption())); - values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE, + values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION, channelOption(r.getChannelOption())); + values.put( + SeriesRecordings.COLUMN_CANONICAL_GENRE, Utils.getCanonicalGenre(r.getCanonicalGenreIds())); values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri()); values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri()); @@ -234,7 +214,8 @@ public class SeriesRecording implements Parcelable { return SeriesRecordings.OPTION_CHANNEL_ONE; } - @ChannelOption private static int channelOption(String option) { + @ChannelOption + private static int channelOption(String option) { switch (option) { case SeriesRecordings.OPTION_CHANNEL_ONE: return OPTION_CHANNEL_ONE; @@ -254,7 +235,8 @@ public class SeriesRecording implements Parcelable { return SeriesRecordings.STATE_SERIES_NORMAL; } - @SeriesState private static int seriesRecordingState(String state) { + @SeriesState + private static int seriesRecordingState(String state) { switch (state) { case SeriesRecordings.STATE_SERIES_NORMAL: return STATE_SERIES_NORMAL; @@ -264,9 +246,7 @@ public class SeriesRecording implements Parcelable { return STATE_SERIES_NORMAL; } - /** - * Builder for {@link SeriesRecording}. - */ + /** Builder for {@link SeriesRecording}. */ public static class Builder { private long mId = ID_NOT_SET; private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY; @@ -284,141 +264,120 @@ public class SeriesRecording implements Parcelable { private String mPhotoUri; private int mState = SeriesRecording.STATE_SERIES_NORMAL; - /** - * @see #getId() - */ + /** @see #getId() */ public Builder setId(long id) { mId = id; return this; } - /** - * @see #getPriority() () - */ + /** @see #getPriority() () */ public Builder setPriority(long priority) { mPriority = priority; return this; } - /** - * @see #getTitle() - */ + /** @see #getTitle() */ public Builder setTitle(String title) { mTitle = title; return this; } - /** - * @see #getDescription() - */ + /** @see #getDescription() */ public Builder setDescription(String description) { mDescription = description; return this; } - /** - * @see #getLongDescription() - */ + /** @see #getLongDescription() */ public Builder setLongDescription(String longDescription) { mLongDescription = longDescription; return this; } - /** - * @see #getInputId() - */ + /** @see #getInputId() */ public Builder setInputId(String inputId) { mInputId = inputId; return this; } - /** - * @see #getChannelId() - */ + /** @see #getChannelId() */ public Builder setChannelId(long channelId) { mChannelId = channelId; return this; } - /** - * @see #getSeriesId() - */ + /** @see #getSeriesId() */ public Builder setSeriesId(String seriesId) { mSeriesId = seriesId; return this; } - /** - * @see #getStartFromSeason() - */ + /** @see #getStartFromSeason() */ public Builder setStartFromSeason(int startFromSeason) { mStartFromSeason = startFromSeason; return this; } - /** - * @see #getChannelOption() - */ + /** @see #getChannelOption() */ public Builder setChannelOption(@ChannelOption int option) { mChannelOption = option; return this; } - /** - * @see #getStartFromEpisode() - */ + /** @see #getStartFromEpisode() */ public Builder setStartFromEpisode(int startFromEpisode) { mStartFromEpisode = startFromEpisode; return this; } - /** - * @see #getCanonicalGenreIds() - */ + /** @see #getCanonicalGenreIds() */ public Builder setCanonicalGenreIds(String genres) { mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres); return this; } - /** - * @see #getCanonicalGenreIds() - */ + /** @see #getCanonicalGenreIds() */ public Builder setCanonicalGenreIds(int[] canonicalGenreIds) { mCanonicalGenreIds = canonicalGenreIds; return this; } - /** - * @see #getPosterUri() - */ + /** @see #getPosterUri() */ public Builder setPosterUri(String posterUri) { mPosterUri = posterUri; return this; } - /** - * @see #getPhotoUri() - */ + /** @see #getPhotoUri() */ public Builder setPhotoUri(String photoUri) { mPhotoUri = photoUri; return this; } - /** - * @see #getState() - */ + /** @see #getState() */ public Builder setState(@SeriesState int state) { mState = state; return this; } - /** - * Creates a new {@link SeriesRecording}. - */ + /** Creates a new {@link SeriesRecording}. */ public SeriesRecording build() { - return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription, - mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode, - mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); + return new SeriesRecording( + mId, + mPriority, + mTitle, + mDescription, + mLongDescription, + mInputId, + mChannelId, + mSeriesId, + mStartFromSeason, + mStartFromEpisode, + mChannelOption, + mCanonicalGenreIds, + mPosterUri, + mPhotoUri, + mState); } } @@ -444,16 +403,16 @@ public class SeriesRecording implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public SeriesRecording createFromParcel(Parcel in) { - return SeriesRecording.fromParcel(in); - } + @Override + public SeriesRecording createFromParcel(Parcel in) { + return SeriesRecording.fromParcel(in); + } - @Override - public SeriesRecording[] newArray(int size) { - return new SeriesRecording[size]; - } - }; + @Override + public SeriesRecording[] newArray(int size) { + return new SeriesRecording[size]; + } + }; private long mId; private final long mPriority; @@ -471,9 +430,7 @@ public class SeriesRecording implements Parcelable { private final String mPhotoUri; @SeriesState private int mState; - /** - * The input id of this SeriesRecording. - */ + /** The input id of this SeriesRecording. */ public String getInputId() { return mInputId; } @@ -485,16 +442,12 @@ public class SeriesRecording implements Parcelable { return mChannelId; } - /** - * The id of this SeriesRecording. - */ + /** The id of this SeriesRecording. */ public long getId() { return mId; } - /** - * Sets the ID. - */ + /** Sets the ID. */ public void setId(long id) { mId = id; } @@ -502,30 +455,24 @@ public class SeriesRecording implements Parcelable { /** * The priority of this recording. * - *

The highest number is recorded first. If there is a tie in mPriority then the higher mId + *

The highest number is recorded first. If there is a tie in mPriority then the higher mId * wins. */ public long getPriority() { return mPriority; } - /** - * The series title. - */ + /** The series title. */ public String getTitle() { return mTitle; } - /** - * The series description. - */ + /** The series description. */ public String getDescription() { return mDescription; } - /** - * The long series description. - */ + /** The long series description. */ public String getLongDescription() { return mLongDescription; } @@ -555,44 +502,34 @@ public class SeriesRecording implements Parcelable { return mStartFromSeason; } - /** - * Returns the channel recording option. - */ - @ChannelOption public int getChannelOption() { + /** Returns the channel recording option. */ + @ChannelOption + public int getChannelOption() { return mChannelOption; } - /** - * Returns the canonical genre ID's. - */ + /** Returns the canonical genre ID's. */ public int[] getCanonicalGenreIds() { return mCanonicalGenreIds; } - /** - * Returns the poster URI. - */ + /** Returns the poster URI. */ public String getPosterUri() { return mPosterUri; } - /** - * Returns the photo URI. - */ + /** Returns the photo URI. */ public String getPhotoUri() { return mPhotoUri; } - /** - * Returns the state of series recording. - */ - @SeriesState public int getState() { + /** Returns the state of series recording. */ + @SeriesState + public int getState() { return mState; } - /** - * Checks whether the series recording is stopped or not. - */ + /** Checks whether the series recording is stopped or not. */ public boolean isStopped() { return mState == STATE_SERIES_STOPPED; } @@ -620,35 +557,77 @@ public class SeriesRecording implements Parcelable { @Override public int hashCode() { - return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId, - mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption, - mCanonicalGenreIds, mPosterUri, mPhotoUri, mState); + return Objects.hash( + mPriority, + mChannelId, + mStartFromSeason, + mStartFromEpisode, + mId, + mTitle, + mDescription, + mLongDescription, + mSeriesId, + mChannelOption, + mCanonicalGenreIds, + mPosterUri, + mPhotoUri, + mState); } @Override public String toString() { - return "SeriesRecording{" + - "inputId=" + mInputId + - ", channelId=" + mChannelId + - ", id='" + mId + '\'' + - ", priority=" + mPriority + - ", title='" + mTitle + '\'' + - ", description='" + mDescription + '\'' + - ", longDescription='" + mLongDescription + '\'' + - ", startFromSeason=" + mStartFromSeason + - ", startFromEpisode=" + mStartFromEpisode + - ", channelOption=" + mChannelOption + - ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) + - ", posterUri=" + mPosterUri + - ", photoUri=" + mPhotoUri + - ", state=" + mState + - '}'; - } - - private SeriesRecording(long id, long priority, String title, String description, - String longDescription, String inputId, long channelId, String seriesId, - int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds, - String posterUri, String photoUri, int state) { + return "SeriesRecording{" + + "inputId=" + + mInputId + + ", channelId=" + + mChannelId + + ", id='" + + mId + + '\'' + + ", priority=" + + mPriority + + ", title='" + + mTitle + + '\'' + + ", description='" + + mDescription + + '\'' + + ", longDescription='" + + mLongDescription + + '\'' + + ", startFromSeason=" + + mStartFromSeason + + ", startFromEpisode=" + + mStartFromEpisode + + ", channelOption=" + + mChannelOption + + ", canonicalGenreIds=" + + Arrays.toString(mCanonicalGenreIds) + + ", posterUri=" + + mPosterUri + + ", photoUri=" + + mPhotoUri + + ", state=" + + mState + + '}'; + } + + private SeriesRecording( + long id, + long priority, + String title, + String description, + String longDescription, + String inputId, + long channelId, + String seriesId, + int startFromSeason, + int startFromEpisode, + int channelOption, + int[] canonicalGenreIds, + String posterUri, + String photoUri, + int state) { this.mId = id; this.mPriority = priority; this.mTitle = title; @@ -690,9 +669,7 @@ public class SeriesRecording implements Parcelable { out.writeInt(mState); } - /** - * Returns an array containing all of the elements in the list. - */ + /** Returns an array containing all of the elements in the list. */ public static SeriesRecording[] toArray(Collection series) { return series.toArray(new SeriesRecording[series.size()]); } @@ -715,16 +692,16 @@ public class SeriesRecording implements Parcelable { long channelId = program.getChannelId(); String seasonNumber = program.getSeasonNumber(); String episodeNumber = program.getEpisodeNumber(); - if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE - && mChannelId != channelId)) { + if (!mSeriesId.equals(seriesId) + || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE + && mChannelId != channelId)) { return false; } // Season number and episode number matches if // start_season_number < program_season_number // || (start_season_number == program_season_number // && start_episode_number <= program_episode_number). - if (mStartFromSeason == SeriesRecordings.THE_BEGINNING - || TextUtils.isEmpty(seasonNumber)) { + if (mStartFromSeason == SeriesRecordings.THE_BEGINNING || TextUtils.isEmpty(seasonNumber)) { return true; } else { int intSeasonNumber; diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java index c5383d02..ad00bec8 100644 --- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java +++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java @@ -20,27 +20,23 @@ import android.content.Context; import android.database.Cursor; import android.os.AsyncTask; import android.support.annotation.Nullable; - import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; import com.android.tv.util.NamedThreadFactory; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -/** - * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. - */ +/** {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. */ public abstract class AsyncDvrDbTask extends AsyncTask { - private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory( - AsyncDvrDbTask.class.getSimpleName()); - private static final ExecutorService DB_EXECUTOR = Executors - .newSingleThreadExecutor(THREAD_FACTORY); + private static final NamedThreadFactory THREAD_FACTORY = + new NamedThreadFactory(AsyncDvrDbTask.class.getSimpleName()); + private static final ExecutorService DB_EXECUTOR = + Executors.newSingleThreadExecutor(THREAD_FACTORY); private static DvrDatabaseHelper sDbHelper; @@ -57,9 +53,7 @@ public abstract class AsyncDvrDbTask mContext = context; } - /** - * Execute the task on the {@link #DB_EXECUTOR} thread. - */ + /** Execute the task on the {@link #DB_EXECUTOR} thread. */ @SafeVarargs public final void executeOnDbThread(Params... params) { executeOnExecutor(DB_EXECUTOR, params); @@ -71,15 +65,11 @@ public abstract class AsyncDvrDbTask return doInDvrBackground(params); } - /** - * Executes in the background after {@link #initializeDbHelper(Context)} - */ + /** Executes in the background after {@link #initializeDbHelper(Context)} */ @Nullable protected abstract Result doInDvrBackground(Params... params); - /** - * Inserts schedules. - */ + /** Inserts schedules. */ public static class AsyncAddScheduleTask extends AsyncDvrDbTask { public AsyncAddScheduleTask(Context context) { @@ -93,9 +83,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Update schedules. - */ + /** Update schedules. */ public static class AsyncUpdateScheduleTask extends AsyncDvrDbTask { public AsyncUpdateScheduleTask(Context context) { @@ -109,9 +97,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Delete schedules. - */ + /** Delete schedules. */ public static class AsyncDeleteScheduleTask extends AsyncDvrDbTask { public AsyncDeleteScheduleTask(Context context) { @@ -125,9 +111,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Returns all {@link ScheduledRecording}s. - */ + /** Returns all {@link ScheduledRecording}s. */ public abstract static class AsyncDvrQueryScheduleTask extends AsyncDvrDbTask> { public AsyncDvrQueryScheduleTask(Context context) { @@ -150,9 +134,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Inserts series recordings. - */ + /** Inserts series recordings. */ public static class AsyncAddSeriesRecordingTask extends AsyncDvrDbTask { public AsyncAddSeriesRecordingTask(Context context) { @@ -166,9 +148,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Update series recordings. - */ + /** Update series recordings. */ public static class AsyncUpdateSeriesRecordingTask extends AsyncDvrDbTask { public AsyncUpdateSeriesRecordingTask(Context context) { @@ -182,9 +162,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Delete series recordings. - */ + /** Delete series recordings. */ public static class AsyncDeleteSeriesRecordingTask extends AsyncDvrDbTask { public AsyncDeleteSeriesRecordingTask(Context context) { @@ -198,9 +176,7 @@ public abstract class AsyncDvrDbTask } } - /** - * Returns all {@link SeriesRecording}s. - */ + /** Returns all {@link SeriesRecording}s. */ public abstract static class AsyncDvrQuerySeriesRecordingTask extends AsyncDvrDbTask> { public AsyncDvrQuerySeriesRecordingTask(Context context) { @@ -214,8 +190,8 @@ public abstract class AsyncDvrDbTask return null; } List scheduledRecordings = new ArrayList<>(); - try (Cursor c = sDbHelper.query(SeriesRecordings.TABLE_NAME, - SeriesRecording.PROJECTION)) { + try (Cursor c = + sDbHelper.query(SeriesRecordings.TABLE_NAME, SeriesRecording.PROJECTION)) { while (c.moveToNext() && !isCancelled()) { scheduledRecordings.add(SeriesRecording.fromCursor(c)); } diff --git a/src/com/android/tv/dvr/provider/DvrContract.java b/src/com/android/tv/dvr/provider/DvrContract.java index f0aca18e..f956ef0b 100644 --- a/src/com/android/tv/dvr/provider/DvrContract.java +++ b/src/com/android/tv/dvr/provider/DvrContract.java @@ -58,8 +58,8 @@ public final class DvrContract { /** * The priority of this recording. * - *

The lowest number is recorded first. If there is a tie in priority then the lower id - * wins. Defaults to {@value Long#MAX_VALUE} + *

The lowest number is recorded first. If there is a tie in priority then the lower id + * wins. Defaults to {@value Long#MAX_VALUE} * *

Type: INTEGER (long) */ @@ -68,8 +68,8 @@ public final class DvrContract { /** * The type of this recording. * - *

This value should be one of the followings: {@link #TYPE_PROGRAM} and - * {@link #TYPE_TIMED}. + *

This value should be one of the followings: {@link #TYPE_PROGRAM} and {@link + * #TYPE_TIMED}. * *

This is a required field. * @@ -184,9 +184,9 @@ public final class DvrContract { * The state of this recording. * *

This value should be one of the followings: {@link #STATE_RECORDING_NOT_STARTED}, - * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, - * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and - * {@link #STATE_RECORDING_DELETED}. + * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, {@link + * #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and {@link + * #STATE_RECORDING_DELETED}. * *

This is a required field. * @@ -201,7 +201,7 @@ public final class DvrContract { */ public static final String COLUMN_SERIES_RECORDING_ID = "series_recording_id"; - private Schedules() { } + private Schedules() {} } /** Column definition for Recording table. */ @@ -210,8 +210,8 @@ public final class DvrContract { public static final String TABLE_NAME = "series_recording"; /** - * This value is used for {@link #COLUMN_START_FROM_SEASON} and - * {@link #COLUMN_START_FROM_EPISODE} to mean record all seasons or episodes. + * This value is used for {@link #COLUMN_START_FROM_SEASON} and {@link + * #COLUMN_START_FROM_EPISODE} to mean record all seasons or episodes. */ public static final int THE_BEGINNING = -1; @@ -227,21 +227,17 @@ public final class DvrContract { */ public static final String OPTION_CHANNEL_ALL = "OPTION_CHANNEL_ALL"; - /** - * The state indicates that it is a normal one. - */ + /** The state indicates that it is a normal one. */ public static final String STATE_SERIES_NORMAL = "STATE_SERIES_NORMAL"; - /** - * The state indicates that it is stopped. - */ + /** The state indicates that it is stopped. */ public static final String STATE_SERIES_STOPPED = "STATE_SERIES_STOPPED"; /** * The priority of this recording. * - *

The lowest number is recorded first. If there is a tie in priority then the lower id - * wins. Defaults to {@value Long#MAX_VALUE} + *

The lowest number is recorded first. If there is a tie in priority then the lower id + * wins. Defaults to {@value Long#MAX_VALUE} * *

Type: INTEGER (long) */ @@ -266,7 +262,7 @@ public final class DvrContract { public static final String COLUMN_CHANNEL_ID = "channel_id"; /** - * The ID of the associated series to record. + * The ID of the associated series to record. * *

The id is an opaque but stable string. * @@ -300,8 +296,8 @@ public final class DvrContract { public static final String COLUMN_LONG_DESCRIPTION = "long_description"; /** - * The number of the earliest season to record. The - * value {@link #THE_BEGINNING} means record all seasons. + * The number of the earliest season to record. The value {@link #THE_BEGINNING} means + * record all seasons. * *

Default value is {@value #THE_BEGINNING} {@link #THE_BEGINNING}. * @@ -310,7 +306,7 @@ public final class DvrContract { public static final String COLUMN_START_FROM_SEASON = "start_from_season"; /** - * The number of the earliest episode to record in {@link #COLUMN_START_FROM_SEASON}. The + * The number of the earliest episode to record in {@link #COLUMN_START_FROM_SEASON}. The * value {@link #THE_BEGINNING} means record all episodes. * *

Default value is {@value #THE_BEGINNING} {@link #THE_BEGINNING}. @@ -322,8 +318,8 @@ public final class DvrContract { /** * The series recording option which indicates the channels to record. * - *

This value should be one of the followings: {@link #OPTION_CHANNEL_ONE} and - * {@link #OPTION_CHANNEL_ALL}. The default value is OPTION_CHANNEL_ONE. + *

This value should be one of the followings: {@link #OPTION_CHANNEL_ONE} and {@link + * #OPTION_CHANNEL_ALL}. The default value is OPTION_CHANNEL_ONE. * *

Type: TEXT */ @@ -338,6 +334,7 @@ public final class DvrContract { * to get the canonical genre strings from the text stored in the column. * *

Type: TEXT + * * @see android.media.tv.TvContract.Programs.Genres * @see android.media.tv.TvContract.Programs.Genres#encode * @see android.media.tv.TvContract.Programs.Genres#decode @@ -350,10 +347,9 @@ public final class DvrContract { *

The data in the column must be a URL, or a URI in one of the following formats: * *

    - *
  • content ({@link android.content.ContentResolver#SCHEME_CONTENT})
  • - *
  • android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) - *
  • - *
  • file ({@link android.content.ContentResolver#SCHEME_FILE})
  • + *
  • content ({@link android.content.ContentResolver#SCHEME_CONTENT}) + *
  • android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) + *
  • file ({@link android.content.ContentResolver#SCHEME_FILE}) *
* *

Type: TEXT @@ -366,10 +362,9 @@ public final class DvrContract { *

The data in the column must be a URL, or a URI in one of the following formats: * *

    - *
  • content ({@link android.content.ContentResolver#SCHEME_CONTENT})
  • - *
  • android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) - *
  • - *
  • file ({@link android.content.ContentResolver#SCHEME_FILE})
  • + *
  • content ({@link android.content.ContentResolver#SCHEME_CONTENT}) + *
  • android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) + *
  • file ({@link android.content.ContentResolver#SCHEME_FILE}) *
* *

Type: TEXT @@ -379,15 +374,15 @@ public final class DvrContract { /** * The state of whether the series recording be canceled or not. * - *

This value should be one of the followings: {@link #STATE_SERIES_NORMAL} and - * {@link #STATE_SERIES_STOPPED}. The default value is STATE_SERIES_NORMAL. + *

This value should be one of the followings: {@link #STATE_SERIES_NORMAL} and {@link + * #STATE_SERIES_STOPPED}. The default value is STATE_SERIES_NORMAL. * *

Type: TEXT */ public static final String COLUMN_STATE = "state"; - private SeriesRecordings() { } + private SeriesRecordings() {} } - private DvrContract() { } + private DvrContract() {} } diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java index 8b9481a9..fb793a0e 100644 --- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java +++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java @@ -26,15 +26,12 @@ import android.database.sqlite.SQLiteStatement; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; - import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; -/** - * A data class for one recorded contents. - */ +/** A data class for one recorded contents. */ public class DvrDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "DvrDatabaseHelper"; private static final boolean DEBUG = true; @@ -43,81 +40,130 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "dvr.db"; private static final String SQL_CREATE_SCHEDULES = - "CREATE TABLE " + Schedules.TABLE_NAME + "(" - + Schedules._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Schedules.COLUMN_PRIORITY + " INTEGER DEFAULT " - + ScheduledRecording.DEFAULT_PRIORITY + "," - + Schedules.COLUMN_TYPE + " TEXT NOT NULL," - + Schedules.COLUMN_INPUT_ID + " TEXT NOT NULL," - + Schedules.COLUMN_CHANNEL_ID + " INTEGER NOT NULL," - + Schedules.COLUMN_PROGRAM_ID + " INTEGER," - + Schedules.COLUMN_PROGRAM_TITLE + " TEXT," - + Schedules.COLUMN_START_TIME_UTC_MILLIS + " INTEGER NOT NULL," - + Schedules.COLUMN_END_TIME_UTC_MILLIS + " INTEGER NOT NULL," - + Schedules.COLUMN_SEASON_NUMBER + " TEXT," - + Schedules.COLUMN_EPISODE_NUMBER + " TEXT," - + Schedules.COLUMN_EPISODE_TITLE + " TEXT," - + Schedules.COLUMN_PROGRAM_DESCRIPTION + " TEXT," - + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION + " TEXT," - + Schedules.COLUMN_PROGRAM_POST_ART_URI + " TEXT," - + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI + " TEXT," - + Schedules.COLUMN_STATE + " TEXT NOT NULL," - + Schedules.COLUMN_SERIES_RECORDING_ID + " INTEGER," - + "FOREIGN KEY(" + Schedules.COLUMN_SERIES_RECORDING_ID + ") " - + "REFERENCES " + SeriesRecordings.TABLE_NAME - + "(" + SeriesRecordings._ID + ") " + "CREATE TABLE " + + Schedules.TABLE_NAME + + "(" + + Schedules._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Schedules.COLUMN_PRIORITY + + " INTEGER DEFAULT " + + ScheduledRecording.DEFAULT_PRIORITY + + "," + + Schedules.COLUMN_TYPE + + " TEXT NOT NULL," + + Schedules.COLUMN_INPUT_ID + + " TEXT NOT NULL," + + Schedules.COLUMN_CHANNEL_ID + + " INTEGER NOT NULL," + + Schedules.COLUMN_PROGRAM_ID + + " INTEGER," + + Schedules.COLUMN_PROGRAM_TITLE + + " TEXT," + + Schedules.COLUMN_START_TIME_UTC_MILLIS + + " INTEGER NOT NULL," + + Schedules.COLUMN_END_TIME_UTC_MILLIS + + " INTEGER NOT NULL," + + Schedules.COLUMN_SEASON_NUMBER + + " TEXT," + + Schedules.COLUMN_EPISODE_NUMBER + + " TEXT," + + Schedules.COLUMN_EPISODE_TITLE + + " TEXT," + + Schedules.COLUMN_PROGRAM_DESCRIPTION + + " TEXT," + + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION + + " TEXT," + + Schedules.COLUMN_PROGRAM_POST_ART_URI + + " TEXT," + + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI + + " TEXT," + + Schedules.COLUMN_STATE + + " TEXT NOT NULL," + + Schedules.COLUMN_SERIES_RECORDING_ID + + " INTEGER," + + "FOREIGN KEY(" + + Schedules.COLUMN_SERIES_RECORDING_ID + + ") " + + "REFERENCES " + + SeriesRecordings.TABLE_NAME + + "(" + + SeriesRecordings._ID + + ") " + "ON UPDATE CASCADE ON DELETE SET NULL);"; private static final String SQL_DROP_SCHEDULES = "DROP TABLE IF EXISTS " + Schedules.TABLE_NAME; private static final String SQL_CREATE_SERIES_RECORDINGS = - "CREATE TABLE " + SeriesRecordings.TABLE_NAME + "(" - + SeriesRecordings._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SeriesRecordings.COLUMN_PRIORITY + " INTEGER DEFAULT " - + SeriesRecording.DEFAULT_PRIORITY + "," - + SeriesRecordings.COLUMN_TITLE + " TEXT NOT NULL," - + SeriesRecordings.COLUMN_SHORT_DESCRIPTION + " TEXT," - + SeriesRecordings.COLUMN_LONG_DESCRIPTION + " TEXT," - + SeriesRecordings.COLUMN_INPUT_ID + " TEXT NOT NULL," - + SeriesRecordings.COLUMN_CHANNEL_ID + " INTEGER NOT NULL," - + SeriesRecordings.COLUMN_SERIES_ID + " TEXT NOT NULL," - + SeriesRecordings.COLUMN_START_FROM_SEASON + " INTEGER DEFAULT " - + SeriesRecordings.THE_BEGINNING + "," - + SeriesRecordings.COLUMN_START_FROM_EPISODE + " INTEGER DEFAULT " - + SeriesRecordings.THE_BEGINNING + "," - + SeriesRecordings.COLUMN_CHANNEL_OPTION + " TEXT DEFAULT " - + SeriesRecordings.OPTION_CHANNEL_ONE + "," - + SeriesRecordings.COLUMN_CANONICAL_GENRE + " TEXT," - + SeriesRecordings.COLUMN_POSTER_URI + " TEXT," - + SeriesRecordings.COLUMN_PHOTO_URI + " TEXT," - + SeriesRecordings.COLUMN_STATE + " TEXT)"; + "CREATE TABLE " + + SeriesRecordings.TABLE_NAME + + "(" + + SeriesRecordings._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + SeriesRecordings.COLUMN_PRIORITY + + " INTEGER DEFAULT " + + SeriesRecording.DEFAULT_PRIORITY + + "," + + SeriesRecordings.COLUMN_TITLE + + " TEXT NOT NULL," + + SeriesRecordings.COLUMN_SHORT_DESCRIPTION + + " TEXT," + + SeriesRecordings.COLUMN_LONG_DESCRIPTION + + " TEXT," + + SeriesRecordings.COLUMN_INPUT_ID + + " TEXT NOT NULL," + + SeriesRecordings.COLUMN_CHANNEL_ID + + " INTEGER NOT NULL," + + SeriesRecordings.COLUMN_SERIES_ID + + " TEXT NOT NULL," + + SeriesRecordings.COLUMN_START_FROM_SEASON + + " INTEGER DEFAULT " + + SeriesRecordings.THE_BEGINNING + + "," + + SeriesRecordings.COLUMN_START_FROM_EPISODE + + " INTEGER DEFAULT " + + SeriesRecordings.THE_BEGINNING + + "," + + SeriesRecordings.COLUMN_CHANNEL_OPTION + + " TEXT DEFAULT " + + SeriesRecordings.OPTION_CHANNEL_ONE + + "," + + SeriesRecordings.COLUMN_CANONICAL_GENRE + + " TEXT," + + SeriesRecordings.COLUMN_POSTER_URI + + " TEXT," + + SeriesRecordings.COLUMN_PHOTO_URI + + " TEXT," + + SeriesRecordings.COLUMN_STATE + + " TEXT)"; - private static final String SQL_DROP_SERIES_RECORDINGS = "DROP TABLE IF EXISTS " + - SeriesRecordings.TABLE_NAME; + private static final String SQL_DROP_SERIES_RECORDINGS = + "DROP TABLE IF EXISTS " + SeriesRecordings.TABLE_NAME; private static final int SQL_DATA_TYPE_LONG = 0; private static final int SQL_DATA_TYPE_INT = 1; private static final int SQL_DATA_TYPE_STRING = 2; - private static final ColumnInfo[] COLUMNS_SCHEDULES = new ColumnInfo[] { - new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_START_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_END_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG), - new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING), - new ColumnInfo(Schedules.COLUMN_SERIES_RECORDING_ID, SQL_DATA_TYPE_LONG)}; + private static final ColumnInfo[] COLUMNS_SCHEDULES = + new ColumnInfo[] { + new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_START_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_END_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG), + new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING), + new ColumnInfo(Schedules.COLUMN_SERIES_RECORDING_ID, SQL_DATA_TYPE_LONG) + }; private static final String SQL_INSERT_SCHEDULES = buildInsertSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES); @@ -125,22 +171,24 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { buildUpdateSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES); private static final String SQL_DELETE_SCHEDULES = buildDeleteSql(Schedules.TABLE_NAME); - private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS = new ColumnInfo[] { - new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG), - new ColumnInfo(SeriesRecordings.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG), - new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG), - new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_SEASON, SQL_DATA_TYPE_INT), - new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_EPISODE, SQL_DATA_TYPE_INT), - new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_OPTION, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING), - new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING)}; + private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS = + new ColumnInfo[] { + new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG), + new ColumnInfo(SeriesRecordings.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG), + new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG), + new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_SEASON, SQL_DATA_TYPE_INT), + new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_EPISODE, SQL_DATA_TYPE_INT), + new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_OPTION, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING), + new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING) + }; private static final String SQL_INSERT_SERIES_RECORDINGS = buildInsertSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS); @@ -186,6 +234,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { private static String buildDeleteSql(String tableName) { return "DELETE FROM " + tableName + " WHERE " + BaseColumns._ID + "=?"; } + public DvrDatabaseHelper(Context context) { super(context.getApplicationContext(), DB_NAME, null, DATABASE_VERSION); } @@ -212,9 +261,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { onCreate(db); } - /** - * Handles the query request and returns a {@link Cursor}. - */ + /** Handles the query request and returns a {@link Cursor}. */ public Cursor query(String tableName, String[] projections) { SQLiteDatabase db = getReadableDatabase(); SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); @@ -222,9 +269,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { return builder.query(db, projections, null, null, null, null, null); } - /** - * Inserts schedules. - */ + /** Inserts schedules. */ public void insertSchedules(ScheduledRecording... scheduledRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_INSERT_SCHEDULES); @@ -242,9 +287,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - /** - * Update schedules. - */ + /** Update schedules. */ public void updateSchedules(ScheduledRecording... scheduledRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SCHEDULES); @@ -263,9 +306,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - /** - * Delete schedules. - */ + /** Delete schedules. */ public void deleteSchedules(ScheduledRecording... scheduledRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_DELETE_SCHEDULES); @@ -282,9 +323,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - /** - * Inserts series recordings. - */ + /** Inserts series recordings. */ public void insertSeriesRecordings(SeriesRecording... seriesRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_INSERT_SERIES_RECORDINGS); @@ -302,9 +341,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - /** - * Update series recordings. - */ + /** Update series recordings. */ public void updateSeriesRecordings(SeriesRecording... seriesRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SERIES_RECORDINGS); @@ -323,9 +360,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - /** - * Delete series recordings. - */ + /** Delete series recordings. */ public void deleteSeriesRecordings(SeriesRecording... seriesRecordings) { SQLiteDatabase db = getWritableDatabase(); SQLiteStatement statement = db.compileStatement(SQL_DELETE_SERIES_RECORDINGS); @@ -342,8 +377,8 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { } } - private void bindColumns(SQLiteStatement statement, ColumnInfo[] columns, - ContentValues values) { + private void bindColumns( + SQLiteStatement statement, ColumnInfo[] columns, ContentValues values) { for (int i = 0; i < columns.length; ++i) { ColumnInfo columnInfo = columns[i]; Object value = values.get(columnInfo.name); @@ -362,14 +397,15 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper { statement.bindLong(i + 1, (Integer) value); } break; - case SQL_DATA_TYPE_STRING: { - if (TextUtils.isEmpty((String) value)) { - statement.bindNull(i + 1); - } else { - statement.bindString(i + 1, (String) value); + case SQL_DATA_TYPE_STRING: + { + if (TextUtils.isEmpty((String) value)) { + statement.bindNull(i + 1); + } else { + statement.bindString(i + 1, (String) value); + } + break; } - break; - } } } } diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java index ff391959..1cdeef24 100644 --- a/src/com/android/tv/dvr/provider/DvrDbSync.java +++ b/src/com/android/tv/dvr/provider/DvrDbSync.java @@ -29,7 +29,6 @@ import android.os.Looper; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; import android.util.Log; - import com.android.tv.TvApplication; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; @@ -41,7 +40,6 @@ import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.recorder.SeriesRecordingScheduler; import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask; import com.android.tv.util.TvUriMatcher; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -73,25 +71,27 @@ public class DvrDbSync { private final Queue mProgramIdQueue = new LinkedList<>(); private QueryProgramTask mQueryProgramTask; private final SeriesRecordingScheduler mSeriesRecordingScheduler; - private final ContentObserver mContentObserver = new ContentObserver(new Handler( - Looper.getMainLooper())) { - @SuppressLint("SwitchIntDef") - @Override - public void onChange(boolean selfChange, Uri uri) { - switch (TvUriMatcher.match(uri)) { - case TvUriMatcher.MATCH_PROGRAM: - if (DEBUG) Log.d(TAG, "onProgramsUpdated"); - onProgramsUpdated(); - break; - case TvUriMatcher.MATCH_PROGRAM_ID: - if (DEBUG) { - Log.d(TAG, "onProgramUpdated: programId=" + ContentUris.parseId(uri)); + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { + @SuppressLint("SwitchIntDef") + @Override + public void onChange(boolean selfChange, Uri uri) { + switch (TvUriMatcher.match(uri)) { + case TvUriMatcher.MATCH_PROGRAM: + if (DEBUG) Log.d(TAG, "onProgramsUpdated"); + onProgramsUpdated(); + break; + case TvUriMatcher.MATCH_PROGRAM_ID: + if (DEBUG) { + Log.d( + TAG, + "onProgramUpdated: programId=" + ContentUris.parseId(uri)); + } + onProgramUpdated(ContentUris.parseId(uri)); + break; } - onProgramUpdated(ContentUris.parseId(uri)); - break; - } - } - }; + } + }; private final ChannelDataManager.Listener mChannelDataManagerListener = new ChannelDataManager.Listener() { @@ -106,44 +106,51 @@ public class DvrDbSync { } @Override - public void onChannelBrowsableChanged() { } + public void onChannelBrowsableChanged() {} }; - private final ScheduledRecordingListener mScheduleListener = new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - addProgramIdToCheckIfNeeded(schedule); - } - startNextUpdateIfNeeded(); - } + private final ScheduledRecordingListener mScheduleListener = + new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + addProgramIdToCheckIfNeeded(schedule); + } + startNextUpdateIfNeeded(); + } - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - mProgramIdQueue.remove(schedule.getProgramId()); - } - } + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + mProgramIdQueue.remove(schedule.getProgramId()); + } + } - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { - for (ScheduledRecording schedule : schedules) { - mProgramIdQueue.remove(schedule.getProgramId()); - addProgramIdToCheckIfNeeded(schedule); - } - startNextUpdateIfNeeded(); - } - }; + @Override + public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { + for (ScheduledRecording schedule : schedules) { + mProgramIdQueue.remove(schedule.getProgramId()); + addProgramIdToCheckIfNeeded(schedule); + } + startNextUpdateIfNeeded(); + } + }; public DvrDbSync(Context context, DvrDataManagerImpl dataManager) { - this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager(), + this( + context, + dataManager, + TvApplication.getSingletons(context).getChannelDataManager(), TvApplication.getSingletons(context).getDvrManager(), SeriesRecordingScheduler.getInstance(context)); } @VisibleForTesting - DvrDbSync(Context context, DvrDataManagerImpl dataManager, - ChannelDataManager channelDataManager, DvrManager dvrManager, + DvrDbSync( + Context context, + DvrDataManagerImpl dataManager, + ChannelDataManager channelDataManager, + DvrManager dvrManager, SeriesRecordingScheduler seriesRecordingScheduler) { mContext = context; mDvrManager = dvrManager; @@ -152,24 +159,20 @@ public class DvrDbSync { mSeriesRecordingScheduler = seriesRecordingScheduler; } - /** - * Starts the DB sync. - */ + /** Starts the DB sync. */ public void start() { if (!mChannelDataManager.isDbLoadFinished()) { mChannelDataManager.addListener(mChannelDataManagerListener); return; } - mContext.getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, - mContentObserver); + mContext.getContentResolver() + .registerContentObserver(Programs.CONTENT_URI, true, mContentObserver); mDataManager.addScheduledRecordingListener(mScheduleListener); onChannelsUpdated(); onProgramsUpdated(); } - /** - * Stops the DB sync. - */ + /** Stops the DB sync. */ public void stop() { mProgramIdQueue.clear(); if (mQueryProgramTask != null) { @@ -185,14 +188,15 @@ public class DvrDbSync { for (SeriesRecording r : mDataManager.getSeriesRecordings()) { if (r.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE && !mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { - seriesRecordingsToUpdate.add(SeriesRecording.buildFrom(r) - .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) - .setState(SeriesRecording.STATE_SERIES_STOPPED).build()); + seriesRecordingsToUpdate.add( + SeriesRecording.buildFrom(r) + .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) + .setState(SeriesRecording.STATE_SERIES_STOPPED) + .build()); } } if (!seriesRecordingsToUpdate.isEmpty()) { - mDataManager.updateSeriesRecording( - SeriesRecording.toArray(seriesRecordingsToUpdate)); + mDataManager.updateSeriesRecording(SeriesRecording.toArray(seriesRecordingsToUpdate)); } List schedulesToRemove = new ArrayList<>(); for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) { @@ -202,8 +206,7 @@ public class DvrDbSync { } } if (!schedulesToRemove.isEmpty()) { - mDataManager.removeScheduledRecording( - ScheduledRecording.toArray(schedulesToRemove)); + mDataManager.removeScheduledRecording(ScheduledRecording.toArray(schedulesToRemove)); } } @@ -227,7 +230,7 @@ public class DvrDbSync { if (programId != ScheduledRecording.ID_NOT_SET && !mProgramIdQueue.contains(programId) && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { if (DEBUG) Log.d(TAG, "Program ID enqueued: " + programId); mProgramIdQueue.offer(programId); // There are schedules to be updated. Pause the SeriesRecordingScheduler until all the @@ -258,7 +261,7 @@ public class DvrDbSync { ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(programId); if (schedule != null && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { if (program == null) { mDataManager.removeScheduledRecording(schedule); if (schedule.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { @@ -270,15 +273,16 @@ public class DvrDbSync { } } else { long currentTimeMs = System.currentTimeMillis(); - ScheduledRecording.Builder builder = ScheduledRecording.buildFrom(schedule) - .setEndTimeMs(program.getEndTimeUtcMillis()) - .setSeasonNumber(program.getSeasonNumber()) - .setEpisodeNumber(program.getEpisodeNumber()) - .setEpisodeTitle(program.getEpisodeTitle()) - .setProgramDescription(program.getDescription()) - .setProgramLongDescription(program.getLongDescription()) - .setProgramPosterArtUri(program.getPosterArtUri()) - .setProgramThumbnailUri(program.getThumbnailUri()); + ScheduledRecording.Builder builder = + ScheduledRecording.buildFrom(schedule) + .setEndTimeMs(program.getEndTimeUtcMillis()) + .setSeasonNumber(program.getSeasonNumber()) + .setEpisodeNumber(program.getEpisodeNumber()) + .setEpisodeTitle(program.getEpisodeTitle()) + .setProgramDescription(program.getDescription()) + .setProgramLongDescription(program.getLongDescription()) + .setProgramPosterArtUri(program.getPosterArtUri()) + .setProgramThumbnailUri(program.getThumbnailUri()); boolean needUpdate = false; // Check the series recording. SeriesRecording seriesRecordingForOldSchedule = @@ -289,9 +293,11 @@ public class DvrDbSync { mDataManager.getSeriesRecording(program.getSeriesId()); if (seriesRecording == null) { // The new program is episodic while the previous one isn't. - SeriesRecording newSeriesRecording = mDvrManager.addSeriesRecording( - program, Collections.singletonList(program), - SeriesRecording.STATE_SERIES_STOPPED); + SeriesRecording newSeriesRecording = + mDvrManager.addSeriesRecording( + program, + Collections.singletonList(program), + SeriesRecording.STATE_SERIES_STOPPED); builder.setSeriesRecordingId(newSeriesRecording.getId()); needUpdate = true; } else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) { @@ -302,10 +308,10 @@ public class DvrDbSync { if (seriesRecordingForOldSchedule != null) { seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); } - } else if (!Objects.equals(schedule.getSeasonNumber(), - program.getSeasonNumber()) - || !Objects.equals(schedule.getEpisodeNumber(), - program.getEpisodeNumber())) { + } else if (!Objects.equals( + schedule.getSeasonNumber(), program.getSeasonNumber()) + || !Objects.equals( + schedule.getEpisodeNumber(), program.getEpisodeNumber())) { // The episode number has been changed. if (seriesRecordingForOldSchedule != null) { seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); @@ -318,23 +324,24 @@ public class DvrDbSync { // Change start time only when the recording is not started yet. boolean needToChangeStartTime = schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS - && program.getStartTimeUtcMillis() != schedule.getStartTimeMs(); + && program.getStartTimeUtcMillis() != schedule.getStartTimeMs(); if (needToChangeStartTime) { builder.setStartTimeMs(program.getStartTimeUtcMillis()); needUpdate = true; } - if (needUpdate || schedule.getEndTimeMs() != program.getEndTimeUtcMillis() + if (needUpdate + || schedule.getEndTimeMs() != program.getEndTimeUtcMillis() || !Objects.equals(schedule.getSeasonNumber(), program.getSeasonNumber()) || !Objects.equals(schedule.getEpisodeNumber(), program.getEpisodeNumber()) || !Objects.equals(schedule.getEpisodeTitle(), program.getEpisodeTitle()) - || !Objects.equals(schedule.getProgramDescription(), - program.getDescription()) - || !Objects.equals(schedule.getProgramLongDescription(), - program.getLongDescription()) - || !Objects.equals(schedule.getProgramPosterArtUri(), - program.getPosterArtUri()) - || !Objects.equals(schedule.getProgramThumbnailUri(), - program.getThumbnailUri())) { + || !Objects.equals( + schedule.getProgramDescription(), program.getDescription()) + || !Objects.equals( + schedule.getProgramLongDescription(), program.getLongDescription()) + || !Objects.equals( + schedule.getProgramPosterArtUri(), program.getPosterArtUri()) + || !Objects.equals( + schedule.getProgramThumbnailUri(), program.getThumbnailUri())) { mDataManager.updateScheduledRecording(builder.build()); } if (!seriesRecordingsToUpdate.isEmpty()) { diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java index ba0aca51..e9ca11e5 100644 --- a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java +++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java @@ -25,18 +25,16 @@ import android.net.Uri; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; - import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask; import com.android.tv.util.AsyncDbTask.CursorFilter; import com.android.tv.util.PermissionUtils; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -44,11 +42,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -/** - * A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings. - */ +/** A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings. */ @TargetApi(Build.VERSION_CODES.N) -abstract public class EpisodicProgramLoadTask { +public abstract class EpisodicProgramLoadTask { private static final String TAG = "EpisodicProgramLoadTask"; private static final int PROGRAM_ID_INDEX = Program.getColumnIndex(Programs._ID); @@ -61,11 +57,15 @@ abstract public class EpisodicProgramLoadTask { private static final String PARAM_END_TIME = "end_time"; private static final String PROGRAM_PREDICATE = - Programs.COLUMN_START_TIME_UTC_MILLIS + ">? AND " - + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; + Programs.COLUMN_START_TIME_UTC_MILLIS + + ">? AND " + + Programs.COLUMN_RECORDING_PROHIBITED + + "=0"; private static final String PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM = - Programs.COLUMN_END_TIME_UTC_MILLIS + ">? AND " - + Programs.COLUMN_RECORDING_PROHIBITED + "=0"; + Programs.COLUMN_END_TIME_UTC_MILLIS + + ">? AND " + + Programs.COLUMN_RECORDING_PROHIBITED + + "=0"; private static final String CHANNEL_ID_PREDICATE = Programs.COLUMN_CHANNEL_ID + "=?"; private static final String PROGRAM_TITLE_PREDICATE = Programs.COLUMN_TITLE + "=?"; @@ -80,10 +80,7 @@ abstract public class EpisodicProgramLoadTask { private final ArrayList mSeriesRecordings = new ArrayList<>(); private AsyncProgramQueryTask mProgramQueryTask; - /** - * - * Constructor used to load programs for one series recording with the given channel option. - */ + /** Constructor used to load programs for one series recording with the given channel option. */ public EpisodicProgramLoadTask(Context context, SeriesRecording seriesRecording) { this(context, Collections.singletonList(seriesRecording)); } @@ -98,60 +95,52 @@ abstract public class EpisodicProgramLoadTask { mSeriesRecordings.addAll(seriesRecordings); } - /** - * Returns the series recordings. - */ + /** Returns the series recordings. */ public List getSeriesRecordings() { return mSeriesRecordings; } - /** - * Returns the program query task. It is {@code null} until it is executed. - */ + /** Returns the program query task. It is {@code null} until it is executed. */ @Nullable public AsyncProgramQueryTask getTask() { return mProgramQueryTask; } - /** - * Enables loading current programs. The default value is {@code false}. - */ + /** Enables loading current programs. The default value is {@code false}. */ public EpisodicProgramLoadTask setLoadCurrentProgram(boolean loadCurrentProgram) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); + SoftPreconditions.checkState( + mProgramQueryTask == null, TAG, "Can't change setting after execution."); mLoadCurrentProgram = loadCurrentProgram; return this; } - /** - * Enables already schedules episodes. The default value is {@code false}. - */ + /** Enables already schedules episodes. The default value is {@code false}. */ public EpisodicProgramLoadTask setLoadScheduledEpisode(boolean loadScheduledEpisode) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); + SoftPreconditions.checkState( + mProgramQueryTask == null, TAG, "Can't change setting after execution."); mLoadScheduledEpisode = loadScheduledEpisode; return this; } /** - * Enables loading disallowed programs whose schedules were removed manually by the user. - * The default value is {@code false}. + * Enables loading disallowed programs whose schedules were removed manually by the user. The + * default value is {@code false}. */ public EpisodicProgramLoadTask setLoadDisallowedProgram(boolean loadDisallowedProgram) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); + SoftPreconditions.checkState( + mProgramQueryTask == null, TAG, "Can't change setting after execution."); mLoadDisallowedProgram = loadDisallowedProgram; return this; } /** - * Gives the option whether to ignore the channel option when matching programs. - * If {@code ignoreChannelOption} is {@code true}, the program will be matched with - * {@link SeriesRecording#OPTION_CHANNEL_ALL} option. + * Gives the option whether to ignore the channel option when matching programs. If {@code + * ignoreChannelOption} is {@code true}, the program will be matched with {@link + * SeriesRecording#OPTION_CHANNEL_ALL} option. */ public EpisodicProgramLoadTask setIgnoreChannelOption(boolean ignoreChannelOption) { - SoftPreconditions.checkState(mProgramQueryTask == null, TAG, - "Can't change setting after execution."); + SoftPreconditions.checkState( + mProgramQueryTask == null, TAG, "Can't change setting after execution."); mIgnoreChannelOption = ignoreChannelOption; return this; } @@ -162,12 +151,15 @@ abstract public class EpisodicProgramLoadTask { * @see com.android.tv.util.AsyncDbTask#executeOnDbThread */ public void execute() { - if (SoftPreconditions.checkState(mProgramQueryTask == null, TAG, + if (SoftPreconditions.checkState( + mProgramQueryTask == null, + TAG, "Can't execute task: the task is already running.")) { - mQueryAllChannels = mSeriesRecordings.size() > 1 - || mSeriesRecordings.get(0).getChannelOption() - == SeriesRecording.OPTION_CHANNEL_ALL - || mIgnoreChannelOption; + mQueryAllChannels = + mSeriesRecordings.size() > 1 + || mSeriesRecordings.get(0).getChannelOption() + == SeriesRecording.OPTION_CHANNEL_ALL + || mIgnoreChannelOption; mProgramQueryTask = createTask(); mProgramQueryTask.executeOnDbThread(); } @@ -184,22 +176,21 @@ abstract public class EpisodicProgramLoadTask { } } - /** - * Runs on the UI thread after the program loading finishes successfully. - */ - protected void onPostExecute(List programs) { - } + /** Runs on the UI thread after the program loading finishes successfully. */ + protected void onPostExecute(List programs) {} - /** - * Runs on the UI thread after the program loading was canceled. - */ - protected void onCancelled(List programs) { - } + /** Runs on the UI thread after the program loading was canceled. */ + protected void onCancelled(List programs) {} private AsyncProgramQueryTask createTask() { SqlParams sqlParams = createSqlParams(); - return new AsyncProgramQueryTask(mContext.getContentResolver(), sqlParams.uri, - sqlParams.selection, sqlParams.selectionArgs, null, sqlParams.filter) { + return new AsyncProgramQueryTask( + mContext.getContentResolver(), + sqlParams.uri, + sqlParams.selection, + sqlParams.selectionArgs, + null, + sqlParams.filter) { @Override protected void onPostExecute(List programs) { EpisodicProgramLoadTask.this.onPostExecute(programs); @@ -217,8 +208,11 @@ abstract public class EpisodicProgramLoadTask { if (PermissionUtils.hasAccessAllEpg(mContext)) { sqlParams.uri = Programs.CONTENT_URI; // Base - StringBuilder selection = new StringBuilder(mLoadCurrentProgram - ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM : PROGRAM_PREDICATE); + StringBuilder selection = + new StringBuilder( + mLoadCurrentProgram + ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM + : PROGRAM_PREDICATE); List args = new ArrayList<>(); args.add(Long.toString(System.currentTimeMillis())); // Channel option @@ -237,15 +231,21 @@ abstract public class EpisodicProgramLoadTask { } else { // The query includes the current program. Will be filtered if needed. if (mQueryAllChannels) { - sqlParams.uri = Programs.CONTENT_URI.buildUpon() - .appendQueryParameter(PARAM_START_TIME, - String.valueOf(System.currentTimeMillis())) - .appendQueryParameter(PARAM_END_TIME, String.valueOf(Long.MAX_VALUE)) - .build(); + sqlParams.uri = + Programs.CONTENT_URI + .buildUpon() + .appendQueryParameter( + PARAM_START_TIME, + String.valueOf(System.currentTimeMillis())) + .appendQueryParameter( + PARAM_END_TIME, String.valueOf(Long.MAX_VALUE)) + .build(); } else { - sqlParams.uri = TvContract.buildProgramsUriForChannel( - mSeriesRecordings.get(0).getChannelId(), - System.currentTimeMillis(), Long.MAX_VALUE); + sqlParams.uri = + TvContract.buildProgramsUriForChannel( + mSeriesRecordings.get(0).getChannelId(), + System.currentTimeMillis(), + Long.MAX_VALUE); } sqlParams.selection = null; sqlParams.selectionArgs = null; @@ -292,16 +292,19 @@ abstract public class EpisodicProgramLoadTask { for (SeriesRecording seriesRecording : mSeriesRecordings) { boolean programMatches; if (mIgnoreChannelOption) { - programMatches = seriesRecording.matchProgram(program, - SeriesRecording.OPTION_CHANNEL_ALL); + programMatches = + seriesRecording.matchProgram( + program, SeriesRecording.OPTION_CHANNEL_ALL); } else { programMatches = seriesRecording.matchProgram(program); } if (programMatches) { return mLoadScheduledEpisode - || !mSeasonEpisodeNumbers.contains(new SeasonEpisodeNumber( - seriesRecording.getId(), program.getSeasonNumber(), - program.getEpisodeNumber())); + || !mSeasonEpisodeNumbers.contains( + new SeasonEpisodeNumber( + seriesRecording.getId(), + program.getSeasonNumber(), + program.getEpisodeNumber())); } } return false; @@ -316,7 +319,8 @@ abstract public class EpisodicProgramLoadTask { @Override public boolean filter(Cursor c) { return (mLoadCurrentProgram || c.getLong(START_TIME_INDEX) > System.currentTimeMillis()) - && c.getInt(RECORDING_PROHIBITED_INDEX) != 0 && super.filter(c); + && c.getInt(RECORDING_PROHIBITED_INDEX) != 0 + && super.filter(c); } } diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java index 8aa90116..732815cd 100644 --- a/src/com/android/tv/dvr/recorder/ConflictChecker.java +++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java @@ -27,7 +27,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.ArraySet; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener; @@ -40,7 +39,6 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -50,8 +48,9 @@ import java.util.concurrent.TimeUnit; /** * Checking the runtime conflict of DVR recording. - *

- * This class runs only while the {@link MainActivity} is resumed and holds the upcoming conflicts. + * + *

This class runs only while the {@link MainActivity} is resumed and holds the upcoming + * conflicts. */ @TargetApi(Build.VERSION_CODES.N) @MainThread @@ -87,24 +86,26 @@ public class ConflictChecker { private final ScheduledRecordingListener mScheduledRecordingListener = new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { + if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { + if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings); - mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); - } - }; + @Override + public void onScheduledRecordingStatusChanged( + ScheduledRecording... scheduledRecordings) { + if (DEBUG) + Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings); + mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); + } + }; private final OnTvViewChannelChangeListener mOnTvViewChannelChangeListener = new OnTvViewChannelChangeListener() { @@ -124,9 +125,7 @@ public class ConflictChecker { mSessionManager = appSingletons.getInputSessionManager(); } - /** - * Starts checking the conflict. - */ + /** Starts checking the conflict. */ public void start() { if (mStarted) { return; @@ -137,9 +136,7 @@ public class ConflictChecker { mSessionManager.addOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener); } - /** - * Stops checking the conflict. - */ + /** Stops checking the conflict. */ public void stop() { if (!mStarted) { return; @@ -150,23 +147,17 @@ public class ConflictChecker { mHandler.removeCallbacksAndMessages(null); } - /** - * Returns the upcoming conflicts. - */ + /** Returns the upcoming conflicts. */ public List getUpcomingConflicts() { return new ArrayList<>(mUpcomingConflicts); } - /** - * Adds a {@link OnUpcomingConflictChangeListener}. - */ + /** Adds a {@link OnUpcomingConflictChangeListener}. */ public void addOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { mOnUpcomingConflictChangeListeners.add(listener); } - /** - * Removes the {@link OnUpcomingConflictChangeListener}. - */ + /** Removes the {@link OnUpcomingConflictChangeListener}. */ public void removeOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) { mOnUpcomingConflictChangeListeners.remove(listener); } @@ -177,9 +168,7 @@ public class ConflictChecker { } } - /** - * Remembers the user's decision to record while watching the channel. - */ + /** Remembers the user's decision to record while watching the channel. */ public void setCheckedConflictsForChannel(long mChannelId, List conflicts) { mCheckedConflictsMap.put(mChannelId, new ArrayList<>(conflicts)); } @@ -190,8 +179,7 @@ public class ConflictChecker { if (DEBUG) Log.d(TAG, "Handling MSG_CHECK_CONFLICT"); mHandler.removeMessages(MSG_CHECK_CONFLICT); mUpcomingConflicts.clear(); - if (!mScheduleManager.isInitialized() - || !mChannelDataManager.isDbLoadFinished()) { + if (!mScheduleManager.isInitialized() || !mChannelDataManager.isDbLoadFinished()) { mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, CHECK_RETRY_PERIOD_MS); notifyUpcomingConflictChanged(); return; @@ -209,8 +197,8 @@ public class ConflictChecker { long channelId = ContentUris.parseId(channelUri); Channel channel = mChannelDataManager.getChannel(channelId); // The conflicts caused by watching the channel. - List conflicts = mScheduleManager - .getConflictingSchedulesForWatching(channel.getId()); + List conflicts = + mScheduleManager.getConflictingSchedulesForWatching(channel.getId()); long earliestToCheck = Long.MAX_VALUE; long currentTimeMs = System.currentTimeMillis(); for (ScheduledRecording schedule : conflicts) { @@ -239,18 +227,15 @@ public class ConflictChecker { } } if (earliestToCheck != Long.MAX_VALUE) { - mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, - earliestToCheck - currentTimeMs); + mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, earliestToCheck - currentTimeMs); } if (DEBUG) Log.d(TAG, "upcoming conflicts: " + mUpcomingConflicts); notifyUpcomingConflictChanged(); if (!mUpcomingConflicts.isEmpty() && !DvrUiHelper.isChannelWatchConflictDialogShown(mMainActivity)) { // Don't show the conflict dialog if the user already knows. - List checkedConflicts = mCheckedConflictsMap.get( - channel.getId()); - if (checkedConflicts == null - || !checkedConflicts.containsAll(mUpcomingConflicts)) { + List checkedConflicts = mCheckedConflictsMap.get(channel.getId()); + if (checkedConflicts == null || !checkedConflicts.containsAll(mUpcomingConflicts)) { DvrUiHelper.showChannelWatchConflictDialog(mMainActivity, channel); } } @@ -271,9 +256,7 @@ public class ConflictChecker { } } - /** - * A listener for the change of upcoming conflicts. - */ + /** A listener for the change of upcoming conflicts. */ public interface OnUpcomingConflictChangeListener { void onUpcomingConflictChange(); } diff --git a/src/com/android/tv/dvr/recorder/DvrRecordingService.java b/src/com/android/tv/dvr/recorder/DvrRecordingService.java index 5d324ca5..3b21bab2 100644 --- a/src/com/android/tv/dvr/recorder/DvrRecordingService.java +++ b/src/com/android/tv/dvr/recorder/DvrRecordingService.java @@ -29,7 +29,6 @@ import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.annotation.VisibleForTesting; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener; @@ -42,8 +41,8 @@ import com.android.tv.util.Clock; import com.android.tv.util.RecurringRunner; /** - * DVR recording service. This service should be a foreground service and send a notification - * to users to do long-running recording task. + * DVR recording service. This service should be a foreground service and send a notification to + * users to do long-running recording task. * *

This service is waken up when there's a scheduled recording coming soon and at boot completed * since schedules have to be loaded from databases in order to set new recording alarms, which @@ -67,9 +66,9 @@ public class DvrRecordingService extends Service { /** * Starts the service in foreground. * - * @param startForRecording {@code true} if there are upcoming recordings in - * {@link RecordingScheduler#SOON_DURATION_IN_MS} and the service is - * started in foreground for those recordings. + * @param startForRecording {@code true} if there are upcoming recordings in {@link + * RecordingScheduler#SOON_DURATION_IN_MS} and the service is started in foreground for + * those recordings. */ @MainThread static void startForegroundService(Context context, boolean startForRecording) { @@ -99,7 +98,8 @@ public class DvrRecordingService extends Service { @VisibleForTesting boolean mIsRecording; private boolean mForeground; - @VisibleForTesting final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener = + @VisibleForTesting + final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener = new OnRecordingSessionChangeListener() { @Override public void onRecordingSessionChange(final boolean create, final int count) { @@ -124,8 +124,12 @@ public class DvrRecordingService extends Service { (WritableDvrDataManager) singletons.getDvrDataManager(); mSessionManager = singletons.getInputSessionManager(); mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener); - mReaperRunner = new RecurringRunner(this, java.util.concurrent.TimeUnit.DAYS.toMillis(1), - new ScheduledProgramReaper(dataManager, Clock.SYSTEM), null); + mReaperRunner = + new RecurringRunner( + this, + java.util.concurrent.TimeUnit.DAYS.toMillis(1), + new ScheduledProgramReaper(dataManager, Clock.SYSTEM), + null); mReaperRunner.start(); mContentTitle = getString(R.string.dvr_notification_content_title); mContentTextRecording = getString(R.string.dvr_notification_content_text_recording); @@ -180,12 +184,16 @@ public class DvrRecordingService extends Service { @VisibleForTesting protected void startForegroundInternal(boolean hasUpcomingRecording) { // STOPSHIP: Replace the content title with real UX strings - Notification.Builder builder = new Notification.Builder(this) - .setContentTitle(mContentTitle) - .setContentText(hasUpcomingRecording ? mContentTextRecording : mContentTextLoading) - .setSmallIcon(R.drawable.ic_dvr); - Notification notification = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? - builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build() : builder.build(); + Notification.Builder builder = + new Notification.Builder(this) + .setContentTitle(mContentTitle) + .setContentText( + hasUpcomingRecording ? mContentTextRecording : mContentTextLoading) + .setSmallIcon(R.drawable.ic_dvr); + Notification notification = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build() + : builder.build(); startForeground(ONGOING_NOTIFICATION_ID, notification); } @@ -197,9 +205,11 @@ public class DvrRecordingService extends Service { private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // STOPSHIP: Replace the channel name with real UX strings - mNotificationChannel = new NotificationChannel(DVR_NOTIFICATION_CHANNEL_ID, - getString(R.string.dvr_notification_channel_name), - NotificationManager.IMPORTANCE_LOW); + mNotificationChannel = + new NotificationChannel( + DVR_NOTIFICATION_CHANNEL_ID, + getString(R.string.dvr_notification_channel_name), + NotificationManager.IMPORTANCE_LOW); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) .createNotificationChannel(mNotificationChannel); } diff --git a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java index f1c0020b..f7521d6a 100644 --- a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java +++ b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java @@ -21,12 +21,9 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.support.annotation.RequiresApi; - import com.android.tv.TvApplication; -/** - * Signals the DVR to start recording shows soon. - */ +/** Signals the DVR to start recording shows soon. */ @RequiresApi(Build.VERSION_CODES.N) public class DvrStartRecordingReceiver extends BroadcastReceiver { @Override diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java index fee4568e..ff46c7c3 100644 --- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java +++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java @@ -25,7 +25,6 @@ import android.support.annotation.VisibleForTesting; import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; - import com.android.tv.InputSessionManager; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; @@ -35,7 +34,6 @@ import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Clock; import com.android.tv.util.CompositeComparator; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -43,9 +41,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -/** - * The scheduler for a TV input. - */ +/** The scheduler for a TV input. */ public class InputTaskScheduler { private static final String TAG = "InputTaskScheduler"; private static final boolean DEBUG = false; @@ -66,9 +62,7 @@ public class InputTaskScheduler { RecordingTask.END_TIME_COMPARATOR, RecordingTask.ID_COMPARATOR); - /** - * Returns the comparator which the schedules are sorted with when executed. - */ + /** Returns the comparator which the schedules are sorted with when executed. */ public static Comparator getRecordingOrderComparator() { return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR; } @@ -81,8 +75,8 @@ public class InputTaskScheduler { private final long mId; private final RecordingTask mTask; - HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording, - RecordingTask recordingTask) { + HandlerWrapper( + Looper looper, ScheduledRecording scheduledRecording, RecordingTask recordingTask) { super(looper, recordingTask); mId = scheduledRecording.getId(); mTask = recordingTask; @@ -94,7 +88,7 @@ public class InputTaskScheduler { // The RecordingTask gets a chance first. // It must return false to pass this message to here. if (msg.what == MESSAGE_REMOVE) { - if (DEBUG) Log.d(TAG, "done " + mId); + if (DEBUG) Log.d(TAG, "done " + mId); mPendingRecordings.remove(mId); } removeCallbacksAndMessages(null); @@ -120,17 +114,37 @@ public class InputTaskScheduler { private final Object mInputLock = new Object(); private final RecordingTaskFactory mRecordingTaskFactory; - public InputTaskScheduler(Context context, TvInputInfo input, Looper looper, - ChannelDataManager channelDataManager, DvrManager dvrManager, - DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock) { - this(context, input, looper, channelDataManager, dvrManager, dataManager, sessionManager, - clock, null); + public InputTaskScheduler( + Context context, + TvInputInfo input, + Looper looper, + ChannelDataManager channelDataManager, + DvrManager dvrManager, + DvrDataManager dataManager, + InputSessionManager sessionManager, + Clock clock) { + this( + context, + input, + looper, + channelDataManager, + dvrManager, + dataManager, + sessionManager, + clock, + null); } @VisibleForTesting - InputTaskScheduler(Context context, TvInputInfo input, Looper looper, - ChannelDataManager channelDataManager, DvrManager dvrManager, - DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock, + InputTaskScheduler( + Context context, + TvInputInfo input, + Looper looper, + ChannelDataManager channelDataManager, + DvrManager dvrManager, + DvrDataManager dataManager, + InputSessionManager sessionManager, + Clock clock, RecordingTaskFactory recordingTaskFactory) { if (DEBUG) Log.d(TAG, "Creating scheduler for " + input); mContext = context; @@ -142,22 +156,32 @@ public class InputTaskScheduler { mSessionManager = sessionManager; mClock = clock; mMainThreadHandler = new Handler(Looper.getMainLooper()); - mRecordingTaskFactory = recordingTaskFactory != null ? recordingTaskFactory - : new RecordingTaskFactory() { - @Override - public RecordingTask createRecordingTask(ScheduledRecording schedule, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock) { - return new RecordingTask(mContext, schedule, channel, mDvrManager, mSessionManager, - mDataManager, mClock); - } - }; + mRecordingTaskFactory = + recordingTaskFactory != null + ? recordingTaskFactory + : new RecordingTaskFactory() { + @Override + public RecordingTask createRecordingTask( + ScheduledRecording schedule, + Channel channel, + DvrManager dvrManager, + InputSessionManager sessionManager, + WritableDvrDataManager dataManager, + Clock clock) { + return new RecordingTask( + mContext, + schedule, + channel, + mDvrManager, + mSessionManager, + mDataManager, + mClock); + } + }; mHandler = new WorkerThreadHandler(looper); } - /** - * Adds a {@link ScheduledRecording}. - */ + /** Adds a {@link ScheduledRecording}. */ public void addSchedule(ScheduledRecording schedule) { mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule)); } @@ -173,9 +197,7 @@ public class InputTaskScheduler { mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE); } - /** - * Removes the {@link ScheduledRecording}. - */ + /** Removes the {@link ScheduledRecording}. */ public void removeSchedule(ScheduledRecording schedule) { mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule)); } @@ -194,9 +216,7 @@ public class InputTaskScheduler { } } - /** - * Updates the {@link ScheduledRecording}. - */ + /** Updates the {@link ScheduledRecording}. */ public void updateSchedule(ScheduledRecording schedule) { mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule)); } @@ -224,18 +244,14 @@ public class InputTaskScheduler { } } - /** - * Updates the TV input. - */ + /** Updates the TV input. */ public void updateTvInputInfo(TvInputInfo input) { synchronized (mInputLock) { mInput = input; } } - /** - * Stops the input task scheduler. - */ + /** Stops the input task scheduler. */ public void stop() { mHandler.removeCallbacksAndMessages(null); mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE); @@ -274,7 +290,8 @@ public class InputTaskScheduler { for (ScheduledRecording schedule : mWaitingSchedules.values()) { if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED && schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS - <= currentTimeMs && schedule.getEndTimeMs() > currentTimeMs) { + <= currentTimeMs + && schedule.getEndTimeMs() > currentTimeMs) { schedulesToStart.add(schedule); } } @@ -321,10 +338,12 @@ public class InputTaskScheduler { earliest = schedule.getEndTimeMs(); } } else { - if (earliest > schedule.getStartTimeMs() - - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) { - earliest = schedule.getStartTimeMs() - - RecordingTask.RECORDING_EARLY_START_OFFSET_MS; + if (earliest + > schedule.getStartTimeMs() + - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) { + earliest = + schedule.getStartTimeMs() + - RecordingTask.RECORDING_EARLY_START_OFFSET_MS; } } } @@ -333,8 +352,9 @@ public class InputTaskScheduler { private RecordingTask createRecordingTask(ScheduledRecording schedule) { Channel channel = mChannelDataManager.getChannel(schedule.getChannelId()); - RecordingTask recordingTask = mRecordingTaskFactory.createRecordingTask(schedule, channel, - mDvrManager, mSessionManager, mDataManager, mClock); + RecordingTask recordingTask = + mRecordingTaskFactory.createRecordingTask( + schedule, channel, mDvrManager, mSessionManager, mDataManager, mClock); HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask); mPendingRecordings.put(schedule.getId(), handlerWrapper); return recordingTask; @@ -371,19 +391,21 @@ public class InputTaskScheduler { private void fail(ScheduledRecording schedule) { // It's called when the scheduling has been failed without creating RecordingTask. - runOnMainHandler(new Runnable() { - @Override - public void run() { - ScheduledRecording scheduleInManager = - mDataManager.getScheduledRecording(schedule.getId()); - if (scheduleInManager != null) { - // The schedule should be updated based on the object from DataManager in case - // when it has been updated. - mDataManager.changeState(scheduleInManager, - ScheduledRecording.STATE_RECORDING_FAILED); - } - } - }); + runOnMainHandler( + new Runnable() { + @Override + public void run() { + ScheduledRecording scheduleInManager = + mDataManager.getScheduledRecording(schedule.getId()); + if (scheduleInManager != null) { + // The schedule should be updated based on the object from DataManager + // in case + // when it has been updated. + mDataManager.changeState( + scheduleInManager, ScheduledRecording.STATE_RECORDING_FAILED); + } + } + }); } private void runOnMainHandler(Runnable runnable) { @@ -396,9 +418,13 @@ public class InputTaskScheduler { @VisibleForTesting interface RecordingTaskFactory { - RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock); + RecordingTask createRecordingTask( + ScheduledRecording scheduledRecording, + Channel channel, + DvrManager dvrManager, + InputSessionManager sessionManager, + WritableDvrDataManager dataManager, + Clock clock); } private class WorkerThreadHandler extends Handler { diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java index cbaf46b5..ea54f8c3 100644 --- a/src/com/android/tv/dvr/recorder/RecordingScheduler.java +++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java @@ -31,7 +31,6 @@ import android.support.annotation.VisibleForTesting; import android.util.ArrayMap; import android.util.Log; import android.util.Range; - import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.TvApplication; @@ -47,19 +46,19 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Clock; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** - * The core class to manage DVR schedule and run recording task. - ** - *

This class is responsible for: + * The core class to manage DVR schedule and run recording task. * + * + *

This class is responsible for: + * *

    - *
  • Sending record commands to TV inputs
  • - *
  • Resolving conflicting schedules, handling overlapping recording time durations, etc.
  • + *
  • Sending record commands to TV inputs + *
  • Resolving conflicting schedules, handling overlapping recording time durations, etc. *
* *

This should be a singleton associated with application's main process. @@ -71,8 +70,8 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco private static final boolean DEBUG = false; private static final String HANDLER_THREAD_NAME = "RecordingScheduler"; - private final static long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(1); - @VisibleForTesting final static long MS_TO_WAKE_BEFORE_START = TimeUnit.SECONDS.toMillis(30); + private static final long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(1); + @VisibleForTesting static final long MS_TO_WAKE_BEFORE_START = TimeUnit.SECONDS.toMillis(30); private final Looper mLooper; private final InputSessionManager mSessionManager; @@ -98,21 +97,22 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco } }; - private Listener mChannelDataLoadListener = new Listener() { - @Override - public void onLoadFinished() { - mChannelDataManager.removeListener(this); - if (isDbLoaded()) { - updateInternal(); - } - } + private Listener mChannelDataLoadListener = + new Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + if (isDbLoaded()) { + updateInternal(); + } + } - @Override - public void onChannelListUpdated() { } + @Override + public void onChannelListUpdated() {} - @Override - public void onChannelBrowsableChanged() { } - }; + @Override + public void onChannelBrowsableChanged() {} + }; /** * Creates a scheduler to schedule alarms for scheduled recordings and create recording tasks. @@ -124,17 +124,28 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco HandlerThread handlerThread = new HandlerThread(HANDLER_THREAD_NAME); handlerThread.start(); ApplicationSingletons singletons = TvApplication.getSingletons(context); - return new RecordingScheduler(handlerThread.getLooper(), - singletons.getDvrManager(), singletons.getInputSessionManager(), + return new RecordingScheduler( + handlerThread.getLooper(), + singletons.getDvrManager(), + singletons.getInputSessionManager(), (WritableDvrDataManager) singletons.getDvrDataManager(), - singletons.getChannelDataManager(), singletons.getTvInputManagerHelper(), context, - Clock.SYSTEM, (AlarmManager) context.getSystemService(Context.ALARM_SERVICE)); + singletons.getChannelDataManager(), + singletons.getTvInputManagerHelper(), + context, + Clock.SYSTEM, + (AlarmManager) context.getSystemService(Context.ALARM_SERVICE)); } @VisibleForTesting - RecordingScheduler(Looper looper, DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, ChannelDataManager channelDataManager, - TvInputManagerHelper inputManager, Context context, Clock clock, + RecordingScheduler( + Looper looper, + DvrManager dvrManager, + InputSessionManager sessionManager, + WritableDvrDataManager dataManager, + ChannelDataManager channelDataManager, + TvInputManagerHelper inputManager, + Context context, + Clock clock, AlarmManager alarmManager) { mLooper = looper; mDvrManager = dvrManager; @@ -159,9 +170,7 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco } } - /** - * Start recording that will happen soon, and set the next alarm time. - */ + /** Start recording that will happen soon, and set the next alarm time. */ public void updateAndStartServiceIfNeeded() { if (DEBUG) Log.d(TAG, "update and start service if needed"); if (isDbLoaded()) { @@ -185,8 +194,10 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco } private boolean updatePendingRecordings() { - List scheduledRecordings = mDataManager - .getScheduledRecordings(new Range<>(mLastStartTimePendingMs, + List scheduledRecordings = + mDataManager.getScheduledRecordings( + new Range<>( + mLastStartTimePendingMs, mClock.currentTimeMillis() + SOON_DURATION_IN_MS), ScheduledRecording.STATE_RECORDING_NOT_STARTED); for (ScheduledRecording r : scheduledRecordings) { @@ -198,7 +209,8 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco // recording service being wrongly pushed back to background in updateInternal(). return scheduledRecordings.size() > 0 || (mLastStartTimePendingMs > mClock.currentTimeMillis() - && mLastStartTimePendingMs < mClock.currentTimeMillis() + SOON_DURATION_IN_MS); + && mLastStartTimePendingMs + < mClock.currentTimeMillis() + SOON_DURATION_IN_MS); } private boolean isDbLoaded() { @@ -279,8 +291,16 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco } InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(input.getId()); if (inputTaskScheduler == null) { - inputTaskScheduler = new InputTaskScheduler(mContext, input, mLooper, - mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mClock); + inputTaskScheduler = + new InputTaskScheduler( + mContext, + input, + mLooper, + mChannelDataManager, + mDvrManager, + mDataManager, + mSessionManager, + mClock); mInputSchedulerMap.put(input.getId(), inputTaskScheduler); } inputTaskScheduler.addSchedule(schedule); @@ -290,8 +310,9 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco } private void updateNextAlarm() { - long nextStartTime = mDataManager.getNextScheduledStartTimeAfter( - Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis())); + long nextStartTime = + mDataManager.getNextScheduledStartTimeAfter( + Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis())); if (nextStartTime != DvrDataManager.NEXT_START_TIME_NOT_FOUND) { long wakeAt = nextStartTime - MS_TO_WAKE_BEFORE_START; if (DEBUG) Log.d(TAG, "Set alarm to record at " + wakeAt); diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java index 14888056..85c6a0d5 100644 --- a/src/com/android/tv/dvr/recorder/RecordingTask.java +++ b/src/com/android/tv/dvr/recorder/RecordingTask.java @@ -30,7 +30,6 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; import android.widget.Toast; - import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.RecordingSession; import com.android.tv.R; @@ -43,7 +42,6 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper; import com.android.tv.util.Clock; import com.android.tv.util.Utils; - import java.util.Comparator; import java.util.concurrent.TimeUnit; @@ -55,58 +53,45 @@ import java.util.concurrent.TimeUnit; */ @WorkerThread @TargetApi(Build.VERSION_CODES.N) -public class RecordingTask extends RecordingCallback implements Handler.Callback, - DvrManager.Listener { +public class RecordingTask extends RecordingCallback + implements Handler.Callback, DvrManager.Listener { private static final String TAG = "RecordingTask"; private static final boolean DEBUG = false; - /** - * Compares the end time in ascending order. - */ - public static final Comparator END_TIME_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs()); - } - }; - - /** - * Compares ID in ascending order. - */ - public static final Comparator ID_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getScheduleId(), rhs.getScheduleId()); - } - }; - - /** - * Compares the priority in ascending order. - */ - public static final Comparator PRIORITY_COMPARATOR - = new Comparator() { - @Override - public int compare(RecordingTask lhs, RecordingTask rhs) { - return Long.compare(lhs.getPriority(), rhs.getPriority()); - } - }; + /** Compares the end time in ascending order. */ + public static final Comparator END_TIME_COMPARATOR = + new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs()); + } + }; + + /** Compares ID in ascending order. */ + public static final Comparator ID_COMPARATOR = + new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getScheduleId(), rhs.getScheduleId()); + } + }; + + /** Compares the priority in ascending order. */ + public static final Comparator PRIORITY_COMPARATOR = + new Comparator() { + @Override + public int compare(RecordingTask lhs, RecordingTask rhs) { + return Long.compare(lhs.getPriority(), rhs.getPriority()); + } + }; - @VisibleForTesting - static final int MSG_INITIALIZE = 1; - @VisibleForTesting - static final int MSG_START_RECORDING = 2; - @VisibleForTesting - static final int MSG_STOP_RECORDING = 3; - /** - * Message to update schedule. - */ + @VisibleForTesting static final int MSG_INITIALIZE = 1; + @VisibleForTesting static final int MSG_START_RECORDING = 2; + @VisibleForTesting static final int MSG_STOP_RECORDING = 3; + /** Message to update schedule. */ public static final int MSG_UDPATE_SCHEDULE = 4; - /** - * The time when the start command will be sent before the recording starts. - */ + /** The time when the start command will be sent before the recording starts. */ public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3); /** * If the recording starts later than the scheduled start time or ends before the scheduled end @@ -126,6 +111,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback ERROR, RELEASED, } + private final InputSessionManager mSessionManager; private final DvrManager mDvrManager; private final Context mContext; @@ -142,9 +128,14 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback private Uri mRecordedProgramUri; private boolean mCanceled; - RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel, - DvrManager dvrManager, InputSessionManager sessionManager, - WritableDvrDataManager dataManager, Clock clock) { + RecordingTask( + Context context, + ScheduledRecording scheduledRecording, + Channel channel, + DvrManager dvrManager, + InputSessionManager sessionManager, + WritableDvrDataManager dataManager, + Clock clock) { mContext = context; mScheduledRecording = scheduledRecording; mChannel = channel; @@ -163,8 +154,10 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback @Override public boolean handleMessage(Message msg) { if (DEBUG) Log.d(TAG, "handleMessage " + msg); - SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null, - TAG, "Null handler trying to handle " + msg); + SoftPreconditions.checkState( + msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null, + TAG, + "Null handler trying to handle " + msg); try { switch (msg.what) { case MSG_INITIALIZE: @@ -219,8 +212,10 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback return; } mState = State.CONNECTED; - if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING, - mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) { + if (mHandler == null + || !sendEmptyMessageAtAbsoluteTime( + MSG_START_RECORDING, + mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) { failAndQuit(); } } @@ -234,8 +229,9 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback mRecordedProgramUri = recordedProgramUri; mState = State.FINISHED; int state = ScheduledRecording.STATE_RECORDING_FINISHED; - if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS - > mClock.currentTimeMillis()) { + if (mStartedWithClipping + || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS + > mClock.currentTimeMillis()) { state = ScheduledRecording.STATE_RECORDING_CLIPPED; } updateRecordingState(state); @@ -253,29 +249,38 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback } switch (reason) { case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE: - mMainThreadHandler.post(new Runnable() { - @Override - public void run() { - if (TvApplication.getSingletons(mContext).getMainActivityWrapper() - .isResumed()) { - ScheduledRecording scheduledRecording = mDataManager - .getScheduledRecording(mScheduledRecording.getId()); - if (scheduledRecording != null) { - Toast.makeText(mContext.getApplicationContext(), - mContext.getString(R.string - .dvr_error_insufficient_space_description_one_recording, - scheduledRecording.getProgramDisplayTitle(mContext)), - Toast.LENGTH_LONG) - .show(); + mMainThreadHandler.post( + new Runnable() { + @Override + public void run() { + if (TvApplication.getSingletons(mContext) + .getMainActivityWrapper() + .isResumed()) { + ScheduledRecording scheduledRecording = + mDataManager.getScheduledRecording( + mScheduledRecording.getId()); + if (scheduledRecording != null) { + Toast.makeText( + mContext.getApplicationContext(), + mContext.getString( + R.string + .dvr_error_insufficient_space_description_one_recording, + scheduledRecording + .getProgramDisplayTitle( + mContext)), + Toast.LENGTH_LONG) + .show(); + } + } else { + Utils.setRecordingFailedReason( + mContext.getApplicationContext(), + TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + Utils.addFailedScheduledRecordingInfo( + mContext.getApplicationContext(), + mScheduledRecording.getProgramDisplayTitle(mContext)); + } } - } else { - Utils.setRecordingFailedReason(mContext.getApplicationContext(), - TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(), - mScheduledRecording.getProgramDisplayTitle(mContext)); - } - } - }); + }); // Pass through default: failAndQuit(); @@ -296,16 +301,24 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback return; } if (mChannel.getId() != mScheduledRecording.getChannelId()) { - Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording " - + mScheduledRecording); + Log.w( + TAG, + "Channel" + + mChannel + + " does not match scheduled recording " + + mScheduledRecording); failAndQuit(); return; } String inputId = mChannel.getInputId(); - mRecordingSession = mSessionManager.createRecordingSession(inputId, - "recordingTask-" + mScheduledRecording.getId(), this, - mHandler, mScheduledRecording.getEndTimeMs()); + mRecordingSession = + mSessionManager.createRecordingSession( + inputId, + "recordingTask-" + mScheduledRecording.getId(), + this, + mHandler, + mScheduledRecording.getEndTimeMs()); mState = State.SESSION_ACQUIRED; mDvrManager.addListener(this, mHandler); mRecordingSession.tune(inputId, mChannel.getUri()); @@ -322,16 +335,18 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback private void sendRemove() { if (DEBUG) Log.d(TAG, "sendRemove"); if (mHandler != null) { - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage( - HandlerWrapper.MESSAGE_REMOVE)); + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(HandlerWrapper.MESSAGE_REMOVE)); } } private void handleStartRecording() { if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording); long programId = mScheduledRecording.getProgramId(); - mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null - : TvContract.buildProgramUri(programId)); + mRecordingSession.startRecording( + programId == ScheduledRecording.ID_NOT_SET + ? null + : TvContract.buildProgramUri(programId)); updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS); // If it starts late, it's clipped. if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS @@ -340,8 +355,8 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback } mState = State.RECORDING_STARTED; - if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, - mScheduledRecording.getEndTimeMs())) { + if (!sendEmptyMessageAtAbsoluteTime( + MSG_STOP_RECORDING, mScheduledRecording.getEndTimeMs())) { failAndQuit(); } } @@ -377,23 +392,17 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback return mScheduledRecording.getId(); } - /** - * Returns the priority. - */ + /** Returns the priority. */ public long getPriority() { return mScheduledRecording.getPriority(); } - /** - * Returns the start time of the recording. - */ + /** Returns the start time of the recording. */ public long getStartTimeMs() { return mScheduledRecording.getStartTimeMs(); } - /** - * Returns the end time of the recording. - */ + /** Returns the end time of the recording. */ public long getEndTimeMs() { return mScheduledRecording.getEndTimeMs(); } @@ -410,33 +419,41 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback long now = mClock.currentTimeMillis(); long delay = Math.max(0L, when - now); if (DEBUG) { - Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000 - + " seconds to arrive at " + Utils.toIsoDateTimeString(when)); + Log.d( + TAG, + "Sending message " + + what + + " with a delay of " + + delay / 1000 + + " seconds to arrive at " + + Utils.toIsoDateTimeString(when)); } return mHandler.sendEmptyMessageDelayed(what, delay); } private void updateRecordingState(@ScheduledRecording.RecordingState int state) { if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state); - mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state) - .build(); - runOnMainThread(new Runnable() { - @Override - public void run() { - ScheduledRecording schedule = mDataManager.getScheduledRecording( - mScheduledRecording.getId()); - if (schedule == null) { - // Schedule has been deleted. Delete the recorded program. - removeRecordedProgram(); - } else { - // Update the state based on the object in DataManager in case when it has been - // updated. mScheduledRecording will be updated from - // onScheduledRecordingStateChanged. - mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule) - .setState(state).build()); - } - } - }); + mScheduledRecording = + ScheduledRecording.buildFrom(mScheduledRecording).setState(state).build(); + runOnMainThread( + new Runnable() { + @Override + public void run() { + ScheduledRecording schedule = + mDataManager.getScheduledRecording(mScheduledRecording.getId()); + if (schedule == null) { + // Schedule has been deleted. Delete the recorded program. + removeRecordedProgram(); + } else { + // Update the state based on the object in DataManager in case when it + // has been + // updated. mScheduledRecording will be updated from + // onScheduledRecordingStateChanged. + mDataManager.updateScheduledRecording( + ScheduledRecording.buildFrom(schedule).setState(state).build()); + } + } + }); } @Override @@ -447,16 +464,12 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback stop(); } - /** - * Starts the task. - */ + /** Starts the task. */ public void start() { mHandler.sendEmptyMessage(MSG_INITIALIZE); } - /** - * Stops the task. - */ + /** Stops the task. */ public void stop() { if (DEBUG) Log.d(TAG, "stop"); switch (mState) { @@ -480,9 +493,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback } } - /** - * Cancels the task - */ + /** Cancels the task */ public void cancel() { if (DEBUG) Log.d(TAG, "cancel"); mCanceled = true; @@ -490,9 +501,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback removeRecordedProgram(); } - /** - * Clean up the task. - */ + /** Clean up the task. */ public void cleanUp() { if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) { updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); @@ -509,14 +518,15 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback } private void removeRecordedProgram() { - runOnMainThread(new Runnable() { - @Override - public void run() { - if (mRecordedProgramUri != null) { - mDvrManager.removeRecordedProgram(mRecordedProgramUri); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + if (mRecordedProgramUri != null) { + mDvrManager.removeRecordedProgram(mRecordedProgramUri); + } + } + }); } private void runOnMainThread(Runnable runnable) { diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java index d958c4a1..c59d4a93 100644 --- a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java +++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java @@ -18,23 +18,18 @@ package com.android.tv.dvr.recorder; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; - import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Clock; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -/** - * Deletes {@link ScheduledRecording} older than {@value @DAYS} days. - */ +/** Deletes {@link ScheduledRecording} older than {@value @DAYS} days. */ class ScheduledProgramReaper implements Runnable { - @VisibleForTesting - static final int DAYS = 2; + @VisibleForTesting static final int DAYS = 2; private final WritableDvrDataManager mDvrDataManager; private final Clock mClock; @@ -54,7 +49,7 @@ class ScheduledProgramReaper implements Runnable { // series recording. if (r.getEndTimeMs() < cutoff && (r.getSeriesRecordingId() == SeriesRecording.ID_NOT_SET - || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) { + || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) { toRemove.add(r); } } diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java index 15508c24..05f876ad 100644 --- a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java +++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java @@ -27,7 +27,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.LongSparseArray; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.common.CollectionUtils; @@ -40,13 +39,12 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; -import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.SeriesInfo; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.EpisodicProgramLoadTask; import com.android.tv.experiments.Experiments; - import com.android.tv.util.LocationUtils; import java.util.ArrayList; import java.util.Arrays; @@ -62,10 +60,11 @@ import java.util.Map.Entry; import java.util.Set; /** - * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for - * the {@link com.android.tv.dvr.data.SeriesRecording}. - *

- * The current implementation assumes that the series recordings are scheduled only for one channel. + * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for the {@link + * com.android.tv.dvr.data.SeriesRecording}. + * + *

The current implementation assumes that the series recordings are scheduled only for one + * channel. */ @TargetApi(Build.VERSION_CODES.N) public class SeriesRecordingScheduler { @@ -78,9 +77,7 @@ public class SeriesRecordingScheduler { @SuppressLint("StaticFieldLeak") private static SeriesRecordingScheduler sInstance; - /** - * Creates and returns the {@link SeriesRecordingScheduler}. - */ + /** Creates and returns the {@link SeriesRecordingScheduler}. */ public static synchronized SeriesRecordingScheduler getInstance(Context context) { if (sInstance == null) { sInstance = new SeriesRecordingScheduler(context); @@ -100,54 +97,59 @@ public class SeriesRecordingScheduler { private boolean mPaused; private final Set mPendingSeriesRecordings = new ArraySet<>(); - private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() { - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - executeFetchSeriesInfoTask(seriesRecording); - } - } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { - // Cancel the update. - for (Iterator iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { - SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR).isEmpty()) { - task.cancel(true); - iter.remove(); + private final SeriesRecordingListener mSeriesRecordingListener = + new SeriesRecordingListener() { + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { + for (SeriesRecording seriesRecording : seriesRecordings) { + executeFetchSeriesInfoTask(seriesRecording); + } } - } - for (SeriesRecording seriesRecording : seriesRecordings) { - FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(seriesRecording.getId()); - if (task != null) { - task.cancel(true); - mFetchSeriesInfoTasks.remove(seriesRecording.getId()); + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + // Cancel the update. + for (Iterator iter = mScheduleTasks.iterator(); + iter.hasNext(); ) { + SeriesRecordingUpdateTask task = iter.next(); + if (CollectionUtils.subtract( + task.getSeriesRecordings(), + seriesRecordings, + SeriesRecording.ID_COMPARATOR) + .isEmpty()) { + task.cancel(true); + iter.remove(); + } + } + for (SeriesRecording seriesRecording : seriesRecordings) { + FetchSeriesInfoTask task = + mFetchSeriesInfoTasks.get(seriesRecording.getId()); + if (task != null) { + task.cancel(true); + mFetchSeriesInfoTasks.remove(seriesRecording.getId()); + } + } } - } - } - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - List stopped = new ArrayList<>(); - List normal = new ArrayList<>(); - for (SeriesRecording r : seriesRecordings) { - if (r.isStopped()) { - stopped.add(r); - } else { - normal.add(r); + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + List stopped = new ArrayList<>(); + List normal = new ArrayList<>(); + for (SeriesRecording r : seriesRecordings) { + if (r.isStopped()) { + stopped.add(r); + } else { + normal.add(r); + } + } + if (!stopped.isEmpty()) { + onSeriesRecordingRemoved(SeriesRecording.toArray(stopped)); + } + if (!normal.isEmpty()) { + updateSchedules(normal); + } } - } - if (!stopped.isEmpty()) { - onSeriesRecordingRemoved(SeriesRecording.toArray(stopped)); - } - if (!normal.isEmpty()) { - updateSchedules(normal); - } - } - }; + }; private final ScheduledRecordingListener mScheduledRecordingListener = new ScheduledRecordingListener() { @@ -166,7 +168,8 @@ public class SeriesRecordingScheduler { List schedulesForUpdate = new ArrayList<>(); for (ScheduledRecording r : schedules) { if ((r.getState() == ScheduledRecording.STATE_RECORDING_FAILED - || r.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED) + || r.getState() + == ScheduledRecording.STATE_RECORDING_CLIPPED) && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET && !TextUtils.isEmpty(r.getSeasonNumber()) && !TextUtils.isEmpty(r.getEpisodeNumber())) { @@ -208,15 +211,14 @@ public class SeriesRecordingScheduler { ApplicationSingletons appSingletons = TvApplication.getSingletons(context); mDvrManager = appSingletons.getDvrManager(); mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); - mSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); - mFetchedSeriesIds.addAll(mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, - Collections.emptySet())); + mSharedPreferences = + context.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); + mFetchedSeriesIds.addAll( + mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, Collections.emptySet())); } - /** - * Starts the scheduler. - */ + /** Starts the scheduler. */ @MainThread public void start() { SoftPreconditions.checkState(mDataManager.isInitialized()); @@ -267,9 +269,7 @@ public class SeriesRecordingScheduler { } } - /** - * Pauses the updates of the series recordings. - */ + /** Pauses the updates of the series recordings. */ public void pauseUpdate() { if (DEBUG) Log.d(TAG, "Schedule paused"); if (mPaused) { @@ -287,9 +287,7 @@ public class SeriesRecordingScheduler { } } - /** - * Resumes the updates of the series recordings. - */ + /** Resumes the updates of the series recordings. */ public void resumeUpdate() { if (DEBUG) Log.d(TAG, "Schedule resumed"); if (!mPaused) { @@ -329,25 +327,28 @@ public class SeriesRecordingScheduler { mPendingSeriesRecordings.add(r.getId()); } if (DEBUG) { - Log.d(TAG, "The scheduler has been paused. Adding to the pending list. size=" - + mPendingSeriesRecordings.size()); + Log.d( + TAG, + "The scheduler has been paused. Adding to the pending list. size=" + + mPendingSeriesRecordings.size()); } return; } Set previousSeriesRecordings = new HashSet<>(); for (Iterator iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { + iter.hasNext(); ) { SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.containsAny(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR)) { + if (CollectionUtils.containsAny( + task.getSeriesRecordings(), seriesRecordings, SeriesRecording.ID_COMPARATOR)) { // The task is affected by the seriesRecordings task.cancel(true); previousSeriesRecordings.addAll(task.getSeriesRecordings()); iter.remove(); } } - List seriesRecordingsToUpdate = CollectionUtils.union(seriesRecordings, - previousSeriesRecordings, SeriesRecording.ID_COMPARATOR); + List seriesRecordingsToUpdate = + CollectionUtils.union( + seriesRecordings, previousSeriesRecordings, SeriesRecording.ID_COMPARATOR); for (Iterator iter = seriesRecordingsToUpdate.iterator(); iter.hasNext(); ) { SeriesRecording seriesRecording = mDataManager.getSeriesRecording(iter.next().getId()); @@ -367,8 +368,8 @@ public class SeriesRecordingScheduler { task.execute(); } else { for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { - SeriesRecordingUpdateTask task = new SeriesRecordingUpdateTask( - Collections.singletonList(seriesRecording)); + SeriesRecordingUpdateTask task = + new SeriesRecordingUpdateTask(Collections.singletonList(seriesRecording)); mScheduleTasks.add(task); if (DEBUG) Log.d(TAG, "Added schedule task: " + task); task.execute(); @@ -389,8 +390,9 @@ public class SeriesRecordingScheduler { * Pick one program per an episode. * *

Note that the programs which has been already scheduled have the highest priority, and all - * of them are added even though they are the same episodes. That's because the schedules - * should be added to the series recording. + * of them are added even though they are the same episodes. That's because the schedules should + * be added to the series recording. + * *

If there are no existing schedules for an episode, one program which starts earlier is * picked. */ @@ -399,11 +401,10 @@ public class SeriesRecordingScheduler { return pickOneProgramPerEpisode(mDataManager, seriesRecordings, programs); } - /** - * @see #pickOneProgramPerEpisode(List, List) - */ + /** @see #pickOneProgramPerEpisode(List, List) */ public static LongSparseArray> pickOneProgramPerEpisode( - DvrDataManager dataManager, List seriesRecordings, + DvrDataManager dataManager, + List seriesRecordings, List programs) { // Initialize. LongSparseArray> result = new LongSparseArray<>(); @@ -422,8 +423,11 @@ public class SeriesRecordingScheduler { result.get(seriesRecordingId).add(program); continue; } - SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber(seriesRecordingId, - program.getSeasonNumber(), program.getEpisodeNumber()); + SeasonEpisodeNumber seasonEpisodeNumber = + new SeasonEpisodeNumber( + seriesRecordingId, + program.getSeasonNumber(), + program.getEpisodeNumber()); List programsForEpisode = programsForEpisodeMap.get(seasonEpisodeNumber); if (programsForEpisode == null) { programsForEpisode = new ArrayList<>(); @@ -434,22 +438,24 @@ public class SeriesRecordingScheduler { // Pick one program. for (Entry> entry : programsForEpisodeMap.entrySet()) { List programsForEpisode = entry.getValue(); - Collections.sort(programsForEpisode, new Comparator() { - @Override - public int compare(Program lhs, Program rhs) { - // Place the existing schedule first. - boolean lhsScheduled = isProgramScheduled(dataManager, lhs); - boolean rhsScheduled = isProgramScheduled(dataManager, rhs); - if (lhsScheduled && !rhsScheduled) { - return -1; - } - if (!lhsScheduled && rhsScheduled) { - return 1; - } - // Sort by the start time in ascending order. - return lhs.compareTo(rhs); - } - }); + Collections.sort( + programsForEpisode, + new Comparator() { + @Override + public int compare(Program lhs, Program rhs) { + // Place the existing schedule first. + boolean lhsScheduled = isProgramScheduled(dataManager, lhs); + boolean rhsScheduled = isProgramScheduled(dataManager, rhs); + if (lhsScheduled && !rhsScheduled) { + return -1; + } + if (!lhsScheduled && rhsScheduled) { + return 1; + } + // Sort by the start time in ascending order. + return lhs.compareTo(rhs); + } + }); boolean added = false; // Add all the scheduled programs List programsForSeries = result.get(entry.getKey().seriesRecordingId); @@ -469,8 +475,8 @@ public class SeriesRecordingScheduler { private static boolean isProgramScheduled(DvrDataManager dataManager, Program program) { ScheduledRecording schedule = dataManager.getScheduledRecordingForProgramId(program.getId()); - return schedule != null && schedule.getState() - == ScheduledRecording.STATE_RECORDING_NOT_STARTED; + return schedule != null + && schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED; } private void updateFetchedSeries() { @@ -478,8 +484,8 @@ public class SeriesRecordingScheduler { } /** - * This works only for the existing series recordings. Do not use this task for the - * "adding series recording" UI. + * This works only for the existing series recordings. Do not use this task for the "adding + * series recording" UI. */ private class SeriesRecordingUpdateTask extends EpisodicProgramLoadTask { SeriesRecordingUpdateTask(List seriesRecordings) { @@ -491,16 +497,17 @@ public class SeriesRecordingScheduler { if (DEBUG) Log.d(TAG, "onPostExecute: updating schedules with programs:" + programs); mScheduleTasks.remove(this); if (programs == null) { - Log.e(TAG, "Creating schedules for series recording failed: " - + getSeriesRecordings()); + Log.e( + TAG, + "Creating schedules for series recording failed: " + getSeriesRecordings()); return; } - LongSparseArray> seriesProgramMap = pickOneProgramPerEpisode( - getSeriesRecordings(), programs); + LongSparseArray> seriesProgramMap = + pickOneProgramPerEpisode(getSeriesRecordings(), programs); for (SeriesRecording seriesRecording : getSeriesRecordings()) { // Check the series recording is still valid. - SeriesRecording actualSeriesRecording = mDataManager.getSeriesRecording( - seriesRecording.getId()); + SeriesRecording actualSeriesRecording = + mDataManager.getSeriesRecording(seriesRecording.getId()); if (actualSeriesRecording == null || actualSeriesRecording.isStopped()) { continue; } @@ -520,7 +527,8 @@ public class SeriesRecordingScheduler { @Override public String toString() { return "SeriesRecordingUpdateTask:{" - + "series_recordings=" + getSeriesRecordings() + + "series_recordings=" + + getSeriesRecordings() + "}"; } } @@ -541,14 +549,15 @@ public class SeriesRecordingScheduler { @Override protected void onPostExecute(SeriesInfo seriesInfo) { if (seriesInfo != null) { - mDataManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording) - .setTitle(seriesInfo.getTitle()) - .setDescription(seriesInfo.getDescription()) - .setLongDescription(seriesInfo.getLongDescription()) - .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds()) - .setPosterUri(seriesInfo.getPosterUri()) - .setPhotoUri(seriesInfo.getPhotoUri()) - .build()); + mDataManager.updateSeriesRecording( + SeriesRecording.buildFrom(mSeriesRecording) + .setTitle(seriesInfo.getTitle()) + .setDescription(seriesInfo.getDescription()) + .setLongDescription(seriesInfo.getLongDescription()) + .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds()) + .setPosterUri(seriesInfo.getPosterUri()) + .setPhotoUri(seriesInfo.getPhotoUri()) + .build()); mFetchedSeriesIds.add(seriesInfo.getId()); updateFetchedSeries(); } diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java index ec3b5065..0d6ff8b1 100644 --- a/src/com/android/tv/dvr/ui/BigArguments.java +++ b/src/com/android/tv/dvr/ui/BigArguments.java @@ -17,37 +17,27 @@ package com.android.tv.dvr.ui; import android.support.annotation.NonNull; - import com.android.tv.common.SoftPreconditions; - import java.util.HashMap; import java.util.Map; -/** - * Stores the object to pass through activities/fragments. - */ +/** Stores the object to pass through activities/fragments. */ public class BigArguments { - private final static String TAG = "BigArguments"; + private static final String TAG = "BigArguments"; private static Map sBigArgumentMap = new HashMap<>(); - /** - * Sets the argument. - */ + /** Sets the argument. */ public static void setArgument(String name, @NonNull Object value) { SoftPreconditions.checkState(value != null, TAG, "Set argument, but value is null"); sBigArgumentMap.put(name, value); } - /** - * Returns the argument which is associated to the name. - */ + /** Returns the argument which is associated to the name. */ public static Object getArgument(String name) { return sBigArgumentMap.get(name); } - /** - * Resets the arguments. - */ + /** Resets the arguments. */ public static void reset() { sBigArgumentMap.clear(); } diff --git a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java index cddece73..32679421 100644 --- a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java +++ b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java @@ -26,16 +26,14 @@ import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.ImageView.ScaleType; - import com.android.tv.R; - import java.util.Map; /** - * TODO: Remove this class once b/32405620 is fixed. - * This class is for the workaround of b/32405620 and only for the shared element transition between - * {@link com.android.tv.dvr.ui.browse.RecordingCardView} and - * {@link com.android.tv.dvr.ui.browse.DvrDetailsActivity}. + * TODO: Remove this class once b/32405620 is fixed. This class is for the workaround of b/32405620 + * and only for the shared element transition between {@link + * com.android.tv.dvr.ui.browse.RecordingCardView} and {@link + * com.android.tv.dvr.ui.browse.DvrDetailsActivity}. */ public class ChangeImageTransformWithScaledParent extends ChangeImageTransform { private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix"; @@ -60,7 +58,8 @@ public class ChangeImageTransformWithScaledParent extends ChangeImageTransform { View view = transitionValues.view; Map values = transitionValues.values; Matrix matrix = (Matrix) values.get(PROPNAME_MATRIX); - if (matrix != null && view.getId() == R.id.details_overview_image + if (matrix != null + && view.getId() == R.id.details_overview_image && view instanceof ImageView) { ImageView imageView = (ImageView) view; if (imageView.getScaleType() == ScaleType.CENTER_INSIDE @@ -68,10 +67,13 @@ public class ChangeImageTransformWithScaledParent extends ChangeImageTransform { Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); if (bitmap.getWidth() < imageView.getWidth() && bitmap.getHeight() < imageView.getHeight()) { - float scale = imageView.getContext().getResources().getFraction( - R.fraction.lb_focus_zoom_factor_medium, 1, 1); - matrix.postScale(scale, scale, imageView.getWidth() / 2, - imageView.getHeight() / 2); + float scale = + imageView + .getContext() + .getResources() + .getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1); + matrix.postScale( + scale, scale, imageView.getWidth() / 2, imageView.getHeight() / 2); } } } diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java index 62327870..f4077e44 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java @@ -24,13 +24,11 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.RecordedProgram; - import java.util.List; /** @@ -52,12 +50,18 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment { super.onAttach(context); mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager(); - mDuplicate = dvrManager.getRecordedProgram(mProgram.getTitle(), - mProgram.getSeasonNumber(), mProgram.getEpisodeNumber()); + mDuplicate = + dvrManager.getRecordedProgram( + mProgram.getTitle(), + mProgram.getSeasonNumber(), + mProgram.getEpisodeNumber()); if (mDuplicate == null) { dvrManager.addSchedule(mProgram); - DvrUiHelper.showAddScheduleToast(context, mProgram.getTitle(), - mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis()); + DvrUiHelper.showAddScheduleToast( + context, + mProgram.getTitle(), + mProgram.getStartTimeUtcMillis(), + mProgram.getEndTimeUtcMillis()); dismissDialog(); } } @@ -74,18 +78,21 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { Context context = getContext(); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_RECORD_ANYWAY) - .title(R.string.dvr_action_record_anyway) - .build()); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_WATCH) - .title(R.string.dvr_action_watch_now) - .build()); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_CANCEL) - .title(R.string.dvr_action_record_cancel) - .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_RECORD_ANYWAY) + .title(R.string.dvr_action_record_anyway) + .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_WATCH) + .title(R.string.dvr_action_watch_now) + .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_CANCEL) + .title(R.string.dvr_action_record_cancel) + .build()); } @Override diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java index 6da75e55..f27ec5c5 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java @@ -25,13 +25,11 @@ import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.text.format.DateUtils; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; - import java.util.List; /** @@ -53,12 +51,18 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { super.onAttach(context); mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager(); - mDuplicate = dvrManager.getScheduledRecording(mProgram.getTitle(), - mProgram.getSeasonNumber(), mProgram.getEpisodeNumber()); + mDuplicate = + dvrManager.getScheduledRecording( + mProgram.getTitle(), + mProgram.getSeasonNumber(), + mProgram.getEpisodeNumber()); if (mDuplicate == null) { dvrManager.addSchedule(mProgram); - DvrUiHelper.showAddScheduleToast(context, mProgram.getTitle(), - mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis()); + DvrUiHelper.showAddScheduleToast( + context, + mProgram.getTitle(), + mProgram.getStartTimeUtcMillis(), + mProgram.getEndTimeUtcMillis()); dismissDialog(); } } @@ -67,9 +71,13 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getString(R.string.dvr_already_scheduled_dialog_title); - String description = getString(R.string.dvr_already_scheduled_dialog_description, - DateUtils.formatDateTime(getContext(), mDuplicate.getStartTimeMs(), - DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE)); + String description = + getString( + R.string.dvr_already_scheduled_dialog_description, + DateUtils.formatDateTime( + getContext(), + mDuplicate.getStartTimeMs(), + DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE)); Drawable image = getResources().getDrawable(R.drawable.ic_warning_white_96dp, null); return new Guidance(title, description, null, image); } @@ -77,18 +85,21 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { Context context = getContext(); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_RECORD_ANYWAY) - .title(R.string.dvr_action_record_anyway) - .build()); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_RECORD_INSTEAD) - .title(R.string.dvr_action_record_instead) - .build()); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_CANCEL) - .title(R.string.dvr_action_record_cancel) - .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_RECORD_ANYWAY) + .title(R.string.dvr_action_record_anyway) + .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_RECORD_INSTEAD) + .title(R.string.dvr_action_record_instead) + .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_CANCEL) + .title(R.string.dvr_action_record_cancel) + .build()); } @Override diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java index 36659412..e247b82b 100644 --- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java @@ -21,7 +21,6 @@ import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -29,7 +28,6 @@ import com.android.tv.data.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -43,8 +41,10 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen Bundle args = getArguments(); if (args != null) { long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); - mChannel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(channelId); + mChannel = + TvApplication.getSingletons(getContext()) + .getChannelDataManager() + .getChannel(channelId); } SoftPreconditions.checkArgument(mChannel != null); super.onCreate(savedInstanceState); @@ -66,22 +66,26 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen mDurations.add(TimeUnit.HOURS.toMillis(1)); mDurations.add(TimeUnit.HOURS.toMillis(3)); - actions.add(new GuidedAction.Builder(getContext()) - .id(++actionId) - .title(R.string.recording_start_dialog_10_min_duration) - .build()); - actions.add(new GuidedAction.Builder(getContext()) - .id(++actionId) - .title(R.string.recording_start_dialog_30_min_duration) - .build()); - actions.add(new GuidedAction.Builder(getContext()) - .id(++actionId) - .title(R.string.recording_start_dialog_1_hour_duration) - .build()); - actions.add(new GuidedAction.Builder(getContext()) - .id(++actionId) - .title(R.string.recording_start_dialog_3_hours_duration) - .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(++actionId) + .title(R.string.recording_start_dialog_10_min_duration) + .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(++actionId) + .title(R.string.recording_start_dialog_30_min_duration) + .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(++actionId) + .title(R.string.recording_start_dialog_1_hour_duration) + .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(++actionId) + .title(R.string.recording_start_dialog_3_hours_duration) + .build()); } @Override @@ -90,8 +94,8 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen long duration = mDurations.get((int) action.getId()); long startTimeMs = System.currentTimeMillis(); long endTimeMs = System.currentTimeMillis() + duration; - List conflicts = dvrManager.getConflictingSchedules( - mChannel.getId(), startTimeMs, endTimeMs); + List conflicts = + dvrManager.getConflictingSchedules(mChannel.getId(), startTimeMs, endTimeMs); dvrManager.addSchedule(mChannel, startTimeMs, endTimeMs); if (conflicts.isEmpty()) { dismissDialog(); @@ -102,8 +106,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen args.putLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS, startTimeMs); args.putLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS, endTimeMs); fragment.setArguments(args); - GuidedStepFragment.add(getFragmentManager(), fragment, - R.id.halfsized_dialog_host); + GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host); } } diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java index 6f362e68..80011acd 100644 --- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java +++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java @@ -27,18 +27,16 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.ConflictChecker; import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener; -import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -72,15 +70,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getContext()) - .clickAction(GuidedAction.ACTION_ID_OK) - .build()); - actions.add(new GuidedAction.Builder(getContext()) - .id(ACTION_VIEW_SCHEDULES) - .title(R.string.dvr_action_view_schedules) - .build()); + public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { + actions.add( + new GuidedAction.Builder(getContext()) + .clickAction(GuidedAction.ACTION_ID_OK) + .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(ACTION_VIEW_SCHEDULES) + .title(R.string.dvr_action_view_schedules) + .build()); } @Override @@ -114,33 +113,45 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } switch (titles.size()) { case 0: - Log.i(TAG, "Conflict has been resolved by any reason. Maybe input might have" - + " been deleted."); + Log.i( + TAG, + "Conflict has been resolved by any reason. Maybe input might have" + + " been deleted."); return null; case 1: - return getResources().getString( - R.string.dvr_program_conflict_dialog_description_1, titles.get(0)); + return getResources() + .getString( + R.string.dvr_program_conflict_dialog_description_1, titles.get(0)); case 2: - return getResources().getString( - R.string.dvr_program_conflict_dialog_description_2, titles.get(0), - titles.get(1)); + return getResources() + .getString( + R.string.dvr_program_conflict_dialog_description_2, + titles.get(0), + titles.get(1)); case 3: - return getResources().getString( - R.string.dvr_program_conflict_dialog_description_3, titles.get(0), - titles.get(1)); + return getResources() + .getString( + R.string.dvr_program_conflict_dialog_description_3, + titles.get(0), + titles.get(1)); default: - return getResources().getQuantityString( - R.plurals.dvr_program_conflict_dialog_description_many, - titles.size() - LISTED_PROGRAM_COUNT, titles.get(0), titles.get(1), - titles.size() - LISTED_PROGRAM_COUNT); + return getResources() + .getQuantityString( + R.plurals.dvr_program_conflict_dialog_description_many, + titles.size() - LISTED_PROGRAM_COUNT, + titles.get(0), + titles.get(1), + titles.size() - LISTED_PROGRAM_COUNT); } } @Nullable private String getScheduleTitle(ScheduledRecording schedule) { if (schedule.getType() == ScheduledRecording.TYPE_TIMED) { - Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(schedule.getChannelId()); + Channel channel = + TvApplication.getSingletons(getContext()) + .getChannelDataManager() + .getChannel(schedule.getChannelId()); if (channel != null) { return channel.getDisplayName(); } else { @@ -151,14 +162,13 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } } - /** - * A fragment to show the program conflict. - */ + /** A fragment to show the program conflict. */ public static class DvrProgramConflictFragment extends DvrConflictFragment { private Program mProgram; + @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); @@ -168,8 +178,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { SoftPreconditions.checkNotNull(input); List conflicts = null; if (input != null) { - conflicts = TvApplication.getSingletons(getContext()).getDvrManager() - .getConflictingSchedules(mProgram); + conflicts = + TvApplication.getSingletons(getContext()) + .getDvrManager() + .getConflictingSchedules(mProgram); } if (conflicts == null) { conflicts = Collections.emptyList(); @@ -185,8 +197,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_program_conflict_dialog_title); - String descriptionPrefix = getString( - R.string.dvr_program_conflict_dialog_description_prefix, mProgram.getTitle()); + String descriptionPrefix = + getString( + R.string.dvr_program_conflict_dialog_description_prefix, + mProgram.getTitle()); String description = getConflictDescription(); if (description == null) { dismissDialog(); @@ -201,21 +215,21 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { } } - /** - * A fragment to show the channel recording conflict. - */ + /** A fragment to show the channel recording conflict. */ public static class DvrChannelRecordConflictFragment extends DvrConflictFragment { private Channel mChannel; private long mStartTimeMs; private long mEndTimeMs; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); - mChannel = TvApplication.getSingletons(getContext()).getChannelDataManager() - .getChannel(channelId); + mChannel = + TvApplication.getSingletons(getContext()) + .getChannelDataManager() + .getChannel(channelId); SoftPreconditions.checkArgument(mChannel != null); TvInputInfo input = Utils.getTvInputInfoForChannelId(getContext(), mChannel.getId()); SoftPreconditions.checkNotNull(input); @@ -223,8 +237,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { if (input != null) { mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS); mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS); - conflicts = TvApplication.getSingletons(getContext()).getDvrManager() - .getConflictingSchedules(mChannel.getId(), mStartTimeMs, mEndTimeMs); + conflicts = + TvApplication.getSingletons(getContext()) + .getDvrManager() + .getConflictingSchedules( + mChannel.getId(), mStartTimeMs, mEndTimeMs); } if (conflicts == null) { conflicts = Collections.emptyList(); @@ -240,9 +257,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_channel_conflict_dialog_title); - String descriptionPrefix = getString( - R.string.dvr_channel_conflict_dialog_description_prefix, - mChannel.getDisplayName()); + String descriptionPrefix = + getString( + R.string.dvr_channel_conflict_dialog_description_prefix, + mChannel.getDisplayName()); String description = getConflictDescription(); if (description == null) { dismissDialog(); @@ -259,16 +277,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { /** * A fragment to show the channel watching conflict. - *

- * This fragment is automatically closed when there are no upcoming conflicts. + * + *

This fragment is automatically closed when there are no upcoming conflicts. */ public static class DvrChannelWatchConflictFragment extends DvrConflictFragment implements OnUpcomingConflictChangeListener { private long mChannelId; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { mChannelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); @@ -298,24 +316,27 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { @NonNull @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString( - R.string.dvr_epg_channel_watch_conflict_dialog_title); - String description = getResources().getString( - R.string.dvr_epg_channel_watch_conflict_dialog_description); + String title = + getResources().getString(R.string.dvr_epg_channel_watch_conflict_dialog_title); + String description = + getResources() + .getString(R.string.dvr_epg_channel_watch_conflict_dialog_description); return new Guidance(title, description, null, null); } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getContext()) - .id(ACTION_DELETE_CONFLICT) - .title(R.string.dvr_action_delete_schedule) - .build()); - actions.add(new GuidedAction.Builder(getContext()) - .id(ACTION_CANCEL) - .title(R.string.dvr_action_record_program) - .build()); + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { + actions.add( + new GuidedAction.Builder(getContext()) + .id(ACTION_DELETE_CONFLICT) + .title(R.string.dvr_action_delete_schedule) + .build()); + actions.add( + new GuidedAction.Builder(getContext()) + .id(ACTION_CANCEL) + .title(R.string.dvr_action_record_program) + .build()); } @Override diff --git a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java index 6b0c22ff..611962d0 100644 --- a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java +++ b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java @@ -24,12 +24,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; - import com.android.tv.R; -/** - * Stylist class used for DVR settings {@link GuidedStepFragment}. - */ +/** Stylist class used for DVR settings {@link GuidedStepFragment}. */ public class DvrGuidedActionsStylist extends GuidedActionsStylist { private static boolean sInitialized; private static float sWidthWeight; @@ -68,11 +65,13 @@ public class DvrGuidedActionsStylist extends GuidedActionsStylist { return; } sInitialized = true; - sItemHeight = context.getResources().getDimensionPixelSize( - R.dimen.dvr_settings_one_line_action_container_height); + sItemHeight = + context.getResources() + .getDimensionPixelSize( + R.dimen.dvr_settings_one_line_action_container_height); TypedValue outValue = new TypedValue(); - context.getResources().getValue(R.dimen.dvr_settings_button_actions_list_width_weight, - outValue, true); + context.getResources() + .getValue(R.dimen.dvr_settings_button_actions_list_width_weight, outValue, true); sWidthWeight = outValue.getFloat(); } } diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java index ab852e10..8524e1ea 100644 --- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java +++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java @@ -26,7 +26,6 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; @@ -35,22 +34,16 @@ import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener; import com.android.tv.dialog.SafeDismissDialogFragment; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrStorageStatusManager; - import java.util.List; public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { - /** - * Action ID for "recording/scheduling the program anyway". - */ + /** Action ID for "recording/scheduling the program anyway". */ public static final int ACTION_RECORD_ANYWAY = 1; - /** - * Action ID for "deleting existed recordings". - */ + /** Action ID for "deleting existed recordings". */ public static final int ACTION_DELETE_RECORDINGS = 2; - /** - * Action ID for "cancelling current recording request". - */ + /** Action ID for "cancelling current recording request". */ public static final int ACTION_CANCEL_RECORDING = 3; + public static final String UNKNOWN_DVR_ACTION = "Unknown DVR Action"; private DvrManager mDvrManager; @@ -68,8 +61,8 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); VerticalGridView actionsList = getGuidedActionsStylist().getActionsGridView(); actionsList.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE); @@ -122,32 +115,37 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { } /** - * The inner guided step fragment for - * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment + * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment * .DvrNoFreeSpaceErrorDialogFragment}. */ public static class DvrNoFreeSpaceErrorFragment extends DvrGuidedStepFragment { @Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { - return new GuidanceStylist.Guidance(getString(R.string.dvr_error_no_free_space_title), - getString(R.string.dvr_error_no_free_space_description), null, null); + return new GuidanceStylist.Guidance( + getString(R.string.dvr_error_no_free_space_title), + getString(R.string.dvr_error_no_free_space_description), + null, + null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_RECORD_ANYWAY) - .title(R.string.dvr_action_record_anyway) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_DELETE_RECORDINGS) - .title(R.string.dvr_action_delete_recordings) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_CANCEL_RECORDING) - .title(R.string.dvr_action_record_cancel) - .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_RECORD_ANYWAY) + .title(R.string.dvr_action_record_anyway) + .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_DELETE_RECORDINGS) + .title(R.string.dvr_action_delete_recordings) + .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_CANCEL_RECORDING) + .title(R.string.dvr_action_record_cancel) + .build()); } @Override @@ -157,29 +155,32 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { } /** - * The inner guided step fragment for - * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment + * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment * .DvrSmallSizedStorageErrorDialogFragment}. */ public static class DvrSmallSizedStorageErrorFragment extends DvrGuidedStepFragment { @Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getResources().getString( - R.string.dvr_error_small_sized_storage_title); - String description = getResources().getString( - R.string.dvr_error_small_sized_storage_description, - DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024 - / 1024 / 1024); + String title = getResources().getString(R.string.dvr_error_small_sized_storage_title); + String description = + getResources() + .getString( + R.string.dvr_error_small_sized_storage_description, + DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES + / 1024 + / 1024 + / 1024); return new GuidanceStylist.Guidance(title, description, null, null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(GuidedAction.ACTION_ID_OK) - .title(android.R.string.ok) - .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(GuidedAction.ACTION_ID_OK) + .title(android.R.string.ok) + .build()); } @Override @@ -192,4 +193,4 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { return "DvrSmallSizedStorageErrorFragment"; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java index f8ef3850..3ee1e48a 100644 --- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java +++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java @@ -20,47 +20,26 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; -import android.support.v17.leanback.widget.GuidedAction; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment; import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment; import com.android.tv.guide.ProgramGuide; -import java.util.List; - public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { - /** - * Key for input ID. - * Type: String. - */ + /** Key for input ID. Type: String. */ public static final String KEY_INPUT_ID = "DvrHalfSizedDialogFragment.input_id"; - /** - * Key for the program. - * Type: {@link com.android.tv.data.Program}. - */ + /** Key for the program. Type: {@link com.android.tv.data.Program}. */ public static final String KEY_PROGRAM = "DvrHalfSizedDialogFragment.program"; - /** - * Key for the channel ID. - * Type: long. - */ + /** Key for the channel ID. Type: long. */ public static final String KEY_CHANNEL_ID = "DvrHalfSizedDialogFragment.channel_id"; - /** - * Key for the recording start time in millisecond. - * Type: long. - */ + /** Key for the recording start time in millisecond. Type: long. */ public static final String KEY_START_TIME_MS = "DvrHalfSizedDialogFragment.start_time_ms"; - /** - * Key for the recording end time in millisecond. - * Type: long. - */ + /** Key for the recording end time in millisecond. Type: long. */ public static final String KEY_END_TIME_MS = "DvrHalfSizedDialogFragment.end_time_ms"; @Override @@ -93,14 +72,14 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { private DvrGuidedStepFragment mFragment; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); mFragment = onCreateGuidedStepFragment(); mFragment.setArguments(getArguments()); mFragment.setOnActionClickListener(getOnActionClickListener()); - GuidedStepFragment.add(getChildFragmentManager(), - mFragment, R.id.halfsized_dialog_host); + GuidedStepFragment.add( + getChildFragmentManager(), mFragment, R.id.halfsized_dialog_host); return view; } @@ -158,19 +137,15 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { } /** A dialog fragment for {@link DvrMissingStorageErrorFragment}. */ - public static class DvrMissingStorageErrorDialogFragment - extends DvrGuidedStepDialogFragment { + public static class DvrMissingStorageErrorDialogFragment extends DvrGuidedStepDialogFragment { @Override protected DvrGuidedStepFragment onCreateGuidedStepFragment() { return new DvrMissingStorageErrorFragment(); } } - /** - * A dialog fragment to show error message when there is no enough free space to record. - */ - public static class DvrNoFreeSpaceErrorDialogFragment - extends DvrGuidedStepDialogFragment { + /** A dialog fragment to show error message when there is no enough free space to record. */ + public static class DvrNoFreeSpaceErrorDialogFragment extends DvrGuidedStepDialogFragment { @Override protected DvrGuidedStepFragment onCreateGuidedStepFragment() { return new DvrGuidedStepFragment.DvrNoFreeSpaceErrorFragment(); @@ -178,8 +153,7 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { } /** - * A dialog fragment to show error message when the current storage is too small to - * support DVR + * A dialog fragment to show error message when the current storage is too small to support DVR */ public static class DvrSmallSizedStorageErrorDialogFragment extends DvrGuidedStepDialogFragment { @@ -212,4 +186,4 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment { return new DvrAlreadyRecordedFragment(); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java index 182416b6..ad26a5c2 100644 --- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java +++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java @@ -22,19 +22,15 @@ import android.content.Intent; import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.ui.browse.DvrBrowseActivity; - import java.util.ArrayList; import java.util.List; public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { - /** - * Key for the failed scheduled recordings information. - */ + /** Key for the failed scheduled recordings information. */ public static final String FAILED_SCHEDULED_RECORDING_INFOS = "failed_scheduled_recording_infos"; @@ -54,7 +50,8 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { } SoftPreconditions.checkState( mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(), - TAG, "failed scheduled recording is null"); + TAG, + "failed scheduled recording is null"); } @Override @@ -63,28 +60,39 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { String description; int failedScheduledRecordingSize = mFailedScheduledRecordingInfos.size(); if (failedScheduledRecordingSize == 1) { - title = getString( - R.string.dvr_error_insufficient_space_title_one_recording, - mFailedScheduledRecordingInfos.get(0)); - description = getString( - R.string.dvr_error_insufficient_space_description_one_recording, - mFailedScheduledRecordingInfos.get(0)); + title = + getString( + R.string.dvr_error_insufficient_space_title_one_recording, + mFailedScheduledRecordingInfos.get(0)); + description = + getString( + R.string.dvr_error_insufficient_space_description_one_recording, + mFailedScheduledRecordingInfos.get(0)); } else if (failedScheduledRecordingSize == 2) { - title = getString( - R.string.dvr_error_insufficient_space_title_two_recordings, - mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1)); - description = getString( - R.string.dvr_error_insufficient_space_description_two_recordings, - mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1)); + title = + getString( + R.string.dvr_error_insufficient_space_title_two_recordings, + mFailedScheduledRecordingInfos.get(0), + mFailedScheduledRecordingInfos.get(1)); + description = + getString( + R.string.dvr_error_insufficient_space_description_two_recordings, + mFailedScheduledRecordingInfos.get(0), + mFailedScheduledRecordingInfos.get(1)); } else { - title = getString( - R.string.dvr_error_insufficient_space_title_three_or_more_recordings, - mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1), - mFailedScheduledRecordingInfos.get(2)); - description = getString( - R.string.dvr_error_insufficient_space_description_three_or_more_recordings, - mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1), - mFailedScheduledRecordingInfos.get(2)); + title = + getString( + R.string.dvr_error_insufficient_space_title_three_or_more_recordings, + mFailedScheduledRecordingInfos.get(0), + mFailedScheduledRecordingInfos.get(1), + mFailedScheduledRecordingInfos.get(2)); + description = + getString( + R.string + .dvr_error_insufficient_space_description_three_or_more_recordings, + mFailedScheduledRecordingInfos.get(0), + mFailedScheduledRecordingInfos.get(1), + mFailedScheduledRecordingInfos.get(2)); } return new Guidance(title, description, null, null); } @@ -92,15 +100,18 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .clickAction(GuidedAction.ACTION_ID_OK) - .build()); + actions.add( + new GuidedAction.Builder(activity).clickAction(GuidedAction.ACTION_ID_OK).build()); if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) { - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_VIEW_RECENT_RECORDINGS) - .title(getResources().getString( - R.string.dvr_error_insufficient_space_action_view_recent_recordings)) - .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_VIEW_RECENT_RECORDINGS) + .title( + getResources() + .getString( + R.string + .dvr_error_insufficient_space_action_view_recent_recordings)) + .build()); } } diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java index e726995f..e5f40260 100644 --- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java +++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java @@ -24,10 +24,8 @@ import android.provider.Settings; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.util.Log; - import com.android.tv.R; import com.android.tv.dvr.ui.browse.DvrDetailsActivity; - import java.util.List; public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment { @@ -44,22 +42,24 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment { @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getResources().getString(R.string.dvr_error_missing_storage_title); - String description = getResources().getString( - R.string.dvr_error_missing_storage_description); + String description = + getResources().getString(R.string.dvr_error_missing_storage_description); return new Guidance(title, description, null, null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_OK) - .title(android.R.string.ok) - .build()); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_OPEN_STORAGE_SETTINGS) - .title(getResources().getString(R.string.dvr_action_error_storage_settings)) - .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_OK) + .title(android.R.string.ok) + .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_OPEN_STORAGE_SETTINGS) + .title(getResources().getString(R.string.dvr_action_error_storage_settings)) + .build()); } @Override diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java index e4cb7243..03124260 100644 --- a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java +++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java @@ -26,23 +26,18 @@ import android.support.v17.leanback.widget.GuidedActionsStylist; import android.view.View; import android.widget.ImageView; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.SeriesRecording; - import java.util.ArrayList; import java.util.List; /** Fragment for DVR series recording settings. */ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { - /** - * Name of series recording id starting the fragment. - * Type: Long - */ + /** Name of series recording id starting the fragment. Type: Long */ public static final String COME_FROM_SERIES_RECORDING_ID = "series_recording_id"; private static final int ONE_TIME_RECORDING_ID = 0; @@ -61,14 +56,14 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { public void onAttach(Context context) { super.onAttach(context); mSeriesRecordings.clear(); - mSeriesRecordings.add(new SeriesRecording.Builder() - .setTitle(getString(R.string.dvr_priority_action_one_time_recording)) - .setPriority(Long.MAX_VALUE) - .setId(ONE_TIME_RECORDING_ID) - .build()); + mSeriesRecordings.add( + new SeriesRecording.Builder() + .setTitle(getString(R.string.dvr_priority_action_one_time_recording)) + .setPriority(Long.MAX_VALUE) + .setId(ONE_TIME_RECORDING_ID) + .build()); DvrDataManager dvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - long comeFromSeriesRecordingId = - getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1); + long comeFromSeriesRecordingId = getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1); for (SeriesRecording series : dvrDataManager.getSeriesRecordings()) { if (series.getState() == SeriesRecording.STATE_SERIES_NORMAL || series.getId() == comeFromSeriesRecordingId) { @@ -86,38 +81,46 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { @Override public void onResume() { super.onResume(); - setSelectedActionPosition(mComeFromSeriesRecording == null ? 1 - : mSeriesRecordings.indexOf(mComeFromSeriesRecording)); + setSelectedActionPosition( + mComeFromSeriesRecording == null + ? 1 + : mSeriesRecordings.indexOf(mComeFromSeriesRecording)); } @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { - String breadcrumb = mComeFromSeriesRecording == null ? null - : mComeFromSeriesRecording.getTitle(); - return new Guidance(getString(R.string.dvr_priority_title), - getString(R.string.dvr_priority_description), breadcrumb, null); + String breadcrumb = + mComeFromSeriesRecording == null ? null : mComeFromSeriesRecording.getTitle(); + return new Guidance( + getString(R.string.dvr_priority_title), + getString(R.string.dvr_priority_description), + breadcrumb, + null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { int position = 0; for (SeriesRecording seriesRecording : mSeriesRecordings) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(position++) - .title(seriesRecording.getTitle()) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(position++) + .title(seriesRecording.getTitle()) + .build()); } } @Override public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SAVE) - .title(getString(R.string.dvr_priority_button_action_save)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SAVE) + .title(getString(R.string.dvr_priority_button_action_save)) + .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); } @Override @@ -130,8 +133,10 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { long priority = DvrScheduleManager.suggestSeriesPriority(size - i); SeriesRecording seriesRecording = mSeriesRecordings.get(i); if (seriesRecording.getPriority() != priority) { - dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(seriesRecording) - .setPriority(priority).build()); + dvrManager.updateSeriesRecording( + SeriesRecording.buildFrom(seriesRecording) + .setPriority(priority) + .build()); } } FragmentManager fragmentManager = getFragmentManager(); @@ -222,8 +227,9 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { private void updateItem(View itemView, int position) { GuidedAction action = getActions().get(position); action.setTitle(mSeriesRecordings.get(position).getTitle()); - boolean selected = mSelectedRecording != null - && mSeriesRecordings.indexOf(mSelectedRecording) == position; + boolean selected = + mSelectedRecording != null + && mSeriesRecordings.indexOf(mSelectedRecording) == position; TextView titleView = (TextView) itemView.findViewById(R.id.guidedactions_item_title); ImageView imageView = (ImageView) itemView.findViewById(R.id.guidedactions_item_tail_image); if (position == 0) { @@ -259,4 +265,4 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java index 390e0928..854fea56 100644 --- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java +++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java @@ -26,7 +26,6 @@ import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.text.format.DateUtils; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -36,21 +35,17 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment; import com.android.tv.util.Utils; - import java.util.Collections; import java.util.List; /** * A fragment which asks the user the type of the recording. - *

- * The program should be episodic and the series recording should not had been created yet. + * + *

The program should be episodic and the series recording should not had been created yet. */ @TargetApi(Build.VERSION_CODES.N) public class DvrScheduleFragment extends DvrGuidedStepFragment { - /** - * Key for the whether to add the current program to series. - * Type: boolean - */ + /** Key for the whether to add the current program to series. Type: boolean */ public static final String KEY_ADD_CURRENT_PROGRAM_TO_SERIES = "add_current_program_to_series"; private static final String TAG = "DvrScheduleFragment"; @@ -69,11 +64,14 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false); } DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); - SoftPreconditions.checkArgument(mProgram != null && mProgram.isEpisodic(), TAG, + SoftPreconditions.checkArgument( + mProgram != null && mProgram.isEpisodic(), + TAG, "The program should be episodic: " + mProgram); SeriesRecording seriesRecording = dvrManager.getSeriesRecording(mProgram); - SoftPreconditions.checkArgument(seriesRecording == null - || seriesRecording.isStopped(), TAG, + SoftPreconditions.checkArgument( + seriesRecording == null || seriesRecording.isStopped(), + TAG, "The series recording should be stopped or null: " + seriesRecording); super.onCreate(savedInstanceState); } @@ -96,23 +94,33 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { Context context = getContext(); String description; if (mProgram.getStartTimeUtcMillis() <= System.currentTimeMillis()) { - description = getString(R.string.dvr_action_record_episode_from_now_description, - DateUtils.formatDateTime(context, mProgram.getEndTimeUtcMillis(), - DateUtils.FORMAT_SHOW_TIME)); + description = + getString( + R.string.dvr_action_record_episode_from_now_description, + DateUtils.formatDateTime( + context, + mProgram.getEndTimeUtcMillis(), + DateUtils.FORMAT_SHOW_TIME)); } else { - description = Utils.getDurationString(context, mProgram.getStartTimeUtcMillis(), - mProgram.getEndTimeUtcMillis(), true); + description = + Utils.getDurationString( + context, + mProgram.getStartTimeUtcMillis(), + mProgram.getEndTimeUtcMillis(), + true); } - actions.add(new GuidedAction.Builder(context) - .id(ACTION_RECORD_EPISODE) - .title(R.string.dvr_action_record_episode) - .description(description) - .build()); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_RECORD_SERIES) - .title(R.string.dvr_action_record_series) - .description(mProgram.getTitle()) - .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_RECORD_EPISODE) + .title(R.string.dvr_action_record_episode) + .description(description) + .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_RECORD_SERIES) + .title(R.string.dvr_action_record_series) + .description(mProgram.getTitle()) + .build()); } @Override @@ -121,34 +129,50 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { getDvrManager().addSchedule(mProgram); List conflicts = getDvrManager().getConflictingSchedules(mProgram); if (conflicts.isEmpty()) { - DvrUiHelper.showAddScheduleToast(getContext(), mProgram.getTitle(), - mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis()); + DvrUiHelper.showAddScheduleToast( + getContext(), + mProgram.getTitle(), + mProgram.getStartTimeUtcMillis(), + mProgram.getEndTimeUtcMillis()); dismissDialog(); } else { GuidedStepFragment fragment = new DvrProgramConflictFragment(); Bundle args = new Bundle(); args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, mProgram); fragment.setArguments(args); - GuidedStepFragment.add(getFragmentManager(), fragment, - R.id.halfsized_dialog_host); + GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host); } } else if (action.getId() == ACTION_RECORD_SERIES) { - SeriesRecording seriesRecording = TvApplication.getSingletons(getContext()) - .getDvrDataManager().getSeriesRecording(mProgram.getSeriesId()); + SeriesRecording seriesRecording = + TvApplication.getSingletons(getContext()) + .getDvrDataManager() + .getSeriesRecording(mProgram.getSeriesId()); if (seriesRecording == null) { - seriesRecording = getDvrManager().addSeriesRecording(mProgram, - Collections.emptyList(), SeriesRecording.STATE_SERIES_STOPPED); + seriesRecording = + getDvrManager() + .addSeriesRecording( + mProgram, + Collections.emptyList(), + SeriesRecording.STATE_SERIES_STOPPED); } else { // Reset priority to the highest. - seriesRecording = SeriesRecording.buildFrom(seriesRecording) - .setPriority(TvApplication.getSingletons(getContext()) - .getDvrScheduleManager().suggestNewSeriesPriority()) - .build(); + seriesRecording = + SeriesRecording.buildFrom(seriesRecording) + .setPriority( + TvApplication.getSingletons(getContext()) + .getDvrScheduleManager() + .suggestNewSeriesPriority()) + .build(); getDvrManager().updateSeriesRecording(seriesRecording); } - DvrUiHelper.startSeriesSettingsActivity(getContext(), - seriesRecording.getId(), null, true, true, true, + DvrUiHelper.startSeriesSettingsActivity( + getContext(), + seriesRecording.getId(), + null, + true, + true, + true, mAddCurrentProgramToSeries ? mProgram : null); dismissDialog(); } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java index 667af34a..8b05cf1c 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java @@ -19,17 +19,12 @@ package com.android.tv.dvr.ui; import android.app.Activity; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; - import com.android.tv.R; import com.android.tv.TvApplication; -/** - * Activity to show details view in DVR. - */ +/** Activity to show details view in DVR. */ public class DvrSeriesDeletionActivity extends Activity { - /** - * Name of series id added to the Intent. - */ + /** Name of series id added to the Intent. */ public static final String SERIES_RECORDING_ID = "series_recording_id"; @Override diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java index 8bf8560f..5f2c3582 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java @@ -26,7 +26,6 @@ import android.support.v17.leanback.widget.GuidedActionsStylist; import android.text.TextUtils; import android.view.ViewGroup.LayoutParams; import android.widget.Toast; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -37,7 +36,6 @@ import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.ui.GuidedActionsStylistWithDivider; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -45,9 +43,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -/** - * Fragment for DVR series recording settings. - */ +/** Fragment for DVR series recording settings. */ public class DvrSeriesDeletionFragment extends GuidedStepFragment { private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2); @@ -68,18 +64,23 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { @Override public void onAttach(Context context) { super.onAttach(context); - mSeriesRecordingId = getArguments() - .getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); + mSeriesRecordingId = + getArguments().getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); SoftPreconditions.checkArgument(mSeriesRecordingId != -1); mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); mDvrWatchedPositionManager = TvApplication.getSingletons(context).getDvrWatchedPositionManager(); mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); - mOneLineActionHeight = getResources().getDimensionPixelSize( - R.dimen.dvr_settings_one_line_action_container_height); + mOneLineActionHeight = + getResources() + .getDimensionPixelSize( + R.dimen.dvr_settings_one_line_action_container_height); if (mRecordings.isEmpty()) { - Toast.makeText(getActivity(), getString(R.string.dvr_series_deletion_no_recordings), - Toast.LENGTH_LONG).show(); + Toast.makeText( + getActivity(), + getString(R.string.dvr_series_deletion_no_recordings), + Toast.LENGTH_LONG) + .show(); finishGuidedStepFragments(); return; } @@ -93,28 +94,35 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { if (series != null) { breadcrumb = series.getTitle(); } - return new Guidance(getString(R.string.dvr_series_deletion_title), - getString(R.string.dvr_series_deletion_description), breadcrumb, null); + return new Guidance( + getString(R.string.dvr_series_deletion_title), + getString(R.string.dvr_series_deletion_description), + breadcrumb, + null); } @Override public void onCreateActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SELECT_WATCHED) - .title(getString(R.string.dvr_series_select_watched)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_SELECT_ALL) - .title(getString(R.string.dvr_series_select_all)) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SELECT_WATCHED) + .title(getString(R.string.dvr_series_select_watched)) + .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_SELECT_ALL) + .title(getString(R.string.dvr_series_select_all)) + .build()); actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext())); for (RecordedProgram recording : mRecordings) { long watchedPositionMs = mDvrWatchedPositionManager.getWatchedPosition(recording.getId()); String title = recording.getEpisodeDisplayTitle(getContext()); if (TextUtils.isEmpty(title)) { - title = TextUtils.isEmpty(recording.getTitle()) ? - getString(R.string.channel_banner_no_title) : recording.getTitle(); + title = + TextUtils.isEmpty(recording.getTitle()) + ? getString(R.string.channel_banner_no_title) + : recording.getTitle(); } String description; if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { @@ -123,24 +131,27 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { } else { description = getString(R.string.dvr_series_never_watched); } - actions.add(new GuidedAction.Builder(getActivity()) - .id(recording.getId()) - .title(title) - .description(description) - .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(recording.getId()) + .title(title) + .description(description) + .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID) + .build()); } } @Override public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_DELETE) - .title(getString(R.string.dvr_detail_delete)) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_DELETE) + .title(getString(R.string.dvr_detail_delete)) + .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); } @Override @@ -158,9 +169,16 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); dvrManager.removeRecordedPrograms(idsToDelete); } - Toast.makeText(getContext(), getResources().getQuantityString( - R.plurals.dvr_msg_episodes_deleted, idsToDelete.size(), idsToDelete.size(), - mRecordings.size()), Toast.LENGTH_LONG).show(); + Toast.makeText( + getContext(), + getResources() + .getQuantityString( + R.plurals.dvr_msg_episodes_deleted, + idsToDelete.size(), + idsToDelete.size(), + mRecordings.size()), + Toast.LENGTH_LONG) + .show(); finishGuidedStepFragments(); } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { finishGuidedStepFragments(); @@ -218,13 +236,17 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { private String getWatchedString(long watchedPositionMs, long durationMs) { if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) { - return getResources().getString(R.string.dvr_series_watched_info_minutes, - Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)), - Utils.getRoundOffMinsFromMs(durationMs)); + return getResources() + .getString( + R.string.dvr_series_watched_info_minutes, + Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)), + Utils.getRoundOffMinsFromMs(durationMs)); } else { - return getResources().getString(R.string.dvr_series_watched_info_seconds, - Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), - TimeUnit.MILLISECONDS.toSeconds(durationMs)); + return getResources() + .getString( + R.string.dvr_series_watched_info_seconds, + Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)), + TimeUnit.MILLISECONDS.toSeconds(durationMs)); } } @@ -246,8 +268,10 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { } private void updateSelectAllState(GuidedAction selectAll, boolean select) { - selectAll.setTitle(select ? getString(R.string.dvr_series_deselect_all) - : getString(R.string.dvr_series_select_all)); + selectAll.setTitle( + select + ? getString(R.string.dvr_series_deselect_all) + : getString(R.string.dvr_series_select_all)); notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL)); } } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java index 1a0d13d3..9acb5b5e 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java @@ -19,18 +19,13 @@ package com.android.tv.dvr.ui; import android.app.Activity; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; - import com.android.tv.R; public class DvrSeriesScheduledDialogActivity extends Activity { - /** - * Name of series recording id added to the Intent. - */ + /** Name of series recording id added to the Intent. */ public static final String SERIES_RECORDING_ID = "series_recording_id"; - /** - * Name of flag to check if the dialog should show view schedule option. - */ + /** Name of flag to check if the dialog should show view schedule option. */ public static final String SHOW_VIEW_SCHEDULE_OPTION = "show_view_schedule_option"; @Override @@ -41,8 +36,8 @@ public class DvrSeriesScheduledDialogActivity extends Activity { DvrSeriesScheduledFragment dvrSeriesScheduledFragment = new DvrSeriesScheduledFragment(); dvrSeriesScheduledFragment.setArguments(getIntent().getExtras()); - GuidedStepFragment.addAsRoot(this, dvrSeriesScheduledFragment, - R.id.halfsized_dialog_host); + GuidedStepFragment.addAsRoot( + this, dvrSeriesScheduledFragment, R.id.halfsized_dialog_host); } } } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java index 2c4bb3ea..d600b54d 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java @@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Program; @@ -31,19 +30,18 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.list.DvrSchedulesActivity; import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; - import java.util.List; public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { /** - * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}. - * Type: List<{@link Program}> + * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}. Type: + * List<{@link Program}> */ public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs"; - private final static long SERIES_RECORDING_ID_NOT_SET = -1; + private static final long SERIES_RECORDING_ID_NOT_SET = -1; - private final static int ACTION_VIEW_SCHEDULES = 1; + private static final int ACTION_VIEW_SCHEDULES = 1; private SeriesRecording mSeriesRecording; private boolean mShowViewScheduleOption; @@ -57,24 +55,33 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { @Override public void onAttach(Context context) { super.onAttach(context); - long seriesRecordingId = getArguments().getLong( - DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, SERIES_RECORDING_ID_NOT_SET); + long seriesRecordingId = + getArguments() + .getLong( + DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, + SERIES_RECORDING_ID_NOT_SET); if (seriesRecordingId == SERIES_RECORDING_ID_NOT_SET) { getActivity().finish(); return; } - mShowViewScheduleOption = getArguments().getBoolean( - DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION); - mSeriesRecording = TvApplication.getSingletons(context).getDvrDataManager() - .getSeriesRecording(seriesRecordingId); + mShowViewScheduleOption = + getArguments() + .getBoolean(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION); + mSeriesRecording = + TvApplication.getSingletons(context) + .getDvrDataManager() + .getSeriesRecording(seriesRecordingId); if (mSeriesRecording == null) { getActivity().finish(); return; } mPrograms = (List) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS); BigArguments.reset(); - mSchedulesAddedCount = TvApplication.getSingletons(getContext()).getDvrManager() - .getAvailableScheduledRecording(mSeriesRecording.getId()).size(); + mSchedulesAddedCount = + TvApplication.getSingletons(getContext()) + .getDvrManager() + .getAvailableScheduledRecording(mSeriesRecording.getId()) + .size(); DvrScheduleManager dvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager(); List conflictingRecordings = @@ -87,7 +94,7 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { ++mOutThisSeriesConflictCount; } } - } + } @Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { @@ -104,14 +111,14 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(List actions, Bundle savedInstanceState) { Context context = getContext(); - actions.add(new GuidedAction.Builder(context) - .clickAction(GuidedAction.ACTION_ID_OK) - .build()); + actions.add( + new GuidedAction.Builder(context).clickAction(GuidedAction.ACTION_ID_OK).build()); if (mShowViewScheduleOption) { - actions.add(new GuidedAction.Builder(context) - .id(ACTION_VIEW_SCHEDULES) - .title(R.string.dvr_action_view_schedules) - .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_VIEW_SCHEDULES) + .title(R.string.dvr_action_view_schedules) + .build()); } } @@ -119,13 +126,15 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_VIEW_SCHEDULES) { Intent intent = new Intent(getActivity(), DvrSchedulesActivity.class); - intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity - .TYPE_SERIES_SCHEDULE); - intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, + intent.putExtra( + DvrSchedulesActivity.KEY_SCHEDULES_TYPE, + DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); + intent.putExtra( + DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, mSeriesRecording); BigArguments.reset(); - BigArguments.setArgument(DvrSeriesSchedulesFragment - .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms); + BigArguments.setArgument( + DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms); startActivity(intent); } getActivity().finish(); @@ -148,33 +157,47 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { private String getDescription() { if (!mHasConflict) { - return getResources().getQuantityString( - R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount, - mSchedulesAddedCount, mSeriesRecording.getTitle()); + return getResources() + .getQuantityString( + R.plurals.dvr_series_scheduled_no_conflict, + mSchedulesAddedCount, + mSchedulesAddedCount, + mSeriesRecording.getTitle()); } else { // mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means // mHasConflict is false. So we don't need to check that case. if (mInThisSeriesConflictCount != 0 && mOutThisSeriesConflictCount != 0) { - return getResources().getQuantityString( - R.plurals.dvr_series_scheduled_this_and_other_series_conflict, - mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), - mInThisSeriesConflictCount + mOutThisSeriesConflictCount); + return getResources() + .getQuantityString( + R.plurals.dvr_series_scheduled_this_and_other_series_conflict, + mSchedulesAddedCount, + mSchedulesAddedCount, + mSeriesRecording.getTitle(), + mInThisSeriesConflictCount + mOutThisSeriesConflictCount); } else if (mInThisSeriesConflictCount != 0) { - return getResources().getQuantityString( - R.plurals.dvr_series_recording_scheduled_only_this_series_conflict, - mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), - mInThisSeriesConflictCount); + return getResources() + .getQuantityString( + R.plurals.dvr_series_recording_scheduled_only_this_series_conflict, + mSchedulesAddedCount, + mSchedulesAddedCount, + mSeriesRecording.getTitle(), + mInThisSeriesConflictCount); } else { if (mOutThisSeriesConflictCount == 1) { - return getResources().getQuantityString( - R.plurals.dvr_series_scheduled_only_other_series_one_conflict, - mSchedulesAddedCount, mSchedulesAddedCount, - mSeriesRecording.getTitle()); + return getResources() + .getQuantityString( + R.plurals.dvr_series_scheduled_only_other_series_one_conflict, + mSchedulesAddedCount, + mSchedulesAddedCount, + mSeriesRecording.getTitle()); } else { - return getResources().getQuantityString( - R.plurals.dvr_series_scheduled_only_other_series_many_conflicts, - mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(), - mOutThisSeriesConflictCount); + return getResources() + .getQuantityString( + R.plurals.dvr_series_scheduled_only_other_series_many_conflicts, + mSchedulesAddedCount, + mSchedulesAddedCount, + mSeriesRecording.getTitle(), + mOutThisSeriesConflictCount); } } } diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java index 6dd20b3a..117f72d8 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java @@ -20,34 +20,27 @@ import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; -/** - * Activity to show details view in DVR. - */ +/** Activity to show details view in DVR. */ public class DvrSeriesSettingsActivity extends Activity { - /** - * Name of series id added to the Intent. - * Type: Long - */ + /** Name of series id added to the Intent. Type: Long */ public static final String SERIES_RECORDING_ID = "series_recording_id"; /** * Name of the boolean flag to decide if the series recording with empty schedule and recording - * will be removed. - * Type: boolean + * will be removed. Type: boolean */ public static final String REMOVE_EMPTY_SERIES_RECORDING = "remove_empty_series_recording"; /** - * Name of the boolean flag to decide if the setting fragment should be translucent. - * Type: boolean + * Name of the boolean flag to decide if the setting fragment should be translucent. Type: + * boolean */ public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent"; /** - * Name of the program list. The list contains the programs which belong to the series. - * Type: List<{@link com.android.tv.data.Program}> + * Name of the program list. The list contains the programs which belong to the series. Type: + * List<{@link com.android.tv.data.Program}> */ public static final String PROGRAM_LIST = "program_list"; @@ -83,8 +76,9 @@ public class DvrSeriesSettingsActivity extends Activity { @Override public void onAttachedToWindow() { if (!getIntent().getExtras().getBoolean(IS_WINDOW_TRANSLUCENT, true)) { - getWindow().setBackgroundDrawable( - new ColorDrawable(getColor(R.color.common_tv_background))); + getWindow() + .setBackgroundDrawable( + new ColorDrawable(getColor(R.color.common_tv_background))); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java index f28382da..c44e44a3 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java @@ -24,7 +24,6 @@ import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.GuidedActionsStylist; import android.util.LongSparseArray; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Channel; @@ -37,16 +36,13 @@ import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.data.SeriesRecording.ChannelOption; import com.android.tv.dvr.recorder.SeriesRecordingScheduler; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -/** - * Fragment for DVR series recording settings. - */ +/** Fragment for DVR series recording settings. */ public class DvrSeriesSettingsFragment extends GuidedStepFragment implements DvrDataManager.SeriesRecordingListener { private static final String TAG = "SeriesSettingsFragment"; @@ -92,12 +88,13 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment getActivity().finish(); return; } - mShowViewScheduleOptionInDialog = getArguments().getBoolean( - DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); + mShowViewScheduleOptionInDialog = + getArguments() + .getBoolean(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM); mDvrDataManager.addSeriesRecordingListener(this); - mPrograms = (List) BigArguments.getArgument( - DvrSeriesSettingsActivity.PROGRAM_LIST); + mPrograms = + (List) BigArguments.getArgument(DvrSeriesSettingsActivity.PROGRAM_LIST); BigArguments.reset(); if (mPrograms == null) { getActivity().finish(); @@ -150,8 +147,9 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment @Override public void onDestroy() { - if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments() - .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) { + if (getFragmentManager().getBackStackEntryCount() == mBackStackCount + && getArguments() + .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) { mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId); } super.onDestroy(); @@ -166,29 +164,33 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment @Override public void onCreateActions(List actions, Bundle savedInstanceState) { - mPriorityGuidedAction = new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_PRIORITY) - .title(mProrityActionTitle) - .build(); + mPriorityGuidedAction = + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_PRIORITY) + .title(mProrityActionTitle) + .build(); actions.add(mPriorityGuidedAction); - mChannelsGuidedAction = new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_CHANNEL) - .title(mChannelsActionTitle) - .subActions(buildChannelSubAction()) - .build(); + mChannelsGuidedAction = + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_CHANNEL) + .title(mChannelsActionTitle) + .subActions(buildChannelSubAction()) + .build(); actions.add(mChannelsGuidedAction); updateChannelsGuidedAction(false); } @Override public void onCreateButtonActions(List actions, Bundle savedInstanceState) { - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_OK) - .build()); - actions.add(new GuidedAction.Builder(getActivity()) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_OK) + .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); } @Override @@ -199,16 +201,18 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment || mSeriesRecording.isStopped() || (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE && mSeriesRecording.getChannelId() != mSelectedChannelId)) { - SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording) - .setChannelOption(mChannelOption) - .setState(SeriesRecording.STATE_SERIES_NORMAL); + SeriesRecording.Builder builder = + SeriesRecording.buildFrom(mSeriesRecording) + .setChannelOption(mChannelOption) + .setState(SeriesRecording.STATE_SERIES_NORMAL); if (mSelectedChannelId != Channel.INVALID_ID) { builder.setChannelId(mSelectedChannelId); } DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); - dvrManager.updateSeriesRecording(builder.build()); - if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL - || mSelectedChannelId == mCurrentProgram.getChannelId())) { + dvrManager.updateSeriesRecording(builder.build()); + if (mCurrentProgram != null + && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL + || mSelectedChannelId == mCurrentProgram.getChannelId())) { dvrManager.addSchedule(mCurrentProgram); } updateSchedulesToSeries(); @@ -222,7 +226,8 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment FragmentManager fragmentManager = getFragmentManager(); DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment(); Bundle args = new Bundle(); - args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, + args.putLong( + DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, mSeriesRecording.getId()); fragment.setArguments(args); GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame); @@ -254,9 +259,9 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment private void updateChannelsGuidedAction(boolean notifyActionChanged) { if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) { mChannelsGuidedAction.setDescription(mChannelsActionAllText); - } else if (mId2Channel.get(mSelectedChannelId) != null){ - mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId) - .getDisplayText()); + } else if (mId2Channel.get(mSelectedChannelId) != null) { + mChannelsGuidedAction.setDescription( + mId2Channel.get(mSelectedChannelId).getDisplayText()); } if (notifyActionChanged) { notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); @@ -282,8 +287,8 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment } else if (priorityOrder >= totalSeriesCount - 1) { mPriorityGuidedAction.setDescription(mProrityActionLowestText); } else { - mPriorityGuidedAction.setDescription(getString( - R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); + mPriorityGuidedAction.setDescription( + getString(R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); } notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY)); } @@ -294,54 +299,66 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) { if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) { - scheduledEpisodes.add(new SeasonEpisodeNumber( - r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber())); + scheduledEpisodes.add( + new SeasonEpisodeNumber( + r.getSeriesRecordingId(), + r.getSeasonNumber(), + r.getEpisodeNumber())); } } for (Program program : mPrograms) { // Removes current programs and scheduled episodes out, matches the channel option. if (program.getStartTimeUtcMillis() >= System.currentTimeMillis() && mSeriesRecording.matchProgram(program) - && !scheduledEpisodes.contains(new SeasonEpisodeNumber( - mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) { + && !scheduledEpisodes.contains( + new SeasonEpisodeNumber( + mSeriesRecordingId, + program.getSeasonNumber(), + program.getEpisodeNumber()))) { recordingCandidates.add(program); } } if (recordingCandidates.isEmpty()) { return; } - List programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates) - .get(mSeriesRecordingId); + List programsToSchedule = + SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDvrDataManager, + Collections.singletonList(mSeriesRecording), + recordingCandidates) + .get(mSeriesRecordingId); if (!programsToSchedule.isEmpty()) { - TvApplication.getSingletons(getContext()).getDvrManager() + TvApplication.getSingletons(getContext()) + .getDvrManager() .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule); } } private List buildChannelSubAction() { List channelSubActions = new ArrayList<>(); - channelSubActions.add(new GuidedAction.Builder(getActivity()) - .id(SUB_ACTION_ID_CHANNEL_ALL) - .title(mChannelsActionAllText) - .build()); + channelSubActions.add( + new GuidedAction.Builder(getActivity()) + .id(SUB_ACTION_ID_CHANNEL_ALL) + .title(mChannelsActionAllText) + .build()); for (Channel channel : mChannels) { - channelSubActions.add(new GuidedAction.Builder(getActivity()) - .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) - .title(channel.getDisplayText()) - .build()); + channelSubActions.add( + new GuidedAction.Builder(getActivity()) + .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) + .title(channel.getDisplayText()) + .build()); } return channelSubActions; } private void showConfirmDialog() { - DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording, - mShowViewScheduleOptionInDialog, mPrograms); + DvrUiHelper.StartSeriesScheduledDialogActivity( + getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog, mPrograms); finishGuidedStepFragments(); } @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {} @Override public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { @@ -363,4 +380,4 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java index baa45793..6f34e8a0 100644 --- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java @@ -25,45 +25,36 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.data.ScheduledRecording; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * A fragment which asks the user to make a recording schedule for the program. - *

- * If the program belongs to a series and the series recording is not created yet, we will show the - * option to record all the episodes of the series. + * + *

If the program belongs to a series and the series recording is not created yet, we will show + * the option to record all the episodes of the series. */ @TargetApi(Build.VERSION_CODES.N) public class DvrStopRecordingFragment extends DvrGuidedStepFragment { - /** - * The action ID for the stop action. - */ + /** The action ID for the stop action. */ public static final int ACTION_STOP = 1; - /** - * Key for the program. - * Type: {@link com.android.tv.data.Program}. - */ + /** Key for the program. Type: {@link com.android.tv.data.Program}. */ public static final String KEY_REASON = "DvrStopRecordingFragment.type"; @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_USER_STOP, REASON_ON_CONFLICT}) public @interface ReasonType {} - /** - * The dialog is shown because users want to stop some currently recording program. - */ + /** The dialog is shown because users want to stop some currently recording program. */ public static final int REASON_USER_STOP = 1; /** - * The dialog is shown because users want to record some program that is conflict to the - * current recording program. + * The dialog is shown because users want to record some program that is conflict to the current + * recording program. */ public static final int REASON_ON_CONFLICT = 2; @@ -74,7 +65,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { private final ScheduledRecordingListener mScheduledRecordingListener = new ScheduledRecordingListener() { @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { } + public void onScheduledRecordingAdded(ScheduledRecording... schedules) {} @Override public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { @@ -91,7 +82,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { for (ScheduledRecording schedule : schedules) { if (schedule.getId() == mSchedule.getId() && schedule.getState() - != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { + != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { dismissDialog(); return; } @@ -128,8 +119,10 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { String title = getString(R.string.dvr_stop_recording_dialog_title); String description; if (mStopReason == REASON_ON_CONFLICT) { - description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict, - mSchedule.getProgramDisplayTitle(getContext())); + description = + getString( + R.string.dvr_stop_recording_dialog_description_on_conflict, + mSchedule.getProgramDisplayTitle(getContext())); } else { description = getString(R.string.dvr_stop_recording_dialog_description); } @@ -140,13 +133,15 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { Context context = getContext(); - actions.add(new GuidedAction.Builder(context) - .id(ACTION_STOP) - .title(R.string.dvr_action_stop) - .build()); - actions.add(new GuidedAction.Builder(context) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); + actions.add( + new GuidedAction.Builder(context) + .id(ACTION_STOP) + .title(R.string.dvr_action_stop) + .build()); + actions.add( + new GuidedAction.Builder(context) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); } @Override @@ -163,4 +158,4 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { return super.getTrackerLabelForGuidedAction(action); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java index 5b880bd6..15abf902 100644 --- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java @@ -22,18 +22,15 @@ import android.support.v17.leanback.app.GuidedStepFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; -/** - * A dialog fragment which contains {@link DvrStopSeriesRecordingFragment}. - */ +/** A dialog fragment which contains {@link DvrStopSeriesRecordingFragment}. */ public class DvrStopSeriesRecordingDialogFragment extends DialogFragment { public static final String DIALOG_TAG = "dialog_tag"; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.halfsized_dialog, container, false); GuidedStepFragment fragment = new DvrStopSeriesRecordingFragment(); fragment.setArguments(getArguments()); diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java index 7b56cfc1..3d84f48f 100644 --- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java @@ -25,7 +25,6 @@ import android.support.v17.leanback.widget.GuidedAction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -33,17 +32,12 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; - import java.util.ArrayList; import java.util.List; -/** - * A fragment which asks the user to stop series recording. - */ +/** A fragment which asks the user to stop series recording. */ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { - /** - * Key for the series recording to be stopped. - */ + /** Key for the series recording to be stopped. */ public static final String KEY_SERIES_RECORDING = "key_series_recoridng"; private static final int ACTION_STOP_SERIES_RECORDING = 1; @@ -51,7 +45,8 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { private SeriesRecording mSeriesRecording; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mSeriesRecording = getArguments().getParcelable(KEY_SERIES_RECORDING); return super.onCreateView(inflater, container, savedInstanceState); } @@ -68,13 +63,15 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { Activity activity = getActivity(); - actions.add(new GuidedAction.Builder(activity) - .id(ACTION_STOP_SERIES_RECORDING) - .title(R.string.dvr_series_schedules_stop_dialog_action_stop) - .build()); - actions.add(new GuidedAction.Builder(activity) - .clickAction(GuidedAction.ACTION_ID_CANCEL) - .build()); + actions.add( + new GuidedAction.Builder(activity) + .id(ACTION_STOP_SERIES_RECORDING) + .title(R.string.dvr_series_schedules_stop_dialog_action_stop) + .build()); + actions.add( + new GuidedAction.Builder(activity) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build()); } @Override @@ -96,8 +93,10 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { if (!toDelete.isEmpty()) { dvrManager.forceRemoveScheduledRecording(ScheduledRecording.toArray(toDelete)); } - dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording) - .setState(SeriesRecording.STATE_SERIES_STOPPED).build()); + dvrManager.updateSeriesRecording( + SeriesRecording.buildFrom(mSeriesRecording) + .setState(SeriesRecording.STATE_SERIES_STOPPED) + .build()); } dismissDialog(); } diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java index 302fd6cd..ae60f4a4 100644 --- a/src/com/android/tv/dvr/ui/DvrUiHelper.java +++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java @@ -37,7 +37,6 @@ import android.text.TextUtils; import android.text.style.TextAppearanceSpan; import android.widget.ImageView; import android.widget.Toast; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; @@ -71,15 +70,12 @@ import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; import com.android.tv.dvr.ui.playback.DvrPlaybackActivity; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; -/** - * A helper class for DVR UI. - */ +/** A helper class for DVR UI. */ @MainThread @TargetApi(Build.VERSION_CODES.N) public class DvrUiHelper { @@ -91,12 +87,13 @@ public class DvrUiHelper { * Checks if the storage status is good for recording and shows error messages if needed. * * @param recordingRequestRunnable if the storage status is OK to record or users choose to - * perform the operation anyway, this Runnable will run. + * perform the operation anyway, this Runnable will run. */ - public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId, - Runnable recordingRequestRunnable) { + public static void checkStorageStatusAndShowErrorMessage( + Activity activity, String inputId, Runnable recordingRequestRunnable) { if (Utils.isBundledInput(inputId)) { - switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager() + switch (TvApplication.getSingletons(activity) + .getDvrStorageStatusManager() .getDvrStorageStatus()) { case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL: showDvrSmallSizedStorageErrorDialog(activity); @@ -112,24 +109,20 @@ public class DvrUiHelper { recordingRequestRunnable.run(); } - /** - * Shows the schedule dialog. - */ - public static void showScheduleDialog(Activity activity, Program program, - boolean addCurrentProgramToSeries) { + /** Shows the schedule dialog. */ + public static void showScheduleDialog( + Activity activity, Program program, boolean addCurrentProgramToSeries) { if (SoftPreconditions.checkNotNull(program) == null) { return; } Bundle args = new Bundle(); args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); - args.putBoolean(DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, - addCurrentProgramToSeries); + args.putBoolean( + DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, addCurrentProgramToSeries); showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true); } - /** - * Shows the recording duration options dialog. - */ + /** Shows the recording duration options dialog. */ public static void showChannelRecordDurationOptions(Activity activity, Channel channel) { if (SoftPreconditions.checkNotNull(channel) == null) { return; @@ -139,9 +132,7 @@ public class DvrUiHelper { showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args); } - /** - * Shows the dialog which says that the new schedule conflicts with others. - */ + /** Shows the dialog which says that the new schedule conflicts with others. */ public static void showScheduleConflictDialog(Activity activity, Program program) { if (program == null) { return; @@ -151,9 +142,7 @@ public class DvrUiHelper { showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true); } - /** - * Shows the conflict dialog for the channel watching. - */ + /** Shows the conflict dialog for the channel watching. */ public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) { if (channel == null) { return; @@ -163,63 +152,60 @@ public class DvrUiHelper { showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args); } - /** - * Shows DVR insufficient space error dialog. - */ - public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity, - Set failedScheduledRecordingInfoSet) { + /** Shows DVR insufficient space error dialog. */ + public static void showDvrInsufficientSpaceErrorDialog( + MainActivity activity, Set failedScheduledRecordingInfoSet) { Bundle args = new Bundle(); ArrayList failedScheduledRecordingInfoArray = new ArrayList<>(failedScheduledRecordingInfoSet); - args.putStringArrayList(DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS, + args.putStringArrayList( + DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS, failedScheduledRecordingInfoArray); showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args); - Utils.clearRecordingFailedReason(activity, - TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + Utils.clearRecordingFailedReason( + activity, TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); Utils.clearFailedScheduledRecordingInfoSet(activity); } /** * Shows DVR no free space error dialog. * - * @param recordingRequestRunnable the recording request to be executed when users choose - * {@link DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}. + * @param recordingRequestRunnable the recording request to be executed when users choose {@link + * DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}. */ - public static void showDvrNoFreeSpaceErrorDialog(Activity activity, - Runnable recordingRequestRunnable) { + public static void showDvrNoFreeSpaceErrorDialog( + Activity activity, Runnable recordingRequestRunnable) { DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment(); - fragment.setOnActionClickListener(new HalfSizedDialogFragment.OnActionClickListener() { - @Override - public void onActionClick(long actionId) { - if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) { - recordingRequestRunnable.run(); - } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) { - Intent intent = new Intent(activity, DvrBrowseActivity.class); - activity.startActivity(intent); - } - } - }); + fragment.setOnActionClickListener( + new HalfSizedDialogFragment.OnActionClickListener() { + @Override + public void onActionClick(long actionId) { + if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) { + recordingRequestRunnable.run(); + } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) { + Intent intent = new Intent(activity, DvrBrowseActivity.class); + activity.startActivity(intent); + } + } + }); showDialogFragment(activity, fragment, null); } - /** - * Shows DVR missing storage error dialog. - */ + /** Shows DVR missing storage error dialog. */ private static void showDvrMissingStorageErrorDialog(Activity activity) { showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null); } - /** - * Shows DVR small sized storage error dialog. - */ + /** Shows DVR small sized storage error dialog. */ public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); } - /** - * Shows stop recording dialog. - */ - public static void showStopRecordingDialog(Activity activity, long channelId, int reason, + /** Shows stop recording dialog. */ + public static void showStopRecordingDialog( + Activity activity, + long channelId, + int reason, HalfSizedDialogFragment.OnActionClickListener listener) { Bundle args = new Bundle(); args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId); @@ -229,9 +215,7 @@ public class DvrUiHelper { showDialogFragment(activity, fragment, args); } - /** - * Shows "already scheduled" dialog. - */ + /** Shows "already scheduled" dialog. */ public static void showAlreadyScheduleDialog(Activity activity, Program program) { if (program == null) { return; @@ -241,9 +225,7 @@ public class DvrUiHelper { showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true); } - /** - * Shows "already recorded" dialog. - */ + /** Shows "already recorded" dialog. */ public static void showAlreadyRecordedDialog(Activity activity, Program program) { if (program == null) { return; @@ -258,32 +240,34 @@ public class DvrUiHelper { * shows the proper dialog and toast message respectively for timed-recording and program * recording cases. * - * @param addProgramToSeries denotes whether the program to be recorded should be added into - * the series recording when users choose to record the entire series. + * @param addProgramToSeries denotes whether the program to be recorded should be added into the + * series recording when users choose to record the entire series. */ - public static void requestRecordingCurrentProgram(Activity activity, - Channel channel, Program program, boolean addProgramToSeries) { + public static void requestRecordingCurrentProgram( + Activity activity, Channel channel, Program program, boolean addProgramToSeries) { if (program == null) { DvrUiHelper.showChannelRecordDurationOptions(activity, channel); } else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { - String msg = activity.getString(R.string.dvr_msg_current_program_scheduled, - program.getTitle(), Utils.toTimeString(program.getEndTimeUtcMillis(), false)); + String msg = + activity.getString( + R.string.dvr_msg_current_program_scheduled, + program.getTitle(), + Utils.toTimeString(program.getEndTimeUtcMillis(), false)); Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); } } /** - * Handle the request of recording a future program. It will handle creating schedules and - * shows the proper toast message. + * Handle the request of recording a future program. It will handle creating schedules and shows + * the proper toast message. * - * @param addProgramToSeries denotes whether the program to be recorded should be added into - * the series recording when users choose to record the entire series. + * @param addProgramToSeries denotes whether the program to be recorded should be added into the + * series recording when users choose to record the entire series. */ - public static void requestRecordingFutureProgram(Activity activity, - Program program, boolean addProgramToSeries) { + public static void requestRecordingFutureProgram( + Activity activity, Program program, boolean addProgramToSeries) { if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { - String msg = activity.getString( - R.string.dvr_msg_program_scheduled, program.getTitle()); + String msg = activity.getString(R.string.dvr_msg_program_scheduled, program.getTitle()); ToastUtils.show(activity, msg, Toast.LENGTH_SHORT); } } @@ -292,8 +276,8 @@ public class DvrUiHelper { * Handles the action to create the new schedule. It returns {@code true} if the schedule is * added and there's no additional UI, otherwise {@code false}. */ - private static boolean handleCreateSchedule(Activity activity, Program program, - boolean addProgramToSeries) { + private static boolean handleCreateSchedule( + Activity activity, Program program, boolean addProgramToSeries) { if (program == null) { return false; } @@ -307,18 +291,24 @@ public class DvrUiHelper { } } else { // Show recorded program rather than the schedule. - RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(), - program.getSeasonNumber(), program.getEpisodeNumber()); + RecordedProgram recordedProgram = + dvrManager.getRecordedProgram( + program.getTitle(), + program.getSeasonNumber(), + program.getEpisodeNumber()); if (recordedProgram != null) { DvrUiHelper.showAlreadyRecordedDialog(activity, program); return false; } - ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(), - program.getSeasonNumber(), program.getEpisodeNumber()); + ScheduledRecording duplicate = + dvrManager.getScheduledRecording( + program.getTitle(), + program.getSeasonNumber(), + program.getEpisodeNumber()); if (duplicate != null && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || duplicate.getState() - == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { + || duplicate.getState() + == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { DvrUiHelper.showAlreadyScheduleDialog(activity, program); return false; } @@ -334,39 +324,44 @@ public class DvrUiHelper { return true; } - private static void showDialogFragment(Activity activity, - DvrHalfSizedDialogFragment dialogFragment, Bundle args) { + private static void showDialogFragment( + Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args) { showDialogFragment(activity, dialogFragment, args, false, false); } - private static void showDialogFragment(Activity activity, - DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, + private static void showDialogFragment( + Activity activity, + DvrHalfSizedDialogFragment dialogFragment, + Bundle args, + boolean keepSidePanelHistory, boolean keepProgramGuide) { dialogFragment.setArguments(args); if (activity instanceof MainActivity) { - ((MainActivity) activity).getOverlayManager() - .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment, - keepSidePanelHistory, keepProgramGuide); + ((MainActivity) activity) + .getOverlayManager() + .showDialogFragment( + DvrHalfSizedDialogFragment.DIALOG_TAG, + dialogFragment, + keepSidePanelHistory, + keepProgramGuide); } else { - dialogFragment.show(activity.getFragmentManager(), - DvrHalfSizedDialogFragment.DIALOG_TAG); + dialogFragment.show( + activity.getFragmentManager(), DvrHalfSizedDialogFragment.DIALOG_TAG); } } - /** - * Checks whether channel watch conflict dialog is open or not. - */ + /** Checks whether channel watch conflict dialog is open or not. */ public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { - return activity.getOverlayManager().getCurrentDialog() instanceof - DvrChannelWatchConflictDialogFragment; + return activity.getOverlayManager().getCurrentDialog() + instanceof DvrChannelWatchConflictDialogFragment; } - private static ScheduledRecording getEarliestScheduledRecording(List - recordings) { + private static ScheduledRecording getEarliestScheduledRecording( + List recordings) { ScheduledRecording earlistScheduledRecording = null; if (!recordings.isEmpty()) { - Collections.sort(recordings, - ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); + Collections.sort( + recordings, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); earlistScheduledRecording = recordings.get(0); } return earlistScheduledRecording; @@ -378,10 +373,10 @@ public class DvrUiHelper { * @param programId the ID of the recorded program going to be played. * @param seekTimeMs the seek position to initial playback. * @param pinChecked {@code true} if the pin code for parental controls has already been - * verified, otherwise {@code false}. + * verified, otherwise {@code false}. */ - public static void startPlaybackActivity(Context context, long programId, - long seekTimeMs, boolean pinChecked) { + public static void startPlaybackActivity( + Context context, long programId, long seekTimeMs, boolean pinChecked) { Intent intent = new Intent(context, DvrPlaybackActivity.class); intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { @@ -391,51 +386,46 @@ public class DvrUiHelper { context.startActivity(intent); } - /** - * Shows the schedules activity to resolve the tune conflict. - */ + /** Shows the schedules activity to resolve the tune conflict. */ public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) { if (channel == null) { return; } - List conflicts = TvApplication.getSingletons(context).getDvrManager() - .getConflictingSchedulesForTune(channel.getId()); + List conflicts = + TvApplication.getSingletons(context) + .getDvrManager() + .getConflictingSchedulesForTune(channel.getId()); startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); } - /** - * Shows the schedules activity to resolve the one time recording conflict. - */ - public static void startSchedulesActivityForOneTimeRecordingConflict(Context context, - List conflicts) { + /** Shows the schedules activity to resolve the one time recording conflict. */ + public static void startSchedulesActivityForOneTimeRecordingConflict( + Context context, List conflicts) { startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); } - /** - * Shows the schedules activity with full schedule. - */ - public static void startSchedulesActivity(Context context, ScheduledRecording - focusedScheduledRecording) { + /** Shows the schedules activity with full schedule. */ + public static void startSchedulesActivity( + Context context, ScheduledRecording focusedScheduledRecording) { Intent intent = new Intent(context, DvrSchedulesActivity.class); - intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, - DvrSchedulesActivity.TYPE_FULL_SCHEDULE); + intent.putExtra( + DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_FULL_SCHEDULE); if (focusedScheduledRecording != null) { - intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, + intent.putExtra( + DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, focusedScheduledRecording); } context.startActivity(intent); } - /** - * Shows the schedules activity for series recording. - */ - public static void startSchedulesActivityForSeries(Context context, - SeriesRecording seriesRecording) { + /** Shows the schedules activity for series recording. */ + public static void startSchedulesActivityForSeries( + Context context, SeriesRecording seriesRecording) { Intent intent = new Intent(context, DvrSchedulesActivity.class); - intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, - DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); - intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, - seriesRecording); + intent.putExtra( + DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); + intent.putExtra( + DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, seriesRecording); context.startActivity(intent); } @@ -444,93 +434,125 @@ public class DvrUiHelper { * * @param programs list of programs which belong to the series. */ - public static void startSeriesSettingsActivity(Context context, long seriesRecordingId, - @Nullable List programs, boolean removeEmptySeriesSchedule, - boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, + public static void startSeriesSettingsActivity( + Context context, + long seriesRecordingId, + @Nullable List programs, + boolean removeEmptySeriesSchedule, + boolean isWindowTranslucent, + boolean showViewScheduleOptionInDialog, Program currentProgram) { - SeriesRecording series = TvApplication.getSingletons(context).getDvrDataManager() - .getSeriesRecording(seriesRecordingId); + SeriesRecording series = + TvApplication.getSingletons(context) + .getDvrDataManager() + .getSeriesRecording(seriesRecordingId); if (series == null) { return; } if (programs != null) { - startSeriesSettingsActivityInternal(context, seriesRecordingId, programs, - removeEmptySeriesSchedule, isWindowTranslucent, - showViewScheduleOptionInDialog, currentProgram); + startSeriesSettingsActivityInternal( + context, + seriesRecordingId, + programs, + removeEmptySeriesSchedule, + isWindowTranslucent, + showViewScheduleOptionInDialog, + currentProgram); } else { EpisodicProgramLoadTask episodicProgramLoadTask = new EpisodicProgramLoadTask(context, series) { - @Override - protected void onPostExecute(List loadedPrograms) { - sProgressDialog.dismiss(); - sProgressDialog = null; - startSeriesSettingsActivityInternal(context, seriesRecordingId, - loadedPrograms == null ? Collections.EMPTY_LIST : loadedPrograms, - removeEmptySeriesSchedule, isWindowTranslucent, - showViewScheduleOptionInDialog, currentProgram); - } - }.setLoadCurrentProgram(true) - .setLoadDisallowedProgram(true) - .setLoadScheduledEpisode(true) - .setIgnoreChannelOption(true); - sProgressDialog = ProgressDialog.show(context, null, context.getString( - R.string.dvr_series_progress_message_reading_programs), true, true, - new DialogInterface.OnCancelListener() { @Override - public void onCancel(DialogInterface dialogInterface) { - episodicProgramLoadTask.cancel(true); + protected void onPostExecute(List loadedPrograms) { + sProgressDialog.dismiss(); sProgressDialog = null; + startSeriesSettingsActivityInternal( + context, + seriesRecordingId, + loadedPrograms == null + ? Collections.EMPTY_LIST + : loadedPrograms, + removeEmptySeriesSchedule, + isWindowTranslucent, + showViewScheduleOptionInDialog, + currentProgram); } - }); + }.setLoadCurrentProgram(true) + .setLoadDisallowedProgram(true) + .setLoadScheduledEpisode(true) + .setIgnoreChannelOption(true); + sProgressDialog = + ProgressDialog.show( + context, + null, + context.getString( + R.string.dvr_series_progress_message_reading_programs), + true, + true, + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + episodicProgramLoadTask.cancel(true); + sProgressDialog = null; + } + }); episodicProgramLoadTask.execute(); } } - private static void startSeriesSettingsActivityInternal(Context context, long seriesRecordingId, - @NonNull List programs, boolean removeEmptySeriesSchedule, - boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, + private static void startSeriesSettingsActivityInternal( + Context context, + long seriesRecordingId, + @NonNull List programs, + boolean removeEmptySeriesSchedule, + boolean isWindowTranslucent, + boolean showViewScheduleOptionInDialog, Program currentProgram) { - SoftPreconditions.checkState(programs != null, - TAG, "Start series settings activity but programs is null"); + SoftPreconditions.checkState( + programs != null, TAG, "Start series settings activity but programs is null"); Intent intent = new Intent(context, DvrSeriesSettingsActivity.class); intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId); BigArguments.reset(); BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs); - intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, - removeEmptySeriesSchedule); + intent.putExtra( + DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, removeEmptySeriesSchedule); intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent); - intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, + intent.putExtra( + DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, showViewScheduleOptionInDialog); intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram); context.startActivity(intent); } - /** - * Shows "series recording scheduled" dialog activity. - */ - public static void StartSeriesScheduledDialogActivity(Context context, - SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog, + /** Shows "series recording scheduled" dialog activity. */ + public static void StartSeriesScheduledDialogActivity( + Context context, + SeriesRecording seriesRecording, + boolean showViewScheduleOptionInDialog, List programs) { if (seriesRecording == null) { return; } Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class); - intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, - seriesRecording.getId()); - intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, + intent.putExtra( + DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, seriesRecording.getId()); + intent.putExtra( + DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, showViewScheduleOptionInDialog); BigArguments.reset(); - BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, - programs); + BigArguments.setArgument( + DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, programs); context.startActivity(intent); } /** - * Shows the details activity for the DVR items. The type of DVR items may be - * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. + * Shows the details activity for the DVR items. The type of DVR items may be {@link + * ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. */ - public static void startDetailsActivity(Activity activity, Object dvrItem, - @Nullable ImageView imageView, boolean hideViewSchedule) { + public static void startDetailsActivity( + Activity activity, + Object dvrItem, + @Nullable ImageView imageView, + boolean hideViewSchedule) { if (dvrItem == null) { return; } @@ -561,89 +583,111 @@ public class DvrUiHelper { intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule); Bundle bundle = null; if (imageView != null) { - bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, - DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); + bundle = + ActivityOptionsCompat.makeSceneTransitionAnimation( + activity, imageView, DvrDetailsActivity.SHARED_ELEMENT_NAME) + .toBundle(); } activity.startActivity(intent, bundle); } - /** - * Shows the cancel all dialog for series schedules list. - */ - public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, - SeriesRecording seriesRecording) { + /** Shows the cancel all dialog for series schedules list. */ + public static void showCancelAllSeriesRecordingDialog( + DvrSchedulesActivity activity, SeriesRecording seriesRecording) { DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = new DvrStopSeriesRecordingDialogFragment(); Bundle arguments = new Bundle(); - arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, - seriesRecording); + arguments.putParcelable( + DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, seriesRecording); dvrStopSeriesRecordingDialogFragment.setArguments(arguments); - dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(), - DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); + dvrStopSeriesRecordingDialogFragment.show( + activity.getFragmentManager(), DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); } - /** - * Shows the series deletion activity. - */ + /** Shows the series deletion activity. */ public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) { Intent intent = new Intent(context, DvrSeriesDeletionActivity.class); intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId); context.startActivity(intent); } - public static void showAddScheduleToast(Context context, - String title, long startTimeMs, long endTimeMs) { - String msg = (startTimeMs > System.currentTimeMillis()) ? - context.getString(R.string.dvr_msg_program_scheduled, title) - : context.getString(R.string.dvr_msg_current_program_scheduled, title, - Utils.toTimeString(endTimeMs, false)); + public static void showAddScheduleToast( + Context context, String title, long startTimeMs, long endTimeMs) { + String msg = + (startTimeMs > System.currentTimeMillis()) + ? context.getString(R.string.dvr_msg_program_scheduled, title) + : context.getString( + R.string.dvr_msg_current_program_scheduled, + title, + Utils.toTimeString(endTimeMs, false)); Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); } - /** - * Returns the styled schedule's title with its season and episode number. - */ - public static CharSequence getStyledTitleWithEpisodeNumber(Context context, - ScheduledRecording schedule, int episodeNumberStyleResId) { - return getStyledTitleWithEpisodeNumber(context, schedule.getProgramTitle(), - schedule.getSeasonNumber(), schedule.getEpisodeNumber(), episodeNumberStyleResId); + /** Returns the styled schedule's title with its season and episode number. */ + public static CharSequence getStyledTitleWithEpisodeNumber( + Context context, ScheduledRecording schedule, int episodeNumberStyleResId) { + return getStyledTitleWithEpisodeNumber( + context, + schedule.getProgramTitle(), + schedule.getSeasonNumber(), + schedule.getEpisodeNumber(), + episodeNumberStyleResId); } - /** - * Returns the styled program's title with its season and episode number. - */ - public static CharSequence getStyledTitleWithEpisodeNumber(Context context, - BaseProgram program, int episodeNumberStyleResId) { - return getStyledTitleWithEpisodeNumber(context, program.getTitle(), - program.getSeasonNumber(), program.getEpisodeNumber(), episodeNumberStyleResId); + /** Returns the styled program's title with its season and episode number. */ + public static CharSequence getStyledTitleWithEpisodeNumber( + Context context, BaseProgram program, int episodeNumberStyleResId) { + return getStyledTitleWithEpisodeNumber( + context, + program.getTitle(), + program.getSeasonNumber(), + program.getEpisodeNumber(), + episodeNumberStyleResId); } @NonNull - public static CharSequence getStyledTitleWithEpisodeNumber(Context context, String title, - String seasonNumber, String episodeNumber, int episodeNumberStyleResId) { + public static CharSequence getStyledTitleWithEpisodeNumber( + Context context, + String title, + String seasonNumber, + String episodeNumber, + int episodeNumberStyleResId) { if (TextUtils.isEmpty(title)) { return ""; } SpannableStringBuilder builder; if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) { - builder = TextUtils.isEmpty(episodeNumber) ? new SpannableStringBuilder(title) : - new SpannableStringBuilder(Html.fromHtml( - context.getString(R.string.program_title_with_episode_number_no_season, - title, episodeNumber))); + builder = + TextUtils.isEmpty(episodeNumber) + ? new SpannableStringBuilder(title) + : new SpannableStringBuilder( + Html.fromHtml( + context.getString( + R.string + .program_title_with_episode_number_no_season, + title, + episodeNumber))); } else { - builder = new SpannableStringBuilder(Html.fromHtml( - context.getString(R.string.program_title_with_episode_number, - title, seasonNumber, episodeNumber))); + builder = + new SpannableStringBuilder( + Html.fromHtml( + context.getString( + R.string.program_title_with_episode_number, + title, + seasonNumber, + episodeNumber))); } Object[] spans = builder.getSpans(0, builder.length(), Object.class); if (spans.length > 0) { if (episodeNumberStyleResId != 0) { - builder.setSpan(new TextAppearanceSpan(context, episodeNumberStyleResId), - builder.getSpanStart(spans[0]), builder.getSpanEnd(spans[0]), + builder.setSpan( + new TextAppearanceSpan(context, episodeNumberStyleResId), + builder.getSpanStart(spans[0]), + builder.getSpanEnd(spans[0]), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } builder.removeSpan(spans[0]); } return new SpannableString(builder); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java index 4f06ebcf..373daaaf 100644 --- a/src/com/android/tv/dvr/ui/FadeBackground.java +++ b/src/com/android/tv/dvr/ui/FadeBackground.java @@ -28,12 +28,9 @@ import android.transition.TransitionValues; import android.transition.Visibility; import android.util.AttributeSet; import android.view.ViewGroup; - import com.android.tv.R; -/** - * This transition fades in/out of the background of the view by changing the background color. - */ +/** This transition fades in/out of the background of the view by changing the background color. */ public class FadeBackground extends Transition { private final int mMode; @@ -45,22 +42,22 @@ public class FadeBackground extends Transition { } @Override - public void captureStartValues(TransitionValues transitionValues) { } + public void captureStartValues(TransitionValues transitionValues) {} @Override - public void captureEndValues(TransitionValues transitionValues) { } + public void captureEndValues(TransitionValues transitionValues) {} @Override - public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, - TransitionValues endValues) { + public Animator createAnimator( + ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } Drawable background = endValues.view.getBackground(); if (background instanceof ColorDrawable) { int color = ((ColorDrawable) background).getColor(); - int transparentColor = Color.argb(0, Color.red(color), Color.green(color), - Color.blue(color)); + int transparentColor = + Color.argb(0, Color.red(color), Color.green(color), Color.blue(color)); return mMode == Visibility.MODE_OUT ? ObjectAnimator.ofArgb(background, "color", transparentColor) : ObjectAnimator.ofArgb(background, "color", transparentColor, color); diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java index 8c0af9ed..1eb8080a 100644 --- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java +++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java @@ -19,9 +19,7 @@ package com.android.tv.dvr.ui; import android.support.annotation.VisibleForTesting; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.PresenterSelector; - import com.android.tv.common.SoftPreconditions; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -45,8 +43,8 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { this(presenterSelector, comparator, Integer.MAX_VALUE); } - public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator comparator, - int maxItemCount) { + public SortedArrayAdapter( + PresenterSelector presenterSelector, Comparator comparator, int maxItemCount) { super(presenterSelector); mComparator = comparator; mMaxItemCount = maxItemCount; @@ -88,9 +86,8 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { * Adds an item in sorted order to the adapter. * * @param item The item to add in sorted order to the adapter. - * @param insertToEnd If items are inserted in a more or less sorted fashion, - * sets this parameter to {@code true} to search insertion position from - * the end to save search time. + * @param insertToEnd If items are inserted in a more or less sorted fashion, sets this + * parameter to {@code true} to search insertion position from the end to save search time. */ public final void add(T item, boolean insertToEnd) { long newItemId = getId(item); @@ -127,9 +124,7 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { return removeWithId((T) item); } - /** - * Removes an item which has the same ID as {@code item}. - */ + /** Removes an item which has the same ID as {@code item}. */ public boolean removeWithId(T item) { int index = indexWithId(item); return index >= 0 && index < size() && removeItems(index, 1) == 1; @@ -166,6 +161,7 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { /** * Changes an item in the list. + * * @param item The item to change. */ public final void change(T item) { @@ -181,9 +177,7 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { add(item); } - /** - * Checks whether the item is in the list. - */ + /** Checks whether the item is in the list. */ public final boolean contains(T item) { return indexWithId(item) != -1; } @@ -194,10 +188,10 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { } /** - * Returns the id of the the given {@code item}, which will be used in {@link #change} to - * decide if the given item is already existed in the adapter. + * Returns the id of the the given {@code item}, which will be used in {@link #change} to decide + * if the given item is already existed in the adapter. * - * The id must be stable. + *

The id must be stable. */ protected abstract long getId(T item); @@ -212,11 +206,9 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { return -1; } - /** - * Finds the position that the given item should be inserted to keep the sorted order. - */ + /** Finds the position that the given item should be inserted to keep the sorted order. */ public int findInsertPosition(T item) { - for (int i = size() - mExtraItemCount - 1; i >=0; i--) { + for (int i = size() - mExtraItemCount - 1; i >= 0; i--) { T r = (T) get(i); if (mComparator.compare(r, item) <= 0) { return i + 1; @@ -242,4 +234,4 @@ public abstract class SortedArrayAdapter extends ArrayObjectAdapter { } return lb; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java index 5fe9c478..0a24187a 100644 --- a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java +++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java @@ -19,7 +19,6 @@ package com.android.tv.dvr.ui; import android.content.Context; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.TvApplication; import com.android.tv.analytics.Tracker; diff --git a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java index 38a78f5d..f3a6fea4 100644 --- a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java +++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java @@ -32,8 +32,8 @@ import android.widget.Button; class ActionPresenterSelector extends PresenterSelector { private final Presenter mOneLineActionPresenter = new OneLineActionPresenter(); private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter(); - private final Presenter[] mPresenters = new Presenter[] { - mOneLineActionPresenter, mTwoLineActionPresenter}; + private final Presenter[] mPresenters = + new Presenter[] {mOneLineActionPresenter, mTwoLineActionPresenter}; @Override public Presenter getPresenter(Object item) { @@ -65,8 +65,9 @@ class ActionPresenterSelector extends PresenterSelector { class OneLineActionPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.lb_action_1_line, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.lb_action_1_line, parent, false); return new ActionViewHolder(v, parent.getLayoutDirection()); } @@ -87,8 +88,9 @@ class ActionPresenterSelector extends PresenterSelector { class TwoLineActionPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.lb_action_2_lines, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.lb_action_2_lines, parent, false); return new ActionViewHolder(v, parent.getLayoutDirection()); } @@ -100,14 +102,20 @@ class ActionPresenterSelector extends PresenterSelector { vh.mAction = action; if (icon != null) { - final int startPadding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start); - final int endPadding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end); + final int startPadding = + vh.view + .getResources() + .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start); + final int endPadding = + vh.view + .getResources() + .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end); vh.view.setPaddingRelative(startPadding, 0, endPadding, 0); } else { - final int padding = vh.view.getResources() - .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal); + final int padding = + vh.view + .getResources() + .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal); vh.view.setPaddingRelative(padding, 0, padding, 0); } vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); @@ -131,4 +139,4 @@ class ActionPresenterSelector extends PresenterSelector { vh.mAction = null; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java index bf18ddc0..22246e5a 100644 --- a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java @@ -21,7 +21,6 @@ import android.content.res.Resources; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dialog.HalfSizedDialogFragment; @@ -31,9 +30,7 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrStopRecordingFragment; import com.android.tv.dvr.ui.DvrUiHelper; -/** - * {@link RecordingDetailsFragment} for current recording in DVR. - */ +/** {@link RecordingDetailsFragment} for current recording in DVR. */ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { private static final int ACTION_STOP_RECORDING = 1; @@ -41,7 +38,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener = new DvrDataManager.ScheduledRecordingListener() { @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { } + public void onScheduledRecordingAdded(ScheduledRecording... schedules) {} @Override public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { @@ -58,7 +55,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { for (ScheduledRecording schedule : schedules) { if (schedule.getId() == getRecording().getId() && schedule.getState() - != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { + != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { getActivity().finish(); return; } @@ -78,9 +75,13 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); Resources res = getResources(); - adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING, - res.getString(R.string.dvr_detail_stop_recording), null, - res.getDrawable(R.drawable.lb_ic_stop))); + adapter.set( + ACTION_STOP_RECORDING, + new Action( + ACTION_STOP_RECORDING, + res.getString(R.string.dvr_detail_stop_recording), + null, + res.getDrawable(R.drawable.lb_ic_stop))); return adapter; } @@ -90,7 +91,8 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { @Override public void onActionClicked(Action action) { if (action.getId() == ACTION_STOP_RECORDING) { - DvrUiHelper.showStopRecordingDialog(getActivity(), + DvrUiHelper.showStopRecordingDialog( + getActivity(), getRecording().getChannelId(), DvrStopRecordingFragment.REASON_USER_STOP, new HalfSizedDialogFragment.OnActionClickListener() { diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java index c1fa05d7..9f588aa3 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java @@ -20,7 +20,6 @@ import android.content.Context; import android.media.tv.TvContract; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Channel; @@ -29,9 +28,7 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrUiHelper; -/** - * A class for details content. - */ +/** A class for details content. */ class DetailsContent { /** Constant for invalid time. */ public static final long INVALID_TIME = -1; @@ -44,8 +41,8 @@ class DetailsContent { private String mBackgroundImageUri; private boolean mUsingChannelLogo; - static DetailsContent createFromRecordedProgram(Context context, - RecordedProgram recordedProgram) { + static DetailsContent createFromRecordedProgram( + Context context, RecordedProgram recordedProgram) { return new DetailsContent.Builder() .setChannelId(recordedProgram.getChannelId()) .setProgramTitle(recordedProgram.getTitle()) @@ -53,32 +50,39 @@ class DetailsContent { .setEpisodeNumber(recordedProgram.getEpisodeNumber()) .setStartTimeUtcMillis(recordedProgram.getStartTimeUtcMillis()) .setEndTimeUtcMillis(recordedProgram.getEndTimeUtcMillis()) - .setDescription(TextUtils.isEmpty(recordedProgram.getLongDescription()) - ? recordedProgram.getDescription() : recordedProgram.getLongDescription()) + .setDescription( + TextUtils.isEmpty(recordedProgram.getLongDescription()) + ? recordedProgram.getDescription() + : recordedProgram.getLongDescription()) .setPosterArtUri(recordedProgram.getPosterArtUri()) .setThumbnailUri(recordedProgram.getThumbnailUri()) .build(context); } - static DetailsContent createFromSeriesRecording(Context context, - SeriesRecording seriesRecording) { + static DetailsContent createFromSeriesRecording( + Context context, SeriesRecording seriesRecording) { return new DetailsContent.Builder() .setChannelId(seriesRecording.getChannelId()) .setTitle(seriesRecording.getTitle()) - .setDescription(TextUtils.isEmpty(seriesRecording.getLongDescription()) - ? seriesRecording.getDescription() : seriesRecording.getLongDescription()) + .setDescription( + TextUtils.isEmpty(seriesRecording.getLongDescription()) + ? seriesRecording.getDescription() + : seriesRecording.getLongDescription()) .setPosterArtUri(seriesRecording.getPosterUri()) .setThumbnailUri(seriesRecording.getPhotoUri()) .build(context); } - static DetailsContent createFromScheduledRecording(Context context, - ScheduledRecording scheduledRecording) { - Channel channel = TvApplication.getSingletons(context).getChannelDataManager() - .getChannel(scheduledRecording.getChannelId()); - String description = !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) ? - scheduledRecording.getProgramDescription() - : scheduledRecording.getProgramLongDescription(); + static DetailsContent createFromScheduledRecording( + Context context, ScheduledRecording scheduledRecording) { + Channel channel = + TvApplication.getSingletons(context) + .getChannelDataManager() + .getChannel(scheduledRecording.getChannelId()); + String description = + !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) + ? scheduledRecording.getProgramDescription() + : scheduledRecording.getProgramLongDescription(); if (TextUtils.isEmpty(description)) { description = channel != null ? channel.getDescription() : null; } @@ -95,60 +99,44 @@ class DetailsContent { .build(context); } - private DetailsContent() { } + private DetailsContent() {} - /** - * Returns title. - */ + /** Returns title. */ public CharSequence getTitle() { return mTitle; } - /** - * Returns start time. - */ + /** Returns start time. */ public long getStartTimeUtcMillis() { return mStartTimeUtcMillis; } - /** - * Returns end time. - */ + /** Returns end time. */ public long getEndTimeUtcMillis() { return mEndTimeUtcMillis; } - /** - * Returns description. - */ + /** Returns description. */ public String getDescription() { return mDescription; } - /** - * Returns Logo image URI as a String. - */ + /** Returns Logo image URI as a String. */ public String getLogoImageUri() { return mLogoImageUri; } - /** - * Returns background image URI as a String. - */ + /** Returns background image URI as a String. */ public String getBackgroundImageUri() { return mBackgroundImageUri; } - /** - * Returns if image URIs are from its channels' logo. - */ + /** Returns if image URIs are from its channels' logo. */ public boolean isUsingChannelLogo() { return mUsingChannelLogo; } - /** - * Copies other details content. - */ + /** Copies other details content. */ public void copyFrom(DetailsContent other) { if (this == other) { return; @@ -162,9 +150,7 @@ class DetailsContent { mUsingChannelLogo = other.mUsingChannelLogo; } - /** - * A class for building details content. - */ + /** A class for building details content. */ public static final class Builder { private final DetailsContent mDetailsContent; @@ -181,49 +167,37 @@ class DetailsContent { mDetailsContent.mEndTimeUtcMillis = INVALID_TIME; } - /** - * Sets title. - */ + /** Sets title. */ public Builder setTitle(CharSequence title) { mDetailsContent.mTitle = title; return this; } - /** - * Sets start time. - */ + /** Sets start time. */ public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis; return this; } - /** - * Sets end time. - */ + /** Sets end time. */ public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis; return this; } - /** - * Sets description. - */ + /** Sets description. */ public Builder setDescription(String description) { mDetailsContent.mDescription = description; return this; } - /** - * Sets logo image URI as a String. - */ + /** Sets logo image URI as a String. */ public Builder setLogoImageUri(String logoImageUri) { mDetailsContent.mLogoImageUri = logoImageUri; return this; } - /** - * Sets background image URI as a String. - */ + /** Sets background image URI as a String. */ public Builder setBackgroundImageUri(String backgroundImageUri) { mDetailsContent.mBackgroundImageUri = backgroundImageUri; return this; @@ -260,12 +234,18 @@ class DetailsContent { } private void createStyledTitle(Context context, Channel channel) { - CharSequence title = DvrUiHelper.getStyledTitleWithEpisodeNumber(context, - mProgramTitle, mSeasonNumber, mEpisodeNumber, - R.style.text_appearance_card_view_episode_number); + CharSequence title = + DvrUiHelper.getStyledTitleWithEpisodeNumber( + context, + mProgramTitle, + mSeasonNumber, + mEpisodeNumber, + R.style.text_appearance_card_view_episode_number); if (TextUtils.isEmpty(title)) { - mDetailsContent.mTitle = channel != null ? channel.getDisplayName() - : context.getResources().getString(R.string.no_program_information); + mDetailsContent.mTitle = + channel != null + ? channel.getDisplayName() + : context.getResources().getString(R.string.no_program_information); } else { mDetailsContent.mTitle = title; } @@ -288,20 +268,19 @@ class DetailsContent { mDetailsContent.mBackgroundImageUri = mThumbnailUri; } if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) { - String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()) - .toString(); + String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()).toString(); mDetailsContent.mLogoImageUri = channelLogoUri; mDetailsContent.mBackgroundImageUri = channelLogoUri; mDetailsContent.mUsingChannelLogo = true; } } - /** - * Builds details content. - */ + /** Builds details content. */ public DetailsContent build(Context context) { - Channel channel = TvApplication.getSingletons(context).getChannelDataManager() - .getChannel(mChannelId); + Channel channel = + TvApplication.getSingletons(context) + .getChannelDataManager() + .getChannel(mChannelId); if (mDetailsContent.mTitle == null) { createStyledTitle(context, channel); } @@ -314,4 +293,4 @@ class DetailsContent { return detailsContent; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java index 09b57887..aec8c411 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java @@ -16,11 +16,11 @@ package com.android.tv.dvr.ui.browse; -import android.app.Activity; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.app.Activity; import android.content.Context; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; @@ -33,24 +33,20 @@ import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.ui.ViewUtils; import com.android.tv.util.Utils; /** - * An {@link Presenter} for rendering a detailed description of an DVR item. - * Typically this Presenter will be used in a - * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. - * Most codes of this class is originated from - * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}. - * The latter class are re-used to provide a customized version of - * {@link android.support.v17.leanback.widget.DetailsOverviewRow}. + * An {@link Presenter} for rendering a detailed description of an DVR item. Typically this + * Presenter will be used in a {@link + * android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is + * originated from {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}. + * The latter class are re-used to provide a customized version of {@link + * android.support.v17.leanback.widget.DetailsOverviewRow}. */ class DetailsContentPresenter extends Presenter { - /** - * The ViewHolder for the {@link DetailsContentPresenter}. - */ + /** The ViewHolder for the {@link DetailsContentPresenter}. */ public static class ViewHolder extends Presenter.ViewHolder { final TextView mTitle; final TextView mSubtitle; @@ -85,31 +81,40 @@ class DetailsContentPresenter extends Presenter { return false; } final int bodyLines = mBody.getLineCount(); - int maxLines = mFullTextMode ? bodyLines : - (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines); + int maxLines = + mFullTextMode + ? bodyLines + : (mTitle.getLineCount() > 1 + ? mBodyMinLines + : mBodyMaxLines); if (bodyLines > maxLines) { mReadMoreView.setVisibility(View.VISIBLE); mDescriptionContainer.setFocusable(true); mDescriptionContainer.setClickable(true); - mDescriptionContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mFullTextMode = true; - mReadMoreView.setVisibility(View.GONE); - mDescriptionContainer.setFocusable(( - (AccessibilityManager) view.getContext() - .getSystemService( - Context.ACCESSIBILITY_SERVICE)) - .isEnabled()); - mDescriptionContainer.setClickable(false); - mDescriptionContainer.setOnClickListener(null); - int oldMaxLines = mBody.getMaxLines(); - mBody.setMaxLines(bodyLines); - // Minus 1 from line difference to eliminate the space - // originally occupied by "READ MORE" - showFullText((bodyLines - oldMaxLines - 1) * mBodyLineSpacing); - } - }); + mDescriptionContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + mFullTextMode = true; + mReadMoreView.setVisibility(View.GONE); + mDescriptionContainer.setFocusable( + ((AccessibilityManager) + view.getContext() + .getSystemService( + Context + .ACCESSIBILITY_SERVICE)) + .isEnabled()); + mDescriptionContainer.setClickable(false); + mDescriptionContainer.setOnClickListener(null); + int oldMaxLines = mBody.getMaxLines(); + mBody.setMaxLines(bodyLines); + // Minus 1 from line difference to eliminate the space + // originally occupied by "READ MORE" + showFullText( + (bodyLines - oldMaxLines - 1) + * mBodyLineSpacing); + } + }); } if (mReadMoreView.getVisibility() == View.VISIBLE && mSubtitle.getVisibility() == View.VISIBLE) { @@ -151,30 +156,42 @@ class DetailsContentPresenter extends Presenter { // We have to explicitly set focusable to true here for accessibility, since we might // set the view's focusable state when we need to show "READ MORE", which would remove // the default focusable state for accessibility. - mDescriptionContainer.setFocusable(((AccessibilityManager) view.getContext() - .getSystemService(Context.ACCESSIBILITY_SERVICE)).isEnabled()); + mDescriptionContainer.setFocusable( + ((AccessibilityManager) + view.getContext() + .getSystemService(Context.ACCESSIBILITY_SERVICE)) + .isEnabled()); mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more); FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle); - final int titleAscent = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_title_baseline); + final int titleAscent = + view.getResources() + .getDimensionPixelSize(R.dimen.lb_details_description_title_baseline); // Ascent is negative mTitleMargin = titleAscent + titleFontMetricsInt.ascent; - mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_under_title_baseline_margin); - mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize( - R.dimen.dvr_details_description_under_subtitle_baseline_margin); + mUnderTitleBaselineMargin = + view.getResources() + .getDimensionPixelSize( + R.dimen.lb_details_description_under_title_baseline_margin); + mUnderSubtitleBaselineMargin = + view.getResources() + .getDimensionPixelSize( + R.dimen.dvr_details_description_under_subtitle_baseline_margin); - mTitleLineSpacing = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_title_line_spacing); - mBodyLineSpacing = view.getResources().getDimensionPixelSize( - R.dimen.lb_details_description_body_line_spacing); + mTitleLineSpacing = + view.getResources() + .getDimensionPixelSize( + R.dimen.lb_details_description_title_line_spacing); + mBodyLineSpacing = + view.getResources() + .getDimensionPixelSize( + R.dimen.lb_details_description_body_line_spacing); - mBodyMaxLines = view.getResources().getInteger( - R.integer.lb_details_description_body_max_lines); - mBodyMinLines = view.getResources().getInteger( - R.integer.lb_details_description_body_min_lines); + mBodyMaxLines = + view.getResources().getInteger(R.integer.lb_details_description_body_max_lines); + mBodyMinLines = + view.getResources().getInteger(R.integer.lb_details_description_body_min_lines); mTitleMaxLines = mTitle.getMaxLines(); mTitleFontMetricsInt = getFontMetricsInt(mTitle); @@ -218,12 +235,14 @@ class DetailsContentPresenter extends Presenter { private void showFullText(int heightDiff) { final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame); int nowHeight = ViewUtils.getLayoutHeight(detailsFrame); - Animator expandAnimator = ViewUtils.createHeightAnimator( - detailsFrame, nowHeight, nowHeight + heightDiff); + Animator expandAnimator = + ViewUtils.createHeightAnimator(detailsFrame, nowHeight, nowHeight + heightDiff); expandAnimator.setDuration(mFullTextAnimationDuration); - Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame, - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - 0f, -(heightDiff / 2))); + Animator shiftAnimator = + ObjectAnimator.ofPropertyValuesHolder( + detailsFrame, + PropertyValuesHolder.ofFloat( + View.TRANSLATION_Y, 0f, -(heightDiff / 2))); shiftAnimator.setDuration(mFullTextAnimationDuration); AnimatorSet fullTextAnimator = new AnimatorSet(); fullTextAnimator.playTogether(expandAnimator, shiftAnimator); @@ -237,14 +256,17 @@ class DetailsContentPresenter extends Presenter { public DetailsContentPresenter(Activity activity) { super(); mActivity = activity; - mFullTextAnimationDuration = mActivity.getResources() - .getInteger(R.integer.dvr_details_full_text_animation_duration); + mFullTextAnimationDuration = + mActivity + .getResources() + .getInteger(R.integer.dvr_details_full_text_animation_duration); } @Override public final ViewHolder onCreateViewHolder(ViewGroup parent) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.dvr_details_description, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.dvr_details_description, parent, false); return new ViewHolder(v); } @@ -263,8 +285,11 @@ class DetailsContentPresenter extends Presenter { } else { vh.mTitle.setText(detailsContent.getTitle()); vh.mTitle.setVisibility(View.VISIBLE); - vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() - + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier()); + vh.mTitle.setLineSpacing( + vh.mTitleLineSpacing + - vh.mTitle.getLineHeight() + + vh.mTitle.getLineSpacingExtra(), + vh.mTitle.getLineSpacingMultiplier()); vh.mTitle.setMaxLines(vh.mTitleMaxLines); } setTopMargin(vh.mTitle, vh.mTitleMargin); @@ -272,13 +297,19 @@ class DetailsContentPresenter extends Presenter { boolean hasSubtitle = true; if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) { - vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(), - detailsContent.getStartTimeUtcMillis(), - detailsContent.getEndTimeUtcMillis(), false)); + vh.mSubtitle.setText( + Utils.getDurationString( + viewHolder.view.getContext(), + detailsContent.getStartTimeUtcMillis(), + detailsContent.getEndTimeUtcMillis(), + false)); vh.mSubtitle.setVisibility(View.VISIBLE); if (hasTitle) { - setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin - + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent); + setTopMargin( + vh.mSubtitle, + vh.mUnderTitleBaselineMargin + + vh.mSubtitleFontMetricsInt.ascent + - vh.mTitleFontMetricsInt.descent); } else { setTopMargin(vh.mSubtitle, 0); } @@ -292,16 +323,23 @@ class DetailsContentPresenter extends Presenter { } else { vh.mBody.setText(detailsContent.getDescription()); vh.mBody.setVisibility(View.VISIBLE); - vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() - + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier()); + vh.mBody.setLineSpacing( + vh.mBodyLineSpacing - vh.mBody.getLineHeight() + vh.mBody.getLineSpacingExtra(), + vh.mBody.getLineSpacingMultiplier()); if (hasSubtitle) { - setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin - + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent - - vh.mBody.getPaddingTop()); + setTopMargin( + vh.mDescriptionContainer, + vh.mUnderSubtitleBaselineMargin + + vh.mBodyFontMetricsInt.ascent + - vh.mSubtitleFontMetricsInt.descent + - vh.mBody.getPaddingTop()); } else if (hasTitle) { - setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin - + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent - - vh.mBody.getPaddingTop()); + setTopMargin( + vh.mDescriptionContainer, + vh.mUnderTitleBaselineMargin + + vh.mBodyFontMetricsInt.ascent + - vh.mTitleFontMetricsInt.descent + - vh.mBody.getPaddingTop()); } else { setTopMargin(vh.mDescriptionContainer, 0); } @@ -309,11 +347,11 @@ class DetailsContentPresenter extends Presenter { } @Override - public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { } + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {} private void setTopMargin(View view, int topMargin) { ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); lp.topMargin = topMargin; view.setLayoutParams(lp); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java index 82fe9ce3..5a058454 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java @@ -23,9 +23,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.support.v17.leanback.app.BackgroundManager; -/** - * The Background Helper. - */ +/** The Background Helper. */ class DetailsViewBackgroundHelper { // Background delay serves to avoid kicking off expensive bitmap loading // in case multiple backgrounds are set in quick succession. @@ -61,9 +59,7 @@ class DetailsViewBackgroundHelper { mBackgroundManager.attach(activity.getWindow()); } - /** - * Sets the given image to background. - */ + /** Sets the given image to background. */ public void setBackground(Drawable background) { if (mRunnable != null) { mHandler.removeCallbacks(mRunnable); @@ -72,18 +68,14 @@ class DetailsViewBackgroundHelper { mHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS); } - /** - * Sets the background color. - */ + /** Sets the background color. */ public void setBackgroundColor(int color) { if (mBackgroundManager.isAttached()) { mBackgroundManager.setColor(color); } } - /** - * Sets the background scrim. - */ + /** Sets the background scrim. */ public void setScrim(int color) { if (mBackgroundManager.isAttached()) { mBackgroundManager.setDimLayer(new ColorDrawable(color)); diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java index 07eec107..f208b5e8 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java @@ -20,13 +20,10 @@ import android.app.Activity; import android.content.Intent; import android.media.tv.TvInputManager; import android.os.Bundle; - import com.android.tv.R; import com.android.tv.TvApplication; -/** - * {@link android.app.Activity} for DVR UI. - */ +/** {@link android.app.Activity} for DVR UI. */ public class DvrBrowseActivity extends Activity { private DvrBrowseFragment mFragment; @@ -49,4 +46,4 @@ public class DvrBrowseActivity extends Activity { mFragment.showScheduledRow(); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java index cb3a5745..f8a54ef0 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java @@ -29,7 +29,6 @@ import android.support.v17.leanback.widget.TitleViewAdapter; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -45,19 +44,19 @@ import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.SortedArrayAdapter; - import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; -/** - * {@link BrowseFragment} for DVR functions. - */ -public class DvrBrowseFragment extends BrowseFragment implements - RecordedProgramListener, ScheduledRecordingListener, SeriesRecordingListener, - OnDvrScheduleLoadFinishedListener, OnRecordedProgramLoadFinishedListener { +/** {@link BrowseFragment} for DVR functions. */ +public class DvrBrowseFragment extends BrowseFragment + implements RecordedProgramListener, + ScheduledRecordingListener, + SeriesRecordingListener, + OnDvrScheduleLoadFinishedListener, + OnRecordedProgramLoadFinishedListener { private static final String TAG = "DvrBrowseFragment"; private static final boolean DEBUG = false; @@ -98,66 +97,71 @@ public class DvrBrowseFragment extends BrowseFragment implements } }; - private final Comparator RECORDED_PROGRAM_COMPARATOR = new Comparator() { - @Override - public int compare(Object lhs, Object rhs) { - if (lhs instanceof SeriesRecording) { - lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId()); - } - if (rhs instanceof SeriesRecording) { - rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId()); - } - if (lhs instanceof RecordedProgram) { - if (rhs instanceof RecordedProgram) { - return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed() - .compare((RecordedProgram) lhs, (RecordedProgram) rhs); - } else { - return -1; + private final Comparator RECORDED_PROGRAM_COMPARATOR = + new Comparator() { + @Override + public int compare(Object lhs, Object rhs) { + if (lhs instanceof SeriesRecording) { + lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId()); + } + if (rhs instanceof SeriesRecording) { + rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId()); + } + if (lhs instanceof RecordedProgram) { + if (rhs instanceof RecordedProgram) { + return RecordedProgram.START_TIME_THEN_ID_COMPARATOR + .reversed() + .compare((RecordedProgram) lhs, (RecordedProgram) rhs); + } else { + return -1; + } + } else if (rhs instanceof RecordedProgram) { + return 1; + } else { + return 0; + } } - } else if (rhs instanceof RecordedProgram) { - return 1; - } else { - return 0; - } - } - }; + }; - private static final Comparator SCHEDULE_COMPARATOR = new Comparator() { - @Override - public int compare(Object lhs, Object rhs) { - if (lhs instanceof ScheduledRecording) { - if (rhs instanceof ScheduledRecording) { - return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR - .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs); - } else { - return -1; + private static final Comparator SCHEDULE_COMPARATOR = + new Comparator() { + @Override + public int compare(Object lhs, Object rhs) { + if (lhs instanceof ScheduledRecording) { + if (rhs instanceof ScheduledRecording) { + return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR + .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs); + } else { + return -1; + } + } else if (rhs instanceof ScheduledRecording) { + return 1; + } else { + return 0; + } } - } else if (rhs instanceof ScheduledRecording) { - return 1; - } else { - return 0; - } - } - }; + }; private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener = new DvrScheduleManager.OnConflictStateChangeListener() { - @Override - public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) { - if (mScheduleAdapter != null) { - for (ScheduledRecording schedule : schedules) { - onScheduledRecordingConflictStatusChanged(schedule); + @Override + public void onConflictStateChange( + boolean conflict, ScheduledRecording... schedules) { + if (mScheduleAdapter != null) { + for (ScheduledRecording schedule : schedules) { + onScheduledRecordingConflictStatusChanged(schedule); + } + } } - } - } - }; + }; - private final Runnable mUpdateRowsRunnable = new Runnable() { - @Override - public void run() { - updateRows(); - } - }; + private final Runnable mUpdateRowsRunnable = + new Runnable() { + @Override + public void run() { + updateRows(); + } + }; @Override public void onCreate(Bundle savedInstanceState) { @@ -167,13 +171,17 @@ public class DvrBrowseFragment extends BrowseFragment implements ApplicationSingletons singletons = TvApplication.getSingletons(context); mDvrDataManager = singletons.getDvrDataManager(); mDvrScheudleManager = singletons.getDvrScheduleManager(); - mPresenterSelector = new ClassPresenterSelector() - .addClassPresenter(ScheduledRecording.class, - new ScheduledRecordingPresenter(context)) - .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context)) - .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context)) - .addClassPresenter(FullScheduleCardHolder.class, - new FullSchedulesCardPresenter(context)); + mPresenterSelector = + new ClassPresenterSelector() + .addClassPresenter( + ScheduledRecording.class, new ScheduledRecordingPresenter(context)) + .addClassPresenter( + RecordedProgram.class, new RecordedProgramPresenter(context)) + .addClassPresenter( + SeriesRecording.class, new SeriesRecordingPresenter(context)) + .addClassPresenter( + FullScheduleCardHolder.class, + new FullSchedulesCardPresenter(context)); mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context))); mGenreLabels.add(getString(R.string.dvr_main_others)); prepareUiElements(); @@ -195,7 +203,8 @@ public class DvrBrowseFragment extends BrowseFragment implements @Override public void onDestroyView() { - getView().getViewTreeObserver() + getView() + .getViewTreeObserver() .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener); super.onDestroyView(); } @@ -376,15 +385,20 @@ public class DvrBrowseFragment extends BrowseFragment implements handleRecordedProgramAdded(recordedProgram, false); } // Series Recordings. Series recordings should be added after recorded programs, because - // we build series recordings' latest program information while adding recorded programs. + // we build series recordings' latest program information while adding recorded + // programs. List recordings = mDvrDataManager.getSeriesRecordings(); handleSeriesRecordingsAdded(recordings); - mRecentRow = new ListRow(new HeaderItem( - getString(R.string.dvr_main_recent)), mRecentAdapter); - mScheduledRow = new ListRow(new HeaderItem( - getString(R.string.dvr_main_scheduled)), mScheduleAdapter); - mSeriesRow = new ListRow(new HeaderItem( - getString(R.string.dvr_main_series)), mSeriesAdapter); + mRecentRow = + new ListRow( + new HeaderItem(getString(R.string.dvr_main_recent)), mRecentAdapter); + mScheduledRow = + new ListRow( + new HeaderItem(getString(R.string.dvr_main_scheduled)), + mScheduleAdapter); + mSeriesRow = + new ListRow( + new HeaderItem(getString(R.string.dvr_main_series)), mSeriesAdapter); mRowsAdapter.add(mScheduledRow); updateRows(); // Initialize listeners @@ -398,16 +412,18 @@ public class DvrBrowseFragment extends BrowseFragment implements return false; } - private void handleRecordedProgramAdded(RecordedProgram recordedProgram, - boolean updateSeriesRecording) { + private void handleRecordedProgramAdded( + RecordedProgram recordedProgram, boolean updateSeriesRecording) { mRecentAdapter.add(recordedProgram); String seriesId = recordedProgram.getSeriesId(); SeriesRecording seriesRecording = null; if (seriesId != null) { seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); - if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR - .compare(latestProgram, recordedProgram) < 0) { + if (latestProgram == null + || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare( + latestProgram, recordedProgram) + < 0) { mSeriesId2LatestProgram.put(seriesId, recordedProgram); if (updateSeriesRecording && seriesRecording != null) { onSeriesRecordingChanged(seriesRecording); @@ -415,8 +431,8 @@ public class DvrBrowseFragment extends BrowseFragment implements } } if (seriesRecording == null) { - for (RecordedProgramAdapter adapter - : getGenreAdapters(recordedProgram.getCanonicalGenres())) { + for (RecordedProgramAdapter adapter : + getGenreAdapters(recordedProgram.getCanonicalGenres())) { adapter.add(recordedProgram); } } @@ -436,8 +452,8 @@ public class DvrBrowseFragment extends BrowseFragment implements } } } - for (RecordedProgramAdapter adapter - : getGenreAdapters(recordedProgram.getCanonicalGenres())) { + for (RecordedProgramAdapter adapter : + getGenreAdapters(recordedProgram.getCanonicalGenres())) { adapter.remove(recordedProgram); } } @@ -449,8 +465,10 @@ public class DvrBrowseFragment extends BrowseFragment implements if (seriesId != null) { seriesRecording = mDvrDataManager.getSeriesRecording(seriesId); RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId); - if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR - .compare(latestProgram, recordedProgram) <= 0) { + if (latestProgram == null + || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare( + latestProgram, recordedProgram) + <= 0) { mSeriesId2LatestProgram.put(seriesId, recordedProgram); if (seriesRecording != null) { onSeriesRecordingChanged(seriesRecording); @@ -463,8 +481,8 @@ public class DvrBrowseFragment extends BrowseFragment implements } } if (seriesRecording == null) { - updateGenreAdapters(getGenreAdapters( - recordedProgram.getCanonicalGenres()), recordedProgram); + updateGenreAdapters( + getGenreAdapters(recordedProgram.getCanonicalGenres()), recordedProgram); } else { updateGenreAdapters(new ArrayList<>(), recordedProgram); } @@ -474,8 +492,8 @@ public class DvrBrowseFragment extends BrowseFragment implements for (SeriesRecording seriesRecording : seriesRecordings) { mSeriesAdapter.add(seriesRecording); if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { - for (RecordedProgramAdapter adapter - : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { + for (RecordedProgramAdapter adapter : + getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { adapter.add(seriesRecording); } } @@ -485,8 +503,8 @@ public class DvrBrowseFragment extends BrowseFragment implements private void handleSeriesRecordingsRemoved(List seriesRecordings) { for (SeriesRecording seriesRecording : seriesRecordings) { mSeriesAdapter.remove(seriesRecording); - for (RecordedProgramAdapter adapter - : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { + for (RecordedProgramAdapter adapter : + getGenreAdapters(seriesRecording.getCanonicalGenreIds())) { adapter.remove(seriesRecording); } } @@ -496,8 +514,8 @@ public class DvrBrowseFragment extends BrowseFragment implements for (SeriesRecording seriesRecording : seriesRecordings) { mSeriesAdapter.change(seriesRecording); if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) { - updateGenreAdapters(getGenreAdapters( - seriesRecording.getCanonicalGenreIds()), seriesRecording); + updateGenreAdapters( + getGenreAdapters(seriesRecording.getCanonicalGenreIds()), seriesRecording); } else { // Remove series recording from all genre rows if it has no recorded program updateGenreAdapters(new ArrayList<>(), seriesRecording); @@ -512,7 +530,7 @@ public class DvrBrowseFragment extends BrowseFragment implements } else { for (String genre : genres) { int genreId = GenreItems.getId(genre); - if(genreId >= mGenreAdapters.length) { + if (genreId >= mGenreAdapters.length) { Log.d(TAG, "Wrong Genre ID: " + genreId); } else { result.add(mGenreAdapters[genreId]); @@ -528,7 +546,7 @@ public class DvrBrowseFragment extends BrowseFragment implements result.add(mGenreAdapters[mGenreAdapters.length - 1]); } else { for (int genreId : genreIds) { - if(genreId >= mGenreAdapters.length) { + if (genreId >= mGenreAdapters.length) { Log.d(TAG, "Wrong Genre ID: " + genreId); } else { result.add(mGenreAdapters[genreId]); @@ -554,7 +572,7 @@ public class DvrBrowseFragment extends BrowseFragment implements } private void updateRows() { - int visibleRowsCount = 1; // Schedule's Row will never be empty + int visibleRowsCount = 1; // Schedule's Row will never be empty if (mRecentAdapter.isEmpty()) { mRowsAdapter.remove(mRecentRow); } else { @@ -597,8 +615,9 @@ public class DvrBrowseFragment extends BrowseFragment implements RecordedProgram latestProgram = null; for (RecordedProgram program : mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) { - if (latestProgram == null || RecordedProgram - .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) { + if (latestProgram == null + || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) + < 0) { latestProgram = program; } } @@ -622,17 +641,19 @@ public class DvrBrowseFragment extends BrowseFragment implements private class SeriesAdapter extends SortedArrayAdapter { SeriesAdapter() { - super(mPresenterSelector, new Comparator() { - @Override - public int compare(SeriesRecording lhs, SeriesRecording rhs) { - if (lhs.isStopped() && !rhs.isStopped()) { - return 1; - } else if (!lhs.isStopped() && rhs.isStopped()) { - return -1; - } - return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs); - } - }); + super( + mPresenterSelector, + new Comparator() { + @Override + public int compare(SeriesRecording lhs, SeriesRecording rhs) { + if (lhs.isStopped() && !rhs.isStopped()) { + return 1; + } else if (!lhs.isStopped() && rhs.isStopped()) { + return -1; + } + return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs); + } + }); } @Override @@ -662,4 +683,4 @@ public class DvrBrowseFragment extends BrowseFragment implements } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java index 35d21db8..a953f1d2 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java @@ -19,7 +19,6 @@ package com.android.tv.dvr.ui.browse; import android.app.Activity; import android.os.Bundle; import android.support.v17.leanback.app.DetailsFragment; - import android.transition.Transition; import android.transition.Transition.TransitionListener; import android.view.View; @@ -27,13 +26,9 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dialog.PinDialogFragment; -/** - * Activity to show details view in DVR. - */ +/** Activity to show details view in DVR. */ public class DvrDetailsActivity extends Activity implements PinDialogFragment.OnPinCheckedListener { - /** - * Name of record id added to the Intent. - */ + /** Name of record id added to the Intent. */ public static final String RECORDING_ID = "record_id"; /** @@ -42,34 +37,22 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On */ public static final String HIDE_VIEW_SCHEDULE = "hide_view_schedule"; - /** - * Name of details view's type added to the intent. - */ + /** Name of details view's type added to the intent. */ public static final String DETAILS_VIEW_TYPE = "details_view_type"; - /** - * Name of shared element between activities. - */ + /** Name of shared element between activities. */ public static final String SHARED_ELEMENT_NAME = "shared_element"; - /** - * CURRENT_RECORDING_VIEW refers to Current Recordings in DVR. - */ + /** CURRENT_RECORDING_VIEW refers to Current Recordings in DVR. */ public static final int CURRENT_RECORDING_VIEW = 1; - /** - * SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR. - */ + /** SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR. */ public static final int SCHEDULED_RECORDING_VIEW = 2; - /** - * RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR. - */ + /** RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR. */ public static final int RECORDED_PROGRAM_VIEW = 3; - /** - * SERIES_RECORDING_VIEW refers to series recording in DVR. - */ + /** SERIES_RECORDING_VIEW refers to series recording in DVR. */ public static final int SERIES_RECORDING_VIEW = 4; private PinDialogFragment.OnPinCheckedListener mOnPinCheckedListener; @@ -97,8 +80,10 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On detailsFragment = new SeriesRecordingDetailsFragment(); } detailsFragment.setArguments(args); - getFragmentManager().beginTransaction() - .replace(R.id.dvr_details_view_frame, detailsFragment).commit(); + getFragmentManager() + .beginTransaction() + .replace(R.id.dvr_details_view_frame, detailsFragment) + .commit(); } // This is a workaround for the focus on O device diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java index 19fb7117..f03f3f58 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java @@ -36,7 +36,6 @@ import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import android.support.v17.leanback.widget.VerticalGridView; import android.text.TextUtils; import android.widget.Toast; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -50,7 +49,6 @@ import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.ImageLoader; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; - import java.io.File; abstract class DvrDetailsFragment extends DetailsFragment { @@ -77,8 +75,8 @@ abstract class DvrDetailsFragment extends DetailsFragment { public void onStart() { super.onStart(); // TODO: remove the workaround of b/30401180. - VerticalGridView container = (VerticalGridView) getActivity() - .findViewById(R.id.container_list); + VerticalGridView container = + (VerticalGridView) getActivity().findViewById(R.id.container_list); // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout. container.setItemAlignmentOffset(0); container.setWindowAlignmentOffset( @@ -86,27 +84,23 @@ abstract class DvrDetailsFragment extends DetailsFragment { } private void setupAdapter() { - DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter( - new DetailsContentPresenter(getActivity())); - rowPresenter.setBackgroundColor(getResources().getColor(R.color.common_tv_background, - null)); - rowPresenter.setSharedElementEnterTransition(getActivity(), - DvrDetailsActivity.SHARED_ELEMENT_NAME); + DetailsOverviewRowPresenter rowPresenter = + new DetailsOverviewRowPresenter(new DetailsContentPresenter(getActivity())); + rowPresenter.setBackgroundColor( + getResources().getColor(R.color.common_tv_background, null)); + rowPresenter.setSharedElementEnterTransition( + getActivity(), DvrDetailsActivity.SHARED_ELEMENT_NAME); rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener()); mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter)); setAdapter(mRowsAdapter); } - /** - * Returns details views' rows adapter. - */ + /** Returns details views' rows adapter. */ protected ArrayObjectAdapter getRowsAdapter() { - return mRowsAdapter; + return mRowsAdapter; } - /** - * Sets details overview. - */ + /** Sets details overview. */ protected void setDetailsOverviewRow(DetailsContent detailsContent) { mDetailsOverview = new DetailsOverviewRow(detailsContent); mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); @@ -114,9 +108,7 @@ abstract class DvrDetailsFragment extends DetailsFragment { onLoadLogoAndBackgroundImages(detailsContent); } - /** - * Creates and returns presenter selector will be used by rows adaptor. - */ + /** Creates and returns presenter selector will be used by rows adaptor. */ protected PresenterSelector onCreatePresenterSelector( DetailsOverviewRowPresenter rowPresenter) { ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); @@ -130,11 +122,9 @@ abstract class DvrDetailsFragment extends DetailsFragment { * do anything after calling {@link #onCreate(Bundle)}. If there's something subclasses have to * do after the super class did onCreate, it should override this method and put the codes here. */ - protected void onCreateInternal() { } + protected void onCreateInternal() {} - /** - * Updates actions of details overview. - */ + /** Updates actions of details overview. */ protected void updateActions() { mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); } @@ -142,14 +132,12 @@ abstract class DvrDetailsFragment extends DetailsFragment { /** * Loads recording details according to the arguments the fragment got. * - * @return false if cannot find valid recordings, else return true. If the return value - * is false, the detail activity and fragment will be ended. + * @return false if cannot find valid recordings, else return true. If the return value is + * false, the detail activity and fragment will be ended. */ abstract boolean onLoadRecordingDetails(Bundle args); - /** - * Creates actions users can interact with and their adaptor for this fragment. - */ + /** Creates actions users can interact with and their adaptor for this fragment. */ abstract SparseArrayObjectAdapter onCreateActionsAdapter(); /** @@ -158,60 +146,70 @@ abstract class DvrDetailsFragment extends DetailsFragment { */ abstract OnActionClickedListener onCreateOnActionClickedListener(); - /** - * Loads logo and background images for detail fragments. - */ + /** Loads logo and background images for detail fragments. */ protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) { Drawable logoDrawable = null; Drawable backgroundDrawable = null; if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) { - logoDrawable = getContext().getResources() - .getDrawable(R.drawable.dvr_default_poster, null); + logoDrawable = + getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null); mDetailsOverview.setImageDrawable(logoDrawable); } if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) { - backgroundDrawable = getContext().getResources() - .getDrawable(R.drawable.dvr_default_poster, null); + backgroundDrawable = + getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null); mBackgroundHelper.setBackground(backgroundDrawable); } if (logoDrawable != null && backgroundDrawable != null) { return; } - if (logoDrawable == null && backgroundDrawable == null - && detailsContent.getLogoImageUri().equals( - detailsContent.getBackgroundImageUri())) { - ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), - new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, - getContext())); + if (logoDrawable == null + && backgroundDrawable == null + && detailsContent + .getLogoImageUri() + .equals(detailsContent.getBackgroundImageUri())) { + ImageLoader.loadBitmap( + getContext(), + detailsContent.getLogoImageUri(), + new MyImageLoaderCallback( + this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, getContext())); return; } if (logoDrawable == null) { int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width); - int imageHeight = getResources() - .getDimensionPixelSize(R.dimen.dvr_details_poster_height); - ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(), - imageWidth, imageHeight, + int imageHeight = + getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_height); + ImageLoader.loadBitmap( + getContext(), + detailsContent.getLogoImageUri(), + imageWidth, + imageHeight, new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext())); } if (backgroundDrawable == null) { - ImageLoader.loadBitmap(getContext(), detailsContent.getBackgroundImageUri(), + ImageLoader.loadBitmap( + getContext(), + detailsContent.getBackgroundImageUri(), new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext())); } } protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) { - if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) && - !isDataUriAccessible(recordedProgram.getDataUri())) { + if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) + && !isDataUriAccessible(recordedProgram.getDataUri())) { // Since cleaning RecordedProgram from forgotten storage will take some time, // ignore playback until cleaning is finished. - ToastUtils.show(getContext(), + ToastUtils.show( + getContext(), getContext().getResources().getString(R.string.dvr_toast_recording_deleted), Toast.LENGTH_SHORT); return; } long programId = recordedProgram.getId(); - ParentalControlSettings parental = TvApplication.getSingletons(getActivity()) - .getTvInputManagerHelper().getParentalControlSettings(); + ParentalControlSettings parental = + TvApplication.getSingletons(getActivity()) + .getTvInputManagerHelper() + .getParentalControlSettings(); if (!parental.isParentalControlsEnabled()) { DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false); return; @@ -249,36 +247,43 @@ abstract class DvrDetailsFragment extends DetailsFragment { private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) { SoftPreconditions.checkState(getActivity() instanceof DvrDetailsActivity); if (getActivity() instanceof DvrDetailsActivity) { - ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(new OnPinCheckedListener() { - @Override - public void onPinChecked(boolean checked, int type, String rating) { - ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(null); - if (checked && type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) { - DvrUiHelper.startPlaybackActivity(getContext(), recordedProgram.getId(), - seekTimeMs, true); - } - } - }); + ((DvrDetailsActivity) getActivity()) + .setOnPinCheckListener( + new OnPinCheckedListener() { + @Override + public void onPinChecked(boolean checked, int type, String rating) { + ((DvrDetailsActivity) getActivity()) + .setOnPinCheckListener(null); + if (checked + && type + == PinDialogFragment + .PIN_DIALOG_TYPE_UNLOCK_PROGRAM) { + DvrUiHelper.startPlaybackActivity( + getContext(), + recordedProgram.getId(), + seekTimeMs, + true); + } + } + }); PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG); } } - private static class MyImageLoaderCallback extends - ImageLoader.ImageLoaderCallback { + private static class MyImageLoaderCallback + extends ImageLoader.ImageLoaderCallback { private final Context mContext; private final int mLoadType; - public MyImageLoaderCallback(DvrDetailsFragment fragment, - int loadType, Context context) { + public MyImageLoaderCallback(DvrDetailsFragment fragment, int loadType, Context context) { super(fragment); mLoadType = loadType; mContext = context; } @Override - public void onBitmapLoaded(DvrDetailsFragment fragment, - @Nullable Bitmap bitmap) { + public void onBitmapLoaded(DvrDetailsFragment fragment, @Nullable Bitmap bitmap) { Drawable drawable; int loadType = mLoadType; if (bitmap == null) { diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java index df0e61c1..4298d86a 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java @@ -23,18 +23,15 @@ import android.support.v17.leanback.widget.Presenter; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; - import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.ui.DvrUiHelper; - import java.util.HashSet; import java.util.Set; /** * An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in - * {@link DvrBrowseFragment}. DVR items might include: - * {@link com.android.tv.dvr.data.ScheduledRecording}, - * {@link com.android.tv.dvr.data.RecordedProgram}, and + * {@link DvrBrowseFragment}. DVR items might include: {@link + * com.android.tv.dvr.data.ScheduledRecording}, {@link com.android.tv.dvr.data.RecordedProgram}, and * {@link com.android.tv.dvr.data.SeriesRecording}. */ public abstract class DvrItemPresenter extends Presenter { @@ -51,9 +48,9 @@ public abstract class DvrItemPresenter extends Presenter { return (RecordingCardView) view; } - protected void onBound(T item) { } + protected void onBound(T item) {} - protected void onUnbound() { } + protected void onUnbound() {} } DvrItemPresenter(Context context) { @@ -94,9 +91,7 @@ public abstract class DvrItemPresenter extends Presenter { viewHolder.view.setOnClickListener(null); } - /** - * Unbinds all bound view holders. - */ + /** Unbinds all bound view holders. */ public void unbindAllViewHolders() { // When browse fragments are destroyed, RecyclerView would not call presenters' // onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks. @@ -105,36 +100,28 @@ public abstract class DvrItemPresenter extends Presenter { } } - /** - * This method will be called when a {@link DvrItemViewHolder} is needed to be created. - */ - abstract protected DvrItemViewHolder onCreateDvrItemViewHolder(); + /** This method will be called when a {@link DvrItemViewHolder} is needed to be created. */ + protected abstract DvrItemViewHolder onCreateDvrItemViewHolder(); - /** - * This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item. - */ - abstract protected void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item); + /** This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item. */ + protected abstract void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item); - /** - * Returns context. - */ + /** Returns context. */ protected Context getContext() { return mContext; } - /** - * Creates {@link OnClickListener} for DVR library's card views. - */ + /** Creates {@link OnClickListener} for DVR library's card views. */ protected OnClickListener onCreateOnClickListener() { return new OnClickListener() { @Override public void onClick(View view) { if (view instanceof RecordingCardView) { RecordingCardView v = (RecordingCardView) view; - DvrUiHelper.startDetailsActivity((Activity) v.getContext(), - v.getTag(), v.getImageView(), false); + DvrUiHelper.startDetailsActivity( + (Activity) v.getContext(), v.getTag(), v.getImageView(), false); } } }; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java index 37a72eaf..a2d1cb28 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java @@ -19,7 +19,6 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.support.v17.leanback.widget.ListRowPresenter; import android.view.ViewGroup; - import com.android.tv.R; /** A list row presenter to display expand/fold card views list. */ diff --git a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java index 311137a9..6def818f 100644 --- a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java +++ b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java @@ -16,14 +16,10 @@ package com.android.tv.dvr.ui.browse; -/** - * Special object for schedule preview; - */ +/** Special object for schedule preview; */ final class FullScheduleCardHolder { - /** - * Full schedule card holder. - */ + /** Full schedule card holder. */ static final FullScheduleCardHolder FULL_SCHEDULE_CARD_HOLDER = new FullScheduleCardHolder(); - private FullScheduleCardHolder() { } + private FullScheduleCardHolder() {} } diff --git a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java index 94c67eec..88133331 100644 --- a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java @@ -19,20 +19,15 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.View; -import android.view.ViewGroup; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.Utils; - import java.util.Collections; import java.util.List; -/** - * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. - */ +/** Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. */ class FullSchedulesCardPresenter extends DvrItemPresenter { private final Drawable mIconDrawable; private final String mCardTitleText; @@ -54,16 +49,26 @@ class FullSchedulesCardPresenter extends DvrItemPresenter { cardView.setTitle(mCardTitleText); cardView.setImage(mIconDrawable); - List scheduledRecordings = TvApplication.getSingletons(mContext) - .getDvrDataManager().getAvailableScheduledRecordings(); + List scheduledRecordings = + TvApplication.getSingletons(mContext) + .getDvrDataManager() + .getAvailableScheduledRecordings(); int fullDays = 0; if (!scheduledRecordings.isEmpty()) { - fullDays = Utils.computeDateDifference(System.currentTimeMillis(), - Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR) - .getStartTimeMs()) + 1; + fullDays = + Utils.computeDateDifference( + System.currentTimeMillis(), + Collections.max( + scheduledRecordings, + ScheduledRecording.START_TIME_COMPARATOR) + .getStartTimeMs()) + + 1; } - cardView.setContent(mContext.getResources().getQuantityString( - R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null); + cardView.setContent( + mContext.getResources() + .getQuantityString( + R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), + null); } @Override @@ -81,4 +86,4 @@ class FullSchedulesCardPresenter extends DvrItemPresenter { } }; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java index eb9cb26c..3b3401b2 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java @@ -22,7 +22,6 @@ import android.os.Bundle; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrDataManager; @@ -30,9 +29,7 @@ import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.data.RecordedProgram; -/** - * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR. - */ +/** {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR. */ public class RecordedProgramDetailsFragment extends DvrDetailsFragment implements DvrDataManager.RecordedProgramListener { private static final int ACTION_RESUME_PLAYING = 1; @@ -54,10 +51,10 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment @Override public void onCreateInternal() { - mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) - .getDvrWatchedPositionManager(); - setDetailsOverviewRow(DetailsContent - .createFromRecordedProgram(getContext(), mRecordedProgram)); + mDvrWatchedPositionManager = + TvApplication.getSingletons(getActivity()).getDvrWatchedPositionManager(); + setDetailsOverviewRow( + DetailsContent.createFromRecordedProgram(getContext(), mRecordedProgram)); } @Override @@ -95,20 +92,36 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment Resources res = getResources(); if (mDvrWatchedPositionManager.getWatchedStatus(mRecordedProgram) == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { - adapter.set(ACTION_RESUME_PLAYING, new Action(ACTION_RESUME_PLAYING, - res.getString(R.string.dvr_detail_resume_play), null, - res.getDrawable(R.drawable.lb_ic_play))); - adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, - res.getString(R.string.dvr_detail_play_from_beginning), null, - res.getDrawable(R.drawable.lb_ic_replay))); + adapter.set( + ACTION_RESUME_PLAYING, + new Action( + ACTION_RESUME_PLAYING, + res.getString(R.string.dvr_detail_resume_play), + null, + res.getDrawable(R.drawable.lb_ic_play))); + adapter.set( + ACTION_PLAY_FROM_BEGINNING, + new Action( + ACTION_PLAY_FROM_BEGINNING, + res.getString(R.string.dvr_detail_play_from_beginning), + null, + res.getDrawable(R.drawable.lb_ic_replay))); } else { - adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING, - res.getString(R.string.dvr_detail_watch), null, - res.getDrawable(R.drawable.lb_ic_play))); + adapter.set( + ACTION_PLAY_FROM_BEGINNING, + new Action( + ACTION_PLAY_FROM_BEGINNING, + res.getString(R.string.dvr_detail_watch), + null, + res.getDrawable(R.drawable.lb_ic_play))); } - adapter.set(ACTION_DELETE_RECORDING, new Action(ACTION_DELETE_RECORDING, - res.getString(R.string.dvr_detail_delete), null, - res.getDrawable(R.drawable.ic_delete_32dp))); + adapter.set( + ACTION_DELETE_RECORDING, + new Action( + ACTION_DELETE_RECORDING, + res.getString(R.string.dvr_detail_delete), + null, + res.getDrawable(R.drawable.ic_delete_32dp))); return adapter; } @@ -120,11 +133,13 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment if (action.getId() == ACTION_PLAY_FROM_BEGINNING) { startPlayback(mRecordedProgram, TvInputManager.TIME_SHIFT_INVALID_TIME); } else if (action.getId() == ACTION_RESUME_PLAYING) { - startPlayback(mRecordedProgram, mDvrWatchedPositionManager - .getWatchedPosition(mRecordedProgram.getId())); + startPlayback( + mRecordedProgram, + mDvrWatchedPositionManager.getWatchedPosition( + mRecordedProgram.getId())); } else if (action.getId() == ACTION_DELETE_RECORDING) { - DvrManager dvrManager = TvApplication - .getSingletons(getActivity()).getDvrManager(); + DvrManager dvrManager = + TvApplication.getSingletons(getActivity()).getDvrManager(); dvrManager.removeRecordedProgram(mRecordedProgram); getActivity().finish(); } @@ -133,10 +148,10 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment } @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { } + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {} @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { } + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {} @Override public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java index 5fe162b6..aad1cc6a 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java @@ -18,7 +18,6 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.media.tv.TvInputManager; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrWatchedPositionManager; @@ -26,9 +25,7 @@ import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListen import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.util.Utils; -/** - * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. - */ +/** Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. */ public class RecordedProgramPresenter extends DvrItemPresenter { private final DvrWatchedPositionManager mDvrWatchedPositionManager; private String mTodayString; @@ -53,10 +50,16 @@ public class RecordedProgramPresenter extends DvrItemPresenter } private void setProgressBar(long watchedPositionMs) { - ((RecordingCardView) view).setProgressBar( - (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null - : Math.min(100, (int) (100.0f * watchedPositionMs - / mProgram.getDurationMillis()))); + ((RecordingCardView) view) + .setProgressBar( + (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) + ? null + : Math.min( + 100, + (int) + (100.0f + * watchedPositionMs + / mProgram.getDurationMillis()))); } @Override @@ -86,15 +89,15 @@ public class RecordedProgramPresenter extends DvrItemPresenter } } - RecordedProgramPresenter(Context context, boolean showEpisodeTitle, - boolean expandTitleWhenFocused) { + RecordedProgramPresenter( + Context context, boolean showEpisodeTitle, boolean expandTitleWhenFocused) { super(context); mTodayString = mContext.getString(R.string.dvr_date_today); mYesterdayString = mContext.getString(R.string.dvr_date_yesterday); mDvrWatchedPositionManager = TvApplication.getSingletons(mContext).getDvrWatchedPositionManager(); - mProgressBarColor = mContext.getResources() - .getColor(R.color.play_controls_progress_bar_watched); + mProgressBarColor = + mContext.getResources().getColor(R.color.play_controls_progress_bar_watched); mShowEpisodeTitle = showEpisodeTitle; mExpandTitleWhenFocused = expandTitleWhenFocused; } @@ -114,29 +117,37 @@ public class RecordedProgramPresenter extends DvrItemPresenter final RecordedProgramViewHolder viewHolder = (RecordedProgramViewHolder) baseHolder; final RecordingCardView cardView = viewHolder.getView(); DetailsContent details = DetailsContent.createFromRecordedProgram(mContext, program); - cardView.setTitle(mShowEpisodeTitle ? - program.getEpisodeDisplayTitle(mContext) : details.getTitle()); + cardView.setTitle( + mShowEpisodeTitle ? program.getEpisodeDisplayTitle(mContext) : details.getTitle()); cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo()); cardView.setContent(generateMajorContent(program), generateMinorContent(program)); cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri()); } private String generateMajorContent(RecordedProgram program) { - int dateDifference = Utils.computeDateDifference(program.getStartTimeUtcMillis(), - System.currentTimeMillis()); + int dateDifference = + Utils.computeDateDifference( + program.getStartTimeUtcMillis(), System.currentTimeMillis()); if (dateDifference == 0) { return mTodayString; } else if (dateDifference == 1) { return mYesterdayString; } else { - return Utils.getDurationString(mContext, program.getStartTimeUtcMillis(), - program.getStartTimeUtcMillis(), false, true, false, 0); + return Utils.getDurationString( + mContext, + program.getStartTimeUtcMillis(), + program.getStartTimeUtcMillis(), + false, + true, + false, + 0); } } private String generateMinorContent(RecordedProgram program) { int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis())); - return mContext.getResources().getQuantityString( - R.plurals.dvr_program_duration, durationMinutes, durationMinutes); + return mContext.getResources() + .getQuantityString( + R.plurals.dvr_program_duration, durationMinutes, durationMinutes); } } diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java index 767addc8..edee5d3a 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java +++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java @@ -31,20 +31,19 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; 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; /** - * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} - * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. + * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} or + * {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. */ public class RecordingCardView extends BaseCardView { // This value should be the same with // android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS - private final static int ANIMATION_DURATION = 150; + private static final int ANIMATION_DURATION = 150; private final ImageView mImageView; private final int mImageWidth; private final int mImageHeight; @@ -70,16 +69,19 @@ public class RecordingCardView extends BaseCardView { } public RecordingCardView(Context context, boolean expandTitleWhenFocused) { - this(context, context.getResources().getDimensionPixelSize( - R.dimen.dvr_library_card_image_layout_width), context.getResources() - .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height), + this( + context, + context.getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_width), + context.getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height), expandTitleWhenFocused); } - public RecordingCardView(Context context, int imageWidth, int imageHeight, - boolean expandTitleWhenFocused) { + public RecordingCardView( + Context context, int imageWidth, int imageHeight, boolean expandTitleWhenFocused) { super(context); - //TODO(dvr): move these to the layout XML. + // TODO(dvr): move these to the layout XML. setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA); setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS); setFocusable(true); @@ -99,21 +101,27 @@ public class RecordingCardView extends BaseCardView { mTitleArea = (FrameLayout) findViewById(R.id.title_area); mFoldedTitleView = (TextView) findViewById(R.id.title_one_line); mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines); - mFoldedTitleHeight = getResources() - .getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height); - mExpandedTitleHeight = getResources() - .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height); + mFoldedTitleHeight = + getResources().getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height); + mExpandedTitleHeight = + getResources() + .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height); mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION); - mExpandTitleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float value = (Float) valueAnimator.getAnimatedValue(); - mExpandedTitleView.setAlpha(value); - mFoldedTitleView.setAlpha(1.0f - value); - ViewUtils.setLayoutHeight(mTitleArea, (int) (mFoldedTitleHeight - + (mExpandedTitleHeight - mFoldedTitleHeight) * value)); - } - }); + mExpandTitleAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + float value = (Float) valueAnimator.getAnimatedValue(); + mExpandedTitleView.setAlpha(value); + mFoldedTitleView.setAlpha(1.0f - value); + ViewUtils.setLayoutHeight( + mTitleArea, + (int) + (mFoldedTitleHeight + + (mExpandedTitleHeight - mFoldedTitleHeight) + * value)); + } + }); mExpandTitleWhenFocused = expandTitleWhenFocused; } @@ -124,8 +132,12 @@ public class RecordingCardView extends BaseCardView { // loading and drawing background images during activity transitions. if (gainFocus) { if (!TextUtils.isEmpty(mDetailBackgroundImageUri)) { - ImageLoader.loadBitmap(getContext(), mDetailBackgroundImageUri, - Integer.MAX_VALUE, Integer.MAX_VALUE, null); + ImageLoader.loadBitmap( + getContext(), + mDetailBackgroundImageUri, + Integer.MAX_VALUE, + Integer.MAX_VALUE, + null); } } if (mExpandTitleWhenFocused) { @@ -186,9 +198,7 @@ public class RecordingCardView extends BaseCardView { } } - /** - * Sets progress bar. If progress is {@code null}, hides progress bar. - */ + /** Sets progress bar. If progress is {@code null}, hides progress bar. */ void setProgressBar(Integer progress) { if (progress == null) { mProgressBar.setVisibility(View.GONE); @@ -198,16 +208,14 @@ public class RecordingCardView extends BaseCardView { } } - /** - * Sets the color of progress bar. - */ + /** Sets the color of progress bar. */ void setProgressBarColor(int color) { mProgressBar.getProgressDrawable().setTint(color); } /** * Sets the image URI of the poster should be shown on the card view. - + * * @param isChannelLogo {@code true} if the image is from channels' logo. */ void setImageUri(String uri, boolean isChannelLogo) { @@ -220,14 +228,16 @@ public class RecordingCardView extends BaseCardView { if (TextUtils.isEmpty(uri)) { mImageView.setImageDrawable(mDefaultImage); } else { - ImageLoader.loadBitmap(getContext(), uri, mImageWidth, mImageHeight, + ImageLoader.loadBitmap( + getContext(), + uri, + mImageWidth, + mImageHeight, new RecordingCardImageLoaderCallback(this, uri)); } } - /** - * Sets the {@link Drawable} of the poster should be shown on the card view. - */ + /** Sets the {@link Drawable} of the poster should be shown on the card view. */ public void setImage(Drawable image) { if (image != null) { mImageView.setImageDrawable(image); @@ -255,9 +265,7 @@ public class RecordingCardView extends BaseCardView { mDetailBackgroundImageUri = uri; } - /** - * Returns image view. - */ + /** Returns image view. */ public ImageView getImageView() { return mImageView; } @@ -287,4 +295,4 @@ public class RecordingCardView extends BaseCardView { setContent(null, null); mImageView.setImageDrawable(mDefaultImage); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java index 56ec357f..c8f1c785 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java @@ -18,34 +18,31 @@ package com.android.tv.dvr.ui.browse; import android.os.Bundle; import android.support.v17.leanback.app.DetailsFragment; - import com.android.tv.TvApplication; import com.android.tv.dvr.data.ScheduledRecording; -/** - * {@link DetailsFragment} for recordings in DVR. - */ +/** {@link DetailsFragment} for recordings in DVR. */ abstract class RecordingDetailsFragment extends DvrDetailsFragment { private ScheduledRecording mRecording; @Override protected void onCreateInternal() { - setDetailsOverviewRow(DetailsContent - .createFromScheduledRecording(getContext(), mRecording)); + setDetailsOverviewRow( + DetailsContent.createFromScheduledRecording(getContext(), mRecording)); } @Override protected boolean onLoadRecordingDetails(Bundle args) { long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID); - mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager() - .getScheduledRecording(scheduledRecordingId); + mRecording = + TvApplication.getSingletons(getContext()) + .getDvrDataManager() + .getScheduledRecording(scheduledRecordingId); return mRecording != null; } - /** - * Returns {@link ScheduledRecording} for the current fragment. - */ + /** Returns {@link ScheduledRecording} for the current fragment. */ public ScheduledRecording getRecording() { return mRecording; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java index 958f8bf8..b3e6ebb3 100644 --- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java @@ -21,16 +21,12 @@ import android.os.Bundle; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.ui.DvrUiHelper; -/** - * {@link RecordingDetailsFragment} for scheduled recording in DVR. - */ +/** {@link RecordingDetailsFragment} for scheduled recording in DVR. */ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment { private static final int ACTION_VIEW_SCHEDULE = 1; private static final int ACTION_CANCEL = 2; @@ -60,14 +56,21 @@ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment new SparseArrayObjectAdapter(new ActionPresenterSelector()); Resources res = getResources(); if (!mHideViewSchedule) { - mScheduleAction = new Action(ACTION_VIEW_SCHEDULE, - res.getString(R.string.dvr_detail_view_schedule), null, - res.getDrawable(getScheduleIconId())); + mScheduleAction = + new Action( + ACTION_VIEW_SCHEDULE, + res.getString(R.string.dvr_detail_view_schedule), + null, + res.getDrawable(getScheduleIconId())); adapter.set(ACTION_VIEW_SCHEDULE, mScheduleAction); } - adapter.set(ACTION_CANCEL, new Action(ACTION_CANCEL, - res.getString(R.string.dvr_detail_cancel_recording), null, - res.getDrawable(R.drawable.ic_dvr_cancel_32dp))); + adapter.set( + ACTION_CANCEL, + new Action( + ACTION_CANCEL, + res.getString(R.string.dvr_detail_cancel_recording), + null, + res.getDrawable(R.drawable.ic_dvr_cancel_32dp))); return adapter; } diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java index 273d3d19..fa948447 100644 --- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java @@ -18,18 +18,14 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.os.Handler; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Utils; - import java.util.concurrent.TimeUnit; -/** - * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. - */ +/** Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. */ class ScheduledRecordingPresenter extends DvrItemPresenter { private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5); @@ -39,13 +35,14 @@ class ScheduledRecordingPresenter extends DvrItemPresenter { private final class ScheduledRecordingViewHolder extends DvrItemViewHolder { private final Handler mHandler = new Handler(); private ScheduledRecording mScheduledRecording; - private final Runnable mProgressBarUpdater = new Runnable() { - @Override - public void run() { - updateProgressBar(); - mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS); - } - }; + private final Runnable mProgressBarUpdater = + new Runnable() { + @Override + public void run() { + updateProgressBar(); + mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS); + } + }; ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) { super(view); @@ -73,9 +70,17 @@ class ScheduledRecordingPresenter extends DvrItemPresenter { int recordingState = mScheduledRecording.getState(); RecordingCardView cardView = (RecordingCardView) view; if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { - cardView.setProgressBar(Math.max(0, Math.min((int) (100 * - (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs()) - / mScheduledRecording.getDuration()), 100))); + cardView.setProgressBar( + Math.max( + 0, + Math.min( + (int) + (100 + * (System.currentTimeMillis() + - mScheduledRecording + .getStartTimeMs()) + / mScheduledRecording.getDuration()), + 100))); } else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) { cardView.setProgressBar(100); } else { @@ -96,8 +101,9 @@ class ScheduledRecordingPresenter extends DvrItemPresenter { public ScheduledRecordingPresenter(Context context) { super(context); mDvrManager = TvApplication.getSingletons(mContext).getDvrManager(); - mProgressBarColor = mContext.getResources() - .getColor(R.color.play_controls_recording_icon_color_on_focus); + mProgressBarColor = + mContext.getResources() + .getColor(R.color.play_controls_recording_icon_color_on_focus); } @Override @@ -106,33 +112,53 @@ class ScheduledRecordingPresenter extends DvrItemPresenter { } @Override - public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, - ScheduledRecording recording) { + public void onBindDvrItemViewHolder( + DvrItemViewHolder baseHolder, ScheduledRecording recording) { final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder; final RecordingCardView cardView = viewHolder.getView(); DetailsContent details = DetailsContent.createFromScheduledRecording(mContext, recording); cardView.setTitle(details.getTitle()); cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo()); - cardView.setAffiliatedIcon(mDvrManager.isConflicting(recording) ? - R.drawable.ic_warning_white_32dp : 0); + cardView.setAffiliatedIcon( + mDvrManager.isConflicting(recording) ? R.drawable.ic_warning_white_32dp : 0); cardView.setContent(generateMajorContent(recording), null); cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri()); } private String generateMajorContent(ScheduledRecording recording) { - int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(), - recording.getStartTimeMs()); + int dateDifference = + Utils.computeDateDifference(System.currentTimeMillis(), recording.getStartTimeMs()); if (dateDifference <= 0) { - return mContext.getString(R.string.dvr_date_today_time, - Utils.getDurationString(mContext, recording.getStartTimeMs(), - recording.getEndTimeMs(), false, false, true, 0)); + return mContext.getString( + R.string.dvr_date_today_time, + Utils.getDurationString( + mContext, + recording.getStartTimeMs(), + recording.getEndTimeMs(), + false, + false, + true, + 0)); } else if (dateDifference == 1) { - return mContext.getString(R.string.dvr_date_tomorrow_time, - Utils.getDurationString(mContext, recording.getStartTimeMs(), - recording.getEndTimeMs(), false, false, true, 0)); + return mContext.getString( + R.string.dvr_date_tomorrow_time, + Utils.getDurationString( + mContext, + recording.getStartTimeMs(), + recording.getEndTimeMs(), + false, + false, + true, + 0)); } else { - return Utils.getDurationString(mContext, recording.getStartTimeMs(), - recording.getStartTimeMs(), false, true, false, 0); + return Utils.getDurationString( + mContext, + recording.getStartTimeMs(), + recording.getStartTimeMs(), + false, + true, + false, + 0); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java index c2aa8e98..48bc9cbd 100644 --- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java @@ -32,7 +32,6 @@ import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.PresenterSelector; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.BaseProgram; @@ -42,16 +41,13 @@ import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.dvr.ui.SortedArrayAdapter; - import java.util.Collections; import java.util.Comparator; import java.util.List; -/** - * {@link DetailsFragment} for series recording in DVR. - */ -public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implements - DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener { +/** {@link DetailsFragment} for series recording in DVR. */ +public class SeriesRecordingDetailsFragment extends DvrDetailsFragment + implements DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener { private static final int ACTION_WATCH = 1; private static final int ACTION_SERIES_SCHEDULES = 2; private static final int ACTION_DELETE = 3; @@ -87,8 +83,8 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement @Override protected void onCreateInternal() { - mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) - .getDvrWatchedPositionManager(); + mDvrWatchedPositionManager = + TvApplication.getSingletons(getActivity()).getDvrWatchedPositionManager(); setDetailsOverviewRow(DetailsContent.createFromSeriesRecording(getContext(), mSeries)); setupRecordedProgramsRow(); mDvrDataManager.addSeriesRecordingListener(this); @@ -119,27 +115,31 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement mActionsAdapter.clear(ACTION_WATCH); } else { String episodeStatus; - if(mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram) + if (mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram) == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { episodeStatus = mResumeLabel; - mInitialPlaybackPositionMs = mDvrWatchedPositionManager - .getWatchedPosition(mRecommendRecordedProgram.getId()); + mInitialPlaybackPositionMs = + mDvrWatchedPositionManager.getWatchedPosition( + mRecommendRecordedProgram.getId()); } else { episodeStatus = mWatchLabel; mInitialPlaybackPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; } - String episodeDisplayNumber = mRecommendRecordedProgram.getEpisodeDisplayNumber( - getContext()); - mActionsAdapter.set(ACTION_WATCH, new Action(ACTION_WATCH, - episodeStatus, episodeDisplayNumber, mWatchDrawable)); + String episodeDisplayNumber = + mRecommendRecordedProgram.getEpisodeDisplayNumber(getContext()); + mActionsAdapter.set( + ACTION_WATCH, + new Action(ACTION_WATCH, episodeStatus, episodeDisplayNumber, mWatchDrawable)); } } @Override protected boolean onLoadRecordingDetails(Bundle args) { long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID); - mSeries = TvApplication.getSingletons(getActivity()).getDvrDataManager() - .getSeriesRecording(recordId); + mSeries = + TvApplication.getSingletons(getActivity()) + .getDvrDataManager() + .getSeriesRecording(recordId); if (mSeries == null) { return false; } @@ -162,12 +162,19 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); Resources res = getResources(); updateWatchAction(); - mActionsAdapter.set(ACTION_SERIES_SCHEDULES, new Action(ACTION_SERIES_SCHEDULES, - getString(R.string.dvr_detail_view_schedule), null, - res.getDrawable(R.drawable.ic_schedule_32dp, null))); - mDeleteAction = new Action(ACTION_DELETE, - getString(R.string.dvr_detail_series_delete), null, - res.getDrawable(R.drawable.ic_delete_32dp, null)); + mActionsAdapter.set( + ACTION_SERIES_SCHEDULES, + new Action( + ACTION_SERIES_SCHEDULES, + getString(R.string.dvr_detail_view_schedule), + null, + res.getDrawable(R.drawable.ic_schedule_32dp, null))); + mDeleteAction = + new Action( + ACTION_DELETE, + getString(R.string.dvr_detail_series_delete), + null, + res.getDrawable(R.drawable.ic_delete_32dp, null)); if (!mRecordedPrograms.isEmpty()) { mActionsAdapter.set(ACTION_DELETE, mDeleteAction); } @@ -207,11 +214,9 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement }; } - /** - * The programs are sorted by season number and episode number. - */ + /** The programs are sorted by season number and episode number. */ private RecordedProgram getRecommendProgram(List programs) { - for (int i = programs.size() - 1 ; i >= 0 ; i--) { + for (int i = programs.size() - 1; i >= 0; i--) { RecordedProgram program = programs.get(i); int watchedStatus = mDvrWatchedPositionManager.getWatchedStatus(program); if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_NEW) { @@ -230,7 +235,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement } @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {} @Override public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { @@ -308,8 +313,10 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement for (int i = rowsAdaptor.size() - 1; i >= 0; i--) { Object row = rowsAdaptor.get(i); if (row instanceof ListRow) { - int compareResult = BaseProgram.numberCompare(seasonNumber, - ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber); + int compareResult = + BaseProgram.numberCompare( + seasonNumber, + ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber); if (compareResult == 0) { return (ListRow) row; } else if (compareResult < 0) { @@ -321,18 +328,25 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement } private ListRow createNewSeasonRow(String seasonNumber, int position) { - String seasonTitle = seasonNumber.isEmpty() ? mSeries.getTitle() - : getString(R.string.dvr_detail_series_season_title, seasonNumber); + String seasonTitle = + seasonNumber.isEmpty() + ? mSeries.getTitle() + : getString(R.string.dvr_detail_series_season_title, seasonNumber); HeaderItem header = new HeaderItem(mSeasonRowCount++, seasonTitle); ClassPresenterSelector selector = new ClassPresenterSelector(); selector.addClassPresenter(RecordedProgram.class, mRecordedProgramPresenter); - ListRow row = new ListRow(header, new SeasonRowAdapter(selector, - new Comparator() { - @Override - public int compare(RecordedProgram lhs, RecordedProgram rhs) { - return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs); - } - }, seasonNumber)); + ListRow row = + new ListRow( + header, + new SeasonRowAdapter( + selector, + new Comparator() { + @Override + public int compare(RecordedProgram lhs, RecordedProgram rhs) { + return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs); + } + }, + seasonNumber)); getRowsAdapter().add(position, row); return row; } @@ -340,7 +354,9 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement private class SeasonRowAdapter extends SortedArrayAdapter { private String mSeasonNumber; - SeasonRowAdapter(PresenterSelector selector, Comparator comparator, + SeasonRowAdapter( + PresenterSelector selector, + Comparator comparator, String seasonNumber) { super(selector, comparator); mSeasonNumber = seasonNumber; @@ -351,4 +367,4 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement return program.getId(); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java index e508259d..02ce24ef 100644 --- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java @@ -19,7 +19,6 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.media.tv.TvInputManager; import android.text.TextUtils; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -32,27 +31,29 @@ import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListen import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; - import java.util.List; -/** - * Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}. - */ +/** Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}. */ class SeriesRecordingPresenter extends DvrItemPresenter { private final DvrDataManager mDvrDataManager; private final DvrManager mDvrManager; private final DvrWatchedPositionManager mWatchedPositionManager; - private final class SeriesRecordingViewHolder extends DvrItemViewHolder implements - WatchedPositionChangedListener, ScheduledRecordingListener, RecordedProgramListener { + private final class SeriesRecordingViewHolder extends DvrItemViewHolder + implements WatchedPositionChangedListener, + ScheduledRecordingListener, + RecordedProgramListener { private SeriesRecording mSeriesRecording; private RecordingCardView mCardView; private DvrDataManager mDvrDataManager; private DvrManager mDvrManager; private DvrWatchedPositionManager mWatchedPositionManager; - SeriesRecordingViewHolder(RecordingCardView view, DvrDataManager dvrDataManager, - DvrManager dvrManager, DvrWatchedPositionManager watchedPositionManager) { + SeriesRecordingViewHolder( + RecordingCardView view, + DvrDataManager dvrDataManager, + DvrManager dvrManager, + DvrWatchedPositionManager watchedPositionManager) { super(view); mCardView = view; mDvrDataManager = dvrDataManager; @@ -92,8 +93,8 @@ class SeriesRecordingPresenter extends DvrItemPresenter { public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { boolean needToUpdateCardView = false; for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), - mSeriesRecording.getSeriesId())) { + if (TextUtils.equals( + recordedProgram.getSeriesId(), mSeriesRecording.getSeriesId())) { mDvrDataManager.removeScheduledRecordingListener(this); mWatchedPositionManager.addListener(this, recordedProgram.getId()); needToUpdateCardView = true; @@ -108,8 +109,8 @@ class SeriesRecordingPresenter extends DvrItemPresenter { public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { boolean needToUpdateCardView = false; for (RecordedProgram recordedProgram : recordedPrograms) { - if (TextUtils.equals(recordedProgram.getSeriesId(), - mSeriesRecording.getSeriesId())) { + if (TextUtils.equals( + recordedProgram.getSeriesId(), mSeriesRecording.getSeriesId())) { if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId()) == TvInputManager.TIME_SHIFT_INVALID_TIME) { mWatchedPositionManager.removeListener(this, recordedProgram.getId()); @@ -177,8 +178,9 @@ class SeriesRecordingPresenter extends DvrItemPresenter { quantityStringID = R.plurals.dvr_count_new_recordings; } } - mCardView.setContent(mCardView.getResources() - .getQuantityString(quantityStringID, count, count), null); + mCardView.setContent( + mCardView.getResources().getQuantityString(quantityStringID, count, count), + null); } } @@ -192,8 +194,11 @@ class SeriesRecordingPresenter extends DvrItemPresenter { @Override public DvrItemViewHolder onCreateDvrItemViewHolder() { - return new SeriesRecordingViewHolder(new RecordingCardView(mContext), mDvrDataManager, - mDvrManager, mWatchedPositionManager); + return new SeriesRecordingViewHolder( + new RecordingCardView(mContext), + mDvrDataManager, + mDvrManager, + mWatchedPositionManager); } @Override diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java index b9407b15..42c7086a 100644 --- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java @@ -1,18 +1,18 @@ /* -* Copyright (C) 2016 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License -*/ + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ package com.android.tv.dvr.ui.list; @@ -23,7 +23,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -31,15 +30,11 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; -/** - * A base fragment to show the list of schedule recordings. - */ +/** A base fragment to show the list of schedule recordings. */ public abstract class BaseDvrSchedulesFragment extends DetailsFragment implements DvrDataManager.ScheduledRecordingListener, - DvrScheduleManager.OnConflictStateChangeListener { - /** - * The key for scheduled recording which has be selected in the list. - */ + DvrScheduleManager.OnConflictStateChangeListener { + /** The key for scheduled recording which has be selected in the list. */ public static final String SCHEDULES_KEY_SCHEDULED_RECORDING = "schedules_key_scheduled_recording"; @@ -62,8 +57,8 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); int firstItemPosition = getFirstItemPosition(); if (firstItemPosition != -1) { @@ -72,16 +67,12 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment return view; } - /** - * Returns rows adapter. - */ + /** Returns rows adapter. */ protected ScheduleRowAdapter getRowsAdapter() { return mRowsAdapter; } - /** - * Shows the empty message. - */ + /** Shows the empty message. */ void showEmptyMessage(int messageId) { mEmptyInfoScreenView.setText(messageId); if (mEmptyInfoScreenView.getVisibility() != View.VISIBLE) { @@ -89,9 +80,7 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment } } - /** - * Hides the empty message. - */ + /** Hides the empty message. */ void hideEmptyMessage() { if (mEmptyInfoScreenView.getVisibility() == View.VISIBLE) { mEmptyInfoScreenView.setVisibility(View.GONE); @@ -99,8 +88,8 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment } @Override - public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent, - Bundle savedInstanceState) { + public View onInflateTitleView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { // Workaround of b/31046014 return null; } @@ -114,24 +103,16 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment super.onDestroy(); } - /** - * Creates header row presenter. - */ + /** Creates header row presenter. */ public abstract SchedulesHeaderRowPresenter onCreateHeaderRowPresenter(); - /** - * Creates rows presenter. - */ + /** Creates rows presenter. */ public abstract ScheduleRowPresenter onCreateRowPresenter(); - /** - * Creates rows adapter. - */ + /** Creates rows adapter. */ public abstract ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelecor); - /** - * Gets the first focus position in schedules list. - */ + /** Gets the first focus position in schedules list. */ protected int getFirstItemPosition() { for (int i = 0; i < mRowsAdapter.size(); i++) { if (mRowsAdapter.get(i) instanceof ScheduleRow) { @@ -176,4 +157,4 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java index a0410bb3..11df780c 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.ProgressDialog; import android.os.Bundle; import android.support.annotation.IntDef; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.Program; @@ -28,15 +27,12 @@ import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.EpisodicProgramLoadTask; import com.android.tv.dvr.recorder.SeriesRecordingScheduler; import com.android.tv.dvr.ui.BigArguments; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; -/** - * Activity to show the list of recording schedules. - */ +/** Activity to show the list of recording schedules. */ public class DvrSchedulesActivity extends Activity { /** * The key for the type of the schedules which will be listed in the list. The type of the value @@ -47,9 +43,7 @@ public class DvrSchedulesActivity extends Activity { @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_FULL_SCHEDULE, TYPE_SERIES_SCHEDULE}) public @interface ScheduleListType {} - /** - * A type which means the activity will display the full scheduled recordings. - */ + /** A type which means the activity will display the full scheduled recordings. */ public static final int TYPE_FULL_SCHEDULE = 0; /** * A type which means the activity will display a scheduled recording list of a series @@ -67,20 +61,29 @@ public class DvrSchedulesActivity extends Activity { if (scheduleType == TYPE_FULL_SCHEDULE) { DvrSchedulesFragment schedulesFragment = new DvrSchedulesFragment(); schedulesFragment.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction().add( - R.id.fragment_container, schedulesFragment).commit(); + getFragmentManager() + .beginTransaction() + .add(R.id.fragment_container, schedulesFragment) + .commit(); } else if (scheduleType == TYPE_SERIES_SCHEDULE) { - if (BigArguments.getArgument(DvrSeriesSchedulesFragment - .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) != null) { + if (BigArguments.getArgument( + DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) + != null) { // The programs will be passed to the DvrSeriesSchedulesFragment, so don't need // to reset the BigArguments. showDvrSeriesSchedulesFragment(getIntent().getExtras()); } else { - final ProgressDialog dialog = ProgressDialog.show(this, null, getString( - R.string.dvr_series_progress_message_reading_programs)); - SeriesRecording seriesRecording = getIntent().getExtras() - .getParcelable(DvrSeriesSchedulesFragment - .SERIES_SCHEDULES_KEY_SERIES_RECORDING); + final ProgressDialog dialog = + ProgressDialog.show( + this, + null, + getString(R.string.dvr_series_progress_message_reading_programs)); + SeriesRecording seriesRecording = + getIntent() + .getExtras() + .getParcelable( + DvrSeriesSchedulesFragment + .SERIES_SCHEDULES_KEY_SERIES_RECORDING); // To get programs faster, hold the update of the series schedules. SeriesRecordingScheduler.getInstance(this).pauseUpdate(); new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) { @@ -110,7 +113,9 @@ public class DvrSchedulesActivity extends Activity { private void showDvrSeriesSchedulesFragment(Bundle args) { DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment(); schedulesFragment.setArguments(args); - getFragmentManager().beginTransaction().add( - R.id.fragment_container, schedulesFragment).commit(); + getFragmentManager() + .beginTransaction() + .add(R.id.fragment_container, schedulesFragment) + .commit(); } } diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java index c906c62a..c86721e8 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java @@ -23,12 +23,9 @@ import android.graphics.RectF; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; - import com.android.tv.R; -/** - * A view used for focus in schedules list. - */ +/** A view used for focus in schedules list. */ public class DvrSchedulesFocusView extends View { private final Paint mPaint; private final RectF mRoundRectF = new RectF(); @@ -76,13 +73,11 @@ public class DvrSchedulesFocusView extends View { private int getRoundRectRadius() { if (TextUtils.equals(mViewTag, mHeaderFocusViewTag)) { - return getResources().getDimensionPixelSize( - R.dimen.dvr_schedules_header_selector_radius); + return getResources() + .getDimensionPixelSize(R.dimen.dvr_schedules_header_selector_radius); } else if (TextUtils.equals(mViewTag, mItemFocusViewTag)) { return getResources().getDimensionPixelSize(R.dimen.dvr_schedules_selector_radius); } return 0; } } - - diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java index 3cbb500a..e5290601 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java @@ -18,14 +18,11 @@ package com.android.tv.dvr.ui.list; import android.os.Bundle; import android.support.v17.leanback.widget.ClassPresenterSelector; - import com.android.tv.R; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter; -/** - * A fragment to show the list of schedule recordings. - */ +/** A fragment to show the list of schedule recordings. */ public class DvrSchedulesFragment extends BaseDvrSchedulesFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -73,11 +70,11 @@ public class DvrSchedulesFragment extends BaseDvrSchedulesFragment { if (args != null) { recording = args.getParcelable(SCHEDULES_KEY_SCHEDULED_RECORDING); } - final int selectedPostion = getRowsAdapter().indexOf( - getRowsAdapter().findRowByScheduledRecording(recording)); + final int selectedPostion = + getRowsAdapter().indexOf(getRowsAdapter().findRowByScheduledRecording(recording)); if (selectedPostion != -1) { return selectedPostion; } return super.getFirstItemPosition(); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java index 57e7a88f..6ec2e152 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java @@ -1,18 +1,18 @@ /* -* Copyright (C) 2016 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License -*/ + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ package com.android.tv.dvr.ui.list; @@ -30,7 +30,6 @@ import android.transition.Fade; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -41,25 +40,21 @@ import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.EpisodicProgramLoadTask; import com.android.tv.dvr.ui.BigArguments; - import java.util.Collections; import java.util.List; -/** - * A fragment to show the list of series schedule recordings. - */ +/** A fragment to show the list of series schedule recordings. */ @TargetApi(Build.VERSION_CODES.N) public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { /** - * The key for series recording whose scheduled recording list will be displayed. - * Type: {@link SeriesRecording} + * The key for series recording whose scheduled recording list will be displayed. Type: {@link + * SeriesRecording} */ public static final String SERIES_SCHEDULES_KEY_SERIES_RECORDING = "series_schedules_key_series_recording"; /** - * The key for programs which belong to the series recording whose scheduled recording list - * will be displayed. - * Type: List<{@link Program}> + * The key for programs which belong to the series recording whose scheduled recording list will + * be displayed. Type: List<{@link Program}> */ public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS = "series_schedules_key_series_programs"; @@ -73,7 +68,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() { @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {} @Override public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { @@ -101,26 +96,28 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final ContentObserver mContentObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - executeProgramLoadingTask(); - } - }; + private final ContentObserver mContentObserver = + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + executeProgramLoadingTask(); + } + }; - private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { } + private final ChannelDataManager.Listener mChannelListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() {} - @Override - public void onChannelListUpdated() { - executeProgramLoadingTask(); - } + @Override + public void onChannelListUpdated() { + executeProgramLoadingTask(); + } - @Override - public void onChannelBrowsableChanged() { } - }; + @Override + public void onChannelBrowsableChanged() {} + }; public DvrSeriesSchedulesFragment() { setEnterTransition(new Fade(Fade.IN)); @@ -132,8 +129,8 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { Bundle args = getArguments(); if (args != null) { mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING); - mPrograms = (List) BigArguments.getArgument( - SERIES_SCHEDULES_KEY_SERIES_PROGRAMS); + mPrograms = + (List) BigArguments.getArgument(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS); BigArguments.reset(); } if (args == null || mPrograms == null) { @@ -149,13 +146,14 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { mChannelDataManager.addListener(mChannelListener); mDvrDataManager = singletons.getDvrDataManager(); mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener); - getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, - mContentObserver); + getContext() + .getContentResolver() + .registerContentObserver(Programs.CONTENT_URI, true, mContentObserver); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { onProgramsUpdated(); return super.onCreateView(inflater, container, savedInstanceState); } @@ -218,14 +216,16 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { if (mProgramLoadTask != null) { mProgramLoadTask.cancel(true); } - mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) { - @Override - protected void onPostExecute(List programs) { - mPrograms = programs == null ? Collections.EMPTY_LIST : programs; - onProgramsUpdated(); - } - }; - mProgramLoadTask.setLoadCurrentProgram(true) + mProgramLoadTask = + new EpisodicProgramLoadTask(getContext(), mSeriesRecording) { + @Override + protected void onPostExecute(List programs) { + mPrograms = programs == null ? Collections.EMPTY_LIST : programs; + onProgramsUpdated(); + } + }; + mProgramLoadTask + .setLoadCurrentProgram(true) .setLoadDisallowedProgram(true) .setLoadScheduledEpisode(true) .setIgnoreChannelOption(true) diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java index 2af832ec..d5808412 100644 --- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java +++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java @@ -17,29 +17,27 @@ package com.android.tv.dvr.ui.list; import android.content.Context; - import com.android.tv.data.Program; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.Builder; import com.android.tv.dvr.ui.DvrUiHelper; -/** - * A class for the episodic program. - */ +/** A class for the episodic program. */ class EpisodicProgramRow extends ScheduleRow { private final String mInputId; private final Program mProgram; - public EpisodicProgramRow(String inputId, Program program, ScheduledRecording recording, + public EpisodicProgramRow( + String inputId, + Program program, + ScheduledRecording recording, SchedulesHeaderRow headerRow) { super(recording, headerRow); mInputId = inputId; mProgram = program; } - /** - * Returns the program. - */ + /** Returns the program. */ public Program getProgram() { return mProgram; } @@ -82,9 +80,6 @@ class EpisodicProgramRow extends ScheduleRow { @Override public String toString() { - return super.toString() - + "(inputId=" + mInputId - + ",program=" + mProgram - + ")"; + return super.toString() + "(inputId=" + mInputId + ",program=" + mProgram + ")"; } } diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java index 91ba393a..f54c4203 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java @@ -18,14 +18,11 @@ package com.android.tv.dvr.ui.list; import android.content.Context; import android.support.annotation.Nullable; - import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; -/** - * A class for schedule recording row. - */ +/** A class for schedule recording row. */ class ScheduleRow { private final SchedulesHeaderRow mHeaderRow; @Nullable private ScheduledRecording mSchedule; @@ -37,113 +34,83 @@ class ScheduleRow { mHeaderRow = headerRow; } - /** - * Gets which {@link SchedulesHeaderRow} this schedule row belongs to. - */ + /** Gets which {@link SchedulesHeaderRow} this schedule row belongs to. */ public SchedulesHeaderRow getHeaderRow() { return mHeaderRow; } - /** - * Returns the recording schedule. - */ + /** Returns the recording schedule. */ @Nullable public ScheduledRecording getSchedule() { return mSchedule; } - /** - * Checks if the stop recording has been requested or not. - */ + /** Checks if the stop recording has been requested or not. */ public boolean isStopRecordingRequested() { return mStopRecordingRequested; } - /** - * Sets the flag of stop recording request. - */ + /** Sets the flag of stop recording request. */ public void setStopRecordingRequested(boolean stopRecordingRequested) { SoftPreconditions.checkState(!mStartRecordingRequested); mStopRecordingRequested = stopRecordingRequested; } - /** - * Checks if the start recording has been requested or not. - */ + /** Checks if the start recording has been requested or not. */ public boolean isStartRecordingRequested() { return mStartRecordingRequested; } - /** - * Sets the flag of start recording request. - */ + /** Sets the flag of start recording request. */ public void setStartRecordingRequested(boolean startRecordingRequested) { SoftPreconditions.checkState(!mStopRecordingRequested); mStartRecordingRequested = startRecordingRequested; } - /** - * Sets the recording schedule. - */ + /** Sets the recording schedule. */ public void setSchedule(@Nullable ScheduledRecording schedule) { mSchedule = schedule; } - /** - * Returns the channel ID. - */ + /** Returns the channel ID. */ public long getChannelId() { return mSchedule != null ? mSchedule.getChannelId() : -1; } - /** - * Returns the start time. - */ + /** Returns the start time. */ public long getStartTimeMs() { return mSchedule != null ? mSchedule.getStartTimeMs() : -1; } - /** - * Returns the end time. - */ + /** Returns the end time. */ public long getEndTimeMs() { return mSchedule != null ? mSchedule.getEndTimeMs() : -1; } - /** - * Returns the duration. - */ + /** Returns the duration. */ public final long getDuration() { return getEndTimeMs() - getStartTimeMs(); } - /** - * Checks if the program is on air. - */ + /** Checks if the program is on air. */ public final boolean isOnAir() { long currentTimeMs = System.currentTimeMillis(); return getStartTimeMs() <= currentTimeMs && getEndTimeMs() > currentTimeMs; } - /** - * Checks if the schedule is not started. - */ + /** Checks if the schedule is not started. */ public final boolean isRecordingNotStarted() { return mSchedule != null && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED; } - /** - * Checks if the schedule is in progress. - */ + /** Checks if the schedule is in progress. */ public final boolean isRecordingInProgress() { return mSchedule != null && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS; } - /** - * Checks if the schedule has been canceled or not. - */ + /** Checks if the schedule has been canceled or not. */ public final boolean isScheduleCanceled() { return mSchedule != null && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED; @@ -152,28 +119,23 @@ class ScheduleRow { public boolean isRecordingFinished() { return mSchedule != null && (mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED - || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED - || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED); + || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED + || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED); } - /** - * Creates and returns the new schedule with the existing information. - */ + /** Creates and returns the new schedule with the existing information. */ public ScheduledRecording.Builder createNewScheduleBuilder() { return mSchedule != null ? ScheduledRecording.buildFrom(mSchedule) : null; } - /** - * Returns the program title with episode number. - */ + /** Returns the program title with episode number. */ public String getProgramTitleWithEpisodeNumber(Context context) { - return mSchedule != null ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context, - mSchedule, 0).toString() : null; + return mSchedule != null + ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context, mSchedule, 0).toString() + : null; } - /** - * Returns the program title including the season/episode number. - */ + /** Returns the program title including the season/episode number. */ public String getEpisodeDisplayTitle(Context context) { return mSchedule != null ? mSchedule.getEpisodeDisplayTitle(context) : null; } @@ -181,15 +143,16 @@ class ScheduleRow { @Override public String toString() { return getClass().getSimpleName() - + "(schedule=" + mSchedule - + ",stopRecordingRequested=" + mStopRecordingRequested - + ",startRecordingRequested=" + mStartRecordingRequested + + "(schedule=" + + mSchedule + + ",stopRecordingRequested=" + + mStopRecordingRequested + + ",startRecordingRequested=" + + mStartRecordingRequested + ")"; } - /** - * Checks if the {@code schedule} is for the program or channel. - */ + /** Checks if the {@code schedule} is for the program or channel. */ public boolean matchSchedule(ScheduledRecording schedule) { if (mSchedule == null) { return false; diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java index 97d60473..8dd6c322 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java @@ -25,29 +25,25 @@ import android.support.v17.leanback.widget.ClassPresenterSelector; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow; import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -/** - * An adapter for {@link ScheduleRow}. - */ +/** An adapter for {@link ScheduleRow}. */ class ScheduleRowAdapter extends ArrayObjectAdapter { private static final String TAG = "ScheduleRowAdapter"; private static final boolean DEBUG = false; - private final static long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); + private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); private static final int MSG_UPDATE_ROW = 1; @@ -55,16 +51,17 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { private final List mTitles = new ArrayList<>(); private final Set mPendingUpdate = new ArraySet<>(); - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_UPDATE_ROW) { - long currentTimeMs = System.currentTimeMillis(); - handleUpdateRow(currentTimeMs); - sendNextUpdateMessage(currentTimeMs); - } - } - }; + private final Handler mHandler = + new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_UPDATE_ROW) { + long currentTimeMs = System.currentTimeMillis(); + handleUpdateRow(currentTimeMs); + sendNextUpdateMessage(currentTimeMs); + } + } + }; public ScheduleRowAdapter(Context context, ClassPresenterSelector classPresenterSelector) { super(classPresenterSelector); @@ -73,37 +70,41 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { mTitles.add(mContext.getString(R.string.dvr_date_tomorrow)); } - /** - * Returns context. - */ + /** Returns context. */ protected Context getContext() { return mContext; } - /** - * Starts schedule row adapter. - */ + /** Starts schedule row adapter. */ public void start() { clear(); - List recordingList = TvApplication.getSingletons(mContext) - .getDvrDataManager().getNonStartedScheduledRecordings(); - recordingList.addAll(TvApplication.getSingletons(mContext).getDvrDataManager() - .getStartedRecordings()); - Collections.sort(recordingList, - ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); + List recordingList = + TvApplication.getSingletons(mContext) + .getDvrDataManager() + .getNonStartedScheduledRecordings(); + recordingList.addAll( + TvApplication.getSingletons(mContext).getDvrDataManager().getStartedRecordings()); + Collections.sort( + recordingList, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); long deadLine = Utils.getLastMillisecondOfDay(System.currentTimeMillis()); - for (int i = 0; i < recordingList.size();) { + for (int i = 0; i < recordingList.size(); ) { ArrayList 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); + 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){ + for (ScheduledRecording recording : section) { add(new ScheduleRow(recording, headerRow)); } } @@ -113,22 +114,26 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } private String calculateHeaderDate(long deadLine) { - int titleIndex = (int) ((deadLine - - Utils.getLastMillisecondOfDay(System.currentTimeMillis())) / ONE_DAY_MS); + int titleIndex = + (int) + ((deadLine - Utils.getLastMillisecondOfDay(System.currentTimeMillis())) + / ONE_DAY_MS); String headerDate; if (titleIndex < mTitles.size()) { headerDate = mTitles.get(titleIndex); } else { - headerDate = DateUtils.formatDateTime(getContext(), deadLine, - DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_ABBREV_MONTH); + headerDate = + DateUtils.formatDateTime( + getContext(), + deadLine, + DateUtils.FORMAT_SHOW_WEEKDAY + | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_ABBREV_MONTH); } return headerDate; } - /** - * Stops schedules row adapter. - */ + /** Stops schedules row adapter. */ public void stop() { mHandler.removeCallbacksAndMessages(null); DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); @@ -142,9 +147,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } } - /** - * Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to. - */ + /** Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to. */ public ScheduleRow findRowByScheduledRecording(ScheduledRecording recording) { if (recording == null) { return null; @@ -167,7 +170,8 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { continue; } ScheduleRow row = (ScheduleRow) item; - if (row.getSchedule() != null && row.isStartRecordingRequested() + if (row.getSchedule() != null + && row.isStartRecordingRequested() && row.matchSchedule(schedule)) { return row; } @@ -185,7 +189,8 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { if (get(index) instanceof ScheduleRow) { ScheduleRow scheduleRow = (ScheduleRow) get(index); if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.compare( - scheduleRow.getSchedule(), recording) > 0) { + scheduleRow.getSchedule(), recording) + > 0) { break; } pre = index; @@ -205,9 +210,14 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { add(index, addedRow); updateHeaderDescription(headerRow); } else { - SchedulesHeaderRow headerRow = new DateHeaderRow(calculateHeaderDate(deadLine), - mContext.getResources().getQuantityString( - R.plurals.dvr_schedules_section_subtitle, 1, 1), 1, deadLine); + SchedulesHeaderRow headerRow = + new DateHeaderRow( + calculateHeaderDate(deadLine), + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, 1, 1), + 1, + deadLine); add(++pre, headerRow); ScheduleRow addedRow = new ScheduleRow(recording, headerRow); add(pre, addedRow); @@ -241,14 +251,15 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } private void updateHeaderDescription(SchedulesHeaderRow headerRow) { - headerRow.setDescription(mContext.getResources().getQuantityString( - R.plurals.dvr_schedules_section_subtitle, - headerRow.getItemCount(), headerRow.getItemCount())); + headerRow.setDescription( + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, + headerRow.getItemCount(), + headerRow.getItemCount())); } - /** - * Called when a schedule recording is added to dvr date manager. - */ + /** Called when a schedule recording is added to dvr date manager. */ public void onScheduledRecordingAdded(ScheduledRecording schedule) { if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + schedule); ScheduleRow row = findRowWithStartRequest(schedule); @@ -263,9 +274,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } } - /** - * Called when a schedule recording is removed from dvr date manager. - */ + /** Called when a schedule recording is removed from dvr date manager. */ public void onScheduledRecordingRemoved(ScheduledRecording schedule) { if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + schedule); ScheduleRow row = findRowByScheduledRecording(schedule); @@ -276,9 +285,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } } - /** - * Called when a schedule recording is updated in dvr date manager. - */ + /** Called when a schedule recording is updated in dvr date manager. */ public void onScheduledRecordingUpdated(ScheduledRecording schedule, boolean conflictChange) { if (DEBUG) Log.d(TAG, "onScheduledRecordingUpdated: " + schedule); ScheduleRow row = findRowByScheduledRecording(schedule); @@ -329,9 +336,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } } - /** - * Checks if there is a row which requested start/stop recording. - */ + /** Checks if there is a row which requested start/stop recording. */ protected boolean isStartOrStopRequested() { for (int i = 0; i < size(); i++) { Object item = get(i); @@ -345,16 +350,12 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { return false; } - /** - * Delays update of the row. - */ + /** Delays update of the row. */ protected void addPendingUpdate(ScheduleRow row) { mPendingUpdate.add(row); } - /** - * Executes the pending updates. - */ + /** Executes the pending updates. */ protected void executePendingUpdate() { for (ScheduleRow row : mPendingUpdate) { int index = indexOf(row); @@ -365,21 +366,17 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { mPendingUpdate.clear(); } - /** - * To check whether the recording should be kept or not. - */ + /** To check whether the recording should be kept or not. */ protected boolean willBeKept(ScheduledRecording schedule) { // CANCELED state means that the schedule was removed temporarily, which should be shown // in the list so that the user can reschedule it. return schedule.getEndTimeMs() > System.currentTimeMillis() && (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS - || schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED - || schedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED); + || schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED + || schedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED); } - /** - * Handle the message to update/remove rows. - */ + /** Handle the message to update/remove rows. */ protected void handleUpdateRow(long currentTimeMs) { for (int i = 0; i < size(); i++) { Object item = get(i); @@ -392,9 +389,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } } - /** - * Returns the next update time. Return {@link Long#MAX_VALUE} if no timer is necessary. - */ + /** Returns the next update time. Return {@link Long#MAX_VALUE} if no timer is necessary. */ protected long getNextTimerMs(long currentTimeMs) { long earliest = Long.MAX_VALUE; for (int i = 0; i < size(); i++) { @@ -411,15 +406,12 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { return earliest; } - /** - * Send update message at the time returned by {@link #getNextTimerMs}. - */ + /** Send update message at the time returned by {@link #getNextTimerMs}. */ protected final void sendNextUpdateMessage(long currentTimeMs) { mHandler.removeMessages(MSG_UPDATE_ROW); long nextTime = getNextTimerMs(currentTimeMs); if (nextTime != Long.MAX_VALUE) { - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_ROW, - nextTime - System.currentTimeMillis()); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_ROW, nextTime - System.currentTimeMillis()); } } } diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java index dc4e3c41..67096e3b 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java @@ -37,7 +37,6 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; @@ -50,21 +49,22 @@ import com.android.tv.dvr.ui.DvrStopRecordingFragment; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -/** - * A RowPresenter for {@link ScheduleRow}. - */ +/** A RowPresenter for {@link ScheduleRow}. */ @TargetApi(Build.VERSION_CODES.N) class ScheduleRowPresenter extends RowPresenter { private static final String TAG = "ScheduleRowPresenter"; @Retention(RetentionPolicy.SOURCE) - @IntDef({ACTION_START_RECORDING, ACTION_STOP_RECORDING, ACTION_CREATE_SCHEDULE, - ACTION_REMOVE_SCHEDULE}) + @IntDef({ + ACTION_START_RECORDING, + ACTION_STOP_RECORDING, + ACTION_CREATE_SCHEDULE, + ACTION_REMOVE_SCHEDULE + }) public @interface ScheduleRowAction {} /** An action to start recording. */ public static final int ACTION_START_RECORDING = 1; @@ -85,9 +85,7 @@ class ScheduleRowPresenter extends RowPresenter { private int mLastFocusedViewId; - /** - * A ViewHolder for {@link ScheduleRow} - */ + /** A ViewHolder for {@link ScheduleRow} */ public static class ScheduleRowViewHolder extends RowPresenter.ViewHolder { private ScheduleRowPresenter mPresenter; @ScheduleRowAction private int[] mActions; @@ -118,29 +116,30 @@ class ScheduleRowPresenter extends RowPresenter { new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean focused) { - view.post(new Runnable() { - @Override - public void run() { - if (view.isFocused()) { - mPresenter.mLastFocusedViewId = view.getId(); - } - updateSelector(); - } - }); + view.post( + new Runnable() { + @Override + public void run() { + if (view.isFocused()) { + mPresenter.mLastFocusedViewId = view.getId(); + } + updateSelector(); + } + }); } }; public ScheduleRowViewHolder(View view, ScheduleRowPresenter presenter) { super(view); mPresenter = presenter; - mLtr = view.getContext().getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; + mLtr = + view.getContext().getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; mInfoContainer = (LinearLayout) view.findViewById(R.id.info_container); - mSecondActionContainer = (RelativeLayout) view.findViewById( - R.id.action_second_container); + mSecondActionContainer = + (RelativeLayout) view.findViewById(R.id.action_second_container); mSecondActionView = (ImageView) view.findViewById(R.id.action_second); - mFirstActionContainer = (RelativeLayout) view.findViewById( - R.id.action_first_container); + mFirstActionContainer = (RelativeLayout) view.findViewById(R.id.action_first_container); mFirstActionView = (ImageView) view.findViewById(R.id.action_first); mSelectorView = view.findViewById(R.id.selector); mTimeView = (TextView) view.findViewById(R.id.time); @@ -151,47 +150,48 @@ class ScheduleRowPresenter extends RowPresenter { Resources res = view.getResources(); mSelectorTranslationDelta = res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin) - - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_focus_translation_delta); - mSelectorWidthDelta = res.getDimensionPixelSize( - R.dimen.dvr_schedules_item_focus_width_delta); + - res.getDimensionPixelSize( + R.dimen.dvr_schedules_item_focus_translation_delta); + mSelectorWidthDelta = + res.getDimensionPixelSize(R.dimen.dvr_schedules_item_focus_width_delta); mRoundRectRadius = res.getDimensionPixelSize(R.dimen.dvr_schedules_selector_radius); - int fullWidth = res.getDimensionPixelSize( - R.dimen.dvr_schedules_item_width) - - 2 * res.getDimensionPixelSize(R.dimen.dvr_schedules_layout_padding); + int fullWidth = + res.getDimensionPixelSize(R.dimen.dvr_schedules_item_width) + - 2 * res.getDimensionPixelSize(R.dimen.dvr_schedules_layout_padding); mInfoContainerTargetWidthWithNoAction = fullWidth + 2 * mRoundRectRadius; - mInfoContainerTargetWidthWithOneAction = fullWidth - - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin) - - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_delete_width) - + mRoundRectRadius + mSelectorWidthDelta; - mInfoContainerTargetWidthWithTwoAction = mInfoContainerTargetWidthWithOneAction - - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin) - - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_icon_size); + mInfoContainerTargetWidthWithOneAction = + fullWidth + - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin) + - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_delete_width) + + mRoundRectRadius + + mSelectorWidthDelta; + mInfoContainerTargetWidthWithTwoAction = + mInfoContainerTargetWidthWithOneAction + - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin) + - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_icon_size); mInfoContainer.setOnFocusChangeListener(mOnFocusChangeListener); mFirstActionContainer.setOnFocusChangeListener(mOnFocusChangeListener); mSecondActionContainer.setOnFocusChangeListener(mOnFocusChangeListener); } - /** - * Returns time view. - */ + /** Returns time view. */ public TextView getTimeView() { return mTimeView; } - /** - * Returns title view. - */ + /** Returns title view. */ public TextView getProgramTitleView() { return mProgramTitleView; } private void updateSelector() { - int animationDuration = mSelectorView.getResources().getInteger( - android.R.integer.config_shortAnimTime); + int animationDuration = + mSelectorView.getResources().getInteger(android.R.integer.config_shortAnimTime); DecelerateInterpolator interpolator = new DecelerateInterpolator(); - if (mInfoContainer.isFocused() || mSecondActionContainer.isFocused() + if (mInfoContainer.isFocused() + || mSecondActionContainer.isFocused() || mFirstActionContainer.isFocused()) { final ViewGroup.LayoutParams lp = mSelectorView.getLayoutParams(); final int targetWidth; @@ -208,33 +208,50 @@ class ScheduleRowPresenter extends RowPresenter { } else if (mSecondActionContainer.isFocused()) { targetWidth = Math.max(mSecondActionContainer.getWidth(), 2 * mRoundRectRadius); } else { - targetWidth = mFirstActionContainer.getWidth() + mRoundRectRadius - + mSelectorTranslationDelta; + targetWidth = + mFirstActionContainer.getWidth() + + mRoundRectRadius + + mSelectorTranslationDelta; } float targetTranslationX; if (mInfoContainer.isFocused()) { - targetTranslationX = mLtr ? mInfoContainer.getLeft() - mRoundRectRadius - - mSelectorView.getLeft() : - mInfoContainer.getRight() + mRoundRectRadius - mSelectorView.getRight(); + targetTranslationX = + mLtr + ? mInfoContainer.getLeft() + - mRoundRectRadius + - mSelectorView.getLeft() + : mInfoContainer.getRight() + + mRoundRectRadius + - mSelectorView.getRight(); } else if (mSecondActionContainer.isFocused()) { if (mSecondActionContainer.getWidth() > 2 * mRoundRectRadius) { - targetTranslationX = mLtr ? mSecondActionContainer.getLeft() - - mSelectorView.getLeft() - : mSecondActionContainer.getRight() - mSelectorView.getRight(); + targetTranslationX = + mLtr + ? mSecondActionContainer.getLeft() - mSelectorView.getLeft() + : mSecondActionContainer.getRight() + - mSelectorView.getRight(); } else { - targetTranslationX = mLtr ? mSecondActionContainer.getLeft() - - (mRoundRectRadius - mSecondActionContainer.getWidth() / 2) - - mSelectorView.getLeft() - : mSecondActionContainer.getRight() + - (mRoundRectRadius - mSecondActionContainer.getWidth() / 2) - - mSelectorView.getRight(); + targetTranslationX = + mLtr + ? mSecondActionContainer.getLeft() + - (mRoundRectRadius + - mSecondActionContainer.getWidth() / 2) + - mSelectorView.getLeft() + : mSecondActionContainer.getRight() + + (mRoundRectRadius + - mSecondActionContainer.getWidth() / 2) + - mSelectorView.getRight(); } } else { - targetTranslationX = mLtr ? mFirstActionContainer.getLeft() - - mSelectorTranslationDelta - mSelectorView.getLeft() - : mFirstActionContainer.getRight() + mSelectorTranslationDelta - - mSelectorView.getRight(); + targetTranslationX = + mLtr + ? mFirstActionContainer.getLeft() + - mSelectorTranslationDelta + - mSelectorView.getLeft() + : mFirstActionContainer.getRight() + + mSelectorTranslationDelta + - mSelectorView.getRight(); } if (mSelectorView.getAlpha() == 0) { @@ -246,57 +263,80 @@ class ScheduleRowPresenter extends RowPresenter { // animate the selector in and to the proper width and translation X. final float deltaWidth = lp.width - targetWidth; mSelectorView.animate().cancel(); - mSelectorView.animate().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(); - } - }).setDuration(animationDuration).setInterpolator(interpolator).start(); + mSelectorView + .animate() + .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(); + } + }) + .setDuration(animationDuration) + .setInterpolator(interpolator) + .start(); if (mPendingAnimationRunnable != null) { mPendingAnimationRunnable.run(); mPendingAnimationRunnable = null; } } else { mSelectorView.animate().cancel(); - mSelectorView.animate().alpha(0f).setDuration(animationDuration) - .setInterpolator(interpolator).setUpdateListener(null).start(); + mSelectorView + .animate() + .alpha(0f) + .setDuration(animationDuration) + .setInterpolator(interpolator) + .setUpdateListener(null) + .start(); } } - /** - * Grey out the information body. - */ + /** Grey out the information body. */ public void greyOutInfo() { - mTimeView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info_grey, null)); - mProgramTitleView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info_grey, null)); - mInfoSeparatorView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info_grey, null)); - mChannelNameView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info_grey, null)); - mConflictInfoView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info_grey, null)); + mTimeView.setTextColor( + mInfoContainer + .getResources() + .getColor(R.color.dvr_schedules_item_info_grey, null)); + mProgramTitleView.setTextColor( + mInfoContainer + .getResources() + .getColor(R.color.dvr_schedules_item_info_grey, null)); + mInfoSeparatorView.setTextColor( + mInfoContainer + .getResources() + .getColor(R.color.dvr_schedules_item_info_grey, null)); + mChannelNameView.setTextColor( + mInfoContainer + .getResources() + .getColor(R.color.dvr_schedules_item_info_grey, null)); + mConflictInfoView.setTextColor( + mInfoContainer + .getResources() + .getColor(R.color.dvr_schedules_item_info_grey, null)); } - /** - * Reverse grey out operation. - */ + /** Reverse grey out operation. */ public void whiteBackInfo() { - mTimeView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info, null)); - mProgramTitleView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_main, null)); - mInfoSeparatorView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info, null)); - mChannelNameView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info, null)); - mConflictInfoView.setTextColor(mInfoContainer.getResources().getColor(R.color - .dvr_schedules_item_info, null)); + mTimeView.setTextColor( + mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null)); + mProgramTitleView.setTextColor( + mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_main, null)); + mInfoSeparatorView.setTextColor( + mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null)); + mChannelNameView.setTextColor( + mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null)); + mConflictInfoView.setTextColor( + mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null)); } } @@ -306,30 +346,27 @@ class ScheduleRowPresenter extends RowPresenter { mContext = context; mDvrManager = TvApplication.getSingletons(context).getDvrManager(); mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager(); - mTunerConflictWillNotBeRecordedInfo = mContext.getString( - R.string.dvr_schedules_tuner_conflict_will_not_be_recorded_info); - mTunerConflictWillBePartiallyRecordedInfo = mContext.getString( - R.string.dvr_schedules_tuner_conflict_will_be_partially_recorded); - mAnimationDuration = mContext.getResources().getInteger( - android.R.integer.config_shortAnimTime); + mTunerConflictWillNotBeRecordedInfo = + mContext.getString(R.string.dvr_schedules_tuner_conflict_will_not_be_recorded_info); + mTunerConflictWillBePartiallyRecordedInfo = + mContext.getString( + R.string.dvr_schedules_tuner_conflict_will_be_partially_recorded); + mAnimationDuration = + mContext.getResources().getInteger(android.R.integer.config_shortAnimTime); } @Override public ViewHolder createRowViewHolder(ViewGroup parent) { - return onGetScheduleRowViewHolder(LayoutInflater.from(mContext) - .inflate(R.layout.dvr_schedules_item, parent, false)); + return onGetScheduleRowViewHolder( + LayoutInflater.from(mContext).inflate(R.layout.dvr_schedules_item, parent, false)); } - /** - * Returns context. - */ + /** Returns context. */ protected Context getContext() { return mContext; } - /** - * Returns DVR manager. - */ + /** Returns DVR manager. */ protected DvrManager getDvrManager() { return mDvrManager; } @@ -341,40 +378,49 @@ class ScheduleRowPresenter extends RowPresenter { ScheduleRow row = (ScheduleRow) item; @ScheduleRowAction int[] actions = getAvailableActions(row); viewHolder.mActions = actions; - viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (isInfoClickable(row)) { - onInfoClicked(row); - } - } - }); + viewHolder.mInfoContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isInfoClickable(row)) { + onInfoClicked(row); + } + } + }); - viewHolder.mFirstActionContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - onActionClicked(actions[0], row); - } - }); + viewHolder.mFirstActionContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onActionClicked(actions[0], row); + } + }); - viewHolder.mSecondActionContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - onActionClicked(actions[1], row); - } - }); + viewHolder.mSecondActionContainer.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onActionClicked(actions[1], row); + } + }); viewHolder.mTimeView.setText(onGetRecordingTimeText(row)); String programInfoText = onGetProgramInfoText(row); if (TextUtils.isEmpty(programInfoText)) { int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration())); - programInfoText = mContext.getResources().getQuantityString( - R.plurals.dvr_schedules_recording_duration, durationMins, durationMins); + programInfoText = + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_recording_duration, + durationMins, + durationMins); } String channelName = getChannelNameText(row); viewHolder.mProgramTitleView.setText(programInfoText); - viewHolder.mInfoSeparatorView.setVisibility((!TextUtils.isEmpty(programInfoText) - && !TextUtils.isEmpty(channelName)) ? View.VISIBLE : View.GONE); + viewHolder.mInfoSeparatorView.setVisibility( + (!TextUtils.isEmpty(programInfoText) && !TextUtils.isEmpty(channelName)) + ? View.VISIBLE + : View.GONE); viewHolder.mChannelNameView.setText(channelName); if (actions != null) { switch (actions.length) { @@ -422,39 +468,35 @@ class ScheduleRowPresenter extends RowPresenter { } } - /** - * Returns view holder for schedule row. - */ + /** Returns view holder for schedule row. */ protected ScheduleRowViewHolder onGetScheduleRowViewHolder(View view) { return new ScheduleRowViewHolder(view, this); } - /** - * Returns time text for time view from scheduled recording. - */ + /** Returns time text for time view from scheduled recording. */ protected String onGetRecordingTimeText(ScheduleRow row) { - return Utils.getDurationString(mContext, row.getStartTimeMs(), row.getEndTimeMs(), true, - false, true, 0); + return Utils.getDurationString( + mContext, row.getStartTimeMs(), row.getEndTimeMs(), true, false, true, 0); } - /** - * Returns program info text for program title view. - */ + /** Returns program info text for program title view. */ protected String onGetProgramInfoText(ScheduleRow row) { return row.getProgramTitleWithEpisodeNumber(mContext); } private String getChannelNameText(ScheduleRow row) { - Channel channel = TvApplication.getSingletons(mContext).getChannelDataManager() - .getChannel(row.getChannelId()); - return channel == null ? null : - TextUtils.isEmpty(channel.getDisplayName()) ? channel.getDisplayNumber() : - channel.getDisplayName().trim() + " " + channel.getDisplayNumber(); + Channel channel = + TvApplication.getSingletons(mContext) + .getChannelDataManager() + .getChannel(row.getChannelId()); + return channel == null + ? null + : TextUtils.isEmpty(channel.getDisplayName()) + ? channel.getDisplayNumber() + : channel.getDisplayName().trim() + " " + channel.getDisplayNumber(); } - /** - * Called when user click Info in {@link ScheduleRow}. - */ + /** Called when user click Info in {@link ScheduleRow}. */ protected void onInfoClicked(ScheduleRow row) { DvrUiHelper.startDetailsActivity((Activity) mContext, row.getSchedule(), null, true); } @@ -464,9 +506,7 @@ class ScheduleRowPresenter extends RowPresenter { && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress()); } - /** - * Called when the button in a row is clicked. - */ + /** Called when the button in a row is clicked. */ protected void onActionClicked(@ScheduleRowAction final int action, ScheduleRow row) { switch (action) { case ACTION_START_RECORDING: @@ -484,9 +524,7 @@ class ScheduleRowPresenter extends RowPresenter { } } - /** - * Action handler for {@link #ACTION_START_RECORDING}. - */ + /** Action handler for {@link #ACTION_START_RECORDING}. */ protected void onStartRecording(ScheduleRow row) { ScheduledRecording schedule = row.getSchedule(); if (schedule == null) { @@ -495,12 +533,16 @@ class ScheduleRowPresenter extends RowPresenter { } // Checks if there are current recordings that will be stopped by schedule this program. // If so, shows confirmation dialog to users. - List conflictSchedules = mDvrScheduleManager.getConflictingSchedules( - schedule.getChannelId(), System.currentTimeMillis(), schedule.getEndTimeMs()); + List conflictSchedules = + mDvrScheduleManager.getConflictingSchedules( + schedule.getChannelId(), + System.currentTimeMillis(), + schedule.getEndTimeMs()); for (int i = conflictSchedules.size() - 1; i >= 0; i--) { ScheduledRecording conflictSchedule = conflictSchedules.get(i); if (conflictSchedule.isInProgress()) { - DvrUiHelper.showStopRecordingDialog((Activity) mContext, + DvrUiHelper.showStopRecordingDialog( + (Activity) mContext, conflictSchedule.getChannelId(), DvrStopRecordingFragment.REASON_ON_CONFLICT, new HalfSizedDialogFragment.OnActionClickListener() { @@ -523,26 +565,27 @@ class ScheduleRowPresenter extends RowPresenter { if (row.isRecordingNotStarted()) { mDvrManager.setHighestPriority(row.getSchedule()); } else if (row.isRecordingFinished()) { - mDvrManager.addSchedule(ScheduledRecording.buildFrom(row.getSchedule()) - .setId(ScheduledRecording.ID_NOT_SET) - .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) - .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule())) - .build()); + mDvrManager.addSchedule( + ScheduledRecording.buildFrom(row.getSchedule()) + .setId(ScheduledRecording.ID_NOT_SET) + .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) + .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule())) + .build()); } else { - SoftPreconditions.checkState(false, TAG, "Invalid row state to start recording: " - + row); + SoftPreconditions.checkState( + false, TAG, "Invalid row state to start recording: " + row); return; } - String msg = mContext.getString(R.string.dvr_msg_current_program_scheduled, - row.getSchedule().getProgramTitle(), - Utils.toTimeString(row.getEndTimeMs(), false)); + String msg = + mContext.getString( + R.string.dvr_msg_current_program_scheduled, + row.getSchedule().getProgramTitle(), + Utils.toTimeString(row.getEndTimeMs(), false)); ToastUtils.show(mContext, msg, Toast.LENGTH_SHORT); } } - /** - * Action handler for {@link #ACTION_STOP_RECORDING}. - */ + /** Action handler for {@link #ACTION_STOP_RECORDING}. */ protected void onStopRecording(ScheduleRow row) { if (row.getSchedule() == null) { // This row has been deleted. @@ -555,15 +598,15 @@ class ScheduleRowPresenter extends RowPresenter { if (TextUtils.isEmpty(deletedInfo)) { deletedInfo = getChannelNameText(row); } - ToastUtils.show(mContext, mContext.getResources() - .getString(R.string.dvr_schedules_deletion_info, deletedInfo), + ToastUtils.show( + mContext, + mContext.getResources() + .getString(R.string.dvr_schedules_deletion_info, deletedInfo), Toast.LENGTH_SHORT); } } - /** - * Action handler for {@link #ACTION_CREATE_SCHEDULE}. - */ + /** Action handler for {@link #ACTION_CREATE_SCHEDULE}. */ protected void onCreateSchedule(ScheduleRow row) { if (row.getSchedule() == null) { // This row has been deleted. @@ -571,12 +614,15 @@ class ScheduleRowPresenter extends RowPresenter { } if (!row.isOnAir()) { if (row.isScheduleCanceled()) { - mDvrManager.updateScheduledRecording(ScheduledRecording.buildFrom(row.getSchedule()) - .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) - .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule())) - .build()); - String msg = mContext.getString(R.string.dvr_msg_program_scheduled, - row.getSchedule().getProgramTitle()); + mDvrManager.updateScheduledRecording( + ScheduledRecording.buildFrom(row.getSchedule()) + .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) + .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule())) + .build()); + String msg = + mContext.getString( + R.string.dvr_msg_program_scheduled, + row.getSchedule().getProgramTitle()); ToastUtils.show(mContext, msg, Toast.LENGTH_SHORT); } else if (mDvrManager.isConflicting(row.getSchedule())) { mDvrManager.setHighestPriority(row.getSchedule()); @@ -584,9 +630,7 @@ class ScheduleRowPresenter extends RowPresenter { } } - /** - * Action handler for {@link #ACTION_REMOVE_SCHEDULE}. - */ + /** Action handler for {@link #ACTION_REMOVE_SCHEDULE}. */ protected void onRemoveSchedule(ScheduleRow row) { if (row.getSchedule() == null) { // This row has been deleted. @@ -605,13 +649,16 @@ class ScheduleRowPresenter extends RowPresenter { mDvrManager.removeScheduledRecording(row.getSchedule()); } else if (row.isRecordingNotStarted()) { deletedInfo = getDeletedInfo(row); - mDvrManager.updateScheduledRecording(ScheduledRecording.buildFrom(row.getSchedule()) - .setState(ScheduledRecording.STATE_RECORDING_CANCELED) - .build()); + mDvrManager.updateScheduledRecording( + ScheduledRecording.buildFrom(row.getSchedule()) + .setState(ScheduledRecording.STATE_RECORDING_CANCELED) + .build()); } } if (deletedInfo != null) { - ToastUtils.show(mContext, mContext.getResources() + ToastUtils.show( + mContext, + mContext.getResources() .getString(R.string.dvr_schedules_deletion_info, deletedInfo), Toast.LENGTH_SHORT); } @@ -631,9 +678,7 @@ class ScheduleRowPresenter extends RowPresenter { updateActionContainer(vh, selected); } - /** - * Internal method for onRowViewSelected, can be customized by subclass. - */ + /** Internal method for onRowViewSelected, can be customized by subclass. */ private void updateActionContainer(ViewHolder vh, boolean selected) { ScheduleRowViewHolder viewHolder = (ScheduleRowViewHolder) vh; viewHolder.mSecondActionContainer.animate().setListener(null).cancel(); @@ -643,38 +688,43 @@ class ScheduleRowPresenter extends RowPresenter { case 2: prepareShowActionView(viewHolder.mSecondActionContainer); prepareShowActionView(viewHolder.mFirstActionContainer); - viewHolder.mPendingAnimationRunnable = new Runnable() { - @Override - public void run() { - showActionView(viewHolder.mSecondActionContainer); - showActionView(viewHolder.mFirstActionContainer); - } - }; + viewHolder.mPendingAnimationRunnable = + new Runnable() { + @Override + public void run() { + showActionView(viewHolder.mSecondActionContainer); + showActionView(viewHolder.mFirstActionContainer); + } + }; break; case 1: prepareShowActionView(viewHolder.mFirstActionContainer); - viewHolder.mPendingAnimationRunnable = new Runnable() { - @Override - public void run() { - hideActionView(viewHolder.mSecondActionContainer, View.GONE); - showActionView(viewHolder.mFirstActionContainer); - } - }; + viewHolder.mPendingAnimationRunnable = + new Runnable() { + @Override + public void run() { + hideActionView(viewHolder.mSecondActionContainer, View.GONE); + showActionView(viewHolder.mFirstActionContainer); + } + }; if (mLastFocusedViewId == R.id.action_second_container) { mLastFocusedViewId = R.id.info_container; } break; case 0: default: - viewHolder.mPendingAnimationRunnable = new Runnable() { - @Override - public void run() { - hideActionView(viewHolder.mSecondActionContainer, View.GONE); - hideActionView(viewHolder.mFirstActionContainer, View.GONE); - } - }; + viewHolder.mPendingAnimationRunnable = + new Runnable() { + @Override + public void run() { + hideActionView(viewHolder.mSecondActionContainer, View.GONE); + hideActionView(viewHolder.mFirstActionContainer, View.GONE); + } + }; mLastFocusedViewId = R.id.info_container; - SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG, + SoftPreconditions.checkState( + viewHolder.mInfoContainer.isFocusable(), + TAG, "No focusable view in this row: " + viewHolder); break; } @@ -685,7 +735,7 @@ class ScheduleRowPresenter extends RowPresenter { // requestFocus() explicitly. if (view.hasFocus()) { viewHolder.mPendingAnimationRunnable.run(); - } else if (view.isFocusable()){ + } else if (view.isFocusable()) { view.requestFocus(); } else { viewHolder.view.requestFocus(); @@ -705,17 +755,16 @@ class ScheduleRowPresenter extends RowPresenter { view.setVisibility(View.VISIBLE); } - /** - * Add animation when view is visible. - */ + /** Add animation when view is visible. */ private void showActionView(View view) { - view.animate().alpha(1.0f).setInterpolator(new DecelerateInterpolator()) - .setDuration(mAnimationDuration).start(); + view.animate() + .alpha(1.0f) + .setInterpolator(new DecelerateInterpolator()) + .setDuration(mAnimationDuration) + .start(); } - /** - * Add animation when view change to invisible. - */ + /** Add animation when view change to invisible. */ private void hideActionView(View view, int visibility) { if (view.getVisibility() != View.VISIBLE) { if (view.getVisibility() != visibility) { @@ -723,15 +772,19 @@ class ScheduleRowPresenter extends RowPresenter { } return; } - view.animate().alpha(0.0f).setInterpolator(new DecelerateInterpolator()) + view.animate() + .alpha(0.0f) + .setInterpolator(new DecelerateInterpolator()) .setDuration(mAnimationDuration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(visibility); - view.animate().setListener(null); - } - }).start(); + .setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(visibility); + view.animate().setListener(null); + } + }) + .start(); } /** @@ -742,7 +795,7 @@ class ScheduleRowPresenter extends RowPresenter { protected int[] getAvailableActions(ScheduleRow row) { if (row.getSchedule() != null) { if (row.isRecordingInProgress()) { - return new int[]{ACTION_STOP_RECORDING}; + return new int[] {ACTION_STOP_RECORDING}; } else if (row.isOnAir()) { if (row.isRecordingNotStarted()) { if (canResolveConflict()) { @@ -754,8 +807,12 @@ class ScheduleRowPresenter extends RowPresenter { } else if (row.isRecordingFinished()) { return new int[] {ACTION_START_RECORDING}; } else { - SoftPreconditions.checkState(false, TAG, "Invalid row state in checking the" - + " available actions(on air): " + row); + SoftPreconditions.checkState( + false, + TAG, + "Invalid row state in checking the" + + " available actions(on air): " + + row); } } else { if (row.isScheduleCanceled()) { @@ -765,31 +822,29 @@ class ScheduleRowPresenter extends RowPresenter { } else if (row.isRecordingNotStarted()) { return new int[] {ACTION_REMOVE_SCHEDULE}; } else { - SoftPreconditions.checkState(false, TAG, "Invalid row state in checking the" - + " available actions(future schedule): " + row); + SoftPreconditions.checkState( + false, + TAG, + "Invalid row state in checking the" + + " available actions(future schedule): " + + row); } } } return null; } - /** - * Check if the conflict can be resolved in this screen. - */ + /** Check if the conflict can be resolved in this screen. */ protected boolean canResolveConflict() { return true; } - /** - * Check if the schedule should be kept after removing it. - */ + /** Check if the schedule should be kept after removing it. */ protected boolean shouldKeepScheduleAfterRemoving() { return false; } - /** - * Checks if the row should be grayed out. - */ + /** Checks if the row should be grayed out. */ protected boolean shouldBeGrayedOut(ScheduleRow row) { return row.getSchedule() == null || (row.isOnAir() && !row.isRecordingInProgress()) diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java index 715ecb8c..bbddc07f 100644 --- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java +++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java @@ -18,12 +18,9 @@ package com.android.tv.dvr.ui.list; import com.android.tv.data.Program; import com.android.tv.dvr.data.SeriesRecording; - import java.util.List; -/** - * A base class for the rows for schedules' header. - */ +/** A base class for the rows for schedules' header. */ abstract class SchedulesHeaderRow { private String mTitle; private String mDescription; @@ -35,51 +32,37 @@ abstract class SchedulesHeaderRow { mDescription = description; } - /** - * Sets title. - */ + /** Sets title. */ public void setTitle(String title) { mTitle = title; } - /** - * Sets description. - */ + /** Sets description. */ public void setDescription(String description) { mDescription = description; } - /** - * Sets count of items. - */ + /** Sets count of items. */ public void setItemCount(int itemCount) { mItemCount = itemCount; } - /** - * Returns title. - */ + /** Returns title. */ public String getTitle() { return mTitle; } - /** - * Returns description. - */ + /** Returns description. */ public String getDescription() { return mDescription; } - /** - * Returns count of items. - */ + /** Returns count of items. */ public int getItemCount() { return mItemCount; } - /** - * The header row which represent the date. - */ + /** The header row which represent the date. */ public static class DateHeaderRow extends SchedulesHeaderRow { private long mDeadLineMs; @@ -88,47 +71,41 @@ abstract class SchedulesHeaderRow { mDeadLineMs = deadLineMs; } - /** - * Returns the latest time of the list which belongs to the header row. - */ + /** Returns the latest time of the list which belongs to the header row. */ public long getDeadLineMs() { return mDeadLineMs; } } - /** - * The header row which represent the series recording. - */ + /** The header row which represent the series recording. */ public static class SeriesRecordingHeaderRow extends SchedulesHeaderRow { private SeriesRecording mSeriesRecording; private List mPrograms; - public SeriesRecordingHeaderRow(String title, String description, int itemCount, - SeriesRecording series, List programs) { + public SeriesRecordingHeaderRow( + String title, + String description, + int itemCount, + SeriesRecording series, + List programs) { super(title, description, itemCount); mSeriesRecording = series; mPrograms = programs; } - /** - * Returns the list of programs which belong to the series. - */ + /** Returns the list of programs which belong to the series. */ public List getPrograms() { return mPrograms; } - /** - * Returns the series recording, it is for series schedules list. - */ + /** Returns the series recording, it is for series schedules list. */ public SeriesRecording getSeriesRecording() { return mSeriesRecording; } - /** - * Sets the series recording. - */ + /** Sets the series recording. */ public void setSeriesRecording(SeriesRecording seriesRecording) { mSeriesRecording = seriesRecording; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java index fe2033ba..03cc0a79 100644 --- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java @@ -27,16 +27,13 @@ import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow; -/** - * A base class for RowPresenter for {@link SchedulesHeaderRow} - */ +/** A base class for RowPresenter for {@link SchedulesHeaderRow} */ abstract class SchedulesHeaderRowPresenter extends RowPresenter { private Context mContext; @@ -46,23 +43,20 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { mContext = context; } - /** - * Returns the context. - */ + /** Returns the context. */ Context getContext() { return mContext; } - /** - * A ViewHolder for {@link SchedulesHeaderRow}. - */ + /** A ViewHolder for {@link SchedulesHeaderRow}. */ public static class SchedulesHeaderRowViewHolder extends RowPresenter.ViewHolder { private TextView mTitle; private TextView mDescription; public SchedulesHeaderRowViewHolder(Context context, ViewGroup parent) { - super(LayoutInflater.from(context).inflate(R.layout.dvr_schedules_header, parent, - false)); + super( + LayoutInflater.from(context) + .inflate(R.layout.dvr_schedules_header, parent, false)); mTitle = (TextView) view.findViewById(R.id.header_title); mDescription = (TextView) view.findViewById(R.id.header_description); } @@ -77,9 +71,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { headerViewHolder.mDescription.setText(header.getDescription()); } - /** - * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}. - */ + /** A presenter for {@link SchedulesHeaderRow.DateHeaderRow}. */ public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter { public DateHeaderRowPresenter(Context context) { super(context); @@ -90,10 +82,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { return new DateHeaderRowViewHolder(getContext(), parent); } - /** - * A ViewHolder for - * {@link SchedulesHeaderRow.DateHeaderRow}. - */ + /** A ViewHolder for {@link SchedulesHeaderRow.DateHeaderRow}. */ public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder { public DateHeaderRowViewHolder(Context context, ViewGroup parent) { super(context, parent); @@ -101,9 +90,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { } } - /** - * A presenter for {@link SeriesRecordingHeaderRow}. - */ + /** A presenter for {@link SeriesRecordingHeaderRow}. */ public static class SeriesRecordingHeaderRowPresenter extends SchedulesHeaderRowPresenter { private final boolean mLtr; private final Drawable mSettingsDrawable; @@ -116,8 +103,9 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { public SeriesRecordingHeaderRowPresenter(Context context) { super(context); - mLtr = context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; + mLtr = + context.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; mSettingsDrawable = context.getDrawable(R.drawable.ic_settings); mCancelDrawable = context.getDrawable(R.drawable.ic_dvr_cancel_large); mResumeDrawable = context.getDrawable(R.drawable.ic_record_start); @@ -134,8 +122,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { @Override protected void onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item) { super.onBindRowViewHolder(viewHolder, item); - SeriesHeaderRowViewHolder headerViewHolder = - (SeriesHeaderRowViewHolder) viewHolder; + SeriesHeaderRowViewHolder headerViewHolder = (SeriesHeaderRowViewHolder) viewHolder; SeriesRecordingHeaderRow header = (SeriesRecordingHeaderRow) item; headerViewHolder.mSeriesSettingsButton.setVisibility( header.getSeriesRecording().isStopped() ? View.INVISIBLE : View.VISIBLE); @@ -148,46 +135,59 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { headerViewHolder.mToggleStartStopButton.setText(mCancelAllInfo); setTextDrawable(headerViewHolder.mToggleStartStopButton, mCancelDrawable); } - headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - DvrUiHelper.startSeriesSettingsActivity(getContext(), - header.getSeriesRecording().getId(), - header.getPrograms(), false, false, false, null); - } - }); - headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - if (header.getSeriesRecording().isStopped()) { - // Reset priority to the highest. - SeriesRecording seriesRecording = SeriesRecording - .buildFrom(header.getSeriesRecording()) - .setPriority(TvApplication.getSingletons(getContext()) - .getDvrScheduleManager().suggestNewSeriesPriority()) - .build(); - TvApplication.getSingletons(getContext()).getDvrManager() - .updateSeriesRecording(seriesRecording); - DvrUiHelper.startSeriesSettingsActivity(getContext(), - header.getSeriesRecording().getId(), - header.getPrograms(), false, false, false, null); - } else { - DvrUiHelper.showCancelAllSeriesRecordingDialog( - (DvrSchedulesActivity) view.getContext(), - header.getSeriesRecording()); - } - } - }); + headerViewHolder.mSeriesSettingsButton.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + DvrUiHelper.startSeriesSettingsActivity( + getContext(), + header.getSeriesRecording().getId(), + header.getPrograms(), + false, + false, + false, + null); + } + }); + headerViewHolder.mToggleStartStopButton.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + if (header.getSeriesRecording().isStopped()) { + // Reset priority to the highest. + SeriesRecording seriesRecording = + SeriesRecording.buildFrom(header.getSeriesRecording()) + .setPriority( + TvApplication.getSingletons(getContext()) + .getDvrScheduleManager() + .suggestNewSeriesPriority()) + .build(); + TvApplication.getSingletons(getContext()) + .getDvrManager() + .updateSeriesRecording(seriesRecording); + DvrUiHelper.startSeriesSettingsActivity( + getContext(), + header.getSeriesRecording().getId(), + header.getPrograms(), + false, + false, + false, + null); + } else { + DvrUiHelper.showCancelAllSeriesRecordingDialog( + (DvrSchedulesActivity) view.getContext(), + header.getSeriesRecording()); + } + } + }); } private void setTextDrawable(TextView textView, Drawable drawableStart) { - textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null, - null); + textView.setCompoundDrawablesRelativeWithIntrinsicBounds( + drawableStart, null, null, null); } - /** - * A ViewHolder for {@link SeriesRecordingHeaderRow}. - */ + /** A ViewHolder for {@link SeriesRecordingHeaderRow}. */ public static class SeriesHeaderRowViewHolder extends SchedulesHeaderRowViewHolder { private final TextView mSeriesSettingsButton; private final TextView mToggleStartStopButton; @@ -196,33 +196,40 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { private final View mSelector; private View mLastFocusedView; + public SeriesHeaderRowViewHolder(Context context, ViewGroup parent) { super(context, parent); - mLtr = context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; + mLtr = + context.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; view.findViewById(R.id.button_container).setVisibility(View.VISIBLE); mSeriesSettingsButton = (TextView) view.findViewById(R.id.series_settings); mToggleStartStopButton = (TextView) view.findViewById(R.id.series_toggle_start_stop); mSelector = view.findViewById(R.id.selector); - OnFocusChangeListener onFocusChangeListener = new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean focused) { - view.post(new Runnable() { + OnFocusChangeListener onFocusChangeListener = + new View.OnFocusChangeListener() { @Override - public void run() { - updateSelector(view); + public void onFocusChange(View view, boolean focused) { + view.post( + new Runnable() { + @Override + public void run() { + updateSelector(view); + } + }); } - }); - } - }; + }; mSeriesSettingsButton.setOnFocusChangeListener(onFocusChangeListener); mToggleStartStopButton.setOnFocusChangeListener(onFocusChangeListener); } private void updateSelector(View focusedView) { - int animationDuration = mSelector.getContext().getResources() - .getInteger(android.R.integer.config_shortAnimTime); + int animationDuration = + mSelector + .getContext() + .getResources() + .getInteger(android.R.integer.config_shortAnimTime); DecelerateInterpolator interpolator = new DecelerateInterpolator(); if (focusedView.hasFocus()) { @@ -246,21 +253,38 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { // animate the selector in and to the proper width and translation X. final float deltaWidth = lp.width - targetWidth; mSelector.animate().cancel(); - mSelector.animate().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())); - mSelector.requestLayout(); - } - }).setDuration(animationDuration).setInterpolator(interpolator).start(); + mSelector + .animate() + .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())); + mSelector.requestLayout(); + } + }) + .setDuration(animationDuration) + .setInterpolator(interpolator) + .start(); mLastFocusedView = focusedView; } else if (mLastFocusedView == focusedView) { mSelector.animate().setUpdateListener(null).cancel(); - mSelector.animate().alpha(0f).setDuration(animationDuration) - .setInterpolator(interpolator).start(); + mSelector + .animate() + .alpha(0f) + .setDuration(animationDuration) + .setInterpolator(interpolator) + .start(); mLastFocusedView = null; } } diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java index 6b6de8b8..692c0f99 100644 --- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java @@ -1,18 +1,18 @@ /* -* Copyright (C) 2016 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License -*/ + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ package com.android.tv.dvr.ui.list; @@ -23,7 +23,6 @@ import android.os.Build; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.util.ArrayMap; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -35,16 +34,13 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; -/** - * An adapter for series schedule row. - */ +/** An adapter for series schedule row. */ @TargetApi(Build.VERSION_CODES.N) class SeriesScheduleRowAdapter extends ScheduleRowAdapter { private static final String TAG = "SeriesRowAdapter"; @@ -57,7 +53,9 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter { private final Map mPrograms = new ArrayMap<>(); private SeriesRecordingHeaderRow mHeaderRow; - public SeriesScheduleRowAdapter(Context context, ClassPresenterSelector classPresenterSelector, + public SeriesScheduleRowAdapter( + Context context, + ClassPresenterSelector classPresenterSelector, SeriesRecording seriesRecording) { super(context, classPresenterSelector); mSeriesRecording = seriesRecording; @@ -83,9 +81,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter { super.stop(); } - /** - * Sets the programs to show. - */ + /** Sets the programs to show. */ public void setPrograms(List programs) { if (programs == null) { programs = Collections.emptyList(); @@ -95,8 +91,13 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter { List sortedPrograms = new ArrayList<>(programs); Collections.sort(sortedPrograms); List rows = new ArrayList<>(); - mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(), - null, sortedPrograms.size(), mSeriesRecording, programs); + mHeaderRow = + new SeriesRecordingHeaderRow( + mSeriesRecording.getTitle(), + null, + sortedPrograms.size(), + mSeriesRecording, + programs); for (Program program : sortedPrograms) { ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(program.getId()); @@ -122,8 +123,14 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter { ++conflicts; } } - return conflicts == 0 ? null : getContext().getResources().getQuantityString( - R.plurals.dvr_series_schedules_header_description, conflicts, conflicts); + return conflicts == 0 + ? null + : getContext() + .getResources() + .getQuantityString( + R.plurals.dvr_series_schedules_header_description, + conflicts, + conflicts); } @Override diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java index c8503e0d..263c579e 100644 --- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java @@ -1,33 +1,30 @@ /* -* Copyright (C) 2016 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License -*/ + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ package com.android.tv.dvr.ui.list; import android.content.Context; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.Utils; -/** - * A RowPresenter for series schedule row. - */ +/** A RowPresenter for series schedule row. */ class SeriesScheduleRowPresenter extends ScheduleRowPresenter { private static final String TAG = "SeriesRowPresenter"; @@ -35,16 +32,18 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter { public SeriesScheduleRowPresenter(Context context) { super(context); - mLtr = context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; + mLtr = + context.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; } public static class SeriesScheduleRowViewHolder extends ScheduleRowViewHolder { public SeriesScheduleRowViewHolder(View view, ScheduleRowPresenter presenter) { super(view, presenter); ViewGroup.LayoutParams lp = getTimeView().getLayoutParams(); - lp.width = view.getResources().getDimensionPixelSize( - R.dimen.dvr_series_schedules_item_time_width); + lp.width = + view.getResources() + .getDimensionPixelSize(R.dimen.dvr_series_schedules_item_time_width); getTimeView().setLayoutParams(lp); } } @@ -56,8 +55,8 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter { @Override protected String onGetRecordingTimeText(ScheduleRow row) { - return Utils.getDurationString(getContext(), row.getStartTimeMs(), row.getEndTimeMs(), - false, true, true, 0); + return Utils.getDurationString( + getContext(), row.getStartTimeMs(), row.getEndTimeMs(), false, true, true, 0); } @Override @@ -71,11 +70,17 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter { SeriesScheduleRowViewHolder viewHolder = (SeriesScheduleRowViewHolder) vh; EpisodicProgramRow row = (EpisodicProgramRow) item; if (getDvrManager().isConflicting(row.getSchedule())) { - viewHolder.getProgramTitleView().setCompoundDrawablePadding(getContext() - .getResources().getDimensionPixelOffset( - R.dimen.dvr_schedules_warning_icon_padding)); - viewHolder.getProgramTitleView().setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.ic_warning_gray600_36dp, 0, 0, 0); + viewHolder + .getProgramTitleView() + .setCompoundDrawablePadding( + getContext() + .getResources() + .getDimensionPixelOffset( + R.dimen.dvr_schedules_warning_icon_padding)); + viewHolder + .getProgramTitleView() + .setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.ic_warning_gray600_36dp, 0, 0, 0); } else { viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } @@ -88,16 +93,16 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter { @Override protected void onStartRecording(ScheduleRow row) { - SoftPreconditions.checkState(row.getSchedule() == null, TAG, - "Start request with the existing schedule: " + row); + SoftPreconditions.checkState( + row.getSchedule() == null, TAG, "Start request with the existing schedule: " + row); row.setStartRecordingRequested(true); getDvrManager().addScheduleWithHighestPriority(((EpisodicProgramRow) row).getProgram()); } @Override protected void onStopRecording(ScheduleRow row) { - SoftPreconditions.checkState(row.getSchedule() != null, TAG, - "Stop request with the null schedule: " + row); + SoftPreconditions.checkState( + row.getSchedule() != null, TAG, "Stop request with the null schedule: " + row); row.setStopRecordingRequested(true); getDvrManager().stopRecording(row.getSchedule()); } diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java index 6824cfe2..29f2734d 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java @@ -23,16 +23,13 @@ import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.util.Log; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.util.Utils; -/** - * Activity to play a {@link RecordedProgram}. - */ +/** Activity to play a {@link RecordedProgram}. */ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListener { private static final String TAG = "DvrPlaybackActivity"; private static final boolean DEBUG = false; @@ -47,8 +44,9 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene super.onCreate(savedInstanceState); setIntent(createProgramIntent(getIntent())); setContentView(R.layout.activity_dvr_playback); - mOverlayFragment = (DvrPlaybackOverlayFragment) getFragmentManager() - .findFragmentById(R.id.dvr_playback_controls_fragment); + mOverlayFragment = + (DvrPlaybackOverlayFragment) + getFragmentManager().findFragmentById(R.id.dvr_playback_controls_fragment); } @Override @@ -68,7 +66,8 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); float density = getResources().getDisplayMetrics().density; - mOverlayFragment.onWindowSizeChanged((int) (newConfig.screenWidthDp * density), + mOverlayFragment.onWindowSizeChanged( + (int) (newConfig.screenWidthDp * density), (int) (newConfig.screenHeightDp * density)); } @@ -91,4 +90,4 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene void setOnPinCheckListener(OnPinCheckedListener listener) { mOnPinCheckedListener = listener; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java index 8ef0041d..a6352d95 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java @@ -17,14 +17,11 @@ package com.android.tv.dvr.ui.playback; import android.content.Context; - import com.android.tv.R; import com.android.tv.dvr.ui.browse.RecordedProgramPresenter; import com.android.tv.dvr.ui.browse.RecordingCardView; -/** - * This class is used to generate Views and bind Objects for related recordings in DVR playback. - */ +/** This class is used to generate Views and bind Objects for related recordings in DVR playback. */ class DvrPlaybackCardPresenter extends RecordedProgramPresenter { private final int mRelatedRecordingCardWidth; private final int mRelatedRecordingCardHeight; @@ -39,7 +36,12 @@ class DvrPlaybackCardPresenter extends RecordedProgramPresenter { @Override public DvrItemViewHolder onCreateDvrItemViewHolder() { - return new RecordedProgramViewHolder(new RecordingCardView( - getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true), null); + return new RecordedProgramViewHolder( + new RecordingCardView( + getContext(), + mRelatedRecordingCardWidth, + mRelatedRecordingCardHeight, + true), + null); } } diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java index 1a6ae187..59c90d11 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java @@ -44,8 +44,8 @@ import com.android.tv.util.TimeShiftUtils; import java.util.ArrayList; /** - * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and - * send command to the media controller. It also helps to update playback states displayed in the + * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and send + * command to the media controller. It also helps to update playback states displayed in the * fragment according to information the media session provides. */ class DvrPlaybackControlHelper extends PlaybackControlGlue { @@ -74,8 +74,9 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { mMediaController = activity.getMediaController(); mMediaController.registerCallback(mMediaControllerCallback); mTransportControls = mMediaController.getTransportControls(); - mExtraPaddingTopForNoDescription = activity.getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top); + mExtraPaddingTopForNoDescription = + activity.getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top); mClosedCaptioningAction = new ClosedCaptioningAction(activity); mMultiAudioAction = new MultiAudioAction(activity); createControlsRowPresenter(); @@ -90,42 +91,47 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { private void createControlsRowPresenter() { AbstractDetailsDescriptionPresenter detailsPresenter = new AbstractDetailsDescriptionPresenter() { - @Override - protected void onBindDescription( - AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) { - PlaybackControlGlue glue = (PlaybackControlGlue) object; - if (glue.hasValidMedia()) { - viewHolder.getTitle().setText(glue.getMediaTitle()); - viewHolder.getSubtitle().setText(glue.getMediaSubtitle()); - } else { - viewHolder.getTitle().setText(""); - viewHolder.getSubtitle().setText(""); - } - if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) { - viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(), - mExtraPaddingTopForNoDescription, - viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom()); - } - } - }; + @Override + protected void onBindDescription( + AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, + Object object) { + PlaybackControlGlue glue = (PlaybackControlGlue) object; + if (glue.hasValidMedia()) { + viewHolder.getTitle().setText(glue.getMediaTitle()); + viewHolder.getSubtitle().setText(glue.getMediaSubtitle()); + } else { + viewHolder.getTitle().setText(""); + viewHolder.getSubtitle().setText(""); + } + if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) { + viewHolder.view.setPadding( + viewHolder.view.getPaddingLeft(), + mExtraPaddingTopForNoDescription, + viewHolder.view.getPaddingRight(), + viewHolder.view.getPaddingBottom()); + } + } + }; PlaybackControlsRowPresenter presenter = new PlaybackControlsRowPresenter(detailsPresenter) { - @Override - protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) { - super.onBindRowViewHolder(vh, item); - vh.setOnKeyListener(DvrPlaybackControlHelper.this); - } - - @Override - protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) { - super.onUnbindRowViewHolder(vh); - vh.setOnKeyListener(null); - } - }; - presenter.setProgressColor(getContext().getResources() - .getColor(R.color.play_controls_progress_bar_watched)); - presenter.setBackgroundColor(getContext().getResources() - .getColor(R.color.play_controls_body_background_enabled)); + @Override + protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) { + super.onBindRowViewHolder(vh, item); + vh.setOnKeyListener(DvrPlaybackControlHelper.this); + } + + @Override + protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) { + super.onUnbindRowViewHolder(vh); + vh.setOnKeyListener(null); + } + }; + presenter.setProgressColor( + getContext().getResources().getColor(R.color.play_controls_progress_bar_watched)); + presenter.setBackgroundColor( + getContext() + .getResources() + .getColor(R.color.play_controls_body_background_enabled)); setControlsRowPresenter(presenter); } @@ -166,37 +172,40 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { return false; } int state = playbackState.getState(); - return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING + return state != PlaybackState.STATE_NONE + && state != PlaybackState.STATE_CONNECTING && state != PlaybackState.STATE_PAUSED; } - /** - * Returns the ID of the media under playback. - */ + /** Returns the ID of the media under playback. */ public String getMediaId() { MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? null + return mediaMetadata == null + ? null : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); } @Override public CharSequence getMediaTitle() { MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? "" + return mediaMetadata == null + ? "" : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); } @Override public CharSequence getMediaSubtitle() { MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? "" + return mediaMetadata == null + ? "" : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE); } @Override public int getMediaDuration() { MediaMetadata mediaMetadata = mMediaController.getMetadata(); - return mediaMetadata == null ? 0 + return mediaMetadata == null + ? 0 : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); } @@ -225,19 +234,18 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { return (int) playbackState.getPosition(); } - /** - * Unregister media controller's callback. - */ + /** Unregister media controller's callback. */ void unregisterCallback() { mMediaController.unregisterCallback(mMediaControllerCallback); } /** * Update the secondary controls row. - * @param hasClosedCaption {@code true} to show the closed caption selection button, - * {@code false} to hide it. - * @param hasMultiAudio {@code true} to show the audio track selection button, - * {@code false} to hide it. + * + * @param hasClosedCaption {@code true} to show the closed caption selection button, {@code + * false} to hide it. + * @param hasMultiAudio {@code true} to show the audio track selection button, {@code false} to + * hide it. */ void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) { if (hasClosedCaption) { @@ -274,7 +282,7 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { mTransportControls.play(); } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) { mTransportControls.rewind(); - } else if (speedId >= PLAYBACK_SPEED_FAST_L0){ + } else if (speedId >= PLAYBACK_SPEED_FAST_L0) { mTransportControls.fastForward(); } } @@ -284,12 +292,10 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { mTransportControls.pause(); } - /** - * Notifies closed caption being enabled/disabled to update related UI. - */ + /** Notifies closed caption being enabled/disabled to update related UI. */ void onSubtitleTrackStateChanged(boolean enabled) { - mClosedCaptioningAction.setIndex(enabled ? - ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF); + mClosedCaptioningAction.setIndex( + enabled ? ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF); } private void onStateChanged(int state, long positionMs, int speedLevel) { @@ -344,7 +350,9 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId); DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment(); sideFragment.setArguments(args); - mFragment.getFragmentManager().beginTransaction() + mFragment + .getFragmentManager() + .beginTransaction() .hide(mFragment) .replace(R.id.dvr_playback_side_fragment, sideFragment) .addToBackStack(null) @@ -367,7 +375,7 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue { private static class MultiAudioAction extends MultiAction { MultiAudioAction(Context context) { super(AUDIO_ACTION_ID); - setDrawables(new Drawable[]{context.getDrawable(R.drawable.ic_tvoption_multi_track)}); + setDrawables(new Drawable[] {context.getDrawable(R.drawable.ic_tvoption_multi_track)}); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java index 843d2dbe..3ff90aa4 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java @@ -28,10 +28,8 @@ import android.media.tv.TvContract; import android.os.AsyncTask; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.dvr.DvrWatchedPositionManager; @@ -55,49 +53,52 @@ class DvrPlaybackMediaSessionHelper { private final DvrWatchedPositionManager mDvrWatchedPositionManager; private final ChannelDataManager mChannelDataManager; - public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag, - DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) { + public DvrPlaybackMediaSessionHelper( + Activity activity, + String mediaSessionTag, + DvrPlayer dvrPlayer, + DvrPlaybackOverlayFragment overlayFragment) { mActivity = activity; mDvrPlayer = dvrPlayer; mDvrWatchedPositionManager = TvApplication.getSingletons(activity).getDvrWatchedPositionManager(); mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager(); - mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() { - @Override - public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { - updateMediaSessionPlaybackState(); - } + mDvrPlayer.setCallback( + new DvrPlayer.DvrPlayerCallback() { + @Override + public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { + updateMediaSessionPlaybackState(); + } - @Override - public void onPlaybackPositionChanged(long positionMs) { - updateMediaSessionPlaybackState(); - if (mDvrPlayer.isPlaybackPrepared()) { - mDvrWatchedPositionManager - .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs); - } - } + @Override + public void onPlaybackPositionChanged(long positionMs) { + updateMediaSessionPlaybackState(); + if (mDvrPlayer.isPlaybackPrepared()) { + mDvrWatchedPositionManager.setWatchedPosition( + mDvrPlayer.getProgram().getId(), positionMs); + } + } - @Override - public void onPlaybackEnded() { - // TODO: Deal with watched over recordings in DVR library - RecordedProgram nextEpisode = - overlayFragment.getNextEpisode(mDvrPlayer.getProgram()); - if (nextEpisode == null) { - mDvrPlayer.reset(); - mActivity.finish(); - } else { - Intent intent = new Intent(activity, DvrPlaybackActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId()); - mActivity.startActivity(intent); - } - } - }); + @Override + public void onPlaybackEnded() { + // TODO: Deal with watched over recordings in DVR library + RecordedProgram nextEpisode = + overlayFragment.getNextEpisode(mDvrPlayer.getProgram()); + if (nextEpisode == null) { + mDvrPlayer.reset(); + mActivity.finish(); + } else { + Intent intent = new Intent(activity, DvrPlaybackActivity.class); + intent.putExtra( + Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId()); + mActivity.startActivity(intent); + } + } + }); initializeMediaSession(mediaSessionTag); } - /** - * Stops DVR player and release media session. - */ + /** Stops DVR player and release media session. */ public void release() { if (mDvrPlayer != null) { mDvrPlayer.reset(); @@ -108,13 +109,15 @@ class DvrPlaybackMediaSessionHelper { } } - /** - * Updates media session's playback state and speed. - */ + /** Updates media session's playback state and speed. */ public void updateMediaSessionPlaybackState() { - mMediaSession.setPlaybackState(new PlaybackState.Builder() - .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(), - mSpeedLevel).build()); + mMediaSession.setPlaybackState( + new PlaybackState.Builder() + .setState( + mDvrPlayer.getPlaybackState(), + mDvrPlayer.getPlaybackPosition(), + mSpeedLevel) + .build()); } /** @@ -132,42 +135,35 @@ class DvrPlaybackMediaSessionHelper { } } - /** - * Returns the recorded program now playing. - */ + /** Returns the recorded program now playing. */ public RecordedProgram getProgram() { return mDvrPlayer.getProgram(); } - /** - * Checks if the recorded program is the same as now playing one. - */ + /** Checks if the recorded program is the same as now playing one. */ public boolean isCurrentProgram(RecordedProgram program) { return program != null && program.equals(getProgram()); } - /** - * Returns playback state. - */ + /** Returns playback state. */ public int getPlaybackState() { return mDvrPlayer.getPlaybackState(); } - /** - * Returns the underlying DVR player. - */ + /** Returns the underlying DVR player. */ public DvrPlayer getDvrPlayer() { return mDvrPlayer; } private void initializeMediaSession(String mediaSessionTag) { mMediaSession = new MediaSession(mActivity, mediaSessionTag); - mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); - mNowPlayingCardWidth = mActivity.getResources() - .getDimensionPixelSize(R.dimen.notif_card_img_max_width); - mNowPlayingCardHeight = mActivity.getResources() - .getDimensionPixelSize(R.dimen.notif_card_img_height); + mMediaSession.setFlags( + MediaSession.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mNowPlayingCardWidth = + mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); + mNowPlayingCardHeight = + mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); mMediaSession.setCallback(new MediaSessionCallback()); mActivity.setMediaController( new MediaController(mActivity, mMediaSession.getSessionToken())); @@ -179,11 +175,17 @@ class DvrPlaybackMediaSessionHelper { String cardTitleText = program.getTitle(); if (TextUtils.isEmpty(cardTitleText)) { Channel channel = mChannelDataManager.getChannel(program.getChannelId()); - cardTitleText = (channel != null) ? channel.getDisplayName() - : mActivity.getString(R.string.no_program_information); + cardTitleText = + (channel != null) + ? channel.getDisplayName() + : mActivity.getString(R.string.no_program_information); } - final MediaMetadata currentMetadata = updateMetadataTextInfo(program.getId(), cardTitleText, - program.getDescription(), mProgramDurationMs); + final MediaMetadata currentMetadata = + updateMetadataTextInfo( + program.getId(), + cardTitleText, + program.getDescription(), + mProgramDurationMs); String posterArtUri = program.getPosterArtUri(); if (posterArtUri == null) { posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString(); @@ -192,12 +194,18 @@ class DvrPlaybackMediaSessionHelper { mMediaSession.setActive(true); } - private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata, - @Nullable Bitmap posterArt, @Nullable String posterArtUri) { + private void updatePosterArt( + RecordedProgram program, + MediaMetadata currentMetadata, + @Nullable Bitmap posterArt, + @Nullable String posterArtUri) { if (posterArt != null) { updateMetadataImageInfo(program, currentMetadata, posterArt, 0); } else if (posterArtUri != null) { - ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth, + ImageLoader.loadBitmap( + mActivity, + posterArtUri, + mNowPlayingCardWidth, mNowPlayingCardHeight, new ProgramPosterArtCallback(mActivity, program, currentMetadata)); } else { @@ -205,13 +213,12 @@ class DvrPlaybackMediaSessionHelper { } } - private class ProgramPosterArtCallback extends - ImageLoader.ImageLoaderCallback { + private class ProgramPosterArtCallback extends ImageLoader.ImageLoaderCallback { private final RecordedProgram mRecordedProgram; private final MediaMetadata mCurrentMetadata; - public ProgramPosterArtCallback(Activity activity, RecordedProgram program, - MediaMetadata metadata) { + public ProgramPosterArtCallback( + Activity activity, RecordedProgram program, MediaMetadata metadata) { super(activity); mRecordedProgram = program; mCurrentMetadata = metadata; @@ -225,8 +232,8 @@ class DvrPlaybackMediaSessionHelper { } } - private MediaMetadata updateMetadataTextInfo(final long programId, final String title, - final String subtitle, final long duration) { + private MediaMetadata updateMetadataTextInfo( + final long programId, final String title, final String subtitle, final long duration) { MediaMetadata.Builder builder = new MediaMetadata.Builder(); builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(programId)) .putString(MediaMetadata.METADATA_KEY_TITLE, title) @@ -239,8 +246,11 @@ class DvrPlaybackMediaSessionHelper { return metadata; } - private void updateMetadataImageInfo(final RecordedProgram program, - final MediaMetadata currentMetadata, final Bitmap posterArt, final int imageResId) { + private void updateMetadataImageInfo( + final RecordedProgram program, + final MediaMetadata currentMetadata, + final Bitmap posterArt, + final int imageResId) { if (mMediaSession != null && (posterArt != null || imageResId != 0)) { MediaMetadata.Builder builder = new MediaMetadata.Builder(currentMetadata); if (posterArt != null) { @@ -255,7 +265,8 @@ class DvrPlaybackMediaSessionHelper { @Override protected void onPostExecute(Bitmap programPosterArt) { - if (mMediaSession != null && programPosterArt != null + if (mMediaSession != null + && programPosterArt != null && isCurrentProgram(program)) { builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt); mMediaSession.setMetadata(builder.build()); diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java index 783ae682..c5fccda2 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java @@ -21,12 +21,12 @@ import android.content.Context; import android.content.Intent; import android.graphics.Point; import android.hardware.display.DisplayManager; -import android.media.tv.TvContentRating; -import android.media.tv.TvTrackInfo; -import android.os.Bundle; import android.media.session.PlaybackState; +import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; +import android.media.tv.TvTrackInfo; import android.media.tv.TvView; +import android.os.Bundle; import android.support.v17.leanback.app.PlaybackFragment; import android.support.v17.leanback.app.PlaybackFragmentGlueHost; import android.support.v17.leanback.widget.ArrayObjectAdapter; @@ -37,12 +37,11 @@ import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.RowPresenter; import android.support.v17.leanback.widget.SinglePresenterSelector; +import android.util.Log; import android.view.Display; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; -import android.util.Log; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.data.BaseProgram; @@ -57,9 +56,8 @@ import com.android.tv.parental.ContentRatingsManager; import com.android.tv.util.TvSettings; import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; - -import java.util.List; import java.util.ArrayList; +import java.util.List; public class DvrPlaybackOverlayFragment extends PlaybackFragment { // TODO: Handles audio focus. Deals with block and ratings. @@ -104,15 +102,25 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { public void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); - mVerticalPaddingBase = getActivity().getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base); - mPaddingWithoutRelatedRow = getActivity().getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row); - mPaddingWithoutSecondaryRow = getActivity().getResources() - .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row); + mVerticalPaddingBase = + getActivity() + .getResources() + .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base); + mPaddingWithoutRelatedRow = + getActivity() + .getResources() + .getDimensionPixelOffset( + R.dimen.dvr_playback_overlay_padding_top_no_related_row); + mPaddingWithoutSecondaryRow = + getActivity() + .getResources() + .getDimensionPixelOffset( + R.dimen.dvr_playback_overlay_padding_top_no_secondary_row); mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); - mContentRatingsManager = TvApplication.getSingletons(getContext()) - .getTvInputManagerHelper().getContentRatingsManager(); + mContentRatingsManager = + TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper() + .getContentRatingsManager(); if (!mDvrDataManager.isRecordedProgramLoadFinished()) { mDvrDataManager.addRecordedProgramLoadFinishedListener( new DvrDataManager.OnRecordedProgramLoadFinishedListener() { @@ -124,14 +132,14 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { preparePlayback(getActivity().getIntent()); } } - } - ); + }); } else if (!handleIntent(getActivity().getIntent(), true)) { return; } Point size = new Point(); ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) - .getDisplay(Display.DEFAULT_DISPLAY).getSize(size); + .getDisplay(Display.DEFAULT_DISPLAY) + .getSize(size); mWindowWidth = size.x; mWindowHeight = size.y; mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight; @@ -152,19 +160,20 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view); mBlockScreenView = getActivity().findViewById(R.id.block_screen); mDvrPlayer = new DvrPlayer(mTvView); - mMediaSessionHelper = new DvrPlaybackMediaSessionHelper( - getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this); + mMediaSessionHelper = + new DvrPlaybackMediaSessionHelper( + getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this); mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this); mRelatedRecordingsRow = getRelatedRecordingsRow(); mDvrPlayer.setOnTracksAvailabilityChangedListener( new DvrPlayer.OnTracksAvailabilityChangedListener() { @Override - public void onTracksAvailabilityChanged(boolean hasClosedCaption, - boolean hasMultiAudio) { + public void onTracksAvailabilityChanged( + boolean hasClosedCaption, boolean hasMultiAudio) { mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio); if (hasClosedCaption) { - mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, - mOnSubtitleTrackSelectedListener); + mDvrPlayer.setOnTrackSelectedListener( + TvTrackInfo.TYPE_SUBTITLE, mOnSubtitleTrackSelectedListener); selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE); } else { mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null); @@ -175,15 +184,18 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { updateVerticalPosition(); mPlaybackControlHelper.getHost().notifyPlaybackRowChanged(); } - }); - mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() { - @Override - public void onAspectRatioChanged(float videoAspectRatio) { - updateAspectRatio(videoAspectRatio); - } - }); - mPinChecked = getActivity().getIntent() - .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false); + }); + mDvrPlayer.setOnAspectRatioChangedListener( + new DvrPlayer.OnAspectRatioChangedListener() { + @Override + public void onAspectRatioChanged(float videoAspectRatio) { + updateAspectRatio(videoAspectRatio); + } + }); + mPinChecked = + getActivity() + .getIntent() + .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false); mDvrPlayer.setOnContentBlockedListener( new DvrPlayer.OnContentBlockedListener() { @Override @@ -221,20 +233,25 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { PinDialogFragment.DIALOG_TAG); } }); - setOnItemViewClickedListener(new BaseOnItemViewClickedListener() { - @Override - public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, - RowPresenter.ViewHolder rowViewHolder, Object row) { - if (itemViewHolder.view instanceof RecordingCardView) { - setFadingEnabled(false); - long programId = ((RecordedProgram) itemViewHolder.view.getTag()).getId(); - if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId); - Intent intent = new Intent(getContext(), DvrPlaybackActivity.class); - intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); - getContext().startActivity(intent); - } - } - }); + setOnItemViewClickedListener( + new BaseOnItemViewClickedListener() { + @Override + public void onItemClicked( + Presenter.ViewHolder itemViewHolder, + Object item, + RowPresenter.ViewHolder rowViewHolder, + Object row) { + if (itemViewHolder.view instanceof RecordingCardView) { + setFadingEnabled(false); + long programId = + ((RecordedProgram) itemViewHolder.view.getTag()).getId(); + if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId); + Intent intent = new Intent(getContext(), DvrPlaybackActivity.class); + intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); + getContext().startActivity(intent); + } + } + }); if (mProgram != null) { setUpRows(); preparePlayback(getActivity().getIntent()); @@ -265,9 +282,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { super.onDestroy(); } - /** - * Passes the intent to the fragment. - */ + /** Passes the intent to the fragment. */ public void onNewIntent(Intent intent) { if (mDvrDataManager.isRecordedProgramLoadFinished() && handleIntent(intent, false)) { preparePlayback(intent); @@ -275,8 +290,8 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { } /** - * Should be called when windows' size is changed in order to notify DVR player - * to update it's view width/height and position. + * Should be called when windows' size is changed in order to notify DVR player to update it's + * view width/height and position. */ public void onWindowSizeChanged(final int windowWidth, final int windowHeight) { mWindowWidth = windowWidth; @@ -285,9 +300,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { updateAspectRatio(mAppliedAspectRatio); } - /** - * Returns next recorded episode in the same series as now playing program. - */ + /** Returns next recorded episode in the same series as now playing program. */ public RecordedProgram getNextEpisode(RecordedProgram program) { int position = mRelatedRecordingsRowAdapter.findInsertPosition(program); if (position == mRelatedRecordingsRowAdapter.size()) { @@ -299,9 +312,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { /** * Returns the tracks of the give type of the current playback. - - * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} - * or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}. + * + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link + * TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}. */ public ArrayList getTracks(int trackType) { if (trackType == TvTrackInfo.TYPE_AUDIO) { @@ -312,18 +325,16 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { return null; } - /** - * Returns the ID of the selected track of the given type. - */ + /** Returns the ID of the selected track of the given type. */ public String getSelectedTrackId(int trackType) { return mDvrPlayer.getSelectedTrackId(trackType); } /** * Returns the language setting of the given track type. - - * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} - * or {@link TvTrackInfo#TYPE_AUDIO}. + * + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link + * TvTrackInfo#TYPE_AUDIO}. * @return {@code null} if no language has been set for the given track type. */ TvTrackInfo getTrackSetting(int trackType) { @@ -332,10 +343,11 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { /** * Selects the given audio or subtitle track for DVR playback. - * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} - * or {@link TvTrackInfo#TYPE_AUDIO}. + * + * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link + * TvTrackInfo#TYPE_AUDIO}. * @param selectedTrack {@code null} to disable the audio or subtitle track according to - * trackType. + * trackType. */ void selectTrack(int trackType, TvTrackInfo selectedTrack) { if (mDvrPlayer.isPlaybackPrepared()) { @@ -346,8 +358,11 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { private boolean handleIntent(Intent intent, boolean finishActivity) { mProgram = getProgramFromIntent(intent); if (mProgram == null) { - Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found), - Toast.LENGTH_SHORT).show(); + Toast.makeText( + getActivity(), + getString(R.string.dvr_program_not_found), + Toast.LENGTH_SHORT) + .show(); if (finishActivity) { getActivity().finish(); } @@ -359,12 +374,18 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { private void selectBestMatchedTrack(int trackType) { TvTrackInfo selectedTrack = getTrackSetting(trackType); if (selectedTrack != null) { - TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType), - selectedTrack.getId(), selectedTrack.getLanguage(), - trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0); - if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils - .isEqualLanguage(bestMatchedTrack.getLanguage(), - selectedTrack.getLanguage()))) { + TvTrackInfo bestMatchedTrack = + TvTrackInfoUtils.getBestTrackInfo( + getTracks(trackType), + selectedTrack.getId(), + selectedTrack.getLanguage(), + trackType == TvTrackInfo.TYPE_AUDIO + ? selectedTrack.getAudioChannelCount() + : 0); + if (bestMatchedTrack != null + && (trackType == TvTrackInfo.TYPE_AUDIO + || Utils.isEqualLanguage( + bestMatchedTrack.getLanguage(), selectedTrack.getLanguage()))) { selectTrack(trackType, bestMatchedTrack); return; } @@ -421,7 +442,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { } if (mRelatedRecordingsRowAdapter.size() == 0) { mRowsAdapter.remove(mRelatedRecordingsRow); - } else if (wasEmpty){ + } else if (wasEmpty) { mRowsAdapter.add(mRelatedRecordingsRow); } updateVerticalPosition(); @@ -446,8 +467,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { private ListRow getRelatedRecordingsRow() { mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity()); mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter); - HeaderItem header = new HeaderItem(0, - getActivity().getString(R.string.dvr_playback_related_recordings)); + HeaderItem header = + new HeaderItem( + 0, getActivity().getString(R.string.dvr_playback_related_recordings)); return new ListRow(header, mRelatedRecordingsRowAdapter); } @@ -457,8 +479,8 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { } private long getSeekTimeFromIntent(Intent intent) { - return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, - TvInputManager.TIME_SHIFT_INVALID_TIME); + return intent.getLongExtra( + Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, TvInputManager.TIME_SHIFT_INVALID_TIME); } private void updateVerticalPosition() { @@ -491,4 +513,4 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { return item.getId(); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java index e49870f1..b4481df8 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java @@ -26,24 +26,16 @@ import android.transition.Transition; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; import com.android.tv.util.TvSettings; - import java.util.List; import java.util.Locale; -/** - * Fragment for DVR playback closed-caption/multi-audio settings. - */ +/** Fragment for DVR playback closed-caption/multi-audio settings. */ public class DvrPlaybackSideFragment extends GuidedStepFragment { - /** - * The tag for passing track infos to side fragments. - */ + /** The tag for passing track infos to side fragments. */ public static final String TRACK_INFOS = "dvr_key_track_infos"; - /** - * The tag for passing selected track's ID to side fragments. - */ + /** The tag for passing selected track's ID to side fragments. */ public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id"; private static final int ACTION_ID_NO_SUBTITLE = -1; @@ -60,39 +52,42 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment { mTrackInfos = getArguments().getParcelableArrayList(TRACK_INFOS); mTrackType = mTrackInfos.get(0).getType(); mSelectedTrackId = getArguments().getString(SELECTED_TRACK_ID); - mOverlayFragment = ((DvrPlaybackOverlayFragment) getFragmentManager() - .findFragmentById(R.id.dvr_playback_controls_fragment)); + mOverlayFragment = + ((DvrPlaybackOverlayFragment) + getFragmentManager().findFragmentById(R.id.dvr_playback_controls_fragment)); super.onCreate(savedInstanceState); } @Override - public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateBackgroundView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View backgroundView = super.onCreateBackgroundView(inflater, container, savedInstanceState); - backgroundView.setBackgroundColor(getResources() - .getColor(R.color.lb_playback_controls_background_light)); + backgroundView.setBackgroundColor( + getResources().getColor(R.color.lb_playback_controls_background_light)); return backgroundView; } @Override public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) { if (mTrackType == TvTrackInfo.TYPE_SUBTITLE) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ID_NO_SUBTITLE) - .title(getString(R.string.closed_caption_option_item_off)) - .checkSetId(CHECK_SET_ID) - .checked(mSelectedTrackId == null) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ID_NO_SUBTITLE) + .title(getString(R.string.closed_caption_option_item_off)) + .checkSetId(CHECK_SET_ID) + .checked(mSelectedTrackId == null) + .build()); } for (int i = 0; i < mTrackInfos.size(); i++) { TvTrackInfo info = mTrackInfos.get(i); boolean checked = TextUtils.equals(info.getId(), mSelectedTrackId); - GuidedAction action = new GuidedAction.Builder(getActivity()) - .id(i) - .title(getTrackLabel(info, i)) - .checkSetId(CHECK_SET_ID) - .checked(checked) - .build(); + GuidedAction action = + new GuidedAction.Builder(getActivity()) + .id(i) + .title(getTrackLabel(info, i)) + .checkSetId(CHECK_SET_ID) + .checked(checked) + .build(); actions.add(action); if (checked) { mSelectedTrack = info; @@ -136,8 +131,8 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment { if (track.getLanguage() != null) { return new Locale(track.getLanguage()).getDisplayName(); } - return track.getType() == TvTrackInfo.TYPE_SUBTITLE ? - getString(R.string.closed_caption_unknown_language, trackIndex + 1) + return track.getType() == TvTrackInfo.TYPE_SUBTITLE + ? getString(R.string.closed_caption_unknown_language, trackIndex + 1) : getString(R.string.multi_audio_unknown_language); } @@ -151,4 +146,4 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment { t.excludeTarget(R.id.guidedstep_background, true); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java index 7226c666..85bb31b2 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java @@ -24,9 +24,7 @@ import android.media.tv.TvTrackInfo; import android.media.tv.TvView; import android.text.TextUtils; import android.util.Log; - import com.android.tv.dvr.data.RecordedProgram; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -35,17 +33,13 @@ class DvrPlayer { private static final String TAG = "DvrPlayer"; private static final boolean DEBUG = false; - /** - * The max rewinding speed supported by DVR player. - */ + /** The max rewinding speed supported by DVR player. */ public static final int MAX_REWIND_SPEED = 256; - /** - * The max fast-forwarding speed supported by DVR player. - */ + /** The max fast-forwarding speed supported by DVR player. */ public static final int MAX_FAST_FORWARD_SPEED = 256; private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2); - private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826 + private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826 private RecordedProgram mProgram; private long mInitialSeekPositionMs; @@ -71,49 +65,39 @@ class DvrPlayer { public static class DvrPlayerCallback { /** - * Called when the playback position is changed. The normal updating frequency is - * around 1 sec., which is restricted to the implementation of - * {@link android.media.tv.TvInputService}. + * Called when the playback position is changed. The normal updating frequency is around 1 + * sec., which is restricted to the implementation of {@link + * android.media.tv.TvInputService}. */ - public void onPlaybackPositionChanged(long positionMs) { } - /** - * Called when the playback state or the playback speed is changed. - */ - public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { } - /** - * Called when the playback toward the end. - */ - public void onPlaybackEnded() { } + public void onPlaybackPositionChanged(long positionMs) {} + /** Called when the playback state or the playback speed is changed. */ + public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {} + /** Called when the playback toward the end. */ + public void onPlaybackEnded() {} } public interface OnAspectRatioChangedListener { /** * Called when the Video's aspect ratio is changed. * - * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. - * Listeners should handle it carefully. + * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. Listeners + * should handle it carefully. */ void onAspectRatioChanged(float videoAspectRatio); } public interface OnContentBlockedListener { - /** - * Called when the Video's aspect ratio is changed. - */ + /** Called when the Video's aspect ratio is changed. */ void onContentBlocked(TvContentRating rating); } public interface OnTracksAvailabilityChangedListener { - /** - * Called when the Video's subtitle or audio tracks are changed. - */ + /** Called when the Video's subtitle or audio tracks are changed. */ void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio); } public interface OnTrackSelectedListener { - /** - * Called when certain subtitle or audio track is selected. - */ + /** Called when certain subtitle or audio track is selected. */ void onTrackSelected(String selectedTrackId); } @@ -143,9 +127,7 @@ class DvrPlayer { mCallback.onPlaybackStateChanged(mPlaybackState, 1); } - /** - * Resumes playback. - */ + /** Resumes playback. */ public void play() throws IllegalStateException { if (DEBUG) Log.d(TAG, "play()"); if (!isPlaybackPrepared()) { @@ -163,9 +145,7 @@ class DvrPlayer { mCallback.onPlaybackStateChanged(mPlaybackState, 1); } - /** - * Pauses playback. - */ + /** Pauses playback. */ public void pause() throws IllegalStateException { if (DEBUG) Log.d(TAG, "pause()"); if (!isPlaybackPrepared()) { @@ -187,8 +167,8 @@ class DvrPlayer { } /** - * Fast-forwards playback with the given speed. If the given speed is larger than - * {@value #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}. + * Fast-forwards playback with the given speed. If the given speed is larger than {@value + * #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}. */ public void fastForward(int speed) throws IllegalStateException { if (DEBUG) Log.d(TAG, "fastForward()"); @@ -209,8 +189,8 @@ class DvrPlayer { } /** - * Rewinds playback with the given speed. If the given speed is larger than - * {@value #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}. + * Rewinds playback with the given speed. If the given speed is larger than {@value + * #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}. */ public void rewind(int speed) throws IllegalStateException { if (DEBUG) Log.d(TAG, "rewind()"); @@ -230,9 +210,7 @@ class DvrPlayer { mCallback.onPlaybackStateChanged(mPlaybackState, speed); } - /** - * Seeks playback to the specified position. - */ + /** Seeks playback to the specified position. */ public void seekTo(long positionMs) throws IllegalStateException { if (DEBUG) Log.d(TAG, "seekTo()"); if (!isPlaybackPrepared()) { @@ -244,17 +222,15 @@ class DvrPlayer { positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS); if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs); mTvView.timeShiftSeekTo(positionMs + mStartPositionMs); - if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING || - mPlaybackState == PlaybackState.STATE_REWINDING) { + if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING + || mPlaybackState == PlaybackState.STATE_REWINDING) { mPlaybackState = PlaybackState.STATE_PLAYING; mTvView.timeShiftResume(); mCallback.onPlaybackStateChanged(mPlaybackState, 1); } } - /** - * Resets playback. - */ + /** Resets playback. */ public void reset() { if (DEBUG) Log.d(TAG, "reset()"); mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1); @@ -269,9 +245,7 @@ class DvrPlayer { mSelectedSubtitleTrackId = null; } - /** - * Sets callbacks for playback. - */ + /** Sets callbacks for playback. */ public void setCallback(DvrPlayerCallback callback) { if (callback != null) { mCallback = callback; @@ -280,23 +254,17 @@ class DvrPlayer { } } - /** - * Sets the listener to aspect ratio changing. - */ + /** Sets the listener to aspect ratio changing. */ public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) { mOnAspectRatioChangedListener = listener; } - /** - * Sets the listener to content blocking. - */ + /** Sets the listener to content blocking. */ public void setOnContentBlockedListener(OnContentBlockedListener listener) { mOnContentBlockedListener = listener; } - /** - * Sets the listener to tracks changing. - */ + /** Sets the listener to tracks changing. */ public void setOnTracksAvailabilityChangedListener( OnTracksAvailabilityChangedListener listener) { mOnTracksAvailabilityChangedListener = listener; @@ -305,8 +273,8 @@ class DvrPlayer { /** * Sets the listener to tracks of the given type being selected. * - * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} - * or {@link TvTrackInfo#TYPE_SUBTITLE}. + * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} or {@link + * TvTrackInfo#TYPE_SUBTITLE}. */ public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) { if (trackType == TvTrackInfo.TYPE_AUDIO) { @@ -316,9 +284,7 @@ class DvrPlayer { } } - /** - * Gets the listener to tracks of the given type being selected. - */ + /** Gets the listener to tracks of the given type being selected. */ public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) { if (trackType == TvTrackInfo.TYPE_AUDIO) { return mOnAudioTrackSelectedListener; @@ -328,9 +294,7 @@ class DvrPlayer { return null; } - /** - * Sets recorded programs for playback. If the player is playing another program, stops it. - */ + /** Sets recorded programs for playback. If the player is playing another program, stops it. */ public void setProgram(RecordedProgram program, long initialSeekPositionMs) { if (mProgram != null && mProgram.equals(program)) { return; @@ -342,51 +306,37 @@ class DvrPlayer { mProgram = program; } - /** - * Returns the recorded program now playing. - */ + /** Returns the recorded program now playing. */ public RecordedProgram getProgram() { return mProgram; } - /** - * Returns the currrent playback posistion in msecs. - */ + /** Returns the currrent playback posistion in msecs. */ public long getPlaybackPosition() { return mTimeShiftCurrentPositionMs; } - /** - * Returns the playback speed currently used. - */ + /** Returns the playback speed currently used. */ public int getPlaybackSpeed() { return (int) mPlaybackParams.getSpeed(); } - /** - * Returns the playback state defined in {@link android.media.session.PlaybackState}. - */ + /** Returns the playback state defined in {@link android.media.session.PlaybackState}. */ public int getPlaybackState() { return mPlaybackState; } - /** - * Returns the subtitle tracks of the current playback. - */ + /** Returns the subtitle tracks of the current playback. */ public ArrayList getSubtitleTracks() { return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE)); } - /** - * Returns the audio tracks of the current playback. - */ + /** Returns the audio tracks of the current playback. */ public ArrayList getAudioTracks() { return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO)); } - /** - * Returns the ID of the selected track of the given type. - */ + /** Returns the ID of the selected track of the given type. */ public String getSelectedTrackId(int trackType) { if (trackType == TvTrackInfo.TYPE_AUDIO) { return mSelectedAudioTrackId; @@ -396,9 +346,7 @@ class DvrPlayer { return null; } - /** - * Returns if playback of the recorded program is started. - */ + /** Returns if playback of the recorded program is started. */ public boolean isPlaybackPrepared() { return mPlaybackState != PlaybackState.STATE_NONE && mPlaybackState != PlaybackState.STATE_CONNECTING; @@ -449,125 +397,138 @@ class DvrPlayer { } private void setTvViewCallbacks() { - mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() { - @Override - public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { - if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs); - mStartPositionMs = timeMs; - if (mTimeShiftPlayAvailable) { - resumeToWatchedPositionIfNeeded(); - } - } + mTvView.setTimeShiftPositionCallback( + new TvView.TimeShiftPositionCallback() { + @Override + public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { + if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs); + mStartPositionMs = timeMs; + if (mTimeShiftPlayAvailable) { + resumeToWatchedPositionIfNeeded(); + } + } - @Override - public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { - if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs); - if (!mTimeShiftPlayAvailable) { - // Workaround of b/31436263 - return; - } - // Workaround of b/32211561, TIF won't report start position when TIS report - // its start position as 0. In that case, we have to do the prework of playback - // on the first time we get current position, and the start position should be 0 - // at that time. - if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) { - mStartPositionMs = 0; - resumeToWatchedPositionIfNeeded(); - } - timeMs -= mStartPositionMs; - if (mPlaybackState == PlaybackState.STATE_REWINDING - && timeMs <= REWIND_POSITION_MARGIN_MS) { - play(); - } else { - mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0); - mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs); - if (timeMs >= mProgram.getDurationMillis()) { - pause(); - mCallback.onPlaybackEnded(); + @Override + public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { + if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs); + if (!mTimeShiftPlayAvailable) { + // Workaround of b/31436263 + return; + } + // Workaround of b/32211561, TIF won't report start position when TIS report + // its start position as 0. In that case, we have to do the prework of + // playback + // on the first time we get current position, and the start position should + // be 0 + // at that time. + if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) { + mStartPositionMs = 0; + resumeToWatchedPositionIfNeeded(); + } + timeMs -= mStartPositionMs; + if (mPlaybackState == PlaybackState.STATE_REWINDING + && timeMs <= REWIND_POSITION_MARGIN_MS) { + play(); + } else { + mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0); + mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs); + if (timeMs >= mProgram.getDurationMillis()) { + pause(); + mCallback.onPlaybackEnded(); + } + } } - } - } - }); - mTvView.setCallback(new TvView.TvInputCallback() { - @Override - public void onTimeShiftStatusChanged(String inputId, int status) { - if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status); - if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE - && mPlaybackState == PlaybackState.STATE_CONNECTING) { - mTimeShiftPlayAvailable = true; - if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - // onTimeShiftStatusChanged is sometimes called after - // onTimeShiftStartPositionChanged is called. In this case, - // resumeToWatchedPositionIfNeeded needs to be called here. - resumeToWatchedPositionIfNeeded(); + }); + mTvView.setCallback( + new TvView.TvInputCallback() { + @Override + public void onTimeShiftStatusChanged(String inputId, int status) { + if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status); + if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE + && mPlaybackState == PlaybackState.STATE_CONNECTING) { + mTimeShiftPlayAvailable = true; + if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { + // onTimeShiftStatusChanged is sometimes called after + // onTimeShiftStartPositionChanged is called. In this case, + // resumeToWatchedPositionIfNeeded needs to be called here. + resumeToWatchedPositionIfNeeded(); + } + } } - } - } - - @Override - public void onTracksChanged(String inputId, List tracks) { - boolean hasClosedCaption = - !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty(); - boolean hasMultiAudio = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1; - if ((hasClosedCaption != mHasClosedCaption || hasMultiAudio != mHasMultiAudio) - && mOnTracksAvailabilityChangedListener != null) { - mOnTracksAvailabilityChangedListener - .onTracksAvailabilityChanged(hasClosedCaption, hasMultiAudio); - } - mHasClosedCaption = hasClosedCaption; - mHasMultiAudio = hasMultiAudio; - } - @Override - public void onTrackSelected(String inputId, int type, String trackId) { - if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) { - setSelectedTrackId(type, trackId); - OnTrackSelectedListener listener = getOnTrackSelectedListener(type); - if (listener != null) { - listener.onTrackSelected(trackId); + @Override + public void onTracksChanged(String inputId, List tracks) { + boolean hasClosedCaption = + !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty(); + boolean hasMultiAudio = + mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1; + if ((hasClosedCaption != mHasClosedCaption + || hasMultiAudio != mHasMultiAudio) + && mOnTracksAvailabilityChangedListener != null) { + mOnTracksAvailabilityChangedListener.onTracksAvailabilityChanged( + hasClosedCaption, hasMultiAudio); + } + mHasClosedCaption = hasClosedCaption; + mHasMultiAudio = hasMultiAudio; } - } else if (type == TvTrackInfo.TYPE_VIDEO && trackId != null - && mOnAspectRatioChangedListener != null) { - List trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO); - if (trackInfos != null) { - for (TvTrackInfo trackInfo : trackInfos) { - if (trackInfo.getId().equals(trackId)) { - float videoAspectRatio; - int videoWidth = trackInfo.getVideoWidth(); - int videoHeight = trackInfo.getVideoHeight(); - if (videoWidth > 0 && videoHeight > 0) { - videoAspectRatio = trackInfo.getVideoPixelAspectRatio() - * trackInfo.getVideoWidth() / trackInfo.getVideoHeight(); - } else { - // Aspect ratio is unknown. Pass the message to listeners. - videoAspectRatio = 0; - } - if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio); - if (mAspectRatio != videoAspectRatio || videoAspectRatio == 0) { - mOnAspectRatioChangedListener - .onAspectRatioChanged(videoAspectRatio); - mAspectRatio = videoAspectRatio; - return; + + @Override + public void onTrackSelected(String inputId, int type, String trackId) { + if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) { + setSelectedTrackId(type, trackId); + OnTrackSelectedListener listener = getOnTrackSelectedListener(type); + if (listener != null) { + listener.onTrackSelected(trackId); + } + } else if (type == TvTrackInfo.TYPE_VIDEO + && trackId != null + && mOnAspectRatioChangedListener != null) { + List trackInfos = + mTvView.getTracks(TvTrackInfo.TYPE_VIDEO); + if (trackInfos != null) { + for (TvTrackInfo trackInfo : trackInfos) { + if (trackInfo.getId().equals(trackId)) { + float videoAspectRatio; + int videoWidth = trackInfo.getVideoWidth(); + int videoHeight = trackInfo.getVideoHeight(); + if (videoWidth > 0 && videoHeight > 0) { + videoAspectRatio = + trackInfo.getVideoPixelAspectRatio() + * trackInfo.getVideoWidth() + / trackInfo.getVideoHeight(); + } else { + // Aspect ratio is unknown. Pass the message to + // listeners. + videoAspectRatio = 0; + } + if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio); + if (mAspectRatio != videoAspectRatio + || videoAspectRatio == 0) { + mOnAspectRatioChangedListener.onAspectRatioChanged( + videoAspectRatio); + mAspectRatio = videoAspectRatio; + return; + } + } } } } } - } - } - @Override - public void onContentBlocked(String inputId, TvContentRating rating) { - if (mOnContentBlockedListener != null) { - mOnContentBlockedListener.onContentBlocked(rating); - } - } - }); + @Override + public void onContentBlocked(String inputId, TvContentRating rating) { + if (mOnContentBlockedListener != null) { + mOnContentBlockedListener.onContentBlocked(rating); + } + } + }); } private void resumeToWatchedPositionIfNeeded() { if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { - mTvView.timeShiftSeekTo(getRealSeekPosition(mInitialSeekPositionMs, - SEEK_POSITION_MARGIN_MS) + mStartPositionMs); + mTvView.timeShiftSeekTo( + getRealSeekPosition(mInitialSeekPositionMs, SEEK_POSITION_MARGIN_MS) + + mStartPositionMs); mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; } if (mPauseOnPrepared) { @@ -580,4 +541,4 @@ class DvrPlayer { } mCallback.onPlaybackStateChanged(mPlaybackState, 1); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/src/com/android/tv/experiments/ExperimentFlag.java index c0cbd643..2963482e 100644 --- a/src/com/android/tv/experiments/ExperimentFlag.java +++ b/src/com/android/tv/experiments/ExperimentFlag.java @@ -18,9 +18,7 @@ package com.android.tv.experiments; import android.support.annotation.VisibleForTesting; -/** - * Experiments return values based on user, device and other criteria. - */ +/** Experiments return values based on user, device and other criteria. */ public final class ExperimentFlag { private static boolean sAllowOverrides = false; @@ -31,10 +29,8 @@ public final class ExperimentFlag { } /** Returns a boolean experiment */ - public static ExperimentFlag createFlag( - boolean defaultValue) { - return new ExperimentFlag<>( - defaultValue); + public static ExperimentFlag createFlag(boolean defaultValue) { + return new ExperimentFlag<>(defaultValue); } private final T mDefaultValue; @@ -42,8 +38,7 @@ public final class ExperimentFlag { private T mOverrideValue = null; private boolean mOverridden = false; - private ExperimentFlag( - T defaultValue) { + private ExperimentFlag(T defaultValue) { mDefaultValue = defaultValue; } @@ -64,7 +59,4 @@ public final class ExperimentFlag { public void resetOverride() { mOverridden = false; } - - - } diff --git a/src/com/android/tv/experiments/Experiments.java b/src/com/android/tv/experiments/Experiments.java index 53cce979..0bff384e 100644 --- a/src/com/android/tv/experiments/Experiments.java +++ b/src/com/android/tv/experiments/Experiments.java @@ -26,20 +26,17 @@ import com.android.tv.common.BuildConfig; *

This file is maintained by hand. */ public final class Experiments { - public static final ExperimentFlag CLOUD_EPG = createFlag( - true); + public static final ExperimentFlag CLOUD_EPG = createFlag(true); - public static final ExperimentFlag ENABLE_UNRATED_CONTENT_SETTINGS = - createFlag( - false); + public static final ExperimentFlag ENABLE_UNRATED_CONTENT_SETTINGS = createFlag(false); /** * Allow developer features such as the dev menu and other aids. * *

These features are available to select users(aka fishfooders) on production builds. */ - public static final ExperimentFlag ENABLE_DEVELOPER_FEATURES = createFlag( - BuildConfig.ENG); + public static final ExperimentFlag ENABLE_DEVELOPER_FEATURES = + createFlag(BuildConfig.ENG); private Experiments() {} } diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java index ce19eb2d..b4baf421 100644 --- a/src/com/android/tv/guide/GenreListAdapter.java +++ b/src/com/android/tv/guide/GenreListAdapter.java @@ -24,15 +24,11 @@ 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.data.GenreItems; - import java.util.List; -/** - * Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. - */ +/** Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. */ class GenreListAdapter extends RecyclerView.Adapter { private static final String TAG = "GenreListAdapter"; private static final boolean DEBUG = false; @@ -45,13 +41,14 @@ class GenreListAdapter extends RecyclerView.Adapter focusables = new ArrayList<>(); findFocusables(programRow, focusables); @@ -102,9 +99,10 @@ class GuideUtils { maxFullyOverlappedWidth = width; } } else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) { - int overlappedWidth = (focusRangeLeft <= focusableRect.left) ? - focusRangeRight - focusableRect.left - : focusableRect.right - focusRangeLeft; + int overlappedWidth = + (focusRangeLeft <= focusableRect.left) + ? focusRangeRight - focusableRect.left + : focusableRect.right - focusRangeLeft; if (overlappedWidth > maxPartiallyOverlappedWidth) { nextFocusIndex = i; maxPartiallyOverlappedWidth = overlappedWidth; @@ -118,16 +116,14 @@ class GuideUtils { } /** - * Returns {@code true} if the program displayed in the give - * {@link com.android.tv.guide.ProgramItemView} is a current program. + * Returns {@code true} if the program displayed in the give {@link + * com.android.tv.guide.ProgramItemView} is a current program. */ static boolean isCurrentProgram(ProgramItemView view) { return view.getTableEntry().isCurrentProgram(); } - /** - * Returns {@code true} if the given view is a descendant of the give container. - */ + /** Returns {@code true} if the given view is a descendant of the give container. */ static boolean isDescendant(ViewGroup container, View view) { if (view == null) { return false; @@ -152,5 +148,5 @@ class GuideUtils { } } - private GuideUtils() { } + private GuideUtils() {} } diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java index 58436425..caafb045 100644 --- a/src/com/android/tv/guide/ProgramGrid.java +++ b/src/com/android/tv/guide/ProgramGrid.java @@ -25,15 +25,11 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver; - import com.android.tv.R; import com.android.tv.ui.OnRepeatedKeyInterceptListener; - import java.util.concurrent.TimeUnit; -/** - * A {@link VerticalGridView} for the program table view. - */ +/** A {@link VerticalGridView} for the program table view. */ public class ProgramGrid extends VerticalGridView { private static final String TAG = "ProgramGrid"; @@ -84,7 +80,7 @@ public class ProgramGrid extends VerticalGridView { private final int mRowHeight; private final int mDetailHeight; - private final int mSelectionRow; // Row that is focused + private final int mSelectionRow; // Row that is focused private View mLastFocusedView; private final Rect mTempRect = new Rect(); @@ -97,8 +93,8 @@ public class ProgramGrid extends VerticalGridView { interface ChildFocusListener { /** - * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed. - * See {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}. + * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed. See + * {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}. */ void onRequestChildFocus(View oldFocus, View newFocus); } @@ -207,16 +203,13 @@ public class ProgramGrid extends VerticalGridView { } /** - * Initializes ProgramGrid. It should be called before the view is actually attached to - * Window. + * Initializes ProgramGrid. It should be called before the view is actually attached to Window. */ void initialize(ProgramManager programManager) { mProgramManager = programManager; } - /** - * Registers a listener focus events occurring on children to the {@code ProgramGrid}. - */ + /** Registers a listener focus events occurring on children to the {@code ProgramGrid}. */ void setChildFocusListener(ChildFocusListener childFocusListener) { mChildFocusListener = childFocusListener; } @@ -226,8 +219,8 @@ public class ProgramGrid extends VerticalGridView { } /** - * Resets focus states. If the logic to keep the last focus needs to be cleared, it should - * be called. + * Resets focus states. If the logic to keep the last focus needs to be cleared, it should be + * called. */ void resetFocusState() { mLastFocusedView = null; @@ -255,8 +248,8 @@ public class ProgramGrid extends VerticalGridView { Log.w(TAG, "No child view has focus"); return null; } - int nextChildIndex = direction == View.FOCUS_UP ? focusedChildIndex - 1 - : focusedChildIndex + 1; + int nextChildIndex = + direction == View.FOCUS_UP ? focusedChildIndex - 1 : focusedChildIndex + 1; if (nextChildIndex < 0 || nextChildIndex >= getChildCount()) { // Wraparound if reached head or end if (getSelectedPosition() == 0) { @@ -268,8 +261,12 @@ public class ProgramGrid extends VerticalGridView { } return focused; } - View nextFocusedProgram = GuideUtils.findNextFocusedProgram(getChildAt(nextChildIndex), - mFocusRangeLeft, mFocusRangeRight, mKeepCurrentProgramFocused); + View nextFocusedProgram = + GuideUtils.findNextFocusedProgram( + getChildAt(nextChildIndex), + mFocusRangeLeft, + mFocusRangeRight, + mKeepCurrentProgramFocused); if (nextFocusedProgram != null) { nextFocusedProgram.getGlobalVisibleRect(mTempRect); mNextFocusByUpDown = nextFocusedProgram; @@ -320,8 +317,9 @@ public class ProgramGrid extends VerticalGridView { mFocusRangeRight = getRightMostFocusablePosition(); mNextFocusByUpDown = null; // If focus is not a program item, drop focus to the current program when back to the grid - mKeepCurrentProgramFocused = !(focus instanceof ProgramItemView) - || GuideUtils.isCurrentProgram((ProgramItemView) focus); + mKeepCurrentProgramFocused = + !(focus instanceof ProgramItemView) + || GuideUtils.isCurrentProgram((ProgramItemView) focus); } private int getRightMostFocusablePosition() { diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index dd5444e2..20c3ccdf 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -43,12 +43,10 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; - import com.android.tv.ChannelTuner; import com.android.tv.Features; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; import com.android.tv.data.ChannelDataManager; @@ -58,16 +56,14 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; import com.android.tv.ui.ViewUtils; +import com.android.tv.util.DurationTimer; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -/** - * The program guide. - */ +/** The program guide. */ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private static final String TAG = "ProgramGuide"; private static final boolean DEBUG = false; @@ -83,8 +79,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { // We clip out the first program entry in ProgramManager, if it does not have enough width. // In order to prevent from clipping out the current program, this value need be larger than // or equal to ProgramManager.FIRST_ENTRY_MIN_DURATION. - private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME - = ProgramManager.FIRST_ENTRY_MIN_DURATION; + private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME = + ProgramManager.FIRST_ENTRY_MIN_DURATION; private static final int MSG_PROGRAM_TABLE_FADE_IN_ANIM = 1000; @@ -103,7 +99,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private final long mViewPortMillis; private final int mRowHeight; private final int mDetailHeight; - private final int mSelectionRow; // Row that is focused + private final int mSelectionRow; // Row that is focused private final int mTableFadeAnimDuration; private final int mAnimationDuration; private final int mDetailPadding; @@ -145,34 +141,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private final Handler mHandler = new ProgramGuideHandler(this); private boolean mActive; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hide(); - } - }; + private final Runnable mHideRunnable = + new Runnable() { + @Override + public void run() { + hide(); + } + }; private final long mShowDurationMillis; private ViewTreeObserver.OnGlobalLayoutListener mOnLayoutListenerForShow; private final ProgramManagerListener mProgramManagerListener = new ProgramManagerListener(); - private final Runnable mUpdateTimeIndicator = new Runnable() { - @Override - public void run() { - positionCurrentTimeIndicator(); - mHandler.postAtTime(this, - Utils.ceilTime(SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY)); - } - }; - - public ProgramGuide(MainActivity activity, ChannelTuner channelTuner, - TvInputManagerHelper tvInputManagerHelper, ChannelDataManager channelDataManager, - ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager, - @Nullable DvrScheduleManager dvrScheduleManager, Tracker tracker, - Runnable preShowRunnable, Runnable postHideRunnable) { + private final Runnable mUpdateTimeIndicator = + new Runnable() { + @Override + public void run() { + positionCurrentTimeIndicator(); + mHandler.postAtTime( + this, + Utils.ceilTime( + SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY)); + } + }; + + public ProgramGuide( + MainActivity activity, + ChannelTuner channelTuner, + TvInputManagerHelper tvInputManagerHelper, + ChannelDataManager channelDataManager, + ProgramDataManager programDataManager, + @Nullable DvrDataManager dvrDataManager, + @Nullable DvrScheduleManager dvrScheduleManager, + Tracker tracker, + Runnable preShowRunnable, + Runnable postHideRunnable) { mActivity = activity; - mProgramManager = new ProgramManager(tvInputManagerHelper, channelDataManager, - programDataManager, dvrDataManager, dvrScheduleManager); + mProgramManager = + new ProgramManager( + tvInputManagerHelper, + channelDataManager, + programDataManager, + dvrDataManager, + dvrScheduleManager); mChannelTuner = channelTuner; mTracker = tracker; mPreShowRunnable = preShowRunnable; @@ -185,9 +196,11 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Point displaySize = new Point(); mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize); - int gridWidth = displaySize.x - - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start) - - res.getDimensionPixelSize(R.dimen.program_guide_table_header_column_width); + int gridWidth = + displaySize.x + - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start) + - res.getDimensionPixelSize( + R.dimen.program_guide_table_header_column_width); mViewPortMillis = (gridWidth * HOUR_IN_MILLIS) / mWidthPerHour; mRowHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_item_row_height); @@ -201,43 +214,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding); mContainer = mActivity.findViewById(R.id.program_guide); - ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener - = new GlobalFocusChangeListener(); + ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener = + new GlobalFocusChangeListener(); mContainer.getViewTreeObserver().addOnGlobalFocusChangeListener(globalFocusChangeListener); GenreListAdapter genreListAdapter = new GenreListAdapter(mActivity, mProgramManager, this); mSidePanel = mContainer.findViewById(R.id.program_guide_side_panel); - mSidePanelGridView = (VerticalGridView) mContainer.findViewById( - R.id.program_guide_side_panel_grid_view); - mSidePanelGridView.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_side_panel_row, - res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row)); + mSidePanelGridView = + (VerticalGridView) mContainer.findViewById(R.id.program_guide_side_panel_grid_view); + mSidePanelGridView + .getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_side_panel_row, + res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row)); mSidePanelGridView.setAdapter(genreListAdapter); mSidePanelGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); - mSidePanelGridView.setWindowAlignmentOffset(mActivity.getResources() - .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y)); + mSidePanelGridView.setWindowAlignmentOffset( + mActivity + .getResources() + .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y)); mSidePanelGridView.setWindowAlignmentOffsetPercent( VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); if (Features.EPG_SEARCH.isEnabled(mActivity)) { - mSearchOrb = (SearchOrbView) mContainer.findViewById( - R.id.program_guide_side_panel_search_orb); + mSearchOrb = + (SearchOrbView) + mContainer.findViewById(R.id.program_guide_side_panel_search_orb); mSearchOrb.setVisibility(View.VISIBLE); - mSearchOrb.setOnOrbClickedListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - hide(); - mActivity.showProgramGuideSearchFragment(); - } - }); + mSearchOrb.setOnOrbClickedListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + hide(); + mActivity.showProgramGuideSearchFragment(); + } + }); mSidePanelGridView.setOnChildSelectedListener( new android.support.v17.leanback.widget.OnChildSelectedListener() { - @Override - public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) { - mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f); - } - }); + @Override + public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) { + mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f); + } + }); } else { mSearchOrb = null; } @@ -246,134 +265,155 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mTimelineRow = (TimelineRow) mTable.findViewById(R.id.time_row); mTimeListAdapter = new TimeListAdapter(res); - mTimelineRow.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_table_header_row_item, - res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item)); + mTimelineRow + .getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_table_header_row_item, + res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item)); mTimelineRow.setAdapter(mTimeListAdapter); ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, this); - programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - // It is usually called when Genre is changed. - // Reset selection of ProgramGrid - resetRowSelection(); - updateGuidePosition(); - } - }); + programTableAdapter.registerAdapterDataObserver( + new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + // It is usually called when Genre is changed. + // Reset selection of ProgramGrid + resetRowSelection(); + updateGuidePosition(); + } + }); mGrid = (ProgramGrid) mTable.findViewById(R.id.grid); mGrid.initialize(mProgramManager); - mGrid.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_table_row, - res.getInteger(R.integer.max_recycled_view_pool_epg_table_row)); + mGrid.getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_table_row, + res.getInteger(R.integer.max_recycled_view_pool_epg_table_row)); mGrid.setAdapter(programTableAdapter); mGrid.setChildFocusListener(this); - mGrid.setOnChildSelectedListener(new OnChildSelectedListener() { - @Override - public void onChildSelected(ViewGroup parent, View view, int position, long id) { - if (mIsDuringResetRowSelection) { - // Ignore if it's during the first resetRowSelection, because onChildSelected - // will be called again when rows are bound to the program table. if selectRow - // is called here, mSelectedRow is set and the second selectRow call doesn't - // work as intended. - mIsDuringResetRowSelection = false; - return; - } - selectRow(view); - } - }); + mGrid.setOnChildSelectedListener( + new OnChildSelectedListener() { + @Override + public void onChildSelected( + ViewGroup parent, View view, int position, long id) { + if (mIsDuringResetRowSelection) { + // Ignore if it's during the first resetRowSelection, because + // onChildSelected + // will be called again when rows are bound to the program table. if + // selectRow + // is called here, mSelectedRow is set and the second selectRow call + // doesn't + // work as intended. + mIsDuringResetRowSelection = false; + return; + } + selectRow(view); + } + }); mGrid.setFocusScrollStrategy(ProgramGrid.FOCUS_SCROLL_ALIGNED); mGrid.setWindowAlignmentOffset(mSelectionRow * mRowHeight); mGrid.setWindowAlignmentOffsetPercent(ProgramGrid.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); mGrid.setItemAlignmentOffset(0); mGrid.setItemAlignmentOffsetPercent(ProgramGrid.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); - RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - onHorizontalScrolled(dx); - } - }; + RecyclerView.OnScrollListener onScrollListener = + new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + onHorizontalScrolled(dx); + } + }; mTimelineRow.addOnScrollListener(onScrollListener); mCurrentTimeIndicator = mTable.findViewById(R.id.current_time_indicator); - mShowAnimatorFull = createAnimator( - R.animator.program_guide_side_panel_enter_full, - 0, - R.animator.program_guide_table_enter_full); - - mShowAnimatorPartial = createAnimator( - R.animator.program_guide_side_panel_enter_partial, - 0, - R.animator.program_guide_table_enter_partial); - mShowAnimatorPartial.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mSidePanelGridView.setVisibility(View.VISIBLE); - mSidePanelGridView.setAlpha(1.0f); - } - }); - - mHideAnimatorFull = createAnimator( - R.animator.program_guide_side_panel_exit, - 0, - R.animator.program_guide_table_exit); - mHideAnimatorFull.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainer.setVisibility(View.GONE); - } - }); - mHideAnimatorPartial = createAnimator( - R.animator.program_guide_side_panel_exit, - 0, - R.animator.program_guide_table_exit); - mHideAnimatorPartial.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainer.setVisibility(View.GONE); - } - }); - - mPartialToFullAnimator = createAnimator( - R.animator.program_guide_side_panel_hide, - R.animator.program_guide_side_panel_grid_fade_out, - R.animator.program_guide_table_partial_to_full); - mFullToPartialAnimator = createAnimator( - R.animator.program_guide_side_panel_reveal, - R.animator.program_guide_side_panel_grid_fade_in, - R.animator.program_guide_table_full_to_partial); - - mProgramTableFadeOutAnimator = AnimatorInflater.loadAnimator(mActivity, - R.animator.program_guide_table_fade_out); + mShowAnimatorFull = + createAnimator( + R.animator.program_guide_side_panel_enter_full, + 0, + R.animator.program_guide_table_enter_full); + + mShowAnimatorPartial = + createAnimator( + R.animator.program_guide_side_panel_enter_partial, + 0, + R.animator.program_guide_table_enter_partial); + mShowAnimatorPartial.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mSidePanelGridView.setVisibility(View.VISIBLE); + mSidePanelGridView.setAlpha(1.0f); + } + }); + + mHideAnimatorFull = + createAnimator( + R.animator.program_guide_side_panel_exit, + 0, + R.animator.program_guide_table_exit); + mHideAnimatorFull.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mContainer.setVisibility(View.GONE); + } + }); + mHideAnimatorPartial = + createAnimator( + R.animator.program_guide_side_panel_exit, + 0, + R.animator.program_guide_table_exit); + mHideAnimatorPartial.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mContainer.setVisibility(View.GONE); + } + }); + + mPartialToFullAnimator = + createAnimator( + R.animator.program_guide_side_panel_hide, + R.animator.program_guide_side_panel_grid_fade_out, + R.animator.program_guide_table_partial_to_full); + mFullToPartialAnimator = + createAnimator( + R.animator.program_guide_side_panel_reveal, + R.animator.program_guide_side_panel_grid_fade_in, + R.animator.program_guide_table_full_to_partial); + + mProgramTableFadeOutAnimator = + AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_out); mProgramTableFadeOutAnimator.setTarget(mTable); - mProgramTableFadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable) { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - - if (!isActive()) { - return; - } - mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); - resetTimelineScroll(); - if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { - mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - } - } - }); - mProgramTableFadeInAnimator = AnimatorInflater.loadAnimator(mActivity, - R.animator.program_guide_table_fade_in); + mProgramTableFadeOutAnimator.addListener( + new HardwareLayerAnimatorListenerAdapter(mTable) { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + if (!isActive()) { + return; + } + mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); + resetTimelineScroll(); + if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { + mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM); + } + } + }); + mProgramTableFadeInAnimator = + AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_in); mProgramTableFadeInAnimator.setTarget(mTable); mProgramTableFadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable)); mSharedPreference = PreferenceManager.getDefaultSharedPreferences(mActivity); mAccessibilityManager = (AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE); - mShowGuidePartial = mAccessibilityManager.isEnabled() - || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); + mShowGuidePartial = + mAccessibilityManager.isEnabled() + || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); } @Override @@ -397,12 +437,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } /** - * Show the program guide. This reveals the side panel, and the program guide table is shown + * Show the program guide. This reveals the side panel, and the program guide table is shown * partially. * - *

Note: the animation which starts together with ProgramGuide showing animation needs to - * be initiated in {@code runnableAfterAnimatorReady}. If the animation starts together - * with show(), the animation may drop some frames. + *

Note: the animation which starts together with ProgramGuide showing animation needs to be + * initiated in {@code runnableAfterAnimatorReady}. If the animation starts together with + * show(), the animation may drop some frames. */ public void show(final Runnable runnableAfterAnimatorReady) { if (mContainer.getVisibility() == View.VISIBLE) { @@ -416,9 +456,10 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mVisibleDuration.start(); mProgramManager.programGuideVisibilityChanged(true); - mStartUtcTime = Utils.floorTime( - System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME, - HALF_HOUR_IN_MILLIS); + mStartUtcTime = + Utils.floorTime( + System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME, + HALF_HOUR_IN_MILLIS); mProgramManager.updateInitialTimeRange(mStartUtcTime, mStartUtcTime + mViewPortMillis); mProgramManager.addListener(mProgramManagerListener); mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS; @@ -435,51 +476,60 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { if (DEBUG) { Log.d(TAG, "show()"); } - mOnLayoutListenerForShow = new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this); - mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mTable.buildLayer(); - mSidePanelGridView.buildLayer(); - mOnLayoutListenerForShow = null; - mTimelineAnimation = true; - // Make sure that time indicator update starts after animation is finished. - startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY); - if (DEBUG) { - mContainer.getViewTreeObserver().addOnDrawListener( - new ViewTreeObserver.OnDrawListener() { - long time = System.currentTimeMillis(); - int count = 0; - - @Override - public void onDraw() { - long curtime = System.currentTimeMillis(); - Log.d(TAG, "onDraw " + count++ + " " + (curtime - time) + "ms"); - time = curtime; - if (count > 10) { - mContainer.getViewTreeObserver().removeOnDrawListener(this); - } - } - }); - } - updateGuidePosition(); - runnableAfterAnimatorReady.run(); - if (mShowGuidePartial) { - mShowAnimatorPartial.start(); - } else { - mShowAnimatorFull.start(); - } - } - }; + mOnLayoutListenerForShow = + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mTable.buildLayer(); + mSidePanelGridView.buildLayer(); + mOnLayoutListenerForShow = null; + mTimelineAnimation = true; + // Make sure that time indicator update starts after animation is finished. + startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY); + if (DEBUG) { + mContainer + .getViewTreeObserver() + .addOnDrawListener( + new ViewTreeObserver.OnDrawListener() { + long time = System.currentTimeMillis(); + int count = 0; + + @Override + public void onDraw() { + long curtime = System.currentTimeMillis(); + Log.d( + TAG, + "onDraw " + + count++ + + " " + + (curtime - time) + + "ms"); + time = curtime; + if (count > 10) { + mContainer + .getViewTreeObserver() + .removeOnDrawListener(this); + } + } + }); + } + updateGuidePosition(); + runnableAfterAnimatorReady.run(); + if (mShowGuidePartial) { + mShowAnimatorPartial.start(); + } else { + mShowAnimatorFull.start(); + } + } + }; mContainer.getViewTreeObserver().addOnGlobalLayoutListener(mOnLayoutListenerForShow); scheduleHide(); } - /** - * Hide the program guide. - */ + /** Hide the program guide. */ public void hide() { if (!isActive()) { return; @@ -516,52 +566,44 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - /** - * Schedules hiding the program guide. - */ + /** Schedules hiding the program guide. */ public void scheduleHide() { cancelHide(); mHandler.postDelayed(mHideRunnable, mShowDurationMillis); } - /** - * Cancels hiding the program guide. - */ + /** Cancels hiding the program guide. */ public void cancelHide() { mHandler.removeCallbacks(mHideRunnable); } - /** - * Process the {@code KEYCODE_BACK} key event. - */ + /** Process the {@code KEYCODE_BACK} key event. */ public void onBackPressed() { hide(); } - /** - * Returns {@code true} if the program guide should process the input events. - */ + /** Returns {@code true} if the program guide should process the input events. */ public boolean isActive() { return mActive; } /** - * Returns {@code true} if the program guide is shown, i.e. showing animation is done and - * hiding animation is not started yet. + * Returns {@code true} if the program guide is shown, i.e. showing animation is done and hiding + * animation is not started yet. */ public boolean isRunningAnimation() { - return mShowAnimatorPartial.isStarted() || mShowAnimatorFull.isStarted() - || mHideAnimatorPartial.isStarted() || mHideAnimatorFull.isStarted(); + return mShowAnimatorPartial.isStarted() + || mShowAnimatorFull.isStarted() + || mHideAnimatorPartial.isStarted() + || mHideAnimatorFull.isStarted(); } - /** Returns if program table is in full screen mode. **/ + /** Returns if program table is in full screen mode. * */ boolean isFull() { return !mShowGuidePartial; } - /** - * Requests change genre to {@code genreId}. - */ + /** Requests change genre to {@code genreId}. */ void requestGenreChange(int genreId) { if (mLastRequestedGenreId == genreId) { // When Recycler.onLayout() removes its children to recycle, @@ -575,15 +617,15 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { // When requestGenreChange is called repeatedly in short time, we keep the fade-out // state for mTableFadeAnimDuration from now. Without it, we'll see blinks. mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM, - mTableFadeAnimDuration); + mHandler.sendEmptyMessageDelayed( + MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration); return; } if (mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM, - mTableFadeAnimDuration); + mHandler.sendEmptyMessageDelayed( + MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration); return; } if (mProgramTableFadeInAnimator.isStarted()) { @@ -593,9 +635,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mProgramTableFadeOutAnimator.start(); } - /** - * Returns the scroll offset of the time line row in pixels. - */ + /** Returns the scroll offset of the time line row in pixels. */ int getTimelineRowScrollOffset() { return mTimelineRow.getScrollOffset(); } @@ -605,9 +645,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { return mGrid; } - /** - * Gets {@link VerticalGridView} for "genre select" side panel. - */ + /** Gets {@link VerticalGridView} for "genre select" side panel. */ VerticalGridView getSidePanel() { return mSidePanelGridView; } @@ -628,9 +666,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { int startPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start); int topPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_top); int bottomPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_bottom); - int tableHeight = res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) - + mDetailHeight + mRowHeight * mGrid.getAdapter().getItemCount() + topPadding - + bottomPadding; + int tableHeight = + res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) + + mDetailHeight + + mRowHeight * mGrid.getAdapter().getItemCount() + + topPadding + + bottomPadding; if (tableHeight > screenHeight) { // EPG height is longer that the screen height. mTable.setPaddingRelative(startPadding, topPadding, 0, 0); @@ -645,8 +686,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId, - int tableAnimResId) { + private Animator createAnimator( + int sidePanelAnimResId, int sidePanelGridAnimResId, int tableAnimResId) { List animatorList = new ArrayList<>(); Animator sidePanelAnimator = AnimatorInflater.loadAnimator(mActivity, sidePanelAnimResId); @@ -654,8 +695,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { animatorList.add(sidePanelAnimator); if (sidePanelGridAnimResId != 0) { - Animator sidePanelGridAnimator = AnimatorInflater.loadAnimator(mActivity, - sidePanelGridAnimResId); + Animator sidePanelGridAnimator = + AnimatorInflater.loadAnimator(mActivity, sidePanelGridAnimResId); sidePanelGridAnimator.setTarget(mSidePanelGridView); sidePanelGridAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(mSidePanelGridView)); @@ -700,8 +741,9 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } private void positionCurrentTimeIndicator() { - int offset = GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis()) - - mTimelineRow.getScrollOffset(); + int offset = + GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis()) + - mTimelineRow.getScrollOffset(); if (offset < 0) { mCurrentTimeIndicator.setVisibility(View.GONE); } else { @@ -743,8 +785,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mSelectedRow = null; mIsDuringResetRowSelection = true; mGrid.setSelectedPosition( - Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), - 0)); + Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), 0)); mGrid.resetFocusState(); mGrid.onItemSelectionReset(); mIsDuringResetRowSelection = false; @@ -767,12 +808,13 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { detailView.setVisibility(View.VISIBLE); final ProgramRow programRow = (ProgramRow) row.findViewById(R.id.row); - programRow.post(new Runnable() { - @Override - public void run() { - programRow.focusCurrentProgram(); - } - }); + programRow.post( + new Runnable() { + @Override + public void run() { + programRow.focusCurrentProgram(); + } + }); } else { animateRowChange(mSelectedRow, row); } @@ -799,38 +841,45 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { if (outDetail != null && outDetail.isShown()) { final View outDetailContent = outDetail.findViewById(R.id.detail_content_full); - Animator fadeOutAnimator = ObjectAnimator.ofPropertyValuesHolder(outDetailContent, - PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - outDetailContent.getTranslationY(), animationPadding)); + Animator fadeOutAnimator = + ObjectAnimator.ofPropertyValuesHolder( + outDetailContent, + PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_Y, + outDetailContent.getTranslationY(), + animationPadding)); fadeOutAnimator.setStartDelay(0); fadeOutAnimator.setDuration(mAnimationDuration); fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent)); - Animator collapseAnimator = ViewUtils - .createHeightAnimator(outDetail, ViewUtils.getLayoutHeight(outDetail), 0); + Animator collapseAnimator = + ViewUtils.createHeightAnimator( + outDetail, ViewUtils.getLayoutHeight(outDetail), 0); collapseAnimator.setStartDelay(mAnimationDuration); collapseAnimator.setDuration(mTableFadeAnimDuration); - collapseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - outDetailContent.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - outDetailContent.setVisibility(View.VISIBLE); - } - }); + collapseAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + outDetailContent.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + outDetailContent.setVisibility(View.VISIBLE); + } + }); AnimatorSet outAnimator = new AnimatorSet(); outAnimator.playTogether(fadeOutAnimator, collapseAnimator); - outAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mDetailOutAnimator = null; - } - }); + outAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mDetailOutAnimator = null; + } + }); mDetailOutAnimator = outAnimator; outAnimator.start(); } @@ -842,39 +891,44 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Animator expandAnimator = ViewUtils.createHeightAnimator(inDetail, 0, mDetailHeight); expandAnimator.setStartDelay(mAnimationDuration); expandAnimator.setDuration(mTableFadeAnimDuration); - expandAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - inDetailContent.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - inDetailContent.setVisibility(View.VISIBLE); - inDetailContent.setAlpha(0); - } - }); - Animator fadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(inDetailContent, - PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -animationPadding, 0f)); + expandAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + inDetailContent.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + inDetailContent.setVisibility(View.VISIBLE); + inDetailContent.setAlpha(0); + } + }); + Animator fadeInAnimator = + ObjectAnimator.ofPropertyValuesHolder( + inDetailContent, + PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_Y, -animationPadding, 0f)); fadeInAnimator.setDuration(mAnimationDuration); fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent)); AnimatorSet inAnimator = new AnimatorSet(); inAnimator.playSequentially(expandAnimator, fadeInAnimator); - inAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mDetailInAnimator = null; - } - }); + inAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mDetailInAnimator = null; + } + }); mDetailInAnimator = inAnimator; inAnimator.start(); } } - private class GlobalFocusChangeListener implements - ViewTreeObserver.OnGlobalFocusChangeListener { + private class GlobalFocusChangeListener + implements ViewTreeObserver.OnGlobalFocusChangeListener { private static final int UNKNOWN = 0; private static final int SIDE_PANEL = 1; private static final int PROGRAM_TABLE = 2; @@ -912,11 +966,16 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private class ProgramManagerListener extends ProgramManager.ListenerAdapter { @Override public void onTimeRangeUpdated() { - int scrollOffset = (int) (mWidthPerHour * mProgramManager.getShiftedTime() - / HOUR_IN_MILLIS); + int scrollOffset = + (int) (mWidthPerHour * mProgramManager.getShiftedTime() / HOUR_IN_MILLIS); if (DEBUG) { - Log.d(TAG, "Horizontal scroll to " + scrollOffset + " pixels (" - + mProgramManager.getShiftedTime() + " millis)"); + Log.d( + TAG, + "Horizontal scroll to " + + scrollOffset + + " pixels (" + + mProgramManager.getShiftedTime() + + " millis)"); } mTimelineRow.scrollTo(scrollOffset, mTimelineAnimation); } diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index b23d578c..c8c7ff2d 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -35,7 +35,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; @@ -49,7 +48,6 @@ import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; - import java.lang.reflect.InvocationTargetException; import java.util.concurrent.TimeUnit; @@ -60,10 +58,10 @@ public class ProgramItemView extends TextView { private static final int MAX_PROGRESS = 10000; // From android.widget.ProgressBar.MAX_VALUE // State indicating the focused program is the current program - private static final int[] STATE_CURRENT_PROGRAM = { R.attr.state_current_program }; + private static final int[] STATE_CURRENT_PROGRAM = {R.attr.state_current_program}; // Workaround state in order to not use too much texture memory for RippleDrawable - private static final int[] STATE_TOO_WIDE = { R.attr.state_program_too_wide }; + private static final int[] STATE_TOO_WIDE = {R.attr.state_program_too_wide}; private static int sVisibleThreshold; private static int sItemPadding; @@ -84,96 +82,119 @@ public class ProgramItemView extends TextView { // as a result of the re-layout (see b/21378855). private boolean mPreventParentRelayout; - private static final View.OnClickListener ON_CLICKED = new View.OnClickListener() { - @Override - public void onClick(final View view) { - TableEntry entry = ((ProgramItemView) view).mTableEntry; - if (entry == null) { - //do nothing - return; - } - ApplicationSingletons singletons = TvApplication.getSingletons(view.getContext()); - Tracker tracker = singletons.getTracker(); - tracker.sendEpgItemClicked(); - final MainActivity tvActivity = (MainActivity) view.getContext(); - final Channel channel = tvActivity.getChannelDataManager().getChannel(entry.channelId); - if (entry.isCurrentProgram()) { - view.postDelayed(new Runnable() { - @Override - public void run() { - tvActivity.tuneToChannel(channel); - tvActivity.hideOverlaysForTune(); + private static final View.OnClickListener ON_CLICKED = + new View.OnClickListener() { + @Override + public void onClick(final View view) { + TableEntry entry = ((ProgramItemView) view).mTableEntry; + if (entry == null) { + // do nothing + return; } - }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0 - : view.getResources() - .getInteger(R.integer.program_guide_ripple_anim_duration)); - } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) { - DvrManager dvrManager = singletons.getDvrManager(); - if (entry.entryStartUtcMillis > System.currentTimeMillis() - && dvrManager.isProgramRecordable(entry.program)) { - if (entry.scheduledRecording == null) { - DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity, - channel.getInputId(), new Runnable() { + ApplicationSingletons singletons = + TvApplication.getSingletons(view.getContext()); + Tracker tracker = singletons.getTracker(); + tracker.sendEpgItemClicked(); + final MainActivity tvActivity = (MainActivity) view.getContext(); + final Channel channel = + tvActivity.getChannelDataManager().getChannel(entry.channelId); + if (entry.isCurrentProgram()) { + view.postDelayed( + new Runnable() { @Override public void run() { - DvrUiHelper.requestRecordingFutureProgram(tvActivity, - entry.program, false); + tvActivity.tuneToChannel(channel); + tvActivity.hideOverlaysForTune(); } - }); - } else { - dvrManager.removeScheduledRecording(entry.scheduledRecording); - String msg = view.getResources().getString( - R.string.dvr_schedules_deletion_info, entry.program.getTitle()); - ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); + }, + entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple + ? 0 + : view.getResources() + .getInteger( + R.integer + .program_guide_ripple_anim_duration)); + } else if (entry.program != null + && CommonFeatures.DVR.isEnabled(view.getContext())) { + DvrManager dvrManager = singletons.getDvrManager(); + if (entry.entryStartUtcMillis > System.currentTimeMillis() + && dvrManager.isProgramRecordable(entry.program)) { + if (entry.scheduledRecording == null) { + DvrUiHelper.checkStorageStatusAndShowErrorMessage( + tvActivity, + channel.getInputId(), + new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingFutureProgram( + tvActivity, entry.program, false); + } + }); + } else { + dvrManager.removeScheduledRecording(entry.scheduledRecording); + String msg = + view.getResources() + .getString( + R.string.dvr_schedules_deletion_info, + entry.program.getTitle()); + ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); + } + } else { + ToastUtils.show( + view.getContext(), + view.getResources() + .getString(R.string.dvr_msg_cannot_record_program), + Toast.LENGTH_SHORT); + } } - } else { - ToastUtils.show(view.getContext(), view.getResources() - .getString(R.string.dvr_msg_cannot_record_program), Toast.LENGTH_SHORT); } - } - } - }; + }; private static final View.OnFocusChangeListener ON_FOCUS_CHANGED = new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (hasFocus) { - ((ProgramItemView) view).mUpdateFocus.run(); - } else { - Handler handler = view.getHandler(); - if (handler != null) { - handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus); + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) { + ((ProgramItemView) view).mUpdateFocus.run(); + } else { + Handler handler = view.getHandler(); + if (handler != null) { + handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus); + } + } } - } - } - }; - - private final Runnable mUpdateFocus = new Runnable() { - @Override - public void run() { - refreshDrawableState(); - TableEntry entry = mTableEntry; - if (entry == null) { - //do nothing - return; - } - if (entry.isCurrentProgram()) { - Drawable background = getBackground(); - if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) { - // If program guide is not active or is during showing/hiding, - // the animation is unnecessary, skip it. - background.jumpToCurrentState(); + }; + + private final Runnable mUpdateFocus = + new Runnable() { + @Override + public void run() { + refreshDrawableState(); + TableEntry entry = mTableEntry; + if (entry == null) { + // do nothing + return; + } + if (entry.isCurrentProgram()) { + Drawable background = getBackground(); + if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) { + // If program guide is not active or is during showing/hiding, + // the animation is unnecessary, skip it. + background.jumpToCurrentState(); + } + int progress = + getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); + setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); + } + if (getHandler() != null) { + getHandler() + .postAtTime( + this, + Utils.ceilTime( + SystemClock.uptimeMillis(), + FOCUS_UPDATE_FREQUENCY)); + } } - int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); - setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); - } - if (getHandler() != null) { - getHandler().postAtTime(this, - Utils.ceilTime(SystemClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY)); - } - } - }; + }; public ProgramItemView(Context context) { this(context, null); @@ -196,35 +217,46 @@ public class ProgramItemView extends TextView { } Resources res = getContext().getResources(); - sVisibleThreshold = res.getDimensionPixelOffset( - R.dimen.program_guide_table_item_visible_threshold); + sVisibleThreshold = + res.getDimensionPixelOffset(R.dimen.program_guide_table_item_visible_threshold); sItemPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_item_padding); - sCompoundDrawablePadding = res.getDimensionPixelOffset( - R.dimen.program_guide_table_item_compound_drawable_padding); - - ColorStateList programTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_program_title_text_color, null)); - ColorStateList grayedOutProgramTitleColor = res.getColorStateList( - R.color.program_guide_table_item_grayed_out_program_text_color, null); - ColorStateList episodeTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_program_episode_title_text_color, null)); - ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_grayed_out_program_episode_title_text_color, - null)); - int programTitleSize = res.getDimensionPixelSize( - R.dimen.program_guide_table_item_program_title_font_size); - int episodeTitleSize = res.getDimensionPixelSize( - R.dimen.program_guide_table_item_program_episode_title_font_size); - - sProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor, - null); - sGrayedOutProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize, - grayedOutProgramTitleColor, null); - sEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, - null); - sGrayedOutEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, - grayedOutEpisodeTitleColor, null); + sCompoundDrawablePadding = + res.getDimensionPixelOffset( + R.dimen.program_guide_table_item_compound_drawable_padding); + + ColorStateList programTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color.program_guide_table_item_program_title_text_color, null)); + ColorStateList grayedOutProgramTitleColor = + res.getColorStateList( + R.color.program_guide_table_item_grayed_out_program_text_color, null); + ColorStateList episodeTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color.program_guide_table_item_program_episode_title_text_color, + null)); + ColorStateList grayedOutEpisodeTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color + .program_guide_table_item_grayed_out_program_episode_title_text_color, + null)); + int programTitleSize = + res.getDimensionPixelSize(R.dimen.program_guide_table_item_program_title_font_size); + int episodeTitleSize = + res.getDimensionPixelSize( + R.dimen.program_guide_table_item_program_episode_title_font_size); + + sProgramTitleStyle = + new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor, null); + sGrayedOutProgramTitleStyle = + new TextAppearanceSpan(null, 0, programTitleSize, grayedOutProgramTitleColor, null); + sEpisodeTitleStyle = + new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null); + sGrayedOutEpisodeTitleStyle = + new TextAppearanceSpan(null, 0, episodeTitleSize, grayedOutEpisodeTitleColor, null); } @Override @@ -236,8 +268,9 @@ public class ProgramItemView extends TextView { @Override protected int[] onCreateDrawableState(int extraSpace) { if (mTableEntry != null) { - int states[] = super.onCreateDrawableState(extraSpace - + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); + int states[] = + super.onCreateDrawableState( + extraSpace + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); if (mTableEntry.isCurrentProgram()) { mergeDrawableStates(states, STATE_CURRENT_PROGRAM); } @@ -254,8 +287,13 @@ public class ProgramItemView extends TextView { } @SuppressLint("SwitchIntDef") - public void setValues(ProgramGuide programGuide, TableEntry entry, int selectedGenreId, - long fromUtcMillis, long toUtcMillis, String gapTitle) { + public void setValues( + ProgramGuide programGuide, + TableEntry entry, + int selectedGenreId, + long fromUtcMillis, + long toUtcMillis, + String gapTitle) { mProgramGuide = programGuide; mTableEntry = entry; @@ -264,8 +302,8 @@ public class ProgramItemView extends TextView { setLayoutParams(layoutParams); String title = entry.program != null ? entry.program.getTitle() : null; - String episode = entry.program != null ? - entry.program.getEpisodeDisplayTitle(getContext()) : null; + String episode = + entry.program != null ? entry.program.getEpisodeDisplayTitle(getContext()) : null; TextAppearanceSpan titleStyle = sGrayedOutProgramTitleStyle; TextAppearanceSpan episodeStyle = sGrayedOutEpisodeTitleStyle; @@ -298,11 +336,14 @@ public class ProgramItemView extends TextView { description.append(episode); description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - description.setSpan(episodeStyle, middle, description.length(), + description.setSpan( + episodeStyle, + middle, + description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - description.setSpan(titleStyle, 0, description.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + description.setSpan( + titleStyle, 0, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } setText(description); @@ -331,9 +372,7 @@ public class ProgramItemView extends TextView { mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); } - /** - * Update programItemView to handle alignments of text. - */ + /** Update programItemView to handle alignments of text. */ public void updateVisibleArea() { View parentView = ((View) getParent()); if (parentView == null) { @@ -341,7 +380,7 @@ public class ProgramItemView extends TextView { } if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) { layoutVisibleArea(parentView.getLeft() - getLeft(), getRight() - parentView.getRight()); - } else { + } else { layoutVisibleArea(getRight() - parentView.getRight(), parentView.getLeft() - getLeft()); } } @@ -349,16 +388,14 @@ public class ProgramItemView extends TextView { /** * Layout title and episode according to visible area. * - * Here's the spec. - * 1. Don't show text if it's shorter than 48dp. - * 2. Try showing whole text in visible area by placing and wrapping text, - * but do not wrap text less than 30min. - * 3. Episode title is visible only if title isn't multi-line. + *

Here's the spec. 1. Don't show text if it's shorter than 48dp. 2. Try showing whole text + * in visible area by placing and wrapping text, but do not wrap text less than 30min. 3. + * Episode title is visible only if title isn't multi-line. * * @param startOffset Offset of the start position from the enclosing view's start position. * @param endOffset Offset of the end position from the enclosing view's end position. */ - private void layoutVisibleArea(int startOffset, int endOffset) { + private void layoutVisibleArea(int startOffset, int endOffset) { int width = mTableEntry.getWidth(); int startPadding = Math.max(0, startOffset); int endPadding = Math.max(0, endOffset); @@ -417,11 +454,15 @@ public class ProgramItemView extends TextView { private static int getStateCount(StateListDrawable stateListDrawable) { try { - Object stateCount = StateListDrawable.class.getDeclaredMethod("getStateCount") - .invoke(stateListDrawable); + Object stateCount = + StateListDrawable.class + .getDeclaredMethod("getStateCount") + .invoke(stateListDrawable); return (int) stateCount; - } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException - |InvocationTargetException e) { + } catch (NoSuchMethodException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { Log.e(TAG, "Failed to call StateListDrawable.getStateCount()", e); return 0; } @@ -429,12 +470,15 @@ public class ProgramItemView extends TextView { private static Drawable getStateDrawable(StateListDrawable stateListDrawable, int index) { try { - Object drawable = StateListDrawable.class - .getDeclaredMethod("getStateDrawable", Integer.TYPE) - .invoke(stateListDrawable, index); + Object drawable = + StateListDrawable.class + .getDeclaredMethod("getStateDrawable", Integer.TYPE) + .invoke(stateListDrawable, index); return (Drawable) drawable; - } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException - |InvocationTargetException e) { + } catch (NoSuchMethodException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { Log.e(TAG, "Failed to call StateListDrawable.getStateDrawable(" + index + ")", e); return null; } diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index c1fcdd40..d997db1e 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -22,7 +22,6 @@ import android.util.Log; 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.guide.ProgramManager.TableEntriesUpdatedListener; @@ -111,9 +110,14 @@ class ProgramListAdapter extends RecyclerView.Adapter mEndUtcMillis) { @@ -298,10 +303,7 @@ public class ProgramManager { setTimeRange(startUtcMillis, endUtcMillis); } - - /** - * Shifts the time range by the given time. Also makes ProgramGuide scroll the views. - */ + /** Shifts the time range by the given time. Also makes ProgramGuide scroll the views. */ void shiftTime(long timeMillisToScroll) { long fromUtcMillis = mFromUtcMillis + timeMillisToScroll; long toUtcMillis = mToUtcMillis + timeMillisToScroll; @@ -316,23 +318,17 @@ public class ProgramManager { setTimeRange(fromUtcMillis, toUtcMillis); } - /** - * Returned the scrolled(shifted) time in milliseconds. - */ + /** Returned the scrolled(shifted) time in milliseconds. */ long getShiftedTime() { return mFromUtcMillis - mStartUtcMillis; } - /** - * Returns the start time set by {@link #updateInitialTimeRange}. - */ + /** Returns the start time set by {@link #updateInitialTimeRange}. */ long getStartTime() { return mStartUtcMillis; } - /** - * Returns the program index of the program with {@code entryId} or -1 if not found. - */ + /** Returns the program index of the program with {@code entryId} or -1 if not found. */ int getProgramIdIndex(long channelId, long entryId) { List entries = mChannelIdEntriesMap.get(channelId); if (entries != null) { @@ -345,38 +341,29 @@ public class ProgramManager { return -1; } - /** - * Returns the program index of the program at {@code time} or -1 if not found. - */ + /** Returns the program index of the program at {@code time} or -1 if not found. */ int getProgramIndexAtTime(long channelId, long time) { List entries = mChannelIdEntriesMap.get(channelId); for (int i = 0; i < entries.size(); ++i) { TableEntry entry = entries.get(i); - if (entry.entryStartUtcMillis <= time - && time < entry.entryEndUtcMillis) { + if (entry.entryStartUtcMillis <= time && time < entry.entryEndUtcMillis) { return i; } } return -1; } - /** - * Returns the start time of currently managed time range, in UTC millisecond. - */ + /** Returns the start time of currently managed time range, in UTC millisecond. */ long getFromUtcMillis() { return mFromUtcMillis; } - /** - * Returns the end time of currently managed time range, in UTC millisecond. - */ + /** Returns the end time of currently managed time range, in UTC millisecond. */ long getToUtcMillis() { return mToUtcMillis; } - /** - * Returns the number of the currently managed channels. - */ + /** Returns the number of the currently managed channels. */ int getChannelCount() { return mFilteredChannels.size(); } @@ -393,15 +380,15 @@ public class ProgramManager { } /** - * Returns the index of provided {@link Channel} within the currently managed channels. - * Returns -1 if such a channel is not found. + * Returns the index of provided {@link Channel} within the currently managed channels. Returns + * -1 if such a channel is not found. */ int getChannelIndex(Channel channel) { return mFilteredChannels.indexOf(channel); } /** - * Returns the index of channel with {@code channelId} within the currently managed channels. + * Returns the index of channel with {@code channelId} within the currently managed channels. * Returns -1 if such a channel is not found. */ int getChannelIndex(long channelId) { @@ -425,9 +412,7 @@ public class ProgramManager { return mChannelIdEntriesMap.get(channelId).get(index); } - /** - * Returns list genre ID's which has a channel. - */ + /** Returns list genre ID's which has a channel. */ List getFilteredGenreIds() { return mFilteredGenreIds; } @@ -457,15 +442,13 @@ public class ProgramManager { buildGenreFilters(); } - /** - * Updates the table entries without notifying the change. - */ + /** Updates the table entries without notifying the change. */ private void updateTableEntriesWithoutNotification(boolean clear) { if (clear) { mChannelIdEntriesMap.clear(); } - boolean parentalControlsEnabled = mTvInputManagerHelper.getParentalControlSettings() - .isParentalControlsEnabled(); + boolean parentalControlsEnabled = + mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled(); for (Channel channel : mChannels) { long channelId = channel.getId(); // Inline the updating of the mChannelIdEntriesMap here so we can only call @@ -475,8 +458,12 @@ public class ProgramManager { int size = entries.size(); if (DEBUG) { - Log.d(TAG, "Programs are loaded for channel " + channel.getId() - + ", loaded size = " + size); + Log.d( + TAG, + "Programs are loaded for channel " + + channel.getId() + + ", loaded size = " + + size); } if (size == 0) { continue; @@ -496,14 +483,19 @@ public class ProgramManager { } else { TableEntry lastEntry = entries.get(entries.size() - 1); if (mEndUtcMillis > lastEntry.entryEndUtcMillis) { - entries.add(new TableEntry(channelId, lastEntry.entryEndUtcMillis, - mEndUtcMillis)); + entries.add( + new TableEntry( + channelId, lastEntry.entryEndUtcMillis, mEndUtcMillis)); } else if (lastEntry.entryEndUtcMillis == Long.MAX_VALUE) { entries.remove(entries.size() - 1); - entries.add(new TableEntry(lastEntry.channelId, lastEntry.program, - lastEntry.scheduledRecording, - lastEntry.entryStartUtcMillis, mEndUtcMillis, - lastEntry.mIsBlocked)); + entries.add( + new TableEntry( + lastEntry.channelId, + lastEntry.program, + lastEntry.scheduledRecording, + lastEntry.entryStartUtcMillis, + mEndUtcMillis, + lastEntry.mIsBlocked)); } } } @@ -511,11 +503,10 @@ public class ProgramManager { } /** - * Build genre filters based on the current programs. - * This categories channels by its current program's canonical genres - * and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list - * with built channel list. - * This is expected to be called whenever program guide is shown. + * Build genre filters based on the current programs. This categories channels by its current + * program's canonical genres and subsequent @{link resetChannelListWithGenre(int)} calls will + * reset channel list with built channel list. This is expected to be called whenever program + * guide is shown. */ private void buildGenreFilters() { if (DEBUG) Log.d(TAG, "buildGenreFilters"); @@ -572,9 +563,13 @@ public class ProgramManager { private void setTimeRange(long fromUtcMillis, long toUtcMillis) { if (DEBUG) { - Log.d(TAG, "setTimeRange. {FromTime=" - + Utils.toTimeString(fromUtcMillis) + ", ToTime=" - + Utils.toTimeString(toUtcMillis) + "}"); + Log.d( + TAG, + "setTimeRange. {FromTime=" + + Utils.toTimeString(fromUtcMillis) + + ", ToTime=" + + Utils.toTimeString(toUtcMillis) + + "}"); } if (mFromUtcMillis != fromUtcMillis || mToUtcMillis != toUtcMillis) { mFromUtcMillis = fromUtcMillis; @@ -585,8 +580,8 @@ public class ProgramManager { private List createProgramEntries(long channelId, boolean parentalControlsEnabled) { List entries = new ArrayList<>(); - boolean channelLocked = parentalControlsEnabled - && mChannelDataManager.getChannel(channelId).isLocked(); + boolean channelLocked = + parentalControlsEnabled && mChannelDataManager.getChannel(channelId).isLocked(); if (channelLocked) { entries.add(new TableEntry(channelId, mStartUtcMillis, Long.MAX_VALUE, true)); } else { @@ -597,20 +592,27 @@ public class ProgramManager { // Dummy program. continue; } - long programStartTime = Math.max(program.getStartTimeUtcMillis(), - mStartUtcMillis); + long programStartTime = Math.max(program.getStartTimeUtcMillis(), mStartUtcMillis); long programEndTime = program.getEndTimeUtcMillis(); if (programStartTime > lastProgramEndTime) { // Gap since the last program. - entries.add(new TableEntry(channelId, lastProgramEndTime, - programStartTime)); + entries.add(new TableEntry(channelId, lastProgramEndTime, programStartTime)); lastProgramEndTime = programStartTime; } if (programEndTime > lastProgramEndTime) { - ScheduledRecording scheduledRecording = mDvrDataManager == null ? null - : mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); - entries.add(new TableEntry(channelId, program, scheduledRecording, - lastProgramEndTime, programEndTime, false)); + ScheduledRecording scheduledRecording = + mDvrDataManager == null + ? null + : mDvrDataManager.getScheduledRecordingForProgramId( + program.getId()); + entries.add( + new TableEntry( + channelId, + program, + scheduledRecording, + lastProgramEndTime, + programEndTime, + false)); lastProgramEndTime = programEndTime; } } @@ -622,9 +624,15 @@ public class ProgramManager { // If the first entry's width doesn't have enough width, it is not good to show // the first entry from UI perspective. So we clip it out. entries.remove(0); - entries.set(0, new TableEntry(secondEntry.channelId, secondEntry.program, - secondEntry.scheduledRecording, mStartUtcMillis, - secondEntry.entryEndUtcMillis, secondEntry.mIsBlocked)); + entries.set( + 0, + new TableEntry( + secondEntry.channelId, + secondEntry.program, + secondEntry.scheduledRecording, + mStartUtcMillis, + secondEntry.entryEndUtcMillis, + secondEntry.mIsBlocked)); } } return entries; @@ -662,8 +670,8 @@ public class ProgramManager { /** * Entry for program guide table. An "entry" can be either an actual program or a gap between - * programs. This is needed for {@link ProgramListAdapter} because - * {@link android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items. + * programs. This is needed for {@link ProgramListAdapter} because {@link + * android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items. */ static class TableEntry { /** Channel ID which this entry is included. */ @@ -686,18 +694,27 @@ public class ProgramManager { this(channelId, null, startUtcMillis, endUtcMillis, false); } - private TableEntry(long channelId, long startUtcMillis, long endUtcMillis, - boolean blocked) { + private TableEntry( + long channelId, long startUtcMillis, long endUtcMillis, boolean blocked) { this(channelId, null, null, startUtcMillis, endUtcMillis, blocked); } - private TableEntry(long channelId, Program program, long entryStartUtcMillis, - long entryEndUtcMillis, boolean isBlocked) { + private TableEntry( + long channelId, + Program program, + long entryStartUtcMillis, + long entryEndUtcMillis, + boolean isBlocked) { this(channelId, program, null, entryStartUtcMillis, entryEndUtcMillis, isBlocked); } - private TableEntry(long channelId, Program program, ScheduledRecording scheduledRecording, - long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) { + private TableEntry( + long channelId, + Program program, + ScheduledRecording scheduledRecording, + long entryStartUtcMillis, + long entryEndUtcMillis, + boolean isBlocked) { this.channelId = channelId; this.program = program; this.scheduledRecording = scheduledRecording; @@ -706,46 +723,34 @@ public class ProgramManager { mIsBlocked = isBlocked; } - /** - * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. - */ + /** A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. */ long getId() { // using a negative entryEndUtcMillis keeps it from conflicting with program Id return program != null ? program.getId() : -entryEndUtcMillis; } - /** - * Returns true if this is a gap. - */ + /** Returns true if this is a gap. */ boolean isGap() { return !Program.isValid(program); } - /** - * Returns true if this channel is blocked. - */ + /** Returns true if this channel is blocked. */ boolean isBlocked() { return mIsBlocked; } - /** - * Returns true if this program is on the air. - */ + /** Returns true if this program is on the air. */ boolean isCurrentProgram() { long current = System.currentTimeMillis(); return entryStartUtcMillis <= current && entryEndUtcMillis > current; } - /** - * Returns if this program has the genre. - */ + /** Returns if this program has the genre. */ boolean hasGenre(int genreId) { return !isGap() && program.hasGenre(genreId); } - /** - * Returns the width of table entry, in pixels. - */ + /** Returns the width of table entry, in pixels. */ int getWidth() { return GuideUtils.convertMillisToPixel(entryStartUtcMillis, entryEndUtcMillis); } @@ -753,17 +758,25 @@ public class ProgramManager { @Override public String toString() { return "TableEntry{" - + "hashCode=" + hashCode() - + ", channelId=" + channelId - + ", program=" + program - + ", startTime=" + Utils.toTimeString(entryStartUtcMillis) - + ", endTimeTime=" + Utils.toTimeString(entryEndUtcMillis) + "}"; + + "hashCode=" + + hashCode() + + ", channelId=" + + channelId + + ", program=" + + program + + ", startTime=" + + Utils.toTimeString(entryStartUtcMillis) + + ", endTimeTime=" + + Utils.toTimeString(entryEndUtcMillis) + + "}"; } } interface Listener { void onGenresUpdated(); + void onChannelsUpdated(); + void onTimeRangeUpdated(); } @@ -777,12 +790,12 @@ public class ProgramManager { static class ListenerAdapter implements Listener { @Override - public void onGenresUpdated() { } + public void onGenresUpdated() {} @Override - public void onChannelsUpdated() { } + public void onChannelsUpdated() {} @Override - public void onTimeRangeUpdated() { } + public void onTimeRangeUpdated() {} } } diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index fefc724c..3765aa43 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -24,12 +24,9 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; - -import com.android.tv.MainActivity; import com.android.tv.data.Channel; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.Utils; - import java.util.concurrent.TimeUnit; public class ProgramRow extends TimelineGridView { @@ -47,25 +44,23 @@ public class ProgramRow extends TimelineGridView { interface ChildFocusListener { /** - * Is called after focus is moved. Caller should check if old and new focuses are - * listener's children. - * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. + * Is called after focus is moved. Caller should check if old and new focuses are listener's + * children. See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. */ void onChildFocus(View oldFocus, View newFocus); } - /** - * Used only for debugging. - */ + /** Used only for debugging. */ private Channel mChannel; - private final OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - updateChildVisibleArea(); - } - }; + private final OnGlobalLayoutListener mLayoutListener = + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + updateChildVisibleArea(); + } + }; public ProgramRow(Context context) { this(context, null); @@ -79,9 +74,7 @@ public class ProgramRow extends TimelineGridView { super(context, attrs, defStyle); } - /** - * Registers a listener focus events occurring on children to the {@code ProgramRow}. - */ + /** Registers a listener focus events occurring on children to the {@code ProgramRow}. */ public void setChildFocusListener(ChildFocusListener childFocusListener) { mChildFocusListener = childFocusListener; } @@ -108,9 +101,7 @@ public class ProgramRow extends TimelineGridView { updateChildVisibleArea(); } - /** - * Moves focus to the current program. - */ + /** Moves focus to the current program. */ public void focusCurrentProgram() { View currentProgram = getCurrentProgramView(); if (currentProgram == null) { @@ -124,13 +115,15 @@ public class ProgramRow extends TimelineGridView { // Call this API after RTL is resolved. (i.e. View is measured.) private boolean isDirectionStart(int direction) { return getLayoutDirection() == LAYOUT_DIRECTION_LTR - ? direction == View.FOCUS_LEFT : direction == View.FOCUS_RIGHT; + ? direction == View.FOCUS_LEFT + : direction == View.FOCUS_RIGHT; } // Call this API after RTL is resolved. (i.e. View is measured.) private boolean isDirectionEnd(int direction) { return getLayoutDirection() == LAYOUT_DIRECTION_LTR - ? direction == View.FOCUS_RIGHT : direction == View.FOCUS_LEFT; + ? direction == View.FOCUS_RIGHT + : direction == View.FOCUS_LEFT; } @Override @@ -142,8 +135,8 @@ public class ProgramRow extends TimelineGridView { if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) { if (focusedEntry.entryStartUtcMillis < fromMillis) { // The current entry starts outside of the view; Align or scroll to the left. - scrollByTime(Math.max(-ONE_HOUR_MILLIS, - focusedEntry.entryStartUtcMillis - fromMillis)); + scrollByTime( + Math.max(-ONE_HOUR_MILLIS, focusedEntry.entryStartUtcMillis - fromMillis)); return focused; } } else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) { @@ -169,17 +162,19 @@ public class ProgramRow extends TimelineGridView { TableEntry targetEntry = ((ProgramItemView) target).getTableEntry(); if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) { - if (targetEntry.entryStartUtcMillis < fromMillis && - targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) { + if (targetEntry.entryStartUtcMillis < fromMillis + && targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) { // The target entry starts outside the view; Align or scroll to the left. - scrollByTime(Math.max(-ONE_HOUR_MILLIS, - targetEntry.entryStartUtcMillis - fromMillis)); + scrollByTime( + Math.max(-ONE_HOUR_MILLIS, targetEntry.entryStartUtcMillis - fromMillis)); } } else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) { if (targetEntry.entryStartUtcMillis > fromMillis + ONE_HOUR_MILLIS + HALF_HOUR_MILLIS) { // The target entry starts outside the view; Align or scroll to the right. - scrollByTime(Math.min(ONE_HOUR_MILLIS, - targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS)); + scrollByTime( + Math.min( + ONE_HOUR_MILLIS, + targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS)); } } @@ -188,8 +183,11 @@ public class ProgramRow extends TimelineGridView { private void scrollByTime(long timeToScroll) { if (DEBUG) { - Log.d(TAG, "scrollByTime(timeToScroll=" - + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + "min)"); + Log.d( + TAG, + "scrollByTime(timeToScroll=" + + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + + "min)"); } mProgramManager.shiftTime(timeToScroll); } @@ -203,12 +201,13 @@ public class ProgramRow extends TimelineGridView { // The focus is lost due to information loaded. Requests focus immediately. // (Because this entry is detached after real entries attached, we can't take // the below approach to resume focus on entry being attached.) - post(new Runnable() { - @Override - public void run() { - requestFocus(); - } - }); + post( + new Runnable() { + @Override + public void run() { + requestFocus(); + } + }); } else if (entry.isCurrentProgram()) { if (DEBUG) Log.d(TAG, "Keep focus to the current program"); // Current program is visible in the guide. @@ -227,12 +226,13 @@ public class ProgramRow extends TimelineGridView { TableEntry entry = ((ProgramItemView) child).getTableEntry(); if (entry.isCurrentProgram()) { mKeepFocusToCurrentProgram = false; - post(new Runnable() { - @Override - public void run() { - requestFocus(); - } - }); + post( + new Runnable() { + @Override + public void run() { + requestFocus(); + } + }); } } } @@ -243,8 +243,12 @@ public class ProgramRow extends TimelineGridView { // Give focus according to the previous focused range Range focusRange = programGrid.getFocusRange(); - View nextFocus = GuideUtils.findNextFocusedProgram(this, focusRange.getLower(), - focusRange.getUpper(), programGrid.isKeepCurrentProgramFocused()); + View nextFocus = + GuideUtils.findNextFocusedProgram( + this, + focusRange.getLower(), + focusRange.getUpper(), + programGrid.isKeepCurrentProgramFocused()); if (nextFocus != null) { return nextFocus.requestFocus(); @@ -279,30 +283,29 @@ public class ProgramRow extends TimelineGridView { mChannel = channel; } - /** - * Sets the instance of {@link ProgramGuide} - */ + /** Sets the instance of {@link ProgramGuide} */ public void setProgramGuide(ProgramGuide programGuide) { mProgramGuide = programGuide; mProgramManager = programGuide.getProgramManager(); } - /** - * Resets the scroll with the initial offset {@code scrollOffset}. - */ + /** Resets the scroll with the initial offset {@code scrollOffset}. */ public void resetScroll(int scrollOffset) { - long startTime = GuideUtils.convertPixelToMillis(scrollOffset) - + mProgramManager.getStartTime(); - int position = mChannel == null ? -1 : mProgramManager.getProgramIndexAtTime( - mChannel.getId(), startTime); + long startTime = + GuideUtils.convertPixelToMillis(scrollOffset) + mProgramManager.getStartTime(); + int position = + mChannel == null + ? -1 + : mProgramManager.getProgramIndexAtTime(mChannel.getId(), startTime); if (position < 0) { getLayoutManager().scrollToPosition(0); } else { TableEntry entry = mProgramManager.getTableEntry(mChannel.getId(), position); - int offset = GuideUtils.convertMillisToPixel( - mProgramManager.getStartTime(), entry.entryStartUtcMillis) - scrollOffset; - ((LinearLayoutManager) getLayoutManager()) - .scrollToPositionWithOffset(position, offset); + int offset = + GuideUtils.convertMillisToPixel( + mProgramManager.getStartTime(), entry.entryStartUtcMillis) + - scrollOffset; + ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, offset); // Workaround to b/31598505. When a program's duration is too long, // RecyclerView.onScrolled() will not be called after scrollToPositionWithOffset(). // Therefore we have to update children's visible areas by ourselves in this case. diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 99f853b1..8dd14ca3 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -42,14 +42,12 @@ import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.TvCommonUtils; @@ -68,13 +66,10 @@ import com.android.tv.util.ImageLoader; import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; -/** - * Adapts the {@link ProgramListAdapter} list to the body of the program guide table. - */ +/** Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */ class ProgramTableAdapter extends RecyclerView.Adapter implements ProgramManager.TableEntryChangedListener { private static final String TAG = "ProgramTableAdapter"; @@ -130,58 +125,62 @@ class ProgramTableAdapter extends RecyclerView.Adapter(); mRecycledViewPool = new RecycledViewPool(); - mRecycledViewPool.setMaxRecycledViews(R.layout.program_guide_table_item, - context.getResources().getInteger( - R.integer.max_recycled_view_pool_epg_table_item)); - mProgramManager.addListener(new ProgramManager.ListenerAdapter() { - @Override - public void onChannelsUpdated() { - update(); - } - }); + mRecycledViewPool.setMaxRecycledViews( + R.layout.program_guide_table_item, + context.getResources().getInteger(R.integer.max_recycled_view_pool_epg_table_item)); + mProgramManager.addListener( + new ProgramManager.ListenerAdapter() { + @Override + public void onChannelsUpdated() { + update(); + } + }); update(); mProgramManager.addTableEntryChangedListener(this); } @@ -193,8 +192,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter= android.os.Build.VERSION_CODES.N) { - criticScoreSource.setText(Html.fromHtml(criticScore.source, - Html.FROM_HTML_MODE_LEGACY)); + criticScoreSource.setText( + Html.fromHtml(criticScore.source, Html.FROM_HTML_MODE_LEGACY)); } else { criticScoreSource.setText(Html.fromHtml(criticScore.source)); } criticScoreText.setText(criticScore.score); criticScoreSource.setVisibility(View.VISIBLE); criticScoreText.setVisibility(View.VISIBLE); - ImageLoader.loadBitmap(mContext, criticScore.logoUrl, + ImageLoader.loadBitmap( + mContext, + criticScore.logoUrl, createCriticScoreLogoCallback(holder, programId, criticScoreLogo)); } @@ -768,7 +802,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logoImage) { - if (logoImage == null || holder.mSelectedEntry == null + if (logoImage == null + || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null || holder.mSelectedEntry.program.getId() != programId) { logoView.setVisibility(View.GONE); @@ -785,7 +820,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap posterArt) { - if (posterArt == null || holder.mSelectedEntry == null + if (posterArt == null + || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null) { return; } @@ -803,7 +839,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { - if (logo == null || holder.mChannel == null + if (logo == null + || holder.mChannel == null || holder.mChannel.getId() != channelId) { return; } @@ -817,8 +854,9 @@ class ProgramTableAdapter extends RecyclerView.Adapter(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { - if (logo != null && holder.mChannel != null && info.getId() - .equals(holder.mChannel.getInputId())) { + if (logo != null + && holder.mChannel != null + && info.getId().equals(holder.mChannel.getInputId())) { holder.updateInputLogoInternal(logo); } } diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java index d9e96a40..9c10c952 100644 --- a/src/com/android/tv/guide/TimeListAdapter.java +++ b/src/com/android/tv/guide/TimeListAdapter.java @@ -16,7 +16,6 @@ package com.android.tv.guide; -import android.content.Context; import android.content.res.Resources; import android.support.v7.widget.RecyclerView; import android.text.format.DateFormat; @@ -24,17 +23,15 @@ 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.util.Utils; - import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; /** - * Adapts the time range from {@link ProgramManager} to the timeline header row of the program - * guide table. + * Adapts the time range from {@link ProgramManager} to the timeline header row of the program guide + * table. */ class TimeListAdapter extends RecyclerView.Adapter { private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30); @@ -53,8 +50,10 @@ class TimeListAdapter extends RecyclerView.Adapter { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -97,5 +94,5 @@ public class ActionCardView extends RelativeLayout implements ItemListRowView.Ca } @Override - public void onRecycled() { } + public void onRecycled() {} } diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java index 94ccd37f..3e60723f 100644 --- a/src/com/android/tv/menu/AppLinkCardView.java +++ b/src/com/android/tv/menu/AppLinkCardView.java @@ -33,19 +33,15 @@ import android.util.Log; import android.view.View; 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.util.TvInputManagerHelper; - import java.util.Objects; -/** - * A view to render an app link card. - */ +/** A view to render an app link card. */ public class AppLinkCardView extends BaseCardView { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -88,9 +84,7 @@ public class AppLinkCardView extends BaseCardView { mDefaultDrawable = getResources().getDrawable(R.drawable.ic_recent_thumbnail_default, null); } - /** - * Returns the intent which will be started once this card is clicked. - */ + /** Returns the intent which will be started once this card is clicked. */ public Intent getIntent() { return mIntent; } @@ -100,13 +94,20 @@ public class AppLinkCardView extends BaseCardView { Channel newChannel = item.getChannel(); boolean channelChanged = !Objects.equals(mChannel, newChannel); String previousPosterArtUri = mChannel == null ? null : mChannel.getAppLinkPosterArtUri(); - boolean posterArtChanged = previousPosterArtUri == null - || newChannel.getAppLinkPosterArtUri() == null - || !TextUtils.equals(previousPosterArtUri, newChannel.getAppLinkPosterArtUri()); + boolean posterArtChanged = + previousPosterArtUri == null + || newChannel.getAppLinkPosterArtUri() == null + || !TextUtils.equals( + previousPosterArtUri, newChannel.getAppLinkPosterArtUri()); mChannel = newChannel; if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + mChannel.getDisplayName() + ", selected=" + selected - + ")"); + Log.d( + TAG, + "onBind(channelName=" + + mChannel.getDisplayName() + + ", selected=" + + selected + + ")"); } ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()); if (channelChanged) { @@ -120,8 +121,8 @@ public class AppLinkCardView extends BaseCardView { mAppInfoView.setVisibility(VISIBLE); mAppInfoView.setCompoundDrawablePadding(mIconPadding); mAppInfoView.setCompoundDrawablesRelative(null, null, null, null); - appLabel = mTvInputManagerHelper - .getTvInputApplicationLabel(mChannel.getInputId()); + appLabel = + mTvInputManagerHelper.getTvInputApplicationLabel(mChannel.getInputId()); if (appLabel != null) { mAppInfoView.setText(appLabel); } else { @@ -149,12 +150,17 @@ public class AppLinkCardView extends BaseCardView { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) { - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, - mIconWidth, mIconHeight, createChannelLogoCallback( + mChannel.loadBitmap( + getContext(), + Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, + mIconWidth, + mIconHeight, + createChannelLogoCallback( this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON)); } else if (appInfo.icon != 0) { - Drawable appIcon = mTvInputManagerHelper - .getTvInputApplicationIcon(mChannel.getInputId()); + Drawable appIcon = + mTvInputManagerHelper.getTvInputApplicationIcon( + mChannel.getInputId()); if (appIcon != null) { BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); appIcon.setBounds(0, 0, mIconWidth, mIconHeight); @@ -186,11 +192,14 @@ public class AppLinkCardView extends BaseCardView { } break; case Channel.APP_LINK_TYPE_APP: - appLabel = mTvInputManagerHelper - .getTvInputApplicationLabel(mChannel.getInputId()); + appLabel = + mTvInputManagerHelper.getTvInputApplicationLabel(mChannel.getInputId()); if (appLabel != null) { - setText(getContext() - .getString(R.string.channels_item_app_link_app_launcher, appLabel)); + setText( + getContext() + .getString( + R.string.channels_item_app_link_app_launcher, + appLabel)); } else { new AsyncTask() { private final String mLoadTvInputId = mChannel.getInputId(); @@ -206,15 +215,17 @@ public class AppLinkCardView extends BaseCardView { @Override protected void onPostExecute(CharSequence appLabel) { mTvInputManagerHelper.setTvInputApplicationLabel( - mLoadTvInputId, appLabel); + mLoadTvInputId, appLabel); if (!mLoadTvInputId.equals(mChannel.getInputId()) - || !isAttachedToWindow()) { + || !isAttachedToWindow()) { return; } - setText(getContext() - .getString( - R.string.channels_item_app_link_app_launcher, - appLabel)); + setText( + getContext() + .getString( + R.string + .channels_item_app_link_app_launcher, + appLabel)); } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -235,9 +246,13 @@ public class AppLinkCardView extends BaseCardView { mImageView.setImageDrawable(mDefaultDrawable); mImageView.setForeground(null); if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) { - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, - mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel, - Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); + mChannel.loadBitmap( + getContext(), + Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, + mCardImageWidth, + mCardImageHeight, + createChannelLogoCallback( + this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); } else { setCardImageWithBanner(appInfo); } @@ -265,10 +280,12 @@ public class AppLinkCardView extends BaseCardView { if (bitmap != null) { drawable = new BitmapDrawable(getResources(), bitmap); if (bitmap.getWidth() > bitmap.getHeight()) { - drawable.setBounds(0, 0, mIconWidth, - mIconWidth * bitmap.getHeight() / bitmap.getWidth()); + drawable.setBounds( + 0, 0, mIconWidth, mIconWidth * bitmap.getHeight() / bitmap.getWidth()); } else { - drawable.setBounds(0, 0, + drawable.setBounds( + 0, + 0, mIconHeight * bitmap.getWidth() / bitmap.getHeight(), mIconHeight); } @@ -303,6 +320,7 @@ public class AppLinkCardView extends BaseCardView { private void setCardImageWithBanner(ApplicationInfo appInfo) { new AsyncTask() { private String mLoadTvInputId = mChannel.getInputId(); + @Override protected Drawable doInBackground(Void... params) { Drawable banner = null; @@ -341,6 +359,7 @@ public class AppLinkCardView extends BaseCardView { } else { new AsyncTask() { private final String mLoadTvInputId = mChannel.getInputId(); + @Override protected Drawable doInBackground(Void... params) { Drawable banner = null; @@ -373,8 +392,8 @@ public class AppLinkCardView extends BaseCardView { mImageView.setImageDrawable(mDefaultDrawable); mImageView.setBackgroundResource(R.color.channel_card); } else { - Bitmap bitmap = Bitmap.createBitmap( - mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); + Bitmap bitmap = + Bitmap.createBitmap(mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight); banner.draw(canvas); @@ -387,12 +406,19 @@ public class AppLinkCardView extends BaseCardView { } private void extractAndSetMetaViewBackgroundColor(Bitmap bitmap) { - new Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() { - @Override - public void onGenerated(Palette palette) { - mMetaViewHolder.setBackgroundColor(palette.getDarkVibrantColor( - getResources().getColor(R.color.channel_card_meta_background, null))); - } - }); + new Palette.Builder(bitmap) + .generate( + new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + mMetaViewHolder.setBackgroundColor( + palette.getDarkVibrantColor( + getResources() + .getColor( + R.color + .channel_card_meta_background, + null))); + } + }); } } diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java index 4c5e6c78..3a94ebbf 100644 --- a/src/com/android/tv/menu/BaseCardView.java +++ b/src/com/android/tv/menu/BaseCardView.java @@ -29,12 +29,9 @@ import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; -/** - * A base class to render a card. - */ +/** A base class to render a card. */ public abstract class BaseCardView extends LinearLayout implements ItemListRowView.CardView { private static final float SCALE_FACTOR_0F = 0f; private static final float SCALE_FACTOR_1F = 1f; @@ -49,10 +46,8 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo private final float mExtendedCardHeight; private final float mTextViewHeight; private final float mExtendedTextViewHeight; - @Nullable - private TextView mTextView; - @Nullable - private TextView mTextViewFocused; + @Nullable private TextView mTextView; + @Nullable private TextView mTextViewFocused; private final int mCardImageWidth; private final float mCardHeight; private boolean mSelected; @@ -74,27 +69,32 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo setClipToOutline(true); mFocusAnimDuration = getResources().getInteger(R.integer.menu_focus_anim_duration); - mFocusTranslationZ = getResources().getDimension(R.dimen.channel_card_elevation_focused) - - getResources().getDimension(R.dimen.card_elevation_normal); - mVerticalCardMargin = 2 * ( - getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top) - + getResources().getDimensionPixelOffset(R.dimen.menu_list_margin_top)); + mFocusTranslationZ = + getResources().getDimension(R.dimen.channel_card_elevation_focused) + - getResources().getDimension(R.dimen.card_elevation_normal); + mVerticalCardMargin = + 2 + * (getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top) + + getResources() + .getDimensionPixelOffset(R.dimen.menu_list_margin_top)); // Ensure the same elevation and focus animation for all subclasses. setElevation(getResources().getDimension(R.dimen.card_elevation_normal)); mCardCornerRadius = getResources().getDimensionPixelSize(R.dimen.channel_card_round_radius); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius); - } - }); + setOutlineProvider( + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + 0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius); + } + }); mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width); mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height); - mExtendedCardHeight = getResources().getDimensionPixelSize( - R.dimen.card_layout_height_extended); + mExtendedCardHeight = + getResources().getDimensionPixelSize(R.dimen.card_layout_height_extended); mTextViewHeight = getResources().getDimensionPixelSize(R.dimen.card_meta_layout_height); - mExtendedTextViewHeight = getResources().getDimensionPixelOffset( - R.dimen.card_meta_layout_height_extended); + mExtendedTextViewHeight = + getResources().getDimensionPixelOffset(R.dimen.card_meta_layout_height_extended); } @Override @@ -104,16 +104,14 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo mTextViewFocused = (TextView) findViewById(R.id.card_text_focused); } - /** - * Called when the view is displayed. - */ + /** Called when the view is displayed. */ @Override public void onBind(T item, boolean selected) { setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } @Override - public void onRecycled() { } + public void onRecycled() {} @Override public void onSelected() { @@ -137,9 +135,7 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo } } - /** - * Sets text of this card view. - */ + /** Sets text of this card view. */ public void setText(int resId) { if (mTextResId != resId) { mTextResId = resId; @@ -155,9 +151,7 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo } } - /** - * Sets text of this card view. - */ + /** Sets text of this card view. */ public void setText(String text) { if (!TextUtils.equals(text, mTextString)) { mTextString = text; @@ -176,8 +170,8 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo private void onTextViewUpdated() { if (mTextView != null && mTextViewFocused != null) { mTextViewFocused.measure( - MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1; if (mExtendViewOnFocus) { setTextViewFocusedAlpha(mSelected ? 1f : 0f); @@ -188,9 +182,7 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } - /** - * Enables or disables text view of this card view. - */ + /** Enables or disables text view of this card view. */ public void setTextViewEnabled(boolean enabled) { if (mTextViewFocused != null) { mTextViewFocused.setEnabled(enabled); @@ -200,38 +192,38 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo } } - /** - * Called when the focus animation started. - */ + /** Called when the focus animation started. */ protected void onFocusAnimationStart(boolean selected) { if (mExtendViewOnFocus) { setTextViewFocusedAlpha(selected ? 1f : 0f); } } - /** - * Called when the focus animation ended. - */ + /** Called when the focus animation ended. */ protected void onFocusAnimationEnd(boolean selected) { // do nothing. } /** - * Called when the view is bound, or while focus animation is running with a value - * between {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}. + * Called when the view is bound, or while focus animation is running with a value between + * {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}. */ protected void onSetFocusAnimatedValue(float animatedValue) { - float cardViewHeight = (mExtendViewOnFocus && isFocused()) - ? mExtendedCardHeight : mCardHeight; + float cardViewHeight = + (mExtendViewOnFocus && isFocused()) ? mExtendedCardHeight : mCardHeight; float scale = 1f + (mVerticalCardMargin / cardViewHeight) * animatedValue; setScaleX(scale); setScaleY(scale); setTranslationZ(mFocusTranslationZ * animatedValue); if (mTextView != null && mTextViewFocused != null) { ViewGroup.LayoutParams params = mTextView.getLayoutParams(); - int height = mExtendViewOnFocus ? Math.round(mTextViewHeight - + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue) - : (int) mTextViewHeight; + int height = + mExtendViewOnFocus + ? Math.round( + mTextViewHeight + + (mExtendedTextViewHeight - mTextViewHeight) + * animatedValue) + : (int) mTextViewHeight; if (height != params.height) { params.height = height; setTextViewLayoutParams(params); @@ -252,25 +244,27 @@ public abstract class BaseCardView extends LinearLayout implements ItemListRo final boolean selected = targetAnimatedValue == SCALE_FACTOR_1F; mFocusAnimator = ValueAnimator.ofFloat(mFocusAnimatedValue, targetAnimatedValue); mFocusAnimator.setDuration(mFocusAnimDuration); - mFocusAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - setHasTransientState(true); - onFocusAnimationStart(selected); - } + mFocusAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setHasTransientState(true); + onFocusAnimationStart(selected); + } - @Override - public void onAnimationEnd(Animator animation) { - setHasTransientState(false); - onFocusAnimationEnd(selected); - } - }); - mFocusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setFocusAnimatedValue((Float) animation.getAnimatedValue()); - } - }); + @Override + public void onAnimationEnd(Animator animation) { + setHasTransientState(false); + onFocusAnimationEnd(selected); + } + }); + mFocusAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setFocusAnimatedValue((Float) animation.getAnimatedValue()); + } + }); mFocusAnimator.start(); } diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java index 2ecb6af7..31262b88 100644 --- a/src/com/android/tv/menu/ChannelCardView.java +++ b/src/com/android/tv/menu/ChannelCardView.java @@ -26,19 +26,15 @@ import android.view.View; import android.widget.ImageView; 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.parental.ParentalControlSettings; import com.android.tv.util.ImageLoader; - import java.util.Objects; -/** - * A view to render channel card. - */ +/** A view to render channel card. */ public class ChannelCardView extends BaseCardView { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -81,8 +77,13 @@ public class ChannelCardView extends BaseCardView { @Override public void onBind(ChannelsRowItem item, boolean selected) { if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + item.getChannel().getDisplayName() + ", selected=" - + selected + ")"); + Log.d( + TAG, + "onBind(channelName=" + + item.getChannel().getDisplayName() + + ", selected=" + + selected + + ")"); } updateChannel(item); updateProgram(); @@ -146,7 +147,8 @@ public class ChannelCardView extends BaseCardView { return new ImageLoader.ImageLoaderCallback(cardView) { @Override public void onBitmapLoaded(ChannelCardView cardView, @Nullable Bitmap posterArt) { - if (posterArt == null || cardView.mProgram == null + if (posterArt == null + || cardView.mProgram == null || program.getChannelId() != cardView.mProgram.getChannelId() || program.getChannelId() != cardView.mChannel.getId()) { return; @@ -160,7 +162,10 @@ public class ChannelCardView extends BaseCardView { if (!TextUtils.equals(mPosterArtUri, posterArtUri)) { mPosterArtUri = posterArtUri; if (posterArtUri == null - || !mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight, + || !mProgram.loadPosterArt( + getContext(), + mCardImageWidth, + mCardImageHeight, createProgramPosterArtCallback(this, mProgram))) { mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); mImageView.setForeground(null); @@ -172,4 +177,4 @@ public class ChannelCardView extends BaseCardView { mImageView.setImageBitmap(posterArt); mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java index bc5d6cfb..854bb5a8 100644 --- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java +++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java @@ -23,24 +23,20 @@ import android.os.Message; import android.support.annotation.MainThread; import android.support.annotation.NonNull; 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.Program; import com.android.tv.data.ProgramDataManager; - import java.util.List; -/** - * A poster image prefetcher to show the program poster art in the Channels row faster. - */ +/** A poster image prefetcher to show the program poster art in the Channels row faster. */ public class ChannelsPosterPrefetcher { private static final String TAG = "PosterPrefetcher"; private static final boolean DEBUG = false; private static final int MSG_PREFETCH_IMAGE = 1000; - private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds + private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds private final ProgramDataManager mProgramDataManager; private final ChannelsRowAdapter mChannelsAdapter; @@ -51,23 +47,19 @@ public class ChannelsPosterPrefetcher { private boolean isCanceled; - /** - * Create {@link ChannelsPosterPrefetcher} object with given parameters. - */ - public ChannelsPosterPrefetcher(Context context, ProgramDataManager programDataManager, - ChannelsRowAdapter adapter) { + /** Create {@link ChannelsPosterPrefetcher} object with given parameters. */ + public ChannelsPosterPrefetcher( + Context context, ProgramDataManager programDataManager, ChannelsRowAdapter adapter) { mProgramDataManager = programDataManager; mChannelsAdapter = adapter; - mPosterArtWidth = context.getResources().getDimensionPixelSize( - R.dimen.card_image_layout_width); - mPosterArtHeight = context.getResources().getDimensionPixelSize( - R.dimen.card_image_layout_height); + mPosterArtWidth = + context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_width); + mPosterArtHeight = + context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_height); mContext = context.getApplicationContext(); } - /** - * Start prefetching of program poster art of recommendation. - */ + /** Start prefetching of program poster art of recommendation. */ public void prefetch() { SoftPreconditions.checkState(!isCanceled, TAG, "Prefetch called after cancel was called."); if (isCanceled) { @@ -79,13 +71,11 @@ public class ChannelsPosterPrefetcher { * prefetch the intermediate channels. So ignore previous schedule. */ mHandler.removeMessages(MSG_PREFETCH_IMAGE); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREFETCH_IMAGE), - ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_PREFETCH_IMAGE), ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS); } - /** - * Cancels pending and current prefetch requests. - */ + /** Cancels pending and current prefetch requests. */ public void cancel() { isCanceled = true; mHandler.removeCallbacksAndMessages(null); @@ -107,8 +97,11 @@ public class ChannelsPosterPrefetcher { if (!Channel.isValid(channel)) { continue; } - channel.prefetchImage(mContext, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, - mPosterArtWidth, mPosterArtHeight); + channel.prefetchImage( + mContext, + Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mPosterArtWidth, + mPosterArtHeight); Program program = mProgramDataManager.getCurrentProgram(channel.getId()); if (program != null) { program.prefetchPosterArt(mContext, mPosterArtWidth, mPosterArtHeight); @@ -116,8 +109,11 @@ public class ChannelsPosterPrefetcher { } } if (DEBUG) { - Log.d(TAG, "doPrefetchImages() finished. ImageLoader may still have async tasks for " - + "channels " + items); + Log.d( + TAG, + "doPrefetchImages() finished. ImageLoader may still have async tasks for " + + "channels " + + items); } } diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java index 490d73de..7d03bf2b 100644 --- a/src/com/android/tv/menu/ChannelsRow.java +++ b/src/com/android/tv/menu/ChannelsRow.java @@ -17,7 +17,6 @@ package com.android.tv.menu; import android.content.Context; - import com.android.tv.R; import com.android.tv.data.ProgramDataManager; import com.android.tv.recommendation.RecentChannelEvaluator; @@ -26,13 +25,9 @@ import com.android.tv.recommendation.Recommender; public class ChannelsRow extends ItemListRow { public static final String ID = ChannelsRow.class.getName(); - /** - * Minimum count for recent channels. - */ + /** Minimum count for recent channels. */ public static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5; - /** - * Maximum count for recent channels. - */ + /** Maximum count for recent channels. */ public static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10; private Recommender mTvRecommendation; @@ -41,25 +36,33 @@ public class ChannelsRow extends ItemListRow { public ChannelsRow(Context context, Menu menu, ProgramDataManager programDataManager) { super(context, menu, R.string.menu_title_channels, R.dimen.card_layout_height, null); - mTvRecommendation = new Recommender(getContext(), new Recommender.Listener() { - @Override - public void onRecommenderReady() { - mChannelsAdapter.update(); - mChannelsPosterPrefetcher.prefetch(); - } + mTvRecommendation = + new Recommender( + getContext(), + new Recommender.Listener() { + @Override + public void onRecommenderReady() { + mChannelsAdapter.update(); + mChannelsPosterPrefetcher.prefetch(); + } - @Override - public void onRecommendationChanged() { - mChannelsAdapter.update(); - mChannelsPosterPrefetcher.prefetch(); - } - }, true); + @Override + public void onRecommendationChanged() { + mChannelsAdapter.update(); + mChannelsPosterPrefetcher.prefetch(); + } + }, + true); mTvRecommendation.registerEvaluator(new RecentChannelEvaluator()); - mChannelsAdapter = new ChannelsRowAdapter(context, mTvRecommendation, - MIN_COUNT_FOR_RECENT_CHANNELS, MAX_COUNT_FOR_RECENT_CHANNELS); + mChannelsAdapter = + new ChannelsRowAdapter( + context, + mTvRecommendation, + MIN_COUNT_FOR_RECENT_CHANNELS, + MAX_COUNT_FOR_RECENT_CHANNELS); setAdapter(mChannelsAdapter); - mChannelsPosterPrefetcher = new ChannelsPosterPrefetcher(context, programDataManager, - mChannelsAdapter); + mChannelsPosterPrefetcher = + new ChannelsPosterPrefetcher(context, programDataManager, mChannelsAdapter); } @Override @@ -72,9 +75,7 @@ public class ChannelsRow extends ItemListRow { mChannelsPosterPrefetcher.cancel(); } - /** - * Handle the update event of the recent channel. - */ + /** Handle the update event of the recent channel. */ @Override public void onRecentChannelsChanged() { mChannelsPosterPrefetcher.prefetch(); diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java index 7ff44ea6..73ff6f3b 100644 --- a/src/com/android/tv/menu/ChannelsRowAdapter.java +++ b/src/com/android/tv/menu/ChannelsRowAdapter.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.media.tv.TvInputInfo; import android.view.View; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -31,14 +30,11 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.recommendation.Recommender; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -/** - * An adapter of the Channels row. - */ +/** An adapter of the Channels row. */ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter { // There are four special cards: guide, setup, dvr, applink. private static final int SIZE_OF_VIEW_TYPE = 5; @@ -50,54 +46,60 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter items = getItemList(); boolean isItemInList = index < items.size() && item.equals(items.get(index)); @@ -289,8 +290,10 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter channelList, Channel channel, long currentChannelId) { - if (channel == null || channel.getId() == currentChannelId - || channelList.contains(channel) || !channel.isBrowsable()) { + if (channel == null + || channel.getId() == currentChannelId + || channelList.contains(channel) + || !channel.isBrowsable()) { return false; } channelList.add(channel); diff --git a/src/com/android/tv/menu/ChannelsRowItem.java b/src/com/android/tv/menu/ChannelsRowItem.java index c35189ec..25216920 100644 --- a/src/com/android/tv/menu/ChannelsRowItem.java +++ b/src/com/android/tv/menu/ChannelsRowItem.java @@ -17,14 +17,10 @@ package com.android.tv.menu; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - import com.android.tv.R; import com.android.tv.data.Channel; -/** - * A class for the items in channels row. - */ +/** A class for the items in channels row. */ public class ChannelsRowItem { /** The item ID for guide item */ public static final int GUIDE_ITEM_ID = -1; @@ -62,31 +58,23 @@ public class ChannelsRowItem { mLayoutId = layoutId; } - /** - * Returns the channel for this item. - */ + /** Returns the channel for this item. */ @NonNull public Channel getChannel() { return mChannel; } - /** - * Sets the channel. - */ + /** Sets the channel. */ public void setChannel(@NonNull Channel channel) { mChannel = channel; } - /** - * Returns the layout resource ID to represent this item. - */ + /** Returns the layout resource ID to represent this item. */ public int getLayoutId() { return mLayoutId; } - /** - * Returns the unique ID for this item. - */ + /** Returns the unique ID for this item. */ public long getItemId() { return mItemId; } @@ -94,8 +82,12 @@ public class ChannelsRowItem { @Override public String toString() { return "ChannelsRowItem{" - + "itemId=" + mItemId - + ", layoutId=" + mLayoutId - + ", channel=" + mChannel + "}"; + + "itemId=" + + mItemId + + ", layoutId=" + + mLayoutId + + ", channel=" + + mChannel + + "}"; } } diff --git a/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java index f69d5e86..9ec70d09 100644 --- a/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java +++ b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java @@ -17,15 +17,11 @@ package com.android.tv.menu; import android.content.Context; - import com.android.tv.customization.CustomAction; - import java.util.ArrayList; import java.util.List; -/** - * An adapter of options that can accepts customization data. - */ +/** An adapter of options that can accepts customization data. */ public abstract class CustomizableOptionsRowAdapter extends OptionsRowAdapter { private final List mCustomActions; @@ -54,8 +50,9 @@ public abstract class CustomizableOptionsRowAdapter extends OptionsRowAdapter { // Type of MenuAction should be unique in the Adapter. int type = -(i + 1); CustomAction customAction = mCustomActions.get(i); - MenuAction action = new MenuAction( - customAction.getTitle(), type, customAction.getIconDrawable()); + MenuAction action = + new MenuAction( + customAction.getTitle(), type, customAction.getIconDrawable()); if (customAction.isFront()) { actions.add(position++, action); diff --git a/src/com/android/tv/menu/IMenuView.java b/src/com/android/tv/menu/IMenuView.java index 87c5d9f6..19ebc739 100644 --- a/src/com/android/tv/menu/IMenuView.java +++ b/src/com/android/tv/menu/IMenuView.java @@ -17,33 +17,26 @@ package com.android.tv.menu; import com.android.tv.menu.Menu.MenuShowReason; - import java.util.List; -/** - * An base interface for menu view. - */ +/** An base interface for menu view. */ public interface IMenuView { - /** - * Sets menu rows. - */ + /** Sets menu rows. */ void setMenuRows(List menuRows); /** * Shows the main menu. * - *

The inherited class should show the menu and select the row corresponding to - * {@code rowIdToSelect}. If the menu is already visible, change the current selection to the - * given row. + *

The inherited class should show the menu and select the row corresponding to {@code + * rowIdToSelect}. If the menu is already visible, change the current selection to the given + * row. * * @param reason A reason why this is called. See {@link MenuShowReason}. * @param rowIdToSelect An ID of the row which corresponds to the {@code reason}. */ void onShow(@MenuShowReason int reason, String rowIdToSelect, Runnable runnableAfterShow); - /** - * Hides the main menu - */ + /** Hides the main menu */ void onHide(); /** @@ -60,8 +53,6 @@ public interface IMenuView { */ boolean update(String rowId, boolean menuActive); - /** - * Checks if the menu view is visible or not. - */ + /** Checks if the menu view is visible or not. */ boolean isVisible(); } diff --git a/src/com/android/tv/menu/ItemListRow.java b/src/com/android/tv/menu/ItemListRow.java index faa611fa..2993d085 100644 --- a/src/com/android/tv/menu/ItemListRow.java +++ b/src/com/android/tv/menu/ItemListRow.java @@ -17,33 +17,37 @@ package com.android.tv.menu; import android.content.Context; - import com.android.tv.R; import com.android.tv.menu.ItemListRowView.ItemListAdapter; /** - * A menu item which is used to represents the list of the items. - * A list will be displayed by a HorizontalGridView with cards, so an adapter - * for the GridView is necessary. + * A menu item which is used to represents the list of the items. A list will be displayed by a + * HorizontalGridView with cards, so an adapter for the GridView is necessary. */ @SuppressWarnings("rawtypes") public class ItemListRow extends MenuRow { private ItemListAdapter mAdapter; - public ItemListRow(Context context, Menu menu, int titleResId, int itemHeightResId, + public ItemListRow( + Context context, + Menu menu, + int titleResId, + int itemHeightResId, ItemListAdapter adapter) { this(context, menu, context.getString(titleResId), itemHeightResId, adapter); } - public ItemListRow(Context context, Menu menu, String title, int itemHeightResId, + public ItemListRow( + Context context, + Menu menu, + String title, + int itemHeightResId, ItemListAdapter adapter) { super(context, menu, title, itemHeightResId); mAdapter = adapter; } - /** - * Returns the adapter. - */ + /** Returns the adapter. */ public ItemListAdapter getAdapter() { return mAdapter; } diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java index cbeee936..7042324d 100644 --- a/src/com/android/tv/menu/ItemListRowView.java +++ b/src/com/android/tv/menu/ItemListRowView.java @@ -25,25 +25,24 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.util.ViewCache; - import java.util.Collections; import java.util.List; -/** - * A view that shows a title and list view. - */ +/** A view that shows a title and list view. */ public class ItemListRowView extends MenuRowView implements OnChildSelectedListener { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; public interface CardView { void onBind(T row, boolean selected); + void onRecycled(); + void onSelected(); + void onDeselected(); } @@ -115,7 +114,7 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe } } - public static abstract class ItemListAdapter + public abstract static class ItemListAdapter extends RecyclerView.Adapter { private final MainActivity mMainActivity; private final LayoutInflater mLayoutInflater; @@ -129,25 +128,20 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe } /** - * In most cases, implementation should call {@link #setItemList(java.util.List)} with - * newly update item list. + * In most cases, implementation should call {@link #setItemList(java.util.List)} with newly + * update item list. */ public abstract void update(); - /** - * Gets layout resource ID. It'll be used in {@link #onCreateViewHolder}. - */ + /** Gets layout resource ID. It'll be used in {@link #onCreateViewHolder}. */ protected abstract int getLayoutResId(int viewType); - /** - * Releases all the resources which need to be released. - */ - public void release() { - } + /** Releases all the resources which need to be released. */ + public void release() {} /** - * The initial position of list that will be selected when the main menu appears. - * By default, the first item is initially selected. + * The initial position of list that will be selected when the main menu appears. By + * default, the first item is initially selected. */ public int getInitialPosition() { return 0; @@ -169,8 +163,8 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe *

This sends an item change event, not a structural change event. The items of the same * positions retain the same identity. * - *

If there's any structural change and relayout and rebind is needed, call - * {@link #notifyDataSetChanged} explicitly. + *

If there's any structural change and relayout and rebind is needed, call {@link + * #notifyDataSetChanged} explicitly. */ protected void setItemList(List itemList) { int oldSize = mItemList.size(); @@ -197,24 +191,21 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe return mItemList.size(); } - /** - * Returns the position of the item. - */ + /** Returns the position of the item. */ protected int getItemPosition(T item) { return mItemList.indexOf(item); } - /** - * Returns {@code true} if the item list contains the item, otherwise {@code false}. - */ + /** Returns {@code true} if the item list contains the item, otherwise {@code false}. */ protected boolean containsItem(T item) { return mItemList.contains(item); } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = ViewCache.getInstance().getOrCreateView( - mLayoutInflater, getLayoutResId(viewType), parent); + View view = + ViewCache.getInstance() + .getOrCreateView(mLayoutInflater, getLayoutResId(viewType), parent); return new MyViewHolder(view); } diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java index e373de61..2b8a1fa6 100644 --- a/src/com/android/tv/menu/Menu.java +++ b/src/com/android/tv/menu/Menu.java @@ -28,7 +28,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.v17.leanback.widget.HorizontalGridView; import android.util.Log; - import com.android.tv.ChannelTuner; import com.android.tv.R; import com.android.tv.TvApplication; @@ -41,7 +40,6 @@ import com.android.tv.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; import com.android.tv.util.DurationTimer; import com.android.tv.util.ViewCache; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -49,19 +47,25 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * A class which controls the menu. - */ +/** A class which controls the menu. */ public class Menu { private static final String TAG = "Menu"; private static final boolean DEBUG = false; @Retention(RetentionPolicy.SOURCE) - @IntDef({REASON_NONE, REASON_GUIDE, REASON_PLAY_CONTROLS_PLAY, REASON_PLAY_CONTROLS_PAUSE, - REASON_PLAY_CONTROLS_PLAY_PAUSE, REASON_PLAY_CONTROLS_REWIND, - REASON_PLAY_CONTROLS_FAST_FORWARD, REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS, - REASON_PLAY_CONTROLS_JUMP_TO_NEXT}) + @IntDef({ + REASON_NONE, + REASON_GUIDE, + REASON_PLAY_CONTROLS_PLAY, + REASON_PLAY_CONTROLS_PAUSE, + REASON_PLAY_CONTROLS_PLAY_PAUSE, + REASON_PLAY_CONTROLS_REWIND, + REASON_PLAY_CONTROLS_FAST_FORWARD, + REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS, + REASON_PLAY_CONTROLS_JUMP_TO_NEXT + }) public @interface MenuShowReason {} + public static final int REASON_NONE = 0; public static final int REASON_GUIDE = 1; public static final int REASON_PLAY_CONTROLS_PLAY = 2; @@ -73,6 +77,7 @@ public class Menu { public static final int REASON_PLAY_CONTROLS_JUMP_TO_NEXT = 8; private static final List sRowIdListForReason = new ArrayList<>(); + static { sRowIdListForReason.add(null); // REASON_NONE sRowIdListForReason.add(ChannelsRow.ID); // REASON_GUIDE @@ -86,6 +91,7 @@ public class Menu { } private static final Map PRELOAD_VIEW_IDS = new HashMap<>(); + static { PRELOAD_VIEW_IDS.put(R.layout.menu_card_guide, 1); PRELOAD_VIEW_IDS.put(R.layout.menu_card_setup, 1); @@ -116,13 +122,20 @@ public class Menu { private boolean mAnimationDisabledForTest; @VisibleForTesting - Menu(Context context, IMenuView menuView, MenuRowFactory menuRowFactory, + Menu( + Context context, + IMenuView menuView, + MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { this(context, null, null, menuView, menuRowFactory, onMenuVisibilityChangeListener); } - public Menu(Context context, TunableTvView tvView, TvOptionsManager optionsManager, - IMenuView menuView, MenuRowFactory menuRowFactory, + public Menu( + Context context, + TunableTvView tvView, + TvOptionsManager optionsManager, + IMenuView menuView, + MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { mContext = context; mMenuView = menuView; @@ -134,12 +147,13 @@ public class Menu { mShowAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_enter); mShowAnimator.setTarget(mMenuView); mHideAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_exit); - mHideAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - hideInternal(); - } - }); + mHideAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + hideInternal(); + } + }); mHideAnimator.setTarget(mMenuView); // Build menu rows addMenuRow(menuRowFactory.createMenuRow(this, PlayControlsRow.class)); @@ -163,9 +177,7 @@ public class Menu { } } - /** - * Call this method to end the lifetime of the menu. - */ + /** Call this method to end the lifetime of the menu. */ public void release() { mMenuUpdater.release(); for (MenuRow row : mMenuRows) { @@ -174,9 +186,7 @@ public class Menu { mHandler.removeCallbacksAndMessages(null); } - /** - * Preloads the item view used for the menu. - */ + /** Preloads the item view used for the menu. */ public void preloadItemViews() { HorizontalGridView fakeParent = new HorizontalGridView(mContext); for (int id : PRELOAD_VIEW_IDS.keySet()) { @@ -201,20 +211,23 @@ public class Menu { mOnMenuVisibilityChangeListener.onMenuVisibilityChange(true); } String rowIdToSelect = sRowIdListForReason.get(reason); - mMenuView.onShow(reason, rowIdToSelect, mAnimationDisabledForTest ? null : new Runnable() { - @Override - public void run() { - if (isActive()) { - mShowAnimator.start(); - } - } - }); + mMenuView.onShow( + reason, + rowIdToSelect, + mAnimationDisabledForTest + ? null + : new Runnable() { + @Override + public void run() { + if (isActive()) { + mShowAnimator.start(); + } + } + }); scheduleHide(); } - /** - * Closes the menu. - */ + /** Closes the menu. */ public void hide(boolean withAnimation) { if (mShowAnimator.isStarted()) { mShowAnimator.cancel(); @@ -246,9 +259,7 @@ public class Menu { } } - /** - * Schedules to hide the menu in some seconds. - */ + /** Schedules to hide the menu in some seconds. */ public void scheduleHide() { mHandler.removeMessages(MSG_HIDE_MENU); if (!mKeepVisible) { @@ -257,10 +268,10 @@ public class Menu { } /** - * Called when the caller wants the main menu to be kept visible or not. - * If {@code keepVisible} is set to {@code true}, the hide schedule doesn't close the main menu, - * but calling {@link #hide} still hides it. - * If {@code keepVisible} is set to {@code false}, the hide schedule works as usual. + * Called when the caller wants the main menu to be kept visible or not. If {@code keepVisible} + * is set to {@code true}, the hide schedule doesn't close the main menu, but calling {@link + * #hide} still hides it. If {@code keepVisible} is set to {@code false}, the hide schedule + * works as usual. */ public void setKeepVisible(boolean keepVisible) { mKeepVisible = keepVisible; @@ -276,9 +287,7 @@ public class Menu { return mHandler.hasMessages(MSG_HIDE_MENU); } - /** - * Returns {@code true} if the menu is open and not hiding. - */ + /** Returns {@code true} if the menu is open and not hiding. */ public boolean isActive() { return mMenuView.isVisible() && !mHideAnimator.isStarted(); } @@ -303,9 +312,7 @@ public class Menu { return mMenuView.update(rowId, isActive()); } - /** - * This method is called when channels are changed. - */ + /** This method is called when channels are changed. */ public void onRecentChannelsChanged() { if (DEBUG) Log.d(TAG, "onRecentChannelsChanged"); for (MenuRow row : mMenuRows) { @@ -313,9 +320,7 @@ public class Menu { } } - /** - * This method is called when the stream information is changed. - */ + /** This method is called when the stream information is changed. */ public void onStreamInfoChanged() { if (DEBUG) Log.d(TAG, "update options row in main menu"); mMenuUpdater.onStreamInfoChanged(); @@ -329,13 +334,9 @@ public class Menu { mAnimationDisabledForTest = true; } - /** - * A listener which receives the notification when the menu is visible/invisible. - */ - public static abstract class OnMenuVisibilityChangeListener { - /** - * Called when the menu becomes visible/invisible. - */ + /** A listener which receives the notification when the menu is visible/invisible. */ + public abstract static class OnMenuVisibilityChangeListener { + /** Called when the menu becomes visible/invisible. */ public abstract void onMenuVisibilityChange(boolean visible); } diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java index b4356059..52372535 100644 --- a/src/com/android/tv/menu/MenuAction.java +++ b/src/com/android/tv/menu/MenuAction.java @@ -19,37 +19,47 @@ package com.android.tv.menu; import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.TvOptionsManager; import com.android.tv.TvOptionsManager.OptionType; -/** - * A class to define possible actions from main menu. - */ +/** A class to define possible actions from main menu. */ public class MenuAction { // Actions in the TV option row. public static final MenuAction SELECT_CLOSED_CAPTION_ACTION = - new MenuAction(R.string.options_item_closed_caption, + new MenuAction( + R.string.options_item_closed_caption, TvOptionsManager.OPTION_CLOSED_CAPTIONS, R.drawable.ic_tvoption_cc); public static final MenuAction SELECT_DISPLAY_MODE_ACTION = - new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE, + new MenuAction( + R.string.options_item_display_mode, + TvOptionsManager.OPTION_DISPLAY_MODE, R.drawable.ic_tvoption_aspect); public static final MenuAction SYSTEMWIDE_PIP_ACTION = - new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP, + new MenuAction( + R.string.options_item_pip, + TvOptionsManager.OPTION_SYSTEMWIDE_PIP, R.drawable.ic_tvoption_pip); public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION = - new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO, + new MenuAction( + R.string.options_item_multi_audio, + TvOptionsManager.OPTION_MULTI_AUDIO, R.drawable.ic_tvoption_multi_track); public static final MenuAction MORE_CHANNELS_ACTION = - new MenuAction(R.string.options_item_more_channels, - TvOptionsManager.OPTION_MORE_CHANNELS, R.drawable.ic_store); + new MenuAction( + R.string.options_item_more_channels, + TvOptionsManager.OPTION_MORE_CHANNELS, + R.drawable.ic_store); public static final MenuAction DEV_ACTION = - new MenuAction(R.string.options_item_developer, - TvOptionsManager.OPTION_DEVELOPER, R.drawable.ic_developer_mode_tv_white_48dp); + new MenuAction( + R.string.options_item_developer, + TvOptionsManager.OPTION_DEVELOPER, + R.drawable.ic_developer_mode_tv_white_48dp); public static final MenuAction SETTINGS_ACTION = - new MenuAction(R.string.options_item_settings, TvOptionsManager.OPTION_SETTINGS, + new MenuAction( + R.string.options_item_settings, + TvOptionsManager.OPTION_SETTINGS, R.drawable.ic_settings); private final String mActionName; @@ -60,18 +70,14 @@ public class MenuAction { private int mDrawableResId; private boolean mEnabled = true; - /** - * Sets the action description. Returns {@code trye} if the description is changed. - */ + /** Sets the action description. Returns {@code trye} if the description is changed. */ public static boolean setActionDescription(MenuAction action, String actionDescription) { String oldDescription = action.mActionDescription; action.mActionDescription = actionDescription; return !TextUtils.equals(action.mActionDescription, oldDescription); } - /** - * Enables or disables the action. Returns {@code true} if the value is changed. - */ + /** Enables or disables the action. Returns {@code true} if the value is changed. */ public static boolean setEnabled(MenuAction action, boolean enabled) { boolean changed = action.mEnabled != enabled; action.mEnabled = enabled; @@ -105,13 +111,12 @@ public class MenuAction { return mActionDescription; } - @OptionType public int getType() { + @OptionType + public int getType() { return mType; } - /** - * Returns Drawable. - */ + /** Returns Drawable. */ public Drawable getDrawable(Context context) { if (mDrawable == null) { mDrawable = context.getDrawable(mDrawableResId); diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java index 173d4004..e652463e 100644 --- a/src/com/android/tv/menu/MenuLayoutManager.java +++ b/src/com/android/tv/menu/MenuLayoutManager.java @@ -34,11 +34,9 @@ import android.util.Property; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.common.SoftPreconditions; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -47,9 +45,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; -/** - * A view that represents TV main menu. - */ +/** A view that represents TV main menu. */ @UiThread public class MenuLayoutManager { static final String TAG = "MenuLayoutManager"; @@ -93,24 +89,22 @@ public class MenuLayoutManager { Resources res = context.getResources(); mRowAlignFromBottom = res.getDimensionPixelOffset(R.dimen.menu_row_align_from_bottom); mRowContentsPaddingTop = res.getDimensionPixelOffset(R.dimen.menu_row_contents_padding_top); - mRowContentsPaddingBottomMax = res.getDimensionPixelOffset( - R.dimen.menu_row_contents_padding_bottom_max); - mRowTitleTextDescenderHeight = res.getDimensionPixelOffset( - R.dimen.menu_row_title_text_descender_height); + mRowContentsPaddingBottomMax = + res.getDimensionPixelOffset(R.dimen.menu_row_contents_padding_bottom_max); + mRowTitleTextDescenderHeight = + res.getDimensionPixelOffset(R.dimen.menu_row_title_text_descender_height); mMenuMarginBottomMin = res.getDimensionPixelOffset(R.dimen.menu_margin_bottom_min); mRowTitleHeight = res.getDimensionPixelSize(R.dimen.menu_row_title_height); mRowScrollUpAnimationOffset = res.getDimensionPixelOffset(R.dimen.menu_row_scroll_up_anim_offset); mRowAnimationDuration = res.getInteger(R.integer.menu_row_selection_anim_duration); - mOldContentsFadeOutDuration = res.getInteger( - R.integer.menu_previous_contents_fade_out_duration); - mCurrentContentsFadeInDuration = res.getInteger( - R.integer.menu_current_contents_fade_in_duration); + mOldContentsFadeOutDuration = + res.getInteger(R.integer.menu_previous_contents_fade_out_duration); + mCurrentContentsFadeInDuration = + res.getInteger(R.integer.menu_current_contents_fade_in_duration); } - /** - * Sets the menu rows and views. - */ + /** Sets the menu rows and views. */ public void setMenuRowsAndViews(List menuRows, List menuRowViews) { mMenuRows.clear(); mMenuRows.addAll(menuRows); @@ -181,22 +175,54 @@ public class MenuLayoutManager { for (MenuRowView view : mMenuRowViews) { View title = view.getChildAt(0); View contents = view.getChildAt(1); - Log.d(TAG, prefix + " position=" + position++ - + " rowView={visiblility=" + view.getVisibility() - + ", alpha=" + view.getAlpha() - + ", translationY=" + view.getTranslationY() - + ", left=" + view.getLeft() + ", top=" + view.getTop() - + ", right=" + view.getRight() + ", bottom=" + view.getBottom() - + "}, title={visiblility=" + title.getVisibility() - + ", alpha=" + title.getAlpha() - + ", translationY=" + title.getTranslationY() - + ", left=" + title.getLeft() + ", top=" + title.getTop() - + ", right=" + title.getRight() + ", bottom=" + title.getBottom() - + "}, contents={visiblility=" + contents.getVisibility() - + ", alpha=" + contents.getAlpha() - + ", translationY=" + contents.getTranslationY() - + ", left=" + contents.getLeft() + ", top=" + contents.getTop() - + ", right=" + contents.getRight() + ", bottom=" + contents.getBottom()+ "}"); + Log.d( + TAG, + prefix + + " position=" + + position++ + + " rowView={visiblility=" + + view.getVisibility() + + ", alpha=" + + view.getAlpha() + + ", translationY=" + + view.getTranslationY() + + ", left=" + + view.getLeft() + + ", top=" + + view.getTop() + + ", right=" + + view.getRight() + + ", bottom=" + + view.getBottom() + + "}, title={visiblility=" + + title.getVisibility() + + ", alpha=" + + title.getAlpha() + + ", translationY=" + + title.getTranslationY() + + ", left=" + + title.getLeft() + + ", top=" + + title.getTop() + + ", right=" + + title.getRight() + + ", bottom=" + + title.getBottom() + + "}, contents={visiblility=" + + contents.getVisibility() + + ", alpha=" + + contents.getAlpha() + + ", translationY=" + + contents.getTranslationY() + + ", left=" + + contents.getLeft() + + ", top=" + + contents.getTop() + + ", right=" + + contents.getRight() + + ", bottom=" + + contents.getBottom() + + "}"); } } @@ -204,14 +230,14 @@ public class MenuLayoutManager { * Checks if the view will take up space for the layout not. * * @param position The index of the menu row view in the list. This is not the index of the view - * in the screen. + * in the screen. * @param view The menu row view. * @param rowsToAdd The menu row views to be added in the next layout process. * @param rowsToRemove The menu row views to be removed in the next layout process. * @return {@code true} if the view will take up space for the layout, otherwise {@code false}. */ - private boolean isVisibleInLayout(int position, MenuRowView view, List rowsToAdd, - List rowsToRemove) { + private boolean isVisibleInLayout( + int position, MenuRowView view, List rowsToAdd, List rowsToRemove) { // Checks if the view will be visible or not. return (view.getVisibility() != View.GONE && !rowsToRemove.contains(position)) || rowsToAdd.contains(position); @@ -226,8 +252,8 @@ public class MenuLayoutManager { * @param bottom The bottom coordinate of the menu view. */ private List getViewLayouts(int left, int top, int right, int bottom) { - return getViewLayouts(left, top, right, bottom, Collections.emptyList(), - Collections.emptyList()); + return getViewLayouts( + left, top, right, bottom, Collections.emptyList(), Collections.emptyList()); } /** @@ -247,8 +273,13 @@ public class MenuLayoutManager { * @param rowsToRemove The menu row views to be removed in the next layout process. * @return the layout bounds of the menu row views. */ - private List getViewLayouts(int left, int top, int right, int bottom, - List rowsToAdd, List rowsToRemove) { + private List getViewLayouts( + int left, + int top, + int right, + int bottom, + List rowsToAdd, + List rowsToRemove) { // The coordinates should be relative to the parent. int relativeLeft = 0; int relateiveRight = right - left; @@ -262,18 +293,29 @@ public class MenuLayoutManager { // Calculate for the selected row first. // The distance between the bottom of the screen and the vertical center of the contents // should be kept fixed. For more information, please see the redlines. - int childTop = relativeBottom - mRowAlignFromBottom - rowContentsHeight / 2 - - mRowContentsPaddingTop - rowTitleHeight; + int childTop = + relativeBottom + - mRowAlignFromBottom + - rowContentsHeight / 2 + - mRowContentsPaddingTop + - rowTitleHeight; int childBottom = relativeBottom; int position = mSelectedPosition + 1; for (; position < count; ++position) { // Find and layout the next row to calculate the bottom line of the selected row. MenuRowView nextView = mMenuRowViews.get(position); if (isVisibleInLayout(position, nextView, rowsToAdd, rowsToRemove)) { - int nextTitleTopMax = relativeBottom - mMenuMarginBottomMin - rowTitleHeight - + mRowTitleTextDescenderHeight; - int childBottomMax = relativeBottom - mRowAlignFromBottom + rowContentsHeight / 2 - + mRowContentsPaddingBottomMax - rowTitleHeight; + int nextTitleTopMax = + relativeBottom + - mMenuMarginBottomMin + - rowTitleHeight + + mRowTitleTextDescenderHeight; + int childBottomMax = + relativeBottom + - mRowAlignFromBottom + + rowContentsHeight / 2 + + mRowContentsPaddingBottomMax + - rowTitleHeight; childBottom = Math.min(nextTitleTopMax, childBottomMax); layouts.add(new Rect(relativeLeft, childBottom, relateiveRight, relativeBottom)); break; @@ -309,13 +351,16 @@ public class MenuLayoutManager { return layouts; } - /** - * Move the current selection to the given {@code position}. - */ + /** Move the current selection to the given {@code position}. */ public void setSelectedPosition(int position) { if (DEBUG) { - Log.d(TAG, "setSelectedPosition(position=" + position + ") {previousPosition=" - + mSelectedPosition + "}"); + Log.d( + TAG, + "setSelectedPosition(position=" + + position + + ") {previousPosition=" + + mSelectedPosition + + "}"); } if (mSelectedPosition == position) { return; @@ -347,13 +392,18 @@ public class MenuLayoutManager { } /** - * Move the current selection to the given {@code position} with animation. - * The animation specification is included in http://b/21069476 + * Move the current selection to the given {@code position} with animation. The animation + * specification is included in http://b/21069476 */ public void setSelectedPositionSmooth(final int position) { if (DEBUG) { - Log.d(TAG, "setSelectedPositionSmooth(position=" + position + ") {previousPosition=" - + mSelectedPosition + "}"); + Log.d( + TAG, + "setSelectedPositionSmooth(position=" + + position + + ") {previousPosition=" + + mSelectedPosition + + "}"); } if (mMenuView.getVisibility() != View.VISIBLE) { setSelectedPosition(position); @@ -363,8 +413,8 @@ public class MenuLayoutManager { return; } boolean oldIndexValid = Utils.isIndexValid(mMenuRowViews, mSelectedPosition); - SoftPreconditions - .checkState(oldIndexValid, TAG, "No previous selection: " + mSelectedPosition); + SoftPreconditions.checkState( + oldIndexValid, TAG, "No previous selection: " + mSelectedPosition); if (!oldIndexValid) { return; } @@ -415,8 +465,7 @@ public class MenuLayoutManager { mMenuView.requestFocus(); if (mTempTitleViewForOld == null) { // Initialize here because we don't know when the views are inflated. - mTempTitleViewForOld = - (TextView) mMenuView.findViewById(R.id.temp_title_for_old); + mTempTitleViewForOld = (TextView) mMenuView.findViewById(R.id.temp_title_for_old); mTempTitleViewForCurrent = (TextView) mMenuView.findViewById(R.id.temp_title_for_current); } @@ -425,16 +474,21 @@ public class MenuLayoutManager { mPropertyValuesAfterAnimation.clear(); List animators = new ArrayList<>(); boolean scrollDown = position > oldPosition; - List layouts = getViewLayouts(mMenuView.getLeft(), mMenuView.getTop(), - mMenuView.getRight(), mMenuView.getBottom()); + List layouts = + getViewLayouts( + mMenuView.getLeft(), + mMenuView.getTop(), + mMenuView.getRight(), + mMenuView.getBottom()); // Old row. MenuRow oldRow = mMenuRows.get(oldPosition); final MenuRowView oldView = mMenuRowViews.get(oldPosition); View oldContentsView = oldView.getContentsView(); // Old contents view. - animators.add(createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) - .setDuration(mOldContentsFadeOutDuration)); + animators.add( + createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) + .setDuration(mOldContentsFadeOutDuration)); final TextView oldTitleView = oldView.getTitleView(); setTempTitleView(mTempTitleViewForOld, oldTitleView); Rect oldLayoutRect = layouts.get(oldPosition); @@ -444,20 +498,36 @@ public class MenuLayoutManager { // This case is not included in the animation specification. mTempTitleViewForOld.setScaleX(1.0f); mTempTitleViewForOld.setScaleY(1.0f); - animators.add(createAlphaAnimator(mTempTitleViewForOld, 0.0f, - oldView.getTitleViewAlphaDeselected(), mFastOutLinearIn)); + animators.add( + createAlphaAnimator( + mTempTitleViewForOld, + 0.0f, + oldView.getTitleViewAlphaDeselected(), + mFastOutLinearIn)); int offset = oldLayoutRect.top - mTempTitleViewForOld.getTop(); - animators.add(createTranslationYAnimator(mTempTitleViewForOld, - offset + mRowScrollUpAnimationOffset, offset)); + animators.add( + createTranslationYAnimator( + mTempTitleViewForOld, + offset + mRowScrollUpAnimationOffset, + offset)); } else { - animators.add(createScaleXAnimator(mTempTitleViewForOld, - oldView.getTitleViewScaleSelected(), 1.0f)); - animators.add(createScaleYAnimator(mTempTitleViewForOld, - oldView.getTitleViewScaleSelected(), 1.0f)); - animators.add(createAlphaAnimator(mTempTitleViewForOld, oldTitleView.getAlpha(), - oldView.getTitleViewAlphaDeselected(), mLinearOutSlowIn)); - animators.add(createTranslationYAnimator(mTempTitleViewForOld, 0, - oldLayoutRect.top - mTempTitleViewForOld.getTop())); + animators.add( + createScaleXAnimator( + mTempTitleViewForOld, oldView.getTitleViewScaleSelected(), 1.0f)); + animators.add( + createScaleYAnimator( + mTempTitleViewForOld, oldView.getTitleViewScaleSelected(), 1.0f)); + animators.add( + createAlphaAnimator( + mTempTitleViewForOld, + oldTitleView.getAlpha(), + oldView.getTitleViewAlphaDeselected(), + mLinearOutSlowIn)); + animators.add( + createTranslationYAnimator( + mTempTitleViewForOld, + 0, + oldLayoutRect.top - mTempTitleViewForOld.getTop())); } oldTitleView.setAlpha(oldView.getTitleViewAlphaDeselected()); oldTitleView.setVisibility(View.INVISIBLE); @@ -471,23 +541,30 @@ public class MenuLayoutManager { // The maximum is to the top of the start position of mTempTitleViewForOld. int distanceCurrentTitle = currentLayoutRect.top - currentView.getTop(); int distance = Math.max(mRowScrollUpAnimationOffset, distanceCurrentTitle); - int distanceToTopOfSecondTitle = oldLayoutRect.top - mRowScrollUpAnimationOffset - - oldView.getTop(); - animators.add(createTranslationYAnimator(oldTitleView, 0.0f, - Math.min(distance, distanceToTopOfSecondTitle))); - animators.add(createAlphaAnimator(oldTitleView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) - .setDuration(mOldContentsFadeOutDuration)); - animators.add(createScaleXAnimator(oldTitleView, - oldView.getTitleViewScaleSelected(), 1.0f)); - animators.add(createScaleYAnimator(oldTitleView, - oldView.getTitleViewScaleSelected(), 1.0f)); + int distanceToTopOfSecondTitle = + oldLayoutRect.top - mRowScrollUpAnimationOffset - oldView.getTop(); + animators.add( + createTranslationYAnimator( + oldTitleView, 0.0f, Math.min(distance, distanceToTopOfSecondTitle))); + animators.add( + createAlphaAnimator(oldTitleView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) + .setDuration(mOldContentsFadeOutDuration)); + animators.add( + createScaleXAnimator(oldTitleView, oldView.getTitleViewScaleSelected(), 1.0f)); + animators.add( + createScaleYAnimator(oldTitleView, oldView.getTitleViewScaleSelected(), 1.0f)); mTempTitleViewForOld.setScaleX(1.0f); mTempTitleViewForOld.setScaleY(1.0f); - animators.add(createAlphaAnimator(mTempTitleViewForOld, 0.0f, - oldView.getTitleViewAlphaDeselected(), mFastOutLinearIn)); + animators.add( + createAlphaAnimator( + mTempTitleViewForOld, + 0.0f, + oldView.getTitleViewAlphaDeselected(), + mFastOutLinearIn)); int offset = oldLayoutRect.top - mTempTitleViewForOld.getTop(); - animators.add(createTranslationYAnimator(mTempTitleViewForOld, - offset - mRowScrollUpAnimationOffset, offset)); + animators.add( + createTranslationYAnimator( + mTempTitleViewForOld, offset - mRowScrollUpAnimationOffset, offset)); } // Current row. Rect currentLayoutRect = new Rect(layouts.get(position)); @@ -502,29 +579,40 @@ public class MenuLayoutManager { // The maximum is to the top of the end position of mTempTitleViewForCurrent. int distanceOldTitle = oldView.getTop() - oldLayoutRect.top; int distance = Math.max(mRowScrollUpAnimationOffset, distanceOldTitle); - int distanceTopOfSecondTitle = currentView.getTop() - mRowScrollUpAnimationOffset - - currentLayoutRect.top; - animators.add(createTranslationYAnimator(currentTitleView, - Math.min(distance, distanceTopOfSecondTitle), 0.0f)); + int distanceTopOfSecondTitle = + currentView.getTop() - mRowScrollUpAnimationOffset - currentLayoutRect.top; + animators.add( + createTranslationYAnimator( + currentTitleView, Math.min(distance, distanceTopOfSecondTitle), 0.0f)); currentView.setTop(currentLayoutRect.top); - ObjectAnimator animator = createAlphaAnimator(currentTitleView, 0.0f, 1.0f, - mFastOutLinearIn).setDuration(mCurrentContentsFadeInDuration); + ObjectAnimator animator = + createAlphaAnimator(currentTitleView, 0.0f, 1.0f, mFastOutLinearIn) + .setDuration(mCurrentContentsFadeInDuration); animator.setStartDelay(mOldContentsFadeOutDuration); currentTitleView.setAlpha(0.0f); animators.add(animator); - animators.add(createScaleXAnimator(currentTitleView, 1.0f, - currentView.getTitleViewScaleSelected())); - animators.add(createScaleYAnimator(currentTitleView, 1.0f, - currentView.getTitleViewScaleSelected())); - animators.add(createTranslationYAnimator(mTempTitleViewForCurrent, 0.0f, - -mRowScrollUpAnimationOffset)); - animators.add(createAlphaAnimator(mTempTitleViewForCurrent, - currentView.getTitleViewAlphaDeselected(), 0, mLinearOutSlowIn)); + animators.add( + createScaleXAnimator( + currentTitleView, 1.0f, currentView.getTitleViewScaleSelected())); + animators.add( + createScaleYAnimator( + currentTitleView, 1.0f, currentView.getTitleViewScaleSelected())); + animators.add( + createTranslationYAnimator( + mTempTitleViewForCurrent, 0.0f, -mRowScrollUpAnimationOffset)); + animators.add( + createAlphaAnimator( + mTempTitleViewForCurrent, + currentView.getTitleViewAlphaDeselected(), + 0, + mLinearOutSlowIn)); // Current contents view. - animators.add(createTranslationYAnimator(currentContentsView, - mRowScrollUpAnimationOffset, 0.0f)); - animator = createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn) - .setDuration(mCurrentContentsFadeInDuration); + animators.add( + createTranslationYAnimator( + currentContentsView, mRowScrollUpAnimationOffset, 0.0f)); + animator = + createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn) + .setDuration(mCurrentContentsFadeInDuration); animator.setStartDelay(mOldContentsFadeOutDuration); animators.add(animator); } else { @@ -532,17 +620,27 @@ public class MenuLayoutManager { // Current title view. int currentViewOffset = currentLayoutRect.top - currentView.getTop(); animators.add(createTranslationYAnimator(currentTitleView, 0, currentViewOffset)); - animators.add(createAlphaAnimator(currentTitleView, - currentView.getTitleViewAlphaDeselected(), 1.0f, mFastOutSlowIn)); - animators.add(createScaleXAnimator(currentTitleView, 1.0f, - currentView.getTitleViewScaleSelected())); - animators.add(createScaleYAnimator(currentTitleView, 1.0f, - currentView.getTitleViewScaleSelected())); + animators.add( + createAlphaAnimator( + currentTitleView, + currentView.getTitleViewAlphaDeselected(), + 1.0f, + mFastOutSlowIn)); + animators.add( + createScaleXAnimator( + currentTitleView, 1.0f, currentView.getTitleViewScaleSelected())); + animators.add( + createScaleYAnimator( + currentTitleView, 1.0f, currentView.getTitleViewScaleSelected())); // Current contents view. - animators.add(createTranslationYAnimator(currentContentsView, - currentViewOffset - mRowScrollUpAnimationOffset, currentViewOffset)); - ObjectAnimator animator = createAlphaAnimator(currentContentsView, 0.0f, 1.0f, - mFastOutLinearIn).setDuration(mCurrentContentsFadeInDuration); + animators.add( + createTranslationYAnimator( + currentContentsView, + currentViewOffset - mRowScrollUpAnimationOffset, + currentViewOffset)); + ObjectAnimator animator = + createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn) + .setDuration(mCurrentContentsFadeInDuration); animator.setStartDelay(mOldContentsFadeOutDuration); animators.add(animator); } @@ -553,9 +651,13 @@ public class MenuLayoutManager { if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); Rect nextLayoutRect = layouts.get(nextPosition); - animators.add(createTranslationYAnimator(nextView, - nextLayoutRect.top + mRowScrollUpAnimationOffset - nextView.getTop(), - nextLayoutRect.top - nextView.getTop())); + animators.add( + createTranslationYAnimator( + nextView, + nextLayoutRect.top + + mRowScrollUpAnimationOffset + - nextView.getTop(), + nextLayoutRect.top - nextView.getTop())); animators.add(createAlphaAnimator(nextView, 0.0f, 1.0f, mFastOutLinearIn)); } } else { @@ -563,15 +665,22 @@ public class MenuLayoutManager { if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); animators.add(createTranslationYAnimator(nextView, 0, mRowScrollUpAnimationOffset)); - animators.add(createAlphaAnimator(nextView, - nextView.getTitleViewAlphaDeselected(), 0.0f, 1.0f, mLinearOutSlowIn)); + animators.add( + createAlphaAnimator( + nextView, + nextView.getTitleViewAlphaDeselected(), + 0.0f, + 1.0f, + mLinearOutSlowIn)); } } // Other rows. int count = mMenuRowViews.size(); for (int i = 0; i < count; ++i) { MenuRowView view = mMenuRowViews.get(i); - if (view.getVisibility() == View.VISIBLE && i != oldPosition && i != position + if (view.getVisibility() == View.VISIBLE + && i != oldPosition + && i != position && i != nextPosition) { Rect rect = layouts.get(i); animators.add(createTranslationYAnimator(view, 0, rect.top - view.getTop())); @@ -582,51 +691,62 @@ public class MenuLayoutManager { propertyValuesAfterAnimation.addAll(mPropertyValuesAfterAnimation); mAnimatorSet = new AnimatorSet(); mAnimatorSet.playTogether(animators); - mAnimatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - if (DEBUG) dumpChildren("onRowAnimationEndBefore"); - mAnimatorSet = null; - // The property values which are different from the end values and need to be - // changed after the animation are set here. - // e.g. setting translationY to 0, alpha of the contents view to 1. - for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { - holder.property.set(holder.view, holder.value); - } - oldView.onDeselected(); - currentView.onSelected(true); - mTempTitleViewForOld.setVisibility(View.GONE); - mTempTitleViewForCurrent.setVisibility(View.GONE); - layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(), - mMenuView.getBottom()); - if (DEBUG) dumpChildren("onRowAnimationEndAfter"); - - MenuRow currentRow = mMenuRows.get(position); - if (currentRow.hideTitleWhenSelected()) { - View titleView = mMenuRowViews.get(position).getTitleView(); - mTitleFadeOutAnimator = createAlphaAnimator(titleView, titleView.getAlpha(), - 0.0f, mLinearOutSlowIn); - mTitleFadeOutAnimator.setStartDelay(TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS); - mTitleFadeOutAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mCanceled; - - @Override - public void onAnimationCancel(Animator animator) { - mCanceled = true; + mAnimatorSet.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (DEBUG) dumpChildren("onRowAnimationEndBefore"); + mAnimatorSet = null; + // The property values which are different from the end values and need to + // be + // changed after the animation are set here. + // e.g. setting translationY to 0, alpha of the contents view to 1. + for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { + holder.property.set(holder.view, holder.value); } - - @Override - public void onAnimationEnd(Animator animator) { - mTitleFadeOutAnimator = null; - if (!mCanceled) { - mMenuRowViews.get(position).onSelected(false); - } + oldView.onDeselected(); + currentView.onSelected(true); + mTempTitleViewForOld.setVisibility(View.GONE); + mTempTitleViewForCurrent.setVisibility(View.GONE); + layout( + mMenuView.getLeft(), + mMenuView.getTop(), + mMenuView.getRight(), + mMenuView.getBottom()); + if (DEBUG) dumpChildren("onRowAnimationEndAfter"); + + MenuRow currentRow = mMenuRows.get(position); + if (currentRow.hideTitleWhenSelected()) { + View titleView = mMenuRowViews.get(position).getTitleView(); + mTitleFadeOutAnimator = + createAlphaAnimator( + titleView, + titleView.getAlpha(), + 0.0f, + mLinearOutSlowIn); + mTitleFadeOutAnimator.setStartDelay( + TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS); + mTitleFadeOutAnimator.addListener( + new AnimatorListenerAdapter() { + private boolean mCanceled; + + @Override + public void onAnimationCancel(Animator animator) { + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animator) { + mTitleFadeOutAnimator = null; + if (!mCanceled) { + mMenuRowViews.get(position).onSelected(false); + } + } + }); + mTitleFadeOutAnimator.start(); } - }); - mTitleFadeOutAnimator.start(); - } - } - }); + } + }); mAnimatorSet.start(); if (DEBUG) dumpChildren("startedRowAnimation()"); } @@ -661,7 +781,8 @@ public class MenuLayoutManager { if (mMenuView.getVisibility() != View.VISIBLE) { int count = mMenuRowViews.size(); for (int i = 0; i < count; ++i) { - mMenuRowViews.get(i) + mMenuRowViews + .get(i) .setVisibility(mMenuRows.get(i).isVisible() ? View.VISIBLE : View.GONE); } return; @@ -674,8 +795,8 @@ public class MenuLayoutManager { for (int i = mSelectedPosition - 1; i >= 0; --i) { MenuRow row = mMenuRows.get(i); MenuRowView view = mMenuRowViews.get(i); - if (row.isVisible() && (view.getVisibility() == View.GONE - || mRemovingRowViews.contains(i))) { + if (row.isVisible() + && (view.getVisibility() == View.GONE || mRemovingRowViews.contains(i))) { // Removing rows are still VISIBLE. addedRowViews.add(i); ++added; @@ -691,8 +812,8 @@ public class MenuLayoutManager { for (int i = mSelectedPosition + 1; i < count; ++i) { MenuRow row = mMenuRows.get(i); MenuRowView view = mMenuRowViews.get(i); - if (row.isVisible() && (view.getVisibility() == View.GONE - || mRemovingRowViews.contains(i))) { + if (row.isVisible() + && (view.getVisibility() == View.GONE || mRemovingRowViews.contains(i))) { // Removing rows are still VISIBLE. addedRowViews.add(i); ++added; @@ -717,8 +838,14 @@ public class MenuLayoutManager { } mPropertyValuesAfterAnimation.clear(); List animators = new ArrayList<>(); - List layouts = getViewLayouts(mMenuView.getLeft(), mMenuView.getTop(), - mMenuView.getRight(), mMenuView.getBottom(), addedRowViews, removedRowViews); + List layouts = + getViewLayouts( + mMenuView.getLeft(), + mMenuView.getTop(), + mMenuView.getRight(), + mMenuView.getBottom(), + addedRowViews, + removedRowViews); for (int position : addedRowViews) { MenuRowView view = mMenuRowViews.get(position); view.setVisibility(View.VISIBLE); @@ -728,7 +855,8 @@ public class MenuLayoutManager { view.layout(rect.left, rect.top, rect.right, rect.bottom); View titleView = view.getTitleView(); MarginLayoutParams params = (MarginLayoutParams) titleView.getLayoutParams(); - titleView.layout(view.getPaddingLeft() + params.leftMargin, + titleView.layout( + view.getPaddingLeft() + params.leftMargin, view.getPaddingTop() + params.topMargin, rect.right - rect.left - view.getPaddingRight() - params.rightMargin, rect.bottom - rect.top - view.getPaddingBottom() - params.bottomMargin); @@ -749,23 +877,28 @@ public class MenuLayoutManager { mRemovingRowViews.addAll(removedRowViews); mAnimatorSet = new AnimatorSet(); mAnimatorSet.playTogether(animators); - mAnimatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimatorSet = null; - // The property values which are different from the end values and need to be - // changed after the animation are set here. - // e.g. setting translationY to 0, alpha of the contents view to 1. - for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { - holder.property.set(holder.view, holder.value); - } - for (int position : mRemovingRowViews) { - mMenuRowViews.get(position).setVisibility(View.GONE); - } - layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(), - mMenuView.getBottom()); - } - }); + mAnimatorSet.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimatorSet = null; + // The property values which are different from the end values and need to + // be + // changed after the animation are set here. + // e.g. setting translationY to 0, alpha of the contents view to 1. + for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { + holder.property.set(holder.view, holder.value); + } + for (int position : mRemovingRowViews) { + mMenuRowViews.get(position).setVisibility(View.GONE); + } + layout( + mMenuView.getLeft(), + mMenuView.getTop(), + mMenuView.getRight(), + mMenuView.getBottom()); + } + }); mAnimatorSet.start(); if (DEBUG) dumpChildren("onMenuRowUpdated()"); } @@ -778,16 +911,16 @@ public class MenuLayoutManager { return animator; } - private ObjectAnimator createAlphaAnimator(View view, float from, float to, - TimeInterpolator interpolator) { + private ObjectAnimator createAlphaAnimator( + View view, float from, float to, TimeInterpolator interpolator) { ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, from, to); animator.setDuration(mRowAnimationDuration); animator.setInterpolator(interpolator); return animator; } - private ObjectAnimator createAlphaAnimator(View view, float from, float to, float end, - TimeInterpolator interpolator) { + private ObjectAnimator createAlphaAnimator( + View view, float from, float to, float end, TimeInterpolator interpolator) { ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, from, to); animator.setDuration(mRowAnimationDuration); animator.setInterpolator(interpolator); @@ -809,9 +942,7 @@ public class MenuLayoutManager { return animator; } - /** - * Returns the current position. - */ + /** Returns the current position. */ public int getSelectedPosition() { return mSelectedPosition; } @@ -828,15 +959,10 @@ public class MenuLayoutManager { } } - /** - * Called when the menu becomes visible. - */ - public void onMenuShow() { - } + /** Called when the menu becomes visible. */ + public void onMenuShow() {} - /** - * Called when the menu becomes hidden. - */ + /** Called when the menu becomes hidden. */ public void onMenuHide() { if (mAnimatorSet != null) { mAnimatorSet.end(); diff --git a/src/com/android/tv/menu/MenuRow.java b/src/com/android/tv/menu/MenuRow.java index 47804f11..8dc12bad 100644 --- a/src/com/android/tv/menu/MenuRow.java +++ b/src/com/android/tv/menu/MenuRow.java @@ -17,13 +17,11 @@ package com.android.tv.menu; import android.content.Context; -import android.view.View; /** - * A base class of the item which will be displayed in the main menu. - * It contains the data such as title to represent a row. - * This is an abstract class and the sub-class could have it's own data for - * the row. + * A base class of the item which will be displayed in the main menu. It contains the data such as + * title to represent a row. This is an abstract class and the sub-class could have it's own data + * for the row. */ public abstract class MenuRow { private final Context mContext; @@ -45,86 +43,60 @@ public abstract class MenuRow { mHeight = context.getResources().getDimensionPixelSize(heightResId); } - /** - * Returns the context. - */ + /** Returns the context. */ protected Context getContext() { return mContext; } - /** - * Returns the menu object. - */ + /** Returns the menu object. */ public Menu getMenu() { return mMenu; } - /** - * Returns the title of this row. - */ + /** Returns the title of this row. */ public String getTitle() { return mTitle; } - /** - * Returns the height of this row. - */ + /** Returns the height of this row. */ public int getHeight() { return mHeight; } - /** - * Sets the menu row view. - */ + /** Sets the menu row view. */ public void setMenuRowView(MenuRowView menuRowView) { mMenuRowView = menuRowView; } - /** - * Returns the menu row view. - */ + /** Returns the menu row view. */ protected MenuRowView getMenuRowView() { return mMenuRowView; } - /** - * Updates the contents in this row. - * This method is called only by the menu when necessary. - */ - abstract public void update(); + /** Updates the contents in this row. This method is called only by the menu when necessary. */ + public abstract void update(); - /** - * Indicates whether this row is shown in the menu. - */ + /** Indicates whether this row is shown in the menu. */ public boolean isVisible() { return true; } /** - * Releases all the resources which need to be released. - * This method is called when the main menu is not available any more. + * Releases all the resources which need to be released. This method is called when the main + * menu is not available any more. */ - public void release() { - } + public void release() {} - /** - * Returns the ID of the layout resource for this row. - */ - abstract public int getLayoutResId(); + /** Returns the ID of the layout resource for this row. */ + public abstract int getLayoutResId(); - /** - * Returns the ID of this row. This ID is used to select the row in the main menu. - */ - abstract public String getId(); + /** Returns the ID of this row. This ID is used to select the row in the main menu. */ + public abstract String getId(); - /** - * This method is called when recent channels are changed. - */ - public void onRecentChannelsChanged() { } + /** This method is called when recent channels are changed. */ + public void onRecentChannelsChanged() {} - /** - * Returns whether to hide the title when the row is selected. - */ + /** Returns whether to hide the title when the row is selected. */ public boolean hideTitleWhenSelected() { return false; } diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java index 570cfb8f..5424e6f6 100644 --- a/src/com/android/tv/menu/MenuRowFactory.java +++ b/src/com/android/tv/menu/MenuRowFactory.java @@ -19,26 +19,20 @@ package com.android.tv.menu; import android.content.Context; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.customization.CustomAction; import com.android.tv.customization.TvCustomizationManager; import com.android.tv.ui.TunableTvView; - import java.util.List; -/** - * A factory class to create menu rows. - */ +/** A factory class to create menu rows. */ public class MenuRowFactory { private final MainActivity mMainActivity; private final TunableTvView mTvView; private final TvCustomizationManager mTvCustomizationManager; - /** - * A constructor. - */ + /** A constructor. */ public MenuRowFactory(MainActivity mainActivity, TunableTvView tvView) { mMainActivity = mainActivity; mTvView = tvView; @@ -46,53 +40,56 @@ public class MenuRowFactory { mTvCustomizationManager.initialize(); } - /** - * Creates an object corresponding to the given {@code key}. - */ + /** Creates an object corresponding to the given {@code key}. */ @Nullable public MenuRow createMenuRow(Menu menu, Class key) { if (PlayControlsRow.class.equals(key)) { - return new PlayControlsRow(mMainActivity, mTvView, menu, - mMainActivity.getTimeShiftManager()); + return new PlayControlsRow( + mMainActivity, mTvView, menu, mMainActivity.getTimeShiftManager()); } else if (ChannelsRow.class.equals(key)) { return new ChannelsRow(mMainActivity, menu, mMainActivity.getProgramDataManager()); } else if (PartnerRow.class.equals(key)) { - List customActions = mTvCustomizationManager.getCustomActions( - TvCustomizationManager.ID_PARTNER_ROW); + List customActions = + mTvCustomizationManager.getCustomActions(TvCustomizationManager.ID_PARTNER_ROW); String title = mTvCustomizationManager.getPartnerRowTitle(); if (customActions != null && !TextUtils.isEmpty(title)) { return new PartnerRow(mMainActivity, menu, title, customActions); } return null; } else if (TvOptionsRow.class.equals(key)) { - return new TvOptionsRow(mMainActivity, menu, mTvCustomizationManager - .getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW)); + return new TvOptionsRow( + mMainActivity, + menu, + mTvCustomizationManager.getCustomActions( + TvCustomizationManager.ID_OPTIONS_ROW)); } return null; } - /** - * A menu row which represents the TV options row. - */ + /** A menu row which represents the TV options row. */ public static class TvOptionsRow extends ItemListRow { - /** - * The ID of the row. - */ + /** The ID of the row. */ public static final String ID = TvOptionsRow.class.getName(); private TvOptionsRow(Context context, Menu menu, List customActions) { - super(context, menu, R.string.menu_title_options, R.dimen.action_card_height, + super( + context, + menu, + R.string.menu_title_options, + R.dimen.action_card_height, new TvOptionsRowAdapter(context, customActions)); } } - /** - * A menu row which represents the partner row. - */ + /** A menu row which represents the partner row. */ public static class PartnerRow extends ItemListRow { - private PartnerRow(Context context, Menu menu, String title, - List customActions) { - super(context, menu, title, R.dimen.action_card_height, + private PartnerRow( + Context context, Menu menu, String title, List customActions) { + super( + context, + menu, + title, + R.dimen.action_card_height, new PartnerOptionsRowAdapter(context, customActions)); } } diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java index 97dea29a..a064f352 100644 --- a/src/com/android/tv/menu/MenuRowView.java +++ b/src/com/android/tv/menu/MenuRowView.java @@ -27,7 +27,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.menu.Menu.MenuShowReason; @@ -46,25 +45,23 @@ public abstract class MenuRowView extends LinearLayout { * reset when the menu is popped up. */ private View mLastFocusView; + private MenuRow mRow; - private final OnFocusChangeListener mOnFocusChangeListener = new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - onChildFocusChange(v, hasFocus); - } - }; + private final OnFocusChangeListener mOnFocusChangeListener = + new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + onChildFocusChange(v, hasFocus); + } + }; - /** - * Returns the alpha value of the title view when it's deselected. - */ + /** Returns the alpha value of the title view when it's deselected. */ public float getTitleViewAlphaDeselected() { return mTitleViewAlphaDeselected; } - /** - * Returns the scale value of the title view when it's selected. - */ + /** Returns the scale value of the title view when it's selected. */ public float getTitleViewScaleSelected() { return mTitleViewScaleSelected; } @@ -125,26 +122,22 @@ public abstract class MenuRowView extends LinearLayout { } } - abstract protected int getContentsViewId(); + protected abstract int getContentsViewId(); - /** - * Returns the title view. - */ + /** Returns the title view. */ public final TextView getTitleView() { return mTitleView; } - /** - * Returns the contents view. - */ + /** Returns the contents view. */ public final View getContentsView() { return mContentsView; } /** - * Initialize this view. e.g. Set the initial selection. - * This method is called when the main menu is visible. - * Subclass of {@link MenuRowView} should override this to set correct mLastFocusView. + * Initialize this view. e.g. Set the initial selection. This method is called when the main + * menu is visible. Subclass of {@link MenuRowView} should override this to set correct + * mLastFocusView. * * @param reason A reason why this is initialized. See {@link MenuShowReason} */ @@ -177,17 +170,17 @@ public abstract class MenuRowView extends LinearLayout { } /** - * Sets the view which needs to have focus when this row appears. - * Subclasses should call this in {@link #initialize} if needed. + * Sets the view which needs to have focus when this row appears. Subclasses should call this in + * {@link #initialize} if needed. */ protected void setInitialFocusView(@NonNull View v) { mLastFocusView = v; } /** - * Called when the focus of a child view is changed. - * The inherited class should override this method instead of calling - * {@link android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}. + * Called when the focus of a child view is changed. The inherited class should override this + * method instead of calling {@link + * android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}. */ protected void onChildFocusChange(View v, boolean hasFocus) { if (hasFocus) { @@ -195,9 +188,7 @@ public abstract class MenuRowView extends LinearLayout { } } - /** - * Returns the ID of row object bound to this view. - */ + /** Returns the ID of row object bound to this view. */ public String getRowId() { return mRow == null ? null : mRow.getId(); } @@ -206,7 +197,7 @@ public abstract class MenuRowView extends LinearLayout { * Called when this row is selected. * * @param showTitle If {@code true}, the title is not hidden immediately after the row is - * selected even though hideTitleWhenSelected() is {@code true}. + * selected even though hideTitleWhenSelected() is {@code true}. */ public void onSelected(boolean showTitle) { if (mRow.hideTitleWhenSelected() && !showTitle) { @@ -225,9 +216,7 @@ public abstract class MenuRowView extends LinearLayout { mLastFocusView = lastFocusView; } - /** - * Called when this row is deselected. - */ + /** Called when this row is deselected. */ public void onDeselected() { mTitleView.setVisibility(VISIBLE); mTitleView.setAlpha(mTitleViewAlphaDeselected); @@ -236,9 +225,7 @@ public abstract class MenuRowView extends LinearLayout { mContentsView.setVisibility(GONE); } - /** - * Returns the preferred height of the contents view. The top/bottom padding is excluded. - */ + /** Returns the preferred height of the contents view. The top/bottom padding is excluded. */ public int getPreferredContentsHeight() { return mRow.getHeight(); } diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java index 18416c85..2d81b93a 100644 --- a/src/com/android/tv/menu/MenuUpdater.java +++ b/src/com/android/tv/menu/MenuUpdater.java @@ -17,7 +17,6 @@ package com.android.tv.menu; import android.support.annotation.Nullable; - import com.android.tv.ChannelTuner; import com.android.tv.TvOptionsManager; import com.android.tv.TvOptionsManager.OptionChangedListener; @@ -39,49 +38,52 @@ public class MenuUpdater { @Nullable private final TvOptionsManager mOptionsManager; private ChannelTuner mChannelTuner; - private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() { - @Override - public void onLoadFinished() {} + private final ChannelTuner.Listener mChannelTunerListener = + new ChannelTuner.Listener() { + @Override + public void onLoadFinished() {} - @Override - public void onBrowsableChannelListChanged() { - mMenu.update(ChannelsRow.ID); - } + @Override + public void onBrowsableChannelListChanged() { + mMenu.update(ChannelsRow.ID); + } - @Override - public void onCurrentChannelUnavailable(Channel channel) {} + @Override + public void onCurrentChannelUnavailable(Channel channel) {} - @Override - public void onChannelChanged(Channel previousChannel, Channel currentChannel) { - mMenu.update(ChannelsRow.ID); - } - }; - private final OptionChangedListener mOptionChangeListener = new OptionChangedListener() { - @Override - public void onOptionChanged(@OptionType int optionType, String newString) { - mMenu.update(TvOptionsRow.ID); - } - }; + @Override + public void onChannelChanged(Channel previousChannel, Channel currentChannel) { + mMenu.update(ChannelsRow.ID); + } + }; + private final OptionChangedListener mOptionChangeListener = + new OptionChangedListener() { + @Override + public void onOptionChanged(@OptionType int optionType, String newString) { + mMenu.update(TvOptionsRow.ID); + } + }; public MenuUpdater(Menu menu, TunableTvView tvView, TvOptionsManager optionsManager) { mMenu = menu; mTvView = tvView; mOptionsManager = optionsManager; if (mTvView != null) { - mTvView.setOnScreenBlockedListener(new OnScreenBlockingChangedListener() { - @Override - public void onScreenBlockingChanged(boolean blocked) { - mMenu.update(PlayControlsRow.ID); - } - }); + mTvView.setOnScreenBlockedListener( + new OnScreenBlockingChangedListener() { + @Override + public void onScreenBlockingChanged(boolean blocked) { + mMenu.update(PlayControlsRow.ID); + } + }); } if (mOptionsManager != null) { - mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, - mOptionChangeListener); - mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, - mOptionChangeListener); - mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, - mOptionChangeListener); + mOptionsManager.setOptionChangedListener( + TvOptionsManager.OPTION_CLOSED_CAPTIONS, mOptionChangeListener); + mOptionsManager.setOptionChangedListener( + TvOptionsManager.OPTION_DISPLAY_MODE, mOptionChangeListener); + mOptionsManager.setOptionChangedListener( + TvOptionsManager.OPTION_MULTI_AUDIO, mOptionChangeListener); } } @@ -98,16 +100,12 @@ public class MenuUpdater { } } - /** - * Called when the stream information changes. - */ + /** Called when the stream information changes. */ public void onStreamInfoChanged() { mMenu.update(TvOptionsRow.ID); } - /** - * Called at the end of the menu's lifetime. - */ + /** Called at the end of the menu's lifetime. */ public void release() { if (mChannelTuner != null) { mChannelTuner.removeListener(mChannelTunerListener); diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java index ee0b036e..f5fec000 100644 --- a/src/com/android/tv/menu/MenuView.java +++ b/src/com/android/tv/menu/MenuView.java @@ -26,15 +26,11 @@ import android.view.ViewParent; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.FrameLayout; - import com.android.tv.menu.Menu.MenuShowReason; - import java.util.ArrayList; import java.util.List; -/** - * A view that represents TV main menu. - */ +/** A view that represents TV main menu. */ public class MenuView extends FrameLayout implements IMenuView { static final String TAG = MenuView.class.getSimpleName(); static final boolean DEBUG = false; @@ -60,20 +56,25 @@ public class MenuView extends FrameLayout implements IMenuView { mLayoutInflater = LayoutInflater.from(context); // Set hardware layer type for smooth animation of lots of views. setLayerType(LAYER_TYPE_HARDWARE, null); - getViewTreeObserver().addOnGlobalFocusChangeListener(new OnGlobalFocusChangeListener() { - @Override - public void onGlobalFocusChanged(View oldFocus, View newFocus) { - MenuRowView newParent = getParentMenuRowView(newFocus); - if (newParent != null) { - if (DEBUG) Log.d(TAG, "Focus changed to " + newParent); - // When the row is selected, the row view itself has the focus because the row - // is collapsed. To make the child of the row have the focus, requestFocus() - // should be called again after the row is expanded. It's done in - // setSelectedPosition(). - setSelectedPositionSmooth(mMenuRowViews.indexOf(newParent)); - } - } - }); + getViewTreeObserver() + .addOnGlobalFocusChangeListener( + new OnGlobalFocusChangeListener() { + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + MenuRowView newParent = getParentMenuRowView(newFocus); + if (newParent != null) { + if (DEBUG) Log.d(TAG, "Focus changed to " + newParent); + // When the row is selected, the row view itself has the focus + // because the row + // is collapsed. To make the child of the row have the focus, + // requestFocus() + // should be called again after the row is expanded. It's done + // in + // setSelectedPosition(). + setSelectedPositionSmooth(mMenuRowViews.indexOf(newParent)); + } + } + }); mLayoutManager = new MenuLayoutManager(context, this); } @@ -102,8 +103,8 @@ public class MenuView extends FrameLayout implements IMenuView { } @Override - public void onShow(@MenuShowReason int reason, String rowIdToSelect, - final Runnable runnableAfterShow) { + public void onShow( + @MenuShowReason int reason, String rowIdToSelect, final Runnable runnableAfterShow) { if (DEBUG) { Log.d(TAG, "onShow(reason=" + reason + ", rowIdToSelect=" + rowIdToSelect + ")"); } @@ -132,15 +133,18 @@ public class MenuView extends FrameLayout implements IMenuView { // Make the selected row have the focus. requestFocus(); if (runnableAfterShow != null) { - getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - // Start show animation after layout finishes for smooth animation because the - // layout can take long time. - runnableAfterShow.run(); - } - }); + getViewTreeObserver() + .addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + // Start show animation after layout finishes for smooth + // animation because the + // layout can take long time. + runnableAfterShow.run(); + } + }); } mLayoutManager.onMenuShow(); } diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java index dd6194a1..593bede7 100644 --- a/src/com/android/tv/menu/OptionsRowAdapter.java +++ b/src/com/android/tv/menu/OptionsRowAdapter.java @@ -18,11 +18,9 @@ package com.android.tv.menu; import android.content.Context; import android.view.View; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.analytics.Tracker; - import java.util.List; /* @@ -33,33 +31,33 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< protected final Tracker mTracker; private List mActionList; - private final View.OnClickListener mMenuActionOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - final MenuAction action = (MenuAction) view.getTag(); - view.post(new Runnable() { + private final View.OnClickListener mMenuActionOnClickListener = + new View.OnClickListener() { @Override - public void run() { - int resId = action.getActionNameResId(); - if (resId == 0) { - mTracker.sendMenuClicked(CUSTOM_ACTION_LABEL); - } else { - mTracker.sendMenuClicked(resId); - } - executeAction(action.getType()); + public void onClick(View view) { + final MenuAction action = (MenuAction) view.getTag(); + view.post( + new Runnable() { + @Override + public void run() { + int resId = action.getActionNameResId(); + if (resId == 0) { + mTracker.sendMenuClicked(CUSTOM_ACTION_LABEL); + } else { + mTracker.sendMenuClicked(resId); + } + executeAction(action.getType()); + } + }); } - }); - } - }; + }; public OptionsRowAdapter(Context context) { super(context); mTracker = TvApplication.getSingletons(context).getTracker(); } - /** - * Update action list and its content. - */ + /** Update action list and its content. */ @Override public void update() { if (mActionList == null) { @@ -76,13 +74,14 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< } protected abstract List createActions(); + protected abstract void updateActions(); + protected abstract void executeAction(int type); /** - * Gets the action at the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} + * Gets the action at the given position. Note that action at the position may differ from + * returned by {@link #createActions}. See {@link CustomizableOptionsRowAdapter} */ protected MenuAction getAction(int position) { return mActionList.get(position); diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java index c8249a4c..1a962640 100644 --- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java +++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java @@ -17,9 +17,7 @@ package com.android.tv.menu; import android.content.Context; - import com.android.tv.customization.CustomAction; - import java.util.Collections; import java.util.List; @@ -34,8 +32,7 @@ public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter { } @Override - protected void executeBaseAction(int option) { - } + protected void executeBaseAction(int option) {} @Override protected void updateActions() { diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java index 77715f28..ac3292a3 100644 --- a/src/com/android/tv/menu/PlayControlsButton.java +++ b/src/com/android/tv/menu/PlayControlsButton.java @@ -25,7 +25,6 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - import com.android.tv.R; public class PlayControlsButton extends FrameLayout { @@ -54,8 +53,8 @@ public class PlayControlsButton extends FrameLayout { this(context, attrs, defStyleAttr, 0); } - public PlayControlsButton(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public PlayControlsButton( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); inflate(context, R.layout.play_controls_button, this); mButton = (ImageView) findViewById(R.id.button); @@ -66,9 +65,7 @@ public class PlayControlsButton extends FrameLayout { mIconFocusedColor = mIconColor; } - /** - * Sets the resource ID of the image to be displayed in the center of this control. - */ + /** Sets the resource ID of the image to be displayed in the center of this control. */ public void setImageResId(int imageResId) { int newTintColor = hasFocus() ? mIconFocusedColor : mIconColor; if (mImageResourceId != imageResId) { @@ -87,40 +84,39 @@ public class PlayControlsButton extends FrameLayout { mIcon.getDrawable().setTint(tintColor); } - /** - * Sets an action which is to be run when the button is clicked. - */ + /** Sets an action which is to be run when the button is clicked. */ public void setAction(final Runnable clickAction) { - mButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - clickAction.run(); - } - }); + mButton.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + clickAction.run(); + } + }); } - /** - * Sets the icon's color should change to when the button is on focus. - */ + /** Sets the icon's color should change to when the button is on focus. */ public void setFocusedIconColor(int color) { final ValueAnimator valueAnimator = ValueAnimator.ofArgb(mIconColor, color); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(final ValueAnimator animator) { - mIcon.getDrawable().setTint((int) animator.getAnimatedValue()); - } - }); + valueAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(final ValueAnimator animator) { + mIcon.getDrawable().setTint((int) animator.getAnimatedValue()); + } + }); valueAnimator.setDuration(mFocusAnimationTimeMs); - mButton.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - valueAnimator.start(); - } else { - valueAnimator.reverse(); - } - } - }); + mButton.setOnFocusChangeListener( + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + valueAnimator.start(); + } else { + valueAnimator.reverse(); + } + } + }); mIconFocusedColor = color; } diff --git a/src/com/android/tv/menu/PlayControlsRow.java b/src/com/android/tv/menu/PlayControlsRow.java index a60ff153..f19bd2b0 100644 --- a/src/com/android/tv/menu/PlayControlsRow.java +++ b/src/com/android/tv/menu/PlayControlsRow.java @@ -17,7 +17,6 @@ package com.android.tv.menu; import android.content.Context; - import com.android.tv.R; import com.android.tv.TimeShiftManager; import com.android.tv.ui.TunableTvView; @@ -28,8 +27,8 @@ public class PlayControlsRow extends MenuRow { private final TunableTvView mTvView; private final TimeShiftManager mTimeShiftManager; - public PlayControlsRow(Context context, TunableTvView tvView, Menu menu, - TimeShiftManager timeShiftManager) { + public PlayControlsRow( + Context context, TunableTvView tvView, Menu menu, TimeShiftManager timeShiftManager) { super(context, menu, R.string.menu_title_play_controls, R.dimen.play_controls_height); mTvView = tvView; mTimeShiftManager = timeShiftManager; @@ -45,16 +44,12 @@ public class PlayControlsRow extends MenuRow { return R.layout.play_controls; } - /** - * Returns TV view. - */ + /** Returns TV view. */ public TunableTvView getTvView() { return mTvView; } - /** - * Returns an instance of {@link TimeShiftManager}. - */ + /** Returns an instance of {@link TimeShiftManager}. */ public TimeShiftManager getTimeShiftManager() { return mTimeShiftManager; } diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index 4d766788..d9879e18 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -24,7 +24,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; import android.widget.Toast; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TimeShiftManager; @@ -79,27 +78,29 @@ public class PlayControlsRowView extends MenuRowView { private final String mUnavailableMessage; - private final ScheduledRecordingListener mScheduledRecordingListener - = new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { - Channel currentChannel = mMainActivity.getCurrentChannel(); - if (currentChannel != null && isShown()) { - for (ScheduledRecording schedule : scheduledRecordings) { - if (schedule.getChannelId() == currentChannel.getId()) { - updateRecordButton(); - break; + private final ScheduledRecordingListener mScheduledRecordingListener = + new ScheduledRecordingListener() { + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {} + + @Override + public void onScheduledRecordingRemoved( + ScheduledRecording... scheduledRecordings) {} + + @Override + public void onScheduledRecordingStatusChanged( + ScheduledRecording... scheduledRecordings) { + Channel currentChannel = mMainActivity.getCurrentChannel(); + if (currentChannel != null && isShown()) { + for (ScheduledRecording schedule : scheduledRecordings) { + if (schedule.getChannelId() == currentChannel.getId()) { + updateRecordButton(); + break; + } + } } } - } - } - }; + }; public PlayControlsRowView(Context context) { this(context, null); @@ -113,14 +114,13 @@ public class PlayControlsRowView extends MenuRowView { this(context, attrs, defStyleAttr, 0); } - public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public PlayControlsRowView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); Resources res = context.getResources(); mTimeIndicatorLeftMargin = - - res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2; - mTimeTextLeftMargin = - - res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2; + -res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2; + mTimeTextLeftMargin = -res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2; mTimelineWidth = res.getDimensionPixelSize(R.dimen.play_controls_width); mTimeFormat = DateFormat.getTimeFormat(context); mNormalButtonMargin = res.getDimensionPixelSize(R.dimen.play_controls_button_normal_margin); @@ -154,7 +154,6 @@ public class PlayControlsRowView extends MenuRowView { } }); } - } } @@ -181,104 +180,141 @@ public class PlayControlsRowView extends MenuRowView { mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time); mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time); - initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous, - R.string.play_controls_description_skip_previous, null, new Runnable() { - @Override - public void run() { - if (mTimeShiftManager.isAvailable()) { - mTimeShiftManager.jumpToPrevious(); - updateControls(true); - } - } - }); - initializeButton(mRewindButton, R.drawable.lb_ic_fast_rewind, - R.string.play_controls_description_fast_rewind, null, new Runnable() { - @Override - public void run() { - if (mTimeShiftManager.isAvailable()) { - mTimeShiftManager.rewind(); - updateButtons(); - } - } - }); - initializeButton(mPlayPauseButton, R.drawable.lb_ic_play, - R.string.play_controls_description_play_pause, null, new Runnable() { - @Override - public void run() { - if (mTimeShiftManager.isAvailable()) { - mTimeShiftManager.togglePlayPause(); - updateButtons(); - } - } - }); - initializeButton(mFastForwardButton, R.drawable.lb_ic_fast_forward, - R.string.play_controls_description_fast_forward, null, new Runnable() { - @Override - public void run() { - if (mTimeShiftManager.isAvailable()) { - mTimeShiftManager.fastForward(); - updateButtons(); - } - } - }); - initializeButton(mJumpNextButton, R.drawable.lb_ic_skip_next, - R.string.play_controls_description_skip_next, null, new Runnable() { - @Override - public void run() { - if (mTimeShiftManager.isAvailable()) { - mTimeShiftManager.jumpToNext(); - updateControls(true); - } - } - }); - int color = getResources().getColor(R.color.play_controls_recording_icon_color_on_focus, - null); - initializeButton(mRecordButton, R.drawable.ic_record_start, R.string - .channels_item_record_start, color, new Runnable() { - @Override - public void run() { - onRecordButtonClicked(); - } - }); + initializeButton( + mJumpPreviousButton, + R.drawable.lb_ic_skip_previous, + R.string.play_controls_description_skip_previous, + null, + new Runnable() { + @Override + public void run() { + if (mTimeShiftManager.isAvailable()) { + mTimeShiftManager.jumpToPrevious(); + updateControls(true); + } + } + }); + initializeButton( + mRewindButton, + R.drawable.lb_ic_fast_rewind, + R.string.play_controls_description_fast_rewind, + null, + new Runnable() { + @Override + public void run() { + if (mTimeShiftManager.isAvailable()) { + mTimeShiftManager.rewind(); + updateButtons(); + } + } + }); + initializeButton( + mPlayPauseButton, + R.drawable.lb_ic_play, + R.string.play_controls_description_play_pause, + null, + new Runnable() { + @Override + public void run() { + if (mTimeShiftManager.isAvailable()) { + mTimeShiftManager.togglePlayPause(); + updateButtons(); + } + } + }); + initializeButton( + mFastForwardButton, + R.drawable.lb_ic_fast_forward, + R.string.play_controls_description_fast_forward, + null, + new Runnable() { + @Override + public void run() { + if (mTimeShiftManager.isAvailable()) { + mTimeShiftManager.fastForward(); + updateButtons(); + } + } + }); + initializeButton( + mJumpNextButton, + R.drawable.lb_ic_skip_next, + R.string.play_controls_description_skip_next, + null, + new Runnable() { + @Override + public void run() { + if (mTimeShiftManager.isAvailable()) { + mTimeShiftManager.jumpToNext(); + updateControls(true); + } + } + }); + int color = + getResources().getColor(R.color.play_controls_recording_icon_color_on_focus, null); + initializeButton( + mRecordButton, + R.drawable.ic_record_start, + R.string.channels_item_record_start, + color, + new Runnable() { + @Override + public void run() { + onRecordButtonClicked(); + } + }); } private boolean isCurrentChannelRecording() { Channel currentChannel = mMainActivity.getCurrentChannel(); - return currentChannel != null && mDvrManager != null + return currentChannel != null + && mDvrManager != null && mDvrManager.getCurrentRecording(currentChannel.getId()) != null; } private void onRecordButtonClicked() { boolean isRecording = isCurrentChannelRecording(); Channel currentChannel = mMainActivity.getCurrentChannel(); - TvApplication.getSingletons(getContext()).getTracker().sendMenuClicked(isRecording ? - R.string.channels_item_record_start : R.string.channels_item_record_stop); + TvApplication.getSingletons(getContext()) + .getTracker() + .sendMenuClicked( + isRecording + ? R.string.channels_item_record_start + : R.string.channels_item_record_stop); if (!isRecording) { if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) { - Toast.makeText(mMainActivity, R.string.dvr_msg_cannot_record_channel, - Toast.LENGTH_SHORT).show(); + Toast.makeText( + mMainActivity, + R.string.dvr_msg_cannot_record_channel, + Toast.LENGTH_SHORT) + .show(); } else { - Program program = TvApplication.getSingletons(mMainActivity).getProgramDataManager() - .getCurrentProgram(currentChannel.getId()); - DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity, - currentChannel.getInputId(), new Runnable() { + Program program = + TvApplication.getSingletons(mMainActivity) + .getProgramDataManager() + .getCurrentProgram(currentChannel.getId()); + DvrUiHelper.checkStorageStatusAndShowErrorMessage( + mMainActivity, + currentChannel.getInputId(), + new Runnable() { @Override public void run() { - DvrUiHelper.requestRecordingCurrentProgram(mMainActivity, - currentChannel, program, true); + DvrUiHelper.requestRecordingCurrentProgram( + mMainActivity, currentChannel, program, true); } }); } } else if (currentChannel != null) { - DvrUiHelper.showStopRecordingDialog(mMainActivity, currentChannel.getId(), + DvrUiHelper.showStopRecordingDialog( + mMainActivity, + currentChannel.getId(), DvrStopRecordingFragment.REASON_USER_STOP, new HalfSizedDialogFragment.OnActionClickListener() { @Override public void onActionClick(long actionId) { if (actionId == DvrStopRecordingFragment.ACTION_STOP) { ScheduledRecording currentRecording = - mDvrManager.getCurrentRecording( - currentChannel.getId()); + mDvrManager.getCurrentRecording(currentChannel.getId()); if (currentRecording != null) { mDvrManager.stopRecording(currentRecording); } @@ -288,8 +324,12 @@ public class PlayControlsRowView extends MenuRowView { } } - private void initializeButton(PlayControlsButton button, int imageResId, - int descriptionId, Integer focusedIconColor, Runnable clickAction) { + private void initializeButton( + PlayControlsButton button, + int imageResId, + int descriptionId, + Integer focusedIconColor, + Runnable clickAction) { button.setImageResId(imageResId); button.setAction(clickAction); if (focusedIconColor != null) { @@ -305,69 +345,77 @@ public class PlayControlsRowView extends MenuRowView { PlayControlsRow playControlsRow = (PlayControlsRow) row; mTvView = playControlsRow.getTvView(); mTimeShiftManager = playControlsRow.getTimeShiftManager(); - mTimeShiftManager.setListener(new TimeShiftManager.Listener() { - @Override - public void onAvailabilityChanged() { - updateMenuVisibility(); - PlayControlsRowView.this.updateAll(false); - } + mTimeShiftManager.setListener( + new TimeShiftManager.Listener() { + @Override + public void onAvailabilityChanged() { + updateMenuVisibility(); + PlayControlsRowView.this.updateAll(false); + } - @Override - public void onPlayStatusChanged(int status) { - updateMenuVisibility(); - if (mTimeShiftManager.isAvailable()) { - updateControls(false); - } - } + @Override + public void onPlayStatusChanged(int status) { + updateMenuVisibility(); + if (mTimeShiftManager.isAvailable()) { + updateControls(false); + } + } - @Override - public void onRecordTimeRangeChanged() { - if (mTimeShiftManager.isAvailable()) { - updateControls(false); - } - } + @Override + public void onRecordTimeRangeChanged() { + if (mTimeShiftManager.isAvailable()) { + updateControls(false); + } + } - @Override - public void onCurrentPositionChanged() { - if (mTimeShiftManager.isAvailable()) { - initializeTimeline(); - updateControls(false); - } - } + @Override + public void onCurrentPositionChanged() { + if (mTimeShiftManager.isAvailable()) { + initializeTimeline(); + updateControls(false); + } + } - @Override - public void onProgramInfoChanged() { - if (mTimeShiftManager.isAvailable()) { - initializeTimeline(); - updateControls(false); - } - } + @Override + public void onProgramInfoChanged() { + if (mTimeShiftManager.isAvailable()) { + initializeTimeline(); + updateControls(false); + } + } - @Override - public void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled) { - // Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or - // FAST_FORWARD button is clicked and the button becomes disabled. - // No need to update the UI here because the UI will be updated by other callbacks. - if (!enabled && - ((actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS - && mJumpPreviousButton.hasFocus()) - || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND - && mRewindButton.hasFocus()) - || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD - && mFastForwardButton.hasFocus()) - || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT - && mJumpNextButton.hasFocus()))) { - mPlayPauseButton.requestFocus(); - } - } - }); + @Override + public void onActionEnabledChanged( + @TimeShiftActionId int actionId, boolean enabled) { + // Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or + // FAST_FORWARD button is clicked and the button becomes disabled. + // No need to update the UI here because the UI will be updated by other + // callbacks. + if (!enabled + && ((actionId + == TimeShiftManager + .TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS + && mJumpPreviousButton.hasFocus()) + || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND + && mRewindButton.hasFocus()) + || (actionId + == TimeShiftManager + .TIME_SHIFT_ACTION_ID_FAST_FORWARD + && mFastForwardButton.hasFocus()) + || (actionId + == TimeShiftManager + .TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT + && mJumpNextButton.hasFocus()))) { + mPlayPauseButton.requestFocus(); + } + } + }); // force update to initialize everything updateAll(true); } private void initializeTimeline() { - Program program = mTimeShiftManager.getProgramAt( - mTimeShiftManager.getCurrentPositionMs()); + Program program = mTimeShiftManager.getProgramAt(mTimeShiftManager.getCurrentPositionMs()); mProgramStartTimeMs = program.getStartTimeUtcMillis(); mProgramEndTimeMs = program.getEndTimeUtcMillis(); mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs); @@ -441,16 +489,17 @@ public class PlayControlsRowView extends MenuRowView { // Focus may be changed in another message if requestFocus is called in this message. // After the focus is actually changed, hideRippleAnimation should run // to reflect the result of the focus change. To be sure, hideRippleAnimation is posted. - post(new Runnable() { - @Override - public void run() { - mJumpPreviousButton.hideRippleAnimation(); - mRewindButton.hideRippleAnimation(); - mPlayPauseButton.hideRippleAnimation(); - mFastForwardButton.hideRippleAnimation(); - mJumpNextButton.hideRippleAnimation(); - } - }); + post( + new Runnable() { + @Override + public void run() { + mJumpPreviousButton.hideRippleAnimation(); + mRewindButton.hideRippleAnimation(); + mPlayPauseButton.hideRippleAnimation(); + mFastForwardButton.hideRippleAnimation(); + mJumpNextButton.hideRippleAnimation(); + } + }); } @Override @@ -465,9 +514,7 @@ public class PlayControlsRowView extends MenuRowView { } } - /** - * Updates the view contents. It is called from the PlayControlsRow. - */ + /** Updates the view contents. It is called from the PlayControlsRow. */ public void update() { updateAll(false); } @@ -516,13 +563,22 @@ public class PlayControlsRowView extends MenuRowView { private void updateProgress() { if (isEnabled()) { - long progressStartTimeMs = Math.min(mProgramEndTimeMs, - Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs())); - long currentPlayingTimeMs = Math.min(mProgramEndTimeMs, - Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs())); - long progressEndTimeMs = Math.min(mProgramEndTimeMs, - Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs())); - mProgress.setProgressRange(progressStartTimeMs - mProgramStartTimeMs, + long progressStartTimeMs = + Math.min( + mProgramEndTimeMs, + Math.max( + mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs())); + long currentPlayingTimeMs = + Math.min( + mProgramEndTimeMs, + Math.max( + mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs())); + long progressEndTimeMs = + Math.min( + mProgramEndTimeMs, + Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs())); + mProgress.setProgressRange( + progressStartTimeMs - mProgramStartTimeMs, progressEndTimeMs - mProgramStartTimeMs); mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs); } else { @@ -560,21 +616,24 @@ public class PlayControlsRowView extends MenuRowView { if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PAUSED) { mPlayPauseButton.setImageResId(R.drawable.lb_ic_play); - mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY)); + mPlayPauseButton.setEnabled( + mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY)); } else { mPlayPauseButton.setImageResId(R.drawable.lb_ic_pause); - mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE)); + mPlayPauseButton.setEnabled( + mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE)); } - mJumpPreviousButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)); - mRewindButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND)); - mFastForwardButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD)); - mJumpNextButton.setEnabled(mTimeShiftManager.isActionEnabled( - TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)); + mJumpPreviousButton.setEnabled( + mTimeShiftManager.isActionEnabled( + TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)); + mRewindButton.setEnabled( + mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND)); + mFastForwardButton.setEnabled( + mTimeShiftManager.isActionEnabled( + TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD)); + mJumpNextButton.setEnabled( + mTimeShiftManager.isActionEnabled( + TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)); mJumpPreviousButton.setVisibility(VISIBLE); mJumpNextButton.setVisibility(VISIBLE); updateButtonMargin(); @@ -590,8 +649,11 @@ public class PlayControlsRowView extends MenuRowView { if (mTimeShiftManager.getDisplayedPlaySpeed() == TimeShiftManager.PLAY_SPEED_1X) { button.setLabel(null); } else { - button.setLabel(getResources().getString(R.string.play_controls_speed, - mTimeShiftManager.getDisplayedPlaySpeed())); + button.setLabel( + getResources() + .getString( + R.string.play_controls_speed, + mTimeShiftManager.getDisplayedPlaySpeed())); } } @@ -618,12 +680,13 @@ public class PlayControlsRowView extends MenuRowView { } private void updateButtonMargin() { - int numOfVisibleButtons = (mJumpPreviousButton.getVisibility() == View.VISIBLE ? 1 : 0) - + (mRewindButton.getVisibility() == View.VISIBLE ? 1 : 0) - + (mPlayPauseButton.getVisibility() == View.VISIBLE ? 1 : 0) - + (mFastForwardButton.getVisibility() == View.VISIBLE ? 1 : 0) - + (mJumpNextButton.getVisibility() == View.VISIBLE ? 1 : 0) - + (mRecordButton.getVisibility() == View.VISIBLE ? 1 : 0); + int numOfVisibleButtons = + (mJumpPreviousButton.getVisibility() == View.VISIBLE ? 1 : 0) + + (mRewindButton.getVisibility() == View.VISIBLE ? 1 : 0) + + (mPlayPauseButton.getVisibility() == View.VISIBLE ? 1 : 0) + + (mFastForwardButton.getVisibility() == View.VISIBLE ? 1 : 0) + + (mJumpNextButton.getVisibility() == View.VISIBLE ? 1 : 0) + + (mRecordButton.getVisibility() == View.VISIBLE ? 1 : 0); boolean useCompactLayout = numOfVisibleButtons > NORMAL_WIDTH_MAX_BUTTON_COUNT; if (mUseCompactLayout == useCompactLayout) { return; diff --git a/src/com/android/tv/menu/PlaybackProgressBar.java b/src/com/android/tv/menu/PlaybackProgressBar.java index e8061bc6..398afe13 100644 --- a/src/com/android/tv/menu/PlaybackProgressBar.java +++ b/src/com/android/tv/menu/PlaybackProgressBar.java @@ -24,12 +24,9 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.view.View; - import com.android.tv.R; -/** - * A progress bar control which has two progresses which start in the middle of the control. - */ +/** A progress bar control which has two progresses which start in the middle of the control. */ public class PlaybackProgressBar extends View { private final LayerDrawable mProgressDrawable; private final Drawable mPrimaryDrawable; @@ -51,11 +48,12 @@ public class PlaybackProgressBar extends View { this(context, attrs, defStyleAttr, 0); } - public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public PlaybackProgressBar( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes); + TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes); mProgressDrawable = (LayerDrawable) a.getDrawable(R.styleable.PlaybackProgressBar_progressDrawable); mPrimaryDrawable = mProgressDrawable.findDrawableByLayerId(android.R.id.progress); @@ -98,9 +96,7 @@ public class PlaybackProgressBar extends View { } } - /** - * Sets the start and end position of the progress. - */ + /** Sets the start and end position of the progress. */ public void setProgressRange(long start, long end) { start = constrain(start, 0, mMax); end = constrain(end, start, mMax); @@ -112,9 +108,7 @@ public class PlaybackProgressBar extends View { } } - /** - * Sets the progress position. - */ + /** Sets the progress position. */ public void setProgress(long progress) { progress = constrain(progress, mProgressStart, mProgressEnd); if (progress != mProgress) { diff --git a/src/com/android/tv/menu/SimpleCardView.java b/src/com/android/tv/menu/SimpleCardView.java index fc5192da..e8ecdc72 100644 --- a/src/com/android/tv/menu/SimpleCardView.java +++ b/src/com/android/tv/menu/SimpleCardView.java @@ -19,9 +19,7 @@ package com.android.tv.menu; import android.content.Context; import android.util.AttributeSet; -/** - * A view to render a guide card. - */ +/** A view to render a guide card. */ public class SimpleCardView extends BaseCardView { public SimpleCardView(Context context) { diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java index 6e035f22..d340d309 100644 --- a/src/com/android/tv/menu/TvOptionsRowAdapter.java +++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java @@ -19,7 +19,6 @@ package com.android.tv.menu; import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.annotation.VisibleForTesting; - import com.android.tv.Features; import com.android.tv.TvOptionsManager; import com.android.tv.customization.CustomAction; @@ -30,7 +29,6 @@ import com.android.tv.ui.sidepanel.DeveloperOptionFragment; import com.android.tv.ui.sidepanel.DisplayModeFragment; import com.android.tv.ui.sidepanel.MultiAudioFragment; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; @@ -87,7 +85,8 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { private boolean updatePipAction() { if (containsItem(MenuAction.SYSTEMWIDE_PIP_ACTION)) { - return MenuAction.setEnabled(MenuAction.SYSTEMWIDE_PIP_ACTION, + return MenuAction.setEnabled( + MenuAction.SYSTEMWIDE_PIP_ACTION, !getMainActivity().isScreenBlockedByResourceConflictOrParentalControl()); } return false; @@ -103,41 +102,50 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { private boolean updateDisplayModeAction() { TvViewUiManager uiManager = getMainActivity().getTvViewUiManager(); - boolean enabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) - || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM); + boolean enabled = + uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) + || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM); // Use "|" operator for non-short-circuit evaluation. return MenuAction.setEnabled(MenuAction.SELECT_DISPLAY_MODE_ACTION, enabled) | updateActionDescription(MenuAction.SELECT_DISPLAY_MODE_ACTION); } private boolean updateActionDescription(MenuAction action) { - return MenuAction.setActionDescription(action, - getMainActivity().getTvOptionsManager().getOptionString(action.getType())); + return MenuAction.setActionDescription( + action, getMainActivity().getTvOptionsManager().getOptionString(action.getType())); } @Override protected void executeBaseAction(int type) { switch (type) { case TvOptionsManager.OPTION_CLOSED_CAPTIONS: - getMainActivity().getOverlayManager().getSideFragmentManager() + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() .show(new ClosedCaptionFragment()); break; case TvOptionsManager.OPTION_DISPLAY_MODE: - getMainActivity().getOverlayManager().getSideFragmentManager() + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() .show(new DisplayModeFragment()); break; case TvOptionsManager.OPTION_SYSTEMWIDE_PIP: getMainActivity().enterPictureInPictureMode(); break; case TvOptionsManager.OPTION_MULTI_AUDIO: - getMainActivity().getOverlayManager().getSideFragmentManager() + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() .show(new MultiAudioFragment()); break; case TvOptionsManager.OPTION_MORE_CHANNELS: getMainActivity().showMerchantCollection(); break; case TvOptionsManager.OPTION_DEVELOPER: - getMainActivity().getOverlayManager().getSideFragmentManager() + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() .show(new DeveloperOptionFragment()); break; case TvOptionsManager.OPTION_SETTINGS: @@ -145,4 +153,4 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { break; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/onboarding/NewSourcesFragment.java b/src/com/android/tv/onboarding/NewSourcesFragment.java index 8509b50c..eaf06990 100644 --- a/src/com/android/tv/onboarding/NewSourcesFragment.java +++ b/src/com/android/tv/onboarding/NewSourcesFragment.java @@ -23,28 +23,18 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.ui.setup.SetupActionHelper; import com.android.tv.util.SetupUtils; -/** - * A fragment for new channel source info/setup. - */ +/** A fragment for new channel source info/setup. */ public class NewSourcesFragment extends Fragment { - /** - * The action category. - */ - public static final String ACTION_CATEOGRY = - "com.android.tv.onboarding.NewSourcesFragment"; - /** - * An action to show the setup screen. - */ + /** The action category. */ + public static final String ACTION_CATEOGRY = "com.android.tv.onboarding.NewSourcesFragment"; + /** An action to show the setup screen. */ public static final int ACTION_SETUP = 1; - /** - * An action to close this fragment. - */ + /** An action to close this fragment. */ public static final int ACTION_SKIP = 2; public NewSourcesFragment() { @@ -57,19 +47,21 @@ public class NewSourcesFragment extends Fragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_new_sources, container, false); initializeButton(view.findViewById(R.id.setup), ACTION_SETUP); initializeButton(view.findViewById(R.id.skip), ACTION_SKIP); - SetupUtils.getInstance(getActivity()).markAllInputsRecognized(TvApplication - .getSingletons(getActivity()).getTvInputManagerHelper()); + SetupUtils.getInstance(getActivity()) + .markAllInputsRecognized( + TvApplication.getSingletons(getActivity()).getTvInputManagerHelper()); view.requestFocus(); return view; } private void initializeButton(View view, int actionId) { - view.setOnClickListener(SetupActionHelper.createOnClickListenerForAction(this, - ACTION_CATEOGRY, actionId, null)); + view.setOnClickListener( + SetupActionHelper.createOnClickListenerForAction( + this, ACTION_CATEOGRY, actionId, null)); } } diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java index 45205c4c..8d429a1d 100644 --- a/src/com/android/tv/onboarding/OnboardingActivity.java +++ b/src/com/android/tv/onboarding/OnboardingActivity.java @@ -26,7 +26,6 @@ import android.media.tv.TvInputInfo; import android.os.Bundle; import android.support.annotation.NonNull; import android.widget.Toast; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.SetupPassthroughActivity; @@ -51,29 +50,30 @@ public class OnboardingActivity extends SetupActivity { private ChannelDataManager mChannelDataManager; private TvInputManagerHelper mInputManager; - private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - mChannelDataManager.removeListener(this); - SetupUtils.getInstance(OnboardingActivity.this).markNewChannelsBrowsable(); - } + private final ChannelDataManager.Listener mChannelListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + SetupUtils.getInstance(OnboardingActivity.this).markNewChannelsBrowsable(); + } - @Override - public void onChannelListUpdated() { } + @Override + public void onChannelListUpdated() {} - @Override - public void onChannelBrowsableChanged() { } - }; + @Override + public void onChannelBrowsableChanged() {} + }; /** * Returns an intent to start {@link OnboardingActivity}. * * @param context context to create an intent. Should not be {@code null}. * @param intentAfterCompletion intent which will be used to start a new activity when this - * activity finishes. Should not be {@code null}. + * activity finishes. Should not be {@code null}. */ - public static Intent buildIntent(@NonNull Context context, - @NonNull Intent intentAfterCompletion) { + public static Intent buildIntent( + @NonNull Context context, @NonNull Intent intentAfterCompletion) { return new Intent(context, OnboardingActivity.class) .putExtra(OnboardingActivity.KEY_INTENT_AFTER_COMPLETION, intentAfterCompletion); } @@ -93,7 +93,8 @@ public class OnboardingActivity extends SetupActivity { mChannelDataManager.addListener(mChannelListener); } } else { - requestPermissions(new String[] {PermissionUtils.PERMISSION_READ_TV_LISTINGS}, + requestPermissions( + new String[] {PermissionUtils.PERMISSION_READ_TV_LISTINGS}, PERMISSIONS_REQUEST_READ_TV_LISTINGS); } } @@ -109,32 +110,35 @@ public class OnboardingActivity extends SetupActivity { @Override protected Fragment onCreateInitialFragment() { if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) { - return OnboardingUtils.isFirstRunWithCurrentVersion(this) ? new WelcomeFragment() + return OnboardingUtils.isFirstRunWithCurrentVersion(this) + ? new WelcomeFragment() : new SetupSourcesFragment(); } return null; } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { finish(); - Intent intentForNextActivity = getIntent().getParcelableExtra( - KEY_INTENT_AFTER_COMPLETION); + Intent intentForNextActivity = + getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION); startActivity(buildIntent(this, intentForNextActivity)); } else { - Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied, - Toast.LENGTH_LONG).show(); + Toast.makeText( + this, + R.string.msg_read_tv_listing_permission_denied, + Toast.LENGTH_LONG) + .show(); finish(); } } } private void finishActivity() { - Intent intentForNextActivity = getIntent().getParcelableExtra( - KEY_INTENT_AFTER_COMPLETION); + Intent intentForNextActivity = getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION); if (intentForNextActivity != null) { startActivity(intentForNextActivity); } @@ -142,12 +146,14 @@ public class OnboardingActivity extends SetupActivity { } private void showMerchantCollection() { - executeActionWithDelay(new Runnable() { - @Override - public void run() { - startActivity(OnboardingUtils.ONLINE_STORE_INTENT); - } - }, SHOW_RIPPLE_DURATION_MS); + executeActionWithDelay( + new Runnable() { + @Override + public void run() { + startActivity(OnboardingUtils.ONLINE_STORE_INTENT); + } + }, + SHOW_RIPPLE_DURATION_MS); } @Override @@ -167,42 +173,55 @@ public class OnboardingActivity extends SetupActivity { case SetupSourcesFragment.ACTION_ONLINE_STORE: showMerchantCollection(); return true; - case SetupSourcesFragment.ACTION_SETUP_INPUT: { - String inputId = params.getString( - SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - Intent intent = TvCommonUtils.createSetupIntent(input); - if (intent == null) { - Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT) - .show(); + case SetupSourcesFragment.ACTION_SETUP_INPUT: + { + String inputId = + params.getString( + SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); + TvInputInfo input = mInputManager.getTvInputInfo(inputId); + Intent intent = TvCommonUtils.createSetupIntent(input); + if (intent == null) { + Toast.makeText( + this, + R.string.msg_no_setup_activity, + Toast.LENGTH_SHORT) + .show(); + return true; + } + // Even though other app can handle the intent, the setup launched by + // Live + // channels should go through Live channels SetupPassthroughActivity. + intent.setComponent( + new ComponentName(this, SetupPassthroughActivity.class)); + try { + // Now we know that the user intends to set up this input. Grant + // permission for writing EPG data. + SetupUtils.grantEpgPermission( + this, input.getServiceInfo().packageName); + startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); + } catch (ActivityNotFoundException e) { + Toast.makeText( + this, + getString( + R.string.msg_unable_to_start_setup_activity, + input.loadLabel(this)), + Toast.LENGTH_SHORT) + .show(); + } return true; } - // Even though other app can handle the intent, the setup launched by Live - // channels should go through Live channels SetupPassthroughActivity. - intent.setComponent(new ComponentName(this, - SetupPassthroughActivity.class)); - try { - // Now we know that the user intends to set up this input. Grant - // permission for writing EPG data. - SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName); - startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, - getString(R.string.msg_unable_to_start_setup_activity, - input.loadLabel(this)), Toast.LENGTH_SHORT).show(); - } - return true; - } - case SetupMultiPaneFragment.ACTION_DONE: { - ChannelDataManager manager = TvApplication.getSingletons( - OnboardingActivity.this).getChannelDataManager(); - if (manager.getChannelCount() == 0) { - finish(); - } else { - finishActivity(); + case SetupMultiPaneFragment.ACTION_DONE: + { + ChannelDataManager manager = + TvApplication.getSingletons(OnboardingActivity.this) + .getChannelDataManager(); + if (manager.getChannelCount() == 0) { + finish(); + } else { + finishActivity(); + } + return true; } - return true; - } } break; } diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java index f56daec5..1ac6b27e 100644 --- a/src/com/android/tv/onboarding/SetupSourcesFragment.java +++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java @@ -30,7 +30,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -42,29 +41,21 @@ import com.android.tv.tuner.TunerInputController; import com.android.tv.ui.GuidedActionsStylistWithDivider; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; - import java.util.ArrayList; import java.util.Collections; import java.util.List; -/** - * A fragment for channel source info/setup. - */ +/** A fragment for channel source info/setup. */ public class SetupSourcesFragment extends SetupMultiPaneFragment { - /** - * The action category for the actions which is fired from this fragment. - */ - public static final String ACTION_CATEGORY = - "com.android.tv.onboarding.SetupSourcesFragment"; - /** - * An action to open the merchant collection. - */ + /** The action category for the actions which is fired from this fragment. */ + public static final String ACTION_CATEGORY = "com.android.tv.onboarding.SetupSourcesFragment"; + /** An action to open the merchant collection. */ public static final int ACTION_ONLINE_STORE = 1; /** * An action to show the setup activity of TV input. - *

- * This action is not added to the action list. This is sent outside of the fragment. - * Use {@link #ACTION_PARAM_KEY_INPUT_ID} to get the input ID from the parameter. + * + *

This action is not added to the action list. This is sent outside of the fragment. Use + * {@link #ACTION_PARAM_KEY_INPUT_ID} to get the input ID from the parameter. */ public static final int ACTION_SETUP_INPUT = 2; @@ -77,8 +68,8 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { private static final String SETUP_TRACKER_LABEL = "Setup fragment"; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); TvApplication.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL); return view; @@ -130,68 +121,71 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { private int mPendingAction = PENDING_ACTION_NONE; - private final TvInputCallback mInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - handleInputChanged(); - } + private final TvInputCallback mInputCallback = + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + handleInputChanged(); + } - @Override - public void onInputRemoved(String inputId) { - handleInputChanged(); - } + @Override + public void onInputRemoved(String inputId) { + handleInputChanged(); + } - @Override - public void onInputUpdated(String inputId) { - handleInputChanged(); - } + @Override + public void onInputUpdated(String inputId) { + handleInputChanged(); + } - @Override - public void onTvInputInfoUpdated(TvInputInfo inputInfo) { - handleInputChanged(); - } + @Override + public void onTvInputInfoUpdated(TvInputInfo inputInfo) { + handleInputChanged(); + } - private void handleInputChanged() { - // The actions created while enter transition is running will not be included in the - // fragment transition. - if (mParentFragment.isEnterTransitionRunning()) { - mPendingAction = PENDING_ACTION_INPUT_CHANGED; - return; - } - buildInputs(); - updateActions(); - } - }; + private void handleInputChanged() { + // The actions created while enter transition is running will not be + // included in the + // fragment transition. + if (mParentFragment.isEnterTransitionRunning()) { + mPendingAction = PENDING_ACTION_INPUT_CHANGED; + return; + } + buildInputs(); + updateActions(); + } + }; - private final ChannelDataManager.Listener mChannelDataManagerListener - = new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - handleChannelChanged(); - } + private final ChannelDataManager.Listener mChannelDataManagerListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + handleChannelChanged(); + } - @Override - public void onChannelListUpdated() { - handleChannelChanged(); - } + @Override + public void onChannelListUpdated() { + handleChannelChanged(); + } - @Override - public void onChannelBrowsableChanged() { - handleChannelChanged(); - } + @Override + public void onChannelBrowsableChanged() { + handleChannelChanged(); + } - private void handleChannelChanged() { - // The actions created while enter transition is running will not be included in the - // fragment transition. - if (mParentFragment.isEnterTransitionRunning()) { - if (mPendingAction != PENDING_ACTION_INPUT_CHANGED) { - mPendingAction = PENDING_ACTION_CHANNEL_CHANGED; + private void handleChannelChanged() { + // The actions created while enter transition is running will not be + // included in the + // fragment transition. + if (mParentFragment.isEnterTransitionRunning()) { + if (mPendingAction != PENDING_ACTION_INPUT_CHANGED) { + mPendingAction = PENDING_ACTION_CHANNEL_CHANGED; + } + return; + } + updateActions(); } - return; - } - updateActions(); - } - }; + }; @Override public void onCreate(Bundle savedInstanceState) { @@ -229,8 +223,8 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { createActionsInternal(actions); } @@ -274,24 +268,26 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { int position = 0; if (mDoneInputStartIndex > 0) { // Need a "New" category - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_HEADER) - .title(null) - .description(getString(R.string.setup_category_new)) - .focusable(false) - .infoOnly(true) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_HEADER) + .title(null) + .description(getString(R.string.setup_category_new)) + .focusable(false) + .infoOnly(true) + .build()); } for (int i = 0; i < mInputs.size(); ++i) { if (i == mDoneInputStartIndex) { ++position; - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_HEADER) - .title(null) - .description(getString(R.string.setup_category_done)) - .focusable(false) - .infoOnly(true) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_HEADER) + .title(null) + .description(getString(R.string.setup_category_done)) + .focusable(false) + .infoOnly(true) + .build()); } TvInputInfo input = mInputs.get(i); String inputId = input.getId(); @@ -301,8 +297,12 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { if (channelCount == 0) { description = getString(R.string.setup_input_no_channels); } else { - description = getResources().getQuantityString( - R.plurals.setup_input_channels, channelCount, channelCount); + description = + getResources() + .getQuantityString( + R.plurals.setup_input_channels, + channelCount, + channelCount); } } else if (i >= mKnownInputStartIndex) { description = getString(R.string.setup_input_setup_now); @@ -313,11 +313,12 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { if (input.getId().equals(mNewlyAddedInputId)) { newPosition = position; } - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_INPUT_START + i) - .title(input.loadLabel(getActivity()).toString()) - .description(description) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_INPUT_START + i) + .title(input.loadLabel(getActivity()).toString()) + .description(description) + .build()); } if (mInputs.size() > 0) { // Divider @@ -326,12 +327,13 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { } // online store action ++position; - actions.add(new GuidedAction.Builder(getActivity()) - .id(ACTION_ONLINE_STORE) - .title(getString(R.string.setup_store_action_title)) - .description(getString(R.string.setup_store_action_description)) - .icon(R.drawable.ic_store) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_ONLINE_STORE) + .title(getString(R.string.setup_store_action_title)) + .description(getString(R.string.setup_store_action_description)) + .icon(R.drawable.ic_store) + .build()); if (newPosition != -1) { VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView(); @@ -382,14 +384,15 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { if (descriptionView != null) { if (action.getId() == ACTION_HEADER) { descriptionView.setAlpha(ALPHA_CATEGORY); - descriptionView.setTextColor(getResources().getColor(R.color.setup_category, - null)); - descriptionView.setTypeface(Typeface.create( - getString(R.string.condensed_font), 0)); + descriptionView.setTextColor( + getResources().getColor(R.color.setup_category, null)); + descriptionView.setTypeface( + Typeface.create(getString(R.string.condensed_font), 0)); } else { descriptionView.setAlpha(ALPHA_INPUT_DESCRIPTION); - descriptionView.setTextColor(getResources().getColor( - R.color.common_setup_input_description, null)); + descriptionView.setTextColor( + getResources() + .getColor(R.color.common_setup_input_description, null)); descriptionView.setTypeface(Typeface.create(getString(R.string.font), 0)); } } diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java index f12233e9..d139314e 100644 --- a/src/com/android/tv/onboarding/WelcomeFragment.java +++ b/src/com/android/tv/onboarding/WelcomeFragment.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; @@ -30,17 +29,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import com.android.tv.R; import com.android.tv.common.ui.setup.SetupActionHelper; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; - import java.util.ArrayList; import java.util.List; -/** - * A fragment for the onboarding welcome screen. - */ +/** A fragment for the onboarding welcome screen. */ public class WelcomeFragment extends OnboardingFragment { public static final String ACTION_CATEGORY = "comgoogle.android.tv.onboarding.WelcomeFragment"; public static final int ACTION_NEXT = 1; @@ -56,523 +51,523 @@ public class WelcomeFragment extends OnboardingFragment { // TODO: Use animator list xml. private static final int[] TV_FRAMES_1_START = { - R.drawable.tv_1a_01, - R.drawable.tv_1a_02, - R.drawable.tv_1a_03, - R.drawable.tv_1a_04, - R.drawable.tv_1a_05, - R.drawable.tv_1a_06, - R.drawable.tv_1a_07, - R.drawable.tv_1a_08, - R.drawable.tv_1a_09, - R.drawable.tv_1a_10, - R.drawable.tv_1a_11, - R.drawable.tv_1a_12, - R.drawable.tv_1a_13, - R.drawable.tv_1a_14, - R.drawable.tv_1a_15, - R.drawable.tv_1a_16, - R.drawable.tv_1a_17, - R.drawable.tv_1a_18, - R.drawable.tv_1a_19, - R.drawable.tv_1a_20 + R.drawable.tv_1a_01, + R.drawable.tv_1a_02, + R.drawable.tv_1a_03, + R.drawable.tv_1a_04, + R.drawable.tv_1a_05, + R.drawable.tv_1a_06, + R.drawable.tv_1a_07, + R.drawable.tv_1a_08, + R.drawable.tv_1a_09, + R.drawable.tv_1a_10, + R.drawable.tv_1a_11, + R.drawable.tv_1a_12, + R.drawable.tv_1a_13, + R.drawable.tv_1a_14, + R.drawable.tv_1a_15, + R.drawable.tv_1a_16, + R.drawable.tv_1a_17, + R.drawable.tv_1a_18, + R.drawable.tv_1a_19, + R.drawable.tv_1a_20 }; private static final int[] TV_FRAMES_1_END = { - R.drawable.tv_1b_01, - R.drawable.tv_1b_02, - R.drawable.tv_1b_03, - R.drawable.tv_1b_04, - R.drawable.tv_1b_05, - R.drawable.tv_1b_06, - R.drawable.tv_1b_07, - R.drawable.tv_1b_08, - R.drawable.tv_1b_09, - R.drawable.tv_1b_10, - R.drawable.tv_1b_11 + R.drawable.tv_1b_01, + R.drawable.tv_1b_02, + R.drawable.tv_1b_03, + R.drawable.tv_1b_04, + R.drawable.tv_1b_05, + R.drawable.tv_1b_06, + R.drawable.tv_1b_07, + R.drawable.tv_1b_08, + R.drawable.tv_1b_09, + R.drawable.tv_1b_10, + R.drawable.tv_1b_11 }; private static final int[] TV_FRAMES_2_START = { - R.drawable.tv_5a_0, - R.drawable.tv_5a_1, - R.drawable.tv_5a_2, - R.drawable.tv_5a_3, - R.drawable.tv_5a_4, - R.drawable.tv_5a_5, - R.drawable.tv_5a_6, - R.drawable.tv_5a_7, - R.drawable.tv_5a_8, - R.drawable.tv_5a_9, - R.drawable.tv_5a_10, - R.drawable.tv_5a_11, - R.drawable.tv_5a_12, - R.drawable.tv_5a_13, - R.drawable.tv_5a_14, - R.drawable.tv_5a_15, - R.drawable.tv_5a_16, - R.drawable.tv_5a_17, - R.drawable.tv_5a_18, - R.drawable.tv_5a_19, - R.drawable.tv_5a_20, - R.drawable.tv_5a_21, - R.drawable.tv_5a_22, - R.drawable.tv_5a_23, - R.drawable.tv_5a_24, - R.drawable.tv_5a_25, - R.drawable.tv_5a_26, - R.drawable.tv_5a_27, - R.drawable.tv_5a_28, - R.drawable.tv_5a_29, - R.drawable.tv_5a_30, - R.drawable.tv_5a_31, - R.drawable.tv_5a_32, - R.drawable.tv_5a_33, - R.drawable.tv_5a_34, - R.drawable.tv_5a_35, - R.drawable.tv_5a_36, - R.drawable.tv_5a_37, - R.drawable.tv_5a_38, - R.drawable.tv_5a_39, - R.drawable.tv_5a_40, - R.drawable.tv_5a_41, - R.drawable.tv_5a_42, - R.drawable.tv_5a_43, - R.drawable.tv_5a_44, - R.drawable.tv_5a_45, - R.drawable.tv_5a_46, - R.drawable.tv_5a_47, - R.drawable.tv_5a_48, - R.drawable.tv_5a_49, - R.drawable.tv_5a_50, - R.drawable.tv_5a_51, - R.drawable.tv_5a_52, - R.drawable.tv_5a_53, - R.drawable.tv_5a_54, - R.drawable.tv_5a_55, - R.drawable.tv_5a_56, - R.drawable.tv_5a_57, - R.drawable.tv_5a_58, - R.drawable.tv_5a_59, - R.drawable.tv_5a_60, - R.drawable.tv_5a_61, - R.drawable.tv_5a_62, - R.drawable.tv_5a_63, - R.drawable.tv_5a_64, - R.drawable.tv_5a_65, - R.drawable.tv_5a_66, - R.drawable.tv_5a_67, - R.drawable.tv_5a_68, - R.drawable.tv_5a_69, - R.drawable.tv_5a_70, - R.drawable.tv_5a_71, - R.drawable.tv_5a_72, - R.drawable.tv_5a_73, - R.drawable.tv_5a_74, - R.drawable.tv_5a_75, - R.drawable.tv_5a_76, - R.drawable.tv_5a_77, - R.drawable.tv_5a_78, - R.drawable.tv_5a_79, - R.drawable.tv_5a_80, - R.drawable.tv_5a_81, - R.drawable.tv_5a_82, - R.drawable.tv_5a_83, - R.drawable.tv_5a_84, - R.drawable.tv_5a_85, - R.drawable.tv_5a_86, - R.drawable.tv_5a_87, - R.drawable.tv_5a_88, - R.drawable.tv_5a_89, - R.drawable.tv_5a_90, - R.drawable.tv_5a_91, - R.drawable.tv_5a_92, - R.drawable.tv_5a_93, - R.drawable.tv_5a_94, - R.drawable.tv_5a_95, - R.drawable.tv_5a_96, - R.drawable.tv_5a_97, - R.drawable.tv_5a_98, - R.drawable.tv_5a_99, - R.drawable.tv_5a_100, - R.drawable.tv_5a_101, - R.drawable.tv_5a_102, - R.drawable.tv_5a_103, - R.drawable.tv_5a_104, - R.drawable.tv_5a_105, - R.drawable.tv_5a_106, - R.drawable.tv_5a_107, - R.drawable.tv_5a_108, - R.drawable.tv_5a_109, - R.drawable.tv_5a_110, - R.drawable.tv_5a_111, - R.drawable.tv_5a_112, - R.drawable.tv_5a_113, - R.drawable.tv_5a_114, - R.drawable.tv_5a_115, - R.drawable.tv_5a_116, - R.drawable.tv_5a_117, - R.drawable.tv_5a_118, - R.drawable.tv_5a_119, - R.drawable.tv_5a_120, - R.drawable.tv_5a_121, - R.drawable.tv_5a_122, - R.drawable.tv_5a_123, - R.drawable.tv_5a_124, - R.drawable.tv_5a_125, - R.drawable.tv_5a_126, - R.drawable.tv_5a_127, - R.drawable.tv_5a_128, - R.drawable.tv_5a_129, - R.drawable.tv_5a_130, - R.drawable.tv_5a_131, - R.drawable.tv_5a_132, - R.drawable.tv_5a_133, - R.drawable.tv_5a_134, - R.drawable.tv_5a_135, - R.drawable.tv_5a_136, - R.drawable.tv_5a_137, - R.drawable.tv_5a_138, - R.drawable.tv_5a_139, - R.drawable.tv_5a_140, - R.drawable.tv_5a_141, - R.drawable.tv_5a_142, - R.drawable.tv_5a_143, - R.drawable.tv_5a_144, - R.drawable.tv_5a_145, - R.drawable.tv_5a_146, - R.drawable.tv_5a_147, - R.drawable.tv_5a_148, - R.drawable.tv_5a_149, - R.drawable.tv_5a_150, - R.drawable.tv_5a_151, - R.drawable.tv_5a_152, - R.drawable.tv_5a_153, - R.drawable.tv_5a_154, - R.drawable.tv_5a_155, - R.drawable.tv_5a_156, - R.drawable.tv_5a_157, - R.drawable.tv_5a_158, - R.drawable.tv_5a_159, - R.drawable.tv_5a_160, - R.drawable.tv_5a_161, - R.drawable.tv_5a_162, - R.drawable.tv_5a_163, - R.drawable.tv_5a_164, - R.drawable.tv_5a_165, - R.drawable.tv_5a_166, - R.drawable.tv_5a_167, - R.drawable.tv_5a_168, - R.drawable.tv_5a_169, - R.drawable.tv_5a_170, - R.drawable.tv_5a_171, - R.drawable.tv_5a_172, - R.drawable.tv_5a_173, - R.drawable.tv_5a_174, - R.drawable.tv_5a_175, - R.drawable.tv_5a_176, - R.drawable.tv_5a_177, - R.drawable.tv_5a_178, - R.drawable.tv_5a_179, - R.drawable.tv_5a_180, - R.drawable.tv_5a_181, - R.drawable.tv_5a_182, - R.drawable.tv_5a_183, - R.drawable.tv_5a_184, - R.drawable.tv_5a_185, - R.drawable.tv_5a_186, - R.drawable.tv_5a_187, - R.drawable.tv_5a_188, - R.drawable.tv_5a_189, - R.drawable.tv_5a_190, - R.drawable.tv_5a_191, - R.drawable.tv_5a_192, - R.drawable.tv_5a_193, - R.drawable.tv_5a_194, - R.drawable.tv_5a_195, - R.drawable.tv_5a_196, - R.drawable.tv_5a_197, - R.drawable.tv_5a_198, - R.drawable.tv_5a_199, - R.drawable.tv_5a_200, - R.drawable.tv_5a_201, - R.drawable.tv_5a_202, - R.drawable.tv_5a_203, - R.drawable.tv_5a_204, - R.drawable.tv_5a_205, - R.drawable.tv_5a_206, - R.drawable.tv_5a_207, - R.drawable.tv_5a_208, - R.drawable.tv_5a_209, - R.drawable.tv_5a_210, - R.drawable.tv_5a_211, - R.drawable.tv_5a_212, - R.drawable.tv_5a_213, - R.drawable.tv_5a_214, - R.drawable.tv_5a_215, - R.drawable.tv_5a_216, - R.drawable.tv_5a_217, - R.drawable.tv_5a_218, - R.drawable.tv_5a_219, - R.drawable.tv_5a_220, - R.drawable.tv_5a_221, - R.drawable.tv_5a_222, - R.drawable.tv_5a_223, - R.drawable.tv_5a_224 + R.drawable.tv_5a_0, + R.drawable.tv_5a_1, + R.drawable.tv_5a_2, + R.drawable.tv_5a_3, + R.drawable.tv_5a_4, + R.drawable.tv_5a_5, + R.drawable.tv_5a_6, + R.drawable.tv_5a_7, + R.drawable.tv_5a_8, + R.drawable.tv_5a_9, + R.drawable.tv_5a_10, + R.drawable.tv_5a_11, + R.drawable.tv_5a_12, + R.drawable.tv_5a_13, + R.drawable.tv_5a_14, + R.drawable.tv_5a_15, + R.drawable.tv_5a_16, + R.drawable.tv_5a_17, + R.drawable.tv_5a_18, + R.drawable.tv_5a_19, + R.drawable.tv_5a_20, + R.drawable.tv_5a_21, + R.drawable.tv_5a_22, + R.drawable.tv_5a_23, + R.drawable.tv_5a_24, + R.drawable.tv_5a_25, + R.drawable.tv_5a_26, + R.drawable.tv_5a_27, + R.drawable.tv_5a_28, + R.drawable.tv_5a_29, + R.drawable.tv_5a_30, + R.drawable.tv_5a_31, + R.drawable.tv_5a_32, + R.drawable.tv_5a_33, + R.drawable.tv_5a_34, + R.drawable.tv_5a_35, + R.drawable.tv_5a_36, + R.drawable.tv_5a_37, + R.drawable.tv_5a_38, + R.drawable.tv_5a_39, + R.drawable.tv_5a_40, + R.drawable.tv_5a_41, + R.drawable.tv_5a_42, + R.drawable.tv_5a_43, + R.drawable.tv_5a_44, + R.drawable.tv_5a_45, + R.drawable.tv_5a_46, + R.drawable.tv_5a_47, + R.drawable.tv_5a_48, + R.drawable.tv_5a_49, + R.drawable.tv_5a_50, + R.drawable.tv_5a_51, + R.drawable.tv_5a_52, + R.drawable.tv_5a_53, + R.drawable.tv_5a_54, + R.drawable.tv_5a_55, + R.drawable.tv_5a_56, + R.drawable.tv_5a_57, + R.drawable.tv_5a_58, + R.drawable.tv_5a_59, + R.drawable.tv_5a_60, + R.drawable.tv_5a_61, + R.drawable.tv_5a_62, + R.drawable.tv_5a_63, + R.drawable.tv_5a_64, + R.drawable.tv_5a_65, + R.drawable.tv_5a_66, + R.drawable.tv_5a_67, + R.drawable.tv_5a_68, + R.drawable.tv_5a_69, + R.drawable.tv_5a_70, + R.drawable.tv_5a_71, + R.drawable.tv_5a_72, + R.drawable.tv_5a_73, + R.drawable.tv_5a_74, + R.drawable.tv_5a_75, + R.drawable.tv_5a_76, + R.drawable.tv_5a_77, + R.drawable.tv_5a_78, + R.drawable.tv_5a_79, + R.drawable.tv_5a_80, + R.drawable.tv_5a_81, + R.drawable.tv_5a_82, + R.drawable.tv_5a_83, + R.drawable.tv_5a_84, + R.drawable.tv_5a_85, + R.drawable.tv_5a_86, + R.drawable.tv_5a_87, + R.drawable.tv_5a_88, + R.drawable.tv_5a_89, + R.drawable.tv_5a_90, + R.drawable.tv_5a_91, + R.drawable.tv_5a_92, + R.drawable.tv_5a_93, + R.drawable.tv_5a_94, + R.drawable.tv_5a_95, + R.drawable.tv_5a_96, + R.drawable.tv_5a_97, + R.drawable.tv_5a_98, + R.drawable.tv_5a_99, + R.drawable.tv_5a_100, + R.drawable.tv_5a_101, + R.drawable.tv_5a_102, + R.drawable.tv_5a_103, + R.drawable.tv_5a_104, + R.drawable.tv_5a_105, + R.drawable.tv_5a_106, + R.drawable.tv_5a_107, + R.drawable.tv_5a_108, + R.drawable.tv_5a_109, + R.drawable.tv_5a_110, + R.drawable.tv_5a_111, + R.drawable.tv_5a_112, + R.drawable.tv_5a_113, + R.drawable.tv_5a_114, + R.drawable.tv_5a_115, + R.drawable.tv_5a_116, + R.drawable.tv_5a_117, + R.drawable.tv_5a_118, + R.drawable.tv_5a_119, + R.drawable.tv_5a_120, + R.drawable.tv_5a_121, + R.drawable.tv_5a_122, + R.drawable.tv_5a_123, + R.drawable.tv_5a_124, + R.drawable.tv_5a_125, + R.drawable.tv_5a_126, + R.drawable.tv_5a_127, + R.drawable.tv_5a_128, + R.drawable.tv_5a_129, + R.drawable.tv_5a_130, + R.drawable.tv_5a_131, + R.drawable.tv_5a_132, + R.drawable.tv_5a_133, + R.drawable.tv_5a_134, + R.drawable.tv_5a_135, + R.drawable.tv_5a_136, + R.drawable.tv_5a_137, + R.drawable.tv_5a_138, + R.drawable.tv_5a_139, + R.drawable.tv_5a_140, + R.drawable.tv_5a_141, + R.drawable.tv_5a_142, + R.drawable.tv_5a_143, + R.drawable.tv_5a_144, + R.drawable.tv_5a_145, + R.drawable.tv_5a_146, + R.drawable.tv_5a_147, + R.drawable.tv_5a_148, + R.drawable.tv_5a_149, + R.drawable.tv_5a_150, + R.drawable.tv_5a_151, + R.drawable.tv_5a_152, + R.drawable.tv_5a_153, + R.drawable.tv_5a_154, + R.drawable.tv_5a_155, + R.drawable.tv_5a_156, + R.drawable.tv_5a_157, + R.drawable.tv_5a_158, + R.drawable.tv_5a_159, + R.drawable.tv_5a_160, + R.drawable.tv_5a_161, + R.drawable.tv_5a_162, + R.drawable.tv_5a_163, + R.drawable.tv_5a_164, + R.drawable.tv_5a_165, + R.drawable.tv_5a_166, + R.drawable.tv_5a_167, + R.drawable.tv_5a_168, + R.drawable.tv_5a_169, + R.drawable.tv_5a_170, + R.drawable.tv_5a_171, + R.drawable.tv_5a_172, + R.drawable.tv_5a_173, + R.drawable.tv_5a_174, + R.drawable.tv_5a_175, + R.drawable.tv_5a_176, + R.drawable.tv_5a_177, + R.drawable.tv_5a_178, + R.drawable.tv_5a_179, + R.drawable.tv_5a_180, + R.drawable.tv_5a_181, + R.drawable.tv_5a_182, + R.drawable.tv_5a_183, + R.drawable.tv_5a_184, + R.drawable.tv_5a_185, + R.drawable.tv_5a_186, + R.drawable.tv_5a_187, + R.drawable.tv_5a_188, + R.drawable.tv_5a_189, + R.drawable.tv_5a_190, + R.drawable.tv_5a_191, + R.drawable.tv_5a_192, + R.drawable.tv_5a_193, + R.drawable.tv_5a_194, + R.drawable.tv_5a_195, + R.drawable.tv_5a_196, + R.drawable.tv_5a_197, + R.drawable.tv_5a_198, + R.drawable.tv_5a_199, + R.drawable.tv_5a_200, + R.drawable.tv_5a_201, + R.drawable.tv_5a_202, + R.drawable.tv_5a_203, + R.drawable.tv_5a_204, + R.drawable.tv_5a_205, + R.drawable.tv_5a_206, + R.drawable.tv_5a_207, + R.drawable.tv_5a_208, + R.drawable.tv_5a_209, + R.drawable.tv_5a_210, + R.drawable.tv_5a_211, + R.drawable.tv_5a_212, + R.drawable.tv_5a_213, + R.drawable.tv_5a_214, + R.drawable.tv_5a_215, + R.drawable.tv_5a_216, + R.drawable.tv_5a_217, + R.drawable.tv_5a_218, + R.drawable.tv_5a_219, + R.drawable.tv_5a_220, + R.drawable.tv_5a_221, + R.drawable.tv_5a_222, + R.drawable.tv_5a_223, + R.drawable.tv_5a_224 }; private static final int[] TV_FRAMES_3_BLUE_ARROW = { - R.drawable.arrow_blue_00, - R.drawable.arrow_blue_01, - R.drawable.arrow_blue_02, - R.drawable.arrow_blue_03, - R.drawable.arrow_blue_04, - R.drawable.arrow_blue_05, - R.drawable.arrow_blue_06, - R.drawable.arrow_blue_07, - R.drawable.arrow_blue_08, - R.drawable.arrow_blue_09, - R.drawable.arrow_blue_10, - R.drawable.arrow_blue_11, - R.drawable.arrow_blue_12, - R.drawable.arrow_blue_13, - R.drawable.arrow_blue_14, - R.drawable.arrow_blue_15, - R.drawable.arrow_blue_16, - R.drawable.arrow_blue_17, - R.drawable.arrow_blue_18, - R.drawable.arrow_blue_19, - R.drawable.arrow_blue_20, - R.drawable.arrow_blue_21, - R.drawable.arrow_blue_22, - R.drawable.arrow_blue_23, - R.drawable.arrow_blue_24, - R.drawable.arrow_blue_25, - R.drawable.arrow_blue_26, - R.drawable.arrow_blue_27, - R.drawable.arrow_blue_28, - R.drawable.arrow_blue_29, - R.drawable.arrow_blue_30, - R.drawable.arrow_blue_31, - R.drawable.arrow_blue_32, - R.drawable.arrow_blue_33, - R.drawable.arrow_blue_34, - R.drawable.arrow_blue_35, - R.drawable.arrow_blue_36, - R.drawable.arrow_blue_37, - R.drawable.arrow_blue_38, - R.drawable.arrow_blue_39, - R.drawable.arrow_blue_40, - R.drawable.arrow_blue_41, - R.drawable.arrow_blue_42, - R.drawable.arrow_blue_43, - R.drawable.arrow_blue_44, - R.drawable.arrow_blue_45, - R.drawable.arrow_blue_46, - R.drawable.arrow_blue_47, - R.drawable.arrow_blue_48, - R.drawable.arrow_blue_49, - R.drawable.arrow_blue_50, - R.drawable.arrow_blue_51, - R.drawable.arrow_blue_52, - R.drawable.arrow_blue_53, - R.drawable.arrow_blue_54, - R.drawable.arrow_blue_55, - R.drawable.arrow_blue_56, - R.drawable.arrow_blue_57, - R.drawable.arrow_blue_58, - R.drawable.arrow_blue_59, - R.drawable.arrow_blue_60 + R.drawable.arrow_blue_00, + R.drawable.arrow_blue_01, + R.drawable.arrow_blue_02, + R.drawable.arrow_blue_03, + R.drawable.arrow_blue_04, + R.drawable.arrow_blue_05, + R.drawable.arrow_blue_06, + R.drawable.arrow_blue_07, + R.drawable.arrow_blue_08, + R.drawable.arrow_blue_09, + R.drawable.arrow_blue_10, + R.drawable.arrow_blue_11, + R.drawable.arrow_blue_12, + R.drawable.arrow_blue_13, + R.drawable.arrow_blue_14, + R.drawable.arrow_blue_15, + R.drawable.arrow_blue_16, + R.drawable.arrow_blue_17, + R.drawable.arrow_blue_18, + R.drawable.arrow_blue_19, + R.drawable.arrow_blue_20, + R.drawable.arrow_blue_21, + R.drawable.arrow_blue_22, + R.drawable.arrow_blue_23, + R.drawable.arrow_blue_24, + R.drawable.arrow_blue_25, + R.drawable.arrow_blue_26, + R.drawable.arrow_blue_27, + R.drawable.arrow_blue_28, + R.drawable.arrow_blue_29, + R.drawable.arrow_blue_30, + R.drawable.arrow_blue_31, + R.drawable.arrow_blue_32, + R.drawable.arrow_blue_33, + R.drawable.arrow_blue_34, + R.drawable.arrow_blue_35, + R.drawable.arrow_blue_36, + R.drawable.arrow_blue_37, + R.drawable.arrow_blue_38, + R.drawable.arrow_blue_39, + R.drawable.arrow_blue_40, + R.drawable.arrow_blue_41, + R.drawable.arrow_blue_42, + R.drawable.arrow_blue_43, + R.drawable.arrow_blue_44, + R.drawable.arrow_blue_45, + R.drawable.arrow_blue_46, + R.drawable.arrow_blue_47, + R.drawable.arrow_blue_48, + R.drawable.arrow_blue_49, + R.drawable.arrow_blue_50, + R.drawable.arrow_blue_51, + R.drawable.arrow_blue_52, + R.drawable.arrow_blue_53, + R.drawable.arrow_blue_54, + R.drawable.arrow_blue_55, + R.drawable.arrow_blue_56, + R.drawable.arrow_blue_57, + R.drawable.arrow_blue_58, + R.drawable.arrow_blue_59, + R.drawable.arrow_blue_60 }; private static final int[] TV_FRAMES_3_BLUE_START = { - R.drawable.tv_2a_01, - R.drawable.tv_2a_02, - R.drawable.tv_2a_03, - R.drawable.tv_2a_04, - R.drawable.tv_2a_05, - R.drawable.tv_2a_06, - R.drawable.tv_2a_07, - R.drawable.tv_2a_08, - R.drawable.tv_2a_09, - R.drawable.tv_2a_10, - R.drawable.tv_2a_11, - R.drawable.tv_2a_12, - R.drawable.tv_2a_13, - R.drawable.tv_2a_14, - R.drawable.tv_2a_15, - R.drawable.tv_2a_16, - R.drawable.tv_2a_17, - R.drawable.tv_2a_18, - R.drawable.tv_2a_19 + R.drawable.tv_2a_01, + R.drawable.tv_2a_02, + R.drawable.tv_2a_03, + R.drawable.tv_2a_04, + R.drawable.tv_2a_05, + R.drawable.tv_2a_06, + R.drawable.tv_2a_07, + R.drawable.tv_2a_08, + R.drawable.tv_2a_09, + R.drawable.tv_2a_10, + R.drawable.tv_2a_11, + R.drawable.tv_2a_12, + R.drawable.tv_2a_13, + R.drawable.tv_2a_14, + R.drawable.tv_2a_15, + R.drawable.tv_2a_16, + R.drawable.tv_2a_17, + R.drawable.tv_2a_18, + R.drawable.tv_2a_19 }; private static final int[] TV_FRAMES_3_BLUE_END = { - R.drawable.tv_2b_01, - R.drawable.tv_2b_02, - R.drawable.tv_2b_03, - R.drawable.tv_2b_04, - R.drawable.tv_2b_05, - R.drawable.tv_2b_06, - R.drawable.tv_2b_07, - R.drawable.tv_2b_08, - R.drawable.tv_2b_09, - R.drawable.tv_2b_10, - R.drawable.tv_2b_11, - R.drawable.tv_2b_12, - R.drawable.tv_2b_13, - R.drawable.tv_2b_14, - R.drawable.tv_2b_15, - R.drawable.tv_2b_16, - R.drawable.tv_2b_17, - R.drawable.tv_2b_18, - R.drawable.tv_2b_19 + R.drawable.tv_2b_01, + R.drawable.tv_2b_02, + R.drawable.tv_2b_03, + R.drawable.tv_2b_04, + R.drawable.tv_2b_05, + R.drawable.tv_2b_06, + R.drawable.tv_2b_07, + R.drawable.tv_2b_08, + R.drawable.tv_2b_09, + R.drawable.tv_2b_10, + R.drawable.tv_2b_11, + R.drawable.tv_2b_12, + R.drawable.tv_2b_13, + R.drawable.tv_2b_14, + R.drawable.tv_2b_15, + R.drawable.tv_2b_16, + R.drawable.tv_2b_17, + R.drawable.tv_2b_18, + R.drawable.tv_2b_19 }; private static final int[] TV_FRAMES_3_ORANGE_ARROW = { - R.drawable.arrow_orange_180, - R.drawable.arrow_orange_181, - R.drawable.arrow_orange_182, - R.drawable.arrow_orange_183, - R.drawable.arrow_orange_184, - R.drawable.arrow_orange_185, - R.drawable.arrow_orange_186, - R.drawable.arrow_orange_187, - R.drawable.arrow_orange_188, - R.drawable.arrow_orange_189, - R.drawable.arrow_orange_190, - R.drawable.arrow_orange_191, - R.drawable.arrow_orange_192, - R.drawable.arrow_orange_193, - R.drawable.arrow_orange_194, - R.drawable.arrow_orange_195, - R.drawable.arrow_orange_196, - R.drawable.arrow_orange_197, - R.drawable.arrow_orange_198, - R.drawable.arrow_orange_199, - R.drawable.arrow_orange_200, - R.drawable.arrow_orange_201, - R.drawable.arrow_orange_202, - R.drawable.arrow_orange_203, - R.drawable.arrow_orange_204, - R.drawable.arrow_orange_205, - R.drawable.arrow_orange_206, - R.drawable.arrow_orange_207, - R.drawable.arrow_orange_208, - R.drawable.arrow_orange_209, - R.drawable.arrow_orange_210, - R.drawable.arrow_orange_211, - R.drawable.arrow_orange_212, - R.drawable.arrow_orange_213, - R.drawable.arrow_orange_214, - R.drawable.arrow_orange_215, - R.drawable.arrow_orange_216, - R.drawable.arrow_orange_217, - R.drawable.arrow_orange_218, - R.drawable.arrow_orange_219, - R.drawable.arrow_orange_220, - R.drawable.arrow_orange_221, - R.drawable.arrow_orange_222, - R.drawable.arrow_orange_223, - R.drawable.arrow_orange_224, - R.drawable.arrow_orange_225, - R.drawable.arrow_orange_226, - R.drawable.arrow_orange_227, - R.drawable.arrow_orange_228, - R.drawable.arrow_orange_229, - R.drawable.arrow_orange_230, - R.drawable.arrow_orange_231, - R.drawable.arrow_orange_232, - R.drawable.arrow_orange_233, - R.drawable.arrow_orange_234, - R.drawable.arrow_orange_235, - R.drawable.arrow_orange_236, - R.drawable.arrow_orange_237, - R.drawable.arrow_orange_238, - R.drawable.arrow_orange_239, - R.drawable.arrow_orange_240 + R.drawable.arrow_orange_180, + R.drawable.arrow_orange_181, + R.drawable.arrow_orange_182, + R.drawable.arrow_orange_183, + R.drawable.arrow_orange_184, + R.drawable.arrow_orange_185, + R.drawable.arrow_orange_186, + R.drawable.arrow_orange_187, + R.drawable.arrow_orange_188, + R.drawable.arrow_orange_189, + R.drawable.arrow_orange_190, + R.drawable.arrow_orange_191, + R.drawable.arrow_orange_192, + R.drawable.arrow_orange_193, + R.drawable.arrow_orange_194, + R.drawable.arrow_orange_195, + R.drawable.arrow_orange_196, + R.drawable.arrow_orange_197, + R.drawable.arrow_orange_198, + R.drawable.arrow_orange_199, + R.drawable.arrow_orange_200, + R.drawable.arrow_orange_201, + R.drawable.arrow_orange_202, + R.drawable.arrow_orange_203, + R.drawable.arrow_orange_204, + R.drawable.arrow_orange_205, + R.drawable.arrow_orange_206, + R.drawable.arrow_orange_207, + R.drawable.arrow_orange_208, + R.drawable.arrow_orange_209, + R.drawable.arrow_orange_210, + R.drawable.arrow_orange_211, + R.drawable.arrow_orange_212, + R.drawable.arrow_orange_213, + R.drawable.arrow_orange_214, + R.drawable.arrow_orange_215, + R.drawable.arrow_orange_216, + R.drawable.arrow_orange_217, + R.drawable.arrow_orange_218, + R.drawable.arrow_orange_219, + R.drawable.arrow_orange_220, + R.drawable.arrow_orange_221, + R.drawable.arrow_orange_222, + R.drawable.arrow_orange_223, + R.drawable.arrow_orange_224, + R.drawable.arrow_orange_225, + R.drawable.arrow_orange_226, + R.drawable.arrow_orange_227, + R.drawable.arrow_orange_228, + R.drawable.arrow_orange_229, + R.drawable.arrow_orange_230, + R.drawable.arrow_orange_231, + R.drawable.arrow_orange_232, + R.drawable.arrow_orange_233, + R.drawable.arrow_orange_234, + R.drawable.arrow_orange_235, + R.drawable.arrow_orange_236, + R.drawable.arrow_orange_237, + R.drawable.arrow_orange_238, + R.drawable.arrow_orange_239, + R.drawable.arrow_orange_240 }; private static final int[] TV_FRAMES_3_ORANGE_START = { - R.drawable.tv_2c_01, - R.drawable.tv_2c_02, - R.drawable.tv_2c_03, - R.drawable.tv_2c_04, - R.drawable.tv_2c_05, - R.drawable.tv_2c_06, - R.drawable.tv_2c_07, - R.drawable.tv_2c_08, - R.drawable.tv_2c_09, - R.drawable.tv_2c_10, - R.drawable.tv_2c_11, - R.drawable.tv_2c_12, - R.drawable.tv_2c_13, - R.drawable.tv_2c_14, - R.drawable.tv_2c_15, - R.drawable.tv_2c_16 + R.drawable.tv_2c_01, + R.drawable.tv_2c_02, + R.drawable.tv_2c_03, + R.drawable.tv_2c_04, + R.drawable.tv_2c_05, + R.drawable.tv_2c_06, + R.drawable.tv_2c_07, + R.drawable.tv_2c_08, + R.drawable.tv_2c_09, + R.drawable.tv_2c_10, + R.drawable.tv_2c_11, + R.drawable.tv_2c_12, + R.drawable.tv_2c_13, + R.drawable.tv_2c_14, + R.drawable.tv_2c_15, + R.drawable.tv_2c_16 }; private static final int[] TV_FRAMES_4_START = { - R.drawable.tv_3a_01, - R.drawable.tv_3a_02, - R.drawable.tv_3a_03, - R.drawable.tv_3a_04, - R.drawable.tv_3a_05, - R.drawable.tv_3a_06, - R.drawable.tv_3a_07, - R.drawable.tv_3a_08, - R.drawable.tv_3a_09, - R.drawable.tv_3a_10, - R.drawable.tv_3a_11, - R.drawable.tv_3a_12, - R.drawable.tv_3a_13, - R.drawable.tv_3a_14, - R.drawable.tv_3a_15, - R.drawable.tv_3a_16, - R.drawable.tv_3a_17, - R.drawable.tv_3b_75, - R.drawable.tv_3b_76, - R.drawable.tv_3b_77, - R.drawable.tv_3b_78, - R.drawable.tv_3b_79, - R.drawable.tv_3b_80, - R.drawable.tv_3b_81, - R.drawable.tv_3b_82, - R.drawable.tv_3b_83, - R.drawable.tv_3b_84, - R.drawable.tv_3b_85, - R.drawable.tv_3b_86, - R.drawable.tv_3b_87, - R.drawable.tv_3b_88, - R.drawable.tv_3b_89, - R.drawable.tv_3b_90, - R.drawable.tv_3b_91, - R.drawable.tv_3b_92, - R.drawable.tv_3b_93, - R.drawable.tv_3b_94, - R.drawable.tv_3b_95, - R.drawable.tv_3b_96, - R.drawable.tv_3b_97, - R.drawable.tv_3b_98, - R.drawable.tv_3b_99, - R.drawable.tv_3b_100, - R.drawable.tv_3b_101, - R.drawable.tv_3b_102, - R.drawable.tv_3b_103, - R.drawable.tv_3b_104, - R.drawable.tv_3b_105, - R.drawable.tv_3b_106, - R.drawable.tv_3b_107, - R.drawable.tv_3b_108, - R.drawable.tv_3b_109, - R.drawable.tv_3b_110, - R.drawable.tv_3b_111, - R.drawable.tv_3b_112, - R.drawable.tv_3b_113, - R.drawable.tv_3b_114, - R.drawable.tv_3b_115, - R.drawable.tv_3b_116, - R.drawable.tv_3b_117, - R.drawable.tv_3b_118 + R.drawable.tv_3a_01, + R.drawable.tv_3a_02, + R.drawable.tv_3a_03, + R.drawable.tv_3a_04, + R.drawable.tv_3a_05, + R.drawable.tv_3a_06, + R.drawable.tv_3a_07, + R.drawable.tv_3a_08, + R.drawable.tv_3a_09, + R.drawable.tv_3a_10, + R.drawable.tv_3a_11, + R.drawable.tv_3a_12, + R.drawable.tv_3a_13, + R.drawable.tv_3a_14, + R.drawable.tv_3a_15, + R.drawable.tv_3a_16, + R.drawable.tv_3a_17, + R.drawable.tv_3b_75, + R.drawable.tv_3b_76, + R.drawable.tv_3b_77, + R.drawable.tv_3b_78, + R.drawable.tv_3b_79, + R.drawable.tv_3b_80, + R.drawable.tv_3b_81, + R.drawable.tv_3b_82, + R.drawable.tv_3b_83, + R.drawable.tv_3b_84, + R.drawable.tv_3b_85, + R.drawable.tv_3b_86, + R.drawable.tv_3b_87, + R.drawable.tv_3b_88, + R.drawable.tv_3b_89, + R.drawable.tv_3b_90, + R.drawable.tv_3b_91, + R.drawable.tv_3b_92, + R.drawable.tv_3b_93, + R.drawable.tv_3b_94, + R.drawable.tv_3b_95, + R.drawable.tv_3b_96, + R.drawable.tv_3b_97, + R.drawable.tv_3b_98, + R.drawable.tv_3b_99, + R.drawable.tv_3b_100, + R.drawable.tv_3b_101, + R.drawable.tv_3b_102, + R.drawable.tv_3b_103, + R.drawable.tv_3b_104, + R.drawable.tv_3b_105, + R.drawable.tv_3b_106, + R.drawable.tv_3b_107, + R.drawable.tv_3b_108, + R.drawable.tv_3b_109, + R.drawable.tv_3b_110, + R.drawable.tv_3b_111, + R.drawable.tv_3b_112, + R.drawable.tv_3b_113, + R.drawable.tv_3b_114, + R.drawable.tv_3b_115, + R.drawable.tv_3b_116, + R.drawable.tv_3b_117, + R.drawable.tv_3b_118 }; private String[] mPageTitles; @@ -584,10 +579,11 @@ public class WelcomeFragment extends OnboardingFragment { private Animator mAnimator; public WelcomeFragment() { - setExitTransition(new SetupAnimationHelper.TransitionBuilder() - .setSlideEdge(Gravity.START) - .setParentIdsForDelay(new int[]{R.id.onboarding_fragment_root}) - .build()); + setExitTransition( + new SetupAnimationHelper.TransitionBuilder() + .setSlideEdge(Gravity.START) + .setParentIdsForDelay(new int[] {R.id.onboarding_fragment_root}) + .build()); } @Override @@ -605,8 +601,8 @@ public class WelcomeFragment extends OnboardingFragment { @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); setLogoResourceId(R.drawable.splash_logo); if (savedInstanceState != null) { @@ -645,24 +641,27 @@ public class WelcomeFragment extends OnboardingFragment { // Cloud 1 View view = getActivity().findViewById(R.id.cloud1); view.setAlpha(0); - Animator animator = AnimatorInflater.loadAnimator(getActivity(), - R.animator.onboarding_welcome_cloud_enter); + Animator animator = + AnimatorInflater.loadAnimator( + getActivity(), R.animator.onboarding_welcome_cloud_enter); animator.setStartDelay(START_DELAY_CLOUD_MS); animator.setTarget(view); animators.add(animator); // Cloud 2 view = getActivity().findViewById(R.id.cloud2); view.setAlpha(0); - animator = AnimatorInflater.loadAnimator(getActivity(), - R.animator.onboarding_welcome_cloud_enter); + animator = + AnimatorInflater.loadAnimator( + getActivity(), R.animator.onboarding_welcome_cloud_enter); animator.setStartDelay(START_DELAY_CLOUD_MS); animator.setTarget(view); animators.add(animator); // TV container view = getActivity().findViewById(R.id.tv_container); view.setAlpha(0); - animator = AnimatorInflater.loadAnimator(getActivity(), - R.animator.onboarding_welcome_tv_enter); + animator = + AnimatorInflater.loadAnimator( + getActivity(), R.animator.onboarding_welcome_tv_enter); animator.setStartDelay(START_DELAY_TV_MS); animator.setTarget(view); animators.add(animator); @@ -675,8 +674,9 @@ public class WelcomeFragment extends OnboardingFragment { // Shadow view = getActivity().findViewById(R.id.shadow); view.setAlpha(0); - animator = AnimatorInflater.loadAnimator(getActivity(), - R.animator.onboarding_welcome_shadow_enter); + animator = + AnimatorInflater.loadAnimator( + getActivity(), R.animator.onboarding_welcome_shadow_enter); animator.setStartDelay(START_DELAY_SHADOW_MS); animator.setTarget(view); animators.add(animator); @@ -702,8 +702,9 @@ public class WelcomeFragment extends OnboardingFragment { @Nullable @Override protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container) { - mArrowView = (ImageView) inflater.inflate(R.layout.onboarding_welcome_foreground, container, - false); + mArrowView = + (ImageView) + inflater.inflate(R.layout.onboarding_welcome_foreground, container, false); return mArrowView; } @@ -734,63 +735,71 @@ public class WelcomeFragment extends OnboardingFragment { } mArrowView.setVisibility(View.GONE); // TV screen hiding animator. - Animator hideAnimator = previousPage == 0 - ? SetupAnimationHelper.createFrameAnimator(mTvContentView, TV_FRAMES_1_END) - : SetupAnimationHelper.createFadeOutAnimator(mTvContentView, - VIDEO_FADE_OUT_DURATION_MS, true); + Animator hideAnimator = + previousPage == 0 + ? SetupAnimationHelper.createFrameAnimator(mTvContentView, TV_FRAMES_1_END) + : SetupAnimationHelper.createFadeOutAnimator( + mTvContentView, VIDEO_FADE_OUT_DURATION_MS, true); // TV screen showing animator. AnimatorSet animatorSet = new AnimatorSet(); int firstFrame; switch (newPage) { case 0: - animatorSet.playSequentially(hideAnimator, - SetupAnimationHelper.createFrameAnimator(mTvContentView, - TV_FRAMES_1_START)); + animatorSet.playSequentially( + hideAnimator, + SetupAnimationHelper.createFrameAnimator( + mTvContentView, TV_FRAMES_1_START)); firstFrame = TV_FRAMES_1_START[0]; break; case 1: - animatorSet.playSequentially(hideAnimator, - SetupAnimationHelper.createFrameAnimator(mTvContentView, - TV_FRAMES_2_START)); + animatorSet.playSequentially( + hideAnimator, + SetupAnimationHelper.createFrameAnimator( + mTvContentView, TV_FRAMES_2_START)); firstFrame = TV_FRAMES_2_START[0]; break; case 2: mArrowView.setVisibility(View.VISIBLE); - animatorSet.playSequentially(hideAnimator, - SetupAnimationHelper.createFrameAnimator(mArrowView, - TV_FRAMES_3_BLUE_ARROW), - SetupAnimationHelper.createFrameAnimator(mTvContentView, - TV_FRAMES_3_BLUE_START), - SetupAnimationHelper.createFrameAnimatorWithDelay(mTvContentView, - TV_FRAMES_3_BLUE_END, BLUE_SCREEN_HOLD_DURATION_MS), - SetupAnimationHelper.createFrameAnimator(mArrowView, - TV_FRAMES_3_ORANGE_ARROW), - SetupAnimationHelper.createFrameAnimator(mTvContentView, - TV_FRAMES_3_ORANGE_START)); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mArrowView.setImageResource(TV_FRAMES_3_BLUE_ARROW[0]); - } - }); + animatorSet.playSequentially( + hideAnimator, + SetupAnimationHelper.createFrameAnimator( + mArrowView, TV_FRAMES_3_BLUE_ARROW), + SetupAnimationHelper.createFrameAnimator( + mTvContentView, TV_FRAMES_3_BLUE_START), + SetupAnimationHelper.createFrameAnimatorWithDelay( + mTvContentView, TV_FRAMES_3_BLUE_END, BLUE_SCREEN_HOLD_DURATION_MS), + SetupAnimationHelper.createFrameAnimator( + mArrowView, TV_FRAMES_3_ORANGE_ARROW), + SetupAnimationHelper.createFrameAnimator( + mTvContentView, TV_FRAMES_3_ORANGE_START)); + animatorSet.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mArrowView.setImageResource(TV_FRAMES_3_BLUE_ARROW[0]); + } + }); firstFrame = TV_FRAMES_3_BLUE_START[0]; break; case 3: default: - animatorSet.playSequentially(hideAnimator, - SetupAnimationHelper.createFrameAnimator(mTvContentView, - TV_FRAMES_4_START)); + animatorSet.playSequentially( + hideAnimator, + SetupAnimationHelper.createFrameAnimator( + mTvContentView, TV_FRAMES_4_START)); firstFrame = TV_FRAMES_4_START[0]; break; } final int firstImageResource = firstFrame; - hideAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Shows the first frame of show animation when the hide animator is canceled. - mTvContentView.setImageResource(firstImageResource); - } - }); + hideAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Shows the first frame of show animation when the hide animator is + // canceled. + mTvContentView.setImageResource(firstImageResource); + } + }); mAnimator = SetupAnimationHelper.applyAnimationTimeScale(animatorSet); mAnimator.start(); } diff --git a/src/com/android/tv/parental/ContentRatingLevelPolicy.java b/src/com/android/tv/parental/ContentRatingLevelPolicy.java index 9bd15423..5b0a6cf5 100644 --- a/src/com/android/tv/parental/ContentRatingLevelPolicy.java +++ b/src/com/android/tv/parental/ContentRatingLevelPolicy.java @@ -17,12 +17,10 @@ package com.android.tv.parental; import android.media.tv.TvContentRating; - import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; import com.android.tv.util.TvSettings; import com.android.tv.util.TvSettings.ContentRatingLevel; - import java.util.HashSet; import java.util.Set; @@ -31,10 +29,11 @@ public class ContentRatingLevelPolicy { private static final int AGE_THRESHOLD_FOR_LEVEL_MEDIUM = 12; private static final int AGE_THRESHOLD_FOR_LEVEL_LOW = -1; // Highest age for each rating system - private ContentRatingLevelPolicy() { } + private ContentRatingLevelPolicy() {} public static Set getRatingsForLevel( - ParentalControlSettings settings, ContentRatingsManager manager, + ParentalControlSettings settings, + ContentRatingsManager manager, @ContentRatingLevel int level) { if (level == TvSettings.CONTENT_RATING_LEVEL_NONE) { return new HashSet<>(); @@ -64,14 +63,17 @@ public class ContentRatingLevelPolicy { if (rating.getAgeHint() < ageLimit) { continue; } - TvContentRating tvContentRating = TvContentRating.createRating( - contentRatingSystem.getDomain(), contentRatingSystem.getName(), - rating.getName()); + TvContentRating tvContentRating = + TvContentRating.createRating( + contentRatingSystem.getDomain(), + contentRatingSystem.getName(), + rating.getName()); ratings.add(tvContentRating); for (SubRating subRating : rating.getSubRatings()) { - tvContentRating = TvContentRating.createRating( - contentRatingSystem.getDomain(), contentRatingSystem.getName(), - rating.getName(), subRating.getName()); + tvContentRating = + TvContentRating.createRating( + contentRatingSystem.getDomain(), contentRatingSystem.getName(), + rating.getName(), subRating.getName()); ratings.add(tvContentRating); } } diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java index 5672b793..600aaca1 100644 --- a/src/com/android/tv/parental/ContentRatingSystem.java +++ b/src/com/android/tv/parental/ContentRatingSystem.java @@ -20,9 +20,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.media.tv.TvContentRating; import android.text.TextUtils; - import com.android.tv.R; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -86,7 +84,7 @@ public class ContentRatingSystem { return mDomain + DELIMITER + mName; } - public String getName(){ + public String getName() { return mName; } @@ -94,19 +92,19 @@ public class ContentRatingSystem { return mDomain; } - public String getTitle(){ + public String getTitle() { return mTitle; } - public String getDescription(){ + public String getDescription() { return mDescription; } - public List getCountries(){ + public List getCountries() { return mCountries; } - public List getRatings(){ + public List getRatings() { return mRatings; } @@ -119,11 +117,11 @@ public class ContentRatingSystem { return null; } - public List getSubRatings(){ + public List getSubRatings() { return mSubRatings; } - public List getOrders(){ + public List getOrders() { return mOrders; } @@ -139,9 +137,7 @@ public class ContentRatingSystem { return mIsCustom; } - /** - * Returns true if the ratings is owned by this content rating system. - */ + /** Returns true if the ratings is owned by this content rating system. */ public boolean ownsRating(TvContentRating rating) { return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem()); } @@ -161,9 +157,16 @@ public class ContentRatingSystem { } private ContentRatingSystem( - String name, String domain, String title, String description, List countries, - String displayName, List ratings, List subRatings, - List orders, boolean isCustom) { + String name, + String domain, + String title, + String description, + List countries, + String displayName, + List ratings, + List subRatings, + List orders, + boolean isCustom) { mName = name; mDomain = domain; mTitle = title; @@ -298,8 +301,8 @@ public class ContentRatingSystem { } } if (!used) { - throw new IllegalArgumentException("Subrating " + subRating.getName() + - " isn't used by any rating"); + throw new IllegalArgumentException( + "Subrating " + subRating.getName() + " isn't used by any rating"); } } @@ -310,8 +313,17 @@ public class ContentRatingSystem { } } - return new ContentRatingSystem(mName, mDomain, mTitle, mDescription, mCountries, - displayName, ratings, subRatings, orders, mIsCustom); + return new ContentRatingSystem( + mName, + mDomain, + mTitle, + mDescription, + mCountries, + displayName, + ratings, + subRatings, + orders, + mIsCustom); } } @@ -347,8 +359,13 @@ public class ContentRatingSystem { return mSubRatings; } - private Rating(String name, String title, String description, Drawable icon, - int contentAgeHint, List subRatings) { + private Rating( + String name, + String title, + String description, + Drawable icon, + int contentAgeHint, + List subRatings) { mName = name; mTitle = title; mDescription = description; @@ -365,8 +382,7 @@ public class ContentRatingSystem { private int mContentAgeHint = -1; private final List mSubRatingNames = new ArrayList<>(); - public Builder() { - } + public Builder() {} public void setName(String name) { mName = name; @@ -400,8 +416,8 @@ public class ContentRatingSystem { throw new IllegalArgumentException("Invalid subrating for rating " + mName); } if (mContentAgeHint < 0) { - throw new IllegalArgumentException("Rating " + mName + " should define " + - "non-negative contentAgeHint"); + throw new IllegalArgumentException( + "Rating " + mName + " should define " + "non-negative contentAgeHint"); } List subRatings = new ArrayList<>(); @@ -415,12 +431,11 @@ public class ContentRatingSystem { } } if (!found) { - throw new IllegalArgumentException("Unknown subrating name " + subRatingId + - " in rating " + mName); + throw new IllegalArgumentException( + "Unknown subrating name " + subRatingId + " in rating " + mName); } } - return new Rating( - mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings); + return new Rating(mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings); } } } @@ -460,8 +475,7 @@ public class ContentRatingSystem { private String mDescription; private Drawable mIcon; - public Builder() { - } + public Builder() {} public void setName(String name) { mName = name; @@ -500,8 +514,8 @@ public class ContentRatingSystem { } /** - * Returns index of the rating in this order. - * Returns -1 if this order doesn't contain the rating. + * Returns index of the rating in this order. Returns -1 if this order doesn't contain the + * rating. */ public int getRatingIndex(Rating rating) { for (int i = 0; i < mRatingOrder.size(); i++) { @@ -515,8 +529,7 @@ public class ContentRatingSystem { public static class Builder { private final List mRatingNames = new ArrayList<>(); - public Builder() { - } + public Builder() {} private Order build(List ratings) { List ratingOrder = new ArrayList<>(); @@ -531,8 +544,8 @@ public class ContentRatingSystem { } if (!found) { - throw new IllegalArgumentException("Unknown rating " + ratingName + - " in rating-order tag"); + throw new IllegalArgumentException( + "Unknown rating " + ratingName + " in rating-order tag"); } } diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java index c3bb8c0f..a9c947c6 100644 --- a/src/com/android/tv/parental/ContentRatingsManager.java +++ b/src/com/android/tv/parental/ContentRatingsManager.java @@ -22,11 +22,9 @@ import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputManager; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.tv.R; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; - import java.util.ArrayList; import java.util.List; @@ -55,9 +53,7 @@ public class ContentRatingsManager { } } - /** - * Returns the content rating system with the give ID. - */ + /** Returns the content rating system with the give ID. */ @Nullable public ContentRatingSystem getContentRatingSystem(String contentRatingSystemId) { for (ContentRatingSystem ratingSystem : mContentRatingSystems) { @@ -68,9 +64,7 @@ public class ContentRatingsManager { return null; } - /** - * Returns a new list of all content rating systems defined. - */ + /** Returns a new list of all content rating systems defined. */ public List getContentRatingSystems() { return new ArrayList<>(mContentRatingSystems); } @@ -118,8 +112,10 @@ public class ContentRatingsManager { private List getSubRatings(Rating rating, TvContentRating canonicalRating) { List subRatings = new ArrayList<>(); - if (rating == null || rating.getSubRatings() == null - || canonicalRating == null || canonicalRating.getSubRatings() == null) { + if (rating == null + || rating.getSubRatings() == null + || canonicalRating == null + || canonicalRating.getSubRatings() == null) { return subRatings; } for (String subRatingString : canonicalRating.getSubRatings()) { diff --git a/src/com/android/tv/parental/ContentRatingsParser.java b/src/com/android/tv/parental/ContentRatingsParser.java index d9f62473..3e645ce9 100644 --- a/src/com/android/tv/parental/ContentRatingsParser.java +++ b/src/com/android/tv/parental/ContentRatingsParser.java @@ -24,17 +24,14 @@ import android.content.res.XmlResourceParser; import android.media.tv.TvContentRatingSystemInfo; import android.net.Uri; import android.util.Log; - import com.android.tv.parental.ContentRatingSystem.Order; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; public class ContentRatingsParser { private static final String TAG = "ContentRatingsParser"; @@ -74,8 +71,8 @@ public class ContentRatingsParser { try { String packageName = uri.getAuthority(); int resId = (int) ContentUris.parseId(uri); - try (XmlResourceParser parser = mContext.getPackageManager() - .getXml(packageName, resId, null)) { + try (XmlResourceParser parser = + mContext.getPackageManager().getXml(packageName, resId, null)) { if (parser == null) { throw new IllegalArgumentException("Cannot get XML with URI " + uri); } @@ -90,8 +87,8 @@ public class ContentRatingsParser { return ratingSystems; } - private List parse(XmlResourceParser parser, String domain, - boolean isCustom) + private List parse( + XmlResourceParser parser, String domain, boolean isCustom) throws XmlPullParserException, IOException { try { mResources = mContext.getPackageManager().getResourcesForApplication(domain); @@ -112,7 +109,9 @@ public class ContentRatingsParser { int eventType = parser.getEventType(); assertEquals(eventType, XmlPullParser.START_TAG, "Malformed XML: Not a valid XML file"); - assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS, + assertEquals( + parser.getName(), + TAG_RATING_SYSTEM_DEFINITIONS, "Malformed XML: Should start with tag " + TAG_RATING_SYSTEM_DEFINITIONS); boolean hasVersionAttr = false; @@ -124,8 +123,10 @@ public class ContentRatingsParser { } } if (!hasVersionAttr) { - throw new XmlPullParserException("Malformed XML: Should contains a version attribute" - + " in " + TAG_RATING_SYSTEM_DEFINITIONS); + throw new XmlPullParserException( + "Malformed XML: Should contains a version attribute" + + " in " + + TAG_RATING_SYSTEM_DEFINITIONS); } List ratingSystems = new ArrayList<>(); @@ -135,25 +136,29 @@ public class ContentRatingsParser { if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) { ratingSystems.add(parseRatingSystemDefinition(parser, domain, isCustom)); } else { - checkVersion("Malformed XML: Should contains " + - TAG_RATING_SYSTEM_DEFINITION); + checkVersion( + "Malformed XML: Should contains " + TAG_RATING_SYSTEM_DEFINITION); } break; case XmlPullParser.END_TAG: if (TAG_RATING_SYSTEM_DEFINITIONS.equals(parser.getName())) { eventType = parser.next(); - assertEquals(eventType, XmlPullParser.END_DOCUMENT, - "Malformed XML: Should end with tag " + - TAG_RATING_SYSTEM_DEFINITIONS); + assertEquals( + eventType, + XmlPullParser.END_DOCUMENT, + "Malformed XML: Should end with tag " + + TAG_RATING_SYSTEM_DEFINITIONS); return ratingSystems; } else { - checkVersion("Malformed XML: Should end with tag " + - TAG_RATING_SYSTEM_DEFINITIONS); + checkVersion( + "Malformed XML: Should end with tag " + + TAG_RATING_SYSTEM_DEFINITIONS); } } } - throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITIONS + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_RATING_SYSTEM_DEFINITIONS + + " section is incomplete or section ending tag is missing"); } private static void assertEquals(int a, int b, String msg) throws XmlPullParserException { @@ -174,8 +179,9 @@ public class ContentRatingsParser { } } - private ContentRatingSystem parseRatingSystemDefinition(XmlResourceParser parser, String domain, - boolean isCustom) throws XmlPullParserException, IOException { + private ContentRatingSystem parseRatingSystemDefinition( + XmlResourceParser parser, String domain, boolean isCustom) + throws XmlPullParserException, IOException { ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder(mContext); builder.setDomain(domain); @@ -198,8 +204,11 @@ public class ContentRatingsParser { mResources.getString(parser.getAttributeResourceValue(i, 0))); break; default: - checkVersion("Malformed XML: Unknown attribute " + attr + " in " + - TAG_RATING_SYSTEM_DEFINITION); + checkVersion( + "Malformed XML: Unknown attribute " + + attr + + " in " + + TAG_RATING_SYSTEM_DEFINITION); } } @@ -218,8 +227,11 @@ public class ContentRatingsParser { builder.addOrderBuilder(parseOrder(parser)); break; default: - checkVersion("Malformed XML: Unknown tag " + tag + " in " + - TAG_RATING_SYSTEM_DEFINITION); + checkVersion( + "Malformed XML: Unknown tag " + + tag + + " in " + + TAG_RATING_SYSTEM_DEFINITION); } break; case XmlPullParser.END_TAG: @@ -227,13 +239,14 @@ public class ContentRatingsParser { builder.setIsCustom(isCustom); return builder.build(); } else { - checkVersion("Malformed XML: Tag mismatch for " + - TAG_RATING_SYSTEM_DEFINITION); + checkVersion( + "Malformed XML: Tag mismatch for " + TAG_RATING_SYSTEM_DEFINITION); } } } - throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITION + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_RATING_SYSTEM_DEFINITION + + " section is incomplete or section ending tag is missing"); } private Rating.Builder parseRatingDefinition(XmlResourceParser parser) @@ -265,14 +278,19 @@ public class ContentRatingsParser { } if (contentAgeHint < 0) { - throw new XmlPullParserException("Malformed XML: " + ATTR_CONTENT_AGE_HINT + - " should be a non-negative number"); + throw new XmlPullParserException( + "Malformed XML: " + + ATTR_CONTENT_AGE_HINT + + " should be a non-negative number"); } builder.setContentAgeHint(contentAgeHint); break; default: - checkVersion("Malformed XML: Unknown attribute " + attr + " in " + - TAG_RATING_DEFINITION); + checkVersion( + "Malformed XML: Unknown attribute " + + attr + + " in " + + TAG_RATING_DEFINITION); } } @@ -282,8 +300,11 @@ public class ContentRatingsParser { if (TAG_SUB_RATING.equals(parser.getName())) { builder = parseSubRating(parser, builder); } else { - checkVersion(("Malformed XML: Only " + TAG_SUB_RATING + " is allowed in " + - TAG_RATING_DEFINITION)); + checkVersion( + ("Malformed XML: Only " + + TAG_SUB_RATING + + " is allowed in " + + TAG_RATING_DEFINITION)); } break; case XmlPullParser.END_TAG: @@ -294,8 +315,8 @@ public class ContentRatingsParser { } } } - throw new XmlPullParserException(TAG_RATING_DEFINITION + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_RATING_DEFINITION + " section is incomplete or section ending tag is missing"); } private SubRating.Builder parseSubRatingDefinition(XmlResourceParser parser) @@ -320,8 +341,11 @@ public class ContentRatingsParser { mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null)); break; default: - checkVersion("Malformed XML: Unknown attribute " + attr + " in " + - TAG_SUB_RATING_DEFINITION); + checkVersion( + "Malformed XML: Unknown attribute " + + attr + + " in " + + TAG_SUB_RATING_DEFINITION); } } @@ -331,23 +355,26 @@ public class ContentRatingsParser { if (TAG_SUB_RATING_DEFINITION.equals(parser.getName())) { return builder; } else { - checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION + - " isn't closed"); + checkVersion( + "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " isn't closed"); } break; default: checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION + " has child"); } } - throw new XmlPullParserException(TAG_SUB_RATING_DEFINITION + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_SUB_RATING_DEFINITION + + " section is incomplete or section ending tag is missing"); } private Order.Builder parseOrder(XmlResourceParser parser) throws XmlPullParserException, IOException { Order.Builder builder = new Order.Builder(); - assertEquals(parser.getAttributeCount(), 0, + assertEquals( + parser.getAttributeCount(), + 0, "Malformed XML: Attribute isn't allowed in " + TAG_RATING_ORDER); while (parser.next() != XmlPullParser.END_DOCUMENT) { @@ -355,19 +382,24 @@ public class ContentRatingsParser { case XmlPullParser.START_TAG: if (TAG_RATING.equals(parser.getName())) { builder = parseRating(parser, builder); - } else { - checkVersion("Malformed XML: Only " + TAG_RATING + " is allowed in " + - TAG_RATING_ORDER); + } else { + checkVersion( + "Malformed XML: Only " + + TAG_RATING + + " is allowed in " + + TAG_RATING_ORDER); } break; case XmlPullParser.END_TAG: - assertEquals(parser.getName(), TAG_RATING_ORDER, + assertEquals( + parser.getName(), + TAG_RATING_ORDER, "Malformed XML: Tag mismatch for " + TAG_RATING_ORDER); return builder; } } - throw new XmlPullParserException(TAG_RATING_ORDER + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_RATING_ORDER + " section is incomplete or section ending tag is missing"); } private Order.Builder parseRating(XmlResourceParser parser, Order.Builder builder) @@ -379,8 +411,11 @@ public class ContentRatingsParser { builder.addRatingName(parser.getAttributeValue(i)); break; default: - checkVersion("Malformed XML: " + TAG_RATING_ORDER + " should only contain " - + ATTR_NAME); + checkVersion( + "Malformed XML: " + + TAG_RATING_ORDER + + " should only contain " + + ATTR_NAME); } } @@ -393,8 +428,8 @@ public class ContentRatingsParser { } } } - throw new XmlPullParserException(TAG_RATING + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_RATING + " section is incomplete or section ending tag is missing"); } private Rating.Builder parseSubRating(XmlResourceParser parser, Rating.Builder builder) @@ -406,8 +441,11 @@ public class ContentRatingsParser { builder.addSubRatingName(parser.getAttributeValue(i)); break; default: - checkVersion("Malformed XML: " + TAG_SUB_RATING + " should only contain " + - ATTR_NAME); + checkVersion( + "Malformed XML: " + + TAG_SUB_RATING + + " should only contain " + + ATTR_NAME); } } @@ -420,8 +458,8 @@ public class ContentRatingsParser { } } } - throw new XmlPullParserException(TAG_SUB_RATING + - " section is incomplete or section ending tag is missing"); + throw new XmlPullParserException( + TAG_SUB_RATING + " section is incomplete or section ending tag is missing"); } // Title might be a resource id or a string value. Try loading as an id first, then use the diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java index 2471c565..b9cf45b2 100644 --- a/src/com/android/tv/parental/ParentalControlSettings.java +++ b/src/com/android/tv/parental/ParentalControlSettings.java @@ -19,30 +19,22 @@ package com.android.tv.parental; import android.content.Context; import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; - import com.android.tv.experiments.Experiments; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; import com.android.tv.util.TvSettings; import com.android.tv.util.TvSettings.ContentRatingLevel; - import java.util.HashSet; import java.util.Set; public class ParentalControlSettings { - /** - * The rating and all of its sub-ratings are blocked. - */ + /** The rating and all of its sub-ratings are blocked. */ public static final int RATING_BLOCKED = 0; - /** - * The rating is blocked but not all of its sub-ratings are blocked. - */ + /** The rating is blocked but not all of its sub-ratings are blocked. */ public static final int RATING_BLOCKED_PARTIAL = 1; - /** - * The rating is not blocked. - */ + /** The rating is not blocked. */ public static final int RATING_NOT_BLOCKED = 2; private final Context mContext; @@ -65,8 +57,10 @@ public class ParentalControlSettings { mTvInputManager.setParentalControlsEnabled(enabled); } - public void setContentRatingSystemEnabled(ContentRatingsManager manager, - ContentRatingSystem contentRatingSystem, boolean enabled) { + public void setContentRatingSystemEnabled( + ContentRatingsManager manager, + ContentRatingSystem contentRatingSystem, + boolean enabled) { if (enabled) { TvSettings.addContentRatingSystem(mContext, contentRatingSystem.getId()); @@ -118,8 +112,8 @@ public class ParentalControlSettings { } } - public void setContentRatingLevel(ContentRatingsManager manager, - @ContentRatingLevel int level) { + public void setContentRatingLevel( + ContentRatingsManager manager, @ContentRatingLevel int level) { @ContentRatingLevel int currentLevel = getContentRatingLevel(); if (level == currentLevel) { return; @@ -167,18 +161,17 @@ public class ParentalControlSettings { /** * Sets the blocked status of a given content rating. - *

- * Note that a call to this method automatically changes the current rating level to - * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed. - *

+ * + *

Note that a call to this method automatically changes the current rating level to {@code + * TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed. * * @param contentRatingSystem The content rating system where the given rating belongs. * @param rating The content rating to set. * @return {@code true} if changed, {@code false} otherwise. * @see #setSubRatingBlocked */ - public boolean setRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating, - boolean blocked) { + public boolean setRatingBlocked( + ContentRatingSystem contentRatingSystem, Rating rating, boolean blocked) { return setRatingBlockedInternal(contentRatingSystem, rating, null, blocked); } @@ -225,10 +218,9 @@ public class ParentalControlSettings { /** * Sets the blocked status of a given content sub-rating. - *

- * Note that a call to this method automatically changes the current rating level to - * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed. - *

+ * + *

Note that a call to this method automatically changes the current rating level to {@code + * TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed. * * @param contentRatingSystem The content rating system where the given rating belongs. * @param rating The content rating associated with the given sub-rating. @@ -236,8 +228,11 @@ public class ParentalControlSettings { * @return {@code true} if changed, {@code false} otherwise. * @see #setRatingBlocked */ - public boolean setSubRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating, - SubRating subRating, boolean blocked) { + public boolean setSubRatingBlocked( + ContentRatingSystem contentRatingSystem, + Rating rating, + SubRating subRating, + boolean blocked) { return setRatingBlockedInternal(contentRatingSystem, rating, subRating, blocked); } @@ -249,16 +244,20 @@ public class ParentalControlSettings { * @param subRating The content sub-rating to check. * @return {@code true} if blocked, {@code false} otherwise. */ - public boolean isSubRatingEnabled(ContentRatingSystem contentRatingSystem, Rating rating, - SubRating subRating) { + public boolean isSubRatingEnabled( + ContentRatingSystem contentRatingSystem, Rating rating, SubRating subRating) { return mRatings.contains(toTvContentRating(contentRatingSystem, rating, subRating)); } - private boolean setRatingBlockedInternal(ContentRatingSystem contentRatingSystem, Rating rating, - SubRating subRating, boolean blocked) { - TvContentRating tvContentRating = (subRating == null) - ? toTvContentRating(contentRatingSystem, rating) - : toTvContentRating(contentRatingSystem, rating, subRating); + private boolean setRatingBlockedInternal( + ContentRatingSystem contentRatingSystem, + Rating rating, + SubRating subRating, + boolean blocked) { + TvContentRating tvContentRating = + (subRating == null) + ? toTvContentRating(contentRatingSystem, rating) + : toTvContentRating(contentRatingSystem, rating, subRating); boolean changed; if (blocked) { changed = mRatings.add(tvContentRating); @@ -280,8 +279,8 @@ public class ParentalControlSettings { } /** - * Returns the blocked status of a given rating. The status can be one of the followings: - * {@link #RATING_BLOCKED}, {@link #RATING_BLOCKED_PARTIAL} and {@link #RATING_NOT_BLOCKED} + * Returns the blocked status of a given rating. The status can be one of the followings: {@link + * #RATING_BLOCKED}, {@link #RATING_BLOCKED_PARTIAL} and {@link #RATING_NOT_BLOCKED} */ public int getBlockedStatus(ContentRatingSystem contentRatingSystem, Rating rating) { if (isRatingBlocked(contentRatingSystem, rating)) { @@ -295,15 +294,18 @@ public class ParentalControlSettings { return RATING_NOT_BLOCKED; } - private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem, - Rating rating) { - return TvContentRating.createRating(contentRatingSystem.getDomain(), - contentRatingSystem.getName(), rating.getName()); + private TvContentRating toTvContentRating( + ContentRatingSystem contentRatingSystem, Rating rating) { + return TvContentRating.createRating( + contentRatingSystem.getDomain(), contentRatingSystem.getName(), rating.getName()); } - private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem, - Rating rating, SubRating subRating) { - return TvContentRating.createRating(contentRatingSystem.getDomain(), - contentRatingSystem.getName(), rating.getName(), subRating.getName()); + private TvContentRating toTvContentRating( + ContentRatingSystem contentRatingSystem, Rating rating, SubRating subRating) { + return TvContentRating.createRating( + contentRatingSystem.getDomain(), + contentRatingSystem.getName(), + rating.getName(), + subRating.getName()); } } diff --git a/src/com/android/tv/perf/EventNames.java b/src/com/android/tv/perf/EventNames.java index 6026897b..12488ddb 100644 --- a/src/com/android/tv/perf/EventNames.java +++ b/src/com/android/tv/perf/EventNames.java @@ -16,17 +16,15 @@ package com.android.tv.perf; -import android.support.annotation.StringDef; +import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.support.annotation.StringDef; import java.lang.annotation.Retention; -import static java.lang.annotation.RetentionPolicy.SOURCE; - /** * Constants for performance event names. * *

Only constants are used to insure no PII is sent. - * */ public final class EventNames { @@ -47,8 +45,8 @@ public final class EventNames { public static final String MAIN_ACTIVITY_ONSTART = "MainActivity.onStart"; public static final String MAIN_ACTIVITY_ONRESUME = "MainActivity.onResume"; /** - * Event name for query running time of on-device search in - * {@link com.android.tv.search.LocalSearchProvider}. + * Event name for query running time of on-device search in {@link + * com.android.tv.search.LocalSearchProvider}. */ public static final String ON_DEVICE_SEARCH = "OnDeviceSearch"; diff --git a/src/com/android/tv/perf/PerformanceMonitor.java b/src/com/android/tv/perf/PerformanceMonitor.java index 40368b41..111aa851 100644 --- a/src/com/android/tv/perf/PerformanceMonitor.java +++ b/src/com/android/tv/perf/PerformanceMonitor.java @@ -16,10 +16,10 @@ package com.android.tv.perf; -import android.content.Context; - import static com.android.tv.perf.EventNames.EventName; +import android.content.Context; + /** Measures Performance. */ public interface PerformanceMonitor { @@ -62,7 +62,6 @@ public interface PerformanceMonitor { */ TimerEvent startTimer(); - /** * Stops timer for a specific event and records the timer duration. passing a null TimerEvent * will cause this operation to be skipped. diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java index 313b2dfa..90e087f0 100644 --- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java +++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java @@ -24,7 +24,6 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.analytics.Analytics; @@ -33,8 +32,8 @@ import com.android.tv.common.SharedPreferencesUtils; /** * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google - * Analytics and listeners. Call {@link #register} to start receiving notifications, and - * {@link #unregister} to stop. + * Analytics and listeners. Call {@link #register} to start receiving notifications, and {@link + * #unregister} to stop. */ public final class AudioCapabilitiesReceiver { private static final String SETTINGS_KEY_AC3_PASSTHRU_REPORTED = "ac3_passthrough_reported"; @@ -50,8 +49,7 @@ public final class AudioCapabilitiesReceiver { private final Context mContext; private final Analytics mAnalytics; private final Tracker mTracker; - @Nullable - private final OnAc3PassthroughCapabilityChangeListener mListener; + @Nullable private final OnAc3PassthroughCapabilityChangeListener mListener; private final BroadcastReceiver mReceiver = new HdmiAudioPlugBroadcastReceiver(); /** @@ -60,8 +58,8 @@ public final class AudioCapabilitiesReceiver { * @param context context for registering to receive broadcasts * @param listener listener which receives AC3 passthrough capability change notification */ - public AudioCapabilitiesReceiver(@NonNull Context context, - @Nullable OnAc3PassthroughCapabilityChangeListener listener) { + public AudioCapabilitiesReceiver( + @NonNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener) { mContext = context; ApplicationSingletons appSingletons = TvApplication.getSingletons(context); mAnalytics = appSingletons.getAnalytics(); @@ -121,8 +119,8 @@ public final class AudioCapabilitiesReceiver { } private SharedPreferences getSharedPreferences() { - return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES, - Context.MODE_PRIVATE); + return mContext.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE); } private boolean getBoolean(String key, boolean def) { @@ -141,13 +139,9 @@ public final class AudioCapabilitiesReceiver { getSharedPreferences().edit().putInt(key, val).apply(); } - /** - * Listener notified when AC3 passthrough capability changes. - */ + /** Listener notified when AC3 passthrough capability changes. */ public interface OnAc3PassthroughCapabilityChangeListener { - /** - * Called when the AC3 passthrough capability changes. - */ + /** Called when the AC3 passthrough capability changes. */ void onAc3PassthroughCapabilityChange(boolean capability); } } diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java index 369e7d54..b3ecb8e6 100644 --- a/src/com/android/tv/receiver/BootCompletedReceiver.java +++ b/src/com/android/tv/receiver/BootCompletedReceiver.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; - import com.android.tv.Features; import com.android.tv.TvActivity; import com.android.tv.TvApplication; @@ -38,11 +37,12 @@ import com.android.tv.util.SetupUtils; * Boot completed receiver for TV app. * *

It's used to + * *

    - *
  • start the {@link NotificationService} for recommendation
  • - *
  • grant permission to the TIS's
  • - *
  • enable {@link TvActivity} if necessary
  • - *
  • start the {@link DvrRecordingService}
  • + *
  • start the {@link NotificationService} for recommendation + *
  • grant permission to the TIS's + *
  • enable {@link TvActivity} if necessary + *
  • start the {@link DvrRecordingService} *
*/ public class BootCompletedReceiver extends BroadcastReceiver { @@ -77,8 +77,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { ComponentName name = new ComponentName(context, TvActivity.class); if (pm.getComponentEnabledSetting(name) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { - pm.setComponentEnabledSetting(name, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + pm.setComponentEnabledSetting( + name, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); } OnboardingUtils.setFirstBootCompleted(context); } diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java index cc8e76c4..7c4117d4 100644 --- a/src/com/android/tv/receiver/GlobalKeyReceiver.java +++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java @@ -23,12 +23,9 @@ import android.os.AsyncTask; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; - import com.android.tv.TvApplication; -/** - * Handles global keys. - */ +/** Handles global keys. */ public class GlobalKeyReceiver extends BroadcastReceiver { private static final boolean DEBUG = false; private static final String TAG = "GlobalKeyReceiver"; @@ -55,8 +52,11 @@ public class GlobalKeyReceiver extends BroadcastReceiver { new AsyncTask() { @Override protected Boolean doInBackground(Void... params) { - return Settings.Secure.getInt(appContext.getContentResolver(), - SETTINGS_USER_SETUP_COMPLETE, 0) != 0; + return Settings.Secure.getInt( + appContext.getContentResolver(), + SETTINGS_USER_SETUP_COMPLETE, + 0) + != 0; } @Override diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java index 124172f0..bd26c7b3 100644 --- a/src/com/android/tv/receiver/PackageIntentsReceiver.java +++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java @@ -21,13 +21,10 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.util.Log; - import com.android.tv.TvApplication; import com.android.tv.util.Partner; -/** - * A class for handling the broadcast intents from PackageManager. - */ +/** A class for handling the broadcast intents from PackageManager. */ public class PackageIntentsReceiver extends BroadcastReceiver { private static final String TAG = "PackageIntentsReceiver"; diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java index 2709ebe1..d332e18a 100644 --- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java +++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java @@ -28,7 +28,6 @@ import android.support.annotation.RequiresApi; import android.support.media.tv.TvContractCompat; import android.text.TextUtils; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.data.Channel; @@ -37,7 +36,6 @@ import com.android.tv.data.PreviewProgramContent; import com.android.tv.data.Program; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -54,17 +52,14 @@ public class ChannelPreviewUpdater { private static final int UPATE_PREVIEW_PROGRAMS_JOB_ID = 1000001; private static final long ROUTINE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10); // The left time of a program should meet the threshold so that it could be recommended. - private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = - TimeUnit.MINUTES.toMillis(10); - private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% + private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = TimeUnit.MINUTES.toMillis(10); + private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% private static final int RECOMMENDATION_COUNT = 6; private static final int MIN_COUNT_TO_ADD_ROW = 4; private static ChannelPreviewUpdater sChannelPreviewUpdater; - /** - * Creates and returns the {@link ChannelPreviewUpdater}. - */ + /** Creates and returns the {@link ChannelPreviewUpdater}. */ public static ChannelPreviewUpdater getInstance(Context context) { if (sChannelPreviewUpdater == null) { sChannelPreviewUpdater = new ChannelPreviewUpdater(context.getApplicationContext()); @@ -82,21 +77,22 @@ public class ChannelPreviewUpdater { private boolean mNeedUpdateAfterRecommenderReady = false; - private Recommender.Listener mRecommenderListener = new Recommender.Listener() { - @Override - public void onRecommenderReady() { - if (mNeedUpdateAfterRecommenderReady) { - if (DEBUG) Log.d(TAG, "Recommender is ready"); - updatePreviewDataForChannelsImmediately(); - mNeedUpdateAfterRecommenderReady = false; - } - } + private Recommender.Listener mRecommenderListener = + new Recommender.Listener() { + @Override + public void onRecommenderReady() { + if (mNeedUpdateAfterRecommenderReady) { + if (DEBUG) Log.d(TAG, "Recommender is ready"); + updatePreviewDataForChannelsImmediately(); + mNeedUpdateAfterRecommenderReady = false; + } + } - @Override - public void onRecommendationChanged() { - updatePreviewDataForChannelsImmediately(); - } - }; + @Override + public void onRecommendationChanged() { + updatePreviewDataForChannelsImmediately(); + } + }; private ChannelPreviewUpdater(Context context) { mContext = context; @@ -106,13 +102,11 @@ public class ChannelPreviewUpdater { mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); ApplicationSingletons appSingleton = TvApplication.getSingletons(context); mPreviewDataManager = appSingleton.getPreviewDataManager(); - mParentalControlSettings = appSingleton.getTvInputManagerHelper() - .getParentalControlSettings(); + mParentalControlSettings = + appSingleton.getTvInputManagerHelper().getParentalControlSettings(); } - /** - * Starts the routine service for updating the preview programs. - */ + /** Starts the routine service for updating the preview programs. */ public void startRoutineService() { JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -120,11 +114,13 @@ public class ChannelPreviewUpdater { if (DEBUG) Log.d(TAG, "UPDATE_PREVIEW_JOB already exists"); return; } - JobInfo job = new JobInfo.Builder(UPATE_PREVIEW_PROGRAMS_JOB_ID, - new ComponentName(mContext, ChannelPreviewUpdateService.class)) - .setPeriodic(ROUTINE_INTERVAL_MS) - .setPersisted(true) - .build(); + JobInfo job = + new JobInfo.Builder( + UPATE_PREVIEW_PROGRAMS_JOB_ID, + new ComponentName(mContext, ChannelPreviewUpdateService.class)) + .setPeriodic(ROUTINE_INTERVAL_MS) + .setPersisted(true) + .build(); if (jobScheduler.schedule(job) < 0) { Log.i(TAG, "JobScheduler failed to schedule the job"); } @@ -138,9 +134,7 @@ public class ChannelPreviewUpdater { updatePreviewDataForChannelsImmediately(); } - /** - * Updates the preview programs table. - */ + /** Updates the preview programs table. */ public void updatePreviewDataForChannelsImmediately() { if (!mRecommender.isReady()) { mNeedUpdateAfterRecommenderReady = true; @@ -148,16 +142,17 @@ public class ChannelPreviewUpdater { } if (!mPreviewDataManager.isLoadFinished()) { - mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { - mPreviewDataManager.removeListener(this); - updatePreviewDataForChannels(); - } + mPreviewDataManager.addListener( + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForChannels(); + } - @Override - public void onPreviewDataUpdateFinished() { } - }); + @Override + public void onPreviewDataUpdateFinished() {} + }); return; } updatePreviewDataForChannels(); @@ -225,8 +220,9 @@ public class ChannelPreviewUpdater { } private void updatePreviewDataForChannelsInternal(Set programs) { - long defaultPreviewChannelId = mPreviewDataManager.getPreviewChannelId( - PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL); + long defaultPreviewChannelId = + mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL); if (defaultPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) { // Only create if there is enough programs if (programs.size() > MIN_COUNT_TO_ADD_ROW) { @@ -248,7 +244,8 @@ public class ChannelPreviewUpdater { }); } } else { - updatePreviewProgramsForPreviewChannel(defaultPreviewChannelId, + updatePreviewProgramsForPreviewChannel( + defaultPreviewChannelId, generatePreviewProgramContentsFromPrograms(defaultPreviewChannelId, programs)); } } @@ -266,33 +263,32 @@ public class ChannelPreviewUpdater { return result; } - private void updatePreviewProgramsForPreviewChannel(long previewChannelId, - Set previewProgramContents) { - PreviewDataManager.PreviewDataListener previewDataListener - = new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { } - - @Override - public void onPreviewDataUpdateFinished() { - mPreviewDataManager.removeListener(this); - if (mJobService != null && mJobParams != null) { - if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService"); - mJobService.jobFinished(mJobParams, false); - mJobService = null; - mJobParams = null; - } else { - if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService"); - } - } - }; + private void updatePreviewProgramsForPreviewChannel( + long previewChannelId, Set previewProgramContents) { + PreviewDataManager.PreviewDataListener previewDataListener = + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() {} + + @Override + public void onPreviewDataUpdateFinished() { + mPreviewDataManager.removeListener(this); + if (mJobService != null && mJobParams != null) { + if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService"); + mJobService.jobFinished(mJobParams, false); + mJobService = null; + mJobParams = null; + } else { + if (DEBUG) + Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService"); + } + } + }; mPreviewDataManager.updatePreviewProgramsForChannel( previewChannelId, previewProgramContents, previewDataListener); } - /** - * Job to execute the update of preview programs. - */ + /** Job to execute the update of preview programs. */ public static class ChannelPreviewUpdateService extends JobService { private ChannelPreviewUpdater mChannelPreviewUpdater; diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java index 26f0fbf0..812b9d3f 100644 --- a/src/com/android/tv/recommendation/ChannelRecord.java +++ b/src/com/android/tv/recommendation/ChannelRecord.java @@ -18,13 +18,10 @@ package com.android.tv.recommendation; import android.content.Context; import android.support.annotation.VisibleForTesting; - import com.android.tv.TvApplication; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; -import com.android.tv.util.Utils; - import java.util.ArrayDeque; import java.util.Deque; diff --git a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java index 9a6de7e2..8b0a3502 100644 --- a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java +++ b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java @@ -19,7 +19,7 @@ package com.android.tv.recommendation; import java.util.List; public class FavoriteChannelEvaluator extends Recommender.Evaluator { - private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day + private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day // When there is no watch history, use the current time as a default value. private long mEarliestWatchStartTimeMs = System.currentTimeMillis(); @@ -46,7 +46,6 @@ public class FavoriteChannelEvaluator extends Recommender.Evaluator { } long watchPeriodMs = System.currentTimeMillis() - mEarliestWatchStartTimeMs; - return (double) cr.getTotalWatchDurationMs() / - Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS); + return (double) cr.getTotalWatchDurationMs() / Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS); } } diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index a44eca41..c4b321e1 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -40,7 +40,6 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseLongArray; import android.view.View; - import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener; import com.android.tv.R; @@ -53,15 +52,12 @@ import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; -/** - * A local service for notify recommendation at home launcher. - */ -public class NotificationService extends Service implements Recommender.Listener, - OnCurrentChannelChangeListener { +/** A local service for notify recommendation at home launcher. */ +public class NotificationService extends Service + implements Recommender.Listener, OnCurrentChannelChangeListener { private static final String TAG = "NotificationService"; private static final boolean DEBUG = false; @@ -71,8 +67,8 @@ public class NotificationService extends Service implements Recommender.Listener "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION"; /** - * Recommendation intent has an extra data for the recommendation type. It'll be also - * sent to a TV input as a tune parameter. + * Recommendation intent has an extra data for the recommendation type. It'll be also sent to a + * TV input as a tune parameter. */ public static final String TUNE_PARAMS_RECOMMENDATION_TYPE = "com.android.tv.recommendation_type"; @@ -92,9 +88,9 @@ public class NotificationService extends Service implements Recommender.Listener private static final int MSG_UPDATE_RECOMMENDATION = 1002; private static final int MSG_HIDE_RECOMMENDATION = 1003; - private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min - private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min - private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% + private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min + private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min + private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90% private static final int MAX_PROGRAM_UPDATE_COUNT = 20; private TvInputManagerHelper mTvInputManagerHelper; @@ -133,10 +129,10 @@ public class NotificationService extends Service implements Recommender.Listener for (int i = 0; i < NOTIFICATION_COUNT; ++i) { mNotificationChannels[i] = Channel.INVALID_ID; } - mNotificationCardMaxWidth = getResources().getDimensionPixelSize( - R.dimen.notif_card_img_max_width); - mNotificationCardHeight = getResources().getDimensionPixelSize( - R.dimen.notif_card_img_height); + mNotificationCardMaxWidth = + getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); + mNotificationCardHeight = + getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height); mCardImageMaxWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); mCardImageMinWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_min_width); @@ -178,8 +174,8 @@ public class NotificationService extends Service implements Recommender.Listener mRecommender.registerEvaluator(new RandomEvaluator()); } else if (TYPE_ROUTINE_WATCH_RECOMMENDATION.equals(mRecommendationType)) { mRecommender.registerEvaluator(new RoutineWatchEvaluator()); - } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION - .equals(mRecommendationType)) { + } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION.equals( + mRecommendationType)) { mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); } else { @@ -197,8 +193,8 @@ public class NotificationService extends Service implements Recommender.Listener } private void handleUpdateRecommendation(int notificationId, Channel channel) { - if (mNotificationChannels[notificationId] == Channel.INVALID_ID || !sendNotification( - channel.getId(), notificationId)) { + if (mNotificationChannels[notificationId] == Channel.INVALID_ID + || !sendNotification(channel.getId(), notificationId)) { changeRecommendation(notificationId); } } @@ -213,7 +209,8 @@ public class NotificationService extends Service implements Recommender.Listener @Override public void onDestroy() { - TvApplication.getSingletons(this).getMainActivityWrapper() + TvApplication.getSingletons(this) + .getMainActivityWrapper() .removeOnCurrentChannelChangeListener(this); if (mRecommender != null) { mRecommender.release(); @@ -316,7 +313,7 @@ public class NotificationService extends Service implements Recommender.Listener } for (Channel c : channels) { if (!isNotifiedChannel(c.getId())) { - if(sendNotification(c.getId(), notificationId)) { + if (sendNotification(c.getId(), notificationId)) { return; } } @@ -334,13 +331,13 @@ public class NotificationService extends Service implements Recommender.Listener } private void hideAllRecommendation() { - for (int i = 0; i < NOTIFICATION_COUNT; ++i) { - if (mNotificationChannels[i] != Channel.INVALID_ID) { - mNotificationChannels[i] = Channel.INVALID_ID; - mNotificationManager.cancel(NOTIFY_TAG, i); - } - } - mCurrentNotificationCount = 0; + for (int i = 0; i < NOTIFICATION_COUNT; ++i) { + if (mNotificationChannels[i] != Channel.INVALID_ID) { + mNotificationChannels[i] = Channel.INVALID_ID; + mNotificationManager.cancel(NOTIFY_TAG, i); + } + } + mCurrentNotificationCount = 0; } private boolean sendNotification(final long channelId, final int notificationId) { @@ -350,8 +347,13 @@ public class NotificationService extends Service implements Recommender.Listener } final Channel channel = cr.getChannel(); if (DEBUG) { - Log.d(TAG, "sendNotification (channelName=" + channel.getDisplayName() + " notifyId=" - + notificationId + ")"); + Log.d( + TAG, + "sendNotification (channelName=" + + channel.getDisplayName() + + " notifyId=" + + notificationId + + ")"); } // TODO: Move some checking logic into TvRecommendation. @@ -369,11 +371,13 @@ public class NotificationService extends Service implements Recommender.Listener if (program == null) { return false; } - final long programDurationMs = program.getEndTimeUtcMillis() - - program.getStartTimeUtcMillis(); + final long programDurationMs = + program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis(); long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); - final int programProgress = (programDurationMs <= 0) ? -1 - : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); + final int programProgress = + (programDurationMs <= 0) + ? -1 + : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); // We recommend those programs that meet the condition only. if (programProgress >= RECOMMENDATION_THRESHOLD_PROGRESS @@ -382,19 +386,25 @@ public class NotificationService extends Service implements Recommender.Listener } // We don't trust TIS to provide us with proper sized image - ScaledBitmapInfo posterArtBitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString(this, - program.getPosterArtUri(), (int) mNotificationCardMaxWidth, - (int) mNotificationCardHeight); + ScaledBitmapInfo posterArtBitmapInfo = + BitmapUtils.decodeSampledBitmapFromUriString( + this, + program.getPosterArtUri(), + (int) mNotificationCardMaxWidth, + (int) mNotificationCardHeight); if (posterArtBitmapInfo == null) { Log.e(TAG, "Failed to decode poster image for " + program.getPosterArtUri()); return false; } final Bitmap posterArtBitmap = posterArtBitmapInfo.bitmap; - channel.loadBitmap(this, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoMaxWidth, + channel.loadBitmap( + this, + Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mChannelLogoMaxWidth, mChannelLogoMaxHeight, - createChannelLogoCallback(this, notificationId, inputDisplayName, channel, program, - posterArtBitmap)); + createChannelLogoCallback( + this, notificationId, inputDisplayName, channel, program, posterArtBitmap)); if (mNotificationChannels[notificationId] == Channel.INVALID_ID) { ++mCurrentNotificationCount; @@ -406,49 +416,71 @@ public class NotificationService extends Service implements Recommender.Listener @NonNull private static ImageLoader.ImageLoaderCallback createChannelLogoCallback( - NotificationService service, final int notificationId, final String inputDisplayName, - final Channel channel, final Program program, final Bitmap posterArtBitmap) { + NotificationService service, + final int notificationId, + final String inputDisplayName, + final Channel channel, + final Program program, + final Bitmap posterArtBitmap) { return new ImageLoader.ImageLoaderCallback(service) { @Override public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) { - service.sendNotification(notificationId, channelLogo, channel, posterArtBitmap, - program, inputDisplayName); + service.sendNotification( + notificationId, + channelLogo, + channel, + posterArtBitmap, + program, + inputDisplayName); } }; } - private void sendNotification(int notificationId, Bitmap channelLogo, Channel channel, - Bitmap posterArtBitmap, Program program, String inputDisplayName) { - final long programDurationMs = program.getEndTimeUtcMillis() - program - .getStartTimeUtcMillis(); + private void sendNotification( + int notificationId, + Bitmap channelLogo, + Channel channel, + Bitmap posterArtBitmap, + Program program, + String inputDisplayName) { + final long programDurationMs = + program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis(); long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis(); - final int programProgress = (programDurationMs <= 0) ? -1 - : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); + final int programProgress = + (programDurationMs <= 0) + ? -1 + : 100 - (int) (programLeftTimsMs * 100 / programDurationMs); Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri()); intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0); // This callback will run on the main thread. - Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap - : overlayChannelLogo(channelLogo, posterArtBitmap); + Bitmap largeIconBitmap = + (channelLogo == null) + ? posterArtBitmap + : overlayChannelLogo(channelLogo, posterArtBitmap); String channelDisplayName = channel.getDisplayName(); - Notification notification = new Notification.Builder(this) - .setContentIntent(notificationIntent) - .setContentTitle(program.getTitle()) - .setContentText(TextUtils.isEmpty(channelDisplayName) ? channel.getDisplayNumber() - : channelDisplayName) - .setContentInfo(channelDisplayName) - .setAutoCancel(true).setLargeIcon(largeIconBitmap) - .setSmallIcon(R.drawable.ic_launcher_s) - .setCategory(Notification.CATEGORY_RECOMMENDATION) - .setProgress((programProgress > 0) ? 100 : 0, programProgress, false) - .setSortKey(mRecommender.getChannelSortKey(channel.getId())) - .build(); + Notification notification = + new Notification.Builder(this) + .setContentIntent(notificationIntent) + .setContentTitle(program.getTitle()) + .setContentText( + TextUtils.isEmpty(channelDisplayName) + ? channel.getDisplayNumber() + : channelDisplayName) + .setContentInfo(channelDisplayName) + .setAutoCancel(true) + .setLargeIcon(largeIconBitmap) + .setSmallIcon(R.drawable.ic_launcher_s) + .setCategory(Notification.CATEGORY_RECOMMENDATION) + .setProgress((programProgress > 0) ? 100 : 0, programProgress, false) + .setSortKey(mRecommender.getChannelSortKey(channel.getId())) + .build(); notification.color = getResources().getColor(R.color.recommendation_card_background, null); if (!TextUtils.isEmpty(program.getThumbnailUri())) { - notification.extras - .putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri()); + notification.extras.putString( + Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri()); } mNotificationManager.notify(NOTIFY_TAG, notificationId, notification); Message msg = mHandler.obtainMessage(MSG_UPDATE_RECOMMENDATION, notificationId, 0, channel); @@ -456,10 +488,10 @@ public class NotificationService extends Service implements Recommender.Listener } private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) { - Bitmap result = BitmapUtils.getScaledMutableBitmap( - background, Integer.MAX_VALUE, mCardImageHeight); - Bitmap scaledLogo = BitmapUtils.scaleBitmap( - logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); + Bitmap result = + BitmapUtils.getScaledMutableBitmap(background, Integer.MAX_VALUE, mCardImageHeight); + Bitmap scaledLogo = + BitmapUtils.scaleBitmap(logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight); Canvas canvas; try { canvas = new Canvas(result); @@ -524,27 +556,32 @@ public class NotificationService extends Service implements Recommender.Listener @Override public void handleMessage(Message msg, @NonNull NotificationService notificationService) { switch (msg.what) { - case MSG_INITIALIZE_RECOMMENDER: { - notificationService.handleInitializeRecommender(); - break; - } - case MSG_SHOW_RECOMMENDATION: { - notificationService.handleShowRecommendation(); - break; - } - case MSG_UPDATE_RECOMMENDATION: { - int notificationId = msg.arg1; - Channel channel = ((Channel) msg.obj); - notificationService.handleUpdateRecommendation(notificationId, channel); - break; - } - case MSG_HIDE_RECOMMENDATION: { - notificationService.handleHideRecommendation(); - break; - } - default: { - super.handleMessage(msg); - } + case MSG_INITIALIZE_RECOMMENDER: + { + notificationService.handleInitializeRecommender(); + break; + } + case MSG_SHOW_RECOMMENDATION: + { + notificationService.handleShowRecommendation(); + break; + } + case MSG_UPDATE_RECOMMENDATION: + { + int notificationId = msg.arg1; + Channel channel = ((Channel) msg.obj); + notificationService.handleUpdateRecommendation(notificationId, channel); + break; + } + case MSG_HIDE_RECOMMENDATION: + { + notificationService.handleHideRecommendation(); + break; + } + default: + { + super.handleMessage(msg); + } } } } diff --git a/src/com/android/tv/recommendation/RecentChannelEvaluator.java b/src/com/android/tv/recommendation/RecentChannelEvaluator.java index e724f4ce..f4c4877d 100644 --- a/src/com/android/tv/recommendation/RecentChannelEvaluator.java +++ b/src/com/android/tv/recommendation/RecentChannelEvaluator.java @@ -51,9 +51,12 @@ public class RecentChannelEvaluator extends Recommender.Evaluator { if (watchDuration < WATCH_DURATION_MS_LOWER_BOUND) { watchDurationScore = MAX_SCORE_FOR_LOWER_BOUND; } else if (watchDuration < WATCH_DURATION_MS_UPPER_BOUND) { - watchDurationScore = (watchDuration - WATCH_DURATION_MS_LOWER_BOUND) - / (WATCH_DURATION_MS_UPPER_BOUND - WATCH_DURATION_MS_LOWER_BOUND) - * (1 - MAX_SCORE_FOR_LOWER_BOUND) + MAX_SCORE_FOR_LOWER_BOUND; + watchDurationScore = + (watchDuration - WATCH_DURATION_MS_LOWER_BOUND) + / (WATCH_DURATION_MS_UPPER_BOUND + - WATCH_DURATION_MS_LOWER_BOUND) + * (1 - MAX_SCORE_FOR_LOWER_BOUND) + + MAX_SCORE_FOR_LOWER_BOUND; } else { watchDurationScore = 1.0; } @@ -61,4 +64,4 @@ public class RecentChannelEvaluator extends Recommender.Evaluator { } return (maxScore > 0.0) ? maxScore : NOT_RECOMMENDED; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index dc148ec8..794ca7e2 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -33,7 +33,6 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; - import com.android.tv.TvApplication; import com.android.tv.common.WeakHandler; import com.android.tv.data.Channel; @@ -42,7 +41,6 @@ import com.android.tv.data.Program; import com.android.tv.data.WatchedHistoryManager; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvUriMatcher; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -84,40 +82,38 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private final HandlerThread mHandlerThread; private final Handler mHandler; private final Handler mMainHandler; - @Nullable - private WatchedHistoryManager mWatchedHistoryManager; + @Nullable private WatchedHistoryManager mWatchedHistoryManager; private final ChannelDataManager mChannelDataManager; private final ChannelDataManager.Listener mChannelDataListener = new ChannelDataManager.Listener() { - @Override - @MainThread - public void onLoadFinished() { - updateChannelData(); - } + @Override + @MainThread + public void onLoadFinished() { + updateChannelData(); + } - @Override - @MainThread - public void onChannelListUpdated() { - updateChannelData(); - } + @Override + @MainThread + public void onChannelListUpdated() { + updateChannelData(); + } - @Override - @MainThread - public void onChannelBrowsableChanged() { - updateChannelData(); - } - }; + @Override + @MainThread + public void onChannelBrowsableChanged() { + updateChannelData(); + } + }; // For thread safety, this variable is handled only on main thread. private final List mListeners = new ArrayList<>(); /** - * Gets instance of RecommendationDataManager, and adds a {@link Listener}. - * The listener methods will be called in the same thread as its caller of the method. - * Note that {@link #release(Listener)} should be called when this manager is not needed - * any more. + * Gets instance of RecommendationDataManager, and adds a {@link Listener}. The listener methods + * will be called in the same thread as its caller of the method. Note that {@link + * #release(Listener)} should be called when this manager is not needed any more. */ - public synchronized static RecommendationDataManager acquireManager( + public static synchronized RecommendationDataManager acquireManager( Context context, @NonNull Listener listener) { if (sManager == null) { sManager = new RecommendationDataManager(context); @@ -129,7 +125,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private final TvInputCallback mInternalCallback = new TvInputCallback() { @Override - public void onInputStateChanged(String inputId, int state) { } + public void onInputStateChanged(String inputId, int state) {} @Override public void onInputAdded(String inputId) { @@ -144,8 +140,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener for (ChannelRecord channelRecord : mChannelRecordMap.values()) { if (channelRecord.getChannel().getInputId().equals(inputId)) { channelRecord.setInputRemoved(false); - mAvailableChannelRecordMap.put(channelRecord.getChannel().getId(), - channelRecord); + mAvailableChannelRecordMap.put( + channelRecord.getChannel().getId(), channelRecord); channelRecordMapChanged = true; } } @@ -179,7 +175,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } @Override - public void onInputUpdated(String inputId) { } + public void onInputUpdated(String inputId) {} }; private RecommendationDataManager(Context context) { @@ -190,47 +186,43 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener mMainHandler = new RecommendationMainHandler(Looper.getMainLooper(), this); mContentObserver = new RecommendationContentObserver(mHandler); mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager(); - runOnMainThread(new Runnable() { - @Override - public void run() { - start(); - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + start(); + } + }); } /** - * Removes the {@link Listener}, and releases RecommendationDataManager - * if there are no listeners remained. + * Removes the {@link Listener}, and releases RecommendationDataManager if there are no + * listeners remained. */ public void release(@NonNull final Listener listener) { - runOnMainThread(new Runnable() { - @Override - public void run() { - removeListener(listener); - if (mListeners.size() == 0) { - stop(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + removeListener(listener); + if (mListeners.size() == 0) { + stop(); + } + } + }); } - /** - * Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}. - */ + /** Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}. */ public ChannelRecord getChannelRecord(long channelId) { return mAvailableChannelRecordMap.get(channelId); } - /** - * Returns the number of channels registered in ChannelRecord map. - */ + /** Returns the number of channels registered in ChannelRecord map. */ public int getChannelRecordCount() { return mAvailableChannelRecordMap.size(); } - /** - * Returns a Collection of ChannelRecords. - */ + /** Returns a Collection of ChannelRecords. */ public Collection getChannelRecords() { return Collections.unmodifiableCollection(mAvailableChannelRecordMap.values()); } @@ -264,12 +256,13 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } private void addListener(Listener listener) { - runOnMainThread(new Runnable() { - @Override - public void run() { - mListeners.add(listener); - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + mListeners.add(listener); + } + }); } @MainThread @@ -286,10 +279,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener mWatchedHistoryManager.setListener(this); mWatchedHistoryManager.start(); } else { - mContext.getContentResolver().registerContentObserver( - TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver); - mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, - TvContract.WatchedPrograms.CONTENT_URI) + mContext.getContentResolver() + .registerContentObserver( + TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver); + mHandler.obtainMessage( + MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI) .sendToTarget(); } mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE); @@ -333,7 +327,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } } - if (isChannelRecordMapChanged && mChannelRecordMapLoaded + if (isChannelRecordMapChanged + && mChannelRecordMapLoaded && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) { mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED); } @@ -356,14 +351,15 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram(watchedProgram); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onNewWatchLog(channelRecord); + } + } + }); } } if (!mChannelRecordMapLoaded) { @@ -374,96 +370,99 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener private WatchedProgram convertFromWatchedHistoryManagerRecords( WatchedHistoryManager.WatchedRecord watchedRecord) { long endTime = watchedRecord.watchedStartTime + watchedRecord.duration; - Program program = new Program.Builder() - .setChannelId(watchedRecord.channelId) - .setTitle("") - .setStartTimeUtcMillis(watchedRecord.watchedStartTime) - .setEndTimeUtcMillis(endTime) - .build(); + Program program = + new Program.Builder() + .setChannelId(watchedRecord.channelId) + .setTitle("") + .setStartTimeUtcMillis(watchedRecord.watchedStartTime) + .setEndTimeUtcMillis(endTime) + .build(); return new WatchedProgram(program, watchedRecord.watchedStartTime, endTime); } @Override public void onLoadFinished() { - for (WatchedHistoryManager.WatchedRecord record - : mWatchedHistoryManager.getWatchedHistory()) { - updateChannelRecordFromWatchedProgram( - convertFromWatchedHistoryManagerRecords(record)); + for (WatchedHistoryManager.WatchedRecord record : + mWatchedHistoryManager.getWatchedHistory()) { + updateChannelRecordFromWatchedProgram(convertFromWatchedHistoryManagerRecords(record)); } mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED); } @Override public void onNewRecordAdded(WatchedHistoryManager.WatchedRecord watchedRecord) { - final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram( - convertFromWatchedHistoryManagerRecords(watchedRecord)); + final ChannelRecord channelRecord = + updateChannelRecordFromWatchedProgram( + convertFromWatchedHistoryManagerRecords(watchedRecord)); if (mChannelRecordMapLoaded && channelRecord != null) { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onNewWatchLog(channelRecord); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onNewWatchLog(channelRecord); + } + } + }); } } private WatchedProgram createWatchedProgramFromWatchedProgramCursor(Cursor cursor) { // Have to initiate the indexes of WatchedProgram Columns. if (mIndexWatchChannelId == -1) { - mIndexWatchChannelId = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_CHANNEL_ID); - mIndexProgramTitle = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_TITLE); - mIndexProgramStartTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); - mIndexProgramEndTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); - mIndexWatchStartTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); - mIndexWatchEndTime = cursor.getColumnIndex( - TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); + mIndexWatchChannelId = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID); + mIndexProgramTitle = cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_TITLE); + mIndexProgramStartTime = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); + mIndexProgramEndTime = + cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); + mIndexWatchStartTime = + cursor.getColumnIndex( + TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); + mIndexWatchEndTime = + cursor.getColumnIndex( + TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); } - Program program = new Program.Builder() - .setChannelId(cursor.getLong(mIndexWatchChannelId)) - .setTitle(cursor.getString(mIndexProgramTitle)) - .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime)) - .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime)) - .build(); + Program program = + new Program.Builder() + .setChannelId(cursor.getLong(mIndexWatchChannelId)) + .setTitle(cursor.getString(mIndexProgramTitle)) + .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime)) + .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime)) + .build(); - return new WatchedProgram(program, - cursor.getLong(mIndexWatchStartTime), - cursor.getLong(mIndexWatchEndTime)); + return new WatchedProgram( + program, cursor.getLong(mIndexWatchStartTime), cursor.getLong(mIndexWatchEndTime)); } private void onNotifyChannelRecordMapLoaded() { mChannelRecordMapLoaded = true; - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordLoaded(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onChannelRecordLoaded(); + } + } + }); } private void onNotifyChannelRecordMapChanged() { - runOnMainThread(new Runnable() { - @Override - public void run() { - for (Listener l : mListeners) { - l.onChannelRecordChanged(); - } - } - }); + runOnMainThread( + new Runnable() { + @Override + public void run() { + for (Listener l : mListeners) { + l.onChannelRecordChanged(); + } + } + }); } - /** - * Returns true if ChannelRecords are added into mChannelRecordMap or removed from it. - */ + /** Returns true if ChannelRecords are added into mChannelRecordMap or removed from it. */ private boolean updateChannelRecordMapFromChannel(Channel channel) { if (!channel.isBrowsable()) { mChannelRecordMap.remove(channel.getId()); @@ -507,8 +506,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener public void onChange(final boolean selfChange, final Uri uri) { switch (TvUriMatcher.match(uri)) { case TvUriMatcher.MATCH_WATCHED_PROGRAM_ID: - if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY, - TvContract.WatchedPrograms.CONTENT_URI)) { + if (!mHandler.hasMessages( + MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)) { mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, uri).sendToTarget(); } break; @@ -524,15 +523,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } } - /** - * A listener interface to receive notification about the recommendation data. - * - * @MainThread - */ + /** A listener interface to receive notification about the recommendation data. @MainThread */ public interface Listener { /** - * Called when loading channel record map from database is finished. - * It will be called after RecommendationDataManager.start() is finished. + * Called when loading channel record map from database is finished. It will be called after + * RecommendationDataManager.start() is finished. * *

Note that this method is called on the main thread. */ @@ -601,6 +596,6 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener } @Override - protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) { } + protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) {} } } diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java index 82c2893d..7542286f 100644 --- a/src/com/android/tv/recommendation/Recommender.java +++ b/src/com/android/tv/recommendation/Recommender.java @@ -20,9 +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 java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -35,8 +33,7 @@ import java.util.concurrent.TimeUnit; public class Recommender implements RecommendationDataManager.Listener { private static final String TAG = "Recommender"; - @VisibleForTesting - static final String INVALID_CHANNEL_SORT_KEY = "INVALID"; + @VisibleForTesting static final String INVALID_CHANNEL_SORT_KEY = "INVALID"; private static final long MINIMUM_RECOMMENDATION_UPDATE_PERIOD = TimeUnit.MINUTES.toMillis(5); private static final Comparator> mChannelScoreComparator = new Comparator>() { @@ -69,7 +66,9 @@ public class Recommender implements RecommendationDataManager.Listener { } @VisibleForTesting - Recommender(Listener listener, boolean includeRecommendedOnly, + Recommender( + Listener listener, + boolean includeRecommendedOnly, RecommendationDataManager dataManager) { mListener = listener; mIncludeRecommendedOnly = includeRecommendedOnly; @@ -85,16 +84,16 @@ public class Recommender implements RecommendationDataManager.Listener { } public void registerEvaluator(Evaluator evaluator) { - registerEvaluator(evaluator, - EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT); + registerEvaluator( + evaluator, EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT); } /** * Register the evaluator used in recommendation. * - * The range of evaluated scores by this evaluator will be between {@code baseScore} and + *

The range of evaluated scores by this evaluator will be between {@code baseScore} and * {@code baseScore} + {@code weight} (inclusive). - + * * @param evaluator The evaluator to register inside this recommender. * @param baseScore Base(Minimum) score of the score evaluated by {@code evaluator}. * @param weight Weight value to rearrange the score evaluated by {@code evaluator}. @@ -108,13 +107,13 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * Return the channel list of recommendation up to {@code n} or the number of channels. - * During the evaluation, this method updates the channel sort key of recommended channels. + * Return the channel list of recommendation up to {@code n} or the number of channels. During + * the evaluation, this method updates the channel sort key of recommended channels. * * @param size The number of channels that might be recommended. - * @return Top {@code size} channels recommended sorted by score in descending order. If - * {@code size} is bigger than the number of channels, the number of results could - * be less than {@code size}. + * @return Top {@code size} channels recommended sorted by score in descending order. If {@code + * size} is bigger than the number of channels, the number of results could be less than + * {@code size}. */ public List recommendChannels(int size) { List> records = new ArrayList<>(); @@ -154,7 +153,7 @@ public class Recommender implements RecommendationDataManager.Listener { * * @param channelId The channel ID to retrieve the {@link Channel} object for. * @return the {@link Channel} object for the given channel ID, {@code null} if such a channel - * is not found. + * is not found. */ public Channel getChannel(long channelId) { ChannelRecord record = mDataManager.getChannelRecord(channelId); @@ -172,10 +171,10 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * Returns the sort key of a given channel Id. Sort key is determined in - * {@link #recommendChannels()} and getChannelSortKey must be called after that. + * Returns the sort key of a given channel Id. Sort key is determined in {@link + * #recommendChannels()} and getChannelSortKey must be called after that. * - * If getChannelSortKey was called before evaluating the channels or trying to get sort key + *

If getChannelSortKey was called before evaluating the channels or trying to get sort key * of non-recommended channel, it returns {@link #INVALID_CHANNEL_SORT_KEY}. */ public String getChannelSortKey(long channelId) { @@ -231,27 +230,25 @@ public class Recommender implements RecommendationDataManager.Listener { mLastRecommendationUpdatedTimeUtcMillis = newUpdatedTimeMs; } - public static abstract class Evaluator { + public abstract static class Evaluator { public static final double NOT_RECOMMENDED = -1.0; private Recommender mRecommender; protected Evaluator() {} - protected void onChannelRecordListChanged(List channelRecords) { - } + protected void onChannelRecordListChanged(List channelRecords) {} /** * This will be called when a new watch log comes into WatchedPrograms table. * * @param channelRecord The channel record corresponds to the new watch log. */ - protected void onNewWatchLog(ChannelRecord channelRecord) { - } + protected void onNewWatchLog(ChannelRecord channelRecord) {} /** - * The implementation should return the recommendation score for the given channel ID. - * The return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting - * that it gives up to calculate the score for the channel. + * The implementation should return the recommendation score for the given channel ID. The + * return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting that it + * gives up to calculate the score for the channel. * * @param channelId The channel ID which will be evaluated by this recommender. * @return The recommendation score @@ -278,8 +275,8 @@ public class Recommender implements RecommendationDataManager.Listener { // this value. private final double mWeight; - public EvaluatorWrapper(Recommender recommender, Evaluator evaluator, - double baseScore, double weight) { + public EvaluatorWrapper( + Recommender recommender, Evaluator evaluator, double baseScore, double weight) { mEvaluator = evaluator; evaluator.setRecommender(recommender); mBaseScore = baseScore; @@ -287,27 +284,27 @@ public class Recommender implements RecommendationDataManager.Listener { } /** - * This returns the scaled score for the given channel ID based on the returned value - * of evaluateChannel(). + * This returns the scaled score for the given channel ID based on the returned value of + * evaluateChannel(). * * @param channelId The channel ID which will be evaluated by the recommender. * @return Returns the scaled score (mBaseScore + score * mWeight) when evaluateChannel() is - * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any - * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more - * than 1.0, it returns (mBaseScore + mWeight). + * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any + * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more than + * 1.0, it returns (mBaseScore + mWeight). */ private double getScaledEvaluatorScore(long channelId) { double score = mEvaluator.evaluateChannel(channelId); if (score < 0.0) { if (score != Evaluator.NOT_RECOMMENDED) { - Log.w(TAG, "Unexpected score (" + score + ") from the recommender" - + mEvaluator); + Log.w( + TAG, + "Unexpected score (" + score + ") from the recommender" + mEvaluator); } // If the recommender gives up to calculate the score, return 0.0 return Evaluator.NOT_RECOMMENDED; } else if (score > 1.0) { - Log.w(TAG, "Unexpected score (" + score + ") from the recommender" - + mEvaluator); + Log.w(TAG, "Unexpected score (" + score + ") from the recommender" + mEvaluator); score = 1.0; } return mBaseScore + score * mWeight; @@ -323,14 +320,10 @@ public class Recommender implements RecommendationDataManager.Listener { } public interface Listener { - /** - * Called after channel record map is loaded. - */ + /** Called after channel record map is loaded. */ void onRecommenderReady(); - /** - * Called when the recommendation changes. - */ + /** Called when the recommendation changes. */ void onRecommendationChanged(); } } diff --git a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java index ad55afb7..2b17c510 100644 --- a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java +++ b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java @@ -21,22 +21,18 @@ import android.os.Build; import android.support.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.PreviewProgramContent; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.data.RecordedProgram; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; -/** - * Class to update the preview data for {@link RecordedProgram} - */ +/** Class to update the preview data for {@link RecordedProgram} */ @RequiresApi(Build.VERSION_CODES.O) public class RecordedProgramPreviewUpdater { private static final String TAG = "RecordedProgramPreviewUpdater"; @@ -47,13 +43,11 @@ public class RecordedProgramPreviewUpdater { private static RecordedProgramPreviewUpdater sRecordedProgramPreviewUpdater; - /** - * Creates and returns the {@link RecordedProgramPreviewUpdater}. - */ + /** Creates and returns the {@link RecordedProgramPreviewUpdater}. */ public static RecordedProgramPreviewUpdater getInstance(Context context) { if (sRecordedProgramPreviewUpdater == null) { - sRecordedProgramPreviewUpdater - = new RecordedProgramPreviewUpdater(context.getApplicationContext()); + sRecordedProgramPreviewUpdater = + new RecordedProgramPreviewUpdater(context.getApplicationContext()); } return sRecordedProgramPreviewUpdater; } @@ -67,53 +61,53 @@ public class RecordedProgramPreviewUpdater { ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); mPreviewDataManager = applicationSingletons.getPreviewDataManager(); mDvrDataManager = applicationSingletons.getDvrDataManager(); - mDvrDataManager.addRecordedProgramListener(new DvrDataManager.RecordedProgramListener() { - @Override - public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Add new preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } + mDvrDataManager.addRecordedProgramListener( + new DvrDataManager.RecordedProgramListener() { + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Add new preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Update preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Update preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { - if (DEBUG) Log.d(TAG, "Delete preview recorded programs"); - updatePreviewDataForRecordedPrograms(); - } - }); + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + if (DEBUG) Log.d(TAG, "Delete preview recorded programs"); + updatePreviewDataForRecordedPrograms(); + } + }); } - /** - * Updates the preview data for recorded programs. - */ + /** Updates the preview data for recorded programs. */ public void updatePreviewDataForRecordedPrograms() { if (!mPreviewDataManager.isLoadFinished()) { - mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() { - @Override - public void onPreviewDataLoadFinished() { - mPreviewDataManager.removeListener(this); - updatePreviewDataForRecordedPrograms(); - } + mPreviewDataManager.addListener( + new PreviewDataManager.PreviewDataListener() { + @Override + public void onPreviewDataLoadFinished() { + mPreviewDataManager.removeListener(this); + updatePreviewDataForRecordedPrograms(); + } - @Override - public void onPreviewDataUpdateFinished() { } - }); + @Override + public void onPreviewDataUpdateFinished() {} + }); return; } if (!mDvrDataManager.isRecordedProgramLoadFinished()) { mDvrDataManager.addRecordedProgramLoadFinishedListener( new DvrDataManager.OnRecordedProgramLoadFinishedListener() { - @Override - public void onRecordedProgramLoadFinished() { - mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); - updatePreviewDataForRecordedPrograms(); - } - }); + @Override + public void onRecordedProgramLoadFinished() { + mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); + updatePreviewDataForRecordedPrograms(); + } + }); return; } updatePreviewDataForRecordedProgramsInternal(); @@ -121,15 +115,18 @@ public class RecordedProgramPreviewUpdater { private void updatePreviewDataForRecordedProgramsInternal() { Set recordedPrograms = generateRecommendationRecordedPrograms(); - Long recordedPreviewChannelId = mPreviewDataManager.getPreviewChannelId( - PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL); + Long recordedPreviewChannelId = + mPreviewDataManager.getPreviewChannelId( + PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL); if (recordedPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID && !recordedPrograms.isEmpty()) { createPreviewChannelForRecordedPrograms(); } else { - mPreviewDataManager.updatePreviewProgramsForChannel(recordedPreviewChannelId, + mPreviewDataManager.updatePreviewProgramsForChannel( + recordedPreviewChannelId, generatePreviewProgramContentsFromRecordedPrograms( - recordedPreviewChannelId, recordedPrograms), null); + recordedPreviewChannelId, recordedPrograms), + null); } } @@ -168,8 +165,9 @@ public class RecordedProgramPreviewUpdater { long previewChannelId, Set recordedPrograms) { Set result = new HashSet<>(); for (RecordedProgram recordedProgram : recordedPrograms) { - result.add(PreviewProgramContent.createFromRecordedProgram(mContext, previewChannelId, - recordedProgram)); + result.add( + PreviewProgramContent.createFromRecordedProgram( + mContext, previewChannelId, recordedProgram)); } return result; } diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java index 5ff7cae9..9240682a 100644 --- a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java +++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java @@ -19,9 +19,7 @@ package com.android.tv.recommendation; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; - import com.android.tv.data.Program; - import java.text.BreakIterator; import java.util.ArrayList; import java.util.Calendar; @@ -32,8 +30,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { // TODO: test and refine constant values in WatchedProgramRecommender in order to // improve the performance of this recommender. private static final double REQUIRED_MIN_SCORE = 0.15; - @VisibleForTesting - static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7; + @VisibleForTesting static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7; private static final double TITLE_MATCH_WEIGHT = 0.5; private static final double TIME_MATCH_WEIGHT = 1 - TITLE_MATCH_WEIGHT; private static final long DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(14); @@ -57,8 +54,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } Program watchedProgram = watchHistory[watchHistory.length - 1].getProgram(); - long startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); + long startTimeDiffMsWithCurrentProgram = + currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis(); if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { return NOT_RECOMMENDED; } @@ -70,42 +67,48 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { == watchHistory[i].getProgram().getStartTimeUtcMillis()) { watchedDurationMs += watchHistory[i].getWatchedDurationMs(); } else { - double score = calculateRoutineWatchScore( - currentProgram, watchedProgram, watchedDurationMs); + double score = + calculateRoutineWatchScore( + currentProgram, watchedProgram, watchedDurationMs); if (score >= REQUIRED_MIN_SCORE && score > maxScore) { maxScore = score; } watchedProgram = watchHistory[i].getProgram(); watchedDurationMs = watchHistory[i].getWatchedDurationMs(); - startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); + startTimeDiffMsWithCurrentProgram = + currentProgram.getStartTimeUtcMillis() + - watchedProgram.getStartTimeUtcMillis(); if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { return maxScore; } } } - double score = calculateRoutineWatchScore( - currentProgram, watchedProgram, watchedDurationMs); + double score = + calculateRoutineWatchScore(currentProgram, watchedProgram, watchedDurationMs); if (score >= REQUIRED_MIN_SCORE && score > maxScore) { maxScore = score; } return maxScore; } - private static double calculateRoutineWatchScore(Program currentProgram, Program watchedProgram, - long watchedDurationMs) { + private static double calculateRoutineWatchScore( + Program currentProgram, Program watchedProgram, long watchedDurationMs) { double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram); - double titleMatchScore = calculateTitleMatchScore( - currentProgram.getTitle(), watchedProgram.getTitle()); + double titleMatchScore = + calculateTitleMatchScore(currentProgram.getTitle(), watchedProgram.getTitle()); double watchDurationScore = calculateWatchDurationScore(watchedProgram, watchedDurationMs); - long diffMs = currentProgram.getStartTimeUtcMillis() - - watchedProgram.getStartTimeUtcMillis(); - double multiplierForOldProgram = (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM) - ? 1.0 - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0) - / (MAX_DIFF_MS_FOR_OLD_PROGRAM - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM) - : 0.0; + long diffMs = + currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis(); + double multiplierForOldProgram = + (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM) + ? 1.0 + - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0) + / (MAX_DIFF_MS_FOR_OLD_PROGRAM + - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM) + : 0.0; return (titleMatchScore * TITLE_MATCH_WEIGHT + timeMatchScore * TIME_MATCH_WEIGHT) - * watchDurationScore * multiplierForOldProgram; + * watchDurationScore + * multiplierForOldProgram; } @VisibleForTesting @@ -118,8 +121,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { if (wordList1.isEmpty() || wordList2.isEmpty()) { return 0; } - int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength( - wordList1, wordList2); + int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength(wordList1, wordList2); // F-measure score double precision = (double) maxMatchedWordSeqLen / wordList1.size(); @@ -128,8 +130,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { } @VisibleForTesting - static int calculateMaximumMatchedWordSequenceLength(List toSearchWords, - List toMatchWords) { + static int calculateMaximumMatchedWordSequenceLength( + List toSearchWords, List toMatchWords) { int[] matchedWordSeqLen = new int[toMatchWords.size()]; int maxMatchedWordSeqLen = 0; for (String word : toSearchWords) { @@ -170,14 +172,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { boolean sameDay = false; // Handle cases like (00:00 - 02:00) - (01:00 - 03:00) or (22:00 - 25:00) - (23:00 - 26:00). - double score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec) - - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec)); + double score = + Math.max( + 0, + Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec) + - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec)); if (score > 0) { sameDay = (t1.weekDay == t2.weekDay); } else if (t1.dayChanged != t2.dayChanged) { // To handle cases like t1 : (00:00 - 01:00) and t2 : (23:00 - 25:00). - score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60) - - t1.startTimeOfDayInSec); + score = + Math.max( + 0, + Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60) + - t1.startTimeOfDayInSec); // Same day if next day of t2's start day equals to t1's start day. (1 <= weekDay <= 7) sameDay = (t1.weekDay == ((t2.weekDay % 7) + 1)); } @@ -206,7 +214,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { BreakIterator boundary = BreakIterator.getWordInstance(); boundary.setText(text); int start = boundary.first(); - for (int end = boundary.next(); end != BreakIterator.DONE; + for (int end = boundary.next(); + end != BreakIterator.DONE; start = end, end = boundary.next()) { String word = text.substring(start, end); if (Character.isLetterOrDigit(word.charAt(0))) { @@ -233,15 +242,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator { time.setTimeInMillis(p.getEndTimeUtcMillis()); boolean dayChanged = (weekDay != time.get(Calendar.DAY_OF_WEEK)); // Set maximum program duration time to 12 hours. - int endTimeOfDayInSec = startTimeOfDayInSec + - (int) Math.min(p.getEndTimeUtcMillis() - p.getStartTimeUtcMillis(), - TimeUnit.HOURS.toMillis(12)) / 1000; + int endTimeOfDayInSec = + startTimeOfDayInSec + + (int) + Math.min( + p.getEndTimeUtcMillis() + - p.getStartTimeUtcMillis(), + TimeUnit.HOURS.toMillis(12)) + / 1000; return new ProgramTime(startTimeOfDayInSec, endTimeOfDayInSec, weekDay, dayChanged); } - private ProgramTime(int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, - boolean dayChanged) { + private ProgramTime( + int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, boolean dayChanged) { this.startTimeOfDayInSec = startTimeOfDayInSec; this.endTimeOfDayInSec = endTimeOfDayInSec; this.weekDay = weekDay; diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java index d90908f1..a60355f4 100644 --- a/src/com/android/tv/search/DataManagerSearch.java +++ b/src/com/android/tv/search/DataManagerSearch.java @@ -26,7 +26,6 @@ import android.os.SystemClock; import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.data.Channel; @@ -36,7 +35,6 @@ import com.android.tv.data.ProgramDataManager; import com.android.tv.search.LocalSearchProvider.SearchResult; import com.android.tv.util.MainThreadExecutor; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -47,8 +45,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** - * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager} - * and {@link ProgramDataManager}. + * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager} and + * {@link ProgramDataManager}. */ public class DataManagerSearch implements SearchInterface { private static final String TAG = "TvProviderSearch"; @@ -69,13 +67,15 @@ public class DataManagerSearch implements SearchInterface { @Override public List search(final String query, final int limit, final int action) { - Future> future = MainThreadExecutor.getInstance() - .submit(new Callable>() { - @Override - public List call() throws Exception { - return searchFromDataManagers(query, limit, action); - } - }); + Future> future = + MainThreadExecutor.getInstance() + .submit( + new Callable>() { + @Override + public List call() throws Exception { + return searchFromDataManagers(query, limit, action); + } + }); try { return future.get(); @@ -94,8 +94,7 @@ public class DataManagerSearch implements SearchInterface { if (!mChannelDataManager.isDbLoadFinished()) { return results; } - if (action == ACTION_TYPE_SWITCH_CHANNEL - || action == ACTION_TYPE_SWITCH_INPUT) { + if (action == ACTION_TYPE_SWITCH_CHANNEL || action == ACTION_TYPE_SWITCH_INPUT) { // Voice search query should be handled by the a system TV app. return results; } @@ -114,9 +113,14 @@ public class DataManagerSearch implements SearchInterface { } if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" + - " searching channels: " + (SystemClock.elapsedRealtime() - time) + - "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " channels. Elapsed time for" + + " searching channels: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @@ -133,16 +137,27 @@ public class DataManagerSearch implements SearchInterface { } if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" + - " searching channels: " + (SystemClock.elapsedRealtime() - time) + - "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " channels. Elapsed time for" + + " searching channels: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } } if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" + - " searching channels: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " channels. Elapsed time for" + + " searching channels: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } int channelResult = results.size(); if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'"); @@ -161,9 +176,14 @@ public class DataManagerSearch implements SearchInterface { } if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed" + - " time for searching programs: " + - (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + (results.size() - channelResult) + + " programs. Elapsed" + + " time for searching programs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @@ -182,16 +202,27 @@ public class DataManagerSearch implements SearchInterface { } if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed" + - " time for searching programs: " + - (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + (results.size() - channelResult) + + " programs. Elapsed" + + " time for searching programs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } } if (DEBUG) { - Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed time for" + - " searching programs: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + (results.size() - channelResult) + + " programs. Elapsed time for" + + " searching programs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @@ -201,11 +232,9 @@ public class DataManagerSearch implements SearchInterface { return string != null && string.toLowerCase().contains(query); } - /** - * If query is matched to channel, {@code program} should be null. - */ - private void addResult(List results, Set channelsFound, Channel channel, - Program program) { + /** If query is matched to channel, {@code program} should be null. */ + private void addResult( + List results, Set channelsFound, Channel channel, Program program) { if (program == null) { program = mProgramDataManager.getCurrentProgram(channel.getId()); if (program != null && isRatingBlocked(program.getContentRatings())) { @@ -229,9 +258,12 @@ public class DataManagerSearch implements SearchInterface { result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE; } else { result.title = program.getTitle(); - result.description = buildProgramDescription(channel.getDisplayNumber(), - channel.getDisplayName(), program.getStartTimeUtcMillis(), - program.getEndTimeUtcMillis()); + result.description = + buildProgramDescription( + channel.getDisplayNumber(), + channel.getDisplayName(), + program.getStartTimeUtcMillis(), + program.getEndTimeUtcMillis()); result.imageUri = program.getPosterArtUri(); result.intentAction = Intent.ACTION_VIEW; result.intentData = buildIntentData(channelId); @@ -240,8 +272,9 @@ public class DataManagerSearch implements SearchInterface { result.videoWidth = program.getVideoWidth(); result.videoHeight = program.getVideoHeight(); result.duration = program.getDurationMillis(); - result.progressPercentage = getProgressPercentage( - program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()); + result.progressPercentage = + getProgressPercentage( + program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()); } if (DEBUG) { Log.d(TAG, "Add a result : channel=" + channel + " program=" + program); @@ -250,10 +283,16 @@ public class DataManagerSearch implements SearchInterface { channelsFound.add(channel.getId()); } - private String buildProgramDescription(String channelNumber, String channelName, - long programStartUtcMillis, long programEndUtcMillis) { + private String buildProgramDescription( + String channelNumber, + String channelName, + long programStartUtcMillis, + long programEndUtcMillis) { return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false) - + System.lineSeparator() + channelNumber + " " + channelName; + + System.lineSeparator() + + channelNumber + + " " + + channelName; } private int getProgressPercentage(long startUtcMillis, long endUtcMillis) { @@ -261,7 +300,7 @@ public class DataManagerSearch implements SearchInterface { if (startUtcMillis > current || endUtcMillis <= current) { return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE; } - return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis)); + return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis)); } private String buildIntentData(long channelId) { @@ -269,7 +308,8 @@ public class DataManagerSearch implements SearchInterface { } private boolean isRatingBlocked(TvContentRating[] ratings) { - if (ratings == null || ratings.length == 0 + if (ratings == null + || ratings.length == 0 || !mTvInputManager.isParentalControlsEnabled()) { return false; } diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java index ef9336d7..dfd585c3 100644 --- a/src/com/android/tv/search/LocalSearchProvider.java +++ b/src/com/android/tv/search/LocalSearchProvider.java @@ -27,7 +27,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; - import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonUtils; @@ -36,7 +35,6 @@ import com.android.tv.perf.PerformanceMonitor; import com.android.tv.perf.TimerEvent; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvUriMatcher; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -53,25 +51,26 @@ public class LocalSearchProvider extends ContentProvider { // TODO: Remove this once added to the SearchManager. private static final String SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE = "progress_bar_percentage"; - private static final String[] SEARCHABLE_COLUMNS = new String[] { - SearchManager.SUGGEST_COLUMN_TEXT_1, - SearchManager.SUGGEST_COLUMN_TEXT_2, - SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE, - SearchManager.SUGGEST_COLUMN_INTENT_ACTION, - SearchManager.SUGGEST_COLUMN_INTENT_DATA, - SearchManager.SUGGEST_COLUMN_CONTENT_TYPE, - SearchManager.SUGGEST_COLUMN_IS_LIVE, - SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH, - SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT, - SearchManager.SUGGEST_COLUMN_DURATION, - SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE - }; + private static final String[] SEARCHABLE_COLUMNS = + new String[] { + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE, + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + SearchManager.SUGGEST_COLUMN_INTENT_DATA, + SearchManager.SUGGEST_COLUMN_CONTENT_TYPE, + SearchManager.SUGGEST_COLUMN_IS_LIVE, + SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH, + SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT, + SearchManager.SUGGEST_COLUMN_DURATION, + SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE + }; private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY; static final String SUGGEST_PARAMETER_ACTION = "action"; // The launcher passes 10 as a 'limit' parameter by default. - @VisibleForTesting - static final int DEFAULT_SEARCH_LIMIT = 10; + @VisibleForTesting static final int DEFAULT_SEARCH_LIMIT = 10; + @VisibleForTesting static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS; @@ -96,15 +95,30 @@ public class LocalSearchProvider extends ContentProvider { } @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { + public Cursor query( + @NonNull Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) { throw new IllegalArgumentException("Unknown URI: " + uri); } TimerEvent queryTimer = mPerformanceMonitor.startTimer(); if (DEBUG) { - Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", " - + Arrays.toString(selectionArgs) + ", " + sortOrder + ")"); + Log.d( + TAG, + "query(" + + uri + + ", " + + Arrays.toString(projection) + + ", " + + selection + + ", " + + Arrays.toString(selectionArgs) + + ", " + + sortOrder + + ")"); } long time = SystemClock.elapsedRealtime(); SearchInterface search = mSearchInterface; @@ -118,8 +132,8 @@ public class LocalSearchProvider extends ContentProvider { } } String query = uri.getLastPathSegment(); - int limit = getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, - DEFAULT_SEARCH_LIMIT); + int limit = + getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, DEFAULT_SEARCH_LIMIT); if (limit <= 0) { limit = DEFAULT_SEARCH_LIMIT; } @@ -134,8 +148,13 @@ public class LocalSearchProvider extends ContentProvider { } Cursor c = createSuggestionsCursor(results); if (DEBUG) { - Log.d(TAG, "Elapsed time(count=" + c.getCount() + "): " - + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Elapsed time(count=" + + c.getCount() + + "): " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH); return c; @@ -199,9 +218,7 @@ public class LocalSearchProvider extends ContentProvider { throw new UnsupportedOperationException(); } - /** - * A placeholder to a search result. - */ + /** A placeholder to a search result. */ public static class SearchResult { public long channelId; public String channelNumber; @@ -219,20 +236,33 @@ public class LocalSearchProvider extends ContentProvider { @Override public String toString() { - return "SearchResult{channelId=" + channelId + - ", channelNumber=" + channelNumber + - ", title=" + title + - ", description=" + description + - ", imageUri=" + imageUri + - ", intentAction=" + intentAction + - ", intentData=" + intentData + - ", contentType=" + contentType + - ", isLive=" + isLive + - ", videoWidth=" + videoWidth + - ", videoHeight=" + videoHeight + - ", duration=" + duration + - ", progressPercentage=" + progressPercentage + - "}"; + return "SearchResult{channelId=" + + channelId + + ", channelNumber=" + + channelNumber + + ", title=" + + title + + ", description=" + + description + + ", imageUri=" + + imageUri + + ", intentAction=" + + intentAction + + ", intentData=" + + intentData + + ", contentType=" + + contentType + + ", isLive=" + + isLive + + ", videoWidth=" + + videoWidth + + ", videoHeight=" + + videoHeight + + ", duration=" + + duration + + ", progressPercentage=" + + progressPercentage + + "}"; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java index 87eec68e..1ca86d22 100644 --- a/src/com/android/tv/search/ProgramGuideSearchFragment.java +++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java @@ -38,12 +38,10 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.util.ImageLoader; import com.android.tv.util.PermissionUtils; - import java.util.List; public class ProgramGuideSearchFragment extends SearchFragment { @@ -51,44 +49,49 @@ public class ProgramGuideSearchFragment extends SearchFragment { private static final boolean DEBUG = false; private static final int SEARCH_RESULT_MAX = 10; - private final Presenter mPresenter = new Presenter() { - @Override - public Presenter.ViewHolder onCreateViewHolder(ViewGroup viewGroup) { - if (DEBUG) Log.d(TAG, "onCreateViewHolder"); - - ImageCardView cardView = new ImageCardView(mMainActivity); - cardView.setFocusable(true); - cardView.setFocusableInTouchMode(true); - cardView.setMainImageAdjustViewBounds(false); - - Resources res = mMainActivity.getResources(); - cardView.setMainImageDimensions( - res.getDimensionPixelSize(R.dimen.card_image_layout_width), - res.getDimensionPixelSize(R.dimen.card_image_layout_height)); - - return new Presenter.ViewHolder(cardView); - } - - @Override - public void onBindViewHolder(ViewHolder viewHolder, Object o) { - ImageCardView cardView = (ImageCardView) viewHolder.view; - LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o; - if (DEBUG) Log.d(TAG, "onBindViewHolder result:" + result); - - cardView.setTitleText(result.title); - if (!TextUtils.isEmpty(result.imageUri)) { - ImageLoader.loadBitmap(mMainActivity, result.imageUri, mMainCardWidth, - mMainCardHeight, createImageLoaderCallback(cardView)); - } else { - cardView.setMainImage(mMainActivity.getDrawable(R.drawable.ic_launcher)); - } - } - - @Override - public void onUnbindViewHolder(ViewHolder viewHolder) { - // Do nothing here. - } - }; + private final Presenter mPresenter = + new Presenter() { + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup viewGroup) { + if (DEBUG) Log.d(TAG, "onCreateViewHolder"); + + ImageCardView cardView = new ImageCardView(mMainActivity); + cardView.setFocusable(true); + cardView.setFocusableInTouchMode(true); + cardView.setMainImageAdjustViewBounds(false); + + Resources res = mMainActivity.getResources(); + cardView.setMainImageDimensions( + res.getDimensionPixelSize(R.dimen.card_image_layout_width), + res.getDimensionPixelSize(R.dimen.card_image_layout_height)); + + return new Presenter.ViewHolder(cardView); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, Object o) { + ImageCardView cardView = (ImageCardView) viewHolder.view; + LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o; + if (DEBUG) Log.d(TAG, "onBindViewHolder result:" + result); + + cardView.setTitleText(result.title); + if (!TextUtils.isEmpty(result.imageUri)) { + ImageLoader.loadBitmap( + mMainActivity, + result.imageUri, + mMainCardWidth, + mMainCardHeight, + createImageLoaderCallback(cardView)); + } else { + cardView.setMainImage(mMainActivity.getDrawable(R.drawable.ic_launcher)); + } + } + + @Override + public void onUnbindViewHolder(ViewHolder viewHolder) { + // Do nothing here. + } + }; private static ImageLoader.ImageLoaderCallback createImageLoaderCallback( ImageCardView cardView) { @@ -101,35 +104,40 @@ public class ProgramGuideSearchFragment extends SearchFragment { }; } - private final SearchResultProvider mSearchResultProvider = new SearchResultProvider() { - @Override - public ObjectAdapter getResultsAdapter() { - return mResultAdapter; - } - - @Override - public boolean onQueryTextChange(String query) { - searchAndRefresh(query); - return true; - } - - @Override - public boolean onQueryTextSubmit(String query) { - searchAndRefresh(query); - return true; - } - }; - - private final OnItemViewClickedListener mItemClickedListener = new OnItemViewClickedListener() { - @Override - public void onItemClicked(Presenter.ViewHolder viewHolder, Object o, RowPresenter - .ViewHolder viewHolder1, Row row) { - LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o; - mMainActivity.getFragmentManager().popBackStack(); - mMainActivity.tuneToChannel( - mMainActivity.getChannelDataManager().getChannel(result.channelId)); - } - }; + private final SearchResultProvider mSearchResultProvider = + new SearchResultProvider() { + @Override + public ObjectAdapter getResultsAdapter() { + return mResultAdapter; + } + + @Override + public boolean onQueryTextChange(String query) { + searchAndRefresh(query); + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + searchAndRefresh(query); + return true; + } + }; + + private final OnItemViewClickedListener mItemClickedListener = + new OnItemViewClickedListener() { + @Override + public void onItemClicked( + Presenter.ViewHolder viewHolder, + Object o, + RowPresenter.ViewHolder viewHolder1, + Row row) { + LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o; + mMainActivity.getFragmentManager().popBackStack(); + mMainActivity.tuneToChannel( + mMainActivity.getChannelDataManager().getChannel(result.channelId)); + } + }; private final ArrayObjectAdapter mResultAdapter = new ArrayObjectAdapter(new ListRowPresenter()); @@ -185,8 +193,7 @@ public class ProgramGuideSearchFragment extends SearchFragment { mSearchTask.execute(); } - private class SearchTask extends - AsyncTask> { + private class SearchTask extends AsyncTask> { private final String mQuery; public SearchTask(String query) { @@ -195,8 +202,8 @@ public class ProgramGuideSearchFragment extends SearchFragment { @Override protected List doInBackground(Void... params) { - return mSearch.search(mQuery, SEARCH_RESULT_MAX, - TvProviderSearch.ACTION_TYPE_AMBIGUOUS); + return mSearch.search( + mQuery, SEARCH_RESULT_MAX, TvProviderSearch.ACTION_TYPE_AMBIGUOUS); } @Override @@ -205,20 +212,23 @@ public class ProgramGuideSearchFragment extends SearchFragment { mResultAdapter.clear(); if (DEBUG) { - Log.d(TAG, "searchAndRefresh query=" + mQuery - + " results=" + ((results == null) ? 0 : results.size())); + Log.d( + TAG, + "searchAndRefresh query=" + + mQuery + + " results=" + + ((results == null) ? 0 : results.size())); } if (results == null || results.size() == 0) { HeaderItem header = - new HeaderItem(0, mMainActivity.getString(R.string - .search_result_no_result)); + new HeaderItem( + 0, mMainActivity.getString(R.string.search_result_no_result)); ArrayObjectAdapter resultsAdapter = new ArrayObjectAdapter(mPresenter); mResultAdapter.add(new ListRow(header, resultsAdapter)); } else { HeaderItem header = - new HeaderItem(0, mMainActivity.getString(R.string - .search_result_title)); + new HeaderItem(0, mMainActivity.getString(R.string.search_result_title)); ArrayObjectAdapter resultsAdapter = new ArrayObjectAdapter(mPresenter); resultsAdapter.addAll(0, results); mResultAdapter.add(new ListRow(header, resultsAdapter)); diff --git a/src/com/android/tv/search/SearchInterface.java b/src/com/android/tv/search/SearchInterface.java index d631972a..4866ee84 100644 --- a/src/com/android/tv/search/SearchInterface.java +++ b/src/com/android/tv/search/SearchInterface.java @@ -17,12 +17,9 @@ package com.android.tv.search; import com.android.tv.search.LocalSearchProvider.SearchResult; - import java.util.List; -/** - * Interface for channel and program search. - */ +/** Interface for channel and program search. */ public interface SearchInterface { int ACTION_TYPE_START = 1; int ACTION_TYPE_AMBIGUOUS = 1; @@ -31,11 +28,11 @@ public interface SearchInterface { int ACTION_TYPE_END = 3; /** - * Search channels, inputs, or programs. - * This assumes that parental control settings will not be change while searching. + * Search channels, inputs, or programs. This assumes that parental control settings will not be + * change while searching. * * @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT}, - * or {@link #ACTION_TYPE_AMBIGUOUS}, + * or {@link #ACTION_TYPE_AMBIGUOUS}, */ List search(String query, int limit, int action); } diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java index e7d8a02d..d6cf9cc9 100644 --- a/src/com/android/tv/search/TvProviderSearch.java +++ b/src/com/android/tv/search/TvProviderSearch.java @@ -32,12 +32,10 @@ import android.os.SystemClock; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; - import com.android.tv.common.TvContentRatingCache; import com.android.tv.search.LocalSearchProvider.SearchResult; import com.android.tv.util.PermissionUtils; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -49,9 +47,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -/** - * An implementation of {@link SearchInterface} to search query from TvProvider directly. - */ +/** An implementation of {@link SearchInterface} to search query from TvProvider directly. */ public class TvProviderSearch implements SearchInterface { private static final String TAG = "TvProviderSearch"; private static final boolean DEBUG = false; @@ -70,11 +66,11 @@ public class TvProviderSearch implements SearchInterface { } /** - * Search channels, inputs, or programs from TvProvider. - * This assumes that parental control settings will not be change while searching. + * Search channels, inputs, or programs from TvProvider. This assumes that parental control + * settings will not be change while searching. * * @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT}, - * or {@link #ACTION_TYPE_AMBIGUOUS}, + * or {@link #ACTION_TYPE_AMBIGUOUS}, */ @Override @WorkerThread @@ -107,15 +103,19 @@ public class TvProviderSearch implements SearchInterface { // Lastly, search programs. limit -= results.size(); - results.addAll(searchPrograms(query, null, new String[] { - Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION }, - channelsFound, limit)); + results.addAll( + searchPrograms( + query, + null, + new String[] {Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION}, + channelsFound, + limit)); } return results; } - private void appendSelectionString(StringBuilder sb, String[] columnForExactMatching, - String[] columnForPartialMatching) { + private void appendSelectionString( + StringBuilder sb, String[] columnForExactMatching, String[] columnForPartialMatching) { boolean firstColumn = true; if (columnForExactMatching != null) { for (String column : columnForExactMatching) { @@ -139,8 +139,12 @@ public class TvProviderSearch implements SearchInterface { } } - private void insertSelectionArgumentStrings(String[] selectionArgs, int pos, - String query, String[] columnForExactMatching, String[] columnForPartialMatching) { + private void insertSelectionArgumentStrings( + String[] selectionArgs, + int pos, + String query, + String[] columnForExactMatching, + String[] columnForPartialMatching) { if (columnForExactMatching != null) { int until = pos + columnForExactMatching.length; for (; pos < until; ++pos) { @@ -162,16 +166,27 @@ public class TvProviderSearch implements SearchInterface { long time = SystemClock.elapsedRealtime(); List results = new ArrayList<>(); if (TextUtils.isDigitsOnly(query)) { - results.addAll(searchChannels(query, new String[] { Channels.COLUMN_DISPLAY_NUMBER }, - null, channels, NO_LIMIT)); + results.addAll( + searchChannels( + query, + new String[] {Channels.COLUMN_DISPLAY_NUMBER}, + null, + channels, + NO_LIMIT)); if (results.size() > 1) { Collections.sort(results, new ChannelComparatorWithSameDisplayNumber()); } } if (results.size() < limit) { - results.addAll(searchChannels(query, null, - new String[] { Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION }, - channels, limit - results.size())); + results.addAll( + searchChannels( + query, + null, + new String[] { + Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION + }, + channels, + limit - results.size())); } if (results.size() > limit) { results = results.subList(0, limit); @@ -180,25 +195,37 @@ public class TvProviderSearch implements SearchInterface { fillProgramInfo(result); } if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for searching" + - " channels: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " channels. Elapsed time for searching" + + " channels: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @WorkerThread - private List searchChannels(String query, String[] columnForExactMatching, - String[] columnForPartialMatching, Set channelsFound, int limit) { + private List searchChannels( + String query, + String[] columnForExactMatching, + String[] columnForPartialMatching, + Set channelsFound, + int limit) { String[] projection = { - Channels._ID, - Channels.COLUMN_DISPLAY_NUMBER, - Channels.COLUMN_DISPLAY_NAME, - Channels.COLUMN_DESCRIPTION + Channels._ID, + Channels.COLUMN_DISPLAY_NUMBER, + Channels.COLUMN_DISPLAY_NAME, + Channels.COLUMN_DESCRIPTION }; StringBuilder sb = new StringBuilder(); - sb.append(Channels.COLUMN_BROWSABLE).append("=1 AND ") - .append(Channels.COLUMN_SEARCHABLE).append("=1"); + sb.append(Channels.COLUMN_BROWSABLE) + .append("=1 AND ") + .append(Channels.COLUMN_SEARCHABLE) + .append("=1"); if (mTvInputManager.isParentalControlsEnabled()) { sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0"); } @@ -207,16 +234,18 @@ public class TvProviderSearch implements SearchInterface { sb.append(")"); String selection = sb.toString(); - int len = (columnForExactMatching == null ? 0 : columnForExactMatching.length) + - (columnForPartialMatching == null ? 0 : columnForPartialMatching.length); + int len = + (columnForExactMatching == null ? 0 : columnForExactMatching.length) + + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length); String[] selectionArgs = new String[len]; - insertSelectionArgumentStrings(selectionArgs, 0, query, columnForExactMatching, - columnForPartialMatching); + insertSelectionArgumentStrings( + selectionArgs, 0, query, columnForExactMatching, columnForPartialMatching); List searchResults = new ArrayList<>(); - try (Cursor c = mContentResolver.query(Channels.CONTENT_URI, projection, selection, - selectionArgs, null)) { + try (Cursor c = + mContentResolver.query( + Channels.CONTENT_URI, projection, selection, selectionArgs, null)) { if (c != null) { int count = 0; while (c.moveToNext()) { @@ -259,15 +288,16 @@ public class TvProviderSearch implements SearchInterface { private void fillProgramInfo(SearchResult result) { long now = System.currentTimeMillis(); Uri uri = TvContract.buildProgramsUriForChannel(result.channelId, now, now); - String[] projection = new String[] { - Programs.COLUMN_TITLE, - Programs.COLUMN_POSTER_ART_URI, - Programs.COLUMN_CONTENT_RATING, - Programs.COLUMN_VIDEO_WIDTH, - Programs.COLUMN_VIDEO_HEIGHT, - Programs.COLUMN_START_TIME_UTC_MILLIS, - Programs.COLUMN_END_TIME_UTC_MILLIS - }; + String[] projection = + new String[] { + Programs.COLUMN_TITLE, + Programs.COLUMN_POSTER_ART_URI, + Programs.COLUMN_CONTENT_RATING, + Programs.COLUMN_VIDEO_WIDTH, + Programs.COLUMN_VIDEO_HEIGHT, + Programs.COLUMN_START_TIME_UTC_MILLIS, + Programs.COLUMN_END_TIME_UTC_MILLIS + }; try (Cursor c = mContentResolver.query(uri, projection, null, null, null)) { if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) { @@ -275,8 +305,9 @@ public class TvProviderSearch implements SearchInterface { long startUtcMillis = c.getLong(5); long endUtcMillis = c.getLong(6); result.title = c.getString(0); - result.description = buildProgramDescription(result.channelNumber, channelName, - startUtcMillis, endUtcMillis); + result.description = + buildProgramDescription( + result.channelNumber, channelName, startUtcMillis, endUtcMillis); String imageUri = c.getString(1); if (imageUri != null) { result.imageUri = imageUri; @@ -289,10 +320,16 @@ public class TvProviderSearch implements SearchInterface { } } - private String buildProgramDescription(String channelNumber, String channelName, - long programStartUtcMillis, long programEndUtcMillis) { + private String buildProgramDescription( + String channelNumber, + String channelName, + long programStartUtcMillis, + long programEndUtcMillis) { return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false) - + System.lineSeparator() + channelNumber + " " + channelName; + + System.lineSeparator() + + channelNumber + + " " + + channelName; } private int getProgressPercentage(long startUtcMillis, long endUtcMillis) { @@ -300,23 +337,27 @@ public class TvProviderSearch implements SearchInterface { if (startUtcMillis > current || endUtcMillis <= current) { return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE; } - return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis)); + return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis)); } @WorkerThread - private List searchPrograms(String query, String[] columnForExactMatching, - String[] columnForPartialMatching, Set channelsFound, int limit) { + private List searchPrograms( + String query, + String[] columnForExactMatching, + String[] columnForPartialMatching, + Set channelsFound, + int limit) { if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'"); long time = SystemClock.elapsedRealtime(); String[] projection = { - Programs.COLUMN_CHANNEL_ID, - Programs.COLUMN_TITLE, - Programs.COLUMN_POSTER_ART_URI, - Programs.COLUMN_CONTENT_RATING, - Programs.COLUMN_VIDEO_WIDTH, - Programs.COLUMN_VIDEO_HEIGHT, - Programs.COLUMN_START_TIME_UTC_MILLIS, - Programs.COLUMN_END_TIME_UTC_MILLIS + Programs.COLUMN_CHANNEL_ID, + Programs.COLUMN_TITLE, + Programs.COLUMN_POSTER_ART_URI, + Programs.COLUMN_CONTENT_RATING, + Programs.COLUMN_VIDEO_WIDTH, + Programs.COLUMN_VIDEO_HEIGHT, + Programs.COLUMN_START_TIME_UTC_MILLIS, + Programs.COLUMN_END_TIME_UTC_MILLIS }; StringBuilder sb = new StringBuilder(); @@ -327,17 +368,19 @@ public class TvProviderSearch implements SearchInterface { sb.append(")"); String selection = sb.toString(); - int len = (columnForExactMatching == null ? 0 : columnForExactMatching.length) + - (columnForPartialMatching == null ? 0 : columnForPartialMatching.length); + int len = + (columnForExactMatching == null ? 0 : columnForExactMatching.length) + + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length); String[] selectionArgs = new String[len + 2]; selectionArgs[0] = selectionArgs[1] = String.valueOf(System.currentTimeMillis()); - insertSelectionArgumentStrings(selectionArgs, 2, query, columnForExactMatching, - columnForPartialMatching); + insertSelectionArgumentStrings( + selectionArgs, 2, query, columnForExactMatching, columnForPartialMatching); List searchResults = new ArrayList<>(); - try (Cursor c = mContentResolver.query(Programs.CONTENT_URI, projection, selection, - selectionArgs, null)) { + try (Cursor c = + mContentResolver.query( + Programs.CONTENT_URI, projection, selection, selectionArgs, null)) { if (c != null) { int count = 0; while (c.moveToNext()) { @@ -350,30 +393,40 @@ public class TvProviderSearch implements SearchInterface { // Don't know whether the channel is searchable or not. String[] channelProjection = { - Channels._ID, - Channels.COLUMN_DISPLAY_NUMBER, - Channels.COLUMN_DISPLAY_NAME + Channels._ID, Channels.COLUMN_DISPLAY_NUMBER, Channels.COLUMN_DISPLAY_NAME }; sb = new StringBuilder(); - sb.append(Channels._ID).append("=? AND ") - .append(Channels.COLUMN_BROWSABLE).append("=1 AND ") - .append(Channels.COLUMN_SEARCHABLE).append("=1"); + sb.append(Channels._ID) + .append("=? AND ") + .append(Channels.COLUMN_BROWSABLE) + .append("=1 AND ") + .append(Channels.COLUMN_SEARCHABLE) + .append("=1"); if (mTvInputManager.isParentalControlsEnabled()) { sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0"); } String selectionChannel = sb.toString(); - try (Cursor cChannel = mContentResolver.query(Channels.CONTENT_URI, - channelProjection, selectionChannel, - new String[] { String.valueOf(id) }, null)) { - if (cChannel != null && cChannel.moveToNext() + try (Cursor cChannel = + mContentResolver.query( + Channels.CONTENT_URI, + channelProjection, + selectionChannel, + new String[] {String.valueOf(id)}, + null)) { + if (cChannel != null + && cChannel.moveToNext() && !isRatingBlocked(c.getString(3))) { long startUtcMillis = c.getLong(6); long endUtcMillis = c.getLong(7); SearchResult result = new SearchResult(); result.channelId = c.getLong(0); result.title = c.getString(1); - result.description = buildProgramDescription(cChannel.getString(1), - cChannel.getString(2), startUtcMillis, endUtcMillis); + result.description = + buildProgramDescription( + cChannel.getString(1), + cChannel.getString(2), + startUtcMillis, + endUtcMillis); result.imageUri = c.getString(2); result.intentAction = Intent.ACTION_VIEW; result.intentData = buildIntentData(id); @@ -382,8 +435,8 @@ public class TvProviderSearch implements SearchInterface { result.videoWidth = c.getInt(4); result.videoHeight = c.getInt(5); result.duration = endUtcMillis - startUtcMillis; - result.progressPercentage = getProgressPercentage(startUtcMillis, - endUtcMillis); + result.progressPercentage = + getProgressPercentage(startUtcMillis, endUtcMillis); searchResults.add(result); if (limit != NO_LIMIT && ++count >= limit) { @@ -395,8 +448,14 @@ public class TvProviderSearch implements SearchInterface { } } if (DEBUG) { - Log.d(TAG, "Found " + searchResults.size() + " programs. Elapsed time for searching" + - " programs: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + searchResults.size() + + " programs. Elapsed time for searching" + + " programs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return searchResults; } @@ -439,9 +498,14 @@ public class TvProviderSearch implements SearchInterface { results.add(buildSearchResultForInput(input.getId())); if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for" + - " searching inputs: " + (SystemClock.elapsedRealtime() - time) + - "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " inputs. Elapsed time for" + + " searching inputs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @@ -455,22 +519,33 @@ public class TvProviderSearch implements SearchInterface { } String label = canonicalizeLabel(input.loadLabel(mContext)); String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext)); - if ((label != null && label.contains(query)) || - (customLabel != null && customLabel.contains(query))) { + if ((label != null && label.contains(query)) + || (customLabel != null && customLabel.contains(query))) { results.add(buildSearchResultForInput(input.getId())); if (results.size() >= limit) { if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for" + - " searching inputs: " + (SystemClock.elapsedRealtime() - time) + - "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " inputs. Elapsed time for" + + " searching inputs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } } } if (DEBUG) { - Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for searching" + - " inputs: " + (SystemClock.elapsedRealtime() - time) + "(msec)"); + Log.d( + TAG, + "Found " + + results.size() + + " inputs. Elapsed time for searching" + + " inputs: " + + (SystemClock.elapsedRealtime() - time) + + "(msec)"); } return results; } @@ -513,14 +588,16 @@ public class TvProviderSearch implements SearchInterface { private long getMaxWatchStartTime(long channelId) { Uri uri = WatchedPrograms.CONTENT_URI; - String[] projections = new String[] { - "MAX(" + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS - + ") AS max_watch_start_time" - }; + String[] projections = + new String[] { + "MAX(" + + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS + + ") AS max_watch_start_time" + }; String selection = WatchedPrograms.COLUMN_CHANNEL_ID + "=?"; - String[] selectionArgs = new String[] { Long.toString(channelId) }; - try (Cursor c = mContentResolver.query(uri, projections, selection, selectionArgs, - null)) { + String[] selectionArgs = new String[] {Long.toString(channelId)}; + try (Cursor c = + mContentResolver.query(uri, projections, selection, selectionArgs, null)) { if (c != null && c.moveToNext()) { return c.getLong(0); } diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java index 7e627410..df25afa6 100644 --- a/src/com/android/tv/setup/SystemSetupActivity.java +++ b/src/com/android/tv/setup/SystemSetupActivity.java @@ -24,27 +24,21 @@ import android.content.Intent; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.widget.Toast; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.SetupPassthroughActivity; +import com.android.tv.TvApplication; import com.android.tv.common.TvCommonUtils; import com.android.tv.common.ui.setup.SetupActivity; -import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.TvApplication; -import com.android.tv.data.ChannelDataManager; import com.android.tv.onboarding.SetupSourcesFragment; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; -/** - * A activity to start input sources setup fragment for initial setup flow. - */ +/** 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 = "com.android.tv.action.LAUNCH_SYSTEM_SETUP"; private static final int SHOW_RIPPLE_DURATION_MS = 266; private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1; @@ -68,12 +62,14 @@ public class SystemSetupActivity extends SetupActivity { } private void showMerchantCollection() { - executeActionWithDelay(new Runnable() { - @Override - public void run() { - startActivity(OnboardingUtils.ONLINE_STORE_INTENT); - } - }, SHOW_RIPPLE_DURATION_MS); + executeActionWithDelay( + new Runnable() { + @Override + public void run() { + startActivity(OnboardingUtils.ONLINE_STORE_INTENT); + } + }, + SHOW_RIPPLE_DURATION_MS); } @Override @@ -84,38 +80,50 @@ public class SystemSetupActivity extends SetupActivity { case SetupSourcesFragment.ACTION_ONLINE_STORE: showMerchantCollection(); return true; - case SetupSourcesFragment.ACTION_SETUP_INPUT: { - String inputId = params.getString( - SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - Intent intent = TvCommonUtils.createSetupIntent(input); - if (intent == null) { - Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT) - .show(); + case SetupSourcesFragment.ACTION_SETUP_INPUT: + { + String inputId = + params.getString( + SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); + TvInputInfo input = mInputManager.getTvInputInfo(inputId); + Intent intent = TvCommonUtils.createSetupIntent(input); + if (intent == null) { + Toast.makeText( + this, + R.string.msg_no_setup_activity, + Toast.LENGTH_SHORT) + .show(); + return true; + } + // Even though other app can handle the intent, the setup launched by + // Live + // channels should go through Live channels SetupPassthroughActivity. + intent.setComponent( + new ComponentName(this, SetupPassthroughActivity.class)); + try { + // Now we know that the user intends to set up this input. Grant + // permission for writing EPG data. + SetupUtils.grantEpgPermission( + this, input.getServiceInfo().packageName); + startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); + } catch (ActivityNotFoundException e) { + Toast.makeText( + this, + getString( + R.string.msg_unable_to_start_setup_activity, + input.loadLabel(this)), + Toast.LENGTH_SHORT) + .show(); + } return true; } - // Even though other app can handle the intent, the setup launched by Live - // channels should go through Live channels SetupPassthroughActivity. - intent.setComponent(new ComponentName(this, - SetupPassthroughActivity.class)); - try { - // Now we know that the user intends to set up this input. Grant - // permission for writing EPG data. - SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName); - startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, - getString(R.string.msg_unable_to_start_setup_activity, - input.loadLabel(this)), Toast.LENGTH_SHORT).show(); + case SetupMultiPaneFragment.ACTION_DONE: + { + // To make sure user can finish setup flow, set result as RESULT_OK. + setResult(Activity.RESULT_OK); + finish(); + return true; } - return true; - } - case SetupMultiPaneFragment.ACTION_DONE: { - // To make sure user can finish setup flow, set result as RESULT_OK. - setResult(Activity.RESULT_OK); - finish(); - return true; - } } break; } diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java index a255de3e..50153f89 100644 --- a/src/com/android/tv/tuner/ChannelScanFileParser.java +++ b/src/com/android/tv/tuner/ChannelScanFileParser.java @@ -17,9 +17,7 @@ package com.android.tv.tuner; import android.util.Log; - import com.android.tv.tuner.data.nano.Channel; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -27,9 +25,7 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; -/** - * Parses plain text formatted scan files, which contain the list of channels. - */ +/** Parses plain text formatted scan files, which contain the list of channels. */ public class ChannelScanFileParser { private static final String TAG = "ChannelScanFileParser"; @@ -40,22 +36,26 @@ public class ChannelScanFileParser { public final String filename; /** * Radio frequency (channel) number specified at - * https://en.wikipedia.org/wiki/North_American_television_frequencies - * This can be {@code null} for cases like cable signal. + * https://en.wikipedia.org/wiki/North_American_television_frequencies This can be {@code + * null} for cases like cable signal. */ public final Integer radioFrequencyNumber; - public static ScanChannel forTuner(int frequency, String modulation, - Integer radioFrequencyNumber) { - return new ScanChannel(Channel.TYPE_TUNER, frequency, modulation, null, - radioFrequencyNumber); + public static ScanChannel forTuner( + int frequency, String modulation, Integer radioFrequencyNumber) { + return new ScanChannel( + Channel.TYPE_TUNER, frequency, modulation, null, radioFrequencyNumber); } public static ScanChannel forFile(int frequency, String filename) { return new ScanChannel(Channel.TYPE_FILE, frequency, "file:", filename, null); } - private ScanChannel(int type, int frequency, String modulation, String filename, + private ScanChannel( + int type, + int frequency, + String modulation, + String filename, Integer radioFrequencyNumber) { this.type = type; this.frequency = frequency; @@ -68,9 +68,9 @@ public class ChannelScanFileParser { /** * Parses a given scan file and returns the list of {@link ScanChannel} objects. * - * @param is {@link InputStream} of a scan file. Each line matches one channel. - * The line format of the scan file is as follows:
- * "A <frequency> <modulation>". + * @param is {@link InputStream} of a scan file. Each line matches one channel. The line format + * of the scan file is as follows:
+ * "A <frequency> <modulation>". * @return a list of {@link ScanChannel} objects parsed */ public static List parseScanFile(InputStream is) { @@ -90,8 +90,11 @@ public class ChannelScanFileParser { if (tokens.length != 3 && tokens.length != 4) { continue; } - scanChannelList.add(ScanChannel.forTuner(Integer.parseInt(tokens[1]), tokens[2], - tokens.length == 4 ? Integer.parseInt(tokens[3]) : null)); + scanChannelList.add( + ScanChannel.forTuner( + Integer.parseInt(tokens[1]), + tokens[2], + tokens.length == 4 ? Integer.parseInt(tokens[3]) : null)); } } catch (IOException e) { Log.e(TAG, "error on parseScanFile()", e); diff --git a/src/com/android/tv/tuner/DvbDeviceAccessor.java b/src/com/android/tv/tuner/DvbDeviceAccessor.java index 4f5d8ee4..217433d2 100644 --- a/src/com/android/tv/tuner/DvbDeviceAccessor.java +++ b/src/com/android/tv/tuner/DvbDeviceAccessor.java @@ -22,10 +22,7 @@ import android.os.ParcelFileDescriptor; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.util.Log; - import com.android.tv.common.recording.RecordingCapability; -import com.android.tv.tuner.tvinput.TunerTvInputService; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.InvocationTargetException; @@ -35,15 +32,14 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -/** - * Provides with the file descriptors to access DVB device. - */ +/** Provides with the file descriptors to access DVB device. */ public class DvbDeviceAccessor { private static final String TAG = "DvbDeviceAccessor"; @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND}) @Retention(RetentionPolicy.SOURCE) public @interface DvbDevice {} + public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX; public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR; public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND; @@ -59,8 +55,9 @@ public class DvbDeviceAccessor { Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo"); sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList"); sGetDvbDeviceListMethod.setAccessible(true); - sOpenDvbDeviceMethod = tvInputManagerClass.getDeclaredMethod( - "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE); + sOpenDvbDeviceMethod = + tvInputManagerClass.getDeclaredMethod( + "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE); sOpenDvbDeviceMethod.setAccessible(true); } catch (ClassNotFoundException e) { Log.e(TAG, "Couldn't find class", e); @@ -90,9 +87,7 @@ public class DvbDeviceAccessor { return null; } - /** - * Returns the number of currently connected DVB devices. - */ + /** Returns the number of currently connected DVB devices. */ public int getNumOfDvbDevices() { List dvbDeviceList = getDvbDeviceList(); return dvbDeviceList == null ? 0 : dvbDeviceList.size(); @@ -110,11 +105,12 @@ public class DvbDeviceAccessor { return false; } - public ParcelFileDescriptor openDvbDevice(DvbDeviceInfoWrapper deviceInfo, - @DvbDevice int device) { + public ParcelFileDescriptor openDvbDevice( + DvbDeviceInfoWrapper deviceInfo, @DvbDevice int device) { try { - return (ParcelFileDescriptor) sOpenDvbDeviceMethod.invoke( - mTvInputManager, deviceInfo.getDvbDeviceInfo(), device); + return (ParcelFileDescriptor) + sOpenDvbDeviceMethod.invoke( + mTvInputManager, deviceInfo.getDvbDeviceInfo(), device); } catch (IllegalAccessException e) { Log.e(TAG, "Couldn't access", e); } catch (InvocationTargetException e) { @@ -125,6 +121,7 @@ public class DvbDeviceAccessor { /** * Returns the current recording capability for USB tuner. + * * @param inputId the input id to use. */ public RecordingCapability getRecordingCapability(String inputId) { @@ -215,7 +212,9 @@ public class DvbDeviceAccessor { @Override public String toString() { - return String.format(Locale.US, "DvbDeviceInfo {adapterId: %d, deviceId: %d}", + return String.format( + Locale.US, + "DvbDeviceInfo {adapterId: %d, deviceId: %d}", getAdapterId(), getDeviceId()); } diff --git a/src/com/android/tv/tuner/DvbTunerHal.java b/src/com/android/tv/tuner/DvbTunerHal.java index ea977230..8397400f 100644 --- a/src/com/android/tv/tuner/DvbTunerHal.java +++ b/src/com/android/tv/tuner/DvbTunerHal.java @@ -20,14 +20,11 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.util.Log; import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper; - import java.util.List; import java.util.SortedSet; import java.util.TreeSet; -/** - * A class to handle a hardware Linux DVB API supported tuner device. - */ +/** A class to handle a hardware Linux DVB API supported tuner device. */ public class DvbTunerHal extends TunerHal { private static final Object sLock = new Object(); @@ -133,8 +130,9 @@ public class DvbTunerHal extends TunerHal { @Override protected int openDvbFrontEndFd() { if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND); + ParcelFileDescriptor descriptor = + mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND); if (descriptor != null) { return descriptor.detachFd(); } @@ -145,8 +143,9 @@ public class DvbTunerHal extends TunerHal { @Override protected int openDvbDemuxFd() { if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX); + ParcelFileDescriptor descriptor = + mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX); if (descriptor != null) { return descriptor.detachFd(); } @@ -157,8 +156,9 @@ public class DvbTunerHal extends TunerHal { @Override protected int openDvbDvrFd() { if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR); + ParcelFileDescriptor descriptor = + mDvbDeviceAccessor.openDvbDevice( + mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR); if (descriptor != null) { return descriptor.detachFd(); } @@ -166,9 +166,7 @@ public class DvbTunerHal extends TunerHal { return -1; } - /** - * Gets the number of USB tuner devices currently present. - */ + /** Gets the number of USB tuner devices currently present. */ public static int getNumberOfDevices(Context context) { try { return (new DvbDeviceAccessor(context)).getNumOfDvbDevices(); diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java index 1176cdf0..c8db73c3 100644 --- a/src/com/android/tv/tuner/TunerHal.java +++ b/src/com/android/tv/tuner/TunerHal.java @@ -22,40 +22,44 @@ import android.support.annotation.StringDef; import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Pair; - -import com.android.tv.Features; import com.android.tv.customization.TvCustomizationManager; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -/** - * A base class to handle a hardware tuner device. - */ +/** A base class to handle a hardware tuner device. */ public abstract class TunerHal implements AutoCloseable { protected static final String TAG = "TunerHal"; protected static final boolean DEBUG = false; - @IntDef({ FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR }) + @IntDef({FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR}) @Retention(RetentionPolicy.SOURCE) public @interface FilterType {} + public static final int FILTER_TYPE_OTHER = 0; public static final int FILTER_TYPE_AUDIO = 1; public static final int FILTER_TYPE_VIDEO = 2; public static final int FILTER_TYPE_PCR = 3; - @StringDef({ MODULATION_8VSB, MODULATION_QAM256 }) + @StringDef({MODULATION_8VSB, MODULATION_QAM256}) @Retention(RetentionPolicy.SOURCE) public @interface ModulationType {} + public static final String MODULATION_8VSB = "8VSB"; public static final String MODULATION_QAM256 = "QAM256"; - @IntDef({ DELIVERY_SYSTEM_UNDEFINED, DELIVERY_SYSTEM_ATSC, DELIVERY_SYSTEM_DVBC, - DELIVERY_SYSTEM_DVBS, DELIVERY_SYSTEM_DVBS2, DELIVERY_SYSTEM_DVBT, - DELIVERY_SYSTEM_DVBT2 }) + @IntDef({ + DELIVERY_SYSTEM_UNDEFINED, + DELIVERY_SYSTEM_ATSC, + DELIVERY_SYSTEM_DVBC, + DELIVERY_SYSTEM_DVBS, + DELIVERY_SYSTEM_DVBS2, + DELIVERY_SYSTEM_DVBT, + DELIVERY_SYSTEM_DVBT2 + }) @Retention(RetentionPolicy.SOURCE) public @interface DeliverySystemType {} + public static final int DELIVERY_SYSTEM_UNDEFINED = 0; public static final int DELIVERY_SYSTEM_ATSC = 1; public static final int DELIVERY_SYSTEM_DVBC = 2; @@ -64,9 +68,10 @@ public abstract class TunerHal implements AutoCloseable { public static final int DELIVERY_SYSTEM_DVBT = 5; public static final int DELIVERY_SYSTEM_DVBT2 = 6; - @IntDef({ TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK }) + @IntDef({TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK}) @Retention(RetentionPolicy.SOURCE) public @interface TunerType {} + public static final int TUNER_TYPE_BUILT_IN = 1; public static final int TUNER_TYPE_USB = 2; public static final int TUNER_TYPE_NETWORK = 3; @@ -77,12 +82,11 @@ public abstract class TunerHal implements AutoCloseable { protected static final int PID_DVB_EIT = 0x0012; protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000; protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for - // QAM256 tuning. - @IntDef({ - BUILT_IN_TUNER_TYPE_LINUX_DVB - }) + // QAM256 tuning. + @IntDef({BUILT_IN_TUNER_TYPE_LINUX_DVB}) @Retention(RetentionPolicy.SOURCE) private @interface BuiltInTunerType {} + private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1; private static Integer sBuiltInTunerType; @@ -98,11 +102,12 @@ public abstract class TunerHal implements AutoCloseable { /** * Creates a TunerHal instance. + * * @param context context for creating the TunerHal instance * @return the TunerHal instance */ @WorkerThread - public synchronized static TunerHal createInstance(Context context) { + public static synchronized TunerHal createInstance(Context context) { TunerHal tunerHal = null; if (DvbTunerHal.getNumberOfDevices(context) > 0) { if (DEBUG) Log.d(TAG, "Use DvbTunerHal"); @@ -111,9 +116,7 @@ public abstract class TunerHal implements AutoCloseable { return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null; } - /** - * Gets the number of tuner devices currently present. - */ + /** Gets the number of tuner devices currently present. */ @WorkerThread public static Pair getTunerTypeAndCount(Context context) { if (useBuiltInTuner(context)) { @@ -129,9 +132,7 @@ public abstract class TunerHal implements AutoCloseable { return new Pair<>(null, 0); } - /** - * Check a delivery system is for DVB or not. - */ + /** Check a delivery system is for DVB or not. */ public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) { return deliverySystemType == DELIVERY_SYSTEM_DVBC || deliverySystemType == DELIVERY_SYSTEM_DVBS @@ -176,8 +177,8 @@ public abstract class TunerHal implements AutoCloseable { } /** - * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels - * of the same frequency. + * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels of + * the same frequency. */ public boolean isReusable() { return true; @@ -208,12 +209,12 @@ public abstract class TunerHal implements AutoCloseable { * * @param frequency a frequency of the channel to tune to * @param modulation a modulation method of the channel to tune to - * @param channelNumber channel number when channel number is already known. Some tuner HAL - * may use channelNumber instead of frequency for tune. + * @param channelNumber channel number when channel number is already known. Some tuner HAL may + * use channelNumber instead of frequency for tune. * @return {@code true} if the operation was successful, {@code false} otherwise */ - public synchronized boolean tune(int frequency, @ModulationType String modulation, - String channelNumber) { + public synchronized boolean tune( + int frequency, @ModulationType String modulation, String channelNumber) { if (!isDeviceOpen()) { Log.e(TAG, "There's no available device"); return false; @@ -235,8 +236,10 @@ public abstract class TunerHal implements AutoCloseable { mIsStreaming = true; return true; } - int timeout_ms = modulation.equals(MODULATION_8VSB) ? DEFAULT_VSB_TUNE_TIMEOUT_MS - : DEFAULT_QAM_TUNE_TIMEOUT_MS; + int timeout_ms = + modulation.equals(MODULATION_8VSB) + ? DEFAULT_VSB_TUNE_TIMEOUT_MS + : DEFAULT_QAM_TUNE_TIMEOUT_MS; if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) { addPidFilter(PID_PAT, FILTER_TYPE_OTHER); addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER); @@ -252,8 +255,8 @@ public abstract class TunerHal implements AutoCloseable { return false; } - protected native boolean nativeTune(long deviceId, int frequency, - @ModulationType String modulation, int timeout_ms); + protected native boolean nativeTune( + long deviceId, int frequency, @ModulationType String modulation, int timeout_ms); /** * Sets a pid filter. This should be set after setting a channel. @@ -275,8 +278,11 @@ public abstract class TunerHal implements AutoCloseable { } protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType); + protected native void nativeCloseAllPidFilters(long deviceId); + protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune); + protected native int nativeGetDeliverySystemType(long deviceId); /** @@ -306,15 +312,15 @@ public abstract class TunerHal implements AutoCloseable { protected native void nativeStopTune(long deviceId); /** - * This method must be called after {@link TunerHal#tune} and before - * {@link TunerHal#stopTune}. Writes at most maxSize TS frames in a buffer - * provided by the user. The frames employ MPEG encoding. + * This method must be called after {@link TunerHal#tune} and before {@link TunerHal#stopTune}. + * Writes at most maxSize TS frames in a buffer provided by the user. The frames employ MPEG + * encoding. * * @param javaBuffer a buffer to write the video data in * @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number - * should be equal to the length of the buffer. + * should be equal to the length of the buffer. * @return the amount of bytes written in the buffer. Note that this value could be 0 if no new - * frames have been obtained since the last call. + * frames have been obtained since the last call. */ public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) { if (isDeviceOpen()) { diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java index e06b9b4a..829bec1c 100644 --- a/src/com/android/tv/tuner/TunerInputController.java +++ b/src/com/android/tv/tuner/TunerInputController.java @@ -38,7 +38,6 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; - import com.android.tv.Features; import com.android.tv.R; import com.android.tv.TvApplication; @@ -47,19 +46,17 @@ import com.android.tv.tuner.setup.TunerSetupActivity; import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.tuner.util.SystemPropertiesProxy; import com.android.tv.tuner.util.TunerInputInfoUtils; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; /** * Controls the package visibility of {@link TunerTvInputService}. - *

- * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, - * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} - * to update the connection status of the supported USB TV tuners. + * + *

Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, {@code + * UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} to + * update the connection status of the supported USB TV tuners. */ public class TunerInputController { private static final boolean DEBUG = true; @@ -68,9 +65,7 @@ public class TunerInputController { private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch"; private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd"; - /** - * Action of {@link Intent} to check network connection repeatedly when it is necessary. - */ + /** Action of {@link Intent} to check network connection repeatedly when it is necessary. */ private static final String CHECKING_NETWORK_CONNECTION = "com.android.tv.action.CHECKING_NETWORK_CONNECTION"; @@ -92,15 +87,13 @@ public class TunerInputController { private static final int MSG_ENABLE_INPUT_SERVICE = 1000; private static final long DVB_DRIVER_CHECK_DELAY_MS = 300; - /** - * Checks status of USB devices to see if there are available USB tuners connected. - */ + /** Checks status of USB devices to see if there are available USB tuners connected. */ public static void onCheckingUsbTunerStatus(Context context, String action) { onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler()); } - private static void onCheckingUsbTunerStatus(Context context, String action, - @NonNull CheckDvbDeviceHandler handler) { + private static void onCheckingUsbTunerStatus( + Context context, String action, @NonNull CheckDvbDeviceHandler handler) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); if (TunerHal.useBuiltInTuner(context)) { @@ -114,7 +107,8 @@ public class TunerInputController { // Need to check if DVB driver is accessible. Since the driver creation // could be happen after the USB event, delay the checking by // DVB_DRIVER_CHECK_DELAY_MS. - handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), + handler.sendMessageDelayed( + handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), DVB_DRIVER_CHECK_DELAY_MS); } else { if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) { @@ -123,9 +117,13 @@ public class TunerInputController { TunerInputInfoUtils.updateTunerInputInfo(context); return; } - enableTunerTvInputService(context, false, false, TextUtils - .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ? - TunerHal.TUNER_TYPE_USB : null); + enableTunerTvInputService( + context, + false, + false, + TextUtils.equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) + ? TunerHal.TUNER_TYPE_USB + : null); } } @@ -138,9 +136,11 @@ public class TunerInputController { sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply(); enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK); } else { - sharedPreferences.edit() - .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply(); - if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) { + sharedPreferences + .edit() + .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false) + .apply(); + if (!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) { // Network tuner detection is initiated by UI. So the app should not // be killed. enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK); @@ -184,24 +184,30 @@ public class TunerInputController { * @param context {@link Context} instance * @param enabled {@code true} to enable the service; otherwise {@code false} */ - private static void enableTunerTvInputService(Context context, boolean enabled, - boolean forceDontKillApp, Integer tunerType) { + private static void enableTunerTvInputService( + Context context, boolean enabled, boolean forceDontKillApp, Integer tunerType) { if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled); - PackageManager pm = context.getPackageManager(); + PackageManager pm = context.getPackageManager(); ComponentName componentName = new ComponentName(context, TunerTvInputService.class); // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling // TvActivity, the following pm.setComponentEnabledSetting doesn't work. - ((TvApplication) context.getApplicationContext()).handleInputCountChanged( - true, enabled, true); + ((TvApplication) context.getApplicationContext()) + .handleInputCountChanged(true, enabled, true); // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only // when the LiveChannels app is active since we don't want to kill the running app. - int flags = forceDontKillApp - || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated() - ? PackageManager.DONT_KILL_APP : 0; - int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + int flags = + forceDontKillApp + || TvApplication.getSingletons(context) + .getMainActivityWrapper() + .isCreated() + ? PackageManager.DONT_KILL_APP + : 0; + int newState = + enabled + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; if (newState != pm.getComponentEnabledSetting(componentName)) { // Send/cancel the USB tuner TV input setup notification. TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType); @@ -209,11 +215,14 @@ public class TunerInputController { pm.setComponentEnabledSetting(componentName, newState, flags); if (!enabled && tunerType != null) { if (tunerType == TunerHal.TUNER_TYPE_USB) { - Toast.makeText(context, R.string.msg_usb_tuner_disconnected, - Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.msg_usb_tuner_disconnected, Toast.LENGTH_SHORT) + .show(); } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) { - Toast.makeText(context, R.string.msg_network_tuner_disconnected, - Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + R.string.msg_network_tuner_disconnected, + Toast.LENGTH_SHORT) + .show(); } } if (DEBUG) Log.d(TAG, "Status updated:" + enabled); @@ -239,13 +248,14 @@ public class TunerInputController { /** * Discovers a network tuner. + * * @param context {@link Context} * @param repeatedDurationMs the time length to wait to repeatedly check network status to start - * finding network tuner when the network connection is not available. - * {@code 0} to disable repeatedly checking. + * finding network tuner when the network connection is not available. {@code 0} to disable + * repeatedly checking. */ - private static void executeNetworkTunerDiscoveryAsyncTask(final Context context, - final long repeatedDurationMs) { + private static void executeNetworkTunerDiscoveryAsyncTask( + final Context context, final long repeatedDurationMs) { if (!Features.NETWORK_TUNER.isEnabled(context)) { return; } @@ -260,10 +270,16 @@ public class TunerInputController { Intent networkCheckingIntent = new Intent(context, IntentReceiver.class); networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION); networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs); - PendingIntent alarmIntent = PendingIntent.getBroadcast( - context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT); - alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() - + repeatedDurationMs, alarmIntent); + PendingIntent alarmIntent = + PendingIntent.getBroadcast( + context, + 0, + networkCheckingIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + repeatedDurationMs, + alarmIntent); } return null; } @@ -279,8 +295,8 @@ public class TunerInputController { } private static boolean isNetworkConnected(Context context) { - ConnectivityManager cm = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); return networkInfo != null && networkInfo.isConnected(); } @@ -305,9 +321,11 @@ public class TunerInputController { onCheckingUsbTunerStatus(context, intent.getAction(), mHandler); break; case CHECKING_NETWORK_CONNECTION: - long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION, - INITIAL_CHECKING_DURATION_MS); - executeNetworkTunerDiscoveryAsyncTask(context, + long repeatedDurationMs = + intent.getLongExtra( + EXTRA_CHECKING_DURATION, INITIAL_CHECKING_DURATION_MS); + executeNetworkTunerDiscoveryAsyncTask( + context, Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS)); break; } @@ -315,8 +333,8 @@ public class TunerInputController { } /** - * Simple data holder for a USB device. Used to represent a tuner model, and compare - * against {@link UsbDevice}. + * Simple data holder for a USB device. Used to represent a tuner model, and compare against + * {@link UsbDevice}. */ private static class TunerDevice { private final int vendorId; diff --git a/src/com/android/tv/tuner/TunerPreferenceProvider.java b/src/com/android/tv/tuner/TunerPreferenceProvider.java index 3a3561b6..425c30ac 100644 --- a/src/com/android/tv/tuner/TunerPreferenceProvider.java +++ b/src/com/android/tv/tuner/TunerPreferenceProvider.java @@ -54,31 +54,23 @@ public class TunerPreferenceProvider extends ContentProvider { /** * Builds a Uri that points to a specific preference. - + * * @param key a key of the preference to point to */ public static Uri buildPreferenceUri(String key) { return Preferences.CONTENT_URI.buildUpon().appendPath(key).build(); } - /** - * Columns definitions for the preferences table. - */ + /** Columns definitions for the preferences table. */ public interface Preferences { - /** - * The content:// style for the preferences table. - */ + /** The content:// style for the preferences table. */ Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES); - /** - * The MIME type of a directory of preferences. - */ + /** The MIME type of a directory of preferences. */ String CONTENT_TYPE = "vnd.android.cursor.dir/preferences"; - /** - * The MIME type of a single preference. - */ + /** The MIME type of a single preference. */ String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences"; /** @@ -114,10 +106,16 @@ public class TunerPreferenceProvider extends ContentProvider { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + PREFERENCES_TABLE + " (" - + Preferences._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Preferences.COLUMN_KEY + " TEXT NOT NULL," - + Preferences.COLUMN_VALUE + " TEXT);"); + db.execSQL( + "CREATE TABLE " + + PREFERENCES_TABLE + + " (" + + Preferences._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Preferences.COLUMN_KEY + + " TEXT NOT NULL," + + Preferences.COLUMN_VALUE + + " TEXT);"); } @Override @@ -133,15 +131,26 @@ public class TunerPreferenceProvider extends ContentProvider { } @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, String sortOrder) { int match = sUriMatcher.match(uri); if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) { throw new UnsupportedOperationException(); } SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase(); - Cursor cursor = db.query(PREFERENCES_TABLE, projection, selection, selectionArgs, - null, null, sortOrder); + Cursor cursor = + db.query( + PREFERENCES_TABLE, + projection, + selection, + selectionArgs, + null, + null, + sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @@ -160,7 +169,7 @@ public class TunerPreferenceProvider extends ContentProvider { /** * Inserts a preference row into the preference table. * - * If a key is already exists in the table, it removes the old row and inserts a new row. + *

If a key is already exists in the table, it removes the old row and inserts a new row. * * @param uri the URL of the table to insert into * @param values the initial values for the newly inserted row @@ -178,8 +187,10 @@ public class TunerPreferenceProvider extends ContentProvider { SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase(); // Remove the old row. - db.delete(PREFERENCES_TABLE, Preferences.COLUMN_KEY + " like ?", - new String[]{values.getAsString(Preferences.COLUMN_KEY)}); + db.delete( + PREFERENCES_TABLE, + Preferences.COLUMN_KEY + " like ?", + new String[] {values.getAsString(Preferences.COLUMN_KEY)}); long rowId = db.insert(PREFERENCES_TABLE, null, values); if (rowId > 0) { diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java index 11a6a969..62a4ce99 100644 --- a/src/com/android/tv/tuner/TunerPreferences.java +++ b/src/com/android/tv/tuner/TunerPreferences.java @@ -28,16 +28,13 @@ import android.os.Handler; import android.support.annotation.GuardedBy; import android.support.annotation.IntDef; import android.support.annotation.MainThread; - import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.TunerPreferenceProvider.Preferences; import com.android.tv.tuner.util.TisConfiguration; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * A helper class for the USB tuner preferences. - */ +/** A helper class for the USB tuner preferences. */ public class TunerPreferences { private static final String TAG = "TunerPreferences"; @@ -56,42 +53,32 @@ public class TunerPreferences { @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED}) @Retention(RetentionPolicy.SOURCE) - public @interface TrickplaySetting { - } + public @interface TrickplaySetting {} - /** - * Trickplay setting is not changed by a user. Trickplay will be enabled in this case. - */ + /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */ public static final int TRICKPLAY_SETTING_NOT_SET = -1; - /** - * Trickplay setting is disabled. - */ + /** Trickplay setting is disabled. */ public static final int TRICKPLAY_SETTING_DISABLED = 0; - /** - * Trickplay setting is enabled. - */ + /** Trickplay setting is enabled. */ public static final int TRICKPLAY_SETTING_ENABLED = 1; @GuardedBy("TunerPreferences.class") private static final Bundle sPreferenceValues = new Bundle(); + private static LoadPreferencesTask sLoadPreferencesTask; private static ContentObserver sContentObserver; private static TunerPreferencesChangedListener sPreferencesChangedListener = null; private static boolean sInitialized; - /** - * Listeners for TunerPreferences change. - */ + /** Listeners for TunerPreferences change. */ public interface TunerPreferencesChangedListener { void onTunerPreferencesChanged(); } - /** - * Initializes the USB tuner preferences. - */ + /** Initializes the USB tuner preferences. */ @MainThread public static void initialize(final Context context) { if (sInitialized) { @@ -100,14 +87,18 @@ public class TunerPreferences { sInitialized = true; if (useContentProvider(context)) { loadPreferences(context); - sContentObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - loadPreferences(context); - } - }; - context.getContentResolver().registerContentObserver( - TunerPreferenceProvider.Preferences.CONTENT_URI, true, sContentObserver); + sContentObserver = + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + loadPreferences(context); + } + }; + context.getContentResolver() + .registerContentObserver( + TunerPreferenceProvider.Preferences.CONTENT_URI, + true, + sContentObserver); } else { new AsyncTask() { @Override @@ -119,9 +110,7 @@ public class TunerPreferences { } } - /** - * Releases the resources. - */ + /** Releases the resources. */ public static synchronized void release(Context context) { if (useContentProvider(context) && sContentObserver != null) { context.getContentResolver().unregisterContentObserver(sContentObserver); @@ -129,9 +118,7 @@ public class TunerPreferences { setTunerPreferencesChangedListener(null); } - /** - * Sets the listener for TunerPreferences change. - */ + /** Sets the listener for TunerPreferences change. */ public static void setTunerPreferencesChangedListener( TunerPreferencesChangedListener listener) { sPreferencesChangedListener = listener; @@ -139,9 +126,9 @@ public class TunerPreferences { /** * Loads the preferences from database. - *

- * This preferences is used across processes, so the preferences should be loaded again when the - * databases changes. + * + *

This preferences is used across processes, so the preferences should be loaded again when + * the databases changes. */ @MainThread public static void loadPreferences(Context context) { @@ -161,11 +148,12 @@ public class TunerPreferences { public static synchronized int getChannelDataVersion(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { - return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION, - CHANNEL_DATA_VERSION_NOT_SET); + return sPreferenceValues.getInt( + PREFS_KEY_CHANNEL_DATA_VERSION, CHANNEL_DATA_VERSION_NOT_SET); } else { return getSharedPreferences(context) - .getInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, + .getInt( + TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, CHANNEL_DATA_VERSION_NOT_SET); } } @@ -174,7 +162,8 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, version) .apply(); } @@ -194,7 +183,8 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount) .apply(); } @@ -213,8 +203,10 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); } else { - getSharedPreferences(context).edit() - .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode).apply(); + getSharedPreferences(context) + .edit() + .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode) + .apply(); } } @@ -232,7 +224,8 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCAN_DONE, true); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, true) .apply(); } @@ -252,7 +245,8 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, need) .apply(); } @@ -272,32 +266,36 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs) .apply(); } } - public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { + public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); } else { return getSharedPreferences(context) - .getInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); + .getInt( + TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, + TRICKPLAY_SETTING_NOT_SET); } } - public static synchronized void setTrickplaySetting(Context context, - @TrickplaySetting int trickplaySetting) { + public static synchronized void setTrickplaySetting( + Context context, @TrickplaySetting int trickplaySetting) { SoftPreconditions.checkState(sInitialized); SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); if (useContentProvider(context)) { setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); } else { - getSharedPreferences(context).edit() - .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) - .apply(); + getSharedPreferences(context) + .edit() + .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) + .apply(); } } @@ -315,7 +313,8 @@ public class TunerPreferences { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); } else { - getSharedPreferences(context).edit() + getSharedPreferences(context) + .edit() .putBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, shouldStore) .apply(); } @@ -335,7 +334,7 @@ public class TunerPreferences { savePreference(context, key, Integer.toString(value)); } - private static synchronized void setPreference(Context context, String key, long value) { + private static synchronized void setPreference(Context context, String key, long value) { sPreferenceValues.putLong(key, value); savePreference(context, key, Long.toString(value)); } @@ -345,8 +344,8 @@ public class TunerPreferences { savePreference(context, key, Boolean.toString(value)); } - private static void savePreference(final Context context, final String key, - final String value) { + private static void savePreference( + final Context context, final String key, final String value) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -357,8 +356,8 @@ public class TunerPreferences { try { resolver.insert(Preferences.CONTENT_URI, values); } catch (Exception e) { - SoftPreconditions.warn(TAG, "setPreference", "Error writing preference values", - e); + SoftPreconditions.warn( + TAG, "setPreference", "Error writing preference values", e); } return null; } @@ -367,6 +366,7 @@ public class TunerPreferences { private static class LoadPreferencesTask extends AsyncTask { private final Context mContext; + private LoadPreferencesTask(Context context) { mContext = context; } @@ -375,9 +375,9 @@ public class TunerPreferences { protected Bundle doInBackground(Void... params) { Bundle bundle = new Bundle(); ContentResolver resolver = mContext.getContentResolver(); - String[] projection = new String[] { Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE }; - try (Cursor cursor = resolver.query(Preferences.CONTENT_URI, projection, null, null, - null)) { + String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE}; + try (Cursor cursor = + resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) { if (cursor != null) { while (!isCancelled() && cursor.moveToNext()) { String key = cursor.getString(0); @@ -425,4 +425,4 @@ public class TunerPreferences { } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java index a88538df..eb9ad463 100644 --- a/src/com/android/tv/tuner/cc/CaptionLayout.java +++ b/src/com/android/tv/tuner/cc/CaptionLayout.java @@ -18,13 +18,12 @@ package com.android.tv.tuner.cc; import android.content.Context; import android.util.AttributeSet; - import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.layout.ScaledLayout; /** - * Layout containing the safe title area that helps the closed captions look more prominent. - * This is required by CEA-708B. + * Layout containing the safe title area that helps the closed captions look more prominent. This is + * required by CEA-708B. */ public class CaptionLayout extends ScaledLayout { // The safe title area has 10% margins of the screen. @@ -47,13 +46,15 @@ public class CaptionLayout extends ScaledLayout { public CaptionLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mSafeTitleAreaLayout = new ScaledLayout(context); - addView(mSafeTitleAreaLayout, new ScaledLayoutParams( - SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X, - SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y)); + addView( + mSafeTitleAreaLayout, + new ScaledLayoutParams( + SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X, + SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y)); } - public void addOrUpdateViewToSafeTitleArea(CaptionWindowLayout captionWindowLayout, - ScaledLayoutParams scaledLayoutParams) { + public void addOrUpdateViewToSafeTitleArea( + CaptionWindowLayout captionWindowLayout, ScaledLayoutParams scaledLayoutParams) { int index = mSafeTitleAreaLayout.indexOfChild(captionWindowLayout); if (index < 0) { mSafeTitleAreaLayout.addView(captionWindowLayout, scaledLayoutParams); diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java index 24a0f354..bcb8e1c0 100644 --- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java +++ b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java @@ -20,7 +20,6 @@ import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; - import com.android.tv.tuner.data.Cea708Data.CaptionEvent; import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; @@ -28,13 +27,10 @@ import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation; import com.android.tv.tuner.data.Cea708Data.CaptionWindow; import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; - import java.util.ArrayList; import java.util.concurrent.TimeUnit; -/** - * Decodes and renders CEA-708. - */ +/** Decodes and renders CEA-708. */ public class CaptionTrackRenderer implements Handler.Callback { // TODO: Remaining works // CaptionTrackRenderer does not support the full spec of CEA-708. The remaining works are @@ -291,8 +287,8 @@ public class CaptionTrackRenderer implements Handler.Callback { return; } mIsDelayed = true; - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DELAY_CANCEL), - tenthsOfSeconds * DELAY_IN_MILLIS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_DELAY_CANCEL), tenthsOfSeconds * DELAY_IN_MILLIS); } private void delayCancel() { @@ -318,8 +314,8 @@ public class CaptionTrackRenderer implements Handler.Callback { if (mCurrentWindowLayout != null) { mCurrentWindowLayout.sendBuffer(buffer); mHandler.removeMessages(MSG_CAPTION_CLEAR); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CAPTION_CLEAR), - CAPTION_CLEAR_INTERVAL_MS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_CAPTION_CLEAR), CAPTION_CLEAR_INTERVAL_MS); } } diff --git a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java b/src/com/android/tv/tuner/cc/CaptionWindowLayout.java index 6f42b506..e9371f94 100644 --- a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java +++ b/src/com/android/tv/tuner/cc/CaptionWindowLayout.java @@ -39,15 +39,13 @@ import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import android.view.accessibility.CaptioningManager.CaptioningChangeListener; import android.widget.RelativeLayout; - -import com.google.android.exoplayer.text.CaptionStyleCompat; -import com.google.android.exoplayer.text.SubtitleView; import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; import com.android.tv.tuner.data.Cea708Data.CaptionWindow; import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; import com.android.tv.tuner.layout.ScaledLayout; - +import com.google.android.exoplayer.text.CaptionStyleCompat; +import com.google.android.exoplayer.text.SubtitleView; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -55,8 +53,8 @@ import java.util.Arrays; import java.util.List; /** - * Layout which renders a caption window of CEA-708B. It contains a {@link SubtitleView} that - * takes care of displaying the actual cc text. + * Layout which renders a caption window of CEA-708B. It contains a {@link SubtitleView} that takes + * care of displaying the actual cc text. */ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayoutChangeListener { private static final String TAG = "CaptionWindowLayout"; @@ -136,8 +134,9 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout // Add a subtitle view to the layout. mSubtitleView = new SubtitleView(context); - LayoutParams params = new RelativeLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + LayoutParams params = + new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); addView(mSubtitleView, params); // Set the system wide cc preferences to the subtitle view. @@ -241,12 +240,13 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout /** * This method places the window on a given CaptionLayout along with the anchor of the window. - *

- * According to CEA-708B, the anchor id indicates the gravity of the window as the follows. + * + *

According to CEA-708B, the anchor id indicates the gravity of the window as the follows. * For example, A value 7 of a anchor id says that a window is align with its parent bottom and * is located at the center horizontally of its parent. - *

+ * *

Anchor id and the gravity of a window

+ * * * * @@ -273,28 +273,29 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout * * *
GRAVITY8
- *

- * In order to handle the gravity of a window, there are two steps. First, set the size of the - * window. Since the window will be positioned at {@link ScaledLayout}, the size factors are + * + *

In order to handle the gravity of a window, there are two steps. First, set the size of + * the window. Since the window will be positioned at {@link ScaledLayout}, the size factors are * determined in a ratio. Second, set the gravity of the window. {@link CaptionWindowLayout} is * inherited from {@link RelativeLayout}. Hence, we could set the gravity of its child view, * {@link SubtitleView}. - *

- *

- * The gravity of the window is also related to its size. When it should be pushed to a one of - * the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point should be a boundary - * of the window. When it should be pushed in the horizontal/vertical center of its container, - * the horizontal/vertical center point of the window should be the same as the anchor point. - *

+ * + *

The gravity of the window is also related to its size. When it should be pushed to a one + * of the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point should be a + * boundary of the window. When it should be pushed in the horizontal/vertical center of its + * container, the horizontal/vertical center point of the window should be the same as the + * anchor point. * * @param captionLayout a given {@link CaptionLayout}, which contains a safe title area * @param captionWindow a given {@link CaptionWindow}, which stores the construction info of the - * window + * window */ public void initWindow(CaptionLayout captionLayout, CaptionWindow captionWindow) { if (DEBUG) { - Log.d(TAG, "initWindow with " - + (captionLayout != null ? captionLayout.getCaptionTrack() : null)); + Log.d( + TAG, + "initWindow with " + + (captionLayout != null ? captionLayout.getCaptionTrack() : null)); } if (mCaptionLayout != captionLayout) { if (mCaptionLayout != null) { @@ -306,23 +307,35 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout } // Both anchor vertical and horizontal indicates the position cell number of the window. - float scaleRow = (float) captionWindow.anchorVertical / (captionWindow.relativePositioning - ? ANCHOR_RELATIVE_POSITIONING_MAX : ANCHOR_VERTICAL_MAX); - float scaleCol = (float) captionWindow.anchorHorizontal / - (captionWindow.relativePositioning ? ANCHOR_RELATIVE_POSITIONING_MAX - : (isWideAspectRatio() - ? ANCHOR_HORIZONTAL_16_9_MAX : ANCHOR_HORIZONTAL_4_3_MAX)); + float scaleRow = + (float) captionWindow.anchorVertical + / (captionWindow.relativePositioning + ? ANCHOR_RELATIVE_POSITIONING_MAX + : ANCHOR_VERTICAL_MAX); + float scaleCol = + (float) captionWindow.anchorHorizontal + / (captionWindow.relativePositioning + ? ANCHOR_RELATIVE_POSITIONING_MAX + : (isWideAspectRatio() + ? ANCHOR_HORIZONTAL_16_9_MAX + : ANCHOR_HORIZONTAL_4_3_MAX)); // The range of scaleRow/Col need to be verified to be in [0, 1]. // Otherwise a {@link RuntimeException} will be raised in {@link ScaledLayout}. if (scaleRow < 0 || scaleRow > 1) { - Log.i(TAG, "The vertical position of the anchor point should be at the range of 0 and 1" - + " but " + scaleRow); + Log.i( + TAG, + "The vertical position of the anchor point should be at the range of 0 and 1" + + " but " + + scaleRow); scaleRow = Math.max(0, Math.min(scaleRow, 1)); } if (scaleCol < 0 || scaleCol > 1) { - Log.i(TAG, "The horizontal position of the anchor point should be at the range of 0 and" - + " 1 but " + scaleCol); + Log.i( + TAG, + "The horizontal position of the anchor point should be at the range of 0 and" + + " 1 but " + + scaleCol); scaleCol = Math.max(0, Math.min(scaleCol, 1)); } int gravity = Gravity.CENTER; @@ -356,8 +369,10 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout paint.setTypeface(mCaptionStyleCompat.typeface); paint.setTextSize(mTextSize); float maxWindowWidth = paint.measureText(widestTextBuilder.toString()); - float halfMaxWidthScale = mCaptionLayout.getWidth() > 0 - ? maxWindowWidth / 2.0f / (mCaptionLayout.getWidth() * 0.8f) : 0.0f; + float halfMaxWidthScale = + mCaptionLayout.getWidth() > 0 + ? maxWindowWidth / 2.0f / (mCaptionLayout.getWidth() * 0.8f) + : 0.0f; if (halfMaxWidthScale > 0f && halfMaxWidthScale < scaleCol) { // Calculate the expected max window size based on the column count of the // caption window multiplied by average alphabets char width, then align the @@ -403,8 +418,10 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout scaleEndRow = scaleRow; break; } - mCaptionLayout.addOrUpdateViewToSafeTitleArea(this, new ScaledLayout - .ScaledLayoutParams(scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); + mCaptionLayout.addOrUpdateViewToSafeTitleArea( + this, + new ScaledLayout.ScaledLayoutParams( + scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); setCaptionWindowId(captionWindow.id); setRowLimit(captionWindow.rowCount); setGravity(gravity); @@ -420,8 +437,16 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout } @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { + public void onLayoutChange( + View v, + int left, + int top, + int right, + int bottom, + int oldLeft, + int oldTop, + int oldRight, + int oldBottom) { int width = right - left; int height = bottom - top; if (width != mLastCaptionLayoutWidth || height != mLastCaptionLayoutHeight) { @@ -432,13 +457,15 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout } private boolean isKoreanLanguageTrack() { - return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null + return mCaptionLayout != null + && mCaptionLayout.getCaptionTrack() != null && mCaptionLayout.getCaptionTrack().language != null && "KOR".compareToIgnoreCase(mCaptionLayout.getCaptionTrack().language) == 0; } private boolean isWideAspectRatio() { - return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null + return mCaptionLayout != null + && mCaptionLayout.getCaptionTrack() != null && mCaptionLayout.getCaptionTrack().wideAspectRatio; } @@ -451,7 +478,7 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout Charset latin1 = Charset.forName("ISO-8859-1"); float widestCharWidth = 0f; for (int i = 0; i < 256; ++i) { - String ch = new String(new byte[]{(byte) i}, latin1); + String ch = new String(new byte[] {(byte) i}, latin1); float charWidth = paint.measureText(ch); if (widestCharWidth < charWidth) { widestCharWidth = charWidth; @@ -548,7 +575,10 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout int length = mBuilder.length(); mBuilder.append(text); for (CharacterStyle characterStyle : mCharacterStyles) { - mBuilder.setSpan(characterStyle, length, mBuilder.length(), + mBuilder.setSpan( + characterStyle, + length, + mBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } @@ -557,8 +587,8 @@ public class CaptionWindowLayout extends RelativeLayout implements View.OnLayout // Truncate text not to exceed the row limit. // Plus one here since the range of the rows is [0, mRowLimit]. int startRow = Math.max(0, lines.length - (mRowLimit + 1)); - String truncatedText = TextUtils.join("\n", Arrays.copyOfRange( - lines, startRow, lines.length)); + String truncatedText = + TextUtils.join("\n", Arrays.copyOfRange(lines, startRow, lines.length)); mBuilder.delete(0, mBuilder.length() - truncatedText.length()); mCurrentTextRow = lines.length - startRow - 1; diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java index d0f6cf11..4e080276 100644 --- a/src/com/android/tv/tuner/cc/Cea708Parser.java +++ b/src/com/android/tv/tuner/cc/Cea708Parser.java @@ -20,7 +20,6 @@ import android.os.SystemClock; import android.support.annotation.IntDef; import android.util.Log; import android.util.SparseIntArray; - import com.android.tv.tuner.data.Cea708Data; import com.android.tv.tuner.data.Cea708Data.CaptionColor; import com.android.tv.tuner.data.Cea708Data.CaptionEvent; @@ -31,7 +30,6 @@ import com.android.tv.tuner.data.Cea708Data.CaptionWindow; import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; import com.android.tv.tuner.data.Cea708Data.CcPacket; import com.android.tv.tuner.util.ByteArrayBuffer; - import java.io.UnsupportedEncodingException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -43,9 +41,9 @@ import java.util.TreeSet; /** * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV. * - *

ATSC DTV closed caption data are carried on picture user data of video streams. - * This class starts to parse from picture user data payload, so extraction process of user_data - * from video streams is up to outside of this code. + *

ATSC DTV closed caption data are carried on picture user data of video streams. This class + * starts to parse from picture user data payload, so extraction process of user_data from video + * streams is up to outside of this code. * *

There are 4 steps to decode user_data to provide closed caption services. * @@ -67,45 +65,47 @@ import java.util.TreeSet; * *

A DTVCC packet consists of multiple service blocks. Each service block represents a caption * track and has a service number, which ranges from 1 to 63, that denotes caption track identity. - * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}. - * Otherwise, just skip the other service blocks. + * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}. Otherwise, + * just skip the other service blocks. * - *

Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX}, - * and {@link #parseExt1} methods)

+ *

Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX}, and + * {@link #parseExt1} methods)

* *

Service block data is actual caption stream. it looks similar to telnet. It uses most parts of - * ASCII table and consists of specially defined commands and some ASCII control codes which work - * in a behavior slightly different from their original purpose. ASCII control codes and caption + * ASCII table and consists of specially defined commands and some ASCII control codes which work in + * a behavior slightly different from their original purpose. ASCII control codes and caption * commands are explicit instructions that control the state of a closed caption service and the * other ASCII and text codes are implicit instructions that send their characters to buffer. * *

There are 4 main code groups and 4 extended code groups. Both the range of code groups are the * same as the range of a byte. * - *

4 main code groups: C0, C1, G0, G1 - *
4 extended code groups: C2, C3, G2, G3 + *

4 main code groups: C0, C1, G0, G1
+ * 4 extended code groups: C2, C3, G2, G3 * *

Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while * {@link #parseExt1} method maps on the extended code groups. * *

The main code groups: + * *

    - *
  • C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA - * standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc, - * even for the alphanumeric characters instead of ASCII characters.
  • - *
  • C1 - contains the caption commands. There are 3 categories of a caption command.
  • - *
      - *
    • Window commands: The window commands control a caption window which is addressable area being - * with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)
    • - *
    • Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)
    • - *
    • Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)
    • - *
    - *
  • G0 - same as printable ASCII character set except music note character.
  • - *
  • G1 - same as ISO 8859-1 Latin 1 character set.
  • + *
  • C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA + * standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc, + * even for the alphanumeric characters instead of ASCII characters. + *
  • C1 - contains the caption commands. There are 3 categories of a caption command. + *
      + *
    • Window commands: The window commands control a caption window which is addressable + * area being with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX) + *
    • Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL) + *
    • Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, + * RST) + *
    + *
  • G0 - same as printable ASCII character set except music note character. + *
  • G1 - same as ISO 8859-1 Latin 1 character set. *
- *

Most of the extended code groups are being skipped. * + *

Most of the extended code groups are being skipped. */ public class Cea708Parser { private static final String TAG = "Cea708Parser"; @@ -113,8 +113,8 @@ public class Cea708Parser { // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps. private static final int MAX_ALLOCATED_SIZE = 9600 / 8; - private static final String MUSIC_NOTE_CHAR = new String( - "\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + private static final String MUSIC_NOTE_CHAR = + new String("\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); // The following values are denoting the type of closed caption data. // See CEA-708B section 4.4.1. @@ -143,44 +143,58 @@ public class Cea708Parser { private boolean mFirstServiceNumberDiscovered; // Assign a dummy listener in order to avoid null checks. - private OnCea708ParserListener mListener = new OnCea708ParserListener() { - @Override - public void emitEvent(CaptionEvent event) { - // do nothing - } + private OnCea708ParserListener mListener = + new OnCea708ParserListener() { + @Override + public void emitEvent(CaptionEvent event) { + // do nothing + } - @Override - public void discoverServiceNumber(int serviceNumber) { - // do nothing - } - }; + @Override + public void discoverServiceNumber(int serviceNumber) { + // do nothing + } + }; /** - * {@link Cea708Parser} emits caption event of three different types. - * {@link OnCea708ParserListener#emitEvent} is invoked with the parameter - * {@link CaptionEvent} to pass all the results to an observer of the decoding process. - * - *

{@link CaptionEvent#type} determines the type of the result and - * {@link CaptionEvent#obj} contains the output value of a caption event. - * The observer must do the casting to the corresponding type. - * - *

  • {@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer. - * {@code obj} must be of {@link String}.
  • + * {@link Cea708Parser} emits caption event of three different types. {@link + * OnCea708ParserListener#emitEvent} is invoked with the parameter {@link CaptionEvent} to pass + * all the results to an observer of the decoding process. * - *
  • {@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer. - * {@code obj} must be of {@link Character}.
  • + *

    {@link CaptionEvent#type} determines the type of the result and {@link CaptionEvent#obj} + * contains the output value of a caption event. The observer must do the casting to the + * corresponding type. * - *

  • {@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer. - * {@code obj} must be {@code NULL}.
+ *
    + *
  • {@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer. {@code + * obj} must be of {@link String}. + *
  • {@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a + * observer. {@code obj} must be of {@link Character}. + *
  • {@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer. {@code + * obj} must be {@code NULL}. + *
*/ - @IntDef({CAPTION_EMIT_TYPE_BUFFER, CAPTION_EMIT_TYPE_CONTROL, CAPTION_EMIT_TYPE_COMMAND_CWX, - CAPTION_EMIT_TYPE_COMMAND_CLW, CAPTION_EMIT_TYPE_COMMAND_DSW, CAPTION_EMIT_TYPE_COMMAND_HDW, - CAPTION_EMIT_TYPE_COMMAND_TGW, CAPTION_EMIT_TYPE_COMMAND_DLW, CAPTION_EMIT_TYPE_COMMAND_DLY, - CAPTION_EMIT_TYPE_COMMAND_DLC, CAPTION_EMIT_TYPE_COMMAND_RST, CAPTION_EMIT_TYPE_COMMAND_SPA, - CAPTION_EMIT_TYPE_COMMAND_SPC, CAPTION_EMIT_TYPE_COMMAND_SPL, CAPTION_EMIT_TYPE_COMMAND_SWA, - CAPTION_EMIT_TYPE_COMMAND_DFX}) + @IntDef({ + CAPTION_EMIT_TYPE_BUFFER, + CAPTION_EMIT_TYPE_CONTROL, + CAPTION_EMIT_TYPE_COMMAND_CWX, + CAPTION_EMIT_TYPE_COMMAND_CLW, + CAPTION_EMIT_TYPE_COMMAND_DSW, + CAPTION_EMIT_TYPE_COMMAND_HDW, + CAPTION_EMIT_TYPE_COMMAND_TGW, + CAPTION_EMIT_TYPE_COMMAND_DLW, + CAPTION_EMIT_TYPE_COMMAND_DLY, + CAPTION_EMIT_TYPE_COMMAND_DLC, + CAPTION_EMIT_TYPE_COMMAND_RST, + CAPTION_EMIT_TYPE_COMMAND_SPA, + CAPTION_EMIT_TYPE_COMMAND_SPC, + CAPTION_EMIT_TYPE_COMMAND_SPL, + CAPTION_EMIT_TYPE_COMMAND_SWA, + CAPTION_EMIT_TYPE_COMMAND_DFX + }) @Retention(RetentionPolicy.SOURCE) public @interface CaptionEmitType {} + public static final int CAPTION_EMIT_TYPE_BUFFER = 1; public static final int CAPTION_EMIT_TYPE_CONTROL = 2; public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3; @@ -200,6 +214,7 @@ public class Cea708Parser { public interface OnCea708ParserListener { void emitEvent(CaptionEvent event); + void discoverServiceNumber(int serviceNumber); } @@ -337,7 +352,8 @@ public class Cea708Parser { // bytes sent with the same service number during a discovery period. // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported. - if (blockSize > 0 && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START + if (blockSize > 0 + && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) { mDiscoveredNumBytes.put( serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0)); @@ -411,9 +427,7 @@ public class Cea708Parser { if (data[pos] == 0) { mBuffer.append((char) data[pos + 1]); } else { - String value = new String( - Arrays.copyOfRange(data, pos, pos + 2), - "EUC-KR"); + String value = new String(Arrays.copyOfRange(data, pos, pos + 2), "EUC-KR"); mBuffer.append(value); } } catch (UnsupportedEncodingException e) { @@ -466,203 +480,265 @@ public class Cea708Parser { case Cea708Data.CODE_C1_CW4: case Cea708Data.CODE_C1_CW5: case Cea708Data.CODE_C1_CW6: - case Cea708Data.CODE_C1_CW7: { - // SetCurrentWindow0-7 - int windowId = mCommand - Cea708Data.CODE_C1_CW0; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId)); + case Cea708Data.CODE_C1_CW7: + { + // SetCurrentWindow0-7 + int windowId = mCommand - Cea708Data.CODE_C1_CW0; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId)); + if (DEBUG) { + Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId)); + } + break; } - break; - } - case Cea708Data.CODE_C1_CLW: { - // ClearWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap)); + case Cea708Data.CODE_C1_CLW: + { + // ClearWindows + int windowBitmap = data[pos] & 0xff; + ++pos; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap)); + if (DEBUG) { + Log.d( + TAG, + String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap)); + } + break; } - break; - } - case Cea708Data.CODE_C1_DSW: { - // DisplayWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap)); + case Cea708Data.CODE_C1_DSW: + { + // DisplayWindows + int windowBitmap = data[pos] & 0xff; + ++pos; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap)); + if (DEBUG) { + Log.d( + TAG, + String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap)); + } + break; } - break; - } - case Cea708Data.CODE_C1_HDW: { - // HideWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap)); + case Cea708Data.CODE_C1_HDW: + { + // HideWindows + int windowBitmap = data[pos] & 0xff; + ++pos; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap)); + if (DEBUG) { + Log.d( + TAG, + String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap)); + } + break; } - break; - } - case Cea708Data.CODE_C1_TGW: { - // ToggleWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap)); + case Cea708Data.CODE_C1_TGW: + { + // ToggleWindows + int windowBitmap = data[pos] & 0xff; + ++pos; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap)); + if (DEBUG) { + Log.d( + TAG, + String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap)); + } + break; } - break; - } - case Cea708Data.CODE_C1_DLW: { - // DeleteWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap)); + case Cea708Data.CODE_C1_DLW: + { + // DeleteWindows + int windowBitmap = data[pos] & 0xff; + ++pos; + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap)); + if (DEBUG) { + Log.d( + TAG, + String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap)); + } + break; } - break; - } - case Cea708Data.CODE_C1_DLY: { - // Delay - int tenthsOfSeconds = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds", - tenthsOfSeconds)); + case Cea708Data.CODE_C1_DLY: + { + // Delay + int tenthsOfSeconds = data[pos] & 0xff; + ++pos; + emitCaptionEvent( + new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds)); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand DLY %d tenths of seconds", + tenthsOfSeconds)); + } + break; } - break; - } - case Cea708Data.CODE_C1_DLC: { - // DelayCancel - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null)); - if (DEBUG) { - Log.d(TAG, "CaptionCommand DLC"); + case Cea708Data.CODE_C1_DLC: + { + // DelayCancel + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null)); + if (DEBUG) { + Log.d(TAG, "CaptionCommand DLC"); + } + break; } - break; - } - case Cea708Data.CODE_C1_RST: { - // Reset - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null)); - if (DEBUG) { - Log.d(TAG, "CaptionCommand RST"); + case Cea708Data.CODE_C1_RST: + { + // Reset + emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null)); + if (DEBUG) { + Log.d(TAG, "CaptionCommand RST"); + } + break; } - break; - } - case Cea708Data.CODE_C1_SPA: { - // SetPenAttributes - int textTag = (data[pos] & 0xf0) >> 4; - int penSize = data[pos] & 0x03; - int penOffset = (data[pos] & 0x0c) >> 2; - boolean italic = (data[pos + 1] & 0x80) != 0; - boolean underline = (data[pos + 1] & 0x40) != 0; - int edgeType = (data[pos + 1] & 0x38) >> 3; - int fontTag = data[pos + 1] & 0x7; - pos += 2; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA, - new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType, - underline, italic))); - if (DEBUG) { - Log.d(TAG, String.format( - "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, " - + "fontTag: %d, edgeType: %d, underline: %s, italic: %s", - penSize, penOffset, textTag, fontTag, edgeType, underline, italic)); + case Cea708Data.CODE_C1_SPA: + { + // SetPenAttributes + int textTag = (data[pos] & 0xf0) >> 4; + int penSize = data[pos] & 0x03; + int penOffset = (data[pos] & 0x0c) >> 2; + boolean italic = (data[pos + 1] & 0x80) != 0; + boolean underline = (data[pos + 1] & 0x40) != 0; + int edgeType = (data[pos + 1] & 0x38) >> 3; + int fontTag = data[pos + 1] & 0x7; + pos += 2; + emitCaptionEvent( + new CaptionEvent( + CAPTION_EMIT_TYPE_COMMAND_SPA, + new CaptionPenAttr( + penSize, penOffset, textTag, fontTag, edgeType, + underline, italic))); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, " + + "fontTag: %d, edgeType: %d, underline: %s, italic: %s", + penSize, penOffset, textTag, fontTag, edgeType, underline, + italic)); + } + break; } - break; - } - case Cea708Data.CODE_C1_SPC: { - // SetPenColor - int opacity = (data[pos] & 0xc0) >> 6; - int red = (data[pos] & 0x30) >> 4; - int green = (data[pos] & 0x0c) >> 2; - int blue = data[pos] & 0x03; - CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue); - ++pos; - opacity = (data[pos] & 0xc0) >> 6; - red = (data[pos] & 0x30) >> 4; - green = (data[pos] & 0x0c) >> 2; - blue = data[pos] & 0x03; - CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue); - ++pos; - red = (data[pos] & 0x30) >> 4; - green = (data[pos] & 0x0c) >> 2; - blue = data[pos] & 0x03; - CaptionColor edgeColor = new CaptionColor( - CaptionColor.OPACITY_SOLID, red, green, blue); - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC, - new CaptionPenColor(foregroundColor, backgroundColor, edgeColor))); - if (DEBUG) { - Log.d(TAG, String.format( - "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s", - foregroundColor, backgroundColor, edgeColor)); + case Cea708Data.CODE_C1_SPC: + { + // SetPenColor + int opacity = (data[pos] & 0xc0) >> 6; + int red = (data[pos] & 0x30) >> 4; + int green = (data[pos] & 0x0c) >> 2; + int blue = data[pos] & 0x03; + CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue); + ++pos; + opacity = (data[pos] & 0xc0) >> 6; + red = (data[pos] & 0x30) >> 4; + green = (data[pos] & 0x0c) >> 2; + blue = data[pos] & 0x03; + CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue); + ++pos; + red = (data[pos] & 0x30) >> 4; + green = (data[pos] & 0x0c) >> 2; + blue = data[pos] & 0x03; + CaptionColor edgeColor = + new CaptionColor(CaptionColor.OPACITY_SOLID, red, green, blue); + ++pos; + emitCaptionEvent( + new CaptionEvent( + CAPTION_EMIT_TYPE_COMMAND_SPC, + new CaptionPenColor( + foregroundColor, backgroundColor, edgeColor))); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s", + foregroundColor, backgroundColor, edgeColor)); + } + break; } - break; - } - case Cea708Data.CODE_C1_SPL: { - // SetPenLocation - // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats - int row = data[pos] & 0x0f; - int column = data[pos + 1] & 0x3f; - pos += 2; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL, - new CaptionPenLocation(row, column))); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d", - row, column)); + case Cea708Data.CODE_C1_SPL: + { + // SetPenLocation + // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats + int row = data[pos] & 0x0f; + int column = data[pos + 1] & 0x3f; + pos += 2; + emitCaptionEvent( + new CaptionEvent( + CAPTION_EMIT_TYPE_COMMAND_SPL, + new CaptionPenLocation(row, column))); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand SPL row: %d, column: %d", row, column)); + } + break; } - break; - } - case Cea708Data.CODE_C1_SWA: { - // SetWindowAttributes - int opacity = (data[pos] & 0xc0) >> 6; - int red = (data[pos] & 0x30) >> 4; - int green = (data[pos] & 0x0c) >> 2; - int blue = data[pos] & 0x03; - CaptionColor fillColor = new CaptionColor(opacity, red, green, blue); - int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5; - red = (data[pos + 1] & 0x30) >> 4; - green = (data[pos + 1] & 0x0c) >> 2; - blue = data[pos + 1] & 0x03; - CaptionColor borderColor = new CaptionColor( - CaptionColor.OPACITY_SOLID, red, green, blue); - boolean wordWrap = (data[pos + 2] & 0x40) != 0; - int printDirection = (data[pos + 2] & 0x30) >> 4; - int scrollDirection = (data[pos + 2] & 0x0c) >> 2; - int justify = (data[pos + 2] & 0x03); - int effectSpeed = (data[pos + 3] & 0xf0) >> 4; - int effectDirection = (data[pos + 3] & 0x0c) >> 2; - int displayEffect = data[pos + 3] & 0x3; - pos += 4; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA, - new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap, - printDirection, scrollDirection, justify, - effectDirection, effectSpeed, displayEffect))); - if (DEBUG) { - Log.d(TAG, String.format( - "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d" - + "wordWrap: %s, printDirection: %d, scrollDirection: %d, " - + "justify: %s, effectDirection: %d, effectSpeed: %d, " - + "displayEffect: %d", - fillColor, borderColor, borderType, wordWrap, printDirection, - scrollDirection, justify, effectDirection, effectSpeed, displayEffect)); + case Cea708Data.CODE_C1_SWA: + { + // SetWindowAttributes + int opacity = (data[pos] & 0xc0) >> 6; + int red = (data[pos] & 0x30) >> 4; + int green = (data[pos] & 0x0c) >> 2; + int blue = data[pos] & 0x03; + CaptionColor fillColor = new CaptionColor(opacity, red, green, blue); + int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5; + red = (data[pos + 1] & 0x30) >> 4; + green = (data[pos + 1] & 0x0c) >> 2; + blue = data[pos + 1] & 0x03; + CaptionColor borderColor = + new CaptionColor(CaptionColor.OPACITY_SOLID, red, green, blue); + boolean wordWrap = (data[pos + 2] & 0x40) != 0; + int printDirection = (data[pos + 2] & 0x30) >> 4; + int scrollDirection = (data[pos + 2] & 0x0c) >> 2; + int justify = (data[pos + 2] & 0x03); + int effectSpeed = (data[pos + 3] & 0xf0) >> 4; + int effectDirection = (data[pos + 3] & 0x0c) >> 2; + int displayEffect = data[pos + 3] & 0x3; + pos += 4; + emitCaptionEvent( + new CaptionEvent( + CAPTION_EMIT_TYPE_COMMAND_SWA, + new CaptionWindowAttr( + fillColor, + borderColor, + borderType, + wordWrap, + printDirection, + scrollDirection, + justify, + effectDirection, + effectSpeed, + displayEffect))); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d" + + "wordWrap: %s, printDirection: %d, scrollDirection: %d, " + + "justify: %s, effectDirection: %d, effectSpeed: %d, " + + "displayEffect: %d", + fillColor, + borderColor, + borderType, + wordWrap, + printDirection, + scrollDirection, + justify, + effectDirection, + effectSpeed, + displayEffect)); + } + break; } - break; - } case Cea708Data.CODE_C1_DF0: case Cea708Data.CODE_C1_DF1: @@ -671,39 +747,65 @@ public class Cea708Parser { case Cea708Data.CODE_C1_DF4: case Cea708Data.CODE_C1_DF5: case Cea708Data.CODE_C1_DF6: - case Cea708Data.CODE_C1_DF7: { - // DefineWindow0-7 - int windowId = mCommand - Cea708Data.CODE_C1_DF0; - boolean visible = (data[pos] & 0x20) != 0; - boolean rowLock = (data[pos] & 0x10) != 0; - boolean columnLock = (data[pos] & 0x08) != 0; - int priority = data[pos] & 0x07; - boolean relativePositioning = (data[pos + 1] & 0x80) != 0; - int anchorVertical = data[pos + 1] & 0x7f; - int anchorHorizontal = data[pos + 2] & 0xff; - int anchorId = (data[pos + 3] & 0xf0) >> 4; - int rowCount = data[pos + 3] & 0x0f; - int columnCount = data[pos + 4] & 0x3f; - int windowStyle = (data[pos + 5] & 0x38) >> 3; - int penStyle = data[pos + 5] & 0x07; - pos += 6; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX, - new CaptionWindow(windowId, visible, rowLock, columnLock, priority, - relativePositioning, anchorVertical, anchorHorizontal, anchorId, - rowCount, columnCount, penStyle, windowStyle))); - if (DEBUG) { - Log.d(TAG, String.format( - "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, " - + "rowLock: %s, visible: %s, anchorVertical: %d, " - + "relativePositioning: %s, anchorHorizontal: %d, " - + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, " - + "windowStyle: %d", - windowId, priority, columnLock, rowLock, visible, anchorVertical, - relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount, - penStyle, windowStyle)); + case Cea708Data.CODE_C1_DF7: + { + // DefineWindow0-7 + int windowId = mCommand - Cea708Data.CODE_C1_DF0; + boolean visible = (data[pos] & 0x20) != 0; + boolean rowLock = (data[pos] & 0x10) != 0; + boolean columnLock = (data[pos] & 0x08) != 0; + int priority = data[pos] & 0x07; + boolean relativePositioning = (data[pos + 1] & 0x80) != 0; + int anchorVertical = data[pos + 1] & 0x7f; + int anchorHorizontal = data[pos + 2] & 0xff; + int anchorId = (data[pos + 3] & 0xf0) >> 4; + int rowCount = data[pos + 3] & 0x0f; + int columnCount = data[pos + 4] & 0x3f; + int windowStyle = (data[pos + 5] & 0x38) >> 3; + int penStyle = data[pos + 5] & 0x07; + pos += 6; + emitCaptionEvent( + new CaptionEvent( + CAPTION_EMIT_TYPE_COMMAND_DFX, + new CaptionWindow( + windowId, + visible, + rowLock, + columnLock, + priority, + relativePositioning, + anchorVertical, + anchorHorizontal, + anchorId, + rowCount, + columnCount, + penStyle, + windowStyle))); + if (DEBUG) { + Log.d( + TAG, + String.format( + "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, " + + "rowLock: %s, visible: %s, anchorVertical: %d, " + + "relativePositioning: %s, anchorHorizontal: %d, " + + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, " + + "windowStyle: %d", + windowId, + priority, + columnLock, + rowLock, + visible, + anchorVertical, + relativePositioning, + anchorHorizontal, + rowCount, + anchorId, + columnCount, + penStyle, + windowStyle)); + } + break; } - break; - } default: break; @@ -748,7 +850,7 @@ public class Cea708Parser { pos = parseG2(data, pos); } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START && mCommand <= Cea708Data.CODE_G3_RANGE_END) { - pos = parseG3(data ,pos); + pos = parseG3(data, pos); } return pos; } diff --git a/src/com/android/tv/tuner/data/Cea708Data.java b/src/com/android/tv/tuner/data/Cea708Data.java index 6350d63c..73a90181 100644 --- a/src/com/android/tv/tuner/data/Cea708Data.java +++ b/src/com/android/tv/tuner/data/Cea708Data.java @@ -16,18 +16,14 @@ package com.android.tv.tuner.data; -import com.android.tv.tuner.cc.Cea708Parser; - import android.graphics.Color; import android.support.annotation.NonNull; +import com.android.tv.tuner.cc.Cea708Parser; -/** - * Collection of CEA-708 structures. - */ +/** Collection of CEA-708 structures. */ public class Cea708Data { - private Cea708Data() { - } + private Cea708Data() {} // According to CEA-708B, the range of valid service number is between 1 and 63. public static final int EMPTY_SERVICE_NUMBER = 0; @@ -134,17 +130,15 @@ public class Cea708Data { } } - /** - * CEA-708B-specific color. - */ + /** CEA-708B-specific color. */ public static class CaptionColor { public static final int OPACITY_SOLID = 0; public static final int OPACITY_FLASH = 1; public static final int OPACITY_TRANSLUCENT = 2; public static final int OPACITY_TRANSPARENT = 3; - private static final int[] COLOR_MAP = new int[] { 0x00, 0x0f, 0xf0, 0xff }; - private static final int[] OPACITY_MAP = new int[] { 0xff, 0xfe, 0x80, 0x00 }; + private static final int[] COLOR_MAP = new int[] {0x00, 0x0f, 0xf0, 0xff}; + private static final int[] OPACITY_MAP = new int[] {0xff, 0xfe, 0x80, 0x00}; public final int opacity; public final int red; @@ -164,9 +158,7 @@ public class Cea708Data { } } - /** - * Caption event generated by {@link Cea708Parser}. - */ + /** Caption event generated by {@link Cea708Parser}. */ public static class CaptionEvent { @Cea708Parser.CaptionEmitType public final int type; public final Object obj; @@ -177,9 +169,7 @@ public class Cea708Data { } } - /** - * Pen style information. - */ + /** Pen style information. */ public static class CaptionPenAttr { // Pen sizes public static final int PEN_SIZE_SMALL = 0; @@ -199,8 +189,14 @@ public class Cea708Data { public final boolean underline; public final boolean italic; - public CaptionPenAttr(int penSize, int penOffset, int textTag, int fontTag, int edgeType, - boolean underline, boolean italic) { + public CaptionPenAttr( + int penSize, + int penOffset, + int textTag, + int fontTag, + int edgeType, + boolean underline, + boolean italic) { this.penSize = penSize; this.penOffset = penOffset; this.textTag = textTag; @@ -220,7 +216,9 @@ public class Cea708Data { public final CaptionColor backgroundColor; public final CaptionColor edgeColor; - public CaptionPenColor(CaptionColor foregroundColor, CaptionColor backgroundColor, + public CaptionPenColor( + CaptionColor foregroundColor, + CaptionColor backgroundColor, CaptionColor edgeColor) { this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; @@ -228,9 +226,7 @@ public class Cea708Data { } } - /** - * Location information of a pen. - */ + /** Location information of a pen. */ public static class CaptionPenLocation { public final int row; public final int column; @@ -241,9 +237,7 @@ public class Cea708Data { } } - /** - * Attributes of a caption window, which is defined in CEA-708B. - */ + /** Attributes of a caption window, which is defined in CEA-708B. */ public static class CaptionWindowAttr { public static final int JUSTIFY_LEFT = 0; public static final int JUSTIFY_CENTER = 2; @@ -263,10 +257,17 @@ public class Cea708Data { public final int effectSpeed; public final int displayEffect; - public CaptionWindowAttr(CaptionColor fillColor, CaptionColor borderColor, int borderType, - boolean wordWrap, int printDirection, int scrollDirection, int justify, + public CaptionWindowAttr( + CaptionColor fillColor, + CaptionColor borderColor, + int borderType, + boolean wordWrap, + int printDirection, + int scrollDirection, + int justify, int effectDirection, - int effectSpeed, int displayEffect) { + int effectSpeed, + int displayEffect) { this.fillColor = fillColor; this.borderColor = borderColor; this.borderType = borderType; @@ -280,9 +281,7 @@ public class Cea708Data { } } - /** - * Construction information of the caption window of CEA-708B. - */ + /** Construction information of the caption window of CEA-708B. */ public static class CaptionWindow { public final int id; public final boolean visible; @@ -298,10 +297,20 @@ public class Cea708Data { public final int penStyle; public final int windowStyle; - public CaptionWindow(int id, boolean visible, - boolean rowLock, boolean columnLock, int priority, boolean relativePositioning, - int anchorVertical, int anchorHorizontal, int anchorId, - int rowCount, int columnCount, int penStyle, int windowStyle) { + public CaptionWindow( + int id, + boolean visible, + boolean rowLock, + boolean columnLock, + int priority, + boolean relativePositioning, + int anchorVertical, + int anchorHorizontal, + int anchorId, + int rowCount, + int columnCount, + int penStyle, + int windowStyle) { this.id = id; this.visible = visible; this.rowLock = rowLock; diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java index 67700c6a..9b7c2e2c 100644 --- a/src/com/android/tv/tuner/data/PsiData.java +++ b/src/com/android/tv/tuner/data/PsiData.java @@ -14,21 +14,16 @@ * limitations under the License. */ - package com.android.tv.tuner.data; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; - import java.util.List; -/** - * Collection of MPEG PSI table items. - */ +/** Collection of MPEG PSI table items. */ public class PsiData { - private PsiData() { - } + private PsiData() {} public static class PatItem { private final int mProgramNo; @@ -61,8 +56,11 @@ public class PsiData { private final List mAudioTracks; private final List mCaptionTracks; - public PmtItem(int streamType, int esPid, - List audioTracks, List captionTracks) { + public PmtItem( + int streamType, + int esPid, + List audioTracks, + List captionTracks) { mStreamType = streamType; mEsPid = esPid; mAudioTracks = audioTracks; @@ -87,7 +85,8 @@ public class PsiData { @Override public String toString() { - return String.format("Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s", + return String.format( + "Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s", mStreamType, mEsPid, mAudioTracks, mCaptionTracks); } } diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java index 8f98e67c..6459004c 100644 --- a/src/com/android/tv/tuner/data/PsipData.java +++ b/src/com/android/tv/tuner/data/PsipData.java @@ -19,25 +19,20 @@ package com.android.tv.tuner.data; import android.support.annotation.NonNull; import android.text.TextUtils; import android.text.format.DateUtils; - import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.ts.SectionParser; import com.android.tv.tuner.util.ConvertUtils; import com.android.tv.util.StringUtils; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; -/** - * Collection of ATSC PSIP table items. - */ +/** Collection of ATSC PSIP table items. */ public class PsipData { - private PsipData() { - } + private PsipData() {} public static class PsipSection { private final int mTableId; @@ -56,7 +51,10 @@ public class PsipData { return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator); } - private PsipSection(int tableId, int tableIdExtension, int sectionNumber, + private PsipSection( + int tableId, + int tableIdExtension, + int sectionNumber, boolean currentNextIndicator) { mTableId = tableId; mTableIdExtension = tableIdExtension; @@ -104,29 +102,21 @@ public class PsipData { } } - /** - * {@link TvTracksInterface} for serving the audio and caption tracks. - */ + /** {@link TvTracksInterface} for serving the audio and caption tracks. */ public interface TvTracksInterface { - /** - * Set the flag that tells the caption tracks have been found in this section container. - */ + /** Set the flag that tells the caption tracks have been found in this section container. */ void setHasCaptionTrack(); /** - * Returns whether or not the caption tracks have been found in this section container. - * If true, zero caption track will be interpreted as a clearance of the caption tracks. + * Returns whether or not the caption tracks have been found in this section container. If + * true, zero caption track will be interpreted as a clearance of the caption tracks. */ boolean hasCaptionTrack(); - /** - * Returns the audio tracks received. - */ + /** Returns the audio tracks received. */ List getAudioTracks(); - /** - * Returns the caption tracks received. - */ + /** Returns the caption tracks received. */ List getCaptionTracks(); } @@ -165,8 +155,15 @@ public class PsipData { private final int mSourceId; private String mDescription; - public VctItem(String shortName, String longName, int serviceType, int channelTsid, - int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId) { + public VctItem( + String shortName, + String longName, + int serviceType, + int channelTsid, + int programNumber, + int majorChannelNumber, + int minorChannelNumber, + int sourceId) { mShortName = shortName; mLongName = longName; mServiceType = serviceType; @@ -211,11 +208,18 @@ public class PsipData { @Override public String toString() { - return String - .format(Locale.US, "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x " + return String.format( + Locale.US, + "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x " + "ProgramNumber:%d %d-%d SourceId: %x", - mShortName, mLongName, mServiceType, mChannelTsid, - mProgramNumber, mMajorChannelNumber, mMinorChannelNumber, mSourceId); + mShortName, + mLongName, + mServiceType, + mChannelTsid, + mProgramNumber, + mMajorChannelNumber, + mMinorChannelNumber, + mSourceId); } public void setDescription(String description) { @@ -234,8 +238,12 @@ public class PsipData { private final int mServiceId; private final int mOriginalNetWorkId; - public SdtItem(String serviceName, String serviceProviderName, int serviceType, - int serviceId, int originalNetWorkId) { + public SdtItem( + String serviceName, + String serviceProviderName, + int serviceType, + int serviceId, + int originalNetWorkId) { mServiceName = serviceName; mServiceProviderName = serviceProviderName; mServiceType = serviceType; @@ -265,15 +273,14 @@ public class PsipData { @Override public String toString() { - return String.format("ServiceName: %s ServiceProviderName:%s ServiceType:%d " + return String.format( + "ServiceName: %s ServiceProviderName:%s ServiceType:%d " + "OriginalNetworkId:%d", mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId); } } - /** - * A base class for descriptors of Ts packets. - */ + /** A base class for descriptors of Ts packets. */ public abstract static class TsDescriptor { public abstract int getTag(); } @@ -374,10 +381,22 @@ public class PsipData { private final String mLanguage; private final String mLanguage2; - public Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode, - byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod, - byte langCod2, byte mainId, byte priority, byte asvcflags, String text, - String language, String language2) { + public Ac3AudioDescriptor( + byte sampleRateCode, + byte bsid, + byte bitRateCode, + byte surroundMode, + byte bsmod, + int numChannels, + boolean fullSvc, + byte langCod, + byte langCod2, + byte mainId, + byte priority, + byte asvcflags, + String text, + String language, + String language2) { mSampleRateCode = sampleRateCode; mBsid = bsid; mBitRateCode = bitRateCode; @@ -475,13 +494,27 @@ public class PsipData { @Override public String toString() { - return String.format(Locale.US, + return String.format( + Locale.US, "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, " - + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, " - + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s" - + ", language2: %s", mSampleRateCode, mBsid, mBitRateCode, mSurroundMode, - mBsmod, mNumChannels, mFullSvc, mLangCod, mLangCod2, mMainId, mPriority, - mAsvcflags, mText, mLanguage, mLanguage2); + + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, " + + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s" + + ", language2: %s", + mSampleRateCode, + mBsid, + mBitRateCode, + mSurroundMode, + mBsmod, + mNumChannels, + mFullSvc, + mLangCod, + mLangCod2, + mMainId, + mPriority, + mAsvcflags, + mText, + mLanguage, + mLanguage2); } } @@ -540,7 +573,8 @@ public class PsipData { return String.format( "Service descriptor, service type: %d, " + "service provider name: %s, " - + "service name: %s", mServiceType, mServiceProviderName, mServiceName); + + "service name: %s", + mServiceType, mServiceProviderName, mServiceName); } } @@ -566,8 +600,9 @@ public class PsipData { @Override public String toString() { - return String.format("ShortEvent Descriptor, language:%s, event name: %s, " - + "text:%s", mLanguage, mEventName, mText); + return String.format( + "ShortEvent Descriptor, language:%s, event name: %s, " + "text:%s", + mLanguage, mEventName, mText); } } @@ -652,9 +687,17 @@ public class PsipData { private final String mBroadcastGenre; private final String mCanonicalGenre; - public EitItem(long programId, int eventId, String titleText, long startTime, - int lengthInSecond, String contentRating, List audioTracks, - List captionTracks, String broadcastGenre, String canonicalGenre, + public EitItem( + long programId, + int eventId, + String titleText, + long startTime, + int lengthInSecond, + String contentRating, + List audioTracks, + List captionTracks, + String broadcastGenre, + String canonicalGenre, String description) { mProgramId = programId; mEventId = eventId; @@ -702,8 +745,8 @@ public class PsipData { } public long getEndTimeUtcMillis() { - return ConvertUtils.convertGPSTimeToUnixEpoch( - mStartTime + mLengthInSecond) * DateUtils.SECOND_IN_MILLIS; + return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime + mLengthInSecond) + * DateUtils.SECOND_IN_MILLIS; } public String getContentRating() { @@ -797,14 +840,22 @@ public class PsipData { @Override public String toString() { - return String.format(Locale.US, + return String.format( + Locale.US, "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, " + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, " + "genres (broadcast: %s, canonical: %s), description: %s", - mProgramId, mEventId, mTitleText, mStartTime, mLengthInSecond, mContentRating, + mProgramId, + mEventId, + mTitleText, + mStartTime, + mLengthInSecond, + mContentRating, mAudioTracks != null ? mAudioTracks.size() : 0, mCaptionTracks != null ? mCaptionTracks.size() : 0, - mBroadcastGenre, mCanonicalGenre, mDescription); + mBroadcastGenre, + mCanonicalGenre, + mDescription); } } diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java index 1cf514c1..52356c2e 100644 --- a/src/com/android/tv/tuner/data/TunerChannel.java +++ b/src/com/android/tv/tuner/data/TunerChannel.java @@ -18,7 +18,6 @@ package com.android.tv.tuner.data; import android.support.annotation.NonNull; import android.util.Log; - import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.nano.Channel.TunerChannelProto; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; @@ -26,7 +25,6 @@ import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.Ints; import com.android.tv.util.StringUtils; import com.google.protobuf.nano.MessageNano; - import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -34,29 +32,27 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -/** - * A class that represents a single channel accessible through a tuner. - */ +/** A class that represents a single channel accessible through a tuner. */ public class TunerChannel implements Comparable, PsipData.TvTracksInterface { private static final String TAG = "TunerChannel"; - /** - * Channel number separator between major number and minor number. - */ + /** Channel number separator between major number and minor number. */ public static final char CHANNEL_NUMBER_SEPARATOR = '-'; // See ATSC Code Points Registry. - private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] { - "ATSC Reserved", - "Analog television channels", - "ATSC_digital_television", - "ATSC_audio", - "ATSC_data_only_service", - "Software Download", - "Unassociated/Small Screen Service", - "Parameterized Service", - "ATSC NRT Service", - "Extended Parameterized Service" }; + private static final String[] ATSC_SERVICE_TYPE_NAMES = + new String[] { + "ATSC Reserved", + "Analog television channels", + "ATSC_digital_television", + "ATSC_audio", + "ATSC_data_only_service", + "Software Download", + "Unassociated/Small Screen Service", + "Parameterized Service", + "ATSC NRT Service", + "Extended Parameterized Service" + }; private static final String ATSC_SERVICE_TYPE_NAME_RESERVED = ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED]; @@ -71,8 +67,8 @@ public class TunerChannel implements Comparable, PsipData.TvTracks // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766 private final TunerChannelProto mProto; - private TunerChannel(PsipData.VctItem channel, int programNumber, - List pmtItems, int type) { + private TunerChannel( + PsipData.VctItem channel, int programNumber, List pmtItems, int type) { mProto = new TunerChannelProto(); if (channel == null) { mProto.shortName = ""; @@ -107,7 +103,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks List audioStreamTypes = new ArrayList<>(); for (PsiData.PmtItem pmt : pmtItems) { switch (pmt.getStreamType()) { - // MPEG ES stream video types + // MPEG ES stream video types case Channel.MPEG1: case Channel.MPEG2: case Channel.H263: @@ -117,7 +113,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks mProto.videoStreamType = pmt.getStreamType(); break; - // MPEG ES stream audio types + // MPEG ES stream audio types case Channel.MPEG1AUDIO: case Channel.MPEG2AUDIO: case Channel.MPEG2AACAUDIO: @@ -128,7 +124,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks audioStreamTypes.add(pmt.getStreamType()); break; - // Non MPEG ES stream types + // Non MPEG ES stream types case 0x100: // PmtItem.ES_PID_PCR: mProto.pcrPid = pmt.getEsPid(); break; @@ -139,8 +135,8 @@ public class TunerChannel implements Comparable, PsipData.TvTracks mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1; } - private TunerChannel(int programNumber, int type, PsipData.SdtItem channel, - List pmtItems) { + private TunerChannel( + int programNumber, int type, PsipData.SdtItem channel, List pmtItems) { mProto = new TunerChannelProto(); mProto.tsid = 0; mProto.virtualMajor = 0; @@ -156,23 +152,17 @@ public class TunerChannel implements Comparable, PsipData.TvTracks initProto(pmtItems, type); } - /** - * Initialize tuner channel with VCT items and PMT items. - */ + /** Initialize tuner channel with VCT items and PMT items. */ public TunerChannel(PsipData.VctItem channel, List pmtItems) { this(channel, 0, pmtItems, Channel.TYPE_TUNER); } - /** - * Initialize tuner channel with program number and PMT items. - */ + /** Initialize tuner channel with program number and PMT items. */ public TunerChannel(int programNumber, List pmtItems) { this(null, programNumber, pmtItems, Channel.TYPE_TUNER); } - /** - * Initialize tuner channel with SDT items and PMT items. - */ + /** Initialize tuner channel with SDT items and PMT items. */ public TunerChannel(PsipData.SdtItem channel, List pmtItems) { this(0, Channel.TYPE_TUNER, channel, pmtItems); } @@ -192,29 +182,35 @@ public class TunerChannel implements Comparable, PsipData.TvTracks /** * Create a TunerChannel object suitable for network tuners + * * @param major Channel number major * @param minor Channel number minor * @param programNumber Program number * @param shortName Short name * @param recordingProhibited Recording prohibition info - * @param videoFormat Video format. Should be {@code null} or one of the followings: - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, - * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P} + * @param videoFormat Video format. Should be {@code null} or one of the followings: {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, {@link + * android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P} * @return a TunerChannel object */ - public static TunerChannel forNetwork(int major, int minor, int programNumber, - String shortName, boolean recordingProhibited, String videoFormat) { - TunerChannel tunerChannel = new TunerChannel( - null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK); + public static TunerChannel forNetwork( + int major, + int minor, + int programNumber, + String shortName, + boolean recordingProhibited, + String videoFormat) { + TunerChannel tunerChannel = + new TunerChannel(null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK); tunerChannel.setVirtualMajor(major); tunerChannel.setVirtualMinor(minor); tunerChannel.setShortName(shortName); @@ -277,7 +273,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.videoPid; } - synchronized public void setVideoPid(int videoPid) { + public synchronized void setVideoPid(int videoPid) { mProto.videoPid = videoPid; } @@ -303,7 +299,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Ints.asList(mProto.audioPids); } - synchronized public void setAudioPids(List audioPids) { + public synchronized void setAudioPids(List audioPids) { mProto.audioPids = Ints.toArray(audioPids); } @@ -311,7 +307,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Ints.asList(mProto.audioStreamTypes); } - synchronized public void setAudioStreamTypes(List audioStreamTypes) { + public synchronized void setAudioStreamTypes(List audioStreamTypes) { mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); } @@ -323,7 +319,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.type; } - synchronized public void setFilepath(String filepath) { + public synchronized void setFilepath(String filepath) { mProto.filepath = filepath == null ? "" : filepath; } @@ -331,23 +327,23 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.filepath; } - synchronized public void setVirtualMajor(int virtualMajor) { + public synchronized void setVirtualMajor(int virtualMajor) { mProto.virtualMajor = virtualMajor; } - synchronized public void setVirtualMinor(int virtualMinor) { + public synchronized void setVirtualMinor(int virtualMinor) { mProto.virtualMinor = virtualMinor; } - synchronized public void setShortName(String shortName) { + public synchronized void setShortName(String shortName) { mProto.shortName = shortName == null ? "" : shortName; } - synchronized public void setFrequency(int frequency) { + public synchronized void setFrequency(int frequency) { mProto.frequency = frequency; } - synchronized public void setModulation(String modulation) { + public synchronized void setModulation(String modulation) { mProto.modulation = modulation == null ? "" : modulation; } @@ -363,7 +359,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.channelId; } - synchronized public void setChannelId(long channelId) { + public synchronized void setChannelId(long channelId) { mProto.channelId = channelId; } @@ -373,8 +369,8 @@ public class TunerChannel implements Comparable, PsipData.TvTracks public String getDisplayNumber(boolean ignoreZeroMinorNumber) { if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) { - return String.format("%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, - mProto.virtualMinor); + return String.format( + "%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, mProto.virtualMinor); } else if (mProto.virtualMajor != 0) { return Integer.toString(mProto.virtualMajor); } else { @@ -387,7 +383,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } @Override - synchronized public void setHasCaptionTrack() { + public synchronized void setHasCaptionTrack() { mProto.hasCaptionTrack = true; } @@ -401,7 +397,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks)); } - synchronized public void setAudioTracks(List audioTracks) { + public synchronized void setAudioTracks(List audioTracks) { mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]); } @@ -410,11 +406,11 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks)); } - synchronized public void setCaptionTracks(List captionTracks) { + public synchronized void setCaptionTracks(List captionTracks) { mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]); } - synchronized public void selectAudioTrack(int index) { + public synchronized void selectAudioTrack(int index) { if (0 <= index && index < mProto.audioPids.length) { mProto.audioTrackIndex = index; } else { @@ -422,7 +418,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } } - synchronized public void setRecordingProhibited(boolean recordingProhibited) { + public synchronized void setRecordingProhibited(boolean recordingProhibited) { mProto.recordingProhibited = recordingProhibited; } @@ -430,7 +426,7 @@ public class TunerChannel implements Comparable, PsipData.TvTracks return mProto.recordingProhibited; } - synchronized public void setVideoFormat(String videoFormat) { + public synchronized void setVideoFormat(String videoFormat) { mProto.videoFormat = videoFormat == null ? "" : videoFormat; } @@ -442,14 +438,22 @@ public class TunerChannel implements Comparable, PsipData.TvTracks public String toString() { switch (mProto.type) { case Channel.TYPE_FILE: - return String.format("{%d-%d %s} Filepath: %s, ProgramNumber %d", - mProto.virtualMajor, mProto.virtualMinor, mProto.shortName, - mProto.filepath, mProto.programNumber); - //case Channel.TYPE_TUNER: + return String.format( + "{%d-%d %s} Filepath: %s, ProgramNumber %d", + mProto.virtualMajor, + mProto.virtualMinor, + mProto.shortName, + mProto.filepath, + mProto.programNumber); + // case Channel.TYPE_TUNER: default: - return String.format("{%d-%d %s} Frequency: %d, ProgramNumber %d", - mProto.virtualMajor, mProto.virtualMinor, mProto.shortName, - mProto.frequency, mProto.programNumber); + return String.format( + "{%d-%d %s} Frequency: %d, ProgramNumber %d", + mProto.virtualMajor, + mProto.virtualMinor, + mProto.shortName, + mProto.frequency, + mProto.programNumber); } } @@ -486,12 +490,14 @@ public class TunerChannel implements Comparable, PsipData.TvTracks } // Serialization - synchronized public byte[] toByteArray() { + public synchronized byte[] toByteArray() { try { return MessageNano.toByteArray(mProto); } catch (Exception e) { // Retry toByteArray. b/34197766 - Log.w(TAG, "TunerChannel or its variables are modified in multiple thread without lock", + Log.w( + TAG, + "TunerChannel or its variables are modified in multiple thread without lock", e); return MessageNano.toByteArray(mProto); } diff --git a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java index 5f536708..1f48c45b 100644 --- a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java @@ -17,7 +17,8 @@ package com.android.tv.tuner.exoplayer; import android.util.Log; - +import com.android.tv.tuner.cc.Cea708Parser; +import com.android.tv.tuner.data.Cea708Data.CaptionEvent; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaClock; import com.google.android.exoplayer.MediaFormat; @@ -26,16 +27,11 @@ import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.util.Assertions; -import com.android.tv.tuner.cc.Cea708Parser; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; - import java.io.IOException; -/** - * A {@link TrackRenderer} for CEA-708 textual subtitles. - */ -public class Cea708TextTrackRenderer extends TrackRenderer implements - Cea708Parser.OnCea708ParserListener { +/** A {@link TrackRenderer} for CEA-708 textual subtitles. */ +public class Cea708TextTrackRenderer extends TrackRenderer + implements Cea708Parser.OnCea708ParserListener { private static final String TAG = "Cea708TextTrackRenderer"; private static final boolean DEBUG = false; @@ -59,7 +55,9 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements public interface CcListener { void emitEvent(CaptionEvent captionEvent); + void clearCaption(); + void discoverServiceNumber(int serviceNumber); } @@ -165,8 +163,9 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements } private boolean processOutput() { - return !mInputStreamEnded && mCea708Parser != null && - mCea708Parser.processClosedCaptions(mPresentationTimeUs); + return !mInputStreamEnded + && mCea708Parser != null + && mCea708Parser.processClosedCaptions(mPresentationTimeUs); } private boolean feedInputBuffer() throws IOException, ExoPlaybackException { @@ -186,32 +185,36 @@ public class Cea708TextTrackRenderer extends TrackRenderer implements } mSampleHolder.data.clear(); mSampleHolder.size = 0; - int result = mSource.readData(mTrackIndex, mPresentationTimeUs, - mFormatHolder, mSampleHolder); + int result = + mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder); switch (result) { - case SampleSource.NOTHING_READ: { - return false; - } - case SampleSource.FORMAT_READ: { - if (DEBUG) { - Log.i(TAG, "Format was read again"); + case SampleSource.NOTHING_READ: + { + return false; } - return true; - } - case SampleSource.END_OF_STREAM: { - if (DEBUG) { - Log.i(TAG, "End of stream from SampleSource"); + case SampleSource.FORMAT_READ: + { + if (DEBUG) { + Log.i(TAG, "Format was read again"); + } + return true; } - mInputStreamEnded = true; - return false; - } - case SampleSource.SAMPLE_READ: { - mSampleHolder.data.flip(); - if (mCea708Parser != null && !mRenderingDisabled) { - mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs); + case SampleSource.END_OF_STREAM: + { + if (DEBUG) { + Log.i(TAG, "End of stream from SampleSource"); + } + mInputStreamEnded = true; + return false; + } + case SampleSource.SAMPLE_READ: + { + mSampleHolder.data.flip(); + if (mCea708Parser != null && !mRenderingDisabled) { + mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs); + } + return true; } - return true; - } } return false; } diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java index 0ab6d8c4..b5369d69 100644 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java +++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java @@ -15,16 +15,12 @@ */ package com.android.tv.tuner.exoplayer; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.TsExtractor; -import java.util.ArrayList; -import java.util.List; - /** * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for * H.264 stream @@ -34,8 +30,12 @@ public final class ExoPlayerExtractorsFactory implements ExtractorsFactory { public Extractor[] createExtractors() { // Only create TsExtractor since we only target MPEG2TS stream. Extractor[] extractors = { - new TsExtractor(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory( - DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), false) }; + new TsExtractor( + new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory( + DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), + false) + }; return extractors; } } diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java index 0b648400..df520900 100644 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java @@ -24,7 +24,11 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.util.Pair; - +import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; +import com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; +import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.SampleHolder; @@ -44,12 +48,6 @@ import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultAllocator; -import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; -import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; - import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -87,64 +85,90 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { private Handler mOnCompletionListenerHandler; private IOException mError; - public ExoPlayerSampleExtractor(Uri uri, final DataSource source, BufferManager bufferManager, - PlaybackBufferListener bufferListener, boolean isRecording) { + public ExoPlayerSampleExtractor( + Uri uri, + final DataSource source, + BufferManager bufferManager, + PlaybackBufferListener bufferListener, + boolean isRecording) { // It'll be used as a timeshift file chunk name's prefix. mId = System.currentTimeMillis(); - EventListener eventListener = new EventListener() { - @Override - public void onLoadError(IOException error) { - mError = error; - } - }; - - mSourceReaderThread = new HandlerThread("SourceReaderThread"); - mSourceReaderWorker = new SourceReaderWorker(new ExtractorMediaSource(uri, - new com.google.android.exoplayer2.upstream.DataSource.Factory() { + EventListener eventListener = + new EventListener() { @Override - public com.google.android.exoplayer2.upstream.DataSource createDataSource() { - // Returns an adapter implementation for ExoPlayer V2 DataSource interface. - return new com.google.android.exoplayer2.upstream.DataSource() { - @Override - public long open(DataSpec dataSpec) throws IOException { - return source.open( - new com.google.android.exoplayer.upstream.DataSpec( - dataSpec.uri, dataSpec.postBody, - dataSpec.absoluteStreamPosition, dataSpec.position, - dataSpec.length, dataSpec.key, dataSpec.flags)); - } - - @Override - public int read(byte[] buffer, int offset, int readLength) - throws IOException { - return source.read(buffer, offset, readLength); - } - - @Override - public Uri getUri() { - return null; - } - - @Override - public void close() throws IOException { - source.close(); - } - }; + public void onLoadError(IOException error) { + mError = error; } - }, - new ExoPlayerExtractorsFactory(), - // Do not create a handler if we not on a looper. e.g. test. - Looper.myLooper() != null ? new Handler() : null, eventListener)); + }; + + mSourceReaderThread = new HandlerThread("SourceReaderThread"); + mSourceReaderWorker = + new SourceReaderWorker( + new ExtractorMediaSource( + uri, + new com.google.android.exoplayer2.upstream.DataSource.Factory() { + @Override + public com.google.android.exoplayer2.upstream.DataSource + createDataSource() { + // Returns an adapter implementation for ExoPlayer V2 + // DataSource interface. + return new com.google.android.exoplayer2.upstream + .DataSource() { + @Override + public long open(DataSpec dataSpec) throws IOException { + return source.open( + new com.google.android.exoplayer.upstream + .DataSpec( + dataSpec.uri, + dataSpec.postBody, + dataSpec.absoluteStreamPosition, + dataSpec.position, + dataSpec.length, + dataSpec.key, + dataSpec.flags)); + } + + @Override + public int read( + byte[] buffer, int offset, int readLength) + throws IOException { + return source.read(buffer, offset, readLength); + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public void close() throws IOException { + source.close(); + } + }; + } + }, + new ExoPlayerExtractorsFactory(), + // Do not create a handler if we not on a looper. e.g. test. + Looper.myLooper() != null ? new Handler() : null, + eventListener)); if (isRecording) { - mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false, - RecordingSampleBuffer.BUFFER_REASON_RECORDING); + mSampleBuffer = + new RecordingSampleBuffer( + bufferManager, + bufferListener, + false, + RecordingSampleBuffer.BUFFER_REASON_RECORDING); } else { if (bufferManager == null) { mSampleBuffer = new SimpleSampleBuffer(bufferListener); } else { - mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true, - RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK); + mSampleBuffer = + new RecordingSampleBuffer( + bufferManager, + bufferListener, + true, + RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK); } } } @@ -173,39 +197,61 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { public SourceReaderWorker(MediaSource sampleSource) { mSampleSource = sampleSource; - mSampleSource.prepareSource(null, false, new MediaSource.Listener() { - @Override - public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { - // Dynamic stream change is not supported yet. b/28169263 - // For now, this will cause EOS and playback reset. - } - }); - mDecoderInputBuffer = new DecoderInputBuffer( - DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + mSampleSource.prepareSource( + null, + false, + new MediaSource.Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + // Dynamic stream change is not supported yet. b/28169263 + // For now, this will cause EOS and playback reset. + } + }); + mDecoderInputBuffer = + new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); } MediaFormat convertFormat(Format format) { if (format.sampleMimeType.startsWith("audio/")) { - return MediaFormat.createAudioFormat(format.id, format.sampleMimeType, - format.bitrate, format.maxInputSize, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.channelCount, - format.sampleRate, format.initializationData, format.language, + return MediaFormat.createAudioFormat( + format.id, + format.sampleMimeType, + format.bitrate, + format.maxInputSize, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, + format.channelCount, + format.sampleRate, + format.initializationData, + format.language, format.pcmEncoding); } else if (format.sampleMimeType.startsWith("video/")) { return MediaFormat.createVideoFormat( - format.id, format.sampleMimeType, format.bitrate, format.maxInputSize, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.width, format.height, - format.initializationData, format.rotationDegrees, - format.pixelWidthHeightRatio, format.projectionData, format.stereoMode); + format.id, + format.sampleMimeType, + format.bitrate, + format.maxInputSize, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, + format.width, + format.height, + format.initializationData, + format.rotationDegrees, + format.pixelWidthHeightRatio, + format.projectionData, + format.stereoMode); } else if (format.sampleMimeType.endsWith("/cea-608") || format.sampleMimeType.startsWith("text/")) { return MediaFormat.createTextFormat( - format.id, format.sampleMimeType, format.bitrate, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.language); + format.id, + format.sampleMimeType, + format.bitrate, + com.google.android.exoplayer.C.UNKNOWN_TIME_US, + format.language); } else { return MediaFormat.createFormatForMimeType( - format.id, format.sampleMimeType, format.bitrate, + format.id, + format.sampleMimeType, + format.bitrate, com.google.android.exoplayer.C.UNKNOWN_TIME_US); } } @@ -273,8 +319,11 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { case MSG_PREPARE: if (!mPrepareRequested) { mPrepareRequested = true; - mMediaPeriod = mSampleSource.createPeriod(0, - new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), 0); + mMediaPeriod = + mSampleSource.createPeriod( + 0, + new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), + 0); mMediaPeriod.prepare(this); try { mMediaPeriod.maybeThrowPrepareError(); @@ -288,8 +337,9 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { ConditionVariable conditionVariable = new ConditionVariable(); int trackCount = mStreams.length; for (int i = 0; i < trackCount; ++i) { - if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ - != fetchSample(i, mSampleHolder, conditionVariable)) { + if (!mTrackMetEos[i] + && C.RESULT_NOTHING_READ + != fetchSample(i, mSampleHolder, conditionVariable)) { if (mMetEos) { // If mMetEos was on during fetchSample() due to an error, // fetching from other tracks is not necessary. @@ -303,8 +353,8 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { if (didSomething) { mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); } else { - mSourceReaderHandler.sendEmptyMessageDelayed(MSG_FETCH_SAMPLES, - RETRY_INTERVAL_MS); + mSourceReaderHandler.sendEmptyMessageDelayed( + MSG_FETCH_SAMPLES, RETRY_INTERVAL_MS); } } else { notifyCompletionIfNeeded(false); @@ -323,8 +373,8 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { return false; } - private int fetchSample(int track, SampleHolder sample, - ConditionVariable conditionVariable) { + private int fetchSample( + int track, SampleHolder sample, ConditionVariable conditionVariable) { FormatHolder dummyFormatHolder = new FormatHolder(); mDecoderInputBuffer.clear(); int ret = mStreams[track].readData(dummyFormatHolder, mDecoderInputBuffer); @@ -339,7 +389,8 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { if (lastExtractedPositionUs == null) { mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs); } else { - mLastExtractedPositionUsMap.put(track, + mLastExtractedPositionUsMap.put( + track, Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs)); } queueSample(track, conditionVariable); @@ -373,10 +424,15 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); mSampleHolder.flags = (mDecoderInputBuffer.isKeyFrame() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0) - | (mDecoderInputBuffer.isDecodeOnly() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY - : 0); + ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC + : 0) + | (mDecoderInputBuffer.isDecodeOnly() + ? com.google + .android + .exoplayer + .C + .SAMPLE_FLAG_DECODE_ONLY + : 0); sample.timeUs = mDecoderInputBuffer.timeUs; sample.size = mDecoderInputBuffer.data.position(); sample.ensureSpaceForWrite(sample.size); @@ -399,8 +455,7 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } mPendingSamples.clear(); } else { - if (mDecoderInputBuffer.timeUs < mBaseSamplePts - && mVideoTrackIndex != index) { + if (mDecoderInputBuffer.timeUs < mBaseSamplePts && mVideoTrackIndex != index) { return; } } @@ -409,9 +464,11 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { mSampleHolder.clearData(); mSampleHolder.flags = (mDecoderInputBuffer.isKeyFrame() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0) - | (mDecoderInputBuffer.isDecodeOnly() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY : 0); + ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC + : 0) + | (mDecoderInputBuffer.isDecodeOnly() + ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY + : 0); mSampleHolder.timeUs = mDecoderInputBuffer.timeUs; mSampleHolder.size = mDecoderInputBuffer.data.position(); mSampleHolder.ensureSpaceForWrite(mSampleHolder.size); @@ -423,8 +480,8 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable); // Checks whether the storage has enough bandwidth for recording samples. - if (mSampleBuffer.isWriteSpeedSlow(mSampleHolder.size, - SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { + if (mSampleBuffer.isWriteSpeedSlow( + mSampleHolder.size, SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { mSampleBuffer.handleWriteSpeedSlow(); } } @@ -443,8 +500,8 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { public boolean prepare() throws IOException { if (!mSourceReaderThread.isAlive()) { mSourceReaderThread.start(); - mSourceReaderHandler = new Handler(mSourceReaderThread.getLooper(), - mSourceReaderWorker); + mSourceReaderHandler = + new Handler(mSourceReaderThread.getLooper(), mSourceReaderWorker); mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_PREPARE); } if (mExceptionOnPrepare != null) { @@ -527,12 +584,13 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { final OnCompletionListener listener = mOnCompletionListener; final long lastExtractedPositionUs = getLastExtractedPositionUs(); if (mOnCompletionListenerHandler != null && mOnCompletionListener != null) { - mOnCompletionListenerHandler.post(new Runnable() { - @Override - public void run() { - listener.onCompletion(result, lastExtractedPositionUs); - } - }); + mOnCompletionListenerHandler.post( + new Runnable() { + @Override + public void run() { + listener.onCompletion(result, lastExtractedPositionUs); + } + }); } } } diff --git a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java index b7e42a7c..e7224422 100644 --- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java @@ -16,25 +16,23 @@ package com.android.tv.tuner.exoplayer; +import android.os.Handler; +import com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaFormatUtil; import com.google.android.exoplayer.SampleHolder; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; - -import android.os.Handler; - import java.io.IOException; import java.util.ArrayList; import java.util.List; /** - * A class that plays a recorded stream without using {@link android.media.MediaExtractor}, - * since all samples are extracted and stored to the permanent storage already. + * A class that plays a recorded stream without using {@link android.media.MediaExtractor}, since + * all samples are extracted and stored to the permanent storage already. */ -public class FileSampleExtractor implements SampleExtractor{ +public class FileSampleExtractor implements SampleExtractor { private static final String TAG = "FileSampleExtractor"; private static final boolean DEBUG = false; @@ -46,8 +44,7 @@ public class FileSampleExtractor implements SampleExtractor{ private final PlaybackBufferListener mBufferListener; private BufferManager.SampleBuffer mSampleBuffer; - public FileSampleExtractor( - BufferManager bufferManager, PlaybackBufferListener bufferListener) { + public FileSampleExtractor(BufferManager bufferManager, PlaybackBufferListener bufferListener) { mBufferManager = bufferManager; mBufferListener = bufferListener; mTrackCount = -1; @@ -72,8 +69,12 @@ public class FileSampleExtractor implements SampleExtractor{ ids.add(trackFormat.trackId); mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format)); } - mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true, - RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK); + mSampleBuffer = + new RecordingSampleBuffer( + mBufferManager, + mBufferListener, + true, + RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK); mSampleBuffer.init(ids, mTrackFormats); return true; } @@ -134,5 +135,5 @@ public class FileSampleExtractor implements SampleExtractor{ } @Override - public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { } + public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {} } diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java index 2694298a..a49cbfaf 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java @@ -23,7 +23,16 @@ import android.media.PlaybackParams; import android.os.Handler; import android.support.annotation.IntDef; import android.view.Surface; - +import com.android.tv.common.SoftPreconditions; +import com.android.tv.tuner.data.Cea708Data; +import com.android.tv.tuner.data.Cea708Data.CaptionEvent; +import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; +import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer; +import com.android.tv.tuner.source.TsDataSource; +import com.android.tv.tuner.source.TsDataSourceManager; +import com.android.tv.tuner.tvinput.EventDetector; +import com.android.tv.tuner.tvinput.TunerDebug; import com.google.android.exoplayer.DummyTrackRenderer; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlayer; @@ -35,17 +44,6 @@ import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.upstream.DataSource; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.data.Cea708Data; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; -import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer; -import com.android.tv.tuner.source.TsDataSource; -import com.android.tv.tuner.source.TsDataSourceManager; -import com.android.tv.tuner.tvinput.EventDetector; -import com.android.tv.tuner.tvinput.TunerDebug; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -57,52 +55,46 @@ public class MpegTsPlayer MpegTsMediaCodecAudioTrackRenderer.Ac3EventListener { private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER; - /** - * Interface definition for building specific track renderers. - */ + /** Interface definition for building specific track renderers. */ public interface RendererBuilder { - void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource, - boolean hasSoftwareAudioDecoder, RendererBuilderCallback callback); + void buildRenderers( + MpegTsPlayer mpegTsPlayer, + DataSource dataSource, + boolean hasSoftwareAudioDecoder, + RendererBuilderCallback callback); } - /** - * Interface definition for {@link RendererBuilder#buildRenderers} to notify the result. - */ + /** Interface definition for {@link RendererBuilder#buildRenderers} to notify the result. */ public interface RendererBuilderCallback { void onRenderers(String[][] trackNames, TrackRenderer[] renderers); + void onRenderersError(Exception e); } - /** - * Interface definition for a callback to be notified of changes in player state. - */ + /** Interface definition for a callback to be notified of changes in player state. */ public interface Listener { void onStateChanged(boolean playWhenReady, int playbackState); + void onError(Exception e); - void onVideoSizeChanged(int width, int height, - float pixelWidthHeightRatio); + + void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio); + void onDrawnToSurface(MpegTsPlayer player, Surface surface); + void onAudioUnplayable(); + void onSmoothTrickplayForceStopped(); } - /** - * Interface definition for a callback to be notified of changes on video display. - */ + /** Interface definition for a callback to be notified of changes on video display. */ public interface VideoEventListener { - /** - * Notifies the caption event. - */ + /** Notifies the caption event. */ void onEmitCaptionEvent(CaptionEvent event); - /** - * Notifies clearing up whole closed caption event. - */ + /** Notifies clearing up whole closed caption event. */ void onClearCaptionEvent(); - /** - * Notifies the discovered caption service number. - */ + /** Notifies the discovered caption service number. */ void onDiscoverCaptionServiceNumber(int serviceNumber); } @@ -113,14 +105,19 @@ public class MpegTsPlayer @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT}) @Retention(RetentionPolicy.SOURCE) public @interface TrackType {} + public static final int TRACK_TYPE_VIDEO = 0; public static final int TRACK_TYPE_AUDIO = 1; public static final int TRACK_TYPE_TEXT = 2; - @IntDef({RENDERER_BUILDING_STATE_IDLE, RENDERER_BUILDING_STATE_BUILDING, - RENDERER_BUILDING_STATE_BUILT}) + @IntDef({ + RENDERER_BUILDING_STATE_IDLE, + RENDERER_BUILDING_STATE_BUILDING, + RENDERER_BUILDING_STATE_BUILT + }) @Retention(RetentionPolicy.SOURCE) public @interface RendererBuildingState {} + private static final int RENDERER_BUILDING_STATE_IDLE = 1; private static final int RENDERER_BUILDING_STATE_BUILDING = 2; private static final int RENDERER_BUILDING_STATE_BUILT = 3; @@ -157,8 +154,11 @@ public class MpegTsPlayer * @param capabilities the {@link AudioCapabilities} of the current device * @param listener the listener for playback state changes */ - public MpegTsPlayer(RendererBuilder rendererBuilder, Handler handler, - TsDataSourceManager sourceManager, AudioCapabilities capabilities, + public MpegTsPlayer( + RendererBuilder rendererBuilder, + Handler handler, + TsDataSourceManager sourceManager, + AudioCapabilities capabilities, Listener listener) { mRendererBuilder = rendererBuilder; mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS); @@ -188,8 +188,10 @@ public class MpegTsPlayer public void setCaptionServiceNumber(int captionServiceNumber) { mCaptionServiceNumber = captionServiceNumber; if (mTextRenderer != null) { - mPlayer.sendMessage(mTextRenderer, - Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber); + mPlayer.sendMessage( + mTextRenderer, + Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, + mCaptionServiceNumber); } } @@ -203,16 +205,12 @@ public class MpegTsPlayer pushSurface(false); } - /** - * Returns the current surface of the player. - */ + /** Returns the current surface of the player. */ public Surface getSurface() { return mSurface; } - /** - * Clears the surface and waits until the surface is being cleaned. - */ + /** Clears the surface and waits until the surface is being cleaned. */ public void blockingClearSurface() { mSurface = null; pushSurface(true); @@ -220,13 +218,17 @@ public class MpegTsPlayer /** * Creates renderers and {@link DataSource} and initializes player. + * * @param context a {@link Context} instance * @param channel to play * @param hasSoftwareAudioDecoder {@code true} if there is connected software decoder * @param eventListener for program information which will be scanned from MPEG2-TS stream * @return true when everything is created and initialized well, false otherwise */ - public boolean prepare(Context context, TunerChannel channel, boolean hasSoftwareAudioDecoder, + public boolean prepare( + Context context, + TunerChannel channel, + boolean hasSoftwareAudioDecoder, EventDetector.EventListener eventListener) { TsDataSource source = null; if (channel != null) { @@ -248,9 +250,7 @@ public class MpegTsPlayer return true; } - /** - * Returns {@link TsDataSource} which provides MPEG2-TS stream. - */ + /** Returns {@link TsDataSource} which provides MPEG2-TS stream. */ public TsDataSource getDataSource() { return mDataSource; } @@ -288,18 +288,15 @@ public class MpegTsPlayer /** * Sets the player state to pause or play. * - * @param playWhenReady sets the player state to being ready to play when {@code true}, - * sets the player state to being paused when {@code false} - * + * @param playWhenReady sets the player state to being ready to play when {@code true}, sets the + * player state to being paused when {@code false} */ public void setPlayWhenReady(boolean playWhenReady) { mPlayer.setPlayWhenReady(playWhenReady); stopSmoothTrickplay(false); } - /** - * Returns true, if trickplay is supported. - */ + /** Returns true, if trickplay is supported. */ public boolean supportSmoothTrickPlay(float playbackSpeed) { return playbackSpeed > MIN_SMOOTH_TRICKPLAY_SPEED && playbackSpeed < MAX_SMOOTH_TRICKPLAY_SPEED; @@ -318,7 +315,8 @@ public class MpegTsPlayer MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, playbackParams.getSpeed()); } else { - mPlayer.sendMessage(mAudioRenderer, + mPlayer.sendMessage( + mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS, playbackParams); } @@ -329,10 +327,12 @@ public class MpegTsPlayer mTrickplayRunning = false; if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { mPlayer.sendMessage( - mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, + mAudioRenderer, + MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, 1.0f); } else { - mPlayer.sendMessage(mAudioRenderer, + mPlayer.sendMessage( + mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS, new PlaybackParams().setSpeed(1.0f)); } @@ -352,9 +352,7 @@ public class MpegTsPlayer stopSmoothTrickplay(true); } - /** - * Releases the player. - */ + /** Releases the player. */ public void release() { if (mDataSource != null) { mSourceManager.releaseDataSource(mDataSource); @@ -370,57 +368,45 @@ public class MpegTsPlayer mPlayer.release(); } - /** - * Returns the current status of the player. - */ - public int getPlaybackState() { + /** Returns the current status of the player. */ + public int getPlaybackState() { if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { return ExoPlayer.STATE_PREPARING; } return mPlayer.getPlaybackState(); } - /** - * Returns {@code true} when the player is prepared to play, {@code false} otherwise. - */ - public boolean isPrepared() { + /** Returns {@code true} when the player is prepared to play, {@code false} otherwise. */ + public boolean isPrepared() { int state = getPlaybackState(); return state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING; } - /** - * Returns {@code true} when the player is being ready to play, {@code false} otherwise. - */ + /** Returns {@code true} when the player is being ready to play, {@code false} otherwise. */ public boolean isPlaying() { int state = getPlaybackState(); return (state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING) && mPlayer.getPlayWhenReady(); } - /** - * Returns {@code true} when the player is buffering, {@code false} otherwise. - */ + /** Returns {@code true} when the player is buffering, {@code false} otherwise. */ public boolean isBuffering() { return getPlaybackState() == ExoPlayer.STATE_BUFFERING; } - /** - * Returns the current position of the playback in milli seconds. - */ + /** Returns the current position of the playback in milli seconds. */ public long getCurrentPosition() { return mPlayer.getCurrentPosition(); } - /** - * Returns the total duration of the playback. - */ + /** Returns the total duration of the playback. */ public long getDuration() { return mPlayer.getDuration(); } /** - * Returns {@code true} when the player is being ready to play, - * {@code false} when the player is paused. + * Returns {@code true} when the player is being ready to play, {@code false} when the player is + * paused. */ public boolean getPlayWhenReady() { return mPlayer.getPlayWhenReady(); @@ -434,11 +420,11 @@ public class MpegTsPlayer public void setVolume(float volume) { mVolume = volume; if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME, - volume); + mPlayer.sendMessage( + mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME, volume); } else { - mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, - volume); + mPlayer.sendMessage( + mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume); } } @@ -449,57 +435,49 @@ public class MpegTsPlayer */ public void setAudioTrackAndClosedCaption(boolean enable) { if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK, + mPlayer.sendMessage( + mAudioRenderer, + MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0); } else { - mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, + mPlayer.sendMessage( + mAudioRenderer, + MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, enable ? mVolume : 0.0f); } - mPlayer.sendMessage(mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION, - enable); + mPlayer.sendMessage( + mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION, enable); } - /** - * Returns {@code true} when AC3 audio can be played, {@code false} otherwise. - */ + /** Returns {@code true} when AC3 audio can be played, {@code false} otherwise. */ public boolean isAc3Playable() { return mAudioCapabilities != null && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3); } - /** - * Notifies when the audio cannot be played by the current device. - */ + /** Notifies when the audio cannot be played by the current device. */ public void onAudioUnplayable() { if (mListener != null) { mListener.onAudioUnplayable(); } } - /** - * Returns {@code true} if the player has any video track, {@code false} otherwise. - */ + /** Returns {@code true} if the player has any video track, {@code false} otherwise. */ public boolean hasVideo() { return mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0; } - /** - * Returns {@code true} if the player has any audio trock, {@code false} otherwise. - */ + /** Returns {@code true} if the player has any audio trock, {@code false} otherwise. */ public boolean hasAudio() { return mPlayer.getTrackCount(TRACK_TYPE_AUDIO) > 0; } - /** - * Returns the number of tracks exposed by the specified renderer. - */ + /** Returns the number of tracks exposed by the specified renderer. */ public int getTrackCount(int rendererIndex) { return mPlayer.getTrackCount(rendererIndex); } - /** - * Selects a track for the specified renderer. - */ + /** Selects a track for the specified renderer. */ public void setSelectedTrack(int rendererIndex, int trackIndex) { if (trackIndex >= getTrackCount(rendererIndex)) { return; @@ -511,8 +489,8 @@ public class MpegTsPlayer * Returns the index of the currently selected track for the specified renderer. * * @param rendererIndex The index of the renderer. - * @return The selected track. A negative value or a value greater than or equal to the renderer's - * track count indicates that the renderer is disabled. + * @return The selected track. A negative value or a value greater than or equal to the + * renderer's track count indicates that the renderer is disabled. */ public int getSelectedTrack(int rendererIndex) { return mPlayer.getSelectedTrack(rendererIndex); @@ -529,9 +507,7 @@ public class MpegTsPlayer return mPlayer.getTrackFormat(rendererIndex, trackIndex); } - /** - * Gets the main handler of the player. - */ + /** Gets the main handler of the player. */ /* package */ Handler getMainHandler() { return mMainHandler; } @@ -542,11 +518,11 @@ public class MpegTsPlayer return; } mListener.onStateChanged(playWhenReady, state); - if (state == ExoPlayer.STATE_READY && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0 + if (state == ExoPlayer.STATE_READY + && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0 && playWhenReady) { MediaFormat format = mPlayer.getTrackFormat(TRACK_TYPE_VIDEO, 0); - mListener.onVideoSizeChanged(format.width, - format.height, format.pixelWidthHeightRatio); + mListener.onVideoSizeChanged(format.width, format.height, format.pixelWidthHeightRatio); } } @@ -559,16 +535,16 @@ public class MpegTsPlayer } @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio) { + public void onVideoSizeChanged( + int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (mListener != null) { mListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio); } } @Override - public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, - long initializationDurationMs) { + public void onDecoderInitialized( + String decoderName, long elapsedRealtimeMs, long initializationDurationMs) { // Do nothing. } @@ -590,8 +566,8 @@ public class MpegTsPlayer } @Override - public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, - long elapsedSinceLastFeedMs) { + public void onAudioTrackUnderrun( + int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { // Do nothing. } @@ -693,4 +669,4 @@ public class MpegTsPlayer } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java index 006ccac2..c7f5b333 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java @@ -17,43 +17,45 @@ package com.android.tv.tuner.exoplayer; import android.content.Context; - -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.upstream.DataSource; import com.android.tv.Features; import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder; import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback; import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.tvinput.PlaybackBufferListener; +import com.google.android.exoplayer.MediaCodecSelector; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.TrackRenderer; +import com.google.android.exoplayer.upstream.DataSource; -/** - * Builder for renderer objects for {@link MpegTsPlayer}. - */ +/** Builder for renderer objects for {@link MpegTsPlayer}. */ public class MpegTsRendererBuilder implements RendererBuilder { private final Context mContext; private final BufferManager mBufferManager; private final PlaybackBufferListener mBufferListener; - public MpegTsRendererBuilder(Context context, BufferManager bufferManager, - PlaybackBufferListener bufferListener) { + public MpegTsRendererBuilder( + Context context, BufferManager bufferManager, PlaybackBufferListener bufferListener) { mContext = context; mBufferManager = bufferManager; mBufferListener = bufferListener; } @Override - public void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource, - boolean mHasSoftwareAudioDecoder, RendererBuilderCallback callback) { + public void buildRenderers( + MpegTsPlayer mpegTsPlayer, + DataSource dataSource, + boolean mHasSoftwareAudioDecoder, + RendererBuilderCallback callback) { // Build the video and audio renderers. - SampleExtractor extractor = dataSource == null ? - new MpegTsSampleExtractor(mBufferManager, mBufferListener) : - new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener); + SampleExtractor extractor = + dataSource == null + ? new MpegTsSampleExtractor(mBufferManager, mBufferListener) + : new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener); SampleSource sampleSource = new MpegTsSampleSource(extractor); - MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext, - sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer); + MpegTsVideoTrackRenderer videoRenderer = + new MpegTsVideoTrackRenderer( + mContext, sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer); // TODO: Only using MpegTsDefaultAudioTrackRenderer for A/V sync issue. We will use // {@link MpegTsMediaCodecAudioTrackRenderer} when we use ExoPlayer's extractor. TrackRenderer audioRenderer = diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java index 7bf116c8..593b576e 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java @@ -18,26 +18,22 @@ package com.android.tv.tuner.exoplayer; import android.net.Uri; import android.os.Handler; - +import com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.SamplePool; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.util.MimeTypes; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.SamplePool; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; - import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -/** - * Extracts samples from {@link DataSource} for MPEG-TS streams. - */ +/** Extracts samples from {@link DataSource} for MPEG-TS streams. */ public final class MpegTsSampleExtractor implements SampleExtractor { public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708"; @@ -66,13 +62,14 @@ public final class MpegTsSampleExtractor implements SampleExtractor { * * @param source the {@link DataSource} to extract from * @param bufferManager the manager for reading & writing samples backed by physical storage - * @param bufferListener the {@link PlaybackBufferListener} - * to notify buffer storage status change + * @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status + * change */ - public MpegTsSampleExtractor(DataSource source, BufferManager bufferManager, - PlaybackBufferListener bufferListener) { - mSampleExtractor = new ExoPlayerSampleExtractor(Uri.EMPTY, source, bufferManager, - bufferListener, false); + public MpegTsSampleExtractor( + DataSource source, BufferManager bufferManager, PlaybackBufferListener bufferListener) { + mSampleExtractor = + new ExoPlayerSampleExtractor( + Uri.EMPTY, source, bufferManager, bufferListener, false); init(); } @@ -80,11 +77,11 @@ public final class MpegTsSampleExtractor implements SampleExtractor { * Creates MpegTsSampleExtractor for a recorded program. * * @param bufferManager the samples provider which is stored in physical storage - * @param bufferListener the {@link PlaybackBufferListener} - * to notify buffer storage status change + * @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status + * change */ - public MpegTsSampleExtractor(BufferManager bufferManager, - PlaybackBufferListener bufferListener) { + public MpegTsSampleExtractor( + BufferManager bufferManager, PlaybackBufferListener bufferListener) { mSampleExtractor = new FileSampleExtractor(bufferManager, bufferListener); init(); } @@ -98,7 +95,7 @@ public final class MpegTsSampleExtractor implements SampleExtractor { @Override public boolean prepare() throws IOException { - if(!mSampleExtractor.prepare()) { + if (!mSampleExtractor.prepare()) { return false; } List formats = mSampleExtractor.getTrackFormats(); @@ -124,8 +121,9 @@ public final class MpegTsSampleExtractor implements SampleExtractor { mCea708TextTrackIndex = trackCount; } if (mCea708TextTrackIndex >= 0) { - mTrackFormats.add(MediaFormat.createTextFormat(null, MIMETYPE_TEXT_CEA_708, 0, - mTrackFormats.get(0).durationUs, "")); + mTrackFormats.add( + MediaFormat.createTextFormat( + null, MIMETYPE_TEXT_CEA_708, 0, mTrackFormats.get(0).durationUs, "")); } return true; } @@ -186,23 +184,27 @@ public final class MpegTsSampleExtractor implements SampleExtractor { return SampleSource.SAMPLE_READ; } else { return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex) - ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ; + ? SampleSource.END_OF_STREAM + : SampleSource.NOTHING_READ; } } int result = mSampleExtractor.readSample(track, sampleHolder); switch (result) { - case SampleSource.END_OF_STREAM: { - mReachedEos.set(track, true); - break; - } - case SampleSource.SAMPLE_READ: { - if (mCea708TextTrackSelected && track == mVideoTrackIndex - && sampleHolder.data != null) { - mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs); + case SampleSource.END_OF_STREAM: + { + mReachedEos.set(track, true); + break; + } + case SampleSource.SAMPLE_READ: + { + if (mCea708TextTrackSelected + && track == mVideoTrackIndex + && sampleHolder.data != null) { + mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs); + } + break; } - break; - } } return result; } @@ -221,7 +223,7 @@ public final class MpegTsSampleExtractor implements SampleExtractor { } @Override - public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { } + public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {} private abstract class CcParser { // Interim buffer for reduce direct access to ByteBuffer which is expensive. Using @@ -278,8 +280,12 @@ public final class MpegTsSampleExtractor implements SampleExtractor { && mBuffer[j + 6] == '9' && mBuffer[j + 7] == '4' && mBuffer[j + 8] == 3) { - j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH, - presentationTimeUs) - i; + j = + parseClosedCaption( + buffer, + i + j + PATTERN_LENGTH, + presentationTimeUs) + - i; } else { j += PATTERN_LENGTH; } @@ -307,20 +313,24 @@ public final class MpegTsSampleExtractor implements SampleExtractor { int j = 0; while (j < size - PATTERN_LENGTH) { // Find the start prefix code of a NAL Unit. - if (mBuffer[j] == 0 - && mBuffer[j + 1] == 0 - && mBuffer[j + 2] == 1) { + if (mBuffer[j] == 0 && mBuffer[j + 1] == 0 && mBuffer[j + 2] == 1) { int nalType = mBuffer[j + 3] & 0x1f; int payloadType = mBuffer[j + 4] & 0xff; // ATSC closed caption data embedded in H264 private user data has NAL type // 6, payload type 4, and 'GA94' user identifier for ATSC. - if (nalType == 6 && payloadType == 4 && mBuffer[j + 9] == 'G' + if (nalType == 6 + && payloadType == 4 + && mBuffer[j + 9] == 'G' && mBuffer[j + 10] == 'A' && mBuffer[j + 11] == '9' && mBuffer[j + 12] == '4') { - j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH, - presentationTimeUs) - i; + j = + parseClosedCaption( + buffer, + i + j + PATTERN_LENGTH, + presentationTimeUs) + - i; } else { j += 7; } diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java index 6007b0be..3b5d1011 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java @@ -22,7 +22,6 @@ import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource.SampleSourceReader; import com.google.android.exoplayer.util.Assertions; - import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -132,8 +131,8 @@ public final class MpegTsSampleSource implements SampleSource, SampleSourceReade } @Override - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { + public int readData( + int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) { Assertions.checkState(mPrepared); Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); if (mPendingDiscontinuities.get(track)) { diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java index 19360c69..c4400b47 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java @@ -4,7 +4,7 @@ import android.content.Context; import android.media.MediaCodec; import android.os.Handler; import android.util.Log; - +import com.android.tv.common.feature.CommonFeatures; import com.google.android.exoplayer.DecoderInfo; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaCodecSelector; @@ -13,13 +13,9 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaSoftwareCodecUtil; import com.google.android.exoplayer.SampleSource; -import com.android.tv.common.feature.CommonFeatures; - import java.lang.reflect.Field; -/** - * MPEG-2 TS video track renderer - */ +/** MPEG-2 TS video track renderer */ public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { private static final String TAG = "MpegTsVideoTrackRender"; @@ -37,42 +33,54 @@ public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { static { // Remove the reflection below once b/31223646 is resolved. try { - sRenderedFirstFrameField = MediaCodecVideoTrackRenderer.class.getDeclaredField( - "renderedFirstFrame"); + sRenderedFirstFrameField = + MediaCodecVideoTrackRenderer.class.getDeclaredField("renderedFirstFrame"); sRenderedFirstFrameField.setAccessible(true); } catch (NoSuchFieldException e) { // Null-checking for {@code sRenderedFirstFrameField} will do the error handling. } } - public MpegTsVideoTrackRenderer(Context context, SampleSource source, Handler handler, + public MpegTsVideoTrackRenderer( + Context context, + SampleSource source, + Handler handler, MediaCodecVideoTrackRenderer.EventListener listener) { - super(context, source, MediaCodecSelector.DEFAULT, - MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_PLAYBACK_DEADLINE_IN_MS, handler, - listener, DROPPED_FRAMES_NOTIFICATION_THRESHOLD); + super( + context, + source, + MediaCodecSelector.DEFAULT, + MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, + VIDEO_PLAYBACK_DEADLINE_IN_MS, + handler, + listener, + DROPPED_FRAMES_NOTIFICATION_THRESHOLD); mIsSwCodecEnabled = CommonFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context); } @Override - protected DecoderInfo getDecoderInfo(MediaCodecSelector codecSelector, String mimeType, - boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException { + protected DecoderInfo getDecoderInfo( + MediaCodecSelector codecSelector, String mimeType, boolean requiresSecureDecoder) + throws MediaCodecUtil.DecoderQueryException { try { if (mIsSwCodecEnabled && mCodecIsSwPreferred) { - DecoderInfo swCodec = MediaSoftwareCodecUtil.getSoftwareDecoderInfo( - mimeType, requiresSecureDecoder); + DecoderInfo swCodec = + MediaSoftwareCodecUtil.getSoftwareDecoderInfo( + mimeType, requiresSecureDecoder); if (swCodec != null) { return swCodec; } } } catch (MediaSoftwareCodecUtil.DecoderQueryException e) { } - return super.getDecoderInfo(codecSelector, mimeType,requiresSecureDecoder); + return super.getDecoderInfo(codecSelector, mimeType, requiresSecureDecoder); } @Override protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { - mCodecIsSwPreferred = MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType) - && holder.format.height < MIN_HD_HEIGHT; + mCodecIsSwPreferred = + MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType) + && holder.format.height < MIN_HD_HEIGHT; super.onInputFormatChanged(holder); } @@ -93,8 +101,10 @@ public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { try { sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame); } catch (IllegalAccessException e) { - Log.w(TAG, "renderedFirstFrame is not accessible. Playback may start with a frozen" - +" picture."); + Log.w( + TAG, + "renderedFirstFrame is not accessible. Playback may start with a frozen" + + " picture."); } } } diff --git a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java b/src/com/android/tv/tuner/exoplayer/SampleExtractor.java index 543588c7..256aea92 100644 --- a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/SampleExtractor.java @@ -16,25 +16,23 @@ package com.android.tv.tuner.exoplayer; import android.os.Handler; - import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackRenderer; - import java.io.IOException; import java.util.List; /** * Extractor for reading track metadata and samples stored in tracks. * - *

Call {@link #prepare} until it returns {@code true}, then access track metadata via - * {@link #getTrackFormats} and {@link #getTrackMediaFormat}. + *

Call {@link #prepare} until it returns {@code true}, then access track metadata via {@link + * #getTrackFormats} and {@link #getTrackMediaFormat}. * *

Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected - * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample - * data or seeking. Initially, all tracks are deselected. + * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample data + * or seeking. Initially, all tracks are deselected. * *

Call {@link #release()} when the extractor is no longer needed to free resources. */ @@ -49,11 +47,11 @@ public interface SampleExtractor { void maybeThrowError() throws IOException; /** - * Prepares the extractor for reading track metadata and samples. - * - * @return whether the source is ready; if {@code false}, this method must be called again. - * @throws IOException thrown if the source can't be read - */ + * Prepares the extractor for reading track metadata and samples. + * + * @return whether the source is ready; if {@code false}, this method must be called again. + * @throws IOException thrown if the source can't be read + */ boolean prepare() throws IOException; /** Returns track information about all tracks that can be selected. */ @@ -66,42 +64,42 @@ public interface SampleExtractor { void deselectTrack(int index); /** - * Returns an estimate of the position up to which data is buffered. - * - *

This method should not be called until after the extractor has been successfully prepared. - * - * @return an estimate of the absolute position in microseconds up to which data is buffered, - * or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or - * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available. - */ + * Returns an estimate of the position up to which data is buffered. + * + *

This method should not be called until after the extractor has been successfully prepared. + * + * @return an estimate of the absolute position in microseconds up to which data is buffered, or + * {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or + * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available. + */ long getBufferedPositionUs(); /** - * Seeks to the specified time in microseconds. - * - *

This method should not be called until after the extractor has been successfully prepared. - * - * @param positionUs the seek position in microseconds - */ + * Seeks to the specified time in microseconds. + * + *

This method should not be called until after the extractor has been successfully prepared. + * + * @param positionUs the seek position in microseconds + */ void seekTo(long positionUs); /** Stores the {@link MediaFormat} of {@code track}. */ void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder); /** - * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, returning - * {@link SampleSource#SAMPLE_READ} if it is available. - * - *

Advances to the next sample if a sample was read. - * - * @param track the index of the track from which to read a sample - * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is - * returned - * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or - * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or - * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not - * loaded. - */ + * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, + * returning {@link SampleSource#SAMPLE_READ} if it is available. + * + *

Advances to the next sample if a sample was read. + * + * @param track the index of the track from which to read a sample + * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is + * returned + * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or + * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or + * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not + * loaded. + */ int readSample(int track, SampleHolder sampleHolder); /** Releases resources associated with this extractor. */ @@ -118,17 +116,14 @@ public interface SampleExtractor { */ void setOnCompletionListener(OnCompletionListener listener, Handler handler); - /** - * The listener for SampleExtractor being completed. - */ + /** The listener for SampleExtractor being completed. */ interface OnCompletionListener { /** * Called when sample extraction is completed. * - * @param result {@code true} when the extractor is finished without an error, - * {@code false} otherwise (storage error, weak signal, being reached at EoS - * prematurely, etc.) + * @param result {@code true} when the extractor is finished without an error, {@code false} + * otherwise (storage error, weak signal, being reached at EoS prematurely, etc.) * @param lastExtractedPositionUs the last extracted position when extractor is completed */ void onCompletion(boolean result, long lastExtractedPositionUs); diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java index 5666c5b9..13eabc3a 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java @@ -16,37 +16,32 @@ package com.android.tv.tuner.exoplayer.audio; -import com.android.tv.common.SoftPreconditions; - import android.os.SystemClock; +import com.android.tv.common.SoftPreconditions; /** * Copy of {@link com.google.android.exoplayer.MediaClock}. - *

- * A simple clock for tracking the progression of media time. The clock can be started, stopped and - * its time can be set and retrieved. When started, this clock is based on - * {@link SystemClock#elapsedRealtime()}. + * + *

A simple clock for tracking the progression of media time. The clock can be started, stopped + * and its time can be set and retrieved. When started, this clock is based on {@link + * SystemClock#elapsedRealtime()}. */ /* package */ class AudioClock { private boolean mStarted; - /** - * The media time when the clock was last set or stopped. - */ + /** The media time when the clock was last set or stopped. */ private long mPositionUs; /** - * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs} - * when the clock was last set or mStarted. + * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs} when + * the clock was last set or mStarted. */ private long mDeltaUs; private float mPlaybackSpeed = 1.0f; private long mDeltaUpdatedTimeUs; - /** - * Starts the clock. Does nothing if the clock is already started. - */ + /** Starts the clock. Does nothing if the clock is already started. */ public void start() { if (!mStarted) { mStarted = true; @@ -55,9 +50,7 @@ import android.os.SystemClock; } } - /** - * Stops the clock. Does nothing if the clock is already stopped. - */ + /** Stops the clock. Does nothing if the clock is already stopped. */ public void stop() { if (mStarted) { mPositionUs = elapsedRealtimeMinus(mDeltaUs); @@ -65,25 +58,21 @@ import android.os.SystemClock; } } - /** - * @param timeUs The position to set in microseconds. - */ + /** @param timeUs The position to set in microseconds. */ public void setPositionUs(long timeUs) { this.mPositionUs = timeUs; mDeltaUs = elapsedRealtimeMinus(timeUs); mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; } - /** - * @return The current position in microseconds. - */ + /** @return The current position in microseconds. */ public long getPositionUs() { if (!mStarted) { return mPositionUs; } if (mPlaybackSpeed != 1.0f) { - long elapsedTimeFromPlaybackSpeedChanged = SystemClock.elapsedRealtime() * 1000 - - mDeltaUpdatedTimeUs; + long elapsedTimeFromPlaybackSpeedChanged = + SystemClock.elapsedRealtime() * 1000 - mDeltaUpdatedTimeUs; return elapsedRealtimeMinus(mDeltaUs) + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged); } else { @@ -91,9 +80,7 @@ import android.os.SystemClock; } } - /** - * Sets playback speed. {@code speed} should be positive. - */ + /** Sets playback speed. {@code speed} should be positive. */ public void setPlaybackSpeed(float speed) { SoftPreconditions.checkState(speed > 0); mDeltaUs = elapsedRealtimeMinus(getPositionUs()); diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java index e581092a..fa489883 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java @@ -19,7 +19,6 @@ package com.android.tv.tuner.exoplayer.audio; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; - import java.nio.ByteBuffer; /** A base class for audio decoders. */ diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java index ec616b13..28389017 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java @@ -19,15 +19,12 @@ package com.android.tv.tuner.exoplayer.audio; import android.os.SystemClock; import android.util.Log; import android.util.Pair; - import com.google.android.exoplayer.util.MimeTypes; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; -/** - * Monitors the rendering position of {@link AudioTrack}. - */ +/** Monitors the rendering position of {@link AudioTrack}. */ public class AudioTrackMonitor { private static final String TAG = "AudioTrackMonitor"; private static final boolean DEBUG = false; @@ -98,30 +95,44 @@ public class AudioTrackMonitor { /** * Logs if interested events are present. - *

- * Periodic logging is not enabled in release mode in order to avoid verbose logging. + * + *

Periodic logging is not enabled in release mode in order to avoid verbose logging. */ public void maybeLog() { long now = SystemClock.elapsedRealtime(); if (mExpireMs != 0 && now >= mExpireMs) { if (DEBUG) { - long unitDuration = mIsMp2 ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US - : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US; + long unitDuration = + mIsMp2 + ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US + : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US; long sampleDuration = (mTotalCount - 1) * unitDuration / 1000; long totalDuration = now - mStartMs; StringBuilder ptsBuilder = new StringBuilder(); - ptsBuilder.append("PTS received ").append(mSampleCount).append(", ") - .append(totalDuration - sampleDuration).append(' '); + ptsBuilder + .append("PTS received ") + .append(mSampleCount) + .append(", ") + .append(totalDuration - sampleDuration) + .append(' '); for (Pair pair : mPtsList) { - ptsBuilder.append('[').append(pair.first).append(':').append(pair.second) + ptsBuilder + .append('[') + .append(pair.first) + .append(':') + .append(pair.second) .append("], "); } Log.d(TAG, ptsBuilder.toString()); } if (DEBUG || mCurSampleSize.size() > 1) { - Log.d(TAG, "PTS received sample size: " - + String.valueOf(mSampleSize) + mCurSampleSize + mHeader); + Log.d( + TAG, + "PTS received sample size: " + + String.valueOf(mSampleSize) + + mCurSampleSize + + mHeader); } flush(); } diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java index 953c9fc4..7446c923 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java +++ b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java @@ -17,16 +17,14 @@ package com.android.tv.tuner.exoplayer.audio; import android.media.MediaFormat; - import com.google.android.exoplayer.C; import com.google.android.exoplayer.audio.AudioTrack; - import java.nio.ByteBuffer; /** - * {@link AudioTrack} wrapper class for trickplay operations including FF/RW. - * FF/RW trickplay operations do not need framework {@link AudioTrack}. - * This wrapper class will do nothing in disabled status for those operations. + * {@link AudioTrack} wrapper class for trickplay operations including FF/RW. FF/RW trickplay + * operations do not need framework {@link AudioTrack}. This wrapper class will do nothing in + * disabled status for those operations. */ public class AudioTrackWrapper { private static final int PCM16_FRAME_BYTES = 2; @@ -57,7 +55,7 @@ public class AudioTrackWrapper { resetSessionId(); } - public void release() { + public void release() { if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { mAudioTrack.release(); } diff --git a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java index 72bc68b6..80f91ebd 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java +++ b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java @@ -18,7 +18,6 @@ package com.android.tv.tuner.exoplayer.audio; import android.media.MediaCodec; import android.util.Log; - import com.google.android.exoplayer.CodecCounters; import com.google.android.exoplayer.DecoderInfo; import com.google.android.exoplayer.ExoPlaybackException; @@ -26,7 +25,6 @@ import com.google.android.exoplayer.MediaCodecSelector; import com.google.android.exoplayer.MediaCodecUtil; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; - import java.nio.ByteBuffer; import java.util.ArrayList; diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java index 77170419..ae18e05d 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java @@ -21,7 +21,8 @@ import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.util.Log; - +import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; +import com.android.tv.tuner.tvinput.TunerDebug; import com.google.android.exoplayer.CodecCounters; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaClock; @@ -34,9 +35,6 @@ import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; -import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; -import com.android.tv.tuner.tvinput.TunerDebug; - import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -65,21 +63,21 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me public static final long INITIAL_AUDIO_BUFFERING_TIME_US = BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US; - private static final String TAG = "MpegTsDefaultAudioTrac"; private static final boolean DEBUG = false; /** - * Interface definition for a callback to be notified of - * {@link com.google.android.exoplayer.audio.AudioTrack} error. + * Interface definition for a callback to be notified of {@link + * com.google.android.exoplayer.audio.AudioTrack} error. */ public interface EventListener { void onAudioTrackInitializationError(AudioTrack.InitializationException e); + void onAudioTrackWriteError(AudioTrack.WriteException e); } private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2; - private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024; + private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024 * 1024; private static final int MONITOR_DURATION_MS = 1000; private static final int AC3_HEADER_BITRATE_OFFSET = 4; private static final int MP2_HEADER_BITRATE_OFFSET = 2; @@ -374,8 +372,8 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me } private void readFormat() throws IOException, ExoPlaybackException { - int result = mSource.readData(mTrackIndex, mCurrentPositionUs, - mFormatHolder, mSampleHolder); + int result = + mSource.readData(mTrackIndex, mCurrentPositionUs, mFormatHolder, mSampleHolder); if (result == SampleSource.FORMAT_READ) { onInputFormatChanged(mFormatHolder); } @@ -394,8 +392,7 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me format.language); } - private void onInputFormatChanged(MediaFormatHolder formatHolder) - throws ExoPlaybackException { + private void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException { String mimeType = formatHolder.format.mimeType; mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType); if (mUseFrameworkDecoder) { @@ -470,64 +467,68 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me int result = mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder); switch (result) { - case SampleSource.NOTHING_READ: { - return false; - } - case SampleSource.FORMAT_READ: { - Log.i(TAG, "Format was read again"); - onInputFormatChanged(mFormatHolder); - return true; - } - case SampleSource.END_OF_STREAM: { - Log.i(TAG, "End of stream from SampleSource"); - mInputStreamEnded = true; - return false; - } - default: { - if (mSampleHolder.size != mSampleSize - && mFormatConfigured - && !mUseFrameworkDecoder) { - onSampleSizeChanged(mSampleHolder.size); + case SampleSource.NOTHING_READ: + { + return false; } - mSampleHolder.data.flip(); - if (!mUseFrameworkDecoder) { - if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { - mMonitor.addPts( - mSampleHolder.timeUs, - mOutputBuffer.position(), - mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET) - & MP2_HEADER_BITRATE_MASK); - } else { - mMonitor.addPts( - mSampleHolder.timeUs, - mOutputBuffer.position(), - mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff); - } + case SampleSource.FORMAT_READ: + { + Log.i(TAG, "Format was read again"); + onInputFormatChanged(mFormatHolder); + return true; + } + case SampleSource.END_OF_STREAM: + { + Log.i(TAG, "End of stream from SampleSource"); + mInputStreamEnded = true; + return false; } - if (mAudioDecoder != null) { - mAudioDecoder.decode(mSampleHolder); - if (mUseFrameworkDecoder) { - int outputIndex = - ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex(); - if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - onOutputFormatChanged(mAudioDecoder.getOutputFormat()); - return true; - } else if (outputIndex < 0) { - return true; + default: + { + if (mSampleHolder.size != mSampleSize + && mFormatConfigured + && !mUseFrameworkDecoder) { + onSampleSizeChanged(mSampleHolder.size); + } + mSampleHolder.data.flip(); + if (!mUseFrameworkDecoder) { + if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { + mMonitor.addPts( + mSampleHolder.timeUs, + mOutputBuffer.position(), + mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET) + & MP2_HEADER_BITRATE_MASK); + } else { + mMonitor.addPts( + mSampleHolder.timeUs, + mOutputBuffer.position(), + mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff); } - if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) { - AUDIO_TRACK.handleDiscontinuity(); - return true; + } + if (mAudioDecoder != null) { + mAudioDecoder.decode(mSampleHolder); + if (mUseFrameworkDecoder) { + int outputIndex = + ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex(); + if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + onOutputFormatChanged(mAudioDecoder.getOutputFormat()); + return true; + } else if (outputIndex < 0) { + return true; + } + if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) { + AUDIO_TRACK.handleDiscontinuity(); + return true; + } } + ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample(); + long presentationTimeUs = mAudioDecoder.getDecodedTimeUs(); + decodeDone(outputBuffer, presentationTimeUs); + } else { + decodeDone(mSampleHolder.data, mSampleHolder.timeUs); } - ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample(); - long presentationTimeUs = mAudioDecoder.getDecodedTimeUs(); - decodeDone(outputBuffer, presentationTimeUs); - } else { - decodeDone(mSampleHolder.data, mSampleHolder.timeUs); + return true; } - return true; - } } } @@ -549,11 +550,11 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me try { // To reduce discontinuity, interpolate presentation time. if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { - mInterpolatedTimeUs = mPresentationTimeUs - + mPresentationCount * MP2_SAMPLE_DURATION_US; + mInterpolatedTimeUs = + mPresentationTimeUs + mPresentationCount * MP2_SAMPLE_DURATION_US; } else if (!mUseFrameworkDecoder) { - mInterpolatedTimeUs = mPresentationTimeUs - + mPresentationCount * AC3_SAMPLE_DURATION_US; + mInterpolatedTimeUs = + mPresentationTimeUs + mPresentationCount * AC3_SAMPLE_DURATION_US; } else { mInterpolatedTimeUs = mPresentationTimeUs; } @@ -588,7 +589,8 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me protected long getBufferedPositionUs() { long pos = mSource.getBufferedPositionUs(); return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US - ? pos : Math.max(pos, getPositionUs()); + ? pos + : Math.max(pos, getPositionUs()); } @Override @@ -607,15 +609,19 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me if (DEBUG) { long oldPositionUs = Math.max(mCurrentPositionUs, 0); long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); - Log.d(TAG, "Audio position is not set, diff in us: " - + String.valueOf(currentPositionUs - oldPositionUs)); + Log.d( + TAG, + "Audio position is not set, diff in us: " + + String.valueOf(currentPositionUs - oldPositionUs)); } mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); } else { if (mPreviousPositionUs > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) { - Log.e(TAG, "audio_position BACK JUMP: " - + (mPreviousPositionUs - audioTrackCurrentPositionUs)); + Log.e( + TAG, + "audio_position BACK JUMP: " + + (mPreviousPositionUs - audioTrackCurrentPositionUs)); mCurrentPositionUs = audioTrackCurrentPositionUs; } else { mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs); @@ -660,24 +666,26 @@ public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements Me if (mEventHandler == null || mEventListener == null) { return; } - mEventHandler.post(new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackInitializationError(e); - } - }); + mEventHandler.post( + new Runnable() { + @Override + public void run() { + mEventListener.onAudioTrackInitializationError(e); + } + }); } private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { if (mEventHandler == null || mEventListener == null) { return; } - mEventHandler.post(new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackWriteError(e); - } - }); + mEventHandler.post( + new Runnable() { + @Override + public void run() { + mEventListener.onAudioTrackWriteError(e); + } + }); } @Override diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java index 142aa9b2..b382545f 100644 --- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java @@ -17,7 +17,6 @@ package com.android.tv.tuner.exoplayer.audio; import android.os.Handler; - import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecSelector; @@ -36,8 +35,8 @@ public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRend public interface Ac3EventListener extends EventListener { /** - * Invoked when a {@link android.media.PlaybackParams} set to an - * {@link android.media.AudioTrack} is not valid. + * Invoked when a {@link android.media.PlaybackParams} set to an {@link + * android.media.AudioTrack} is not valid. * * @param e The corresponding exception. */ @@ -70,16 +69,17 @@ public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRend private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) { if (eventHandler != null && mListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - mListener.onAudioTrackSetPlaybackParamsError(e); - } - }); + eventHandler.post( + new Runnable() { + @Override + public void run() { + mListener.onAudioTrackSetPlaybackParamsError(e); + } + }); } } - static private boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { + private static boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { if (e.getStackTrace() == null || e.getStackTrace().length < 1) { return false; } @@ -91,4 +91,4 @@ public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRend } return false; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java index 112e9dc4..206e2098 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java @@ -23,12 +23,10 @@ import android.support.annotation.VisibleForTesting; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; - -import com.google.android.exoplayer.SampleHolder; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.exoplayer.SampleExtractor; import com.android.tv.util.Utils; - +import com.google.android.exoplayer.SampleHolder; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -42,8 +40,8 @@ import java.util.TreeMap; /** * Manages {@link SampleChunk} objects. - *

- * The buffer manager can be disabled, while running, if the write throughput to the associated + * + *

The buffer manager can be disabled, while running, if the write throughput to the associated * external storage is detected to be lower than a threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}". * This leads to restarting playback flow. */ @@ -53,10 +51,10 @@ public class BufferManager { // Constants for the disk write speed checking private static final long MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK = - 10L * 1024 * 1024; // Checks for every 10M disk write + 10L * 1024 * 1024; // Checks for every 10M disk write private static final int MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK = 15 * 1024; - private static final int MAXIMUM_SPEED_CHECK_COUNT = 5; // Checks only 5 times - private static final int MINIMUM_DISK_WRITE_SPEED_MBPS = 3; // 3 Megabytes per second + private static final int MAXIMUM_SPEED_CHECK_COUNT = 5; // Checks only 5 times + private static final int MINIMUM_DISK_WRITE_SPEED_MBPS = 3; // 3 Megabytes per second private final SampleChunk.SampleChunkCreator mSampleChunkCreator; // Maps from track name to a map which maps from starting position to {@link SampleChunk}. @@ -67,17 +65,18 @@ public class BufferManager { private final StorageManager mStorageManager; private long mBufferSize = 0; private final EvictChunkQueueMap mPendingDelete = new EvictChunkQueueMap(); - private final SampleChunk.ChunkCallback mChunkCallback = new SampleChunk.ChunkCallback() { - @Override - public void onChunkWrite(SampleChunk chunk) { - mBufferSize += chunk.getSize(); - } + private final SampleChunk.ChunkCallback mChunkCallback = + new SampleChunk.ChunkCallback() { + @Override + public void onChunkWrite(SampleChunk chunk) { + mBufferSize += chunk.getSize(); + } - @Override - public void onChunkDelete(SampleChunk chunk) { - mBufferSize -= chunk.getSize(); - } - }; + @Override + public void onChunkDelete(SampleChunk chunk) { + mBufferSize -= chunk.getSize(); + } + }; private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK; private long mTotalWriteSize; @@ -88,30 +87,27 @@ public class BufferManager { public interface ChunkEvictedListener { void onChunkEvicted(String id, long createdTimeMs); } - /** - * Handles I/O - * between BufferManager and {@link SampleExtractor}. - */ + /** Handles I/O between BufferManager and {@link SampleExtractor}. */ public interface SampleBuffer { /** * Initializes SampleBuffer. + * * @param Ids track identifiers for storage read/write. * @param mediaFormats meta-data for each track. * @throws IOException */ - void init(@NonNull List Ids, - @NonNull List mediaFormats) + void init( + @NonNull List Ids, + @NonNull List mediaFormats) throws IOException; - /** - * Selects the track {@code index} for reading sample data. - */ + /** Selects the track {@code index} for reading sample data. */ void selectTrack(int index); /** - * Deselects the track at {@code index}, - * so that no more samples will be read from the track. + * Deselects the track at {@code index}, so that no more samples will be read from the + * track. */ void deselectTrack(int index); @@ -126,71 +122,59 @@ public class BufferManager { void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) throws IOException; - /** - * Checks whether storage write speed is slow. - */ + /** Checks whether storage write speed is slow. */ boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs); /** * Handles when write speed is slow. + * * @throws IOException */ void handleWriteSpeedSlow() throws IOException; - /** - * Sets the flag when EoS was reached. - */ + /** Sets the flag when EoS was reached. */ void setEos(); /** * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, - * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ} - * if it is available. - * If the next sample is not available, - * returns {@link com.google.android.exoplayer.SampleSource#NOTHING_READ}. + * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ} if it is + * available. If the next sample is not available, returns {@link + * com.google.android.exoplayer.SampleSource#NOTHING_READ}. */ int readSample(int index, SampleHolder outSample); - /** - * Seeks to the specified time in microseconds. - */ + /** Seeks to the specified time in microseconds. */ void seekTo(long positionUs); - /** - * Returns an estimate of the position up to which data is buffered. - */ + /** Returns an estimate of the position up to which data is buffered. */ long getBufferedPositionUs(); - /** - * Returns whether there is buffered data. - */ + /** Returns whether there is buffered data. */ boolean continueBuffering(long positionUs); /** * Cleans up and releases everything. + * * @throws IOException */ void release() throws IOException; } - /** - * A Track format which will be loaded and saved from the permanent storage for recordings. - */ + /** A Track format which will be loaded and saved from the permanent storage for recordings. */ public static class TrackFormat { /** - * The track id for the specified track. The track id will be used as a track identifier - * for recordings. + * The track id for the specified track. The track id will be used as a track identifier for + * recordings. */ public final String trackId; - /** - * The {@link MediaFormat} for the specified track. - */ + /** The {@link MediaFormat} for the specified track. */ public final MediaFormat format; /** * Creates TrackFormat. + * * @param trackId * @param format */ @@ -200,29 +184,24 @@ public class BufferManager { } } - /** - * A Holder for a sample position which will be loaded from the index file for recordings. - */ + /** A Holder for a sample position which will be loaded from the index file for recordings. */ public static class PositionHolder { /** - * The current sample position in microseconds. - * The position is identical to the PTS(presentation time stamp) of the sample. + * The current sample position in microseconds. The position is identical to the + * PTS(presentation time stamp) of the sample. */ public final long positionUs; - /** - * Base sample position for the current {@link SampleChunk}. - */ + /** Base sample position for the current {@link SampleChunk}. */ public final long basePositionUs; - /** - * The file offset for the current sample in the current {@link SampleChunk}. - */ + /** The file offset for the current sample in the current {@link SampleChunk}. */ public final int offset; /** * Creates a holder for a specific position in the recording. + * * @param positionUs * @param offset */ @@ -233,9 +212,7 @@ public class BufferManager { } } - /** - * Storage configuration and policy manager for {@link BufferManager} - */ + /** Storage configuration and policy manager for {@link BufferManager} */ public interface StorageManager { /** @@ -257,7 +234,7 @@ public class BufferManager { * * @param bufferSize the current total usage of Storage in bytes. * @param pendingDelete the current storage usage which will be deleted in near future by - * bytes + * bytes * @return {@code true} if it reached pre-determined max size */ boolean reachedStorageMax(long bufferSize, long pendingDelete); @@ -266,7 +243,7 @@ public class BufferManager { * Informs whether the storage has enough remained space. * * @param pendingDelete the current storage usage which will be deleted in near future by - * bytes + * bytes * @return {@code true} if it has enough space */ boolean hasEnoughBuffer(long pendingDelete); @@ -295,8 +272,7 @@ public class BufferManager { * @param isAudio {@code true} if it is for audio track * @throws IOException */ - void writeTrackInfoFiles(List formatList, boolean isAudio) - throws IOException; + void writeTrackInfoFiles(List formatList, boolean isAudio) throws IOException; /** * Writes index file to storage. @@ -356,8 +332,8 @@ public class BufferManager { this(storageManager, new SampleChunk.SampleChunkCreator()); } - public BufferManager(StorageManager storageManager, - SampleChunk.SampleChunkCreator sampleChunkCreator) { + public BufferManager( + StorageManager storageManager, SampleChunk.SampleChunkCreator sampleChunkCreator) { mStorageManager = storageManager; mSampleChunkCreator = sampleChunkCreator; } @@ -380,14 +356,19 @@ public class BufferManager { * @param id the name of the track * @param positionUs current position to write a sample in micro seconds. * @param samplePool {@link SamplePool} for the fast creation of samples. - * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create - * a new {@link SampleChunk}. + * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create a + * new {@link SampleChunk}. * @param currentOffset the current offset to write. * @return returns the created {@link SampleChunk}. * @throws IOException */ - public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool, - SampleChunk currentChunk, int currentOffset) throws IOException { + public SampleChunk createNewWriteFileIfNeeded( + String id, + long positionUs, + SamplePool samplePool, + SampleChunk currentChunk, + int currentOffset) + throws IOException { if (!maybeEvictChunk()) { throw new IOException("Not enough storage space"); } @@ -400,8 +381,9 @@ public class BufferManager { } if (currentChunk == null) { File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); - SampleChunk sampleChunk = mSampleChunkCreator - .createSampleChunk(samplePool, file, positionUs, mChunkCallback); + SampleChunk sampleChunk = + mSampleChunkCreator.createSampleChunk( + samplePool, file, positionUs, mChunkCallback); map.put(positionUs, new Pair(sampleChunk, 0)); return sampleChunk; } else { @@ -430,11 +412,16 @@ public class BufferManager { } SampleChunk chunk = null; long basePositionUs = -1; - for (PositionHolder position: keyPositions) { + for (PositionHolder position : keyPositions) { if (position.basePositionUs != basePositionUs) { - chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool, - mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs), - position.positionUs, mChunkCallback, chunk); + chunk = + mSampleChunkCreator.loadSampleChunkFromFile( + samplePool, + mStorageManager.getBufferDir(), + getFileName(trackId, position.positionUs), + position.positionUs, + mChunkCallback, + chunk); basePositionUs = position.basePositionUs; } map.put(position.positionUs, new Pair(chunk, position.offset)); @@ -467,13 +454,13 @@ public class BufferManager { * Evicts chunks which are ready to be evicted for the specified track * * @param id the specified track - * @param earlierThanPositionUs the start position of the {@link SampleChunk} - * should be earlier than + * @param earlierThanPositionUs the start position of the {@link SampleChunk} should be earlier + * than */ public void evictChunks(String id, long earlierThanPositionUs) { SampleChunk chunk = null; while ((chunk = mPendingDelete.poll(id, earlierThanPositionUs)) != null) { - SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()) ; + SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()); } } @@ -518,11 +505,17 @@ public class BufferManager { mPendingDelete.add(earliestChunkId, earliestChunk); earliestChunkMap.remove(earliestChunk.getStartPositionUs()); if (DEBUG) { - Log.d(TAG, String.format("bufferSize = %d; pendingDelete = %b; " - + "earliestChunk size = %d; %s@%d (%s)", - mBufferSize, pendingDelete, earliestChunk.getSize(), earliestChunkId, - earliestChunk.getStartPositionUs(), - Utils.toIsoDateTimeString(earliestChunk.getCreatedTimeMs()))); + Log.d( + TAG, + String.format( + "bufferSize = %d; pendingDelete = %b; " + + "earliestChunk size = %d; %s@%d (%s)", + mBufferSize, + pendingDelete, + earliestChunk.getSize(), + earliestChunkId, + earliestChunk.getStartPositionUs(), + Utils.toIsoDateTimeString(earliestChunk.getCreatedTimeMs()))); } ChunkEvictedListener listener = mEvictListeners.get(earliestChunkId); if (listener != null) { @@ -593,9 +586,7 @@ public class BufferManager { } } - /** - * Releases all the resources. - */ + /** Releases all the resources. */ public void release() { try { mPendingDelete.release(); @@ -613,8 +604,8 @@ public class BufferManager { } catch (ConcurrentModificationException | NullPointerException e) { // TODO: remove this after it it confirmed that race condition issues are resolved. // b/32492258, b/32373376 - SoftPreconditions.checkState(false, "Exception on BufferManager#release: ", - e.toString()); + SoftPreconditions.checkState( + false, "Exception on BufferManager#release: ", e.toString()); } } @@ -624,9 +615,7 @@ public class BufferManager { mTotalWriteTimeNs = 0; } - /** - * Adds a disk write sample size to calculate the average disk write bandwidth. - */ + /** Adds a disk write sample size to calculate the average disk write bandwidth. */ public void addWriteStat(long size, long timeNs) { if (size >= mMinSampleSizeForSpeedCheck) { mTotalWriteSize += size; @@ -635,8 +624,8 @@ public class BufferManager { } /** - * Returns if the average disk write bandwidth is slower than - * threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}. + * Returns if the average disk write bandwidth is slower than threshold {@code + * MINIMUM_DISK_WRITE_SPEED_MBPS}. */ public boolean isWriteSlow() { if (mTotalWriteSize < MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK) { @@ -658,8 +647,8 @@ public class BufferManager { } /** - * Returns recent write bandwidth in MBps. If recent bandwidth is not available, - * returns {float -1.0f}. + * Returns recent write bandwidth in MBps. If recent bandwidth is not available, returns {float + * -1.0f}. */ public float getWriteBandwidth() { return mWriteBandwidth == 0.0f ? -1.0f : mWriteBandwidth; @@ -673,8 +662,8 @@ public class BufferManager { } /** - * Returns if {@link BufferManager} has checked the write speed, - * which is suitable for Trickplay. + * Returns if {@link BufferManager} has checked the write speed, which is suitable for + * Trickplay. */ @VisibleForTesting public boolean hasSpeedCheckDone() { @@ -683,6 +672,7 @@ public class BufferManager { /** * Sets minimum sample size for write speed check. + * * @param sampleSize minimum sample size for write speed check. */ @VisibleForTesting diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java index 6a09016c..2a58ffcf 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java @@ -19,10 +19,8 @@ package com.android.tv.tuner.exoplayer.buffer; import android.media.MediaFormat; import android.util.Log; import android.util.Pair; - import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.google.protobuf.nano.MessageNano; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -36,9 +34,7 @@ import java.util.List; import java.util.Map; import java.util.SortedMap; -/** - * Manages DVR storage. - */ +/** Manages DVR storage. */ public class DvrStorageManager implements BufferManager.StorageManager { private static final String TAG = "DvrStorageManager"; @@ -118,7 +114,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { if (len <= 0) { return null; } - byte [] strBytes = new byte[len]; + byte[] strBytes = new byte[len]; in.readFully(strBytes); return new String(strBytes, StandardCharsets.UTF_8); } @@ -147,7 +143,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { if (len <= 0) { return null; } - byte [] bytes = new byte[len]; + byte[] bytes = new byte[len]; in.readFully(bytes); ByteBuffer buffer = ByteBuffer.allocate(len); buffer.put(bytes); @@ -170,8 +166,9 @@ public class DvrStorageManager implements BufferManager.StorageManager { int index = 0; boolean trackNotFound = false; do { - String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) - + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); + String fileName = + (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) + + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); File file = new File(getBufferDir(), fileName); try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { String name = readString(in); @@ -195,7 +192,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { trackNotFound = true; } index++; - } while(!trackNotFound); + } while (!trackNotFound); return trackFormatList; } @@ -209,8 +206,9 @@ public class DvrStorageManager implements BufferManager.StorageManager { int index = 0; boolean trackNotFound = false; do { - String fileName = META_FILE_TYPE_CAPTION + - ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); + String fileName = + META_FILE_TYPE_CAPTION + + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); File file = new File(getBufferDir(), fileName); try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { byte[] data = new byte[(int) file.length()]; @@ -220,7 +218,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { trackNotFound = true; } index++; - } while(!trackNotFound); + } while (!trackNotFound); return tracks; } @@ -259,7 +257,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { if (file.exists()) { return readNewIndexFile(file); } else { - return readOldIndexFile(new File(getBufferDir(),trackId + IDX_FILE_SUFFIX)); + return readOldIndexFile(new File(getBufferDir(), trackId + IDX_FILE_SUFFIX)); } } @@ -291,7 +289,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { } private void writeString(DataOutputStream out, String str) throws IOException { - byte [] data = str.getBytes(StandardCharsets.UTF_8); + byte[] data = str.getBytes(StandardCharsets.UTF_8); out.writeInt(data.length); if (data.length > 0) { out.write(data); @@ -308,7 +306,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { } private void writeByteBuffer(DataOutputStream out, ByteBuffer buffer) throws IOException { - byte [] data = new byte[buffer.limit()]; + byte[] data = new byte[buffer.limit()]; buffer.get(data); buffer.flip(); out.writeInt(data.length); @@ -331,10 +329,11 @@ public class DvrStorageManager implements BufferManager.StorageManager { @Override public void writeTrackInfoFiles(List formatList, boolean isAudio) throws IOException { - for (int i = 0; i < formatList.size() ; ++i) { + for (int i = 0; i < formatList.size(); ++i) { BufferManager.TrackFormat trackFormat = formatList.get(i); - String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) - + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); + String fileName = + (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) + + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); File file = new File(getBufferDir(), fileName); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { writeString(out, trackFormat.trackId); @@ -365,8 +364,8 @@ public class DvrStorageManager implements BufferManager.StorageManager { } for (int i = 0; i < tracks.size(); i++) { AtscCaptionTrack track = tracks.get(i); - String fileName = META_FILE_TYPE_CAPTION + - ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); + String fileName = + META_FILE_TYPE_CAPTION + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); File file = new File(getBufferDir(), fileName); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { out.write(MessageNano.toByteArray(track)); @@ -379,7 +378,7 @@ public class DvrStorageManager implements BufferManager.StorageManager { @Override public void writeIndexFile(String trackName, SortedMap> index) throws IOException { - File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2); + File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) { out.writeLong(index.size()); for (Map.Entry> entry : index.entrySet()) { diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java index af0c3f0d..ebf00f59 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java @@ -20,16 +20,14 @@ import android.os.ConditionVariable; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.util.Log; - +import com.android.tv.tuner.exoplayer.MpegTsPlayer; +import com.android.tv.tuner.exoplayer.SampleExtractor; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.util.Assertions; -import com.android.tv.tuner.exoplayer.MpegTsPlayer; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.android.tv.tuner.exoplayer.SampleExtractor; - import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -38,43 +36,33 @@ import java.util.List; import java.util.concurrent.TimeUnit; /** - * Handles I/O between {@link SampleExtractor} and - * {@link BufferManager}.Reads & writes samples from/to {@link SampleChunk} which is backed - * by physical storage. + * Handles I/O between {@link SampleExtractor} and {@link BufferManager}.Reads & writes samples + * from/to {@link SampleChunk} which is backed by physical storage. */ -public class RecordingSampleBuffer implements BufferManager.SampleBuffer, - BufferManager.ChunkEvictedListener { +public class RecordingSampleBuffer + implements BufferManager.SampleBuffer, BufferManager.ChunkEvictedListener { private static final String TAG = "RecordingSampleBuffer"; @IntDef({BUFFER_REASON_LIVE_PLAYBACK, BUFFER_REASON_RECORDED_PLAYBACK, BUFFER_REASON_RECORDING}) @Retention(RetentionPolicy.SOURCE) public @interface BufferReason {} - /** - * A buffer reason for live-stream playback. - */ + /** A buffer reason for live-stream playback. */ public static final int BUFFER_REASON_LIVE_PLAYBACK = 0; - /** - * A buffer reason for playback of a recorded program. - */ + /** A buffer reason for playback of a recorded program. */ public static final int BUFFER_REASON_RECORDED_PLAYBACK = 1; - /** - * A buffer reason for recording a program. - */ + /** A buffer reason for recording a program. */ public static final int BUFFER_REASON_RECORDING = 2; - /** - * The minimum duration to support seek in Trickplay. - */ + /** The minimum duration to support seek in Trickplay. */ static final long MIN_SEEK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500); - /** - * The duration of a {@link SampleChunk} for recordings. - */ + /** The duration of a {@link SampleChunk} for recordings. */ static final long RECORDING_CHUNK_DURATION_US = MIN_SEEK_DURATION_US * 1200; // 10 minutes - private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds + + private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds private static final long BUFFER_NEEDED_US = 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS); @@ -97,28 +85,31 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, private SampleChunkIoHelper mSampleChunkIoHelper; private final SampleChunkIoHelper.IoCallback mIoCallback = new SampleChunkIoHelper.IoCallback() { - @Override - public void onIoReachedEos() { - mEos = true; - } + @Override + public void onIoReachedEos() { + mEos = true; + } - @Override - public void onIoError() { - mError = true; - } - }; + @Override + public void onIoError() { + mError = true; + } + }; /** - * Creates {@link BufferManager.SampleBuffer} with - * cached I/O backed by physical storage (e.g. trickplay,recording,recorded-playback). + * Creates {@link BufferManager.SampleBuffer} with cached I/O backed by physical storage (e.g. + * trickplay,recording,recorded-playback). * * @param bufferManager the manager of {@link SampleChunk} * @param bufferListener the listener for buffer I/O event * @param enableTrickplay {@code true} when trickplay should be enabled * @param bufferReason the reason for caching samples {@link RecordingSampleBuffer.BufferReason} */ - public RecordingSampleBuffer(BufferManager bufferManager, PlaybackBufferListener bufferListener, - boolean enableTrickplay, @BufferReason int bufferReason) { + public RecordingSampleBuffer( + BufferManager bufferManager, + PlaybackBufferListener bufferListener, + boolean enableTrickplay, + @BufferReason int bufferReason) { mBufferManager = bufferManager; mBufferListener = bufferListener; if (bufferListener != null) { @@ -136,8 +127,9 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, } mTrackSelected = new boolean[mTrackCount]; mReadSampleQueues = new ArrayList<>(); - mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason, - mBufferManager, mSamplePool, mIoCallback); + mSampleChunkIoHelper = + new SampleChunkIoHelper( + ids, mediaFormats, mBufferReason, mBufferManager, mSamplePool, mIoCallback); for (int i = 0; i < mTrackCount; ++i) { mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); } @@ -186,13 +178,16 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, } @Override - public void handleWriteSpeedSlow() throws IOException{ + public void handleWriteSpeedSlow() throws IOException { if (mBufferReason == BUFFER_REASON_RECORDING) { // Recording does not need to stop because I/O speed is slow temporarily. // If fixed size buffer of TsStreamer overflows, TsDataSource will reach EoS. // Reaching EoS will stop recording eventually. - Log.w(TAG, "Disk I/O speed is slow for recording temporarily: " - + mBufferManager.getWriteBandwidth() + "MBps"); + Log.w( + TAG, + "Disk I/O speed is slow for recording temporarily: " + + mBufferManager.getWriteBandwidth() + + "MBps"); return; } // Disables buffering samples afterwards, and notifies the disk speed is slow. @@ -253,8 +248,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (!mTrackSelected[i]) { continue; } - Long lastQueuedSamplePositionUs = - mReadSampleQueues.get(i).getLastQueuedPositionUs(); + Long lastQueuedSamplePositionUs = mReadSampleQueues.get(i).getLastQueuedPositionUs(); if (lastQueuedSamplePositionUs == null) { // No sample has been queued. result = mLastBufferedPositionUs; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java index 04b5a071..023d3295 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java @@ -19,18 +19,16 @@ package com.android.tv.tuner.exoplayer.buffer; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; - import com.google.android.exoplayer.SampleHolder; - import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; /** - * {@link SampleChunk} stores samples into file and makes them available for read. - * Stored file = { Header, Sample } * N - * Header = sample size : int, sample flag : int, sample PTS in micro second : long + * {@link SampleChunk} stores samples into file and makes them available for read. Stored file = { + * Header, Sample } * N Header = sample size : int, sample flag : int, sample PTS in micro second : + * long */ public class SampleChunk { private static final String TAG = "SampleChunk"; @@ -52,32 +50,25 @@ public class SampleChunk { private boolean mIsReading; private boolean mIsWriting; - /** - * A callback for chunks being committed to permanent storage. - */ - public static abstract class ChunkCallback { + /** A callback for chunks being committed to permanent storage. */ + public abstract static class ChunkCallback { /** * Notifies when writing a SampleChunk is completed. * * @param chunk SampleChunk which is written completely */ - public void onChunkWrite(SampleChunk chunk) { - - } + public void onChunkWrite(SampleChunk chunk) {} /** * Notifies when a SampleChunk is deleted. * * @param chunk SampleChunk which is deleted from storage */ - public void onChunkDelete(SampleChunk chunk) { - } + public void onChunkDelete(SampleChunk chunk) {} } - /** - * A class for SampleChunk creation. - */ + /** A class for SampleChunk creation. */ public static class SampleChunkCreator { /** @@ -88,15 +79,18 @@ public class SampleChunk { * @param startPositionUs the start position of the earliest sample to be stored * @param chunkCallback for total storage usage change notification */ - SampleChunk createSampleChunk(SamplePool samplePool, File file, - long startPositionUs, ChunkCallback chunkCallback) { - return new SampleChunk(samplePool, file, startPositionUs, System.currentTimeMillis(), - chunkCallback); + SampleChunk createSampleChunk( + SamplePool samplePool, + File file, + long startPositionUs, + ChunkCallback chunkCallback) { + return new SampleChunk( + samplePool, file, startPositionUs, System.currentTimeMillis(), chunkCallback); } /** - * Returns a newly created SampleChunk which is backed by an existing file. - * Created SampleChunk is read-only. + * Returns a newly created SampleChunk which is backed by an existing file. Created + * SampleChunk is read-only. * * @param samplePool sample allocator * @param bufferDir the directory where the file to read is located @@ -106,12 +100,16 @@ public class SampleChunk { * @param prev the previous SampleChunk just before the newly created SampleChunk * @throws IOException */ - SampleChunk loadSampleChunkFromFile(SamplePool samplePool, File bufferDir, - String filename, long startPositionUs, ChunkCallback chunkCallback, - SampleChunk prev) throws IOException { + SampleChunk loadSampleChunkFromFile( + SamplePool samplePool, + File bufferDir, + String filename, + long startPositionUs, + ChunkCallback chunkCallback, + SampleChunk prev) + throws IOException { File file = new File(bufferDir, filename); - SampleChunk chunk = - new SampleChunk(samplePool, file, startPositionUs, chunkCallback); + SampleChunk chunk = new SampleChunk(samplePool, file, startPositionUs, chunkCallback); if (prev != null) { prev.mNextChunk = chunk; } @@ -120,8 +118,8 @@ public class SampleChunk { } /** - * Handles I/O for SampleChunk. - * Maintains current SampleChunk and the current offset for next I/O operation. + * Handles I/O for SampleChunk. Maintains current SampleChunk and the current offset for next + * I/O operation. */ static class IoState { private SampleChunk mChunk; @@ -131,16 +129,12 @@ public class SampleChunk { return chunk == mChunk && mCurrentOffset == offset; } - /** - * Returns whether read I/O operation is finished. - */ + /** Returns whether read I/O operation is finished. */ boolean isReadFinished() { return mChunk == null; } - /** - * Returns the start position of the current SampleChunk - */ + /** Returns the start position of the current SampleChunk */ long getStartPositionUs() { return mChunk == null ? 0 : mChunk.getStartPositionUs(); } @@ -175,7 +169,7 @@ public class SampleChunk { * @param chunk the new SampleChunk to write samples afterwards * @throws IOException */ - void openWrite(SampleChunk chunk) throws IOException{ + void openWrite(SampleChunk chunk) throws IOException { if (mChunk != null) { mChunk.closeWrite(chunk); } @@ -215,12 +209,11 @@ public class SampleChunk { * Writes a sample. * * @param sample to write - * @param nextChunk if this is {@code null} writes at the current SampleChunk, - * otherwise close current SampleChunk and writes at this + * @param nextChunk if this is {@code null} writes at the current SampleChunk, otherwise + * close current SampleChunk and writes at this * @throws IOException */ - void write(SampleHolder sample, SampleChunk nextChunk) - throws IOException { + void write(SampleHolder sample, SampleChunk nextChunk) throws IOException { if (nextChunk != null) { if (mChunk == null || mChunk.mNextChunk != null) { throw new IllegalStateException("Requested write for wrong SampleChunk"); @@ -244,16 +237,12 @@ public class SampleChunk { } } - /** - * Returns the current SampleChunk for subsequent I/O operation. - */ + /** Returns the current SampleChunk for subsequent I/O operation. */ SampleChunk getChunk() { return mChunk; } - /** - * Returns the current offset of the current SampleChunk for subsequent I/O operation. - */ + /** Returns the current offset of the current SampleChunk for subsequent I/O operation. */ long getOffset() { return mCurrentOffset; } @@ -262,8 +251,8 @@ public class SampleChunk { * Releases SampleChunk. the SampleChunk will not be used anymore. * * @param chunk to release - * @param delete {@code true} when the backed file needs to be deleted, - * {@code false} otherwise. + * @param delete {@code true} when the backed file needs to be deleted, {@code false} + * otherwise. */ static void release(SampleChunk chunk, boolean delete) { chunk.release(delete); @@ -271,8 +260,12 @@ public class SampleChunk { } @VisibleForTesting - protected SampleChunk(SamplePool samplePool, File file, long startPositionUs, - long createdTimeMs, ChunkCallback chunkCallback) { + protected SampleChunk( + SamplePool samplePool, + File file, + long startPositionUs, + long createdTimeMs, + ChunkCallback chunkCallback) { mStartPositionUs = startPositionUs; mCreatedTimeMs = createdTimeMs; mSamplePool = samplePool; @@ -281,8 +274,9 @@ public class SampleChunk { } // Constructor of SampleChunk which is backed by the given existing file. - private SampleChunk(SamplePool samplePool, File file, long startPositionUs, - ChunkCallback chunkCallback) throws IOException { + private SampleChunk( + SamplePool samplePool, File file, long startPositionUs, ChunkCallback chunkCallback) + throws IOException { mStartPositionUs = startPositionUs; mCreatedTimeMs = mStartPositionUs / 1000; mSamplePool = samplePool; @@ -311,8 +305,8 @@ public class SampleChunk { } if (!mIsWriting) { if (mIsReading) { - throw new IllegalStateException("Write is requested for " - + "an already opened SampleChunk"); + throw new IllegalStateException( + "Write is requested for " + "an already opened SampleChunk"); } mAccessFile = new RandomAccessFile(mFile, "rw"); mIsWriting = true; @@ -331,15 +325,14 @@ public class SampleChunk { } } - private void closeRead() throws IOException{ + private void closeRead() throws IOException { if (mIsReading) { mIsReading = false; CloseAccessFileIfNeeded(); } } - private void closeWrite(SampleChunk nextChunk) - throws IOException { + private void closeWrite(SampleChunk nextChunk) throws IOException { if (mIsWriting) { mNextChunk = nextChunk; mIsWriting = false; @@ -374,16 +367,20 @@ public class SampleChunk { sample.flags = mAccessFile.readInt(); sample.timeUs = mAccessFile.readLong(); sample.clearData(); - sample.data.put(mAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, - offset + SAMPLE_HEADER_LENGTH, sample.size)); + sample.data.put( + mAccessFile + .getChannel() + .map( + FileChannel.MapMode.READ_ONLY, + offset + SAMPLE_HEADER_LENGTH, + sample.size)); offset += sample.size + SAMPLE_HEADER_LENGTH; state.mCurrentOffset = offset; return sample; } @VisibleForTesting - protected void write(SampleHolder sample, IoState state) - throws IOException { + protected void write(SampleHolder sample, IoState state) throws IOException { if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) { throw new IllegalStateException("Requested write for wrong SampleChunk"); } @@ -414,23 +411,17 @@ public class SampleChunk { } } - /** - * Returns the start position. - */ + /** Returns the start position. */ public long getStartPositionUs() { return mStartPositionUs; } - /** - * Returns the creation time. - */ + /** Returns the creation time. */ public long getCreatedTimeMs() { return mCreatedTimeMs; } - /** - * Returns the current size. - */ + /** Returns the current size. */ public long getSize() { return mWriteOffset; } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java index ca97a91a..06fd6558 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java @@ -24,13 +24,11 @@ import android.os.Message; import android.util.ArraySet; import android.util.Log; import android.util.Pair; - +import com.android.tv.common.SoftPreconditions; +import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.util.MimeTypes; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason; - import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -38,8 +36,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; /** - * Handles all {@link SampleChunk} I/O operations. - * An I/O dedicated thread handles all I/O operations for synchronization. + * Handles all {@link SampleChunk} I/O operations. An I/O dedicated thread handles all I/O + * operations for synchronization. */ public class SampleChunkIoHelper implements Handler.Callback { private static final String TAG = "SampleChunkIoHelper"; @@ -77,22 +75,14 @@ public class SampleChunkIoHelper implements Handler.Callback { private boolean mErrorNotified; private boolean mFinished; - /** - * A Callback for I/O events. - */ - public static abstract class IoCallback { + /** A Callback for I/O events. */ + public abstract static class IoCallback { - /** - * Called when there is no sample to read. - */ - public void onIoReachedEos() { - } + /** Called when there is no sample to read. */ + public void onIoReachedEos() {} - /** - * Called when there is an irrecoverable error during I/O. - */ - public void onIoError() { - } + /** Called when there is an irrecoverable error during I/O. */ + public void onIoError() {} } private class IoParams { @@ -102,7 +92,10 @@ public class SampleChunkIoHelper implements Handler.Callback { private final ConditionVariable conditionVariable; private final ConcurrentLinkedQueue readSampleBuffer; - private IoParams(int index, long positionUs, SampleHolder sample, + private IoParams( + int index, + long positionUs, + SampleHolder sample, ConditionVariable conditionVariable, ConcurrentLinkedQueue readSampleBuffer) { this.index = index; @@ -123,8 +116,12 @@ public class SampleChunkIoHelper implements Handler.Callback { * @param samplePool allocator for a sample * @param ioCallback listeners for I/O events */ - public SampleChunkIoHelper(List ids, List mediaFormats, - @BufferReason int bufferReason, BufferManager bufferManager, SamplePool samplePool, + public SampleChunkIoHelper( + List ids, + List mediaFormats, + @BufferReason int bufferReason, + BufferManager bufferManager, + SamplePool samplePool, IoCallback ioCallback) { mTrackCount = ids.size(); mIds = ids; @@ -144,9 +141,9 @@ public class SampleChunkIoHelper implements Handler.Callback { // Small chunk duration for live playback will give more fine grained storage usage // and eviction handling for trickplay. mSampleChunkDurationUs = - bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK ? - RecordingSampleBuffer.MIN_SEEK_DURATION_US : - RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US; + bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK + ? RecordingSampleBuffer.MIN_SEEK_DURATION_US + : RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US; for (int i = 0; i < mTrackCount; ++i) { mWriteIndexEndPositionUs[i] = RecordingSampleBuffer.MIN_SEEK_DURATION_US; mWriteChunkEndPositionUs[i] = mSampleChunkDurationUs; @@ -196,8 +193,8 @@ public class SampleChunkIoHelper implements Handler.Callback { * @param conditionVariable which will be wait until the write is finished * @throws IOException */ - public void writeSample(int index, SampleHolder sample, - ConditionVariable conditionVariable) throws IOException { + public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) + throws IOException { if (mErrorNotified) { throw new IOException("Storage I/O error happened"); } @@ -228,15 +225,14 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_CLOSE_READ, index)); } - /** - * Notifies writes are finished. - */ + /** Notifies writes are finished. */ public void closeWrite() { mIoHandler.sendEmptyMessage(MSG_CLOSE_WRITE); } /** * Finishes I/O operations and releases all the resources. + * * @throws IOException */ public void release() throws IOException { @@ -320,8 +316,8 @@ public class SampleChunkIoHelper implements Handler.Callback { Pair readPosition = mBufferManager.getReadFile(mIds.get(index), params.positionUs); if (readPosition == null) { - String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs - + "is not found"; + String errorMessage = + "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs + "is not found"; SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage); throw new IOException(errorMessage); } @@ -338,8 +334,8 @@ public class SampleChunkIoHelper implements Handler.Callback { } private void doOpenWrite(int index) throws IOException { - SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0, - mSamplePool, null, 0); + SampleChunk chunk = + mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0, mSamplePool, null, 0); mWriteIoStates[index].openWrite(chunk); } @@ -378,8 +374,7 @@ public class SampleChunkIoHelper implements Handler.Callback { // Read reached write but write is not finished yet --- wait a few moments to // see if another sample is written. mIoHandler.sendMessageDelayed( - mIoHandler.obtainMessage(MSG_READ, index), - READ_RESCHEDULING_DELAY_MS); + mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS); } } } @@ -398,15 +393,21 @@ public class SampleChunkIoHelper implements Handler.Callback { mBufferDurationUs = sample.timeUs; } if (sample.timeUs >= mWriteIndexEndPositionUs[index]) { - SampleChunk currentChunk = sample.timeUs >= mWriteChunkEndPositionUs[index] ? - null : mWriteIoStates[params.index].getChunk(); + SampleChunk currentChunk = + sample.timeUs >= mWriteChunkEndPositionUs[index] + ? null + : mWriteIoStates[params.index].getChunk(); int currentOffset = (int) mWriteIoStates[params.index].getOffset(); - nextChunk = mBufferManager.createNewWriteFileIfNeeded( - mIds.get(index), mWriteIndexEndPositionUs[index], mSamplePool, - currentChunk, currentOffset); + nextChunk = + mBufferManager.createNewWriteFileIfNeeded( + mIds.get(index), + mWriteIndexEndPositionUs[index], + mSamplePool, + currentChunk, + currentOffset); mWriteIndexEndPositionUs[index] = - ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) * - RecordingSampleBuffer.MIN_SEEK_DURATION_US; + ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) + * RecordingSampleBuffer.MIN_SEEK_DURATION_US; if (nextChunk != null) { mWriteChunkEndPositionUs[index] = ((sample.timeUs / mSampleChunkDurationUs) + 1) @@ -449,13 +450,15 @@ public class SampleChunkIoHelper implements Handler.Callback { } long currentStartPositionUs = Long.MAX_VALUE; for (int trackIndex : mSelectedTracks) { - currentStartPositionUs = Math.min(currentStartPositionUs, - mReadIoStates[trackIndex].getStartPositionUs()); + currentStartPositionUs = + Math.min( + currentStartPositionUs, mReadIoStates[trackIndex].getStartPositionUs()); } for (int i = 0; i < mTrackCount; ++i) { - long evictEndPositionUs = Math.min(mBufferManager.getStartPositionUs(mIds.get(i)), - currentStartPositionUs); + long evictEndPositionUs = + Math.min( + mBufferManager.getStartPositionUs(mIds.get(i)), currentStartPositionUs); mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java b/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java index bb048e85..b89a14db 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java @@ -17,18 +17,15 @@ package com.android.tv.tuner.exoplayer.buffer; import com.google.android.exoplayer.SampleHolder; - import java.util.LinkedList; -/** - * Pool of samples to recycle ByteBuffers as much as possible. - */ +/** Pool of samples to recycle ByteBuffers as much as possible. */ public class SamplePool { private final LinkedList mSamplePool = new LinkedList<>(); /** - * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize - * an existing buffer if necessary. + * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize an + * existing buffer if necessary. */ public synchronized SampleHolder acquireSample(int size) { if (mSamplePool.isEmpty()) { @@ -40,8 +37,9 @@ public class SamplePool { SampleHolder maxSample = mSamplePool.getFirst(); for (SampleHolder sample : mSamplePool) { // Grab the smallest sufficient sample. - if (sample.data.capacity() >= size && (smallestSufficientSample == null - || smallestSufficientSample.data.capacity() > sample.data.capacity())) { + if (sample.data.capacity() >= size + && (smallestSufficientSample == null + || smallestSufficientSample.data.capacity() > sample.data.capacity())) { smallestSufficientSample = sample; } @@ -61,9 +59,7 @@ public class SamplePool { return sampleFromPool; } - /** - * Releases the sample back to the pool. - */ + /** Releases the sample back to the pool. */ public synchronized void releaseSample(SampleHolder sample) { sample.clearData(); mSamplePool.offerLast(sample); diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java index 75eac5a2..e208f2c2 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java @@ -18,12 +18,9 @@ package com.android.tv.tuner.exoplayer.buffer; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; - import java.util.LinkedList; -/** - * A sample queue which reads from the buffer and passes to player pipeline. - */ +/** A sample queue which reads from the buffer and passes to player pipeline. */ public class SampleQueue { private final LinkedList mQueue = new LinkedList<>(); private final SamplePool mSamplePool; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java index 159fde18..4c6260bf 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java @@ -17,23 +17,20 @@ package com.android.tv.tuner.exoplayer.buffer; import android.os.ConditionVariable; - import android.support.annotation.NonNull; - +import com.android.tv.common.SoftPreconditions; +import com.android.tv.tuner.exoplayer.SampleExtractor; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.android.tv.tuner.exoplayer.SampleExtractor; - import java.io.IOException; import java.util.List; /** - * Handles I/O for {@link SampleExtractor} when - * physical storage based buffer is not used. Trickplay is disabled. + * Handles I/O for {@link SampleExtractor} when physical storage based buffer is not used. Trickplay + * is disabled. */ public class SimpleSampleBuffer implements BufferManager.SampleBuffer { private final SamplePool mSamplePool = new SamplePool(); @@ -50,8 +47,8 @@ public class SimpleSampleBuffer implements BufferManager.SampleBuffer { } @Override - public synchronized void init(@NonNull List ids, - @NonNull List mediaFormats) { + public synchronized void init( + @NonNull List ids, @NonNull List mediaFormats) { int trackCount = ids.size(); mPlayingSampleQueues = new SampleQueue[trackCount]; for (int i = 0; i < trackCount; i++) { @@ -124,8 +121,8 @@ public class SimpleSampleBuffer implements BufferManager.SampleBuffer { } @Override - public void writeSample(int index, SampleHolder sample, - ConditionVariable conditionVariable) throws IOException { + public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) + throws IOException { sample.data.position(0).limit(sample.size); SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size); sampleToQueue.size = sample.size; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java index 9fe921b8..b22b8af1 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java @@ -21,26 +21,21 @@ import android.os.AsyncTask; import android.provider.Settings; import android.support.annotation.NonNull; import android.util.Pair; - import com.android.tv.common.SoftPreconditions; - import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.SortedMap; -/** - * Manages Trickplay storage. - */ +/** Manages Trickplay storage. */ public class TrickplayStorageManager implements BufferManager.StorageManager { // TODO: Support multi-sessions. private static final String BUFFER_DIR = "timeshift"; // Copied from android.provider.Settings.Global (hidden fields) - private static final String - SYS_STORAGE_THRESHOLD_PERCENTAGE = "sys_storage_threshold_percentage"; - private static final String - SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes"; + private static final String SYS_STORAGE_THRESHOLD_PERCENTAGE = + "sys_storage_threshold_percentage"; + private static final String SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes"; // Copied from android.os.StorageManager private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; @@ -54,17 +49,22 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { private static void initParamsIfNeeded(Context context, @NonNull File path) { // TODO: Support multi-sessions. - SoftPreconditions.checkState( - sBufferDir == null || sBufferDir.equals(path)); + SoftPreconditions.checkState(sBufferDir == null || sBufferDir.equals(path)); if (path.equals(sBufferDir)) { return; } sBufferDir = path; - long lowPercentage = Settings.Global.getInt(context.getContentResolver(), - SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); + long lowPercentage = + Settings.Global.getInt( + context.getContentResolver(), + SYS_STORAGE_THRESHOLD_PERCENTAGE, + DEFAULT_THRESHOLD_PERCENTAGE); long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100; - long maxLowBytes = Settings.Global.getLong(context.getContentResolver(), - SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES); + long maxLowBytes = + Settings.Global.getLong( + context.getContentResolver(), + SYS_STORAGE_THRESHOLD_MAX_BYTES, + DEFAULT_THRESHOLD_MAX_BYTES); sStorageBufferBytes = Math.min(lowPercentageToBytes, maxLowBytes); } @@ -80,28 +80,29 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { if (sLastCacheCleanUpTask != null) { sLastCacheCleanUpTask.cancel(true); } - sLastCacheCleanUpTask = new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - if (isCancelled()) { - return null; - } - File files[] = sBufferDir.listFiles(); - if (files == null || files.length == 0) { - return null; - } - for (File file : files) { - if (isCancelled()) { - break; - } - long lastModified = file.lastModified(); - if (lastModified != 0 && lastModified < now) { - file.delete(); + sLastCacheCleanUpTask = + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + if (isCancelled()) { + return null; + } + File files[] = sBufferDir.listFiles(); + if (files == null || files.length == 0) { + return null; + } + for (File file : files) { + if (isCancelled()) { + break; + } + long lastModified = file.lastModified(); + if (lastModified != 0 && lastModified < now) { + file.delete(); + } + } + return null; } - } - return null; - } - }; + }; sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -136,12 +137,9 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { } @Override - public void writeTrackInfoFiles(List formatList, boolean isAudio) { - } + public void writeTrackInfoFiles(List formatList, boolean isAudio) {} @Override - public void writeIndexFile(String trackName, - SortedMap> index) { - } - + public void writeIndexFile( + String trackName, SortedMap> index) {} } diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java index 356636cc..421192f1 100644 --- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java +++ b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java @@ -22,20 +22,18 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; - import android.support.annotation.MainThread; -import android.support.annotation.WorkerThread; import android.support.annotation.VisibleForTesting; -import com.google.android.exoplayer.SampleHolder; +import android.support.annotation.WorkerThread; import com.android.tv.Features; import com.android.tv.tuner.exoplayer.audio.AudioDecoder; - +import com.google.android.exoplayer.SampleHolder; import java.nio.ByteBuffer; /** - * The class connects {@link FfmpegDecoderService} to decode audio samples. - * In order to sandbox ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process - * without any permission and connected by binder. + * The class connects {@link FfmpegDecoderService} to decode audio samples. In order to sandbox + * ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process without any permission + * and connected by binder. */ public class FfmpegDecoderClient extends AudioDecoder { private static FfmpegDecoderClient sInstance; @@ -47,36 +45,38 @@ public class FfmpegDecoderClient extends AudioDecoder { "com.android.tv.tuner.exoplayer.ffmpeg.IFfmpegDecoder"; private static final long FFMPEG_SERVICE_CONNECT_TIMEOUT_MS = 500; - private final ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - mService = IFfmpegDecoder.Stub.asInterface(service); - synchronized (FfmpegDecoderClient.this) { - try { - mIsAvailable = mService.isAvailable(); - } catch (RemoteException e) { + private final ServiceConnection mConnection = + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mService = IFfmpegDecoder.Stub.asInterface(service); + synchronized (FfmpegDecoderClient.this) { + try { + mIsAvailable = mService.isAvailable(); + } catch (RemoteException e) { + } + FfmpegDecoderClient.this.notify(); + } } - FfmpegDecoderClient.this.notify(); - } - } - @Override - public void onServiceDisconnected(ComponentName className) { - synchronized (FfmpegDecoderClient.this) { - sInstance.releaseLocked(); - mIsAvailable = false; - mService = null; - } - } - }; + @Override + public void onServiceDisconnected(ComponentName className) { + synchronized (FfmpegDecoderClient.this) { + sInstance.releaseLocked(); + mIsAvailable = false; + mService = null; + } + } + }; /** * Connects to the decoder service for future uses. + * * @param context * @return {@code true} when decoder service is connected. */ @MainThread - public synchronized static boolean connect(Context context) { + public static synchronized boolean connect(Context context) { if (Features.AC3_SOFTWARE_DECODE.isEnabled(context)) { if (sInstance == null) { sInstance = new FfmpegDecoderClient(); @@ -96,10 +96,11 @@ public class FfmpegDecoderClient extends AudioDecoder { /** * Disconnects from the decoder service and release resources. + * * @param context */ @MainThread - public synchronized static void disconnect(Context context) { + public static synchronized void disconnect(Context context) { if (sInstance != null) { synchronized (sInstance) { sInstance.releaseLocked(); @@ -114,29 +115,26 @@ public class FfmpegDecoderClient extends AudioDecoder { } /** - * Returns whether service is available or not. - * Before using client, this should be used to check availability. + * Returns whether service is available or not. Before using client, this should be used to + * check availability. */ @WorkerThread - public synchronized static boolean isAvailable() { + public static synchronized boolean isAvailable() { if (sInstance != null) { return sInstance.available(); } return false; } - /** - * Returns an client instance. - */ - public synchronized static FfmpegDecoderClient getInstance() { + /** Returns an client instance. */ + public static synchronized FfmpegDecoderClient getInstance() { if (sInstance != null) { sInstance.createDecoder(); } return sInstance; } - private FfmpegDecoderClient() { - } + private FfmpegDecoderClient() {} private synchronized boolean available() { if (mIsAvailable == null) { @@ -163,7 +161,7 @@ public class FfmpegDecoderClient extends AudioDecoder { return; } try { - mService.release(); + mService.release(); } catch (RemoteException e) { } } @@ -178,7 +176,7 @@ public class FfmpegDecoderClient extends AudioDecoder { if (mIsAvailable == null || mIsAvailable == false) { return; } - byte[] sampleBytes = new byte [sampleHolder.data.limit()]; + byte[] sampleBytes = new byte[sampleHolder.data.limit()]; sampleHolder.data.get(sampleBytes, 0, sampleBytes.length); try { mService.decode(sampleHolder.timeUs, sampleBytes); diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java index 3ebdd381..0172d817 100644 --- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java +++ b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java @@ -24,17 +24,12 @@ import android.os.AsyncTask; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.util.Log; - import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioDecoder; - import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; -/** - * Ffmpeg based audio decoder service. - * It should be isolatedProcess due to security reason. - */ +/** Ffmpeg based audio decoder service. It should be isolatedProcess due to security reason. */ public class FfmpegDecoderService extends Service { private static final String TAG = "FfmpegDecoderService"; private static final boolean DEBUG = false; @@ -56,7 +51,7 @@ public class FfmpegDecoderService extends Service { private FfmpegDecoder mBinder = new FfmpegDecoder(); private volatile Object mMinijailSetupMonitor = new Object(); - //@GuardedBy("mMinijailSetupMonitor") + // @GuardedBy("mMinijailSetupMonitor") private volatile Boolean mMinijailSetup; @Override @@ -118,6 +113,7 @@ public class FfmpegDecoderService extends Service { private final class FfmpegDecoder extends IFfmpegDecoder.Stub { FfmpegAudioDecoder mDecoder; + @Override public boolean isAvailable() { return isMinijailSetupDone() && FfmpegAudioDecoder.isAvailable(); @@ -201,5 +197,6 @@ public class FfmpegDecoderService extends Service { } private native void nativeSetupMinijail(int policyFd); + private native void nativeTestMinijail(); } diff --git a/src/com/android/tv/tuner/layout/ScaledLayout.java b/src/com/android/tv/tuner/layout/ScaledLayout.java index 379ea70e..dd92b641 100644 --- a/src/com/android/tv/tuner/layout/ScaledLayout.java +++ b/src/com/android/tv/tuner/layout/ScaledLayout.java @@ -26,28 +26,25 @@ import android.util.Log; import android.view.Display; import android.view.View; import android.view.ViewGroup; - import com.android.tv.tuner.R; - import java.util.Arrays; import java.util.Comparator; -/** - * A layout that scales its children using the given percentage value. - */ +/** A layout that scales its children using the given percentage value. */ public class ScaledLayout extends ViewGroup { private static final String TAG = "ScaledLayout"; private static final boolean DEBUG = false; - private static final Comparator mRectTopLeftSorter = new Comparator() { - @Override - public int compare(Rect lhs, Rect rhs) { - if (lhs.top != rhs.top) { - return lhs.top - rhs.top; - } else { - return lhs.left - rhs.left; - } - } - }; + private static final Comparator mRectTopLeftSorter = + new Comparator() { + @Override + public int compare(Rect lhs, Rect rhs) { + if (lhs.top != rhs.top) { + return lhs.top - rhs.top; + } else { + return lhs.left - rhs.left; + } + } + }; private Rect[] mRectArray; private final int mMaxWidth; @@ -64,8 +61,8 @@ public class ScaledLayout extends ViewGroup { public ScaledLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); Point size = new Point(); - DisplayManager displayManager = (DisplayManager) getContext() - .getSystemService(Context.DISPLAY_SERVICE); + DisplayManager displayManager = + (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); display.getRealSize(size); mMaxWidth = size.x; @@ -73,21 +70,19 @@ public class ScaledLayout extends ViewGroup { } /** - * ScaledLayoutParams stores the four scale factors. - *
- * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) % + * ScaledLayoutParams stores the four scale factors.
+ * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) % * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) % *
* In XML, for example, - *

-     * {@code
+     *
+     * 
{@code
      * 
-     * }
-     * 
+ * }
*/ public static class ScaledLayoutParams extends ViewGroup.LayoutParams { public static final float SCALE_UNSPECIFIED = -1; @@ -96,8 +91,8 @@ public class ScaledLayout extends ViewGroup { public final float scaleStartCol; public final float scaleEndCol; - public ScaledLayoutParams(float scaleStartRow, float scaleEndRow, - float scaleStartCol, float scaleEndCol) { + public ScaledLayoutParams( + float scaleStartRow, float scaleEndRow, float scaleStartCol, float scaleEndCol) { super(MATCH_PARENT, MATCH_PARENT); this.scaleStartRow = scaleStartRow; this.scaleEndRow = scaleEndRow; @@ -107,16 +102,19 @@ public class ScaledLayout extends ViewGroup { public ScaledLayoutParams(Context context, AttributeSet attrs) { super(MATCH_PARENT, MATCH_PARENT); - TypedArray array = - context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout); scaleStartRow = - array.getFloat(R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED); + array.getFloat( + R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED); scaleEndRow = - array.getFloat(R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED); + array.getFloat( + R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED); scaleStartCol = - array.getFloat(R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED); + array.getFloat( + R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED); scaleEndCol = - array.getFloat(R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED); + array.getFloat( + R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED); array.recycle(); } } @@ -155,31 +153,43 @@ public class ScaledLayout extends ViewGroup { scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol; scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol; if (scaleStartRow < 0 || scaleStartRow > 1) { - throw new RuntimeException("A child of ScaledLayout should have a range of " - + "scaleStartRow between 0 and 1"); + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleStartRow between 0 and 1"); } if (scaleEndRow < scaleStartRow || scaleStartRow > 1) { - throw new RuntimeException("A child of ScaledLayout should have a range of " - + "scaleEndRow between scaleStartRow and 1"); + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleEndRow between scaleStartRow and 1"); } if (scaleEndCol < 0 || scaleEndCol > 1) { - throw new RuntimeException("A child of ScaledLayout should have a range of " - + "scaleStartCol between 0 and 1"); + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleStartCol between 0 and 1"); } if (scaleEndCol < scaleStartCol || scaleEndCol > 1) { - throw new RuntimeException("A child of ScaledLayout should have a range of " - + "scaleEndCol between scaleStartCol and 1"); + throw new RuntimeException( + "A child of ScaledLayout should have a range of " + + "scaleEndCol between scaleStartCol and 1"); } if (DEBUG) { - Log.d(TAG, String.format("onMeasure child scaleStartRow: %f scaleEndRow: %f " - + "scaleStartCol: %f scaleEndCol: %f", - scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); + Log.d( + TAG, + String.format( + "onMeasure child scaleStartRow: %f scaleEndRow: %f " + + "scaleStartCol: %f scaleEndCol: %f", + scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); } - mRectArray[i] = new Rect((int) (scaleStartCol * width), (int) (scaleStartRow * height), - (int) (scaleEndCol * width), (int) (scaleEndRow * height)); + mRectArray[i] = + new Rect( + (int) (scaleStartCol * width), + (int) (scaleStartRow * height), + (int) (scaleEndCol * width), + (int) (scaleEndRow * height)); int scaleWidth = (int) (width * (scaleEndCol - scaleStartCol)); - int childWidthSpec = MeasureSpec.makeMeasureSpec( - scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY); + int childWidthSpec = + MeasureSpec.makeMeasureSpec( + scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY); int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(childWidthSpec, childHeightSpec); @@ -201,8 +211,10 @@ public class ScaledLayout extends ViewGroup { } } int scaleHeight = (int) (height * (scaleEndRow - scaleStartRow)); - childHeightSpec = MeasureSpec.makeMeasureSpec( - scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, MeasureSpec.EXACTLY); + childHeightSpec = + MeasureSpec.makeMeasureSpec( + scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, + MeasureSpec.EXACTLY); child.measure(childWidthSpec, childHeightSpec); } @@ -225,7 +237,8 @@ public class ScaledLayout extends ViewGroup { for (int j = i + 1; j < visibleRectCount; ++j) { if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) { visibleRectGroup[j] = visibleRectGroup[i]; - visibleRectArray[j].set(visibleRectArray[j].left, + visibleRectArray[j].set( + visibleRectArray[j].left, visibleRectArray[i].bottom, visibleRectArray[j].right, visibleRectArray[i].bottom + visibleRectArray[j].height()); @@ -239,7 +252,8 @@ public class ScaledLayout extends ViewGroup { int overflowedHeight = visibleRectArray[i].bottom - height; for (int j = 0; j <= i; ++j) { if (visibleRectGroup[i] == visibleRectGroup[j]) { - visibleRectArray[j].set(visibleRectArray[j].left, + visibleRectArray[j].set( + visibleRectArray[j].left, visibleRectArray[j].top - overflowedHeight, visibleRectArray[j].right, visibleRectArray[j].bottom - overflowedHeight); @@ -263,9 +277,11 @@ public class ScaledLayout extends ViewGroup { int childBottom = paddingLeft + mRectArray[i].bottom; int childRight = paddingTop + mRectArray[i].right; if (DEBUG) { - Log.d(TAG, String.format("layoutChild bottom: %d left: %d right: %d top: %d", - childBottom, childLeft, - childRight, childTop)); + Log.d( + TAG, + String.format( + "layoutChild bottom: %d left: %d right: %d top: %d", + childBottom, childLeft, childRight, childTop)); } child.layout(childLeft, childTop, childRight, childBottom); } diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java index e0e21a20..75d1c34c 100644 --- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java +++ b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java @@ -20,18 +20,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - -import com.android.tv.common.BuildConfig; import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.tuner.R; - import java.util.List; -import java.util.TimeZone; -/** - * A fragment for connection type selection. - */ +/** A fragment for connection type selection. */ public class ConnectionTypeFragment extends SetupMultiPaneFragment { public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ConnectionTypeFragment"; @@ -74,22 +68,25 @@ public class ConnectionTypeFragment extends SetupMultiPaneFragment { @NonNull @Override public Guidance onCreateGuidance(Bundle savedInstanceState) { - return new Guidance(getString(R.string.ut_connection_title), + return new Guidance( + getString(R.string.ut_connection_title), getString(R.string.ut_connection_description), - getString(R.string.ut_setup_breadcrumb), null); + getString(R.string.ut_setup_breadcrumb), + null); } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { String[] choices = getResources().getStringArray(R.array.ut_connection_choices); int length = choices.length - 1; int startOffset = 0; for (int i = 0; i < length; ++i) { - actions.add(new GuidedAction.Builder(getActivity()) - .id(startOffset + i) - .title(choices[i]) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(startOffset + i) + .title(choices[i]) + .build()); } } diff --git a/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/src/com/android/tv/tuner/setup/PostalCodeFragment.java index 025b9193..fbf03909 100644 --- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java +++ b/src/com/android/tv/tuner/setup/PostalCodeFragment.java @@ -32,12 +32,9 @@ import com.android.tv.tuner.util.PostalCodeUtils; import com.android.tv.util.LocationUtils; import java.util.List; -/** - * A fragment for initial screen. - */ +/** A fragment for initial screen. */ public class PostalCodeFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = - "com.android.tv.tuner.setup.PostalCodeFragment"; + public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.PostalCodeFragment"; private static final int VIEW_TYPE_EDITABLE = 1; @Override @@ -141,11 +138,15 @@ public class PostalCodeFragment extends SetupMultiPaneFragment { } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { String description = getString(R.string.postal_code_action_description); - mEditAction = new GuidedAction.Builder(getActivity()).id(0).editable(true) - .description(description).build(); + mEditAction = + new GuidedAction.Builder(getActivity()) + .id(0) + .editable(true) + .description(description) + .build(); actions.add(mEditAction); } @@ -175,4 +176,4 @@ public class PostalCodeFragment extends SetupMultiPaneFragment { }; } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/setup/ScanFragment.java b/src/com/android/tv/tuner/setup/ScanFragment.java index b6936e38..044b0d26 100644 --- a/src/com/android/tv/tuner/setup/ScanFragment.java +++ b/src/com/android/tv/tuner/setup/ScanFragment.java @@ -35,7 +35,6 @@ import android.widget.Button; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; - import com.android.tv.common.SoftPreconditions; import com.android.tv.common.ui.setup.SetupFragment; import com.android.tv.tuner.ChannelScanFileParser; @@ -51,16 +50,13 @@ import com.android.tv.tuner.source.TsStreamer; import com.android.tv.tuner.source.TunerTsStreamer; import com.android.tv.tuner.tvinput.ChannelDataManager; import com.android.tv.tuner.tvinput.EventDetector; - import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -/** - * A fragment for scanning channels. - */ +/** A fragment for scanning channels. */ public class ScanFragment extends SetupFragment { private static final String TAG = "ScanFragment"; private static final boolean DEBUG = false; @@ -94,8 +90,8 @@ public class ScanFragment extends SetupFragment { private Button mCancelButton; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreateView"); View view = super.onCreateView(inflater, container, savedInstanceState); mChannelDataManager = new ChannelDataManager(getActivity()); @@ -112,12 +108,13 @@ public class ScanFragment extends SetupFragment { progressHolder.setLayoutTransition(transition); mChannelHolder = view.findViewById(R.id.channel_holder); mCancelButton = (Button) view.findViewById(R.id.tune_cancel); - mCancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - finishScan(false); - } - }); + mCancelButton.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + finishScan(false); + } + }); Bundle args = getArguments(); int tunerType = (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0)); // TODO: Handle the case when the fragment is restored. @@ -172,14 +169,17 @@ public class ScanFragment extends SetupFragment { mChannelScanTask.cancelScan(cancel); // Notifies a user of waiting to finish the scanning process. - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (mChannelScanTask != null) { - mChannelScanTask.showFinishingProgressDialog(); - } - } - }, SHOW_PROGRESS_DIALOG_DELAY_MS); + new Handler() + .postDelayed( + new Runnable() { + @Override + public void run() { + if (mChannelScanTask != null) { + mChannelScanTask.showFinishingProgressDialog(); + } + } + }, + SHOW_PROGRESS_DIALOG_DELAY_MS); // Hides the cancel button. mCancelButton.setEnabled(false); @@ -223,8 +223,8 @@ public class ScanFragment extends SetupFragment { final Context context = parent.getContext(); if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.ut_channel_list, parent, false); } @@ -276,34 +276,44 @@ public class ScanFragment extends SetupFragment { } private void maybeSetChannelListVisible() { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - int channelsFound = mAdapter.getCount(); - if (!mChannelListVisible && channelsFound > 0) { - String format = getResources().getQuantityString( - R.plurals.ut_channel_scan_message, channelsFound, channelsFound); - mScanningMessage.setText(String.format(format, channelsFound)); - mChannelHolder.setVisibility(View.VISIBLE); - mChannelListVisible = true; - } - } - }); + mActivity.runOnUiThread( + new Runnable() { + @Override + public void run() { + int channelsFound = mAdapter.getCount(); + if (!mChannelListVisible && channelsFound > 0) { + String format = + getResources() + .getQuantityString( + R.plurals.ut_channel_scan_message, + channelsFound, + channelsFound); + mScanningMessage.setText(String.format(format, channelsFound)); + mChannelHolder.setVisibility(View.VISIBLE); + mChannelListVisible = true; + } + } + }); } private void addChannel(final TunerChannel channel) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mAdapter.add(channel); - if (mChannelListVisible) { - int channelsFound = mAdapter.getCount(); - String format = getResources().getQuantityString( - R.plurals.ut_channel_scan_message, channelsFound, channelsFound); - mScanningMessage.setText(String.format(format, channelsFound)); - } - } - }); + mActivity.runOnUiThread( + new Runnable() { + @Override + public void run() { + mAdapter.add(channel); + if (mChannelListVisible) { + int channelsFound = mAdapter.getCount(); + String format = + getResources() + .getQuantityString( + R.plurals.ut_channel_scan_message, + channelsFound, + channelsFound); + mScanningMessage.setText(String.format(format, channelsFound)); + } + } + }); } @Override @@ -312,8 +322,9 @@ public class ScanFragment extends SetupFragment { if (SCAN_LOCAL_STREAMS) { FileTsStreamer.addLocalStreamFiles(mScanChannelList); } - mScanChannelList.addAll(ChannelScanFileParser.parseScanFile( - getResources().openRawResource(mChannelMapId))); + mScanChannelList.addAll( + ChannelScanFileParser.parseScanFile( + getResources().openRawResource(mChannelMapId))); scanChannels(); return null; } @@ -362,8 +373,11 @@ public class ScanFragment extends SetupFragment { try { mLatch.await(CHANNEL_SCAN_PERIOD_MS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - Log.e(TAG, "The current thread is interrupted during scanChannels(). " + - "The TS stream is stopped earlier than expected.", e); + Log.e( + TAG, + "The current thread is interrupted during scanChannels(). " + + "The TS stream is stopped earlier than expected.", + e); } streamer.stopStream(); @@ -385,21 +399,23 @@ public class ScanFragment extends SetupFragment { if (DEBUG) Log.i(TAG, "Channel scan ended"); } - private void addChannelsWithoutVct(ChannelScanFileParser.ScanChannel scanChannel) { if (scanChannel.radioFrequencyNumber == null || !(mScanTsStreamer instanceof TunerTsStreamer)) { return; } - for (TunerChannel tunerChannel - : ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) { + for (TunerChannel tunerChannel : + ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) { if ((tunerChannel.getVideoPid() != TunerChannel.INVALID_PID) && (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) { tunerChannel.setFrequency(scanChannel.frequency); tunerChannel.setModulation(scanChannel.modulation); - tunerChannel.setShortName(String.format(Locale.US, VCTLESS_CHANNEL_NAME_FORMAT, - scanChannel.radioFrequencyNumber, - tunerChannel.getProgramNumber())); + tunerChannel.setShortName( + String.format( + Locale.US, + VCTLESS_CHANNEL_NAME_FORMAT, + scanChannel.radioFrequencyNumber, + tunerChannel.getProgramNumber())); tunerChannel.setVirtualMajor(scanChannel.radioFrequencyNumber); tunerChannel.setVirtualMinor(tunerChannel.getProgramNumber()); onChannelDetected(tunerChannel, true); @@ -446,8 +462,9 @@ public class ScanFragment extends SetupFragment { public void showFinishingProgressDialog() { // Show a progress dialog to wait for the scanning process if it's not done yet. if (!mIsFinished && mFinishingProgressDialog == null) { - mFinishingProgressDialog = ProgressDialog.show(mActivity, "", - getString(R.string.ut_setup_cancel), true, false); + mFinishingProgressDialog = + ProgressDialog.show( + mActivity, "", getString(R.string.ut_setup_cancel), true, false); } } @@ -456,7 +473,8 @@ public class ScanFragment extends SetupFragment { mChannelDataManager.setCurrentVersion(mActivity); mChannelDataManager.releaseSafely(); mIsFinished = true; - TunerPreferences.setScannedChannelCount(mActivity.getApplicationContext(), + TunerPreferences.setScannedChannelCount( + mActivity.getApplicationContext(), mChannelDataManager.getScannedChannelCount()); // Cancel a previously shown notification. TunerSetupActivity.cancelNotification(mActivity.getApplicationContext()); @@ -492,17 +510,19 @@ public class ScanFragment extends SetupFragment { } final String displayNumber = Integer.toString(mProgramNumber); final String name = "Channel-" + mProgramNumber; - mEventListener.onChannelDetected(new TunerChannel(mProgramNumber, new ArrayList<>()) { - @Override - public String getDisplayNumber() { - return displayNumber; - } - - @Override - public String getName() { - return name; - } - }, true); + mEventListener.onChannelDetected( + new TunerChannel(mProgramNumber, new ArrayList<>()) { + @Override + public String getDisplayNumber() { + return displayNumber; + } + + @Override + public String getName() { + return name; + } + }, + true); return true; } @@ -512,8 +532,7 @@ public class ScanFragment extends SetupFragment { } @Override - public void stopStream() { - } + public void stopStream() {} @Override public TsDataSource createDataSource() { diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java index 3b8cd823..a6160ef1 100644 --- a/src/com/android/tv/tuner/setup/ScanResultFragment.java +++ b/src/com/android/tv/tuner/setup/ScanResultFragment.java @@ -22,22 +22,16 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; - import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.tuner.R; import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.util.TunerInputInfoUtils; - import java.util.List; -/** - * A fragment for initial screen. - */ +/** A fragment for initial screen. */ public class ScanResultFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = - "com.android.tv.tuner.setup.ScanResultFragment"; + public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ScanResultFragment"; @Override protected SetupGuidedStepFragment onCreateContentFragment() { @@ -71,10 +65,16 @@ public class ScanResultFragment extends SetupMultiPaneFragment { String breadcrumb; if (mChannelCountOnPreference > 0) { Resources res = getResources(); - title = res.getQuantityString(R.plurals.ut_result_found_title, - mChannelCountOnPreference, mChannelCountOnPreference); - description = res.getQuantityString(R.plurals.ut_result_found_description, - mChannelCountOnPreference, mChannelCountOnPreference); + title = + res.getQuantityString( + R.plurals.ut_result_found_title, + mChannelCountOnPreference, + mChannelCountOnPreference); + description = + res.getQuantityString( + R.plurals.ut_result_found_description, + mChannelCountOnPreference, + mChannelCountOnPreference); breadcrumb = null; } else { Bundle args = getArguments(); @@ -97,8 +97,8 @@ public class ScanResultFragment extends SetupMultiPaneFragment { } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { String[] choices; int doneActionIndex; if (mChannelCountOnPreference > 0) { @@ -110,11 +110,17 @@ public class ScanResultFragment extends SetupMultiPaneFragment { } for (int i = 0; i < choices.length; ++i) { if (i == doneActionIndex) { - actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE) - .title(choices[i]).build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_DONE) + .title(choices[i]) + .build()); } else { - actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i]) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(i) + .title(choices[i]) + .build()); } } } diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java index e9f3baa7..58cfc927 100644 --- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java +++ b/src/com/android/tv/tuner/setup/TunerSetupActivity.java @@ -42,7 +42,6 @@ import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; - import com.android.tv.Features; import com.android.tv.TvApplication; import com.android.tv.common.AutoCloseableUtils; @@ -58,19 +57,14 @@ import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.tuner.util.PostalCodeUtils; - import java.util.concurrent.Executor; -/** - * An activity that serves tuner setup process. - */ +/** An activity that serves tuner setup process. */ public class TunerSetupActivity extends SetupActivity { private static final String TAG = "TunerSetupActivity"; private static final boolean DEBUG = false; - /** - * Key for passing tuner type to sub-fragments. - */ + /** Key for passing tuner type to sub-fragments. */ public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType"; // For the notification. @@ -128,7 +122,8 @@ public class TunerSetupActivity extends SetupActivity { if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // No need to check the request result. - requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, + requestPermissions( + new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); } mTunerHalFactory = new TunerHalFactory(getApplicationContext()); @@ -145,10 +140,11 @@ public class TunerSetupActivity extends SetupActivity { } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED && Experiments.CLOUD_EPG.get()) { try { // Updating postal code takes time, therefore we should update postal code @@ -169,8 +165,9 @@ public class TunerSetupActivity extends SetupActivity { Bundle args = new Bundle(); args.putInt(KEY_TUNER_TYPE, mTunerType); fragment.setArguments(args); - fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); + fragment.setShortDistance( + SetupFragment.FRAGMENT_EXIT_TRANSITION + | SetupFragment.FRAGMENT_REENTER_TRANSITION); return fragment; } else { return null; @@ -213,14 +210,17 @@ public class TunerSetupActivity extends SetupActivity { case ConnectionTypeFragment.ACTION_CATEGORY: if (mTunerHalFactory.getOrCreate() == null) { finish(); - Toast.makeText(getApplicationContext(), - R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show(); + Toast.makeText( + getApplicationContext(), + R.string.ut_channel_scan_tuner_unavailable, + Toast.LENGTH_LONG) + .show(); return true; } mLastScanFragment = new ScanFragment(); Bundle args1 = new Bundle(); - args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, - CHANNEL_MAP_SCAN_FILE[actionId]); + args1.putInt( + ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]); args1.putInt(KEY_TUNER_TYPE, mTunerType); mLastScanFragment.setArguments(args1); showFragment(mLastScanFragment, true); @@ -236,8 +236,9 @@ public class TunerSetupActivity extends SetupActivity { Bundle args2 = new Bundle(); args2.putInt(KEY_TUNER_TYPE, mTunerType); fragment.setArguments(args2); - fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); + fragment.setShortDistance( + SetupFragment.FRAGMENT_EXIT_TRANSITION + | SetupFragment.FRAGMENT_REENTER_TRANSITION); showFragment(fragment, true); return true; } @@ -250,8 +251,9 @@ public class TunerSetupActivity extends SetupActivity { break; default: SetupFragment fragment = new ConnectionTypeFragment(); - fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_RETURN_TRANSITION); + fragment.setShortDistance( + SetupFragment.FRAGMENT_ENTER_TRANSITION + | SetupFragment.FRAGMENT_RETURN_TRANSITION); showFragment(fragment, true); break; } @@ -269,7 +271,8 @@ public class TunerSetupActivity extends SetupActivity { String lastTag = manager.getBackStackEntryAt(count - 1).getName(); if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) { // Pops fragment including ScanFragment. - manager.popBackStack(manager.getBackStackEntryAt(count - 2).getName(), + manager.popBackStack( + manager.getBackStackEntryAt(count - 2).getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE); return true; } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) { @@ -293,8 +296,8 @@ public class TunerSetupActivity extends SetupActivity { * A callback to be invoked when the TvInputService is enabled or disabled. * * @param context a {@link Context} instance - * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled; - * otherwise {@code false} + * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled; otherwise + * {@code false} */ public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) { // Send a notification for tuner setup if there's no channels and the tuner TV input @@ -316,12 +319,15 @@ public class TunerSetupActivity extends SetupActivity { * @param context a {@link Context} instance */ public static Intent createSetupActivity(Context context) { - String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(), - TunerTvInputService.class.getName())); + String inputId = + TvContract.buildInputId( + new ComponentName( + context.getPackageName(), TunerTvInputService.class.getName())); // Make an intent to launch the setup activity of TV tuner input. - Intent intent = TvCommonUtils.createSetupIntent( - new Intent(context, TunerSetupActivity.class), inputId); + Intent intent = + TvCommonUtils.createSetupIntent( + new Intent(context, TunerSetupActivity.class), inputId); intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId); Intent tvActivityIntent = new Intent(); tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME)); @@ -329,23 +335,17 @@ public class TunerSetupActivity extends SetupActivity { return intent; } - /** - * Gets the currently used tuner HAL. - */ + /** Gets the currently used tuner HAL. */ TunerHal getTunerHal() { return mTunerHalFactory.getOrCreate(); } - /** - * Generates tuner HAL. - */ + /** Generates tuner HAL. */ void generateTunerHal() { mTunerHalFactory.generate(); } - /** - * Clears the currently used tuner HAL. - */ + /** Clears the currently used tuner HAL. */ void clearTunerHal() { mTunerHalFactory.clear(); } @@ -356,13 +356,13 @@ public class TunerSetupActivity extends SetupActivity { * @param context a {@link Context} instance */ private static PendingIntent createPendingIntentForSetupActivity(Context context) { - return PendingIntent.getActivity(context, 0, createSetupActivity(context), - PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity( + context, 0, createSetupActivity(context), PendingIntent.FLAG_UPDATE_CURRENT); } private static void sendNotification(Context context, Integer tunerType) { - SoftPreconditions.checkState(tunerType != null, TAG, - "tunerType is null when send notification"); + SoftPreconditions.checkState( + tunerType != null, TAG, "tunerType is null when send notification"); if (tunerType == null) { return; } @@ -384,8 +384,8 @@ public class TunerSetupActivity extends SetupActivity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { sendNotificationInternal(context, contentTitle, contentText); } else { - Bitmap largeIcon = BitmapFactory.decodeResource(resources, - R.drawable.recommendation_antenna); + Bitmap largeIcon = + BitmapFactory.decodeResource(resources, R.drawable.recommendation_antenna); sendRecommendationCard(context, contentTitle, contentText, largeIcon); } } @@ -395,58 +395,68 @@ public class TunerSetupActivity extends SetupActivity { * * @param context a {@link Context} instance */ - private static void sendRecommendationCard(Context context, String contentTitle, - String contentText, Bitmap largeIcon) { + private static void sendRecommendationCard( + Context context, String contentTitle, String contentText, Bitmap largeIcon) { // Build and send the notification. - Notification notification = new NotificationCompat.BigPictureStyle( - new NotificationCompat.Builder(context) - .setAutoCancel(false) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setContentInfo(contentText) - .setCategory(Notification.CATEGORY_RECOMMENDATION) - .setLargeIcon(largeIcon) - .setSmallIcon(context.getResources().getIdentifier( - TAG_ICON, TAG_DRAWABLE, context.getPackageName())) - .setContentIntent(createPendingIntentForSetupActivity(context))) - .build(); - NotificationManager notificationManager = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); + Notification notification = + new NotificationCompat.BigPictureStyle( + new NotificationCompat.Builder(context) + .setAutoCancel(false) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setContentInfo(contentText) + .setCategory(Notification.CATEGORY_RECOMMENDATION) + .setLargeIcon(largeIcon) + .setSmallIcon( + context.getResources() + .getIdentifier( + TAG_ICON, + TAG_DRAWABLE, + context.getPackageName())) + .setContentIntent( + createPendingIntentForSetupActivity(context))) + .build(); + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); } - private static void sendNotificationInternal(Context context, String contentTitle, - String contentText) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService( - Context.NOTIFICATION_SERVICE); - notificationManager.createNotificationChannel(new NotificationChannel( - TUNER_SET_UP_NOTIFICATION_CHANNEL_ID, - context.getResources().getString(R.string.ut_setup_notification_channel_name), - NotificationManager.IMPORTANCE_HIGH)); - Notification notification = new Notification.Builder( - context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setSmallIcon(context.getResources().getIdentifier( - TAG_ICON, TAG_DRAWABLE, context.getPackageName())) - .setContentIntent(createPendingIntentForSetupActivity(context)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .extend(new Notification.TvExtender()) - .build(); + private static void sendNotificationInternal( + Context context, String contentTitle, String contentText) { + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel( + new NotificationChannel( + TUNER_SET_UP_NOTIFICATION_CHANNEL_ID, + context.getResources() + .getString(R.string.ut_setup_notification_channel_name), + NotificationManager.IMPORTANCE_HIGH)); + Notification notification = + new Notification.Builder(context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setSmallIcon( + context.getResources() + .getIdentifier( + TAG_ICON, TAG_DRAWABLE, context.getPackageName())) + .setContentIntent(createPendingIntentForSetupActivity(context)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .extend(new Notification.TvExtender()) + .build(); notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); } private void showPostalCodeFragment() { SetupFragment fragment = new PostalCodeFragment(); - fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_RETURN_TRANSITION); + fragment.setShortDistance( + SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION); showFragment(fragment, true); } private void showConnectionTypeFragment() { SetupFragment fragment = new ConnectionTypeFragment(); - fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_RETURN_TRANSITION); + fragment.setShortDistance( + SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION); showFragment(fragment, true); } @@ -456,16 +466,15 @@ public class TunerSetupActivity extends SetupActivity { * @param context a {@link Context} instance */ public static void cancelNotification(Context context) { - NotificationManager notificationManager = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID); } @VisibleForTesting static class TunerHalFactory { private Context mContext; - @VisibleForTesting - TunerHal mTunerHal; + @VisibleForTesting TunerHal mTunerHal; private GenerateTunerHalTask mGenerateTunerHalTask; private final Executor mExecutor; @@ -497,9 +506,7 @@ public class TunerSetupActivity extends SetupActivity { return mTunerHal; } - /** - * Generates tuner hal for scanning with asynchronous tasks. - */ + /** Generates tuner hal for scanning with asynchronous tasks. */ @MainThread void generate() { if (mGenerateTunerHalTask == null && mTunerHal == null) { @@ -508,9 +515,7 @@ public class TunerSetupActivity extends SetupActivity { } } - /** - * Clears the currently used tuner hal. - */ + /** Clears the currently used tuner hal. */ @MainThread void clear() { if (mGenerateTunerHalTask != null) { @@ -540,4 +545,4 @@ public class TunerSetupActivity extends SetupActivity { } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/setup/WelcomeFragment.java b/src/com/android/tv/tuner/setup/WelcomeFragment.java index feae1ec9..326fe126 100644 --- a/src/com/android/tv/tuner/setup/WelcomeFragment.java +++ b/src/com/android/tv/tuner/setup/WelcomeFragment.java @@ -28,12 +28,9 @@ import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.TunerPreferences; import java.util.List; -/** - * A fragment for initial screen. - */ +/** A fragment for initial screen. */ public class WelcomeFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = - "com.android.tv.tuner.setup.WelcomeFragment"; + public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.WelcomeFragment"; @Override protected SetupGuidedStepFragment onCreateContentFragment() { @@ -67,8 +64,11 @@ public class WelcomeFragment extends SetupMultiPaneFragment { public Guidance onCreateGuidance(Bundle savedInstanceState) { String title; String description; - int tunerType = getArguments().getInt(TunerSetupActivity.KEY_TUNER_TYPE, - TunerHal.TUNER_TYPE_BUILT_IN); + int tunerType = + getArguments() + .getInt( + TunerSetupActivity.KEY_TUNER_TYPE, + TunerHal.TUNER_TYPE_BUILT_IN); if (mChannelCountOnPreference == 0) { switch (tunerType) { case TunerHal.TUNER_TYPE_USB: @@ -100,16 +100,23 @@ public class WelcomeFragment extends SetupMultiPaneFragment { } @Override - public void onCreateActions(@NonNull List actions, - Bundle savedInstanceState) { - String[] choices = getResources().getStringArray(mChannelCountOnPreference == 0 - ? R.array.ut_setup_new_choices : R.array.ut_setup_again_choices); + public void onCreateActions( + @NonNull List actions, Bundle savedInstanceState) { + String[] choices = + getResources() + .getStringArray( + mChannelCountOnPreference == 0 + ? R.array.ut_setup_new_choices + : R.array.ut_setup_again_choices); for (int i = 0; i < choices.length - 1; ++i) { - actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i]) - .build()); + actions.add( + new GuidedAction.Builder(getActivity()).id(i).title(choices[i]).build()); } - actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE) - .title(choices[choices.length - 1]).build()); + actions.add( + new GuidedAction.Builder(getActivity()) + .id(ACTION_DONE) + .title(choices[choices.length - 1]) + .build()); } @Override diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java index f17dd46b..f74274f4 100644 --- a/src/com/android/tv/tuner/source/FileTsStreamer.java +++ b/src/com/android/tv/tuner/source/FileTsStreamer.java @@ -20,9 +20,6 @@ import android.content.Context; import android.os.Environment; import android.util.Log; import android.util.SparseBooleanArray; - -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.upstream.DataSpec; import com.android.tv.Features; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.ChannelScanFileParser.ScanChannel; @@ -30,7 +27,8 @@ import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.tvinput.EventDetector; import com.android.tv.tuner.tvinput.FileSourceEventDetector; - +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.upstream.DataSpec; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -105,13 +103,16 @@ public class FileTsStreamer implements TsStreamer { } @Override - public void close() { - } + public void close() {} @Override public int read(byte[] buffer, int offset, int readLength) throws IOException { - int ret = mTsStreamer.readAt(mStartBufferedPosition + mLastReadPosition.get(), buffer, - offset, readLength); + int ret = + mTsStreamer.readAt( + mStartBufferedPosition + mLastReadPosition.get(), + buffer, + offset, + readLength); if (ret > 0) { mLastReadPosition.addAndGet(ret); } @@ -121,6 +122,7 @@ public class FileTsStreamer implements TsStreamer { /** * Creates {@link TsStreamer} for scanning & playing MPEG-2 TS file. + * * @param eventListener the listener for channel & program information */ public FileTsStreamer(EventDetector.EventListener eventListener, Context context) { @@ -216,6 +218,7 @@ public class FileTsStreamer implements TsStreamer { /** * Returns the current buffered position from the file. + * * @return the current buffered position */ public long getBufferedPosition() { @@ -224,9 +227,7 @@ public class FileTsStreamer implements TsStreamer { } } - /** - * Provides MPEG-2 transport stream from a local file. Stream can be filtered by PID. - */ + /** Provides MPEG-2 transport stream from a local file. Stream can be filtered by PID. */ public static class StreamProvider { private final String mFilepath; private final SparseBooleanArray mPids = new SparseBooleanArray(); @@ -252,36 +253,29 @@ public class FileTsStreamer implements TsStreamer { return mInputStream != null; } - /** - * Returns the file path of the MPEG-2 TS file. - */ + /** Returns the file path of the MPEG-2 TS file. */ public String getFilepath() { return mFilepath; } - /** - * Adds a pid for filtering from the MPEG-2 TS file. - */ + /** Adds a pid for filtering from the MPEG-2 TS file. */ public void addPidFilter(int pid) { mPids.put(pid, true); } - /** - * Returns whether the current pid filter is empty or not. - */ + /** Returns whether the current pid filter is empty or not. */ public boolean isFilterEmpty() { return mPids.size() == 0; } - /** - * Clears the current pid filter. - */ + /** Clears the current pid filter. */ public void clearPidFilter() { mPids.clear(); } /** * Returns whether a pid is in the pid filter or not. + * * @param pid the pid to check */ public boolean isInFilter(int pid) { @@ -347,6 +341,7 @@ public class FileTsStreamer implements TsStreamer { /** * Reads data from internal buffer. + * * @param pos the position to read from * @param buffer to read * @param offset start position of the read buffer @@ -387,7 +382,11 @@ public class FileTsStreamer implements TsStreamer { } System.arraycopy(mCircularBuffer, posInBuffer, buffer, offset, bytesToCopyInFirstPass); if (bytesToCopyInFirstPass < amount) { - System.arraycopy(mCircularBuffer, 0, buffer, offset + bytesToCopyInFirstPass, + System.arraycopy( + mCircularBuffer, + 0, + buffer, + offset + bytesToCopyInFirstPass, amount - bytesToCopyInFirstPass); } mLastReadPosition = pos + amount; @@ -416,9 +415,9 @@ public class FileTsStreamer implements TsStreamer { } /** - * A thread managing a circular buffer that holds stream data to be consumed by player. - * Keeps reading data in from a {@link StreamProvider} to hold enough amount for buffering. - * Started and stopped by {@link #startStream()} and {@link #stopStream()}, respectively. + * A thread managing a circular buffer that holds stream data to be consumed by player. Keeps + * reading data in from a {@link StreamProvider} to hold enough amount for buffering. Started + * and stopped by {@link #startStream()} and {@link #stopStream()}, respectively. */ private class StreamingThread extends Thread { @Override @@ -466,10 +465,14 @@ public class FileTsStreamer implements TsStreamer { if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) { bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer; } - System.arraycopy(dataBuffer, 0, mCircularBuffer, posInBuffer, - bytesToCopyInFirstPass); + System.arraycopy( + dataBuffer, 0, mCircularBuffer, posInBuffer, bytesToCopyInFirstPass); if (bytesToCopyInFirstPass < bytesWritten) { - System.arraycopy(dataBuffer, bytesToCopyInFirstPass, mCircularBuffer, 0, + System.arraycopy( + dataBuffer, + bytesToCopyInFirstPass, + mCircularBuffer, + 0, bytesWritten - bytesToCopyInFirstPass); } mBytesFetched += bytesWritten; diff --git a/src/com/android/tv/tuner/source/TsDataSource.java b/src/com/android/tv/tuner/source/TsDataSource.java index 2ce3e670..be902944 100644 --- a/src/com/android/tv/tuner/source/TsDataSource.java +++ b/src/com/android/tv/tuner/source/TsDataSource.java @@ -18,9 +18,7 @@ package com.android.tv.tuner.source; import com.google.android.exoplayer.upstream.DataSource; -/** - * {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}. - */ +/** {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}. */ public abstract class TsDataSource implements DataSource { /** @@ -42,9 +40,10 @@ public abstract class TsDataSource implements DataSource { } /** - * Shifts start position by the specified offset. - * Do not call this method when the class already provided MPEG-TS stream to the extractor. + * Shifts start position by the specified offset. Do not call this method when the class already + * provided MPEG-TS stream to the extractor. + * * @param offset 0 <= offset <= buffered position */ - public void shiftStartPosition(long offset) { } -} \ No newline at end of file + public void shiftStartPosition(long offset) {} +} diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java index 16be7582..fc8a8327 100644 --- a/src/com/android/tv/tuner/source/TsDataSourceManager.java +++ b/src/com/android/tv/tuner/source/TsDataSourceManager.java @@ -18,24 +18,21 @@ package com.android.tv.tuner.source; import android.content.Context; import android.support.annotation.VisibleForTesting; - import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.tvinput.EventDetector; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * Manages {@link DataSource} for playback and recording. - * The class hides handling of {@link TunerHal} and {@link TsStreamer} from other classes. - * One TsDataSourceManager should be created for per session. + * Manages {@link DataSource} for playback and recording. The class hides handling of {@link + * TunerHal} and {@link TsStreamer} from other classes. One TsDataSourceManager should be created + * for per session. */ public class TsDataSourceManager { private static final Object sLock = new Object(); - private static final Map sTsStreamers = - new ConcurrentHashMap<>(); + private static final Map sTsStreamers = new ConcurrentHashMap<>(); private static int sSequenceId; @@ -47,8 +44,9 @@ public class TsDataSourceManager { private boolean mKeepTuneStatus; /** - * Creates TsDataSourceManager to create and release {@link DataSource} which will be - * used for playing and recording. + * Creates TsDataSourceManager to create and release {@link DataSource} which will be used for + * playing and recording. + * * @param isRecording {@code true} when for recording, {@code false} otherwise * @return {@link TsDataSourceManager} */ @@ -68,13 +66,14 @@ public class TsDataSourceManager { /** * Creates or retrieves {@link TsDataSource} for playing or recording + * * @param context a {@link Context} instance * @param channel to play or record * @param eventListener for program information which will be scanned from MPEG2-TS stream * @return {@link TsDataSource} which will provide the specified channel stream */ - public TsDataSource createDataSource(Context context, TunerChannel channel, - EventDetector.EventListener eventListener) { + public TsDataSource createDataSource( + Context context, TunerChannel channel, EventDetector.EventListener eventListener) { if (channel.getType() == Channel.TYPE_FILE) { // MPEG2 TS captured stream file recording is not supported. if (mIsRecording) { @@ -88,18 +87,18 @@ public class TsDataSourceManager { } return null; } - return mTunerStreamerManager.createDataSource(context, channel, eventListener, - mId, !mIsRecording && mKeepTuneStatus); + return mTunerStreamerManager.createDataSource( + context, channel, eventListener, mId, !mIsRecording && mKeepTuneStatus); } /** * Releases the specified {@link TsDataSource} and underlying {@link TunerHal}. + * * @param source to release */ public void releaseDataSource(TsDataSource source) { if (source instanceof TunerTsStreamer.TunerDataSource) { - mTunerStreamerManager.releaseDataSource( - source, mId, !mIsRecording && mKeepTuneStatus); + mTunerStreamerManager.releaseDataSource(source, mId, !mIsRecording && mKeepTuneStatus); } else if (source instanceof FileTsStreamer.FileDataSource) { FileTsStreamer streamer = (FileTsStreamer) sTsStreamers.get(source); if (streamer != null) { @@ -109,34 +108,28 @@ public class TsDataSourceManager { } } - /** - * Indicates that the current session has pending tunes. - */ + /** Indicates that the current session has pending tunes. */ public void setHasPendingTune() { mTunerStreamerManager.setHasPendingTune(mId); } /** - * Indicates whether the underlying {@link TunerHal} should be kept or not when data source - * is being released. - * TODO: If b/30750953 is fixed, we can remove this function. + * Indicates whether the underlying {@link TunerHal} should be kept or not when data source is + * being released. TODO: If b/30750953 is fixed, we can remove this function. + * * @param keepTuneStatus underlying {@link TunerHal} will be reused when data source releasing. */ public void setKeepTuneStatus(boolean keepTuneStatus) { mKeepTuneStatus = keepTuneStatus; } - /** - * Add tuner hal into TunerTsStreamerManager for test. - */ + /** Add tuner hal into TunerTsStreamerManager for test. */ @VisibleForTesting public void addTunerHalForTest(TunerHal tunerHal) { mTunerStreamerManager.addTunerHal(tunerHal, mId); } - /** - * Releases persistent resources. - */ + /** Releases persistent resources. */ public void release() { mTunerStreamerManager.release(mId); } diff --git a/src/com/android/tv/tuner/source/TsStreamWriter.java b/src/com/android/tv/tuner/source/TsStreamWriter.java index 30650555..f90136bf 100644 --- a/src/com/android/tv/tuner/source/TsStreamWriter.java +++ b/src/com/android/tv/tuner/source/TsStreamWriter.java @@ -19,7 +19,6 @@ package com.android.tv.tuner.source; import android.content.Context; import android.util.Log; import com.android.tv.tuner.data.TunerChannel; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -27,9 +26,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Set; -/** - * Stores TS files to the disk for debugging. - */ +/** Stores TS files to the disk for debugging. */ public class TsStreamWriter { private static final String TAG = "TsStreamWriter"; private static final boolean DEBUG = false; @@ -79,16 +76,19 @@ public class TsStreamWriter { mChannel = channel; } - /** - * Opens a file to store TS data. - */ + /** Opens a file to store TS data. */ public void openFile() { if (mChannel == null || mDirectoryPath == null) { return; } mFileStartTimeMs = System.currentTimeMillis(); - mFileName = mChannel.getDisplayNumber() + SEPARATOR + mFileStartTimeMs + SEPARATOR - + mInstanceId + ".ts"; + mFileName = + mChannel.getDisplayNumber() + + SEPARATOR + + mFileStartTimeMs + + SEPARATOR + + mInstanceId + + ".ts"; String filePath = mDirectoryPath + "/" + mFileName; try { mFileOutputStream = new FileOutputStream(filePath, false); @@ -101,7 +101,7 @@ public class TsStreamWriter { * Closes the file and stops storing TS data. * * @param calledWhenStopStream {@code true} if this method is called when the stream is stopped - * {@code false} otherwise + * {@code false} otherwise */ public void closeFile(boolean calledWhenStopStream) { if (mFileOutputStream == null) { @@ -141,8 +141,8 @@ public class TsStreamWriter { /** * Deletes outdated files to save storage. * - * @param deleteAll {@code true} if all the files with the relative ID should be deleted - * {@code false} if the most recent file should not be deleted + * @param deleteAll {@code true} if all the files with the relative ID should be deleted {@code + * false} if the most recent file should not be deleted */ private void deleteOutdatedFiles(boolean deleteAll) { if (mFileName == null) { @@ -157,7 +157,8 @@ public class TsStreamWriter { return; } for (File file : mDirectory.listFiles()) { - if (file.isFile() && getFileId(file) == mInstanceId + if (file.isFile() + && getFileId(file) == mInstanceId && (deleteAll || !mFileName.equals(file.getName()))) { boolean deleted = file.delete(); if (DEBUG && !deleted) { @@ -178,11 +179,11 @@ public class TsStreamWriter { } Set idSet = getExistingIds(); if (idSet == null) { - return NO_INSTANCE_ID; + return NO_INSTANCE_ID; } for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) { // Range [1, MAX_INSTANCE_ID] - int id = (int)Math.floor(Math.random() * MAX_INSTANCE_ID) + 1; + int id = (int) Math.floor(Math.random() * MAX_INSTANCE_ID) + 1; if (!idSet.contains(id)) { return id; } @@ -203,7 +204,7 @@ public class TsStreamWriter { Set idSet = new HashSet<>(); for (File file : mDirectory.listFiles()) { int id = getFileId(file); - if(id != NO_INSTANCE_ID) { + if (id != NO_INSTANCE_ID) { idSet.add(id); } } diff --git a/src/com/android/tv/tuner/source/TsStreamer.java b/src/com/android/tv/tuner/source/TsStreamer.java index 1ac950bb..3dbba7e7 100644 --- a/src/com/android/tv/tuner/source/TsStreamer.java +++ b/src/com/android/tv/tuner/source/TsStreamer.java @@ -20,8 +20,8 @@ import com.android.tv.tuner.ChannelScanFileParser; import com.android.tv.tuner.data.TunerChannel; /** - * Interface definition for a stream generator. The interface will provide streams - * for scanning channels and/or playback. + * Interface definition for a stream generator. The interface will provide streams for scanning + * channels and/or playback. */ public interface TsStreamer { /** @@ -40,15 +40,12 @@ public interface TsStreamer { */ boolean startStream(TunerChannel channel); - /** - * Stops streaming the data. - */ + /** Stops streaming the data. */ void stopStream(); /** - * Creates {@link TsDataSource} which will provide MPEG-2 TS stream for - * {@link android.media.MediaExtractor}. The source will start from the position - * where it is created. + * Creates {@link TsDataSource} which will provide MPEG-2 TS stream for {@link + * android.media.MediaExtractor}. The source will start from the position where it is created. * * @return {@link TsDataSource} */ diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java index 843cbdb7..21b7a1f8 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamer.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamer.java @@ -19,9 +19,6 @@ package com.android.tv.tuner.source; import android.content.Context; import android.util.Log; import android.util.Pair; - -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.upstream.DataSpec; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.ChannelScanFileParser; import com.android.tv.tuner.TunerHal; @@ -29,21 +26,20 @@ import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.tvinput.EventDetector; import com.android.tv.tuner.tvinput.EventDetector.EventListener; - +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.upstream.DataSpec; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -/** - * Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device. - */ +/** Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device. */ public class TunerTsStreamer implements TsStreamer { private static final String TAG = "TunerTsStreamer"; private static final int MIN_READ_UNIT = 1500; private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB - private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB + private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB private static final int TS_PACKET_SIZE = 188; private static final int READ_TIMEOUT_MS = 5000; // 5 secs. @@ -100,20 +96,24 @@ public class TunerTsStreamer implements TsStreamer { } @Override - public void close() { - } + public void close() {} @Override public int read(byte[] buffer, int offset, int readLength) throws IOException { - int ret = mTsStreamer.readAt(mStartBufferedPosition + mLastReadPosition.get(), buffer, - offset, readLength); + int ret = + mTsStreamer.readAt( + mStartBufferedPosition + mLastReadPosition.get(), + buffer, + offset, + readLength); if (ret > 0) { mLastReadPosition.addAndGet(ret); } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) { long currentPosition = mStartBufferedPosition + mLastReadPosition.get(); long endPosition = mTsStreamer.getBufferedPosition(); - long diff = ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE) - * TS_PACKET_SIZE; + long diff = + ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE) + * TS_PACKET_SIZE; Log.w(TAG, "Demux position jump by overwritten buffer: " + diff); mStartBufferedPosition = currentPosition + diff; mLastReadPosition.set(0); @@ -124,6 +124,7 @@ public class TunerTsStreamer implements TsStreamer { } /** * Creates {@link TsStreamer} for playing or recording the specified channel. + * * @param tunerHal the HAL for tuner device * @param eventListener the listener for channel & program information */ @@ -133,8 +134,10 @@ public class TunerTsStreamer implements TsStreamer { if (eventListener != null) { mEventDetector.registerListener(eventListener); } - mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ? - new TsStreamWriter(context) : null; + mTsStreamWriter = + context != null && TunerPreferences.getStoreTsStream(context) + ? new TsStreamWriter(context) + : null; } public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener) { @@ -143,11 +146,10 @@ public class TunerTsStreamer implements TsStreamer { @Override public boolean startStream(TunerChannel channel) { - if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(), - channel.getDisplayNumber(false))) { + if (mTunerHal.tune( + channel.getFrequency(), channel.getModulation(), channel.getDisplayNumber(false))) { if (channel.hasVideo()) { - mTunerHal.addPidFilter(channel.getVideoPid(), - TunerHal.FILTER_TYPE_VIDEO); + mTunerHal.addPidFilter(channel.getVideoPid(), TunerHal.FILTER_TYPE_VIDEO); } boolean audioFilterSet = false; for (Integer audioPid : channel.getAudioPids()) { @@ -160,10 +162,11 @@ public class TunerTsStreamer implements TsStreamer { mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_OTHER); } } - mTunerHal.addPidFilter(channel.getPcrPid(), - TunerHal.FILTER_TYPE_PCR); + mTunerHal.addPidFilter(channel.getPcrPid(), TunerHal.FILTER_TYPE_PCR); if (mEventDetector != null) { - mEventDetector.startDetecting(channel.getFrequency(), channel.getModulation(), + mEventDetector.startDetecting( + channel.getFrequency(), + channel.getModulation(), channel.getProgramNumber()); } mChannel = channel; @@ -242,8 +245,9 @@ public class TunerTsStreamer implements TsStreamer { } /** - * Returns incomplete channel lists which was scanned so far. Incomplete channel means - * the channel whose channel information is not complete or is not well-formed. + * Returns incomplete channel lists which was scanned so far. Incomplete channel means the + * channel whose channel information is not complete or is not well-formed. + * * @return {@link List} of {@link TunerChannel} */ public List getMalFormedChannels() { @@ -252,6 +256,7 @@ public class TunerTsStreamer implements TsStreamer { /** * Returns the current {@link TunerHal} which provides MPEG-TS stream for TunerTsStreamer. + * * @return {@link TunerHal} */ public TunerHal getTunerHal() { @@ -260,6 +265,7 @@ public class TunerTsStreamer implements TsStreamer { /** * Returns the current tuned channel for TunerTsStreamer. + * * @return {@link TunerChannel} */ public TunerChannel getChannel() { @@ -268,6 +274,7 @@ public class TunerTsStreamer implements TsStreamer { /** * Returns the current buffered position from tuner. + * * @return the current buffered position */ public long getBufferedPosition() { @@ -348,10 +355,14 @@ public class TunerTsStreamer implements TsStreamer { if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) { bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer; } - System.arraycopy(dataBuffer, 0, mCircularBuffer, posInBuffer, - bytesToCopyInFirstPass); + System.arraycopy( + dataBuffer, 0, mCircularBuffer, posInBuffer, bytesToCopyInFirstPass); if (bytesToCopyInFirstPass < bytesWritten) { - System.arraycopy(dataBuffer, bytesToCopyInFirstPass, mCircularBuffer, 0, + System.arraycopy( + dataBuffer, + bytesToCopyInFirstPass, + mCircularBuffer, + 0, bytesWritten - bytesToCopyInFirstPass); } mBytesFetched += bytesWritten; @@ -365,6 +376,7 @@ public class TunerTsStreamer implements TsStreamer { /** * Reads data from internal buffer. + * * @param pos the position to read from * @param buffer to read * @param offset start position of the read buffer @@ -397,8 +409,8 @@ public class TunerTsStreamer implements TsStreamer { int firstLength = (startPos > endPos ? CIRCULAR_BUFFER_SIZE : endPos) - startPos; System.arraycopy(mCircularBuffer, startPos, buffer, offset, firstLength); if (firstLength < amount) { - System.arraycopy(mCircularBuffer, 0, buffer, offset + firstLength, - amount - firstLength); + System.arraycopy( + mCircularBuffer, 0, buffer, offset + firstLength, amount - firstLength); } mCircularBufferMonitor.notifyAll(); return amount; diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java index 258a4d86..e94bd56c 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java @@ -17,13 +17,11 @@ package com.android.tv.tuner.source; import android.content.Context; - import com.android.tv.common.AutoCloseableUtils; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.tvinput.EventDetector; - import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -31,9 +29,9 @@ import java.util.Map; import java.util.Set; /** - * Manages {@link TunerTsStreamer} for playback and recording. - * The class hides handling of {@link TunerHal} from other classes. - * This class is used by {@link TsDataSourceManager}. Don't use this class directly. + * Manages {@link TunerTsStreamer} for playback and recording. The class hides handling of {@link + * TunerHal} from other classes. This class is used by {@link TsDataSourceManager}. Don't use this + * class directly. */ class TunerTsStreamerManager { // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator @@ -49,6 +47,7 @@ class TunerTsStreamerManager { /** * Returns the singleton instance for the class + * * @return TunerTsStreamerManager */ static synchronized TunerTsStreamerManager getInstance() { @@ -58,16 +57,19 @@ class TunerTsStreamerManager { return sInstance; } - private TunerTsStreamerManager() { } + private TunerTsStreamerManager() {} synchronized TsDataSource createDataSource( - Context context, TunerChannel channel, EventDetector.EventListener listener, - int sessionId, boolean reuse) { + Context context, + TunerChannel channel, + EventDetector.EventListener listener, + int sessionId, + boolean reuse) { TsStreamerCreator creator; synchronized (mCancelLock) { if (mStreamerFinder.containsLocked(channel)) { mStreamerFinder.appendSessionLocked(channel, sessionId); - TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); + TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); TsDataSource source = streamer.createDataSource(); mListeners.put(sessionId, listener); streamer.registerListener(listener); @@ -99,8 +101,7 @@ class TunerTsStreamerManager { return null; } - synchronized void releaseDataSource(TsDataSource source, int sessionId, - boolean reuse) { + synchronized void releaseDataSource(TsDataSource source, int sessionId, boolean reuse) { TunerTsStreamer streamer; synchronized (mCancelLock) { streamer = mSourceToStreamerMap.get(source); @@ -125,15 +126,13 @@ class TunerTsStreamerManager { void setHasPendingTune(int sessionId) { synchronized (mCancelLock) { - if (mCreators.containsKey(sessionId)) { - mCreators.get(sessionId).cancelLocked(); - } + if (mCreators.containsKey(sessionId)) { + mCreators.get(sessionId).cancelLocked(); + } } } - /** - * Add tuner hal into TunerHalManager for test. - */ + /** Add tuner hal into TunerHalManager for test. */ void addTunerHal(TunerHal tunerHal, int sessionId) { mTunerHalManager.addTunerHal(tunerHal, sessionId); } @@ -183,8 +182,8 @@ class TunerTsStreamerManager { } /** - * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same - * session. The class supports the cancellation in creating new {@link TunerTsStreamer}. + * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same session. + * The class supports the cancellation in creating new {@link TunerTsStreamer}. */ private class TsStreamerCreator { private final Context mContext; @@ -195,8 +194,8 @@ class TunerTsStreamerManager { private boolean mCancelled; private TunerHal mTunerHal; - private TsStreamerCreator(Context context, TunerChannel channel, - EventDetector.EventListener listener) { + private TsStreamerCreator( + Context context, TunerChannel channel, EventDetector.EventListener listener) { mContext = context; mChannel = channel; mEventListener = listener; @@ -233,24 +232,24 @@ class TunerTsStreamerManager { // @GuardedBy("mCancelLock") private void cancelLocked() { - if (mCancelled) { - return; - } - mCancelled = true; - if (mTunerHal != null) { - mTunerHal.setHasPendingTune(true); - } + if (mCancelled) { + return; + } + mCancelled = true; + if (mTunerHal != null) { + mTunerHal.setHasPendingTune(true); + } } // @GuardedBy("mCancelLock") private boolean isCancelledLocked() { - return mCancelled; + return mCancelled; } } /** - * Supports sharing {@link TunerHal} among multiple sessions. - * The class also supports session affinity for {@link TunerHal} allocation. + * Supports sharing {@link TunerHal} among multiple sessions. The class also supports session + * affinity for {@link TunerHal} allocation. */ private class TunerHalManager { private final Map mTunerHals = new HashMap<>(); @@ -301,4 +300,4 @@ class TunerTsStreamerManager { mTunerHals.put(sessionId, tunerHal); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java index e1f890f3..6d0eb90f 100644 --- a/src/com/android/tv/tuner/ts/SectionParser.java +++ b/src/com/android/tv/tuner/ts/SectionParser.java @@ -23,7 +23,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; - import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor; @@ -48,23 +47,19 @@ import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.ByteArrayBuffer; - import com.android.tv.tuner.util.ConvertUtils; import com.ibm.icu.text.UnicodeDecompressor; - import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; -import java.util.Calendar; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; -/** - * Parses ATSC PSIP sections. - */ +/** Parses ATSC PSIP sections. */ public class SectionParser { private static final String TAG = "SectionParser"; private static final boolean DEBUG = false; @@ -99,7 +94,7 @@ public class SectionParser { public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55; private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; - private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff + private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff private static final byte MODE_UTF16 = (byte) 0x3f; private static final byte MODE_SCSU = (byte) 0x3e; private static final int MAX_SHORT_NAME_BYTES = 14; @@ -160,7 +155,6 @@ public class SectionParser { private static final String STRING_US_TV_Y7 = "US_TV_Y7"; private static final String STRING_US_TV_FV = "US_TV_FV"; - /* * The following CRC table is from the code generated by the following command. * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c @@ -236,126 +230,365 @@ public class SectionParser { // A table which maps ATSC genres to TIF genres. // See ATSC/65 Table 6.20. private static final String[] CANONICAL_GENRES_TABLE = { - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - Genres.EDUCATION, Genres.ENTERTAINMENT, Genres.MOVIES, Genres.NEWS, - Genres.LIFE_STYLE, Genres.SPORTS, null, Genres.MOVIES, - null, - Genres.FAMILY_KIDS, Genres.DRAMA, null, Genres.ENTERTAINMENT, Genres.SPORTS, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + Genres.EDUCATION, + Genres.ENTERTAINMENT, + Genres.MOVIES, + Genres.NEWS, + Genres.LIFE_STYLE, + Genres.SPORTS, + null, + Genres.MOVIES, + null, + Genres.FAMILY_KIDS, + Genres.DRAMA, + null, + Genres.ENTERTAINMENT, Genres.SPORTS, - null, null, - Genres.MUSIC, Genres.EDUCATION, + Genres.SPORTS, + null, + null, + Genres.MUSIC, + Genres.EDUCATION, null, Genres.COMEDY, null, Genres.MUSIC, - null, null, - Genres.MOVIES, Genres.ENTERTAINMENT, Genres.NEWS, Genres.DRAMA, - Genres.EDUCATION, Genres.MOVIES, Genres.SPORTS, Genres.MOVIES, null, - Genres.LIFE_STYLE, Genres.ARTS, Genres.LIFE_STYLE, Genres.SPORTS, - null, null, - Genres.GAMING, Genres.LIFE_STYLE, Genres.SPORTS, null, - Genres.LIFE_STYLE, Genres.EDUCATION, Genres.EDUCATION, Genres.LIFE_STYLE, - Genres.SPORTS, Genres.LIFE_STYLE, Genres.MOVIES, Genres.NEWS, - null, null, null, + Genres.MOVIES, + Genres.ENTERTAINMENT, + Genres.NEWS, + Genres.DRAMA, + Genres.EDUCATION, + Genres.MOVIES, + Genres.SPORTS, + Genres.MOVIES, + null, + Genres.LIFE_STYLE, + Genres.ARTS, + Genres.LIFE_STYLE, + Genres.SPORTS, + null, + null, + Genres.GAMING, + Genres.LIFE_STYLE, + Genres.SPORTS, + null, + Genres.LIFE_STYLE, + Genres.EDUCATION, + Genres.EDUCATION, + Genres.LIFE_STYLE, + Genres.SPORTS, + Genres.LIFE_STYLE, + Genres.MOVIES, + Genres.NEWS, + null, + null, + null, Genres.EDUCATION, - null, null, null, + null, + null, + null, Genres.EDUCATION, - null, null, null, - Genres.DRAMA, Genres.MUSIC, Genres.MOVIES, + null, + null, + null, + Genres.DRAMA, + Genres.MUSIC, + Genres.MOVIES, null, Genres.ANIMAL_WILDLIFE, - null, null, + null, + null, Genres.PREMIER, - null, null, null, null, - Genres.SPORTS, Genres.ARTS, - null, null, null, - Genres.MOVIES, Genres.TECH_SCIENCE, Genres.DRAMA, null, - Genres.SHOPPING, Genres.DRAMA, null, - Genres.MOVIES, Genres.ENTERTAINMENT, Genres.TECH_SCIENCE, Genres.SPORTS, - Genres.TRAVEL, Genres.ENTERTAINMENT, Genres.ARTS, Genres.NEWS, null, - Genres.ARTS, Genres.SPORTS, Genres.SPORTS, Genres.NEWS, - Genres.SPORTS, Genres.SPORTS, Genres.SPORTS, Genres.FAMILY_KIDS, - Genres.FAMILY_KIDS, Genres.MOVIES, null, - Genres.TECH_SCIENCE, Genres.MUSIC, + Genres.SPORTS, + Genres.ARTS, + null, + null, + null, + Genres.MOVIES, + Genres.TECH_SCIENCE, + Genres.DRAMA, + null, + Genres.SHOPPING, + Genres.DRAMA, + null, + Genres.MOVIES, + Genres.ENTERTAINMENT, + Genres.TECH_SCIENCE, + Genres.SPORTS, + Genres.TRAVEL, + Genres.ENTERTAINMENT, + Genres.ARTS, + Genres.NEWS, + null, + Genres.ARTS, + Genres.SPORTS, + Genres.SPORTS, + Genres.NEWS, + Genres.SPORTS, + Genres.SPORTS, + Genres.SPORTS, + Genres.FAMILY_KIDS, + Genres.FAMILY_KIDS, + Genres.MOVIES, + null, + Genres.TECH_SCIENCE, + Genres.MUSIC, null, - Genres.SPORTS, Genres.FAMILY_KIDS, Genres.NEWS, Genres.SPORTS, - Genres.NEWS, Genres.SPORTS, Genres.ANIMAL_WILDLIFE, + Genres.SPORTS, + Genres.FAMILY_KIDS, + Genres.NEWS, + Genres.SPORTS, + Genres.NEWS, + Genres.SPORTS, + Genres.ANIMAL_WILDLIFE, null, - Genres.MUSIC, Genres.NEWS, Genres.SPORTS, + Genres.MUSIC, + Genres.NEWS, + Genres.SPORTS, null, - Genres.NEWS, Genres.NEWS, Genres.NEWS, Genres.NEWS, - Genres.SPORTS, Genres.MOVIES, Genres.ARTS, Genres.ANIMAL_WILDLIFE, - Genres.MUSIC, Genres.MUSIC, Genres.MOVIES, Genres.EDUCATION, - Genres.DRAMA, Genres.SPORTS, Genres.SPORTS, Genres.SPORTS, + Genres.NEWS, + Genres.NEWS, + Genres.NEWS, + Genres.NEWS, + Genres.SPORTS, + Genres.MOVIES, + Genres.ARTS, + Genres.ANIMAL_WILDLIFE, + Genres.MUSIC, + Genres.MUSIC, + Genres.MOVIES, + Genres.EDUCATION, + Genres.DRAMA, + Genres.SPORTS, + Genres.SPORTS, + Genres.SPORTS, Genres.SPORTS, null, - Genres.SPORTS, Genres.SPORTS, + Genres.SPORTS, + Genres.SPORTS, }; // A table which contains ATSC categorical genre code assignments. // See ATSC/65 Table 6.20. - private static final String[] BROADCAST_GENRES_TABLE = new String[] { - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null, null, - "Education", "Entertainment", "Movie", "News", - "Religious", "Sports", "Other", "Action", - "Advertisement", "Animated", "Anthology", "Automobile", - "Awards", "Baseball", "Basketball", "Bulletin", - "Business", "Classical", "College", "Combat", - "Comedy", "Commentary", "Concert", "Consumer", - "Contemporary", "Crime", "Dance", "Documentary", - "Drama", "Elementary", "Erotica", "Exercise", - "Fantasy", "Farm", "Fashion", "Fiction", - "Food", "Football", "Foreign", "Fund Raiser", - "Game/Quiz", "Garden", "Golf", "Government", - "Health", "High School", "History", "Hobby", - "Hockey", "Home", "Horror", "Information", - "Instruction", "International", "Interview", "Language", - "Legal", "Live", "Local", "Math", - "Medical", "Meeting", "Military", "Miniseries", - "Music", "Mystery", "National", "Nature", - "Police", "Politics", "Premier", "Prerecorded", - "Product", "Professional", "Public", "Racing", - "Reading", "Repair", "Repeat", "Review", - "Romance", "Science", "Series", "Service", - "Shopping", "Soap Opera", "Special", "Suspense", - "Talk", "Technical", "Tennis", "Travel", - "Variety", "Video", "Weather", "Western", - "Art", "Auto Racing", "Aviation", "Biography", - "Boating", "Bowling", "Boxing", "Cartoon", - "Children", "Classic Film", "Community", "Computers", - "Country Music", "Court", "Extreme Sports", "Family", - "Financial", "Gymnastics", "Headlines", "Horse Racing", - "Hunting/Fishing/Outdoors", "Independent", "Jazz", "Magazine", - "Motorcycle Racing", "Music/Film/Books", "News-International", "News-Local", - "News-National", "News-Regional", "Olympics", "Original", - "Performing Arts", "Pets/Animals", "Pop", "Rock & Roll", - "Sci-Fi", "Self Improvement", "Sitcom", "Skating", - "Skiing", "Soccer", "Track/Field", "True", - "Volleyball", "Wrestling", - }; + private static final String[] BROADCAST_GENRES_TABLE = + new String[] { + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "Education", + "Entertainment", + "Movie", + "News", + "Religious", + "Sports", + "Other", + "Action", + "Advertisement", + "Animated", + "Anthology", + "Automobile", + "Awards", + "Baseball", + "Basketball", + "Bulletin", + "Business", + "Classical", + "College", + "Combat", + "Comedy", + "Commentary", + "Concert", + "Consumer", + "Contemporary", + "Crime", + "Dance", + "Documentary", + "Drama", + "Elementary", + "Erotica", + "Exercise", + "Fantasy", + "Farm", + "Fashion", + "Fiction", + "Food", + "Football", + "Foreign", + "Fund Raiser", + "Game/Quiz", + "Garden", + "Golf", + "Government", + "Health", + "High School", + "History", + "Hobby", + "Hockey", + "Home", + "Horror", + "Information", + "Instruction", + "International", + "Interview", + "Language", + "Legal", + "Live", + "Local", + "Math", + "Medical", + "Meeting", + "Military", + "Miniseries", + "Music", + "Mystery", + "National", + "Nature", + "Police", + "Politics", + "Premier", + "Prerecorded", + "Product", + "Professional", + "Public", + "Racing", + "Reading", + "Repair", + "Repeat", + "Review", + "Romance", + "Science", + "Series", + "Service", + "Shopping", + "Soap Opera", + "Special", + "Suspense", + "Talk", + "Technical", + "Tennis", + "Travel", + "Variety", + "Video", + "Weather", + "Western", + "Art", + "Auto Racing", + "Aviation", + "Biography", + "Boating", + "Bowling", + "Boxing", + "Cartoon", + "Children", + "Classic Film", + "Community", + "Computers", + "Country Music", + "Court", + "Extreme Sports", + "Family", + "Financial", + "Gymnastics", + "Headlines", + "Horse Racing", + "Hunting/Fishing/Outdoors", + "Independent", + "Jazz", + "Magazine", + "Motorcycle Racing", + "Music/Film/Books", + "News-International", + "News-Local", + "News-National", + "News-Regional", + "Olympics", + "Original", + "Performing Arts", + "Pets/Animals", + "Pop", + "Rock & Roll", + "Sci-Fi", + "Self Improvement", + "Sitcom", + "Skating", + "Skiing", + "Soccer", + "Track/Field", + "True", + "Volleyball", + "Wrestling", + }; // Audio language code map from ISO 639-2/B to 639-2/T, in order to show correct audio language. private static final HashMap ISO_LANGUAGE_CODE_MAP; + static { ISO_LANGUAGE_CODE_MAP = new HashMap<>(); ISO_LANGUAGE_CODE_MAP.put("alb", "sqi"); @@ -387,11 +620,17 @@ public class SectionParser { public interface OutputListener { void onPatParsed(List items); + void onPmtParsed(int programNumber, List items); + void onMgtParsed(List items); + void onVctParsed(List items, int sectionNumber, int lastSectionNumber); + void onEitParsed(int sourceId, List items); + void onEttParsed(int sourceId, List descriptions); + void onSdtParsed(List items); } @@ -532,7 +771,7 @@ public class SectionParser { Log.d(TAG, "PMT descriptors size: " + descriptors.size()); } List results = new ArrayList<>(); - for (; pos < data.length - 4;) { + for (; pos < data.length - 4; ) { if (pos < 0) { Log.e(TAG, "Broken PMT."); return false; @@ -607,8 +846,12 @@ public class SectionParser { if (sectionNumber > lastSectionNumber) { // According to section 6.3.1 of the spec ATSC A/65, // last section number is the largest section number. - Log.w(TAG, "Invalid VCT. Section Number " + sectionNumber + " > Last Section Number " - + lastSectionNumber); + Log.w( + TAG, + "Invalid VCT. Section Number " + + sectionNumber + + " > Last Section Number " + + lastSectionNumber); return false; } int pos = 10; @@ -621,8 +864,8 @@ public class SectionParser { String shortName = ""; int shortNameSize = getShortNameSize(data, pos); try { - shortName = new String( - Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16"); + shortName = + new String(Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16"); } catch (UnsupportedEncodingException e) { Log.e(TAG, "Broken VCT.", e); return false; @@ -652,8 +895,8 @@ public class SectionParser { Log.e(TAG, "Broken VCT."); return false; } - List descriptors = parseDescriptors( - data, descriptorsPos, descriptorsPos + descriptorsLength); + List descriptors = + parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); String longName = null; for (TsDescriptor descriptor : descriptors) { if (descriptor instanceof ExtendedChannelNameDescriptor) { @@ -664,18 +907,39 @@ public class SectionParser { } } if (DEBUG) { - Log.d(TAG, String.format( - "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d " - + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d", - shortName, longName, serviceType, channelTsid, programNumber, majorNumber, - minorNumber, accessControlled, hidden, descriptors.size())); - } - if (!accessControlled && !hidden && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO || - serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION || - serviceType == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) { + Log.d( + TAG, + String.format( + "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d " + + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d", + shortName, + longName, + serviceType, + channelTsid, + programNumber, + majorNumber, + minorNumber, + accessControlled, + hidden, + descriptors.size())); + } + if (!accessControlled + && !hidden + && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO + || serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION + || serviceType + == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) { // Hide hidden, encrypted, or unsupported ATSC service type channels - results.add(new VctItem(shortName, longName, serviceType, channelTsid, - programNumber, majorNumber, minorNumber, sourceId)); + results.add( + new VctItem( + shortName, + longName, + serviceType, + channelTsid, + programNumber, + majorNumber, + minorNumber, + sourceId)); } } // Skip the remaining descriptor part which we don't use. @@ -710,10 +974,15 @@ public class SectionParser { return false; } int eventId = ((data[pos] & 0x3f) << 8) + (data[pos + 1] & 0xff); - long startTime = ((data[pos + 2] & (long) 0xff) << 24) | ((data[pos + 3] & 0xff) << 16) - | ((data[pos + 4] & 0xff) << 8) | (data[pos + 5] & 0xff); - int lengthInSecond = ((data[pos + 6] & 0x0f) << 16) - | ((data[pos + 7] & 0xff) << 8) | (data[pos + 8] & 0xff); + long startTime = + ((data[pos + 2] & (long) 0xff) << 24) + | ((data[pos + 3] & 0xff) << 16) + | ((data[pos + 4] & 0xff) << 8) + | (data[pos + 5] & 0xff); + int lengthInSecond = + ((data[pos + 6] & 0x0f) << 16) + | ((data[pos + 7] & 0xff) << 8) + | (data[pos + 8] & 0xff); int titleLength = (data[pos + 9] & 0xff); if (data.length <= pos + 10 + titleLength + 1) { Log.e(TAG, "Broken EIT."); @@ -727,15 +996,16 @@ public class SectionParser { Log.e(TAG, "Broken EIT."); return false; } - int descriptorsLength = ((data[pos + 10 + titleLength] & 0x0f) << 8) - | (data[pos + 10 + titleLength + 1] & 0xff); + int descriptorsLength = + ((data[pos + 10 + titleLength] & 0x0f) << 8) + | (data[pos + 10 + titleLength + 1] & 0xff); int descriptorsPos = pos + 10 + titleLength + 2; if (data.length < descriptorsPos + descriptorsLength) { Log.e(TAG, "Broken EIT."); return false; } - List descriptors = parseDescriptors( - data, descriptorsPos, descriptorsPos + descriptorsLength); + List descriptors = + parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); if (DEBUG) { Log.d(TAG, String.format("EIT descriptors size: %d", descriptors.size())); } @@ -745,9 +1015,19 @@ public class SectionParser { List audioTracks = generateAudioTracks(descriptors); List captionTracks = generateCaptionTracks(descriptors); pos += 10 + titleLength + 2 + descriptorsLength; - results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText, - startTime, lengthInSecond, contentRating, audioTracks, captionTracks, - broadcastGenre, canonicalGenre, null)); + results.add( + new EitItem( + EitItem.INVALID_PROGRAM_ID, + eventId, + titleText, + startTime, + lengthInSecond, + contentRating, + audioTracks, + captionTracks, + broadcastGenre, + canonicalGenre, + null)); } if (mListener != null) { mListener.onEitParsed(sourceId, results); @@ -812,8 +1092,13 @@ public class SectionParser { serviceType = serviceDescriptor.getServiceType(); } if (serviceDescriptors.size() > 0) { - sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId, - originalNetworkId)); + sdtItems.add( + new SdtItem( + serviceName, + serviceProviderName, + serviceType, + serviceId, + originalNetworkId)); } pos += descriptorsLength; } @@ -841,12 +1126,17 @@ public class SectionParser { List results = new ArrayList<>(); while (pos + 12 < data.length) { int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff); - float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff); + float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff); int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f); - int mjdMonth = (int) ((modifiedJulianDate - 14956.1f - - (int) (startYear * 365.25f)) / 30.6001f); - int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f) - - (int) (mjdMonth * 30.6001f); + int mjdMonth = + (int) + ((modifiedJulianDate - 14956.1f - (int) (startYear * 365.25f)) + / 30.6001f); + int startDay = + (int) modifiedJulianDate + - 14956 + - (int) (startYear * 365.25f) + - (int) (mjdMonth * 30.6001f); int startMonth = mjdMonth - 1; if (mjdMonth == 14 || mjdMonth == 15) { startYear += 1; @@ -858,21 +1148,20 @@ public class SectionParser { Calendar calendar = Calendar.getInstance(); startYear += 1900; calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond); - long startTime = ConvertUtils.convertUnixEpochToGPSTime( - calendar.getTimeInMillis() / 1000); - int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10 - + (data[pos + 7] & 0x0f)) * 3600 - + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60 - + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f)); - int descriptorsLength = ((data[pos + 10] & 0x0f) << 8) - | (data[pos + 10 + 1] & 0xff); + long startTime = + ConvertUtils.convertUnixEpochToGPSTime(calendar.getTimeInMillis() / 1000); + int durationInSecond = + (((data[pos + 7] & 0xf0) >> 4) * 10 + (data[pos + 7] & 0x0f)) * 3600 + + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60 + + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f)); + int descriptorsLength = ((data[pos + 10] & 0x0f) << 8) | (data[pos + 10 + 1] & 0xff); int descriptorsPos = pos + 10 + 2; if (data.length < descriptorsPos + descriptorsLength) { Log.e(TAG, "Broken EIT."); return false; } - List descriptors = parseDescriptors( - data, descriptorsPos, descriptorsPos + descriptorsLength); + List descriptors = + parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); if (DEBUG) { Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size())); } @@ -887,9 +1176,19 @@ public class SectionParser { List audioTracks = generateAudioTracks(descriptors); List captionTracks = generateCaptionTracks(descriptors); pos += 12 + descriptorsLength; - results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText, - startTime, durationInSecond, contentRating, audioTracks, captionTracks, - broadcastGenre, canonicalGenre, null)); + results.add( + new EitItem( + EitItem.INVALID_PROGRAM_ID, + eventId, + titleText, + startTime, + durationInSecond, + contentRating, + audioTracks, + captionTracks, + broadcastGenre, + canonicalGenre, + null)); } if (mListener != null) { mListener.onEitParsed(sourceId, results); @@ -904,8 +1203,7 @@ public class SectionParser { List iso639LanguageTracks = new ArrayList<>(); for (TsDescriptor descriptor : descriptors) { if (descriptor instanceof Ac3AudioDescriptor) { - Ac3AudioDescriptor audioDescriptor = - (Ac3AudioDescriptor) descriptor; + Ac3AudioDescriptor audioDescriptor = (Ac3AudioDescriptor) descriptor; AtscAudioTrack audioTrack = new AtscAudioTrack(); if (audioDescriptor.getLanguage() != null) { audioTrack.language = audioDescriptor.getLanguage(); @@ -937,7 +1235,8 @@ public class SectionParser { // Combines two descriptors into one in order to gather more audio track specific // information as much as possible. List tracks = new ArrayList<>(); - if (!ac3Tracks.isEmpty() && !iso639LanguageTracks.isEmpty() + if (!ac3Tracks.isEmpty() + && !iso639LanguageTracks.isEmpty() && ac3Tracks.size() != iso639LanguageTracks.size()) { // This shouldn't be happen. In here, it handles two cases. The first case is that the // only one type of descriptors arrives. The second case is that the two types of @@ -1161,8 +1460,7 @@ public class SectionParser { private static String generateBroadcastGenre(List descriptors) { for (TsDescriptor descriptor : descriptors) { if (descriptor instanceof GenreDescriptor) { - GenreDescriptor genreDescriptor = - (GenreDescriptor) descriptor; + GenreDescriptor genreDescriptor = (GenreDescriptor) descriptor; return TextUtils.join(",", genreDescriptor.getBroadcastGenres()); } } @@ -1172,8 +1470,7 @@ public class SectionParser { private static String generateCanonicalGenre(List descriptors) { for (TsDescriptor descriptor : descriptors) { if (descriptor instanceof GenreDescriptor) { - GenreDescriptor genreDescriptor = - (GenreDescriptor) descriptor; + GenreDescriptor genreDescriptor = (GenreDescriptor) descriptor; return Genres.encode(genreDescriptor.getCanonicalGenres()); } } @@ -1317,7 +1614,7 @@ public class SectionParser { pos += 3; boolean ccType = (data[pos] & 0x80) != 0; if (!ccType) { - pos +=3; + pos += 3; continue; } int captionServiceNumber = data[pos] & 0x3f; @@ -1392,8 +1689,8 @@ public class SectionParser { return new ContentAdvisoryDescriptor(ratingRegions); } - private static ExtendedChannelNameDescriptor parseLongChannelName(byte[] data, int pos, - int limit) { + private static ExtendedChannelNameDescriptor parseLongChannelName( + byte[] data, int pos, int limit) { if (limit <= pos + 2) { Log.e(TAG, "Broken ExtendedChannelName."); return null; @@ -1432,7 +1729,8 @@ public class SectionParser { } } } - return new GenreDescriptor(broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]), + return new GenreDescriptor( + broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]), canonicalGenreSet.toArray(new String[canonicalGenreSet.size()])); } @@ -1517,9 +1815,22 @@ public class SectionParser { if (limit <= pos + 1) { Log.w(TAG, "Missing text and language fields on AC3 audio stream descriptor."); - return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod, - numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags, - null, null, null); + return new Ac3AudioDescriptor( + sampleRateCode, + bsid, + bitRateCode, + surroundMode, + bsmod, + numEncodedChannels, + fullSvc, + langCod, + langCod2, + mainId, + priority, + asvcflags, + null, + null, + null); } ++pos; int textLen = (data[pos] & 0xfe) >> 1; @@ -1562,9 +1873,22 @@ public class SectionParser { } } - return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod, - numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags, text, - language, language2); + return new Ac3AudioDescriptor( + sampleRateCode, + bsid, + bitRateCode, + surroundMode, + bsmod, + numEncodedChannels, + fullSvc, + langCod, + langCod2, + mainId, + priority, + asvcflags, + text, + language, + language2); } private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) { @@ -1645,7 +1969,7 @@ public class SectionParser { } private static String extractText(byte[] data, int pos) { - if (data.length < pos) { + if (data.length < pos) { return null; } int numStrings = data[pos] & 0xff; @@ -1746,11 +2070,11 @@ public class SectionParser { boolean hasCRC = (data[1] & 0x80) != 0; // section_syntax_indicator if (hasCRC) { int crc = 0xffffffff; - for(byte b : data) { + for (byte b : data) { int index = ((crc >> 24) ^ (b & 0xff)) & 0xff; crc = CRC_TABLE[index] ^ (crc << 8); } - if(crc != 0){ + if (crc != 0) { return false; } } diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java index 7cdb534e..fbedc2c3 100644 --- a/src/com/android/tv/tuner/ts/TsParser.java +++ b/src/com/android/tv/tuner/ts/TsParser.java @@ -19,7 +19,6 @@ package com.android.tv.tuner.ts; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; - import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.EitItem; @@ -30,7 +29,6 @@ import com.android.tv.tuner.data.PsipData.VctItem; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.ts.SectionParser.OutputListener; import com.android.tv.tuner.util.ByteArrayBuffer; - import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -38,9 +36,7 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; -/** - * Parses MPEG-2 TS packets. - */ +/** Parses MPEG-2 TS packets. */ public class TsParser { private static final String TAG = "TsParser"; private static final boolean DEBUG = false; @@ -84,11 +80,17 @@ public class TsParser { public interface TsOutputListener { void onPatDetected(List items); + void onEitPidDetected(int pid); + void onVctItemParsed(VctItem channel, List pmtItems); + void onEitItemParsed(VctItem channel, List items); + void onEttPidDetected(int pid); + void onAllVctItemsParsed(); + void onSdtItemParsed(SdtItem channel, List pmtItems); } @@ -108,6 +110,7 @@ public class TsParser { } protected abstract void handleData(byte[] data, boolean startIndicator); + protected abstract void resetDataVersions(); } @@ -150,169 +153,187 @@ public class TsParser { mSectionParser.resetVersionNumbers(); } - private final OutputListener mSectionListener = new OutputListener() { - @Override - public void onPatParsed(List items) { - for (PatItem i : items) { - startListening(i.getPmtPid()); - } - if (mListener != null) { - mListener.onPatDetected(items); - } - } - - @Override - public void onPmtParsed(int programNumber, List items) { - mProgramNumberToPMTMap.put(programNumber, items); - if (DEBUG) { - Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is " - + mProgramNumberHandledStatus.get(programNumber, false)); - } - int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber); - if (statusIndex < 0) { - mProgramNumberHandledStatus.put(programNumber, false); - } - if (!mProgramNumberHandledStatus.get(programNumber)) { - VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber); - if (vctItem != null) { - // When PMT is parsed later than VCT. - mProgramNumberHandledStatus.put(programNumber, true); - handleVctItem(vctItem, items); - mHandledVctItemCount++; - if (mHandledVctItemCount >= mVctItemCount - && mVctSectionParsedCount >= mVctSectionParsed.length - && mListener != null) { - mListener.onAllVctItemsParsed(); + private final OutputListener mSectionListener = + new OutputListener() { + @Override + public void onPatParsed(List items) { + for (PatItem i : items) { + startListening(i.getPmtPid()); + } + if (mListener != null) { + mListener.onPatDetected(items); } } - SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber); - if (sdtItem != null) { - // When PMT is parsed later than SDT. - mProgramNumberHandledStatus.put(programNumber, true); - handleSdtItem(sdtItem, items); - } - } - } - @Override - public void onMgtParsed(List items) { - for (MgtItem i : items) { - if (mStreamMap.get(i.getTableTypePid()) != null) { - continue; - } - if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START - && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) { - startListening(i.getTableTypePid()); - mEITPids.add(i.getTableTypePid()); - if (mListener != null) { - mListener.onEitPidDetected(i.getTableTypePid()); + @Override + public void onPmtParsed(int programNumber, List items) { + mProgramNumberToPMTMap.put(programNumber, items); + if (DEBUG) { + Log.d( + TAG, + "onPMTParsed, programNo " + + programNumber + + " handledStatus is " + + mProgramNumberHandledStatus.get( + programNumber, false)); } - } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT || - (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START - && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) { - startListening(i.getTableTypePid()); - mETTPids.add(i.getTableTypePid()); - if (mListener != null) { - mListener.onEttPidDetected(i.getTableTypePid()); + int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber); + if (statusIndex < 0) { + mProgramNumberHandledStatus.put(programNumber, false); + } + if (!mProgramNumberHandledStatus.get(programNumber)) { + VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber); + if (vctItem != null) { + // When PMT is parsed later than VCT. + mProgramNumberHandledStatus.put(programNumber, true); + handleVctItem(vctItem, items); + mHandledVctItemCount++; + if (mHandledVctItemCount >= mVctItemCount + && mVctSectionParsedCount >= mVctSectionParsed.length + && mListener != null) { + mListener.onAllVctItemsParsed(); + } + } + SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber); + if (sdtItem != null) { + // When PMT is parsed later than SDT. + mProgramNumberHandledStatus.put(programNumber, true); + handleSdtItem(sdtItem, items); + } } } - } - } - @Override - public void onVctParsed(List items, int sectionNumber, int lastSectionNumber) { - if (mVctSectionParsed == null) { - mVctSectionParsed = new boolean[lastSectionNumber + 1]; - } else if (mVctSectionParsed[sectionNumber]) { - // The current section was handled before. - if (DEBUG) { - Log.d(TAG, "Duplicate VCT section found."); - } - return; - } - mVctSectionParsed[sectionNumber] = true; - mVctSectionParsedCount++; - mVctItemCount += items.size(); - for (VctItem i : items) { - if (DEBUG) Log.d(TAG, "onVCTParsed " + i); - if (i.getSourceId() != 0) { - mSourceIdToVctItemMap.put(i.getSourceId(), i); - i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId())); + @Override + public void onMgtParsed(List items) { + for (MgtItem i : items) { + if (mStreamMap.get(i.getTableTypePid()) != null) { + continue; + } + if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START + && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) { + startListening(i.getTableTypePid()); + mEITPids.add(i.getTableTypePid()); + if (mListener != null) { + mListener.onEitPidDetected(i.getTableTypePid()); + } + } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT + || (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START + && i.getTableType() + <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) { + startListening(i.getTableTypePid()); + mETTPids.add(i.getTableTypePid()); + if (mListener != null) { + mListener.onEttPidDetected(i.getTableTypePid()); + } + } + } } - int programNumber = i.getProgramNumber(); - mProgramNumberToVctItemMap.put(programNumber, i); - List pmtList = mProgramNumberToPMTMap.get(programNumber); - if (pmtList != null) { - mProgramNumberHandledStatus.put(programNumber, true); - handleVctItem(i, pmtList); - mHandledVctItemCount++; - if (mHandledVctItemCount >= mVctItemCount - && mVctSectionParsedCount >= mVctSectionParsed.length - && mListener != null) { - mListener.onAllVctItemsParsed(); + + @Override + public void onVctParsed( + List items, int sectionNumber, int lastSectionNumber) { + if (mVctSectionParsed == null) { + mVctSectionParsed = new boolean[lastSectionNumber + 1]; + } else if (mVctSectionParsed[sectionNumber]) { + // The current section was handled before. + if (DEBUG) { + Log.d(TAG, "Duplicate VCT section found."); + } + return; + } + mVctSectionParsed[sectionNumber] = true; + mVctSectionParsedCount++; + mVctItemCount += items.size(); + for (VctItem i : items) { + if (DEBUG) Log.d(TAG, "onVCTParsed " + i); + if (i.getSourceId() != 0) { + mSourceIdToVctItemMap.put(i.getSourceId(), i); + i.setDescription( + mSourceIdToVctItemDescriptionMap.get(i.getSourceId())); + } + int programNumber = i.getProgramNumber(); + mProgramNumberToVctItemMap.put(programNumber, i); + List pmtList = mProgramNumberToPMTMap.get(programNumber); + if (pmtList != null) { + mProgramNumberHandledStatus.put(programNumber, true); + handleVctItem(i, pmtList); + mHandledVctItemCount++; + if (mHandledVctItemCount >= mVctItemCount + && mVctSectionParsedCount >= mVctSectionParsed.length + && mListener != null) { + mListener.onAllVctItemsParsed(); + } + } else { + mProgramNumberHandledStatus.put(programNumber, false); + Log.i( + TAG, + "onVCTParsed, but PMT for programNo " + + programNumber + + " is not found yet."); + } } - } else { - mProgramNumberHandledStatus.put(programNumber, false); - Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber - + " is not found yet."); } - } - } - @Override - public void onEitParsed(int sourceId, List items) { - if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId); - EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); - mEitMap.put(entry, items); - handleEvents(sourceId); - } + @Override + public void onEitParsed(int sourceId, List items) { + if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId); + EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); + mEitMap.put(entry, items); + handleEvents(sourceId); + } - @Override - public void onEttParsed(int sourceId, List descriptions) { - if (DEBUG) { - Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d", - sourceId, descriptions.size())); - } - for (EttItem item : descriptions) { - if (item.eventId == 0) { - // Channel description - mSourceIdToVctItemDescriptionMap.put(sourceId, item.text); - VctItem vctItem = mSourceIdToVctItemMap.get(sourceId); - if (vctItem != null) { - vctItem.setDescription(item.text); - List pmtItems = - mProgramNumberToPMTMap.get(vctItem.getProgramNumber()); - if (pmtItems != null) { - handleVctItem(vctItem, pmtItems); + @Override + public void onEttParsed(int sourceId, List descriptions) { + if (DEBUG) { + Log.d( + TAG, + String.format( + "onETTParsed sourceId: %d, descriptions.size(): %d", + sourceId, descriptions.size())); + } + for (EttItem item : descriptions) { + if (item.eventId == 0) { + // Channel description + mSourceIdToVctItemDescriptionMap.put(sourceId, item.text); + VctItem vctItem = mSourceIdToVctItemMap.get(sourceId); + if (vctItem != null) { + vctItem.setDescription(item.text); + List pmtItems = + mProgramNumberToPMTMap.get(vctItem.getProgramNumber()); + if (pmtItems != null) { + handleVctItem(vctItem, pmtItems); + } + } } } - } - } - // Event Information description - EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); - mETTMap.put(entry, descriptions); - handleEvents(sourceId); - } + // Event Information description + EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); + mETTMap.put(entry, descriptions); + handleEvents(sourceId); + } - @Override - public void onSdtParsed(List sdtItems) { - for (SdtItem sdtItem : sdtItems) { - if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem); - int programNumber = sdtItem.getServiceId(); - mProgramNumberToSdtItemMap.put(programNumber, sdtItem); - List pmtList = mProgramNumberToPMTMap.get(programNumber); - if (pmtList != null) { - mProgramNumberHandledStatus.put(programNumber, true); - handleSdtItem(sdtItem, pmtList); - } else { - mProgramNumberHandledStatus.put(programNumber, false); - Log.i(TAG, "onSdtParsed, but PMT for programNo " + programNumber - + " is not found yet."); + @Override + public void onSdtParsed(List sdtItems) { + for (SdtItem sdtItem : sdtItems) { + if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem); + int programNumber = sdtItem.getServiceId(); + mProgramNumberToSdtItemMap.put(programNumber, sdtItem); + List pmtList = mProgramNumberToPMTMap.get(programNumber); + if (pmtList != null) { + mProgramNumberHandledStatus.put(programNumber, true); + handleSdtItem(sdtItem, pmtList); + } else { + mProgramNumberHandledStatus.put(programNumber, false); + Log.i( + TAG, + "onSdtParsed, but PMT for programNo " + + programNumber + + " is not found yet."); + } + } } - } - } - }; + }; } private static class EventSourceEntry { @@ -470,13 +491,16 @@ public class TsParser { if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet."); return false; } - stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE), - continuityCounter, payloadStartIndicator); + stream.feedData( + Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE), + continuityCounter, + payloadStartIndicator); return true; } /** * Feeds MPEG-2 TS data to parse. + * * @param tsData buffer for ATSC TS stream * @param pos the offset where buffer starts * @param length The length of available data @@ -489,6 +513,7 @@ public class TsParser { /** * Retrieves the channel information regardless of being well-formed. + * * @return {@link List} of {@link TunerChannel} */ public List getMalFormedChannels() { @@ -506,9 +531,7 @@ public class TsParser { return incompleteChannels; } - /** - * Reset the versions so that data with old version number can be handled. - */ + /** Reset the versions so that data with old version number can be handled. */ public void resetDataVersions() { for (int eitPid : mEITPids) { Stream stream = mStreamMap.get(eitPid); diff --git a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java index d2b4998a..49fc0ca1 100644 --- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java +++ b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java @@ -33,13 +33,11 @@ import android.os.RemoteException; import android.support.annotation.Nullable; import android.text.format.DateUtils; import android.util.Log; - import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.util.ConvertUtils; import com.android.tv.util.PermissionUtils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -53,26 +51,28 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -/** - * Manages the channel info and EPG data through {@link TvInputManager}. - */ +/** Manages the channel info and EPG data through {@link TvInputManager}. */ public class ChannelDataManager implements Handler.Callback { private static final String TAG = "ChannelDataManager"; - private static final String[] ALL_PROGRAMS_SELECTION_ARGS = new String[] { - TvContract.Programs._ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_BROADCAST_GENRE, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_VERSION_NUMBER }; - private static final String[] CHANNEL_DATA_SELECTION_ARGS = new String[] { - TvContract.Channels._ID, - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1}; + private static final String[] ALL_PROGRAMS_SELECTION_ARGS = + new String[] { + TvContract.Programs._ID, + TvContract.Programs.COLUMN_TITLE, + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_CONTENT_RATING, + TvContract.Programs.COLUMN_BROADCAST_GENRE, + TvContract.Programs.COLUMN_CANONICAL_GENRE, + TvContract.Programs.COLUMN_SHORT_DESCRIPTION, + TvContract.Programs.COLUMN_VERSION_NUMBER + }; + private static final String[] CHANNEL_DATA_SELECTION_ARGS = + new String[] { + TvContract.Channels._ID, + TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, + TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + }; private static final int MSG_HANDLE_EVENTS = 1; private static final int MSG_HANDLE_CHANNEL = 2; @@ -90,9 +90,9 @@ public class ChannelDataManager implements Handler.Callback { /** * A version number to enforce consistency of the channel data. * - * WARNING: If a change in the database serialization lead to breaking the backward - * compatibility, you must increment this value so that the old data are purged, - * and the user is requested to perform the auto-scan again to generate the new data set. + *

WARNING: If a change in the database serialization lead to breaking the backward + * compatibility, you must increment this value so that the old data are purged, and the user is + * requested to perform the auto-scan again to generate the new data set. */ private static final int VERSION = 6; @@ -140,16 +140,16 @@ public class ChannelDataManager implements Handler.Callback { } public interface ChannelScanListener { - /** - * Invoked when all pending channels have been handled. - */ + /** Invoked when all pending channels have been handled. */ void onChannelHandlingDone(); } public ChannelDataManager(Context context) { mContext = context; - mInputId = TvContract.buildInputId(new ComponentName(mContext.getPackageName(), - TunerTvInputService.class.getName())); + mInputId = + TvContract.buildInputId( + new ComponentName( + mContext.getPackageName(), TunerTvInputService.class.getName())); mChannelsUri = TvContract.buildChannelsUriForInput(mInputId); mTunerChannelMap = new ConcurrentHashMap<>(); mTunerChannelIdMap = new ConcurrentSkipListMap<>(); @@ -211,8 +211,14 @@ public class ChannelDataManager implements Handler.Callback { } mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP); byte[] data = null; - try (Cursor cursor = mContext.getContentResolver().query(TvContract.buildChannelUri( - channelId), CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query( + TvContract.buildChannelUri(channelId), + CHANNEL_DATA_SELECTION_ARGS, + null, + null, + null)) { if (cursor != null && cursor.moveToFirst()) { data = cursor.getBlob(1); } @@ -255,8 +261,9 @@ public class ChannelDataManager implements Handler.Callback { public void notifyScanStarted() { mScannedChannels.clear(); mPreviousScannedChannels.clear(); - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { do { long channelId = cursor.getLong(0); @@ -289,8 +296,10 @@ public class ChannelDataManager implements Handler.Callback { if (!mPreviousScannedChannels.isEmpty()) { ArrayList ops = new ArrayList<>(); for (TunerChannel channel : mPreviousScannedChannels) { - ops.add(ContentProviderOperation.newDelete( - TvContract.buildChannelUri(channel.getChannelId())).build()); + ops.add( + ContentProviderOperation.newDelete( + TvContract.buildChannelUri(channel.getChannelId())) + .build()); } try { mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); @@ -299,20 +308,19 @@ public class ChannelDataManager implements Handler.Callback { } } if (mChannelScanListener != null && mChannelScanHandler != null) { - mChannelScanHandler.post(new Runnable() { - @Override - public void run() { - mChannelScanListener.onChannelHandlingDone(); - } - }); + mChannelScanHandler.post( + new Runnable() { + @Override + public void run() { + mChannelScanListener.onChannelHandlingDone(); + } + }); } else { Log.e(TAG, "Error. mChannelScanListener is null."); } } - /** - * Returns the number of scanned channels in the scanning mode. - */ + /** Returns the number of scanned channels in the scanning mode. */ public int getScannedChannelCount() { return mScannedChannels.size(); } @@ -327,46 +335,54 @@ public class ChannelDataManager implements Handler.Callback { @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_HANDLE_EVENTS: { - ChannelEvent event = (ChannelEvent) msg.obj; - handleEvents(event.channel, event.eitItems); - return true; - } - case MSG_HANDLE_CHANNEL: { - TunerChannel channel = (TunerChannel) msg.obj; - if (channel != null) { - handleChannel(channel); + case MSG_HANDLE_EVENTS: + { + ChannelEvent event = (ChannelEvent) msg.obj; + handleEvents(event.channel, event.eitItems); + return true; } - if (scanCompleted.get() && mIsScanning.get() - && !mHandler.hasMessages(MSG_HANDLE_CHANNEL)) { - // Complete the scan when all found channels have already been handled. - scannedChannelHandlingCompleted(); + case MSG_HANDLE_CHANNEL: + { + TunerChannel channel = (TunerChannel) msg.obj; + if (channel != null) { + handleChannel(channel); + } + if (scanCompleted.get() + && mIsScanning.get() + && !mHandler.hasMessages(MSG_HANDLE_CHANNEL)) { + // Complete the scan when all found channels have already been handled. + scannedChannelHandlingCompleted(); + } + return true; } - return true; - } - case MSG_BUILD_CHANNEL_MAP: { - mHandler.removeMessages(MSG_BUILD_CHANNEL_MAP); - buildChannelMap(); - return true; - } - case MSG_REQUEST_PROGRAMS: { - if (mHandler.hasMessages(MSG_REQUEST_PROGRAMS)) { + case MSG_BUILD_CHANNEL_MAP: + { + mHandler.removeMessages(MSG_BUILD_CHANNEL_MAP); + buildChannelMap(); return true; } - TunerChannel channel = (TunerChannel) msg.obj; - if (mListener != null) { - mListener.onRequestProgramsResponse(channel, getAllProgramsForChannel(channel)); + case MSG_REQUEST_PROGRAMS: + { + if (mHandler.hasMessages(MSG_REQUEST_PROGRAMS)) { + return true; + } + TunerChannel channel = (TunerChannel) msg.obj; + if (mListener != null) { + mListener.onRequestProgramsResponse( + channel, getAllProgramsForChannel(channel)); + } + return true; + } + case MSG_CLEAR_CHANNELS: + { + clearChannels(); + return true; + } + case MSG_CHECK_VERSION: + { + checkVersion(); + return true; } - return true; - } - case MSG_CLEAR_CHANNELS: { - clearChannels(); - return true; - } - case MSG_CHECK_VERSION: { - checkVersion(); - return true; - } } return false; } @@ -386,8 +402,9 @@ public class ChannelDataManager implements Handler.Callback { } long currentTime = System.currentTimeMillis(); - List oldItems = getAllProgramsForChannel(channel, currentTime, - currentTime + PROGRAM_QUERY_DURATION); + List oldItems = + getAllProgramsForChannel( + channel, currentTime, currentTime + PROGRAM_QUERY_DURATION); ArrayList ops = new ArrayList<>(); // TODO: Find a right way to check if the programs are added outside. boolean addedOutside = false; @@ -417,17 +434,21 @@ public class ChannelDataManager implements Handler.Callback { } else if (newItemStartTime > oldItems.get(oldItemCount - 1).getStartTimeUtcMillis()) { // Start time larger than that of any old item. Insert if no overlap. - if (newItemStartTime - < oldItems.get(oldItemCount - 1).getEndTimeUtcMillis()) continue; + if (newItemStartTime < oldItems.get(oldItemCount - 1).getEndTimeUtcMillis()) + continue; } else { - int pos = Collections.binarySearch(oldItems, newItem, - new Comparator() { - @Override - public int compare(EitItem lhs, EitItem rhs) { - return Long.compare(lhs.getStartTimeUtcMillis(), - rhs.getStartTimeUtcMillis()); - } - }); + int pos = + Collections.binarySearch( + oldItems, + newItem, + new Comparator() { + @Override + public int compare(EitItem lhs, EitItem rhs) { + return Long.compare( + lhs.getStartTimeUtcMillis(), + rhs.getStartTimeUtcMillis()); + } + }); if (pos >= 0) { // Same start Time found. Overlapped. continue; @@ -439,8 +460,11 @@ public class ChannelDataManager implements Handler.Callback { continue; } } - ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), newItem, channel)); + ops.add( + buildContentProviderOperation( + ContentProviderOperation.newInsert(TvContract.Programs.CONTENT_URI), + newItem, + channel)); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -464,7 +488,8 @@ public class ChannelDataManager implements Handler.Callback { // Since program descriptions arrive at different time, the older one may have the // correct program description while the newer one has no clue what value is. - if (oldItem.getDescription() != null && item.getDescription() == null + if (oldItem.getDescription() != null + && item.getDescription() == null && oldItem.getEventId() == item.getEventId() && oldItem.getStartTime() == item.getStartTime() && oldItem.getLengthInSecond() == item.getLengthInSecond() @@ -474,8 +499,12 @@ public class ChannelDataManager implements Handler.Callback { item.setDescription(oldItem.getDescription()); } if (item.compareTo(oldItem) != 0) { - ops.add(buildContentProviderOperation(ContentProviderOperation.newUpdate( - TvContract.buildProgramUri(oldItem.getProgramId())), item, null)); + ops.add( + buildContentProviderOperation( + ContentProviderOperation.newUpdate( + TvContract.buildProgramUri(oldItem.getProgramId())), + item, + null)); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -494,8 +523,11 @@ public class ChannelDataManager implements Handler.Callback { long newItemEndTime = item.getEndTimeUtcMillis(); if ((startTime >= newItemStartTime && startTime < newItemEndTime) || (endTime > newItemStartTime && endTime <= newItemEndTime)) { - ops.add(ContentProviderOperation.newDelete(TvContract.buildProgramUri( - unverifiedOldItems.getProgramId())).build()); + ops.add( + ContentProviderOperation.newDelete( + TvContract.buildProgramUri( + unverifiedOldItems.getProgramId())) + .build()); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -509,8 +541,11 @@ public class ChannelDataManager implements Handler.Callback { if (item.getEndTimeUtcMillis() < currentTime) { continue; } - ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), item, channel)); + ops.add( + buildContentProviderOperation( + ContentProviderOperation.newInsert(TvContract.Programs.CONTENT_URI), + item, + channel)); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -525,24 +560,23 @@ public class ChannelDataManager implements Handler.Callback { if (channel != null) { builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED, + builder.withValue( + TvContract.Programs.COLUMN_RECORDING_PROHIBITED, channel.isRecordingProhibited() ? 1 : 0); } } if (item != null) { builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText()) - .withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + .withValue( + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, item.getStartTimeUtcMillis()) - .withValue(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, + .withValue( + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, item.getEndTimeUtcMillis()) - .withValue(TvContract.Programs.COLUMN_CONTENT_RATING, - item.getContentRating()) - .withValue(TvContract.Programs.COLUMN_AUDIO_LANGUAGE, - item.getAudioLanguage()) - .withValue(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - item.getDescription()) - .withValue(TvContract.Programs.COLUMN_VERSION_NUMBER, - item.getEventId()); + .withValue(TvContract.Programs.COLUMN_CONTENT_RATING, item.getContentRating()) + .withValue(TvContract.Programs.COLUMN_AUDIO_LANGUAGE, item.getAudioLanguage()) + .withValue(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, item.getDescription()) + .withValue(TvContract.Programs.COLUMN_VERSION_NUMBER, item.getEventId()); } return builder.build(); } @@ -567,24 +601,28 @@ public class ChannelDataManager implements Handler.Callback { values.put(TvContract.Channels.COLUMN_DESCRIPTION, channel.getDescription()); values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.getVideoFormat()); values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, VERSION); - values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, + values.put( + TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, channel.isRecordingProhibited() ? 1 : 0); if (channelId <= 0) { values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId); - values.put(TvContract.Channels.COLUMN_TYPE, "QAM256".equals(channel.getModulation()) - ? TvContract.Channels.TYPE_ATSC_C : TvContract.Channels.TYPE_ATSC_T); + values.put( + TvContract.Channels.COLUMN_TYPE, + "QAM256".equals(channel.getModulation()) + ? TvContract.Channels.TYPE_ATSC_C + : TvContract.Channels.TYPE_ATSC_T); values.put(TvContract.Channels.COLUMN_SERVICE_ID, channel.getProgramNumber()); // ATSC doesn't have original_network_id values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.getFrequency()); - Uri channelUri = mContext.getContentResolver().insert(TvContract.Channels.CONTENT_URI, - values); + Uri channelUri = + mContext.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values); channelId = ContentUris.parseId(channelUri); } else { - mContext.getContentResolver().update( - TvContract.buildChannelUri(channelId), values, null, null); + mContext.getContentResolver() + .update(TvContract.buildChannelUri(channelId), values, null, null); } channel.setChannelId(channelId); mTunerChannelMap.put(channelId, channel); @@ -612,18 +650,30 @@ public class ChannelDataManager implements Handler.Callback { private void checkVersion() { if (PermissionUtils.hasAccessAllEpg(mContext)) { String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?"; - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, selection, - new String[] {Integer.toString(VERSION)}, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query( + mChannelsUri, + CHANNEL_DATA_SELECTION_ARGS, + selection, + new String[] {Integer.toString(VERSION)}, + null)) { if (cursor != null && cursor.moveToFirst()) { // The stored channel data seem outdated. Delete them all. clearChannels(); } } } else { - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - new String[] { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 }, - null, null, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query( + mChannelsUri, + new String[] { + TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + }, + null, + null, + null)) { if (cursor != null) { while (cursor.moveToNext()) { int version = cursor.getInt(0); @@ -643,8 +693,9 @@ public class ChannelDataManager implements Handler.Callback { return channelId; } mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP); - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { do { channelId = cursor.getLong(0); @@ -666,31 +717,46 @@ public class ChannelDataManager implements Handler.Callback { return getAllProgramsForChannel(channel, null, null); } - private List getAllProgramsForChannel(TunerChannel channel, @Nullable Long startTimeMs, - @Nullable Long endTimeMs) { + private List getAllProgramsForChannel( + TunerChannel channel, @Nullable Long startTimeMs, @Nullable Long endTimeMs) { List items = new ArrayList<>(); long channelId = channel.getChannelId(); - Uri programsUri = (startTimeMs == null || endTimeMs == null) ? - TvContract.buildProgramsUriForChannel(channelId) : - TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs); - try (Cursor cursor = mContext.getContentResolver().query(programsUri, - ALL_PROGRAMS_SELECTION_ARGS, null, null, null)) { + Uri programsUri = + (startTimeMs == null || endTimeMs == null) + ? TvContract.buildProgramsUriForChannel(channelId) + : TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs); + try (Cursor cursor = + mContext.getContentResolver() + .query(programsUri, ALL_PROGRAMS_SELECTION_ARGS, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { do { long id = cursor.getLong(0); String titleText = cursor.getString(1); - long startTime = ConvertUtils.convertUnixEpochToGPSTime( - cursor.getLong(2) / DateUtils.SECOND_IN_MILLIS); - long endTime = ConvertUtils.convertUnixEpochToGPSTime( - cursor.getLong(3) / DateUtils.SECOND_IN_MILLIS); + long startTime = + ConvertUtils.convertUnixEpochToGPSTime( + cursor.getLong(2) / DateUtils.SECOND_IN_MILLIS); + long endTime = + ConvertUtils.convertUnixEpochToGPSTime( + cursor.getLong(3) / DateUtils.SECOND_IN_MILLIS); int lengthInSecond = (int) (endTime - startTime); String contentRating = cursor.getString(4); String broadcastGenre = cursor.getString(5); String canonicalGenre = cursor.getString(6); String description = cursor.getString(7); int eventId = cursor.getInt(8); - EitItem eitItem = new EitItem(id, eventId, titleText, startTime, lengthInSecond, - contentRating, null, null, broadcastGenre, canonicalGenre, description); + EitItem eitItem = + new EitItem( + id, + eventId, + titleText, + startTime, + lengthInSecond, + contentRating, + null, + null, + broadcastGenre, + canonicalGenre, + description); items.add(eitItem); } while (cursor.moveToNext()); } @@ -700,8 +766,9 @@ public class ChannelDataManager implements Handler.Callback { private void buildChannelMap() { ArrayList channels = new ArrayList<>(); - try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { + try (Cursor cursor = + mContext.getContentResolver() + .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { do { long channelId = cursor.getLong(0); diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java index dc99118a..c529c6db 100644 --- a/src/com/android/tv/tuner/tvinput/EventDetector.java +++ b/src/com/android/tv/tuner/tvinput/EventDetector.java @@ -19,15 +19,13 @@ package com.android.tv.tuner.tvinput; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; - import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.data.PsiData; import com.android.tv.tuner.data.PsipData; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; - +import com.android.tv.tuner.ts.TsParser; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -57,163 +55,176 @@ public class EventDetector { private String mModulation; private int mProgramNumber = ALL_PROGRAM_NUMBERS; - private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() { - @Override - public void onPatDetected(List items) { - for (PsiData.PatItem i : items) { - if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) { - mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER); + private final TsParser.TsOutputListener mTsOutputListener = + new TsParser.TsOutputListener() { + @Override + public void onPatDetected(List items) { + for (PsiData.PatItem i : items) { + if (mProgramNumber == ALL_PROGRAM_NUMBERS + || mProgramNumber == i.getProgramNo()) { + mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER); + } + } } - } - } - - @Override - public void onEitPidDetected(int pid) { - startListening(pid); - } - - @Override - public void onEitItemParsed(PsipData.VctItem channel, List items) { - TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); - if (DEBUG) { - Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " " - + channel.getProgramNumber()); - } - int channelSourceId = channel.getSourceId(); - // Source id 0 is useful for cases where a cable operator wishes to define a channel for - // which no EPG data is currently available. - // We don't handle such a case. - if (channelSourceId == 0) { - return; - } - - // If at least a one caption track have been found in EIT items for the given channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); - for (PsipData.EitItem item : items) { - if (captionTracksFound) { - break; - } - List captionTracks = item.getCaptionTracks(); - if (captionTracks != null && !captionTracks.isEmpty()) { - captionTracksFound = true; - } - } - mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); - if (captionTracksFound) { - for (PsipData.EitItem item : items) { - item.setHasCaptionTrack(); - } - } - if (tunerChannel != null && !mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onEventDetected(tunerChannel, items); + @Override + public void onEitPidDetected(int pid) { + startListening(pid); } - } - } - - @Override - public void onEttPidDetected(int pid) { - startListening(pid); - } - @Override - public void onAllVctItemsParsed() { - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelScanDone(); + @Override + public void onEitItemParsed( + PsipData.VctItem channel, List items) { + TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); + if (DEBUG) { + Log.d( + TAG, + "onEitItemParsed tunerChannel:" + + tunerChannel + + " " + + channel.getProgramNumber()); + } + int channelSourceId = channel.getSourceId(); + + // Source id 0 is useful for cases where a cable operator wishes to define a + // channel for + // which no EPG data is currently available. + // We don't handle such a case. + if (channelSourceId == 0) { + return; + } + + // If at least a one caption track have been found in EIT items for the given + // channel, + // we starts to interpret the zero tracks as a clearance of the caption tracks. + boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); + for (PsipData.EitItem item : items) { + if (captionTracksFound) { + break; + } + List captionTracks = item.getCaptionTracks(); + if (captionTracks != null && !captionTracks.isEmpty()) { + captionTracksFound = true; + } + } + mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); + if (captionTracksFound) { + for (PsipData.EitItem item : items) { + item.setHasCaptionTrack(); + } + } + if (tunerChannel != null && !mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onEventDetected(tunerChannel, items); + } + } } - } - } - - @Override - public void onVctItemParsed(PsipData.VctItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onVctItemParsed VCT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - // Merges the audio and caption tracks located in PMT items into the tracks of the given - // tuner channel. - TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PsiData.PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); + @Override + public void onEttPidDetected(int pid) { + startListening(pid); } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getProgramNumber(); - // If at least a one caption track have been found in VCT items for the given channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber) - || !captionTracks.isEmpty(); - mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); - if (captionTracksFound) { - tunerChannel.setHasCaptionTrack(); - } - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - tunerChannel.setFrequency(mFrequency); - tunerChannel.setModulation(mModulation); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mVctProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mVctProgramNumberSet.add(channelProgramNumber); - } - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelDetected(tunerChannel, !found); + @Override + public void onAllVctItemsParsed() { + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelScanDone(); + } + } } - } - } - @Override - public void onSdtItemParsed(PsipData.SdtItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onSdtItemParsed SDT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of the given - // tuner channel. - TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PsiData.PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); + @Override + public void onVctItemParsed( + PsipData.VctItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onVctItemParsed VCT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } + + // Merges the audio and caption tracks located in PMT items into the tracks of + // the given + // tuner channel. + TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PsiData.PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getProgramNumber(); + + // If at least a one caption track have been found in VCT items for the given + // channel, + // we starts to interpret the zero tracks as a clearance of the caption tracks. + boolean captionTracksFound = + mVctCaptionTracksFound.get(channelProgramNumber) + || !captionTracks.isEmpty(); + mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); + if (captionTracksFound) { + tunerChannel.setHasCaptionTrack(); + } + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); + tunerChannel.setFrequency(mFrequency); + tunerChannel.setModulation(mModulation); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mVctProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mVctProgramNumberSet.add(channelProgramNumber); + } + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelDetected(tunerChannel, !found); + } + } } - } - int channelProgramNumber = channel.getServiceId(); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - tunerChannel.setFrequency(mFrequency); - tunerChannel.setModulation(mModulation); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mSdtProgramNumberSet.add(channelProgramNumber); - } - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelDetected(tunerChannel, !found); + + @Override + public void onSdtItemParsed( + PsipData.SdtItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onSdtItemParsed SDT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } + + // Merges the audio and caption tracks located in PMT items into the tracks of + // the given + // tuner channel. + TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PsiData.PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getServiceId(); + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); + tunerChannel.setFrequency(mFrequency); + tunerChannel.setModulation(mModulation); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mSdtProgramNumberSet.add(channelProgramNumber); + } + if (!mEventListeners.isEmpty()) { + for (EventListener eventListener : mEventListeners) { + eventListener.onChannelDetected(tunerChannel, !found); + } + } } - } - } - }; + }; - /** - * Listener for detecting ATSC TV channels and receiving EPG data. - */ + /** Listener for detecting ATSC TV channels and receiving EPG data. */ public interface EventListener { /** @@ -268,7 +279,7 @@ public class EventDetector { * @param frequency The frequency to listen to. * @param modulation The modulation type. * @param programNumber The program number if this is for handling tune request. For scanning - * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. + * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. */ public void startDetecting(int frequency, String modulation, int programNumber) { reset(); @@ -287,6 +298,7 @@ public class EventDetector { /** * Feeds ATSC TS stream to detect channel and program information. + * * @param data buffer for ATSC TS stream * @param startOffset the offset where buffer starts * @param length The length of available data @@ -302,6 +314,7 @@ public class EventDetector { /** * Retrieves the channel information regardless of being well-formed. + * * @return {@link List} of {@link TunerChannel} */ public List getMalFormedChannels() { @@ -310,6 +323,7 @@ public class EventDetector { /** * Registers an EventListener. + * * @param eventListener the listener to be registered */ public void registerListener(EventListener eventListener) { @@ -323,6 +337,7 @@ public class EventDetector { /** * Unregisters an EventListener. + * * @param eventListener the listener to be unregistered */ public void unregisterListener(EventListener eventListener) { diff --git a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java index 99222bf8..f2ed72f1 100644 --- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java +++ b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java @@ -19,7 +19,6 @@ package com.android.tv.tuner.tvinput; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; - import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.EitItem; @@ -31,7 +30,6 @@ import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.source.FileTsStreamer; import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.tvinput.EventDetector.EventListener; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -40,8 +38,8 @@ import java.util.Set; /** * PSIP event detector for a file source. * - *

Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports - * various PSIP-related events via {@link TsParser.TsOutputListener}. + *

Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports various + * PSIP-related events via {@link TsParser.TsOutputListener}. */ public class FileSourceEventDetector { private static final String TAG = "FileSourceEventDetector"; @@ -69,7 +67,7 @@ public class FileSourceEventDetector { * * @param provider MPEG-2 transport stream source. * @param programNumber The program number if this is for handling tune request. For scanning - * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. + * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. */ public void start(FileTsStreamer.StreamProvider provider, int programNumber) { mStreamProvider = provider; @@ -104,146 +102,158 @@ public class FileSourceEventDetector { mStreamProvider.addPidFilter(pid); } - private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() { - @Override - public void onPatDetected(List items) { - for (PatItem i : items) { - if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) { - mStreamProvider.addPidFilter(i.getPmtPid()); + private final TsParser.TsOutputListener mTsOutputListener = + new TsParser.TsOutputListener() { + @Override + public void onPatDetected(List items) { + for (PatItem i : items) { + if (mProgramNumber == ALL_PROGRAM_NUMBERS + || mProgramNumber == i.getProgramNo()) { + mStreamProvider.addPidFilter(i.getPmtPid()); + } + } } - } - } - @Override - public void onEitPidDetected(int pid) { - startListening(pid); - } + @Override + public void onEitPidDetected(int pid) { + startListening(pid); + } - @Override - public void onEitItemParsed(VctItem channel, List items) { - TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); - if (DEBUG) { - Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " " - + channel.getProgramNumber()); - } - int channelSourceId = channel.getSourceId(); - - // Source id 0 is useful for cases where a cable operator wishes to define a channel for - // which no EPG data is currently available. - // We don't handle such a case. - if (channelSourceId == 0) { - return; - } - - // If at least a one caption track have been found in EIT items for the given channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); - for (EitItem item : items) { - if (captionTracksFound) { - break; + @Override + public void onEitItemParsed(VctItem channel, List items) { + TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); + if (DEBUG) { + Log.d( + TAG, + "onEitItemParsed tunerChannel:" + + tunerChannel + + " " + + channel.getProgramNumber()); + } + int channelSourceId = channel.getSourceId(); + + // Source id 0 is useful for cases where a cable operator wishes to define a + // channel for + // which no EPG data is currently available. + // We don't handle such a case. + if (channelSourceId == 0) { + return; + } + + // If at least a one caption track have been found in EIT items for the given + // channel, + // we starts to interpret the zero tracks as a clearance of the caption tracks. + boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); + for (EitItem item : items) { + if (captionTracksFound) { + break; + } + List captionTracks = item.getCaptionTracks(); + if (captionTracks != null && !captionTracks.isEmpty()) { + captionTracksFound = true; + } + } + mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); + if (captionTracksFound) { + for (EitItem item : items) { + item.setHasCaptionTrack(); + } + } + if (tunerChannel != null && mEventListener != null) { + mEventListener.onEventDetected(tunerChannel, items); + } } - List captionTracks = item.getCaptionTracks(); - if (captionTracks != null && !captionTracks.isEmpty()) { - captionTracksFound = true; + + @Override + public void onEttPidDetected(int pid) { + startListening(pid); } - } - mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); - if (captionTracksFound) { - for (EitItem item : items) { - item.setHasCaptionTrack(); + + @Override + public void onAllVctItemsParsed() { + // do nothing. } - } - if (tunerChannel != null && mEventListener != null) { - mEventListener.onEventDetected(tunerChannel, items); - } - } - @Override - public void onEttPidDetected(int pid) { - startListening(pid); - } + @Override + public void onVctItemParsed(VctItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onVctItemParsed VCT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } - @Override - public void onAllVctItemsParsed() { - // do nothing. - } + // Merges the audio and caption tracks located in PMT items into the tracks of + // the given + // tuner channel. + TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getProgramNumber(); - @Override - public void onVctItemParsed(VctItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onVctItemParsed VCT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of the given - // tuner channel. - TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getProgramNumber(); - - // If at least a one caption track have been found in VCT items for the given channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber) - || !captionTracks.isEmpty(); - mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); - if (captionTracksFound) { - tunerChannel.setHasCaptionTrack(); - } - tunerChannel.setFilepath(mStreamProvider.getFilepath()); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mVctProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mVctProgramNumberSet.add(channelProgramNumber); - } - if (mEventListener != null) { - mEventListener.onChannelDetected(tunerChannel, !found); - } - } + // If at least a one caption track have been found in VCT items for the given + // channel, + // we starts to interpret the zero tracks as a clearance of the caption tracks. + boolean captionTracksFound = + mVctCaptionTracksFound.get(channelProgramNumber) + || !captionTracks.isEmpty(); + mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); + if (captionTracksFound) { + tunerChannel.setHasCaptionTrack(); + } + tunerChannel.setFilepath(mStreamProvider.getFilepath()); + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); - @Override - public void onSdtItemParsed(SdtItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onSdtItemParsed SDT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of the given - // tuner channel. - TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mVctProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mVctProgramNumberSet.add(channelProgramNumber); + } + if (mEventListener != null) { + mEventListener.onChannelDetected(tunerChannel, !found); + } } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); + + @Override + public void onSdtItemParsed(SdtItem channel, List pmtItems) { + if (DEBUG) { + Log.d(TAG, "onSdtItemParsed SDT " + channel); + Log.d(TAG, " PMT " + pmtItems); + } + + // Merges the audio and caption tracks located in PMT items into the tracks of + // the given + // tuner channel. + TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems); + List audioTracks = new ArrayList<>(); + List captionTracks = new ArrayList<>(); + for (PmtItem pmtItem : pmtItems) { + if (pmtItem.getAudioTracks() != null) { + audioTracks.addAll(pmtItem.getAudioTracks()); + } + if (pmtItem.getCaptionTracks() != null) { + captionTracks.addAll(pmtItem.getCaptionTracks()); + } + } + int channelProgramNumber = channel.getServiceId(); + tunerChannel.setFilepath(mStreamProvider.getFilepath()); + tunerChannel.setAudioTracks(audioTracks); + tunerChannel.setCaptionTracks(captionTracks); + mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); + boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); + if (!found) { + mSdtProgramNumberSet.add(channelProgramNumber); + } + if (mEventListener != null) { + mEventListener.onChannelDetected(tunerChannel, !found); + } } - } - int channelProgramNumber = channel.getServiceId(); - tunerChannel.setFilepath(mStreamProvider.getFilepath()); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mSdtProgramNumberSet.add(channelProgramNumber); - } - if (mEventListener != null) { - mEventListener.onChannelDetected(tunerChannel, !found); - } - } - }; + }; } diff --git a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java b/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java index 3908fe6c..1628bcfb 100644 --- a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java +++ b/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java @@ -16,9 +16,7 @@ package com.android.tv.tuner.tvinput; -/** - * The listener for buffer events occurred during playback. - */ +/** The listener for buffer events occurred during playback. */ public interface PlaybackBufferListener { /** @@ -35,8 +33,6 @@ public interface PlaybackBufferListener { */ void onBufferStateChanged(boolean available); - /** - * Invoked when the disk speed is too slow to write the buffers. - */ + /** Invoked when the disk speed is too slow to write the buffers. */ void onDiskTooSlow(); } diff --git a/src/com/android/tv/tuner/tvinput/TunerDebug.java b/src/com/android/tv/tuner/tvinput/TunerDebug.java index 2ddc946a..1df0b5c3 100644 --- a/src/com/android/tv/tuner/tvinput/TunerDebug.java +++ b/src/com/android/tv/tuner/tvinput/TunerDebug.java @@ -19,9 +19,7 @@ package com.android.tv.tuner.tvinput; import android.os.SystemClock; import android.util.Log; -/** - * A class to maintain various debugging information. - */ +/** A class to maintain various debugging information. */ public class TunerDebug { private static final String TAG = "TunerDebug"; public static final boolean ENABLED = false; @@ -117,14 +115,13 @@ public class TunerDebug { long duration = currentTime - sTunerDebug.mLastCheckTimestampMs; if (duration != 0) { sTunerDebug.mAudioPositionUsRate = - (sTunerDebug.mAudioPositionUs - sTunerDebug.mLastAudioPositionUs) * 1000 - / duration; + (sTunerDebug.mAudioPositionUs - sTunerDebug.mLastAudioPositionUs) + * 1000 + / duration; sTunerDebug.mAudioPtsUsRate = - (sTunerDebug.mAudioPtsUs - sTunerDebug.mLastAudioPtsUs) * 1000 - / duration; + (sTunerDebug.mAudioPtsUs - sTunerDebug.mLastAudioPtsUs) * 1000 / duration; sTunerDebug.mVideoPtsUsRate = - (sTunerDebug.mVideoPtsUs - sTunerDebug.mLastVideoPtsUs) * 1000 - / duration; + (sTunerDebug.mVideoPtsUs - sTunerDebug.mLastVideoPtsUs) * 1000 / duration; } sTunerDebug.mLastAudioPositionUs = sTunerDebug.mAudioPositionUs; diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java index acdd149f..a1f0c773 100644 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java +++ b/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java @@ -17,7 +17,6 @@ package com.android.tv.tuner.tvinput; import android.content.Context; -import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.net.Uri; import android.support.annotation.MainThread; @@ -25,20 +24,18 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.util.Log; -/** - * Processes DVR recordings, and deletes the previously recorded contents. - */ +/** Processes DVR recordings, and deletes the previously recorded contents. */ public class TunerRecordingSession extends TvInputService.RecordingSession { private static final String TAG = "TunerRecordingSession"; private static final boolean DEBUG = false; private final TunerRecordingSessionWorker mSessionWorker; - public TunerRecordingSession(Context context, String inputId, - ChannelDataManager channelDataManager) { + public TunerRecordingSession( + Context context, String inputId, ChannelDataManager channelDataManager) { super(context); - mSessionWorker = new TunerRecordingSessionWorker(context, inputId, channelDataManager, - this); + mSessionWorker = + new TunerRecordingSessionWorker(context, inputId, channelDataManager, this); } // RecordingSession diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java index 34013bf1..1bc4e295 100644 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java @@ -32,9 +32,7 @@ import android.support.annotation.IntDef; import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.util.Log; - import android.util.Pair; -import com.google.android.exoplayer.C; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.recording.RecordingCapability; @@ -52,7 +50,7 @@ import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsDataSourceManager; import com.android.tv.util.Utils; - +import com.google.android.exoplayer.C; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; @@ -63,18 +61,21 @@ import java.util.Locale; import java.util.Random; import java.util.concurrent.TimeUnit; -/** - * Implements a DVR feature. - */ -public class TunerRecordingSessionWorker implements PlaybackBufferListener, - EventDetector.EventListener, SampleExtractor.OnCompletionListener, - Handler.Callback { +/** Implements a DVR feature. */ +public class TunerRecordingSessionWorker + implements PlaybackBufferListener, + EventDetector.EventListener, + SampleExtractor.OnCompletionListener, + Handler.Callback { private static final String TAG = "TunerRecordingSessionW"; private static final boolean DEBUG = false; - private static final String SORT_BY_TIME = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS - + ", " + TvContract.Programs.COLUMN_CHANNEL_ID + ", " - + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; + private static final String SORT_BY_TIME = + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + + ", " + + TvContract.Programs.COLUMN_CHANNEL_ID + + ", " + + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); @@ -95,6 +96,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING}) @Retention(RetentionPolicy.SOURCE) public @interface DvrSessionState {} + private static final int STATE_IDLE = 1; private static final int STATE_TUNING = 2; private static final int STATE_TUNED = 3; @@ -126,8 +128,11 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private List mCaptionTracks; private DvrStorageManager mDvrStorageManager; - public TunerRecordingSessionWorker(Context context, String inputId, - ChannelDataManager dataManager, TunerRecordingSession session) { + public TunerRecordingSessionWorker( + Context context, + String inputId, + ChannelDataManager dataManager, + TunerRecordingSession session) { mRandom.setSeed(System.nanoTime()); mContext = context; HandlerThread handlerThread = new HandlerThread(TAG); @@ -146,13 +151,13 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, // PlaybackBufferListener @Override - public void onBufferStartTimeChanged(long startTimeMs) { } + public void onBufferStartTimeChanged(long startTimeMs) {} @Override - public void onBufferStateChanged(boolean available) { } + public void onBufferStateChanged(boolean available) {} @Override - public void onDiskTooSlow() { } + public void onDiskTooSlow() {} // EventDetector.EventListener @Override @@ -184,34 +189,26 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, reset(); } - /** - * Tunes to {@code channelUri}. - */ + /** Tunes to {@code channelUri}. */ @MainThread public void tune(Uri channelUri) { mHandler.removeCallbacksAndMessages(null); mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); } - /** - * Starts recording. - */ + /** Starts recording. */ @MainThread public void startRecording(@Nullable Uri programUri) { mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget(); } - /** - * Stops recording. - */ + /** Stops recording. */ @MainThread public void stopRecording() { mHandler.sendEmptyMessage(MSG_STOP_RECORDING); } - /** - * Releases all resources. - */ + /** Releases all resources. */ @MainThread public void release() { mHandler.removeCallbacksAndMessages(null); @@ -221,95 +218,103 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_TUNE: { - Uri channelUri = (Uri) msg.obj; - int retryCount = msg.arg1; - if (DEBUG) Log.d(TAG, "Tune to " + channelUri); - if (doTune(channelUri)) { - if (mSessionState == STATE_TUNED) { - mSession.onTuned(channelUri); - } else { - Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); - if (retryCount < MAX_TUNING_RETRY) { - Message tuneMsg = - mHandler.obtainMessage(MSG_TUNE, retryCount + 1, 0, channelUri); - mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); + case MSG_TUNE: + { + Uri channelUri = (Uri) msg.obj; + int retryCount = msg.arg1; + if (DEBUG) Log.d(TAG, "Tune to " + channelUri); + if (doTune(channelUri)) { + if (mSessionState == STATE_TUNED) { + mSession.onTuned(channelUri); } else { - mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); - reset(); + Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); + if (retryCount < MAX_TUNING_RETRY) { + Message tuneMsg = + mHandler.obtainMessage( + MSG_TUNE, retryCount + 1, 0, channelUri); + mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); + } else { + mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); + reset(); + } } } - } - return true; - } - case MSG_START_RECORDING: { - if (DEBUG) Log.d(TAG, "Start recording"); - if (!doStartRecording((Uri) msg.obj)) { - reset(); - } - return true; - } - case MSG_PREPARE_RECODER: { - if (DEBUG) Log.d(TAG, "Preparing recorder"); - if (!mRecorderRunning) { return true; } - try { - if (!mRecorder.prepare()) { - mHandler.sendEmptyMessageDelayed(MSG_PREPARE_RECODER, - PREPARE_RECORDER_POLL_MS); + case MSG_START_RECORDING: + { + if (DEBUG) Log.d(TAG, "Start recording"); + if (!doStartRecording((Uri) msg.obj)) { + reset(); } - } catch (IOException e) { - Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor"); - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - reset(); - } - return true; - } - case MSG_STOP_RECORDING: { - if (DEBUG) Log.d(TAG, "Stop recording"); - if (mSessionState != STATE_RECORDING) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - reset(); return true; } - if (mRecorderRunning) { - stopRecorder(); - } - return true; - } - case MSG_MONITOR_STORAGE_STATUS: { - if (mSessionState != STATE_RECORDING) { + case MSG_PREPARE_RECODER: + { + if (DEBUG) Log.d(TAG, "Preparing recorder"); + if (!mRecorderRunning) { + return true; + } + try { + if (!mRecorder.prepare()) { + mHandler.sendEmptyMessageDelayed( + MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS); + } + } catch (IOException e) { + Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor"); + mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); + reset(); + } return true; } - if (!mDvrStorageStatusManager.isStorageSufficient()) { + case MSG_STOP_RECORDING: + { + if (DEBUG) Log.d(TAG, "Stop recording"); + if (mSessionState != STATE_RECORDING) { + mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); + reset(); + return true; + } if (mRecorderRunning) { stopRecorder(); } - new DeleteRecordingTask().execute(mStorageDir); - mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + return true; + } + case MSG_MONITOR_STORAGE_STATUS: + { + if (mSessionState != STATE_RECORDING) { + return true; + } + if (!mDvrStorageStatusManager.isStorageSufficient()) { + if (mRecorderRunning) { + stopRecorder(); + } + new DeleteRecordingTask().execute(mStorageDir); + mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); + reset(); + } else { + mHandler.sendEmptyMessageDelayed( + MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); + } + return true; + } + case MSG_RELEASE: + { + // Since release was requested, current recording will be cancelled + // without notification. reset(); - } else { - mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, - STORAGE_MONITOR_INTERVAL_MS); + mSourceManager.release(); + mHandler.removeCallbacksAndMessages(null); + mHandler.getLooper().quitSafely(); + return true; + } + case MSG_UPDATE_CC_INFO: + { + Pair> pair = + (Pair>) msg.obj; + updateCaptionTracks(pair.first, pair.second); + return true; } - return true; - } - case MSG_RELEASE: { - // Since release was requested, current recording will be cancelled - // without notification. - reset(); - mSourceManager.release(); - mHandler.removeCallbacksAndMessages(null); - mHandler.getLooper().quitSafely(); - return true; - } - case MSG_UPDATE_CC_INFO: { - Pair> pair = - (Pair>) msg.obj; - updateCaptionTracks(pair.first, pair.second); - return true; - } } return false; } @@ -385,9 +390,12 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, Log.e(TAG, "Recording session status abnormal"); return false; } - mStorageDir = mDvrStorageStatusManager.isStorageSufficient() ? - new File(mDvrStorageStatusManager.getRecordingRootDataDirectory(), - getStorageKey()) : null; + mStorageDir = + mDvrStorageStatusManager.isStorageSufficient() + ? new File( + mDvrStorageStatusManager.getRecordingRootDataDirectory(), + getStorageKey()) + : null; if (mStorageDir == null) { mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); Log.w(TAG, "Failed to start recording due to insufficient storage."); @@ -397,16 +405,16 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); mRecordStartTime = System.currentTimeMillis(); mDvrStorageManager = new DvrStorageManager(mStorageDir, true); - mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, - new BufferManager(mDvrStorageManager), this, true); + mRecorder = + new ExoPlayerSampleExtractor( + Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true); mRecorder.setOnCompletionListener(this, mHandler); mProgramUri = programUri; mSessionState = STATE_RECORDING; mRecorderRunning = true; mHandler.sendEmptyMessage(MSG_PREPARE_RECODER); mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); - mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, - STORAGE_MONITOR_INTERVAL_MS); + mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); return true; } @@ -423,20 +431,25 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } private void updateCaptionTracks(TunerChannel channel, List items) { - if (mChannel == null || channel == null || mChannel.compareTo(channel) != 0 - || items == null || items.isEmpty()) { + if (mChannel == null + || channel == null + || mChannel.compareTo(channel) != 0 + || items == null + || items.isEmpty()) { return; } PsipData.EitItem currentProgram = getCurrentProgram(items); - if (currentProgram == null || !currentProgram.hasCaptionTrack() + if (currentProgram == null + || !currentProgram.hasCaptionTrack() || mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0) { return; } mCurrenProgram = currentProgram; mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks()); if (DEBUG) { - Log.d(TAG, "updated " + mCaptionTracks.size() + " caption tracks for " - + currentProgram); + Log.d( + TAG, + "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram); } } @@ -470,22 +483,22 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private final byte[] mInternalProviderData; private static final String[] PROJECTION = { - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_SEASON_TITLE, - TvContract.Programs.COLUMN_EPISODE_TITLE, - TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_POSTER_ART_URI, - TvContract.Programs.COLUMN_THUMBNAIL_URI, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_VIDEO_WIDTH, - TvContract.Programs.COLUMN_VIDEO_HEIGHT, - TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA + TvContract.Programs.COLUMN_CHANNEL_ID, + TvContract.Programs.COLUMN_TITLE, + TvContract.Programs.COLUMN_SEASON_TITLE, + TvContract.Programs.COLUMN_EPISODE_TITLE, + TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, + TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + TvContract.Programs.COLUMN_SHORT_DESCRIPTION, + TvContract.Programs.COLUMN_POSTER_ART_URI, + TvContract.Programs.COLUMN_THUMBNAIL_URI, + TvContract.Programs.COLUMN_CANONICAL_GENRE, + TvContract.Programs.COLUMN_CONTENT_RATING, + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_VIDEO_WIDTH, + TvContract.Programs.COLUMN_VIDEO_HEIGHT, + TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA }; public Program(Cursor cursor) { @@ -585,25 +598,32 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } } - private Uri insertRecordedProgram(Program program, long channelId, String storageUri, - long totalBytes, long startTime, long endTime) { + private Uri insertRecordedProgram( + Program program, + long channelId, + String storageUri, + long totalBytes, + long startTime, + long endTime) { // TODO: Set title even though program is null. - RecordedProgram recordedProgram = RecordedProgram.builder() - .setInputId(mInputId) - .setChannelId(channelId) - .setDataUri(storageUri) - .setDurationMillis(endTime - startTime) - .setDataBytes(totalBytes) - // startTime and endTime could be overridden by program's start and end value. - .setStartTimeUtcMillis(startTime) - .setEndTimeUtcMillis(endTime) - .build(); + RecordedProgram recordedProgram = + RecordedProgram.builder() + .setInputId(mInputId) + .setChannelId(channelId) + .setDataUri(storageUri) + .setDurationMillis(endTime - startTime) + .setDataBytes(totalBytes) + // startTime and endTime could be overridden by program's start and end + // value. + .setStartTimeUtcMillis(startTime) + .setEndTimeUtcMillis(endTime) + .build(); ContentValues values = RecordedProgram.toValues(recordedProgram); if (program != null) { values.putAll(program.buildValues()); } - return mContext.getContentResolver().insert(TvContract.RecordedPrograms.CONTENT_URI, - values); + return mContext.getContentResolver() + .insert(TvContract.RecordedPrograms.CONTENT_URI, values); } private void onRecordingResult(boolean success, long lastExtractedPositionUs) { @@ -616,8 +636,9 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, // In case of recorder not being stopped, because of premature termination of recording. stopRecorder(); } - if (!success && lastExtractedPositionUs < - TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) { + if (!success + && lastExtractedPositionUs + < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) { new DeleteRecordingTask().execute(mStorageDir); mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); Log.w(TAG, "Recording failed during recording"); @@ -653,7 +674,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, if (files == null || files.length == 0) { return null; } - for(File file : files) { + for (File file : files) { Utils.deleteDirOrFile(file); } return null; diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java index 44bae908..eec5da1f 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSession.java +++ b/src/com/android/tv/tuner/tvinput/TunerSession.java @@ -35,8 +35,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - -import com.google.android.exoplayer.audio.AudioCapabilities; import com.android.tv.tuner.R; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener; @@ -47,13 +45,14 @@ import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.util.GlobalSettingsUtils; import com.android.tv.tuner.util.StatusTextUtils; import com.android.tv.tuner.util.SystemPropertiesProxy; +import com.google.android.exoplayer.audio.AudioCapabilities; /** - * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions - * are implemented in {@link TunerSessionWorker}. + * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions are + * implemented in {@link TunerSessionWorker}. */ -public class TunerSession extends TvInputService.Session implements - Handler.Callback, TunerPreferencesChangedListener { +public class TunerSession extends TvInputService.Session + implements Handler.Callback, TunerPreferencesChangedListener { private static final String TAG = "TunerSession"; private static final boolean DEBUG = false; private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug"; @@ -87,8 +86,8 @@ public class TunerSession extends TvInputService.Session implements super(context); mContext = context; mUiHandler = new Handler(this); - LayoutInflater inflater = (LayoutInflater) - context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null); mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout); mMessageLayout.setVisibility(View.INVISIBLE); @@ -150,14 +149,13 @@ public class TunerSession extends TvInputService.Session implements @Override public void onTimeShiftSeekTo(long timeMs) { if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000); - mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO, - mPlayPaused ? 1 : 0, 0, timeMs); + mSessionWorker.sendMessage( + TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO, mPlayPaused ? 1 : 0, 0, timeMs); } @Override public void onTimeShiftSetPlaybackParams(PlaybackParams params) { - mSessionWorker.sendMessage( - TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params); + mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params); } @Override @@ -201,8 +199,7 @@ public class TunerSession extends TvInputService.Session implements @Override public void onUnblockContent(TvContentRating unblockedRating) { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING, - unblockedRating); + mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING, unblockedRating); } @Override @@ -216,20 +213,21 @@ public class TunerSession extends TvInputService.Session implements TunerPreferences.setTunerPreferencesChangedListener(null); } - /** - * Sets {@link AudioCapabilities}. - */ + /** Sets {@link AudioCapabilities}. */ public void setAudioCapabilities(AudioCapabilities audioCapabilities) { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED, - audioCapabilities); + mSessionWorker.sendMessage( + TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED, audioCapabilities); } @Override public void notifyVideoAvailable() { super.notifyVideoAvailable(); if (mTuneStartTimestamp != 0) { - Log.i(TAG, "[Profiler] Video available in " - + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms"); + Log.i( + TAG, + "[Profiler] Video available in " + + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + + " ms"); mTuneStartTimestamp = 0; } } @@ -258,61 +256,80 @@ public class TunerSession extends TvInputService.Session implements @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_UI_SHOW_MESSAGE: { - mMessageView.setText((String) msg.obj); - mMessageLayout.setVisibility(View.VISIBLE); - return true; - } - case MSG_UI_HIDE_MESSAGE: { - mMessageLayout.setVisibility(View.INVISIBLE); - return true; - } - case MSG_UI_SHOW_AUDIO_UNPLAYABLE: { - // Showing message of enabling surround sound only when global surround sound - // setting is "never". - final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext); - if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) { - mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( - mContext.getString(R.string.ut_surround_sound_disabled)))); - } else { - mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( - mContext.getString(R.string.audio_passthrough_not_supported)))); + case MSG_UI_SHOW_MESSAGE: + { + mMessageView.setText((String) msg.obj); + mMessageLayout.setVisibility(View.VISIBLE); + return true; + } + case MSG_UI_HIDE_MESSAGE: + { + mMessageLayout.setVisibility(View.INVISIBLE); + return true; + } + case MSG_UI_SHOW_AUDIO_UNPLAYABLE: + { + // Showing message of enabling surround sound only when global surround sound + // setting is "never". + final int value = + GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext); + if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) { + mAudioStatusView.setText( + Html.fromHtml( + StatusTextUtils.getAudioWarningInHTML( + mContext.getString( + R.string.ut_surround_sound_disabled)))); + } else { + mAudioStatusView.setText( + Html.fromHtml( + StatusTextUtils.getAudioWarningInHTML( + mContext.getString( + R.string + .audio_passthrough_not_supported)))); + } + mAudioStatusView.setVisibility(View.VISIBLE); + return true; + } + case MSG_UI_HIDE_AUDIO_UNPLAYABLE: + { + mAudioStatusView.setVisibility(View.INVISIBLE); + return true; + } + case MSG_UI_PROCESS_CAPTION_TRACK: + { + mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj); + return true; + } + case MSG_UI_START_CAPTION_TRACK: + { + mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj); + return true; + } + case MSG_UI_STOP_CAPTION_TRACK: + { + mCaptionTrackRenderer.stop(); + return true; + } + case MSG_UI_RESET_CAPTION_TRACK: + { + mCaptionTrackRenderer.reset(); + return true; + } + case MSG_UI_CLEAR_CAPTION_RENDERER: + { + mCaptionTrackRenderer.clear(); + return true; + } + case MSG_UI_SET_STATUS_TEXT: + { + mStatusView.setText((CharSequence) msg.obj); + return true; + } + case MSG_UI_TOAST_RESCAN_NEEDED: + { + Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show(); + return true; } - mAudioStatusView.setVisibility(View.VISIBLE); - return true; - } - case MSG_UI_HIDE_AUDIO_UNPLAYABLE: { - mAudioStatusView.setVisibility(View.INVISIBLE); - return true; - } - case MSG_UI_PROCESS_CAPTION_TRACK: { - mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj); - return true; - } - case MSG_UI_START_CAPTION_TRACK: { - mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj); - return true; - } - case MSG_UI_STOP_CAPTION_TRACK: { - mCaptionTrackRenderer.stop(); - return true; - } - case MSG_UI_RESET_CAPTION_TRACK: { - mCaptionTrackRenderer.reset(); - return true; - } - case MSG_UI_CLEAR_CAPTION_RENDERER: { - mCaptionTrackRenderer.clear(); - return true; - } - case MSG_UI_SET_STATUS_TEXT: { - mStatusView.setText((CharSequence) msg.obj); - return true; - } - case MSG_UI_TOAST_RESCAN_NEEDED: { - Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show(); - return true; - } } return false; } diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java index e7eb017e..7a0897e2 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java @@ -42,9 +42,6 @@ import android.util.Pair; import android.util.SparseArray; import android.view.Surface; import android.view.accessibility.CaptioningManager; - -import com.google.android.exoplayer.audio.AudioCapabilities; -import com.google.android.exoplayer.ExoPlayer; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvContentRatingCache; import com.android.tv.customization.TvCustomizationManager; @@ -58,18 +55,19 @@ import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; +import com.android.tv.tuner.exoplayer.MpegTsPlayer; import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager; import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; -import com.android.tv.tuner.exoplayer.MpegTsPlayer; import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsDataSourceManager; import com.android.tv.tuner.util.StatusTextUtils; import com.android.tv.tuner.util.SystemPropertiesProxy; - +import com.google.android.exoplayer.ExoPlayer; +import com.google.android.exoplayer.audio.AudioCapabilities; import java.io.File; import java.util.ArrayList; import java.util.Iterator; @@ -79,20 +77,24 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** - * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs - * such as handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on. + * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs such as + * handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on. */ @WorkerThread -public class TunerSessionWorker implements PlaybackBufferListener, - MpegTsPlayer.VideoEventListener, MpegTsPlayer.Listener, EventDetector.EventListener, - ChannelDataManager.ProgramInfoListener, Handler.Callback { +public class TunerSessionWorker + implements PlaybackBufferListener, + MpegTsPlayer.VideoEventListener, + MpegTsPlayer.Listener, + EventDetector.EventListener, + ChannelDataManager.ProgramInfoListener, + Handler.Callback { private static final String TAG = "TunerSessionWorker"; private static final boolean DEBUG = false; private static final boolean ENABLE_PROFILER = true; private static final String PLAY_FROM_CHANNEL = "channel"; private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes"; - private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB - private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB + private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB + private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB // Public messages public static final int MSG_SELECT_TRACK = 1; @@ -214,8 +216,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, private boolean mReleaseRequested; // Guarded by mReleaseLock private final Object mReleaseLock = new Object(); - public TunerSessionWorker(Context context, ChannelDataManager channelDataManager, - TunerSession tunerSession) { + public TunerSessionWorker( + Context context, ChannelDataManager channelDataManager, TunerSession tunerSession) { if (DEBUG) Log.d(TAG, "TunerSessionWorker created"); mContext = context; @@ -240,11 +242,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, mMaxTrickplayBufferSizeMb = SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context); - if (mTrickplayModeCustomization == - TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { + if (mTrickplayModeCustomization + == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { boolean useExternalStorage = - Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && - Environment.isExternalStorageRemovable(); + Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) + && Environment.isExternalStorageRemovable(); mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null; } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { mTrickplayBufferDir = context.getCacheDir(); @@ -286,9 +288,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, sendMessage(MSG_STOP_TUNE); } - /** - * Sets {@link Surface}. - */ + /** Sets {@link Surface}. */ @MainThread public void setSurface(Surface surface) { if (surface != null && !surface.isValid()) { @@ -301,9 +301,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, mHandler.sendEmptyMessage(MSG_SET_SURFACE); } - /** - * Sets volume. - */ + /** Sets volume. */ @MainThread public void setStreamVolume(float volume) { // mVolume is kept even when tune is called right after. But, messages can be deleted by @@ -313,9 +311,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME); } - /** - * Sets if caption is enabled or disabled. - */ + /** Sets if caption is enabled or disabled. */ @MainThread public void setCaptionEnabled(boolean captionEnabled) { // mCaptionEnabled is kept even when tune is called right after. But, messages can be @@ -334,18 +330,16 @@ public class TunerSessionWorker implements PlaybackBufferListener, return mBufferStartTimeMs; } - private String getRecordingPath() { return Uri.parse(mRecordingId).getPath(); } private Long getDurationForRecording(String recordingId) { DvrStorageManager storageManager = - new DvrStorageManager(new File(getRecordingPath()), false); - List trackFormatList = - storageManager.readTrackInfoFiles(false); + new DvrStorageManager(new File(getRecordingPath()), false); + List trackFormatList = storageManager.readTrackInfoFiles(false); if (trackFormatList.isEmpty()) { - trackFormatList = storageManager.readTrackInfoFiles(true); + trackFormatList = storageManager.readTrackInfoFiles(true); } if (!trackFormatList.isEmpty()) { BufferManager.TrackFormat trackFormat = trackFormatList.get(0); @@ -361,16 +355,23 @@ public class TunerSessionWorker implements PlaybackBufferListener, public long getCurrentPosition() { // TODO: More precise time may be necessary. MpegTsPlayer mpegTsPlayer = mPlayer; - long currentTime = mpegTsPlayer != null - ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() : mRecordStartTimeMs; + long currentTime = + mpegTsPlayer != null + ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() + : mRecordStartTimeMs; if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) { currentTime = mRecordingDuration + mRecordStartTimeMs; } if (DEBUG) { long systemCurrentTime = System.currentTimeMillis(); - Log.d(TAG, "currentTime = " + currentTime - + " ; System.currentTimeMillis() = " + systemCurrentTime - + " ; diff = " + (currentTime - systemCurrentTime)); + Log.d( + TAG, + "currentTime = " + + currentTime + + " ; System.currentTimeMillis() = " + + systemCurrentTime + + " ; diff = " + + (currentTime - systemCurrentTime)); } return currentTime; } @@ -440,8 +441,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, public void onError(Exception e) { if (TunerPreferences.getStoreTsStream(mContext)) { // Crash intentionally to capture the error causing TS file. - Log.e(TAG, "Crash intentionally to capture the error causing TS file. " - + e.getMessage()); + Log.e( + TAG, + "Crash intentionally to capture the error causing TS file. " + e.getMessage()); SoftPreconditions.checkState(false); } // There maybe some errors that finally raise ExoPlaybackException and will be handled here. @@ -506,8 +508,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, return; } Log.i(TAG, "AC3 audio cannot be played due to device limitation"); - mSession.sendUiMessage( - TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE); + mSession.sendUiMessage(TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE); } // MpegTsPlayer.VideoEventListener @@ -626,7 +627,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, private RecordedProgram getRecordedProgram(Uri recordedUri) { ContentResolver resolver = mContext.getContentResolver(); - try(Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) { + try (Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) { if (c != null) { RecordedProgram result = RecordedProgram.onQuery(c); if (DEBUG) { @@ -655,460 +656,514 @@ public class TunerSessionWorker implements PlaybackBufferListener, @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_TUNE: { - if (DEBUG) Log.d(TAG, "MSG_TUNE"); - - // When sequential tuning messages arrived, it skips middle tuning messages in order - // to change to the last requested channel quickly. - if (mHandler.hasMessages(MSG_TUNE)) { - return true; - } - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); - if (!mIsActiveSession) { - // Wait until release is finished if there is a pending release. - try { - while (!sActiveSessionSemaphore.tryAcquire( - RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) { - synchronized (mReleaseLock) { - if (mReleaseRequested) { - return true; + case MSG_TUNE: + { + if (DEBUG) Log.d(TAG, "MSG_TUNE"); + + // When sequential tuning messages arrived, it skips middle tuning messages in + // order + // to change to the last requested channel quickly. + if (mHandler.hasMessages(MSG_TUNE)) { + return true; + } + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + if (!mIsActiveSession) { + // Wait until release is finished if there is a pending release. + try { + while (!sActiveSessionSemaphore.tryAcquire( + RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) { + synchronized (mReleaseLock) { + if (mReleaseRequested) { + return true; + } } } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - synchronized (mReleaseLock) { - if (mReleaseRequested) { - sActiveSessionSemaphore.release(); - return true; + synchronized (mReleaseLock) { + if (mReleaseRequested) { + sActiveSessionSemaphore.release(); + return true; + } } + mIsActiveSession = true; } - mIsActiveSession = true; - } - Uri channelUri = (Uri) msg.obj; - String recording = null; - long channelId = parseChannel(channelUri); - TunerChannel channel = (channelId == -1) ? null - : mChannelDataManager.getChannel(channelId); - if (channelId == -1) { - recording = parseRecording(channelUri); + Uri channelUri = (Uri) msg.obj; + String recording = null; + long channelId = parseChannel(channelUri); + TunerChannel channel = + (channelId == -1) ? null : mChannelDataManager.getChannel(channelId); + if (channelId == -1) { + recording = parseRecording(channelUri); + } + if (channel == null && recording == null) { + Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri); + stopTune(); + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); + return true; + } + clearCallbacksAndMessagesSafely(); + mChannelDataManager.removeAllCallbacksAndMessages(); + if (channel != null) { + mChannelDataManager.requestProgramsData(channel); + } + prepareTune(channel, recording); + // TODO: Need to refactor. notifyContentAllowed() should not be called if + // parental + // control is turned on. + mSession.notifyContentAllowed(); + resetTvTracks(); + resetPlayback(); + mHandler.sendEmptyMessageDelayed( + MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); + return true; } - if (channel == null && recording == null) { - Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri); - stopTune(); + case MSG_STOP_TUNE: + { + if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); + mChannel = null; + stopPlayback(true); + stopCaptionTrack(); + resetTvTracks(); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); return true; } - clearCallbacksAndMessagesSafely(); - mChannelDataManager.removeAllCallbacksAndMessages(); - if (channel != null) { - mChannelDataManager.requestProgramsData(channel); - } - prepareTune(channel, recording); - // TODO: Need to refactor. notifyContentAllowed() should not be called if parental - // control is turned on. - mSession.notifyContentAllowed(); - resetTvTracks(); - resetPlayback(); - mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, - RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); - return true; - } - case MSG_STOP_TUNE: { - if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); - mChannel = null; - stopPlayback(true); - stopCaptionTrack(); - resetTvTracks(); - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); - return true; - } - case MSG_RELEASE: { - if (DEBUG) Log.d(TAG, "MSG_RELEASE"); - mHandler.removeCallbacksAndMessages(null); - stopPlayback(true); - stopCaptionTrack(); - mSourceManager.release(); - mHandler.getLooper().quitSafely(); - if (mIsActiveSession) { - sActiveSessionSemaphore.release(); - } - return true; - } - case MSG_RETRY_PLAYBACK: { - if (System.identityHashCode(mPlayer) == (int) msg.obj) { - Log.i(TAG, "Retrying the playback for channel: " + mChannel); - mHandler.removeMessages(MSG_RETRY_PLAYBACK); - // When there is a request of retrying playback, don't reuse TunerHal. - mSourceManager.setKeepTuneStatus(false); - mRetryCount++; - if (DEBUG) { - Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount); - } - mChannelDataManager.removeAllCallbacksAndMessages(); - if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) { - resetPlayback(); - } else { - // When it reaches this point, it may be due to an error that occurred in - // the tuner device. Calling stopPlayback() resets the tuner device - // to recover from the error. - stopPlayback(false); - stopCaptionTrack(); - - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - Log.i(TAG, "Notify weak signal since fail to retry playback"); - - // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen - // value before recovering the playback. - mHandler.sendEmptyMessageDelayed(MSG_RESET_PLAYBACK, - RECOVER_STOPPED_PLAYBACK_PERIOD_MS); + case MSG_RELEASE: + { + if (DEBUG) Log.d(TAG, "MSG_RELEASE"); + mHandler.removeCallbacksAndMessages(null); + stopPlayback(true); + stopCaptionTrack(); + mSourceManager.release(); + mHandler.getLooper().quitSafely(); + if (mIsActiveSession) { + sActiveSessionSemaphore.release(); } + return true; } - return true; - } - case MSG_RESET_PLAYBACK: { - if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK"); - mChannelDataManager.removeAllCallbacksAndMessages(); - resetPlayback(); - return true; - } - case MSG_START_PLAYBACK: { - if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK"); - if (mChannel != null || mRecordingId != null) { - startPlayback((int) msg.obj); - } - return true; - } - case MSG_UPDATE_PROGRAM: { - if (mChannel != null) { - EitItem program = (EitItem) msg.obj; - updateTvTracks(program, false); - mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); + case MSG_RETRY_PLAYBACK: + { + if (System.identityHashCode(mPlayer) == (int) msg.obj) { + Log.i(TAG, "Retrying the playback for channel: " + mChannel); + mHandler.removeMessages(MSG_RETRY_PLAYBACK); + // When there is a request of retrying playback, don't reuse TunerHal. + mSourceManager.setKeepTuneStatus(false); + mRetryCount++; + if (DEBUG) { + Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount); + } + mChannelDataManager.removeAllCallbacksAndMessages(); + if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) { + resetPlayback(); + } else { + // When it reaches this point, it may be due to an error that occurred + // in + // the tuner device. Calling stopPlayback() resets the tuner device + // to recover from the error. + stopPlayback(false); + stopCaptionTrack(); + + notifyVideoUnavailable( + TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); + Log.i(TAG, "Notify weak signal since fail to retry playback"); + + // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically + // chosen + // value before recovering the playback. + mHandler.sendEmptyMessageDelayed( + MSG_RESET_PLAYBACK, RECOVER_STOPPED_PLAYBACK_PERIOD_MS); + } + } + return true; } - return true; - } - case MSG_SCHEDULE_OF_PROGRAMS: { - mHandler.removeMessages(MSG_UPDATE_PROGRAM); - Pair> pair = - (Pair>) msg.obj; - TunerChannel channel = pair.first; - if (mChannel == null) { + case MSG_RESET_PLAYBACK: + { + if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK"); + mChannelDataManager.removeAllCallbacksAndMessages(); + resetPlayback(); return true; } - if (mChannel != null && mChannel.compareTo(channel) != 0) { + case MSG_START_PLAYBACK: + { + if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK"); + if (mChannel != null || mRecordingId != null) { + startPlayback((int) msg.obj); + } return true; } - mPrograms = pair.second; - EitItem currentProgram = getCurrentProgram(); - if (currentProgram == null) { - mProgram = null; + case MSG_UPDATE_PROGRAM: + { + if (mChannel != null) { + EitItem program = (EitItem) msg.obj; + updateTvTracks(program, false); + mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); + } + return true; } - long currentTimeMs = getCurrentPosition(); - if (mPrograms != null) { - for (EitItem item : mPrograms) { - if (currentProgram != null && currentProgram.compareTo(item) == 0) { - if (DEBUG) { - Log.d(TAG, "Update current TvTracks " + item); - } - if (mProgram != null && mProgram.compareTo(item) == 0) { - continue; - } - mProgram = item; - updateTvTracks(item, false); - } else if (item.getStartTimeUtcMillis() > currentTimeMs) { - if (DEBUG) { - Log.d(TAG, "Update next TvTracks " + item + " " - + (item.getStartTimeUtcMillis() - currentTimeMs)); + case MSG_SCHEDULE_OF_PROGRAMS: + { + mHandler.removeMessages(MSG_UPDATE_PROGRAM); + Pair> pair = + (Pair>) msg.obj; + TunerChannel channel = pair.first; + if (mChannel == null) { + return true; + } + if (mChannel != null && mChannel.compareTo(channel) != 0) { + return true; + } + mPrograms = pair.second; + EitItem currentProgram = getCurrentProgram(); + if (currentProgram == null) { + mProgram = null; + } + long currentTimeMs = getCurrentPosition(); + if (mPrograms != null) { + for (EitItem item : mPrograms) { + if (currentProgram != null && currentProgram.compareTo(item) == 0) { + if (DEBUG) { + Log.d(TAG, "Update current TvTracks " + item); + } + if (mProgram != null && mProgram.compareTo(item) == 0) { + continue; + } + mProgram = item; + updateTvTracks(item, false); + } else if (item.getStartTimeUtcMillis() > currentTimeMs) { + if (DEBUG) { + Log.d( + TAG, + "Update next TvTracks " + + item + + " " + + (item.getStartTimeUtcMillis() + - currentTimeMs)); + } + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item), + item.getStartTimeUtcMillis() - currentTimeMs); } - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item), - item.getStartTimeUtcMillis() - currentTimeMs); } } + mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); + return true; } - mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); - return true; - } - case MSG_UPDATE_CHANNEL_INFO: { - TunerChannel channel = (TunerChannel) msg.obj; - if (mChannel != null && mChannel.compareTo(channel) == 0) { - updateChannelInfo(channel); - } - return true; - } - case MSG_PROGRAM_DATA_RESULT: { - TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first; - - // If there already exists, skip it since real-time data is a top priority, - if (mChannel != null && mChannel.compareTo(channel) == 0 - && mPrograms == null && mProgram == null) { - sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj); - } - return true; - } - case MSG_TRICKPLAY_BY_SEEK: { - if (mPlayer == null) { + case MSG_UPDATE_CHANNEL_INFO: + { + TunerChannel channel = (TunerChannel) msg.obj; + if (mChannel != null && mChannel.compareTo(channel) == 0) { + updateChannelInfo(channel); + } return true; } - doTrickplayBySeek(msg.arg1); - return true; - } - case MSG_SMOOTH_TRICKPLAY_MONITOR: { - if (mPlayer == null) { + case MSG_PROGRAM_DATA_RESULT: + { + TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first; + + // If there already exists, skip it since real-time data is a top priority, + if (mChannel != null + && mChannel.compareTo(channel) == 0 + && mPrograms == null + && mProgram == null) { + sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj); + } return true; } - long systemCurrentTime = System.currentTimeMillis(); - long position = getCurrentPosition(); - if (mRecordingId == null) { - // Checks if the position exceeds the upper bound when forwarding, - // or exceed the lower bound when rewinding. - // If the direction is not checked, there can be some issues. - // (See b/29939781 for more details.) - if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L) - || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) { - doTimeShiftResume(); + case MSG_TRICKPLAY_BY_SEEK: + { + if (mPlayer == null) { return true; } - } else { - if (position > mRecordingDuration || position < 0) { - doTimeShiftPause(); + doTrickplayBySeek(msg.arg1); + return true; + } + case MSG_SMOOTH_TRICKPLAY_MONITOR: + { + if (mPlayer == null) { return true; } + long systemCurrentTime = System.currentTimeMillis(); + long position = getCurrentPosition(); + if (mRecordingId == null) { + // Checks if the position exceeds the upper bound when forwarding, + // or exceed the lower bound when rewinding. + // If the direction is not checked, there can be some issues. + // (See b/29939781 for more details.) + if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L) + || (position < mBufferStartTimeMs + && mPlaybackParams.getSpeed() < 0L)) { + doTimeShiftResume(); + return true; + } + } else { + if (position > mRecordingDuration || position < 0) { + doTimeShiftPause(); + return true; + } + } + mHandler.sendEmptyMessageDelayed( + MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS); + return true; } - mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR, - TRICKPLAY_MONITOR_INTERVAL_MS); - return true; - } - case MSG_RESCHEDULE_PROGRAMS: { - if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { - mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); - } else { - doReschedulePrograms(); - } - return true; - } - case MSG_PARENTAL_CONTROLS: { - doParentalControls(); - mHandler.removeMessages(MSG_PARENTAL_CONTROLS); - mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, - PARENTAL_CONTROLS_INTERVAL_MS); - return true; - } - case MSG_UNBLOCKED_RATING: { - mUnblockedContentRating = (TvContentRating) msg.obj; - doParentalControls(); - mHandler.removeMessages(MSG_PARENTAL_CONTROLS); - mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, - PARENTAL_CONTROLS_INTERVAL_MS); - return true; - } - case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: { - int serviceNumber = (int) msg.obj; - doDiscoverCaptionServiceNumber(serviceNumber); - return true; - } - case MSG_SELECT_TRACK: { - if (mChannel != null || mRecordingId != null) { - doSelectTrack(msg.arg1, (String) msg.obj); + case MSG_RESCHEDULE_PROGRAMS: + { + if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { + mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); + } else { + doReschedulePrograms(); + } + return true; } - return true; - } - case MSG_UPDATE_CAPTION_TRACK: { - if (mCaptionEnabled) { - startCaptionTrack(); - } else { - stopCaptionTrack(); + case MSG_PARENTAL_CONTROLS: + { + doParentalControls(); + mHandler.removeMessages(MSG_PARENTAL_CONTROLS); + mHandler.sendEmptyMessageDelayed( + MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); + return true; } - return true; - } - case MSG_TIMESHIFT_PAUSE: { - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE"); - if (mPlayer == null) { + case MSG_UNBLOCKED_RATING: + { + mUnblockedContentRating = (TvContentRating) msg.obj; + doParentalControls(); + mHandler.removeMessages(MSG_PARENTAL_CONTROLS); + mHandler.sendEmptyMessageDelayed( + MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); return true; } - setTrickplayEnabledIfNeeded(); - doTimeShiftPause(); - return true; - } - case MSG_TIMESHIFT_RESUME: { - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME"); - if (mPlayer == null) { + case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: + { + int serviceNumber = (int) msg.obj; + doDiscoverCaptionServiceNumber(serviceNumber); return true; } - setTrickplayEnabledIfNeeded(); - doTimeShiftResume(); - return true; - } - case MSG_TIMESHIFT_SEEK_TO: { - long position = (long) msg.obj; - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")"); - if (mPlayer == null) { + case MSG_SELECT_TRACK: + { + if (mChannel != null || mRecordingId != null) { + doSelectTrack(msg.arg1, (String) msg.obj); + } return true; } - setTrickplayEnabledIfNeeded(); - doTimeShiftSeekTo(position); - return true; - } - case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: { - if (mPlayer == null) { + case MSG_UPDATE_CAPTION_TRACK: + { + if (mCaptionEnabled) { + startCaptionTrack(); + } else { + stopCaptionTrack(); + } return true; } - setTrickplayEnabledIfNeeded(); - doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj); - return true; - } - case MSG_AUDIO_CAPABILITIES_CHANGED: { - AudioCapabilities capabilities = (AudioCapabilities) msg.obj; - if (DEBUG) { - Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities); + case MSG_TIMESHIFT_PAUSE: + { + if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE"); + if (mPlayer == null) { + return true; + } + setTrickplayEnabledIfNeeded(); + doTimeShiftPause(); + return true; } - if (capabilities == null) { + case MSG_TIMESHIFT_RESUME: + { + if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME"); + if (mPlayer == null) { + return true; + } + setTrickplayEnabledIfNeeded(); + doTimeShiftResume(); return true; } - if (!capabilities.equals(mAudioCapabilities)) { - // HDMI supported encodings are changed. restart player. - mAudioCapabilities = capabilities; - resetPlayback(); + case MSG_TIMESHIFT_SEEK_TO: + { + long position = (long) msg.obj; + if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")"); + if (mPlayer == null) { + return true; + } + setTrickplayEnabledIfNeeded(); + doTimeShiftSeekTo(position); + return true; } - return true; - } - case MSG_SET_STREAM_VOLUME: { - if (mPlayer != null && mPlayer.isPlaying()) { - mPlayer.setVolume(mVolume); + case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: + { + if (mPlayer == null) { + return true; + } + setTrickplayEnabledIfNeeded(); + doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj); + return true; } - return true; - } - case MSG_TUNER_PREFERENCES_CHANGED: { - mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED); - @TrickplaySetting int trickplaySetting = - TunerPreferences.getTrickplaySetting(mContext); - if (trickplaySetting != mTrickplaySetting) { - boolean wasTrcikplayEnabled = - mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; - boolean isTrickplayEnabled = - trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; - mTrickplaySetting = trickplaySetting; - if (isTrickplayEnabled != wasTrcikplayEnabled) { - sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer)); + case MSG_AUDIO_CAPABILITIES_CHANGED: + { + AudioCapabilities capabilities = (AudioCapabilities) msg.obj; + if (DEBUG) { + Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities); + } + if (capabilities == null) { + return true; + } + if (!capabilities.equals(mAudioCapabilities)) { + // HDMI supported encodings are changed. restart player. + mAudioCapabilities = capabilities; + resetPlayback(); } + return true; } - return true; - } - case MSG_BUFFER_START_TIME_CHANGED: { - if (mPlayer == null) { + case MSG_SET_STREAM_VOLUME: + { + if (mPlayer != null && mPlayer.isPlaying()) { + mPlayer.setVolume(mVolume); + } return true; } - mBufferStartTimeMs = (long) msg.obj; - if (!hasEnoughBackwardBuffer() - && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) { - mPlayer.setPlayWhenReady(true); - mPlayer.setAudioTrackAndClosedCaption(true); - mPlaybackParams.setSpeed(1.0f); + case MSG_TUNER_PREFERENCES_CHANGED: + { + mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED); + @TrickplaySetting + int trickplaySetting = TunerPreferences.getTrickplaySetting(mContext); + if (trickplaySetting != mTrickplaySetting) { + boolean wasTrcikplayEnabled = + mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + boolean isTrickplayEnabled = + trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + mTrickplaySetting = trickplaySetting; + if (isTrickplayEnabled != wasTrcikplayEnabled) { + sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer)); + } + } + return true; } - return true; - } - case MSG_BUFFER_STATE_CHANGED: { - boolean available = (boolean) msg.obj; - mSession.notifyTimeShiftStatusChanged(available - ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE - : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); - return true; - } - case MSG_CHECK_SIGNAL: { - if (mChannel == null || mPlayer == null) { + case MSG_BUFFER_START_TIME_CHANGED: + { + if (mPlayer == null) { + return true; + } + mBufferStartTimeMs = (long) msg.obj; + if (!hasEnoughBackwardBuffer() + && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) { + mPlayer.setPlayWhenReady(true); + mPlayer.setAudioTrackAndClosedCaption(true); + mPlaybackParams.setSpeed(1.0f); + } return true; } - TsDataSource source = mPlayer.getDataSource(); - long limitInBytes = source != null ? source.getBufferedPosition() : 0L; - if (TunerDebug.ENABLED) { - TunerDebug.calculateDiff(); - mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT, - Html.fromHtml( - StatusTextUtils.getStatusWarningInHTML( - (limitInBytes - mLastLimitInBytes) - / TS_PACKET_SIZE, - TunerDebug.getVideoFrameDrop(), - TunerDebug.getBytesInQueue(), - TunerDebug.getAudioPositionUs(), - TunerDebug.getAudioPositionUsRate(), - TunerDebug.getAudioPtsUs(), - TunerDebug.getAudioPtsUsRate(), - TunerDebug.getVideoPtsUs(), - TunerDebug.getVideoPtsUsRate() - ))); + case MSG_BUFFER_STATE_CHANGED: + { + boolean available = (boolean) msg.obj; + mSession.notifyTimeShiftStatusChanged( + available + ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE + : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); + return true; } - mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); - long currentTime = SystemClock.elapsedRealtime(); - long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME - ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs; - long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME - ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs; - boolean isBufferingTooLong = - bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - boolean isPreparingTooLong = - preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - boolean isWeakSignal = source != null - && mChannel.getType() != Channel.TYPE_FILE - && (isBufferingTooLong || isPreparingTooLong); - if (isWeakSignal && !mReportedWeakSignal) { - if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, - System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); + case MSG_CHECK_SIGNAL: + { + if (mChannel == null || mPlayer == null) { + return true; } - if (mPlayer != null) { - mPlayer.setAudioTrackAndClosedCaption(false); + TsDataSource source = mPlayer.getDataSource(); + long limitInBytes = source != null ? source.getBufferedPosition() : 0L; + if (TunerDebug.ENABLED) { + TunerDebug.calculateDiff(); + mSession.sendUiMessage( + TunerSession.MSG_UI_SET_STATUS_TEXT, + Html.fromHtml( + StatusTextUtils.getStatusWarningInHTML( + (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, + TunerDebug.getVideoFrameDrop(), + TunerDebug.getBytesInQueue(), + TunerDebug.getAudioPositionUs(), + TunerDebug.getAudioPositionUsRate(), + TunerDebug.getAudioPtsUs(), + TunerDebug.getAudioPtsUsRate(), + TunerDebug.getVideoPtsUs(), + TunerDebug.getVideoPtsUsRate()))); } - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - Log.i(TAG, "Notify weak signal due to signal check, " + String.format( - "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " + - "videoFrameDrop:%d", - (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, - bufferingTimeMs, - preparingTimeMs, - TunerDebug.getVideoFrameDrop() - )); - } else if (!isWeakSignal && mReportedWeakSignal) { - boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME - && currentTime - mReadyStartTimeMs - > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - if (!isPlaybackStable) { - // Wait until playback becomes stable. - } else if (mReportedDrawnToSurface) { - mHandler.removeMessages(MSG_RETRY_PLAYBACK); - notifyVideoAvailable(); - mPlayer.setAudioTrackAndClosedCaption(true); + mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); + long currentTime = SystemClock.elapsedRealtime(); + long bufferingTimeMs = + mBufferingStartTimeMs != INVALID_TIME + ? currentTime - mBufferingStartTimeMs + : mBufferingStartTimeMs; + long preparingTimeMs = + mPreparingStartTimeMs != INVALID_TIME + ? currentTime - mPreparingStartTimeMs + : mPreparingStartTimeMs; + boolean isBufferingTooLong = + bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; + boolean isPreparingTooLong = + preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; + boolean isWeakSignal = + source != null + && mChannel.getType() != Channel.TYPE_FILE + && (isBufferingTooLong || isPreparingTooLong); + if (isWeakSignal && !mReportedWeakSignal) { + if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)), + PLAYBACK_RETRY_DELAY_MS); + } + if (mPlayer != null) { + mPlayer.setAudioTrackAndClosedCaption(false); + } + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); + Log.i( + TAG, + "Notify weak signal due to signal check, " + + String.format( + "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " + + "videoFrameDrop:%d", + (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, + bufferingTimeMs, + preparingTimeMs, + TunerDebug.getVideoFrameDrop())); + } else if (!isWeakSignal && mReportedWeakSignal) { + boolean isPlaybackStable = + mReadyStartTimeMs != INVALID_TIME + && currentTime - mReadyStartTimeMs + > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; + if (!isPlaybackStable) { + // Wait until playback becomes stable. + } else if (mReportedDrawnToSurface) { + mHandler.removeMessages(MSG_RETRY_PLAYBACK); + notifyVideoAvailable(); + mPlayer.setAudioTrackAndClosedCaption(true); + } } + mLastLimitInBytes = limitInBytes; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); + return true; } - mLastLimitInBytes = limitInBytes; - mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); - return true; - } - case MSG_SET_SURFACE: { - if (mPlayer != null) { - mPlayer.setSurface(mSurface); - } else { - // TODO: Since surface is dynamically set, we can remove the dependency of - // playback start on mSurface nullity. - resetPlayback(); + case MSG_SET_SURFACE: + { + if (mPlayer != null) { + mPlayer.setSurface(mSurface); + } else { + // TODO: Since surface is dynamically set, we can remove the dependency of + // playback start on mSurface nullity. + resetPlayback(); + } + return true; + } + case MSG_NOTIFY_AUDIO_TRACK_UPDATED: + { + notifyAudioTracksUpdated(); + return true; + } + default: + { + Log.w(TAG, "Unhandled message code: " + msg.what); + return false; } - return true; - } - case MSG_NOTIFY_AUDIO_TRACK_UPDATED: { - notifyAudioTracksUpdated(); - return true; - } - default: { - Log.w(TAG, "Unhandled message code: " + msg.what); - return false; - } } } // Private methods private void doSelectTrack(int type, String trackId) { - int numTrackId = trackId != null - ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1; + int numTrackId = + trackId != null ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1; if (type == TvTrackInfo.TYPE_AUDIO) { if (trackId == null) { return; @@ -1138,14 +1193,13 @@ public class TunerSessionWorker implements PlaybackBufferListener, } private void setTrickplayEnabledIfNeeded() { - if (mChannel == null || - mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { + if (mChannel == null + || mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { return; } if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED; - TunerPreferences.setTrickplaySetting( - mContext, mTrickplaySetting); + TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting); } } @@ -1171,18 +1225,26 @@ public class TunerSessionWorker implements PlaybackBufferListener, StorageManager storageManager = new DvrStorageManager(new File(getRecordingPath()), false); bufferManager = new BufferManager(storageManager); - updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles()); + updateCaptionTracks(((DvrStorageManager) storageManager).readCaptionInfoFiles()); } else if (!mTrickplayDisabledByStorageIssue && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { - bufferManager = new BufferManager(new TrickplayStorageManager(mContext, - mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb)); + bufferManager = + new BufferManager( + new TrickplayStorageManager( + mContext, + mTrickplayBufferDir, + 1024L * 1024 * mMaxTrickplayBufferSizeMb)); } else { Log.w(TAG, "Trickplay is disabled."); } - MpegTsPlayer player = new MpegTsPlayer( - new MpegTsRendererBuilder(mContext, bufferManager, this), - mHandler, mSourceManager, capabilities, this); + MpegTsPlayer player = + new MpegTsPlayer( + new MpegTsRendererBuilder(mContext, bufferManager, this), + mHandler, + mSourceManager, + capabilities, + this); Log.i(TAG, "Passthrough AC3 renderer"); if (DEBUG) Log.d(TAG, "ExoPlayer created"); return player; @@ -1190,8 +1252,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void startCaptionTrack() { if (mCaptionEnabled && mCaptionTrack != null) { - mSession.sendUiMessage( - TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack); + mSession.sendUiMessage(TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack); if (mPlayer != null) { mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber); } @@ -1220,10 +1281,13 @@ public class TunerSessionWorker implements PlaybackBufferListener, } List audioTracks = tvTracksInterface.getAudioTracks(); List captionTracks = tvTracksInterface.getCaptionTracks(); - // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio - // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio + // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for + // audio + // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust + // audio // track info in PMT more and use info in EIT only when we have nothing. - if (audioTracks != null && !audioTracks.isEmpty() + if (audioTracks != null + && !audioTracks.isEmpty() && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) { updateAudioTracks(audioTracks); } @@ -1249,8 +1313,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void updateVideoTrack(int width, int height) { removeTvTracks(TvTrackInfo.TYPE_VIDEO); - mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID) - .setVideoWidth(width).setVideoHeight(height).build()); + mTvTracks.add( + new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID) + .setVideoWidth(width) + .setVideoHeight(height) + .build()); mSession.notifyTracksChanged(mTvTracks); mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID); } @@ -1284,16 +1351,22 @@ public class TunerSessionWorker implements PlaybackBufferListener, com.google.android.exoplayer.MediaFormat infoFromPlayer = mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i); AtscAudioTrack infoFromEit = mAudioTrackMap.get(i); - AtscAudioTrack infoFromVct = (mChannel != null - && mChannel.getAudioTracks().size() == mAudioTrackMap.size() - && i < mChannel.getAudioTracks().size()) - ? mChannel.getAudioTracks().get(i) : null; - String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language - : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language - : (infoFromVct != null && infoFromVct.language != null) - ? infoFromVct.language : null; - TvTrackInfo.Builder builder = new TvTrackInfo.Builder( - TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); + AtscAudioTrack infoFromVct = + (mChannel != null + && mChannel.getAudioTracks().size() == mAudioTrackMap.size() + && i < mChannel.getAudioTracks().size()) + ? mChannel.getAudioTracks().get(i) + : null; + String language = + !TextUtils.isEmpty(infoFromPlayer.language) + ? infoFromPlayer.language + : (infoFromEit != null && infoFromEit.language != null) + ? infoFromEit.language + : (infoFromVct != null && infoFromVct.language != null) + ? infoFromVct.language + : null; + TvTrackInfo.Builder builder = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); builder.setLanguage(language); builder.setAudioChannelCount(infoFromPlayer.channelCount); builder.setAudioSampleRate(infoFromPlayer.sampleRate); @@ -1319,7 +1392,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, // The service number of the caption service is used for track id of a subtitle. // Later, when a subtitle is chosen, track id will be passed on to TsParser. TvTrackInfo.Builder builder = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, + new TvTrackInfo.Builder( + TvTrackInfo.TYPE_SUBTITLE, SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber); builder.setLanguage(language); mTvTracks.add(builder.build()); @@ -1331,9 +1405,13 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void updateChannelInfo(TunerChannel channel) { if (DEBUG) { - Log.d(TAG, String.format("Channel Info (old) videoPid: %d audioPid: %d " + - "audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(), - mChannel.getAudioPids().size())); + Log.d( + TAG, + String.format( + "Channel Info (old) videoPid: %d audioPid: %d " + "audioSize: %d", + mChannel.getVideoPid(), + mChannel.getAudioPid(), + mChannel.getAudioPids().size())); } // The list of the audio tracks resided in a channel is often changed depending on a @@ -1356,19 +1434,22 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } mChannel.selectAudioTrack(index); - mSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, - index == -1 ? null : AUDIO_TRACK_PREFIX + index); + mSession.notifyTrackSelected( + TvTrackInfo.TYPE_AUDIO, index == -1 ? null : AUDIO_TRACK_PREFIX + index); // Reset playback if there is a change in the listening streaming PIDs. - if (oldVideoPid != mChannel.getVideoPid() - || oldAudioPid != mChannel.getAudioPid()) { + if (oldVideoPid != mChannel.getVideoPid() || oldAudioPid != mChannel.getAudioPid()) { // TODO: Implement a switching between tracks more smoothly. resetPlayback(); } if (DEBUG) { - Log.d(TAG, String.format("Channel Info (new) videoPid: %d audioPid: %d " + - " audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(), - mChannel.getAudioPids().size())); + Log.d( + TAG, + String.format( + "Channel Info (new) videoPid: %d audioPid: %d " + " audioSize: %d", + mChannel.getVideoPid(), + mChannel.getAudioPid(), + mChannel.getAudioPids().size())); } } @@ -1405,8 +1486,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); return; } - if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio()) - || (mChannel.hasVideo() && !mPlayer.hasVideo())) + if (mChannel != null + && ((mChannel.hasAudio() && !mPlayer.hasAudio()) + || (mChannel.hasVideo() && !mPlayer.hasVideo())) && mChannel.getType() != Channel.TYPE_NETWORK) { // If the channel is from network, skip this part since the video and audio tracks // information for channels from network are more reliable in the extractor. Otherwise, @@ -1441,8 +1523,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, MpegTsPlayer player = createPlayer(mAudioCapabilities); player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); player.setVideoEventListener(this); - player.setCaptionServiceNumber(mCaptionTrack != null ? - mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER); + player.setCaptionServiceNumber( + mCaptionTrack != null + ? mCaptionTrack.serviceNumber + : Cea708Data.EMPTY_SERVICE_NUMBER); if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) { mSourceManager.setKeepTuneStatus(false); player.release(); @@ -1451,8 +1535,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, // case, retry playback immediately may not help. notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); Log.i(TAG, "Notify weak signal due to player preparation failure"); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, - System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)), + PLAYBACK_RETRY_DELAY_MS); } } else { mPlayer = player; @@ -1500,8 +1586,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void doReschedulePrograms() { long currentPositionMs = getCurrentPosition(); - long forwardDifference = Math.abs(currentPositionMs - mLastPositionMs - - RESCHEDULE_PROGRAMS_INTERVAL_MS); + long forwardDifference = + Math.abs(currentPositionMs - mLastPositionMs - RESCHEDULE_PROGRAMS_INTERVAL_MS); mLastPositionMs = currentPositionMs; // A gap is measured as the time difference between previous and next current position @@ -1510,20 +1596,23 @@ public class TunerSessionWorker implements PlaybackBufferListener, // channel should be rescheduled to new playback timeline. if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) { if (DEBUG) { - Log.d(TAG, "reschedule programs size:" - + (mPrograms != null ? mPrograms.size() : 0) + " current program: " - + getCurrentProgram()); + Log.d( + TAG, + "reschedule programs size:" + + (mPrograms != null ? mPrograms.size() : 0) + + " current program: " + + getCurrentProgram()); } mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms)) .sendToTarget(); } mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS); - mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, - RESCHEDULE_PROGRAMS_INTERVAL_MS); + mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INTERVAL_MS); } private int getTrickPlaySeekIntervalMs() { - return Math.max(EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()), + return Math.max( + EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()), MIN_TRICKPLAY_SEEK_INTERVAL_MS); } @@ -1562,8 +1651,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS; } seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek; - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek); } private void doTimeShiftPause() { @@ -1605,17 +1694,21 @@ public class TunerSessionWorker implements PlaybackBufferListener, mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); mPlayer.setAudioTrackAndClosedCaption(false); mPlayer.startSmoothTrickplay(mPlaybackParams); - mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR, - TRICKPLAY_MONITOR_INTERVAL_MS); + mHandler.sendEmptyMessageDelayed( + MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS); } else { mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) { mPlayer.setAudioTrackAndClosedCaption(false); mPlayer.setPlayWhenReady(false); // Initiate trickplay - mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK, - (int) (mPlayer.getCurrentPosition() - + speed * getTrickPlaySeekIntervalMs()), 0)); + mHandler.sendMessage( + mHandler.obtainMessage( + MSG_TRICKPLAY_BY_SEEK, + (int) + (mPlayer.getCurrentPosition() + + speed * getTrickPlaySeekIntervalMs()), + 0)); } } } @@ -1627,8 +1720,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mChannel.getType() == Channel.TYPE_FILE) { // For the playback from the local file, we use the first one from the given program. EitItem first = mPrograms.get(0); - if (first != null && (mProgram == null - || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) { + if (first != null + && (mProgram == null + || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) { return first; } return null; @@ -1649,8 +1743,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked(); if (DEBUG) { if (blockContentRating != null) { - Log.d(TAG, "Check parental controls: blocked by content rating - " - + blockContentRating); + Log.d( + TAG, + "Check parental controls: blocked by content rating - " + + blockContentRating); } else { Log.d(TAG, "Check parental controls: available"); } @@ -1672,8 +1768,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, captionTrack.wideAspectRatio = false; captionTrack.easyReader = false; mCaptionTrackMap.put(serviceNumber, captionTrack); - mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, - SUBTITLE_TRACK_PREFIX + serviceNumber).build()); + mTvTracks.add( + new TvTrackInfo.Builder( + TvTrackInfo.TYPE_SUBTITLE, + SUBTITLE_TRACK_PREFIX + serviceNumber) + .build()); mSession.notifyTracksChanged(mTvTracks); } } @@ -1683,22 +1782,21 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (currentProgram == null) { return null; } - TvContentRating[] ratings = mTvContentRatingCache - .getRatings(currentProgram.getContentRating()); + TvContentRating[] ratings = + mTvContentRatingCache.getRatings(currentProgram.getContentRating()); if (ratings == null || ratings.length == 0) { ratings = new TvContentRating[] {TvContentRating.UNRATED}; } for (TvContentRating rating : ratings) { - if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager - .isRatingBlocked(rating)) { + if (!Objects.equals(mUnblockedContentRating, rating) + && mTvInputManager.isRatingBlocked(rating)) { return rating; } } return null; } - private void updateChannelBlockStatus(boolean channelBlocked, - TvContentRating contentRating) { + private void updateChannelBlockStatus(boolean channelBlocked, TvContentRating contentRating) { if (mChannelBlocked == channelBlocked) { return; } @@ -1715,8 +1813,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, clearCallbacksAndMessagesSafely(); resetPlayback(); mSession.notifyContentAllowed(); - mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, - RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); + mHandler.sendEmptyMessageDelayed( + MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); mHandler.removeMessages(MSG_CHECK_SIGNAL); mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS); } diff --git a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java index 6ad00daa..f014d568 100644 --- a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java +++ b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java @@ -25,11 +25,9 @@ import android.media.tv.TvContract; import android.net.Uri; import android.os.AsyncTask; import android.util.Log; - import com.android.tv.TvApplication; import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.util.Utils; - import java.io.File; import java.io.IOException; import java.util.HashSet; @@ -37,8 +35,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** - * Creates {@link JobService} to clean up recorded program files which are not referenced - * from database. + * Creates {@link JobService} to clean up recorded program files which are not referenced from + * database. */ public class TunerStorageCleanUpService extends JobService { private static final String TAG = "TunerStorageCleanUpService"; @@ -69,15 +67,15 @@ public class TunerStorageCleanUpService extends JobService { } /** - * Cleans up recorded program files which are not referenced from database. - * Cleaning up will be done periodically. + * Cleans up recorded program files which are not referenced from database. Cleaning up will be + * done periodically. */ public static class CleanUpStorageTask extends AsyncTask { - private final static String[] mProjection = { - TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI + private static final String[] mProjection = { + TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, + TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI }; - private final static long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1); + private static final long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1); private final Context mContext; private final DvrStorageStatusManager mDvrStorageStatusManager; @@ -99,8 +97,13 @@ public class TunerStorageCleanUpService extends JobService { } private Set getRecordedProgramsDirs() { - try (Cursor c = mContentResolver.query( - TvContract.RecordedPrograms.CONTENT_URI, mProjection, null, null, null)) { + try (Cursor c = + mContentResolver.query( + TvContract.RecordedPrograms.CONTENT_URI, + mProjection, + null, + null, + null)) { if (c == null) { return null; } @@ -113,7 +116,8 @@ public class TunerStorageCleanUpService extends JobService { } Uri dataUri = Uri.parse(dataUriString); if (!Utils.isInBundledPackageSet(packageName) - || dataUri == null || dataUri.getPath() == null + || dataUri == null + || dataUri.getPath() == null || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { continue; } @@ -150,8 +154,7 @@ public class TunerStorageCleanUpService extends JobService { if (!recordedProgramDirs.contains(recordingDir.getCanonicalPath())) { long lastModified = recordingDir.lastModified(); long now = System.currentTimeMillis(); - if (lastModified != 0 - && lastModified < now - ELAPSED_MILLIS_TO_DELETE) { + if (lastModified != 0 && lastModified < now - ELAPSED_MILLIS_TO_DELETE) { // To prevent current recordings from being deleted, // deletes recordings which was not modified for long enough time. Utils.deleteDirOrFile(recordingDir); diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java index 2725ddfc..a1596e3b 100644 --- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java +++ b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java @@ -23,22 +23,18 @@ import android.content.Context; import android.media.tv.TvContract; import android.media.tv.TvInputService; import android.util.Log; - -import com.google.android.exoplayer.audio.AudioCapabilities; -import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; import com.android.tv.TvApplication; import com.android.tv.common.feature.CommonFeatures; - +import com.google.android.exoplayer.audio.AudioCapabilities; +import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; -/** - * {@link TunerTvInputService} serves TV channels coming from a tuner device. - */ +/** {@link TunerTvInputService} serves TV channels coming from a tuner device. */ public class TunerTvInputService extends TvInputService - implements AudioCapabilitiesReceiver.Listener{ + implements AudioCapabilitiesReceiver.Listener { private static final String TAG = "TunerTvInputService"; private static final boolean DEBUG = false; @@ -70,9 +66,13 @@ public class TunerTvInputService extends TvInputService if (pendingJob != null) { // storage cleaning job is already scheduled. } else { - JobInfo job = new JobInfo.Builder(DVR_STORAGE_CLEANUP_JOB_ID, - new ComponentName(this, TunerStorageCleanUpService.class)) - .setPersisted(true).setPeriodic(TimeUnit.DAYS.toMillis(1)).build(); + JobInfo job = + new JobInfo.Builder( + DVR_STORAGE_CLEANUP_JOB_ID, + new ComponentName(this, TunerStorageCleanUpService.class)) + .setPersisted(true) + .setPeriodic(TimeUnit.DAYS.toMillis(1)) + .build(); jobScheduler.schedule(job); } } diff --git a/src/com/android/tv/tuner/util/ByteArrayBuffer.java b/src/com/android/tv/tuner/util/ByteArrayBuffer.java index da887e7d..c3e38443 100644 --- a/src/com/android/tv/tuner/util/ByteArrayBuffer.java +++ b/src/com/android/tv/tuner/util/ByteArrayBuffer.java @@ -31,9 +31,7 @@ package com.android.tv.tuner.util; -/** - * An expandable byte buffer built on byte array. - */ +/** An expandable byte buffer built on byte array. */ public final class ByteArrayBuffer { private byte[] buffer; @@ -57,8 +55,11 @@ public final class ByteArrayBuffer { if (b == null) { return; } - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) < 0) || ((off + len) > b.length)) { + if ((off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) < 0) + || ((off + len) > b.length)) { throw new IndexOutOfBoundsException(); } if (len == 0) { @@ -85,8 +86,11 @@ public final class ByteArrayBuffer { if (b == null) { return; } - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) < 0) || ((off + len) > b.length)) { + if ((off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) < 0) + || ((off + len) > b.length)) { throw new IndexOutOfBoundsException(); } if (len == 0) { @@ -145,5 +149,4 @@ public final class ByteArrayBuffer { public boolean isFull() { return this.len == this.buffer.length; } - } diff --git a/src/com/android/tv/tuner/util/ConvertUtils.java b/src/com/android/tv/tuner/util/ConvertUtils.java index abf18d8c..4b7fbdae 100644 --- a/src/com/android/tv/tuner/util/ConvertUtils.java +++ b/src/com/android/tv/tuner/util/ConvertUtils.java @@ -16,14 +16,12 @@ package com.android.tv.tuner.util; -/** - * Utility class for converting date and time. - */ +/** Utility class for converting date and time. */ public class ConvertUtils { // Time diff between 1.1.1970 00:00:00 and 6.1.1980 00:00:00 private static final long DIFF_BETWEEN_UNIX_EPOCH_AND_GPS = 315964800; - private ConvertUtils() { } + private ConvertUtils() {} public static long convertGPSTimeToUnixEpoch(long gpsTime) { return gpsTime + DIFF_BETWEEN_UNIX_EPOCH_AND_GPS; diff --git a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java b/src/com/android/tv/tuner/util/GlobalSettingsUtils.java index 0cefcbed..98463f3b 100644 --- a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java +++ b/src/com/android/tv/tuner/util/GlobalSettingsUtils.java @@ -19,16 +19,14 @@ package com.android.tv.tuner.util; import android.content.Context; import android.provider.Settings; -/** - * Utility class that get information of global settings. - */ +/** Utility class that get information of global settings. */ public class GlobalSettingsUtils { // Since global surround setting is hided, add the related variable here for checking surround // sound setting when the audio is unavailable. Remove this workaround after b/31254857 fixed. private static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output"; public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; - private GlobalSettingsUtils () { } + private GlobalSettingsUtils() {} public static int getEncodedSurroundOutputSettings(Context context) { return Settings.Global.getInt(context.getContentResolver(), ENCODED_SURROUND_OUTPUT, 0); diff --git a/src/com/android/tv/tuner/util/Ints.java b/src/com/android/tv/tuner/util/Ints.java index 0b1be426..74e0ca8d 100644 --- a/src/com/android/tv/tuner/util/Ints.java +++ b/src/com/android/tv/tuner/util/Ints.java @@ -3,9 +3,7 @@ package com.android.tv.tuner.util; import java.util.ArrayList; import java.util.List; -/** - * Static utility methods pertaining to int primitives. (Referred Guava's Ints class) - */ +/** Static utility methods pertaining to int primitives. (Referred Guava's Ints class) */ public class Ints { private Ints() {} diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/src/com/android/tv/tuner/util/PostalCodeUtils.java index 9eb689a7..502c5648 100644 --- a/src/com/android/tv/tuner/util/PostalCodeUtils.java +++ b/src/com/android/tv/tuner/util/PostalCodeUtils.java @@ -24,16 +24,13 @@ import android.text.TextUtils; import android.util.Log; import com.android.tv.tuner.TunerPreferences; import com.android.tv.util.LocationUtils; - import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; -/** - * A utility class to update, get, and set the last known postal or zip code. - */ +/** A utility class to update, get, and set the last known postal or zip code. */ public class PostalCodeUtils { private static final String TAG = "PostalCodeUtils"; @@ -100,8 +97,12 @@ public class PostalCodeUtils { private static String getPostalCode(Context context) throws IOException, SecurityException { Address address = LocationUtils.getCurrentAddress(context); if (address != null) { - Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", " - + address.getPostalCode()); + Log.i( + TAG, + "Current country and postal code is " + + address.getCountryName() + + ", " + + address.getPostalCode()); return address.getPostalCode(); } return null; @@ -109,8 +110,7 @@ public class PostalCodeUtils { /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ public static class NoPostalCodeException extends Exception { - public NoPostalCodeException() { - } + public NoPostalCodeException() {} } /** @@ -135,4 +135,4 @@ public class PostalCodeUtils { REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/tuner/util/StatusTextUtils.java b/src/com/android/tv/tuner/util/StatusTextUtils.java index 2633834b..84e2fc5a 100644 --- a/src/com/android/tv/tuner/util/StatusTextUtils.java +++ b/src/com/android/tv/tuner/util/StatusTextUtils.java @@ -18,9 +18,7 @@ package com.android.tv.tuner.util; import java.util.Locale; -/** - * Utility class for tuner status messages. - */ +/** Utility class for tuner status messages. */ public class StatusTextUtils { private static final int PACKETS_PER_SEC_YELLOW = 1500; private static final int PACKETS_PER_SEC_RED = 1000; @@ -31,18 +29,23 @@ public class StatusTextUtils { private static final String COLOR_GREEN = "green"; private static final String COLOR_GRAY = "gray"; - private StatusTextUtils() { } + private StatusTextUtils() {} /** * Returns tuner status warning message in HTML. * - *

This is only called for debuging and always shown in english.

+ *

This is only called for debuging and always shown in english. */ - public static String getStatusWarningInHTML(long packetsPerSec, - int videoFrameDrop, int bytesInQueue, - long audioPositionUs, long audioPositionUsRate, - long audioPtsUs, long audioPtsUsRate, - long videoPtsUs, long videoPtsUsRate) { + public static String getStatusWarningInHTML( + long packetsPerSec, + int videoFrameDrop, + int bytesInQueue, + long audioPositionUs, + long audioPositionUsRate, + long audioPtsUs, + long audioPtsUsRate, + long videoPtsUs, + long videoPtsUsRate) { StringBuffer buffer = new StringBuffer(); // audioPosition should go in rate of 1000ms. @@ -57,34 +60,49 @@ public class StatusTextUtils { } buffer.append(String.format(Locale.US, "", audioPositionColor)); buffer.append( - String.format(Locale.US, "audioPositionMs: %d (%d)
", audioPositionUs / 1000, + String.format( + Locale.US, + "audioPositionMs: %d (%d)
", + audioPositionUs / 1000, audioPositionMsRate)); buffer.append("
\n"); buffer.append(""); - buffer.append(String.format(Locale.US, "audioPtsMs: %d (%d, %d)
", audioPtsUs / 1000, - audioPtsUsRate / 1000, (audioPtsUs - audioPositionUs) / 1000)); - buffer.append(String.format(Locale.US, "videoPtsMs: %d (%d, %d)
", videoPtsUs / 1000, - videoPtsUsRate / 1000, (videoPtsUs - audioPositionUs) / 1000)); + buffer.append( + String.format( + Locale.US, + "audioPtsMs: %d (%d, %d)
", + audioPtsUs / 1000, + audioPtsUsRate / 1000, + (audioPtsUs - audioPositionUs) / 1000)); + buffer.append( + String.format( + Locale.US, + "videoPtsMs: %d (%d, %d)
", + videoPtsUs / 1000, + videoPtsUsRate / 1000, + (videoPtsUs - audioPositionUs) / 1000)); buffer.append("
\n"); appendStatusLine(buffer, "KbytesInQueue", bytesInQueue / 1000, 1, 10); buffer.append("
"); appendErrorStatusLine(buffer, "videoFrameDrop", videoFrameDrop, 0, 2); buffer.append("
"); - appendStatusLine(buffer, "packetsPerSec", packetsPerSec, PACKETS_PER_SEC_RED, + appendStatusLine( + buffer, + "packetsPerSec", + packetsPerSec, + PACKETS_PER_SEC_RED, PACKETS_PER_SEC_YELLOW); return buffer.toString(); } - /** - * Returns audio unavailable warning message in HTML. - */ + /** Returns audio unavailable warning message in HTML. */ public static String getAudioWarningInHTML(String msg) { return String.format("%s\n", COLOR_YELLOW, msg); } - private static void appendStatusLine(StringBuffer buffer, String factorName, long value, - int minRed, int minYellow) { + private static void appendStatusLine( + StringBuffer buffer, String factorName, long value, int minRed, int minYellow) { buffer.append(""); } - private static void appendErrorStatusLine(StringBuffer buffer, String factorName, int value, - int minGreen, int minYellow) { + private static void appendErrorStatusLine( + StringBuffer buffer, String factorName, int value, int minGreen, int minYellow) { buffer.append(" - * Once an app starts using additional window like SubPanel and it gets window focus, the - * {@link android.media.tv.TvView#setMain()} does not work because its implementation assumes that - * the app uses only application layer. - * TODO: remove this class once the TvView.setMain() is revisited. - *

+ * + *

Once an app starts using additional window like SubPanel and it gets window focus, the {@link + * android.media.tv.TvView#setMain()} does not work because its implementation assumes that the app + * uses only application layer. TODO: remove this class once the TvView.setMain() is revisited. */ public class AppLayerTvView extends TvView { public AppLayerTvView(Context context) { @@ -66,7 +63,7 @@ public class AppLayerTvView extends TvView { super.getLocationOnScreen(outLocation); // The TvView.MySessionCallback.onSessionCreated() will call this method indirectly. - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "AppLayerTvView.getLocationOnScreen, session created"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("AppLayerTvView.getLocationOnScreen, session created"); } } diff --git a/src/com/android/tv/ui/BlockScreenView.java b/src/com/android/tv/ui/BlockScreenView.java index 09c167ca..6b2d9a01 100644 --- a/src/com/android/tv/ui/BlockScreenView.java +++ b/src/com/android/tv/ui/BlockScreenView.java @@ -29,7 +29,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.ui.TunableTvView.BlockScreenType; @@ -62,10 +61,11 @@ public class BlockScreenView extends FrameLayout { public BlockScreenView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mSpacingNormal = getResources().getDimensionPixelOffset( - R.dimen.tvview_block_vertical_spacing); - mSpacingShrunken = getResources().getDimensionPixelOffset( - R.dimen.shrunken_tvview_block_vertical_spacing); + mSpacingNormal = + getResources().getDimensionPixelOffset(R.dimen.tvview_block_vertical_spacing); + mSpacingShrunken = + getResources() + .getDimensionPixelOffset(R.dimen.shrunken_tvview_block_vertical_spacing); } @Override @@ -78,66 +78,60 @@ public class BlockScreenView extends FrameLayout { mSpace = findViewById(R.id.space); mBlockingInfoTextView = (TextView) findViewById(R.id.block_screen_text); mBackgroundImageView = (ImageView) findViewById(R.id.background_image); - mFadeOut = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_out); + mFadeOut = + AnimatorInflater.loadAnimator( + getContext(), R.animator.tvview_block_screen_fade_out); mFadeOut.setTarget(this); - mFadeOut.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setVisibility(GONE); - setBackgroundImage(null); - setAlpha(1.0f); - } - }); - mInfoFadeIn = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_in); + mFadeOut.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(GONE); + setBackgroundImage(null); + setAlpha(1.0f); + } + }); + mInfoFadeIn = + AnimatorInflater.loadAnimator(getContext(), R.animator.tvview_block_screen_fade_in); mInfoFadeIn.setTarget(mContainerView); - mInfoFadeOut = AnimatorInflater.loadAnimator(getContext(), - R.animator.tvview_block_screen_fade_out); + mInfoFadeOut = + AnimatorInflater.loadAnimator( + getContext(), R.animator.tvview_block_screen_fade_out); mInfoFadeOut.setTarget(mContainerView); - mInfoFadeOut.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainerView.setVisibility(GONE); - } - }); + mInfoFadeOut.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mContainerView.setVisibility(GONE); + } + }); } - /** - * Sets the normal image. - */ + /** Sets the normal image. */ public void setIconImage(int resId) { mNormalLockIconView.setImageResource(resId); updateSpaceVisibility(); } - /** - * Sets the scale type of the normal image. - */ + /** Sets the scale type of the normal image. */ public void setIconScaleType(ScaleType scaleType) { mNormalLockIconView.setScaleType(scaleType); updateSpaceVisibility(); } - /** - * Show or hide the image of this view. - */ + /** Show or hide the image of this view. */ public void setIconVisibility(boolean visible) { mImageContainer.setVisibility(visible ? VISIBLE : GONE); updateSpaceVisibility(); } - /** - * Sets the text message. - */ + /** Sets the text message. */ public void setInfoText(int resId) { mBlockingInfoTextView.setText(resId); updateSpaceVisibility(); } - /** - * Sets the text message. - */ + /** Sets the text message. */ public void setInfoText(String text) { mBlockingInfoTextView.setText(text); updateSpaceVisibility(); @@ -175,19 +169,18 @@ public class BlockScreenView extends FrameLayout { } /** - * Changes the spacing between the image view and the text view according to the - * {@code blockScreenType}. + * Changes the spacing between the image view and the text view according to the {@code + * blockScreenType}. */ public void setSpacing(@BlockScreenType int blockScreenType) { mSpace.getLayoutParams().height = blockScreenType == TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW - ? mSpacingShrunken : mSpacingNormal; + ? mSpacingShrunken + : mSpacingNormal; requestLayout(); } - /** - * Changes the view layout according to the {@code blockScreenType}. - */ + /** Changes the view layout according to the {@code blockScreenType}. */ public void onBlockStatusChanged(@BlockScreenType int blockScreenType, boolean withAnimation) { if (!withAnimation) { switch (blockScreenType) { @@ -235,25 +228,19 @@ public class BlockScreenView extends FrameLayout { updateSpaceVisibility(); } - /** - * Adds a listener to the fade-in animation of info text and icons of the block screen. - */ + /** Adds a listener to the fade-in animation of info text and icons of the block screen. */ public void addInfoFadeInAnimationListener(AnimatorListener listener) { mInfoFadeIn.addListener(listener); } - /** - * Fades out the block screen. - */ + /** Fades out the block screen. */ public void fadeOut() { if (getVisibility() == VISIBLE && !mFadeOut.isStarted()) { mFadeOut.start(); } } - /** - * Ends the currently running animations. - */ + /** Ends the currently running animations. */ public void endAnimations() { if (mFadeOut != null && mFadeOut.isRunning()) { mFadeOut.end(); diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java index a5d897f2..c8c3dc86 100644 --- a/src/com/android/tv/ui/ChannelBannerView.java +++ b/src/com/android/tv/ui/ChannelBannerView.java @@ -45,7 +45,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; @@ -63,28 +62,23 @@ import com.android.tv.util.ImageLoader.ImageLoaderCallback; import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.Utils; -/** - * A view to render channel banner. - */ +/** A view to render channel banner. */ public class ChannelBannerView extends FrameLayout implements TvTransitionManager.TransitionLayout { private static final String TAG = "ChannelBannerView"; private static final boolean DEBUG = false; - /** - * Show all information at the channel banner. - */ + /** Show all information at the channel banner. */ public static final int LOCK_NONE = 0; /** - * Lock program details at the channel banner. - * This is used when a content is locked so we don't want to show program details - * including program description text and poster art. + * Lock program details at the channel banner. This is used when a content is locked so we don't + * want to show program details including program description text and poster art. */ public static final int LOCK_PROGRAM_DETAIL = 1; /** - * Lock channel information at the channel banner. - * This is used when a channel is locked so we only want to show input information. + * Lock channel information at the channel banner. This is used when a channel is locked so we + * only want to show input information. */ public static final int LOCK_CHANNEL_INFO = 2; @@ -134,18 +128,21 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private final Animator mProgramDescriptionFadeInAnimator; private final Animator mProgramDescriptionFadeOutAnimator; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - mCurrentHeight = 0; - mMainActivity.getOverlayManager().hideOverlays( - TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); - } - }; + private final Runnable mHideRunnable = + new Runnable() { + @Override + public void run() { + mCurrentHeight = 0; + mMainActivity + .getOverlayManager() + .hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + } + }; private final long mShowDurationMillis; private final int mChannelLogoImageViewWidth; private final int mChannelLogoImageViewHeight; @@ -157,22 +154,23 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private final int mRecordingIconPadding; private final Interpolator mResizeInterpolator; - private final AnimatorListenerAdapter mResizeAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mProgramInfoUpdatePendingByResizing = false; - } + private final AnimatorListenerAdapter mResizeAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + mProgramInfoUpdatePendingByResizing = false; + } - @Override - public void onAnimationEnd(Animator animator) { - mProgramDescriptionTextView.setAlpha(1f); - mResizeAnimator = null; - if (mProgramInfoUpdatePendingByResizing) { - mProgramInfoUpdatePendingByResizing = false; - updateProgramInfo(mLastUpdatedProgram); - } - } - }; + @Override + public void onAnimationEnd(Animator animator) { + mProgramDescriptionTextView.setAlpha(1f); + mResizeAnimator = null; + if (mProgramInfoUpdatePendingByResizing) { + mProgramInfoUpdatePendingByResizing = false; + updateProgramInfo(mLastUpdatedProgram); + } + } + }; public ChannelBannerView(Context context) { this(context, null); @@ -188,47 +186,52 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mMainActivity = (MainActivity) context; - mShowDurationMillis = mResources.getInteger( - R.integer.channel_banner_show_duration); - mChannelLogoImageViewWidth = mResources.getDimensionPixelSize( - R.dimen.channel_banner_channel_logo_width); - mChannelLogoImageViewHeight = mResources.getDimensionPixelSize( - R.dimen.channel_banner_channel_logo_height); - mChannelLogoImageViewMarginStart = mResources.getDimensionPixelSize( - R.dimen.channel_banner_channel_logo_margin_start); - mProgramDescriptionTextViewWidth = mResources.getDimensionPixelSize( - R.dimen.channel_banner_program_description_width); + mShowDurationMillis = mResources.getInteger(R.integer.channel_banner_show_duration); + mChannelLogoImageViewWidth = + mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_width); + mChannelLogoImageViewHeight = + mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_height); + mChannelLogoImageViewMarginStart = + mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_margin_start); + mProgramDescriptionTextViewWidth = + mResources.getDimensionPixelSize(R.dimen.channel_banner_program_description_width); mChannelBannerTextColor = mResources.getColor(R.color.channel_banner_text_color, null); - mChannelBannerDimTextColor = mResources.getColor(R.color.channel_banner_dim_text_color, - null); + mChannelBannerDimTextColor = + mResources.getColor(R.color.channel_banner_dim_text_color, null); mResizeAnimDuration = mResources.getInteger(R.integer.channel_banner_fast_anim_duration); - mRecordingIconPadding = mResources.getDimensionPixelOffset( - R.dimen.channel_banner_recording_icon_padding); + mRecordingIconPadding = + mResources.getDimensionPixelOffset(R.dimen.channel_banner_recording_icon_padding); - mResizeInterpolator = AnimationUtils.loadInterpolator(context, - android.R.interpolator.linear_out_slow_in); + mResizeInterpolator = + AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); - mProgramDescriptionFadeInAnimator = AnimatorInflater.loadAnimator(mMainActivity, - R.animator.channel_banner_program_description_fade_in); - mProgramDescriptionFadeOutAnimator = AnimatorInflater.loadAnimator(mMainActivity, - R.animator.channel_banner_program_description_fade_out); + mProgramDescriptionFadeInAnimator = + AnimatorInflater.loadAnimator( + mMainActivity, R.animator.channel_banner_program_description_fade_in); + mProgramDescriptionFadeOutAnimator = + AnimatorInflater.loadAnimator( + mMainActivity, R.animator.channel_banner_program_description_fade_out); if (CommonFeatures.DVR.isEnabled(mMainActivity)) { mDvrManager = TvApplication.getSingletons(mMainActivity).getDvrManager(); } else { mDvrManager = null; } - mContentRatingsManager = TvApplication.getSingletons(getContext()) - .getTvInputManagerHelper().getContentRatingsManager(); - - mNoProgram = new Program.Builder() - .setTitle(context.getString(R.string.channel_banner_no_title)) - .setDescription(EMPTY_STRING) - .build(); - mLockedChannelProgram = new Program.Builder() - .setTitle(context.getString(R.string.channel_banner_locked_channel_title)) - .setDescription(EMPTY_STRING) - .build(); + mContentRatingsManager = + TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper() + .getContentRatingsManager(); + + mNoProgram = + new Program.Builder() + .setTitle(context.getString(R.string.channel_banner_no_title)) + .setDescription(EMPTY_STRING) + .build(); + mLockedChannelProgram = + new Program.Builder() + .setTitle(context.getString(R.string.channel_banner_locked_channel_title)) + .setDescription(EMPTY_STRING) + .build(); if (sClosedCaptionMark == null) { sClosedCaptionMark = context.getString(R.string.closed_caption); } @@ -260,12 +263,13 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mProgramDescriptionFadeInAnimator.setTarget(mProgramDescriptionTextView); mProgramDescriptionFadeOutAnimator.setTarget(mProgramDescriptionTextView); - mProgramDescriptionFadeOutAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mProgramDescriptionTextView.setText(mProgramDescriptionText); - } - }); + mProgramDescriptionFadeOutAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mProgramDescriptionTextView.setText(mProgramDescriptionText); + } + }); } @Override @@ -308,7 +312,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage * @throws IllegalArgumentException if lockType is invalid. */ public int setLockType(int lockType) { - if (lockType != LOCK_NONE && lockType != LOCK_CHANNEL_INFO + if (lockType != LOCK_NONE + && lockType != LOCK_CHANNEL_INFO && lockType != LOCK_PROGRAM_DETAIL) { throw new IllegalArgumentException("No such lock type " + lockType); } @@ -330,7 +335,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage * Update channel banner view. * * @param updateOnTune {@false} denotes the channel banner is updated due to other reasons than - * tuning. The channel info will not be updated in this case. + * tuning. The channel info will not be updated in this case. */ public void updateViews(boolean updateOnTune) { resetAnimationEffects(); @@ -359,14 +364,18 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage public void updateStreamInfo(StreamInfo info) { // Update stream information in a channel. if (mLockType != LOCK_CHANNEL_INFO && info != null) { - updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark - : EMPTY_STRING); - updateText(mAspectRatioTextView, + updateText( + mClosedCaptionTextView, + info.hasClosedCaption() ? sClosedCaptionMark : EMPTY_STRING); + updateText( + mAspectRatioTextView, Utils.getAspectRatioString(info.getVideoDisplayAspectRatio())); - updateText(mResolutionTextView, + updateText( + mResolutionTextView, Utils.getVideoDefinitionLevelString( mMainActivity, info.getVideoDefinitionLevel())); - updateText(mAudioChannelTextView, + updateText( + mAudioChannelTextView, Utils.getAudioChannelString(mMainActivity, info.getAudioChannelCount())); } else { // Channel change has been requested. But, StreamInfo hasn't been updated yet. @@ -415,9 +424,11 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } mChannelNumberTextView.setText(displayNumber); mChannelNameTextView.setText(displayName); - TvInputInfo info = mMainActivity.getTvInputManagerHelper().getTvInputInfo( - getCurrentInputId()); - if (info == null || !ImageLoader.loadBitmap(createTvInputLogoLoaderCallback(info, this), + TvInputInfo info = + mMainActivity.getTvInputManagerHelper().getTvInputInfo(getCurrentInputId()); + if (info == null + || !ImageLoader.loadBitmap( + createTvInputLogoLoaderCallback(info, this), new LoadTvInputLogoTask(getContext(), ImageCache.getInstance(), info))) { mTvInputLogoImageView.setVisibility(View.GONE); mTvInputLogoImageView.setImageDrawable(null); @@ -425,8 +436,11 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mChannelLogoImageView.setImageBitmap(null); mChannelLogoImageView.setVisibility(View.GONE); if (mCurrentChannel != null && mCurrentChannelLogoExists) { - mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, - mChannelLogoImageViewWidth, mChannelLogoImageViewHeight, + mCurrentChannel.loadBitmap( + getContext(), + Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mChannelLogoImageViewWidth, + mChannelLogoImageViewHeight, createChannelLogoCallback(this, mCurrentChannel)); } } @@ -446,7 +460,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage return new ImageLoaderCallback(channelBannerView) { @Override public void onBitmapLoaded(ChannelBannerView channelBannerView, Bitmap bitmap) { - if (bitmap != null && channelBannerView.mCurrentChannel != null + if (bitmap != null + && channelBannerView.mCurrentChannel != null && info.getId().equals(channelBannerView.mCurrentChannel.getInputId())) { channelBannerView.updateTvInputLogo(bitmap); } @@ -524,8 +539,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (mLastUpdatedProgram == null || !TextUtils.equals(program.getTitle(), mLastUpdatedProgram.getTitle()) - || !TextUtils.equals(program.getEpisodeDisplayTitle(getContext()), - mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) { + || !TextUtils.equals( + program.getEpisodeDisplayTitle(getContext()), + mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) { updateProgramTextView(program); } updateProgramTimeInfo(program); @@ -548,8 +564,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mProgramDescriptionText = program.getDescription(); } String description = mProgramDescriptionTextView.getText().toString(); - boolean programDescriptionNeedFadeAnimation = (isProgramChanged - || !description.equals(mProgramDescriptionText)) && !mUpdateOnTune; + boolean programDescriptionNeedFadeAnimation = + (isProgramChanged || !description.equals(mProgramDescriptionText)) + && !mUpdateOnTune; updateBannerHeight(programDescriptionNeedFadeAnimation); } else { mProgramInfoUpdatePendingByResizing = true; @@ -561,19 +578,21 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (program == null) { return; } - updateProgramTextView(program == mLockedChannelProgram, program.getTitle(), + updateProgramTextView( + program == mLockedChannelProgram, + program.getTitle(), program.getEpisodeDisplayTitle(getContext())); } - private void updateProgramTextView(boolean dimText, String title, - String episodeDisplayTitle) { + private void updateProgramTextView(boolean dimText, String title, String episodeDisplayTitle) { mProgramTextView.setVisibility(View.VISIBLE); if (dimText) { mProgramTextView.setTextColor(mChannelBannerDimTextColor); } else { mProgramTextView.setTextColor(mChannelBannerTextColor); } - updateTextView(mProgramTextView, + updateTextView( + mProgramTextView, R.dimen.channel_banner_program_large_text_size, R.dimen.channel_banner_program_large_margin_top); if (TextUtils.isEmpty(episodeDisplayTitle)) { @@ -582,18 +601,24 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage String fullTitle = title + " " + episodeDisplayTitle; SpannableString text = new SpannableString(fullTitle); - text.setSpan(new TextAppearanceSpan(getContext(), - R.style.text_appearance_channel_banner_episode_title), - fullTitle.length() - episodeDisplayTitle.length(), fullTitle.length(), + text.setSpan( + new TextAppearanceSpan( + getContext(), R.style.text_appearance_channel_banner_episode_title), + fullTitle.length() - episodeDisplayTitle.length(), + fullTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mProgramTextView.setText(text); } - int width = mProgramDescriptionTextViewWidth + (mCurrentChannelLogoExists ? - 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart); + int width = + mProgramDescriptionTextViewWidth + + (mCurrentChannelLogoExists + ? 0 + : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart); ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams(); lp.width = width; mProgramTextView.setLayoutParams(lp); - mProgramTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + mProgramTextView.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); boolean oneline = (mProgramTextView.getLineCount() == 1); @@ -602,13 +627,16 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mProgramTextView, R.dimen.channel_banner_program_medium_text_size, R.dimen.channel_banner_program_medium_margin_top); - mProgramTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + mProgramTextView.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); oneline = (mProgramTextView.getLineCount() == 1); } - updateTopMargin(mAnchorView, oneline - ? R.dimen.channel_banner_anchor_one_line_y - : R.dimen.channel_banner_anchor_two_line_y); + updateTopMargin( + mAnchorView, + oneline + ? R.dimen.channel_banner_anchor_one_line_y + : R.dimen.channel_banner_anchor_two_line_y); } private void updateProgramRatings(Program program) { @@ -650,8 +678,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage if (mLockType != LOCK_CHANNEL_INFO && durationMs > 0 && startTimeMs > 0) { mProgramTimeTextView.setVisibility(View.VISIBLE); mRemainingTimeView.setVisibility(View.VISIBLE); - mProgramTimeTextView.setText(Utils.getDurationString( - getContext(), startTimeMs, endTimeMs, true)); + mProgramTimeTextView.setText( + Utils.getDurationString(getContext(), startTimeMs, endTimeMs, true)); } else { mProgramTimeTextView.setVisibility(View.GONE); mRemainingTimeView.setVisibility(View.GONE); @@ -673,8 +701,10 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage updateProgressBarAndRecIcon(program, null); return; } - ScheduledRecording currentRecording = (mCurrentChannel == null) ? null - : mDvrManager.getCurrentRecording(mCurrentChannel.getId()); + ScheduledRecording currentRecording = + (mCurrentChannel == null) + ? null + : mDvrManager.getCurrentRecording(mCurrentChannel.getId()); if (DEBUG) { Log.d(TAG, currentRecording == null ? "No Recording" : "Recording:" + currentRecording); } @@ -685,22 +715,23 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage } } - private void updateProgressBarAndRecIcon(Program program, - @Nullable ScheduledRecording recording) { + private void updateProgressBarAndRecIcon( + Program program, @Nullable ScheduledRecording recording) { long programStartTime = program.getStartTimeUtcMillis(); long programEndTime = program.getEndTimeUtcMillis(); long currentPosition = mMainActivity.getCurrentPlayingPosition(); updateRecordingIndicator(recording); if (recording != null) { // Recording now. Use recording-style progress bar. - mRemainingTimeView.setProgress(getProgressPercent(recording.getStartTimeMs(), - programStartTime, programEndTime)); - mRemainingTimeView.setSecondaryProgress(getProgressPercent(currentPosition, - programStartTime, programEndTime)); + mRemainingTimeView.setProgress( + getProgressPercent( + recording.getStartTimeMs(), programStartTime, programEndTime)); + mRemainingTimeView.setSecondaryProgress( + getProgressPercent(currentPosition, programStartTime, programEndTime)); } else { // No recording is going now. Recover progress bar. - mRemainingTimeView.setProgress(getProgressPercent(currentPosition, - programStartTime, programEndTime)); + mRemainingTimeView.setProgress( + getProgressPercent(currentPosition, programStartTime, programEndTime)); mRemainingTimeView.setSecondaryProgress(0); } } @@ -708,9 +739,15 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private void updateRecordingIndicator(@Nullable ScheduledRecording recording) { if (recording != null) { if (mRemainingTimeView.getVisibility() == View.GONE) { - mRecordingIndicatorView.setText(mMainActivity.getResources().getString( - R.string.dvr_recording_till_format, DateUtils.formatDateTime(mMainActivity, - recording.getEndTimeMs(), DateUtils.FORMAT_SHOW_TIME))); + mRecordingIndicatorView.setText( + mMainActivity + .getResources() + .getString( + R.string.dvr_recording_till_format, + DateUtils.formatDateTime( + mMainActivity, + recording.getEndTimeMs(), + DateUtils.FORMAT_SHOW_TIME))); mRecordingIndicatorView.setCompoundDrawablePadding(mRecordingIconPadding); } else { mRecordingIndicatorView.setText(""); @@ -725,10 +762,10 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private boolean isCurrentProgram(ScheduledRecording recording, Program program) { long currentPosition = mMainActivity.getCurrentPlayingPosition(); return (recording.getType() == ScheduledRecording.TYPE_PROGRAM - && recording.getProgramId() == program.getId()) + && recording.getProgramId() == program.getId()) || (recording.getType() == ScheduledRecording.TYPE_TIMED - && currentPosition >= recording.getStartTimeMs() - && currentPosition <= recording.getEndTimeMs()); + && currentPosition >= recording.getStartTimeMs() + && currentPosition <= recording.getEndTimeMs()); } private void setLastUpdatedProgram(Program program) { @@ -764,18 +801,20 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage private Animator createResizeAnimator(int targetHeight, boolean addFadeAnimation) { final ValueAnimator heightAnimator = ValueAnimator.ofInt(mCurrentHeight, targetHeight); - heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - LayoutParams layoutParams = (LayoutParams) ChannelBannerView.this.getLayoutParams(); - if (value != layoutParams.height) { - layoutParams.height = value; - ChannelBannerView.this.setLayoutParams(layoutParams); - } - mCurrentHeight = value; - } - }); + heightAnimator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + LayoutParams layoutParams = + (LayoutParams) ChannelBannerView.this.getLayoutParams(); + if (value != layoutParams.height) { + layoutParams.height = value; + ChannelBannerView.this.setLayoutParams(layoutParams); + } + mCurrentHeight = value; + } + }); heightAnimator.setDuration(mResizeAnimDuration); heightAnimator.setInterpolator(mResizeInterpolator); @@ -792,4 +831,4 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage animator.addListener(mResizeAnimatorListener); return animator; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/DialogUtils.java b/src/com/android/tv/ui/DialogUtils.java index acbaf8c8..341db2e1 100644 --- a/src/com/android/tv/ui/DialogUtils.java +++ b/src/com/android/tv/ui/DialogUtils.java @@ -20,7 +20,6 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; - import com.android.tv.common.SoftPreconditions; public final class DialogUtils { @@ -31,31 +30,28 @@ public final class DialogUtils { * @param itemResIds String resource id for each item * @param runnables Runnable for each item */ - public static void showListDialog(Context context, int[] itemResIds, - final Runnable[] runnables) { + public static void showListDialog( + Context context, int[] itemResIds, final Runnable[] runnables) { int size = itemResIds.length; SoftPreconditions.checkState(size == runnables.length); - DialogInterface.OnClickListener onClickListener - = new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, int which) { - Runnable runnable = runnables[which]; - if (runnable != null) { - runnable.run(); - } - dialog.dismiss(); - } - }; + DialogInterface.OnClickListener onClickListener = + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int which) { + Runnable runnable = runnables[which]; + if (runnable != null) { + runnable.run(); + } + dialog.dismiss(); + } + }; CharSequence[] items = new CharSequence[itemResIds.length]; Resources res = context.getResources(); for (int i = 0; i < size; ++i) { items[i] = res.getString(itemResIds[i]); } - new AlertDialog.Builder(context) - .setItems(items, onClickListener) - .create() - .show(); + new AlertDialog.Builder(context).setItems(items, onClickListener).create().show(); } - private DialogUtils() { } + private DialogUtils() {} } diff --git a/src/com/android/tv/ui/FullscreenDialogView.java b/src/com/android/tv/ui/FullscreenDialogView.java index e2220722..800fa85a 100644 --- a/src/com/android/tv/ui/FullscreenDialogView.java +++ b/src/com/android/tv/ui/FullscreenDialogView.java @@ -26,7 +26,6 @@ import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.dialog.FullscreenDialogFragment; @@ -58,41 +57,39 @@ public class FullscreenDialogView extends FrameLayout public FullscreenDialogView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mLinearOutSlowIn = AnimationUtils.loadInterpolator(context, - android.R.interpolator.linear_out_slow_in); - mFastOutLinearIn = AnimationUtils.loadInterpolator(context, - android.R.interpolator.fast_out_linear_in); - getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - startEnterAnimation(); - } - }); + mLinearOutSlowIn = + AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); + mFastOutLinearIn = + AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in); + getViewTreeObserver() + .addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + startEnterAnimation(); + } + }); } protected MainActivity getActivity() { return mActivity; } - /** - * Gets the host {@link Dialog}. - */ + /** Gets the host {@link Dialog}. */ protected Dialog getDialog() { return mDialog; } - /** - * Dismisses the host {@link Dialog}. - */ + /** Dismisses the host {@link Dialog}. */ protected void dismiss() { - startExitAnimation(new Runnable() { - @Override - public void run() { - mDialog.dismiss(); - } - }); + startExitAnimation( + new Runnable() { + @Override + public void run() { + mDialog.dismiss(); + } + }); } @Override @@ -102,51 +99,48 @@ public class FullscreenDialogView extends FrameLayout } @Override - public void onBackPressed() { } + public void onBackPressed() {} @Override - public void onDestroy() { } + public void onDestroy() {} - /** - * Transitions to another view inside the host {@link Dialog}. - */ + /** Transitions to another view inside the host {@link Dialog}. */ public void transitionTo(final FullscreenDialogView v) { mSkipExitAlphaAnimation = true; v.mSkipEnterAlphaAnimation = true; v.initialize(mActivity, mDialog); - startExitAnimation(new Runnable() { - @Override - public void run() { - new Handler().postDelayed(new Runnable() { + startExitAnimation( + new Runnable() { @Override public void run() { - v.initialize(getActivity(), getDialog()); - getDialog().setContentView(v); + new Handler() + .postDelayed( + new Runnable() { + @Override + public void run() { + v.initialize(getActivity(), getDialog()); + getDialog().setContentView(v); + } + }, + TRANSITION_INTERVAL_MS); } - }, TRANSITION_INTERVAL_MS); - } - }); + }); } - /** - * Called when an enter animation starts. Sub-view specific animation can be implemented. - */ - protected void onStartEnterAnimation(TimeInterpolator interpolator, long duration) { - } + /** Called when an enter animation starts. Sub-view specific animation can be implemented. */ + protected void onStartEnterAnimation(TimeInterpolator interpolator, long duration) {} - /** - * Called when an exit animation starts. Sub-view specific animation can be implemented. - */ - protected void onStartExitAnimation(TimeInterpolator interpolator, long duration, - Runnable onAnimationEnded) { - } + /** Called when an exit animation starts. Sub-view specific animation can be implemented. */ + protected void onStartExitAnimation( + TimeInterpolator interpolator, long duration, Runnable onAnimationEnded) {} private void startEnterAnimation() { if (DEBUG) Log.d(TAG, "start an enter animation"); View backgroundView = findViewById(R.id.background); if (!mSkipEnterAlphaAnimation) { backgroundView.setAlpha(0); - backgroundView.animate() + backgroundView + .animate() .alpha(1.0f) .setInterpolator(mLinearOutSlowIn) .setDuration(FADE_IN_DURATION_MS) @@ -160,7 +154,8 @@ public class FullscreenDialogView extends FrameLayout if (DEBUG) Log.d(TAG, "start an exit animation"); View backgroundView = findViewById(R.id.background); if (!mSkipExitAlphaAnimation) { - backgroundView.animate() + backgroundView + .animate() .alpha(0.0f) .setInterpolator(mFastOutLinearIn) .setDuration(FADE_OUT_DURATION_MS) diff --git a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java index 39ec1279..9685b04b 100644 --- a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java +++ b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java @@ -20,17 +20,13 @@ import android.content.Context; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.GuidedActionsStylist; - import com.android.tv.R; -/** - * Extended stylist class used for {@link GuidedStepFragment} with divider support. - */ +/** Extended stylist class used for {@link GuidedStepFragment} with divider support. */ public class GuidedActionsStylistWithDivider extends GuidedActionsStylist { - /** - * ID used mark a divider. - */ + /** ID used mark a divider. */ public static final int ACTION_DIVIDER = -100; + private static final int VIEW_TYPE_DIVIDER = 1; @Override @@ -50,8 +46,8 @@ public class GuidedActionsStylistWithDivider extends GuidedActionsStylist { } /** - * Creates a divider for {@link GuidedStepFragment}, targeted fragments must use - * {@link GuidedActionsStylistWithDivider} as its actions' stylist for divider to work. + * Creates a divider for {@link GuidedStepFragment}, targeted fragments must use {@link + * GuidedActionsStylistWithDivider} as its actions' stylist for divider to work. */ public static GuidedAction createDividerAction(Context context) { return new GuidedAction.Builder(context) diff --git a/src/com/android/tv/ui/InputBannerView.java b/src/com/android/tv/ui/InputBannerView.java index 4e254c62..17bfcab7 100644 --- a/src/com/android/tv/ui/InputBannerView.java +++ b/src/com/android/tv/ui/InputBannerView.java @@ -23,7 +23,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.data.Channel; @@ -31,17 +30,20 @@ import com.android.tv.data.Channel; public class InputBannerView extends LinearLayout implements TvTransitionManager.TransitionLayout { private final long mShowDurationMillis; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - ((MainActivity) getContext()).getOverlayManager().hideOverlays( - TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); - } - }; + private final Runnable mHideRunnable = + new Runnable() { + @Override + public void run() { + ((MainActivity) getContext()) + .getOverlayManager() + .hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + } + }; private TextView mInputLabelTextView; private TextView mSecondaryInputLabelTextView; @@ -56,8 +58,8 @@ public class InputBannerView extends LinearLayout implements TvTransitionManager public InputBannerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mShowDurationMillis = context.getResources().getInteger( - R.integer.select_input_show_duration); + mShowDurationMillis = + context.getResources().getInteger(R.integer.select_input_show_duration); } @Override @@ -73,8 +75,8 @@ public class InputBannerView extends LinearLayout implements TvTransitionManager if (channel == null || !channel.isPassthrough()) { return; } - TvInputInfo input = mainActivity.getTvInputManagerHelper().getTvInputInfo( - channel.getInputId()); + TvInputInfo input = + mainActivity.getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); CharSequence customLabel = input.loadCustomLabel(getContext()); CharSequence label = input.loadLabel(getContext()); if (TextUtils.isEmpty(customLabel) || customLabel.equals(label)) { diff --git a/src/com/android/tv/ui/IntroView.java b/src/com/android/tv/ui/IntroView.java index 7530f283..be9fb691 100644 --- a/src/com/android/tv/ui/IntroView.java +++ b/src/com/android/tv/ui/IntroView.java @@ -22,7 +22,6 @@ import android.graphics.drawable.AnimationDrawable; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; - import com.android.tv.R; import com.android.tv.menu.Menu; @@ -95,20 +94,21 @@ public class IntroView extends FullscreenDialogView { } @Override - protected void onStartExitAnimation(TimeInterpolator interpolator, long duration, - final Runnable onAnimationEnded) { + protected void onStartExitAnimation( + TimeInterpolator interpolator, long duration, final Runnable onAnimationEnded) { View v = findViewById(R.id.container); v.animate() .alpha(0.0f) .setInterpolator(interpolator) .setDuration(duration) .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - onAnimationEnded.run(); - } - }) + .withEndAction( + new Runnable() { + @Override + public void run() { + onAnimationEnded.run(); + } + }) .start(); } } diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java index ac5d841d..0c3613f6 100644 --- a/src/com/android/tv/ui/KeypadChannelSwitchView.java +++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java @@ -35,21 +35,19 @@ import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; - +import com.android.tv.util.DurationTimer; import java.util.ArrayList; import java.util.List; -public class KeypadChannelSwitchView extends LinearLayout implements - TvTransitionManager.TransitionLayout { +public class KeypadChannelSwitchView extends LinearLayout + implements TvTransitionManager.TransitionLayout { private static final String TAG = "KeypadChannelSwitchView"; private static final int MAX_CHANNEL_NUMBER_DIGIT = 4; @@ -62,7 +60,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements private final Tracker mTracker; private final DurationTimer mViewDurationTimer = new DurationTimer(); private boolean mNavigated = false; - @Nullable //Once mChannels is set to null it should not be used again. + @Nullable // Once mChannels is set to null it should not be used again. private List mChannels; private TextView mChannelNumberView; private ListView mChannelItemListView; @@ -72,23 +70,29 @@ public class KeypadChannelSwitchView extends LinearLayout implements private final LayoutInflater mLayoutInflater; private Channel mSelectedChannel; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - mCurrentHeight = 0; - if (mSelectedChannel != null) { - mMainActivity.tuneToChannel(mSelectedChannel); - mTracker.sendChannelNumberItemChosenByTimeout(); - } else { - mMainActivity.getOverlayManager().hideOverlays( - TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); - } - } - }; + private final Runnable mHideRunnable = + new Runnable() { + @Override + public void run() { + mCurrentHeight = 0; + if (mSelectedChannel != null) { + mMainActivity.tuneToChannel(mSelectedChannel); + mTracker.sendChannelNumberItemChosenByTimeout(); + } else { + mMainActivity + .getOverlayManager() + .hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU + | TvOverlayManager + .FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + } + } + }; private final long mShowDurationMillis; private final long mRippleAnimDurationMillis; private final int mBaseViewHeight; @@ -116,61 +120,67 @@ public class KeypadChannelSwitchView extends LinearLayout implements Resources resources = getResources(); mLayoutInflater = LayoutInflater.from(context); mShowDurationMillis = resources.getInteger(R.integer.keypad_channel_switch_show_duration); - mRippleAnimDurationMillis = resources.getInteger( - R.integer.keypad_channel_switch_ripple_anim_duration); - mBaseViewHeight = resources.getDimensionPixelSize( - R.dimen.keypad_channel_switch_base_height); + mRippleAnimDurationMillis = + resources.getInteger(R.integer.keypad_channel_switch_ripple_anim_duration); + mBaseViewHeight = + resources.getDimensionPixelSize(R.dimen.keypad_channel_switch_base_height); mItemHeight = resources.getDimensionPixelSize(R.dimen.keypad_channel_switch_item_height); mResizeAnimDuration = resources.getInteger(R.integer.keypad_channel_switch_anim_duration); - mResizeInterpolator = AnimationUtils.loadInterpolator(context, - android.R.interpolator.linear_out_slow_in); + mResizeInterpolator = + AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); } @Override - protected void onFinishInflate(){ + protected void onFinishInflate() { super.onFinishInflate(); mChannelNumberView = (TextView) findViewById(R.id.channel_number); mChannelItemListView = (ListView) findViewById(R.id.channel_list); mChannelItemListView.setAdapter(mAdapter); - mChannelItemListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (position >= mAdapter.getCount()) { - // It can happen during closing. - return; - } - mChannelItemListView.setFocusable(false); - final Channel channel = ((Channel) mAdapter.getItem(position)); - postDelayed(new Runnable() { + mChannelItemListView.setOnItemClickListener( + new AdapterView.OnItemClickListener() { @Override - public void run() { - mChannelItemListView.setFocusable(true); - mMainActivity.tuneToChannel(channel); - mTracker.sendChannelNumberItemClicked(); + public void onItemClick( + AdapterView parent, View view, int position, long id) { + if (position >= mAdapter.getCount()) { + // It can happen during closing. + return; + } + mChannelItemListView.setFocusable(false); + final Channel channel = ((Channel) mAdapter.getItem(position)); + postDelayed( + new Runnable() { + @Override + public void run() { + mChannelItemListView.setFocusable(true); + mMainActivity.tuneToChannel(channel); + mTracker.sendChannelNumberItemClicked(); + } + }, + mRippleAnimDurationMillis); + } + }); + mChannelItemListView.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView parent, View view, int position, long id) { + if (position >= mAdapter.getCount()) { + // It can happen during closing. + mSelectedChannel = null; + } else { + mSelectedChannel = (Channel) mAdapter.getItem(position); + } + if (position != 0 && !mNavigated) { + mNavigated = true; + mTracker.sendChannelInputNavigated(); + } } - }, mRippleAnimDurationMillis); - } - }); - mChannelItemListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (position >= mAdapter.getCount()) { - // It can happen during closing. - mSelectedChannel = null; - } else { - mSelectedChannel = (Channel) mAdapter.getItem(position); - } - if (position != 0 && !mNavigated) { - mNavigated = true; - mTracker.sendChannelInputNavigated(); - } - } - @Override - public void onNothingSelected(AdapterView parent) { - mSelectedChannel = null; - } - }); + @Override + public void onNothingSelected(AdapterView parent) { + mSelectedChannel = null; + } + }); } @Override @@ -276,8 +286,13 @@ public class KeypadChannelSwitchView extends LinearLayout implements for (Channel channel : mChannels) { ChannelNumber chNumber = ChannelNumber.parseChannelNumber(channel.getDisplayNumber()); if (chNumber == null) { - Log.i(TAG, "Malformed channel number (name=" + channel.getDisplayName() - + ", number=" + channel.getDisplayNumber() + ")"); + Log.i( + TAG, + "Malformed channel number (name=" + + channel.getDisplayName() + + ", number=" + + channel.getDisplayNumber() + + ")"); continue; } if (matchChannelNumber(mTypedChannelNumber, chNumber)) { @@ -286,7 +301,8 @@ public class KeypadChannelSwitchView extends LinearLayout implements // Even if a user doesn't type '-', we need to match the typed number to not only // the major number but also the minor number. For example, when a user types '111' // without delimiter, it should be matched to '111', '1-11' and '11-1'. - if (channel.getDisplayNumber().replaceAll(CHANNEL_DELIMITERS_REGEX, "") + if (channel.getDisplayNumber() + .replaceAll(CHANNEL_DELIMITERS_REGEX, "") .startsWith(mTypedChannelNumber.majorNumber)) { secondaryChannelCandidates.add(channel); } @@ -315,7 +331,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements // Do not add the resize animation when the banner has not been shown before. mCurrentHeight = targetHeight; setViewHeight(this, targetHeight); - } else if (mCurrentHeight != targetHeight){ + } else if (mCurrentHeight != targetHeight) { mResizeAnimator = createResizeAnimator(targetHeight); mResizeAnimator.start(); } @@ -323,21 +339,23 @@ public class KeypadChannelSwitchView extends LinearLayout implements private Animator createResizeAnimator(int targetHeight) { ValueAnimator animator = ValueAnimator.ofInt(mCurrentHeight, targetHeight); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - setViewHeight(KeypadChannelSwitchView.this, value); - mCurrentHeight = value; - } - }); + animator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + setViewHeight(KeypadChannelSwitchView.this, value); + mCurrentHeight = value; + } + }); animator.setDuration(mResizeAnimDuration); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mResizeAnimator = null; - } - }); + animator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mResizeAnimator = null; + } + }); animator.setInterpolator(mResizeInterpolator); return animator; } diff --git a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java index 63ee199d..9b916afe 100644 --- a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java +++ b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java @@ -21,18 +21,15 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.util.Log; import android.view.KeyEvent; import android.view.View; - import com.android.tv.common.WeakHandler; -/** - * Listener to make focus change faster over time. - */ +/** Listener to make focus change faster over time. */ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInterceptListener { private static final String TAG = "OnRepeatedKeyListener"; private static final boolean DEBUG = false; - private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = { 2000, 5000 }; - private static final int[] MAX_SKIPPED_VIEW_COUNT = { 1, 4 }; + private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = {2000, 5000}; + private static final int[] MAX_SKIPPED_VIEW_COUNT = {1, 4}; private static final int MSG_MOVE_FOCUS = 1000; private final VerticalGridView mView; @@ -52,21 +49,20 @@ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInt @Override public boolean onInterceptKeyEvent(KeyEvent event) { mHandler.removeMessages(MSG_MOVE_FOCUS); - if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP && - event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) { + if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP + && event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) { return false; } long duration = event.getEventTime() - event.getDownTime(); - if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0] - || event.isCanceled()) { + if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0] || event.isCanceled()) { mFocusAccelerated = false; return false; } - mDirection = event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP - : View.FOCUS_DOWN; + mDirection = + event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP : View.FOCUS_DOWN; int skippedViewCount = MAX_SKIPPED_VIEW_COUNT[0]; - for (int i = 1 ;i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) { + for (int i = 1; i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) { if (THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[i] < duration) { skippedViewCount = MAX_SKIPPED_VIEW_COUNT[i]; } else { @@ -83,8 +79,8 @@ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInt mFocusAccelerated = false; } for (int i = 0; i < skippedViewCount; ++i) { - mHandler.sendEmptyMessageDelayed(MSG_MOVE_FOCUS, - mRepeatedKeyInterval * i / (skippedViewCount + 1)); + mHandler.sendEmptyMessageDelayed( + MSG_MOVE_FOCUS, mRepeatedKeyInterval * i / (skippedViewCount + 1)); } if (DEBUG) Log.d(TAG, "onInterceptKeyEvent: focused view " + mView.findFocus()); return false; diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java index dc92111c..aa91aa50 100644 --- a/src/com/android/tv/ui/SelectInputView.java +++ b/src/com/android/tv/ui/SelectInputView.java @@ -32,23 +32,21 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.util.DurationTimer; import com.android.tv.analytics.Tracker; import com.android.tv.data.Channel; +import com.android.tv.util.DurationTimer; import com.android.tv.util.TvInputManagerHelper; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -public class SelectInputView extends VerticalGridView implements - TvTransitionManager.TransitionLayout { +public class SelectInputView extends VerticalGridView + implements TvTransitionManager.TransitionLayout { private static final String TAG = "SelectInputView"; private static final boolean DEBUG = false; public static final String SCREEN_NAME = "Input selection"; @@ -59,66 +57,68 @@ public class SelectInputView extends VerticalGridView implements private final TvInputManagerHelper.HardwareInputComparator mComparator; private final Tracker mTracker; private final DurationTimer mViewDurationTimer = new DurationTimer(); - private final TvInputCallback mTvInputCallback = new TvInputCallback() { - @Override - public void onInputAdded(String inputId) { - buildInputListAndNotify(); - updateSelectedPositionIfNeeded(); - } + private final TvInputCallback mTvInputCallback = + new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + buildInputListAndNotify(); + updateSelectedPositionIfNeeded(); + } - @Override - public void onInputRemoved(String inputId) { - buildInputListAndNotify(); - updateSelectedPositionIfNeeded(); - } + @Override + public void onInputRemoved(String inputId) { + buildInputListAndNotify(); + updateSelectedPositionIfNeeded(); + } - @Override - public void onInputUpdated(String inputId) { - buildInputListAndNotify(); - updateSelectedPositionIfNeeded(); - } + @Override + public void onInputUpdated(String inputId) { + buildInputListAndNotify(); + updateSelectedPositionIfNeeded(); + } - @Override - public void onInputStateChanged(String inputId, int state) { - buildInputListAndNotify(); - updateSelectedPositionIfNeeded(); - } + @Override + public void onInputStateChanged(String inputId, int state) { + buildInputListAndNotify(); + updateSelectedPositionIfNeeded(); + } - private void updateSelectedPositionIfNeeded() { - if (!isFocusable() || mSelectedInput == null) { - return; - } - if (!isInputEnabled(mSelectedInput)) { - setSelectedPosition(TUNER_INPUT_POSITION); - return; - } - if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) { - setSelectedPosition(getInputPosition(mSelectedInput.getId())); - } - } - }; + private void updateSelectedPositionIfNeeded() { + if (!isFocusable() || mSelectedInput == null) { + return; + } + if (!isInputEnabled(mSelectedInput)) { + setSelectedPosition(TUNER_INPUT_POSITION); + return; + } + if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) { + setSelectedPosition(getInputPosition(mSelectedInput.getId())); + } + } + }; private Channel mCurrentChannel; private OnInputSelectedCallback mCallback; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - if (mSelectedInput == null) { - return; - } - // TODO: pass english label to tracker http://b/22355024 - final String label = mSelectedInput.loadLabel(getContext()).toString(); - mTracker.sendInputSelected(label); - if (mCallback != null) { - if (mSelectedInput.isPassthroughInput()) { - mCallback.onPassthroughInputSelected(mSelectedInput); - } else { - mCallback.onTunerInputSelected(); + private final Runnable mHideRunnable = + new Runnable() { + @Override + public void run() { + if (mSelectedInput == null) { + return; + } + // TODO: pass english label to tracker http://b/22355024 + final String label = mSelectedInput.loadLabel(getContext()).toString(); + mTracker.sendInputSelected(label); + if (mCallback != null) { + if (mSelectedInput.isPassthroughInput()) { + mCallback.onPassthroughInputSelected(mSelectedInput); + } else { + mCallback.onTunerInputSelected(); + } + } } - } - } - }; + }; private final int mInputItemHeight; private final long mShowDurationMillis; @@ -153,14 +153,14 @@ public class SelectInputView extends VerticalGridView implements Resources resources = context.getResources(); mInputItemHeight = resources.getDimensionPixelSize(R.dimen.input_banner_item_height); mShowDurationMillis = resources.getInteger(R.integer.select_input_show_duration); - mRippleAnimDurationMillis = resources.getInteger( - R.integer.select_input_ripple_anim_duration); + mRippleAnimDurationMillis = + resources.getInteger(R.integer.select_input_ripple_anim_duration); mTextColorPrimary = resources.getColor(R.color.select_input_text_color_primary, null); mTextColorSecondary = resources.getColor(R.color.select_input_text_color_secondary, null); mTextColorDisabled = resources.getColor(R.color.select_input_text_color_disabled, null); - mItemViewForMeasure = LayoutInflater.from(context).inflate( - R.layout.select_input_item, this, false); + mItemViewForMeasure = + LayoutInflater.from(context).inflate(R.layout.select_input_item, this, false); buildInputListAndNotify(); } @@ -199,8 +199,10 @@ public class SelectInputView extends VerticalGridView implements mResetTransitionAlpha = fromEmptyScene; buildInputListAndNotify(); mTvInputManagerHelper.addCallback(mTvInputCallback); - String currentInputId = mCurrentChannel != null && mCurrentChannel.isPassthrough() ? - mCurrentChannel.getInputId() : null; + String currentInputId = + mCurrentChannel != null && mCurrentChannel.isPassthrough() + ? mCurrentChannel.getInputId() + : null; if (currentInputId != null && !isInputEnabled(mTvInputManagerHelper.getTvInputInfo(currentInputId))) { // If current input is disabled, the tuner input will be focused. @@ -233,7 +235,8 @@ public class SelectInputView extends VerticalGridView implements @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = mInputItemHeight * mInputList.size(); - super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxItemWidth, MeasureSpec.EXACTLY), + super.onMeasure( + MeasureSpec.makeMeasureSpec(mMaxItemWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } @@ -299,16 +302,14 @@ public class SelectInputView extends VerticalGridView implements != TvInputManager.INPUT_STATE_DISCONNECTED; } - /** - * Sets a callback which receives the notifications of input selection. - */ + /** Sets a callback which receives the notifications of input selection. */ public void setOnInputSelectedCallback(OnInputSelectedCallback callback) { mCallback = callback; } /** - * Sets the current channel. The initial selection will be the input which contains the - * {@code channel}. + * Sets the current channel. The initial selection will be the input which contains the {@code + * channel}. */ public void setCurrentChannel(Channel channel) { mCurrentChannel = channel; @@ -317,8 +318,9 @@ public class SelectInputView extends VerticalGridView implements class InputListAdapter extends RecyclerView.Adapter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = LayoutInflater.from(parent.getContext()).inflate( - R.layout.select_input_item, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_input_item, parent, false); return new ViewHolder(v); } @@ -343,25 +345,29 @@ public class SelectInputView extends VerticalGridView implements holder.secondaryInputLabelView.setVisibility(View.GONE); } - holder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mSelectedInput = mInputList.get(position); - // The user made a selection. Hide this view after the ripple animation. But - // first, disable focus to avoid any further focus change during the animation. - setFocusable(false); - removeCallbacks(mHideRunnable); - postDelayed(mHideRunnable, mRippleAnimDurationMillis); - } - }); - holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (hasFocus) { - mSelectedInput = mInputList.get(position); - } - } - }); + holder.itemView.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + mSelectedInput = mInputList.get(position); + // The user made a selection. Hide this view after the ripple animation. + // But + // first, disable focus to avoid any further focus change during the + // animation. + setFocusable(false); + removeCallbacks(mHideRunnable); + postDelayed(mHideRunnable, mRippleAnimDurationMillis); + } + }); + holder.itemView.setOnFocusChangeListener( + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) { + mSelectedInput = mInputList.get(position); + } + } + }); if (mResetTransitionAlpha) { ViewUtils.setTransitionAlpha(holder.itemView, 1f); @@ -385,18 +391,12 @@ public class SelectInputView extends VerticalGridView implements } } - /** - * A callback interface for the input selection. - */ + /** A callback interface for the input selection. */ public interface OnInputSelectedCallback { - /** - * Called when the tuner input is selected. - */ + /** Called when the tuner input is selected. */ void onTunerInputSelected(); - /** - * Called when the passthrough input is selected. - */ + /** Called when the passthrough input is selected. */ void onPassthroughInputSelected(@NonNull TvInputInfo input); } } diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index 48386698..97f7c65c 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -57,32 +57,30 @@ import android.view.SurfaceView; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; - import com.android.tv.ApplicationSingletons; import com.android.tv.Features; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.TvViewSession; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.data.Program; -import com.android.tv.data.ProgramDataManager; -import com.android.tv.parental.ParentalControlSettings; -import com.android.tv.util.DurationTimer; -import com.android.tv.util.Debug; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; import com.android.tv.common.feature.CommonFeatures; 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.parental.ContentRatingsManager; +import com.android.tv.parental.ParentalControlSettings; import com.android.tv.recommendation.NotificationService; +import com.android.tv.util.Debug; +import com.android.tv.util.DurationTimer; import com.android.tv.util.ImageLoader; import com.android.tv.util.NetworkUtils; import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -99,6 +97,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Retention(RetentionPolicy.SOURCE) @IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL}) public @interface BlockScreenType {} + public static final int BLOCK_SCREEN_TYPE_NO_UI = 0; public static final int BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW = 1; public static final int BLOCK_SCREEN_TYPE_NORMAL = 2; @@ -107,9 +106,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo { "com.android.tv.permission.RECEIVE_INPUT_EVENT"; @Retention(RetentionPolicy.SOURCE) - @IntDef({ TIME_SHIFT_STATE_NONE, TIME_SHIFT_STATE_PLAY, TIME_SHIFT_STATE_PAUSE, - TIME_SHIFT_STATE_REWIND, TIME_SHIFT_STATE_FAST_FORWARD }) + @IntDef({ + TIME_SHIFT_STATE_NONE, + TIME_SHIFT_STATE_PLAY, + TIME_SHIFT_STATE_PAUSE, + TIME_SHIFT_STATE_REWIND, + TIME_SHIFT_STATE_FAST_FORWARD + }) private @interface TimeShiftState {} + private static final int TIME_SHIFT_STATE_NONE = 0; private static final int TIME_SHIFT_STATE_PLAY = 1; private static final int TIME_SHIFT_STATE_PAUSE = 2; @@ -128,8 +133,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private ContentRatingsManager mContentRatingsManager; private ParentalControlSettings mParentalControlSettings; private ProgramDataManager mProgramDataManager; - @Nullable - private WatchedHistoryManager mWatchedHistoryManager; + @Nullable private WatchedHistoryManager mWatchedHistoryManager; private boolean mStarted; private String mTagetInputId; private TvInputInfo mInputInfo; @@ -182,230 +186,257 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private final ConnectivityManager mConnectivityManager; private final InputSessionManager mInputSessionManager; - private final TvInputCallback mCallback = new TvInputCallback() { - @Override - public void onConnectionFailed(String inputId) { - Log.w(TAG, "Failed to bind an input"); - mTracker.sendInputConnectionFailure(inputId); - Channel channel = mCurrentChannel; - mCurrentChannel = null; - mInputInfo = null; - mCanReceiveInputEvent = false; - if (mOnTuneListener != null) { - // If tune is called inside onTuneFailed, mOnTuneListener will be set to - // a new instance. In order to avoid to clear the new mOnTuneListener, - // we copy mOnTuneListener to l and clear mOnTuneListener before - // calling onTuneFailed. - OnTuneListener listener = mOnTuneListener; - mOnTuneListener = null; - listener.onTuneFailed(channel); - } - } - - @Override - public void onDisconnected(String inputId) { - Log.w(TAG, "Session is released by crash"); - mTracker.sendInputDisconnected(inputId); - Channel channel = mCurrentChannel; - mCurrentChannel = null; - mInputInfo = null; - mCanReceiveInputEvent = false; - if (mOnTuneListener != null) { - OnTuneListener listener = mOnTuneListener; - mOnTuneListener = null; - listener.onUnexpectedStop(channel); - } - } - - @Override - public void onChannelRetuned(String inputId, Uri channelUri) { - if (DEBUG) { - Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri=" - + channelUri + ")"); - } - if (mOnTuneListener != null) { - mOnTuneListener.onChannelRetuned(channelUri); - } - } + private final TvInputCallback mCallback = + new TvInputCallback() { + @Override + public void onConnectionFailed(String inputId) { + Log.w(TAG, "Failed to bind an input"); + mTracker.sendInputConnectionFailure(inputId); + Channel channel = mCurrentChannel; + mCurrentChannel = null; + mInputInfo = null; + mCanReceiveInputEvent = false; + if (mOnTuneListener != null) { + // If tune is called inside onTuneFailed, mOnTuneListener will be set to + // a new instance. In order to avoid to clear the new mOnTuneListener, + // we copy mOnTuneListener to l and clear mOnTuneListener before + // calling onTuneFailed. + OnTuneListener listener = mOnTuneListener; + mOnTuneListener = null; + listener.onTuneFailed(channel); + } + } - @Override - public void onTracksChanged(String inputId, List tracks) { - mHasClosedCaption = false; - for (TvTrackInfo track : tracks) { - if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) { - mHasClosedCaption = true; - break; + @Override + public void onDisconnected(String inputId) { + Log.w(TAG, "Session is released by crash"); + mTracker.sendInputDisconnected(inputId); + Channel channel = mCurrentChannel; + mCurrentChannel = null; + mInputInfo = null; + mCanReceiveInputEvent = false; + if (mOnTuneListener != null) { + OnTuneListener listener = mOnTuneListener; + mOnTuneListener = null; + listener.onUnexpectedStop(channel); + } } - } - if (mOnTuneListener != null) { - mOnTuneListener.onStreamInfoChanged(TunableTvView.this); - } - } - @Override - public void onTrackSelected(String inputId, int type, String trackId) { - if (trackId == null) { - // A track is unselected. - if (type == TvTrackInfo.TYPE_VIDEO) { - mVideoWidth = 0; - mVideoHeight = 0; - mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; - mVideoFrameRate = 0f; - mVideoDisplayAspectRatio = 0f; - } else if (type == TvTrackInfo.TYPE_AUDIO) { - mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; + @Override + public void onChannelRetuned(String inputId, Uri channelUri) { + if (DEBUG) { + Log.d( + TAG, + "onChannelRetuned(inputId=" + + inputId + + ", channelUri=" + + channelUri + + ")"); + } + if (mOnTuneListener != null) { + mOnTuneListener.onChannelRetuned(channelUri); + } } - } else { - List tracks = getTracks(type); - boolean trackFound = false; - if (tracks != null) { + + @Override + public void onTracksChanged(String inputId, List tracks) { + mHasClosedCaption = false; for (TvTrackInfo track : tracks) { - if (track.getId().equals(trackId)) { - if (type == TvTrackInfo.TYPE_VIDEO) { - mVideoWidth = track.getVideoWidth(); - mVideoHeight = track.getVideoHeight(); - mVideoFormat = Utils.getVideoDefinitionLevelFromSize( - mVideoWidth, mVideoHeight); - mVideoFrameRate = track.getVideoFrameRate(); - if (mVideoWidth <= 0 || mVideoHeight <= 0) { - mVideoDisplayAspectRatio = 0.0f; - } else { - float VideoPixelAspectRatio = - track.getVideoPixelAspectRatio(); - mVideoDisplayAspectRatio = VideoPixelAspectRatio - * mVideoWidth / mVideoHeight; - } - } else if (type == TvTrackInfo.TYPE_AUDIO) { - mAudioChannelCount = track.getAudioChannelCount(); - } - trackFound = true; + if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) { + mHasClosedCaption = true; break; } } + if (mOnTuneListener != null) { + mOnTuneListener.onStreamInfoChanged(TunableTvView.this); + } } - if (!trackFound) { - Log.w(TAG, "Invalid track ID: " + trackId); + + @Override + public void onTrackSelected(String inputId, int type, String trackId) { + if (trackId == null) { + // A track is unselected. + if (type == TvTrackInfo.TYPE_VIDEO) { + mVideoWidth = 0; + mVideoHeight = 0; + mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; + mVideoFrameRate = 0f; + mVideoDisplayAspectRatio = 0f; + } else if (type == TvTrackInfo.TYPE_AUDIO) { + mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; + } + } else { + List tracks = getTracks(type); + boolean trackFound = false; + if (tracks != null) { + for (TvTrackInfo track : tracks) { + if (track.getId().equals(trackId)) { + if (type == TvTrackInfo.TYPE_VIDEO) { + mVideoWidth = track.getVideoWidth(); + mVideoHeight = track.getVideoHeight(); + mVideoFormat = + Utils.getVideoDefinitionLevelFromSize( + mVideoWidth, mVideoHeight); + mVideoFrameRate = track.getVideoFrameRate(); + if (mVideoWidth <= 0 || mVideoHeight <= 0) { + mVideoDisplayAspectRatio = 0.0f; + } else { + float VideoPixelAspectRatio = + track.getVideoPixelAspectRatio(); + mVideoDisplayAspectRatio = + VideoPixelAspectRatio + * mVideoWidth + / mVideoHeight; + } + } else if (type == TvTrackInfo.TYPE_AUDIO) { + mAudioChannelCount = track.getAudioChannelCount(); + } + trackFound = true; + break; + } + } + } + if (!trackFound) { + Log.w(TAG, "Invalid track ID: " + trackId); + } + } + if (mOnTuneListener != null) { + mOnTuneListener.onStreamInfoChanged(TunableTvView.this); + } } - } - if (mOnTuneListener != null) { - mOnTuneListener.onStreamInfoChanged(TunableTvView.this); - } - } - @Override - public void onVideoAvailable(String inputId) { - if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}"); - Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start up of Live TV ends," + - " TunableTvView.onVideoAvailable resets timer"); - long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset(); - Debug.removeTimer(Debug.TAG_START_UP_TIMER); - if (BuildConfig.ENG && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) { - showAlertDialogForLongStartUp(); - } - mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE; - updateBlockScreenAndMuting(); - if (mOnTuneListener != null) { - mOnTuneListener.onStreamInfoChanged(TunableTvView.this); - } - } + @Override + public void onVideoAvailable(String inputId) { + if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}"); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log( + "Start up of Live TV ends," + + " TunableTvView.onVideoAvailable resets timer"); + long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset(); + Debug.removeTimer(Debug.TAG_START_UP_TIMER); + if (BuildConfig.ENG + && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) { + showAlertDialogForLongStartUp(); + } + mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE; + updateBlockScreenAndMuting(); + if (mOnTuneListener != null) { + mOnTuneListener.onStreamInfoChanged(TunableTvView.this); + } + } - private void showAlertDialogForLongStartUp() { - new AlertDialog.Builder(getContext()).setTitle( - getContext().getString(R.string.settings_send_feedback)) - .setMessage("Because the start up time of Live channels is too long," + - " please send feedback") - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - Intent intent = new Intent(Intent.ACTION_APP_ERROR); - ApplicationErrorReport report = new ApplicationErrorReport(); - report.packageName = report.processName = getContext() - .getApplicationContext().getPackageName(); - report.time = System.currentTimeMillis(); - report.type = ApplicationErrorReport.TYPE_CRASH; - - // Add the crash info to add title of feedback automatically. - ApplicationErrorReport.CrashInfo crash = new - ApplicationErrorReport.CrashInfo(); - crash.exceptionClassName = - "Live TV start up takes long time"; - crash.exceptionMessage = - "The start up time of Live TV is too long"; - report.crashInfo = crash; - - intent.putExtra(Intent.EXTRA_BUG_REPORT, report); - getContext().startActivity(intent); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); - } + private void showAlertDialogForLongStartUp() { + new AlertDialog.Builder(getContext()) + .setTitle(getContext().getString(R.string.settings_send_feedback)) + .setMessage( + "Because the start up time of Live channels is too long," + + " please send feedback") + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick( + DialogInterface dialogInterface, int i) { + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + ApplicationErrorReport report = + new ApplicationErrorReport(); + report.packageName = + report.processName = + getContext() + .getApplicationContext() + .getPackageName(); + report.time = System.currentTimeMillis(); + report.type = ApplicationErrorReport.TYPE_CRASH; + + // Add the crash info to add title of feedback + // automatically. + ApplicationErrorReport.CrashInfo crash = + new ApplicationErrorReport.CrashInfo(); + crash.exceptionClassName = + "Live TV start up takes long time"; + crash.exceptionMessage = + "The start up time of Live TV is too long"; + report.crashInfo = crash; + + intent.putExtra(Intent.EXTRA_BUG_REPORT, report); + getContext().startActivity(intent); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } - @Override - public void onVideoUnavailable(String inputId, int reason) { - if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING - && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "TunableTvView.onVideoUnAvailable reason = (" + reason - + ") and removes timer"); - Debug.removeTimer(Debug.TAG_START_UP_TIMER); - } else { - Debug.getTimer(Debug.TAG_START_UP_TIMER).log( - "TunableTvView.onVideoUnAvailable reason = (" + reason + ")"); - } - mVideoUnavailableReason = reason; - if (closePipIfNeeded()) { - return; - } - updateBlockScreenAndMuting(); - if (mOnTuneListener != null) { - mOnTuneListener.onStreamInfoChanged(TunableTvView.this); - } - switch (reason) { - case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: - case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: - case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: - mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason); - default: - // do nothing - } - } + @Override + public void onVideoUnavailable(String inputId, int reason) { + if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING + && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log( + "TunableTvView.onVideoUnAvailable reason = (" + + reason + + ") and removes timer"); + Debug.removeTimer(Debug.TAG_START_UP_TIMER); + } else { + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("TunableTvView.onVideoUnAvailable reason = (" + reason + ")"); + } + mVideoUnavailableReason = reason; + if (closePipIfNeeded()) { + return; + } + updateBlockScreenAndMuting(); + if (mOnTuneListener != null) { + mOnTuneListener.onStreamInfoChanged(TunableTvView.this); + } + switch (reason) { + case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: + case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: + case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: + mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason); + default: + // do nothing + } + } - @Override - public void onContentAllowed(String inputId) { - mBlockedContentRating = null; - updateBlockScreenAndMuting(); - if (mOnTuneListener != null) { - mOnTuneListener.onContentAllowed(); - } - } + @Override + public void onContentAllowed(String inputId) { + mBlockedContentRating = null; + updateBlockScreenAndMuting(); + if (mOnTuneListener != null) { + mOnTuneListener.onContentAllowed(); + } + } - @Override - public void onContentBlocked(String inputId, TvContentRating rating) { - if (rating != null && rating.equals(mBlockedContentRating)) { - return; - } - mBlockedContentRating = rating; - if (closePipIfNeeded()) { - return; - } - updateBlockScreenAndMuting(); - if (mOnTuneListener != null) { - mOnTuneListener.onContentBlocked(); - } - } + @Override + public void onContentBlocked(String inputId, TvContentRating rating) { + if (rating != null && rating.equals(mBlockedContentRating)) { + return; + } + mBlockedContentRating = rating; + if (closePipIfNeeded()) { + return; + } + updateBlockScreenAndMuting(); + if (mOnTuneListener != null) { + mOnTuneListener.onContentBlocked(); + } + } - @Override - public void onTimeShiftStatusChanged(String inputId, int status) { - if (DEBUG) { - Log.d(TAG, "onTimeShiftStatusChanged: {inputId=" + inputId + ", status=" + status + - "}"); - } - boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE; - setTimeShiftAvailable(available); - } - }; + @Override + public void onTimeShiftStatusChanged(String inputId, int status) { + if (DEBUG) { + Log.d( + TAG, + "onTimeShiftStatusChanged: {inputId=" + + inputId + + ", status=" + + status + + "}"); + } + boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE; + setTimeShiftAvailable(available); + } + }; public TunableTvView(Context context) { this(context, null); @@ -430,42 +461,46 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mInputSessionManager = null; } mInputManager = appSingletons.getTvInputManagerHelper(); - mConnectivityManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context); mTracker = appSingletons.getTracker(); mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL; mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen); - mBlockScreenView.addInfoFadeInAnimationListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - adjustBlockScreenSpacingAndText(); - } - }); + mBlockScreenView.addInfoFadeInAnimationListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + adjustBlockScreenSpacingAndText(); + } + }); mBufferingSpinnerView = findViewById(R.id.buffering_spinner); - mTuningImageColorFilter = getResources() - .getColor(R.color.tvview_block_image_color_filter, null); + mTuningImageColorFilter = + getResources().getColor(R.color.tvview_block_image_color_filter, null); mDimScreenView = findViewById(R.id.dim_screen); - mDimScreenView.animate().setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mActionAfterFade != null) { - mActionAfterFade.run(); - } - } + mDimScreenView + .animate() + .setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mActionAfterFade != null) { + mActionAfterFade.run(); + } + } - @Override - public void onAnimationCancel(Animator animation) { - if (mActionAfterFade != null) { - mActionAfterFade.run(); - } - } - }); + @Override + public void onAnimationCancel(Animator animation) { + if (mActionAfterFade != null) { + mActionAfterFade.run(); + } + } + }); } - public void initialize(ProgramDataManager programDataManager, - TvInputManagerHelper tvInputManagerHelper) { + public void initialize( + ProgramDataManager programDataManager, TvInputManagerHelper tvInputManagerHelper) { mTvView = (AppLayerTvView) findViewById(R.id.tv_view); mProgramDataManager = programDataManager; mInputManagerHelper = tvInputManagerHelper; @@ -482,9 +517,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mStarted = true; } - /** - * Warms up the input to reduce the start time. - */ + /** Warms up the input to reduce the start time. */ public void warmUpInput(String inputId, Uri channelUri) { if (!mStarted && inputId != null && channelUri != null) { if (mTvViewSession != null) { @@ -506,16 +539,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo { long duration = mChannelViewTimer.reset(); mTracker.sendChannelViewStop(mCurrentChannel, duration); if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) { - mWatchedHistoryManager.logChannelViewStop(mCurrentChannel, - System.currentTimeMillis(), duration); + mWatchedHistoryManager.logChannelViewStop( + mCurrentChannel, System.currentTimeMillis(), duration); } } reset(); } - /** - * Releases the resources. - */ + /** Releases the resources. */ public void release() { if (mInputSessionManager != null) { mInputSessionManager.releaseTvViewSession(mTvViewSession); @@ -523,18 +554,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } } - /** - * Resets TV view. - */ + /** Resets TV view. */ public void reset() { resetInternal(); mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED; updateBlockScreenAndMuting(); } - /** - * Resets TV view to acquire the recording session. - */ + /** Resets TV view to acquire the recording session. */ public void resetByRecording() { resetInternal(); } @@ -560,9 +587,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mWatchedHistoryManager = watchedHistoryManager; } - /** - * Sets if the TunableTvView is under shrunken. - */ + /** Sets if the TunableTvView is under shrunken. */ public void setIsUnderShrunken(boolean isUnderShrunken) { mIsUnderShrunken = isUnderShrunken; } @@ -571,9 +596,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return mStarted; } - /** - * Called when parental control is changed. - */ + /** Called when parental control is changed. */ public void onParentalControlChanged(boolean enabled) { mParentControlEnabled = enabled; if (!enabled) { @@ -586,8 +609,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * Tunes to a channel with the {@code channelId}. * * @param params extra data to send it to TIS and store the data in TIMS. - * @return false, if the TV input is not a proper state to tune to a channel. For example, - * if the state is disconnected or channelId doesn't exist, it returns false. + * @return false, if the TV input is not a proper state to tune to a channel. For example, if + * the state is disconnected or channelId doesn't exist, it returns false. */ public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) { Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TunableTvView.tuneTo"); @@ -603,24 +626,34 @@ public class TunableTvView extends FrameLayout implements StreamInfo { long duration = mChannelViewTimer.reset(); mTracker.sendChannelViewStop(mCurrentChannel, duration); if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) { - mWatchedHistoryManager.logChannelViewStop(mCurrentChannel, - System.currentTimeMillis(), duration); + mWatchedHistoryManager.logChannelViewStop( + mCurrentChannel, System.currentTimeMillis(), duration); } } mOnTuneListener = listener; mCurrentChannel = channel; - boolean tunedByRecommendation = params != null - && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null; + boolean tunedByRecommendation = + params != null + && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) + != null; boolean needSurfaceSizeUpdate = false; if (!inputInfo.equals(mInputInfo)) { mTagetInputId = inputInfo.getId(); mInputInfo = inputInfo; - mCanReceiveInputEvent = getContext().getPackageManager().checkPermission( - PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getServiceInfo().packageName) + mCanReceiveInputEvent = + getContext() + .getPackageManager() + .checkPermission( + PERMISSION_RECEIVE_INPUT_EVENT, + mInputInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED; if (DEBUG) { - Log.d(TAG, "Input \'" + mInputInfo.getId() + "\' can receive input event: " - + mCanReceiveInputEvent); + Log.d( + TAG, + "Input \'" + + mInputInfo.getId() + + "\' can receive input event: " + + mCanReceiveInputEvent); } needSurfaceSizeUpdate = true; } @@ -683,13 +716,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Sets fixed size for the internal {@link android.view.Surface} of - * {@link android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, - * the {@link android.view.Surface}'s size will be matched to the layout. + * Sets fixed size for the internal {@link android.view.Surface} of {@link + * android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, the + * {@link android.view.Surface}'s size will be matched to the layout. * - * Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called, - * {@link android.view.SurfaceView} and its underlying window can be misaligned, when the size - * of {@link android.view.SurfaceView} is changed without changing either left position or top + *

Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called, {@link + * android.view.SurfaceView} and its underlying window can be misaligned, when the size of + * {@link android.view.SurfaceView} is changed without changing either left position or top * position. For detail, please refer the codes of android.view.SurfaceView.updateWindow(). */ public void setFixedSurfaceSize(int width, int height) { @@ -728,10 +761,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo { public interface OnTuneListener { void onTuneFailed(Channel channel); + void onUnexpectedStop(Channel channel); + void onStreamInfoChanged(StreamInfo info); + void onChannelRetuned(Uri channel); + void onContentBlocked(); + void onContentAllowed(); } @@ -759,9 +797,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return mVideoFrameRate; } - /** - * Returns displayed aspect ratio (video width / video height * pixel ratio). - */ + /** Returns displayed aspect ratio (video width / video height * pixel ratio). */ @Override public float getVideoDisplayAspectRatio() { return mVideoDisplayAspectRatio; @@ -793,9 +829,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return mVideoUnavailableReason; } - /** - * Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}. - */ + /** Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}. */ private SurfaceView getSurfaceView() { return (SurfaceView) mTvView.getChildAt(0); } @@ -821,16 +855,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying - * {@link TvView}, which is the actual view to play live TV videos. + * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView}, + * which is the actual view to play live TV videos. */ public MarginLayoutParams getTvViewLayoutParams() { return (MarginLayoutParams) mTvView.getLayoutParams(); } /** - * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying - * {@link TvView}, which is the actual view to play live TV videos. + * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView}, + * which is the actual view to play live TV videos. */ public void setTvViewLayoutParams(MarginLayoutParams layoutParams) { mTvView.setLayoutParams(layoutParams); @@ -851,16 +885,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return isScreenBlocked() || isContentBlocked(); } - /** - * Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}. - */ + /** Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}. */ public boolean isScreenBlocked() { return mScreenBlocked; } - /** - * Returns {@code true} if the content is blocked, otherwise {@code false}. - */ + /** Returns {@code true} if the content is blocked, otherwise {@code false}. */ public boolean isContentBlocked() { return mBlockedContentRating != null; } @@ -869,18 +899,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mOnScreenBlockedListener = listener; } - /** - * Returns currently blocked content rating. {@code null} if it's not blocked. - */ + /** Returns currently blocked content rating. {@code null} if it's not blocked. */ @Override public TvContentRating getBlockedContentRating() { return mBlockedContentRating; } /** - * Blocks/unblocks current TV screen and mutes. - * There would be black screen with lock icon in order to show that - * screen block is intended and not an error. + * Blocks/unblocks current TV screen and mutes. There would be black screen with lock icon in + * order to show that screen block is intended and not an error. * * @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock. */ @@ -909,8 +936,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { /** * Set the type of block screen. If {@code type} is set to {@code BLOCK_SCREEN_TYPE_NO_UI}, the * block screen will not show any description such as a lock icon and a text for the blocked - * reason, if {@code type} is set to {@code BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW}, the block screen - * will show the description for shrunken tv view (Small icon and short text), and if + * reason, if {@code type} is set to {@code BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW}, the block + * screen will show the description for shrunken tv view (Small icon and short text), and if * {@code type} is set to {@code BLOCK_SCREEN_TYPE_NORMAL}, the block screen will show the * description for normal tv view (Big icon and long text). * @@ -925,14 +952,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private void updateBlockScreen(boolean animation) { mBlockScreenView.endAnimations(); - int blockReason = (mScreenBlocked || mBlockedContentRating != null) - && mParentControlEnabled ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED + int blockReason = + (mScreenBlocked || mBlockedContentRating != null) && mParentControlEnabled + ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED : mVideoUnavailableReason; if (blockReason != VIDEO_UNAVAILABLE_REASON_NONE) { mBufferingSpinnerView.setVisibility( blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING - || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING ? - VISIBLE : GONE); + || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING + ? VISIBLE + : GONE); if (!animation) { adjustBlockScreenSpacingAndText(); } @@ -959,7 +988,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) { showImageForTuningIfNeeded(); } else if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN - && mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) { + && mCurrentChannel != null + && !mCurrentChannel.isPhysicalTunerChannel()) { mInternetCheckTask = new InternetCheckTask(); mInternetCheckTask.execute(); } @@ -982,8 +1012,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** - * Returns the block screen text corresponding to the current status. - * Note that returning {@code null} value means that the current text should not be changed. + * Returns the block screen text corresponding to the current status. Note that returning {@code + * null} value means that the current text should not be changed. */ private String getBlockScreenText() { // TODO: add a test for this method @@ -1071,8 +1101,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private boolean shouldShowImageForTuning() { if (mVideoUnavailableReason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING - || mScreenBlocked || mBlockedContentRating != null || mCurrentChannel == null - || mIsUnderShrunken || getWidth() == 0 || getWidth() == 0 || !isBundledInput()) { + || mScreenBlocked + || mBlockedContentRating != null + || mCurrentChannel == null + || mIsUnderShrunken + || getWidth() == 0 + || getWidth() == 0 + || !isBundledInput()) { return false; } Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); @@ -1091,7 +1126,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); if (currentProgram != null) { - currentProgram.loadPosterArt(getContext(), getWidth(), getHeight(), + currentProgram.loadPosterArt( + getContext(), + getWidth(), + getHeight(), createProgramPosterArtCallback(mCurrentChannel.getId())); } } @@ -1102,9 +1140,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo { TvInputInfo input = mInputManager.getTvInputInfo(mTagetInputId); Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(mTagetInputId); if (timeMs != null) { - return getResources().getQuantityString(R.plurals.tvview_msg_input_no_resource, - input.getTunerCount(), - DateUtils.formatDateTime(getContext(), timeMs, DateUtils.FORMAT_SHOW_TIME)); + return getResources() + .getQuantityString( + R.plurals.tvview_msg_input_no_resource, + input.getTunerCount(), + DateUtils.formatDateTime( + getContext(), timeMs, DateUtils.FORMAT_SHOW_TIME)); } } return null; @@ -1119,7 +1160,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { // itself right way when the playback is going to be started, which results the initial // jankiness, too. boolean isBundledInput = isBundledInput(); - if ((isBundledInput || isVideoOrAudioAvailable()) && !mScreenBlocked + if ((isBundledInput || isVideoOrAudioAvailable()) + && !mScreenBlocked && mBlockedContentRating == null) { if (mIsMuted) { mIsMuted = false; @@ -1128,7 +1170,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } else { if (!mIsMuted) { if ((mInputInfo == null || isBundledInput) - && !mScreenBlocked && mBlockedContentRating == null) { + && !mScreenBlocked + && mBlockedContentRating == null) { return; } mIsMuted = true; @@ -1138,7 +1181,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } private boolean isBundledInput() { - return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER + return mInputInfo != null + && mInputInfo.getType() == TvInputInfo.TYPE_TUNER && Utils.isBundledInput(mInputInfo.getId()); } @@ -1148,52 +1192,58 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** Fade out this TunableTvView. Fade out by increasing the dimming. */ - public void fadeOut(int durationMillis, TimeInterpolator interpolator, - final Runnable actionAfterFade) { + public void fadeOut( + int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) { mDimScreenView.setAlpha(0f); mDimScreenView.setVisibility(View.VISIBLE); - mDimScreenView.animate() + mDimScreenView + .animate() .alpha(1f) .setDuration(durationMillis) .setInterpolator(interpolator) - .withStartAction(new Runnable() { - @Override - public void run() { - mFadeState = FADING_OUT; - mActionAfterFade = actionAfterFade; - } - }) - .withEndAction(new Runnable() { - @Override - public void run() { - mFadeState = FADED_OUT; - } - }); + .withStartAction( + new Runnable() { + @Override + public void run() { + mFadeState = FADING_OUT; + mActionAfterFade = actionAfterFade; + } + }) + .withEndAction( + new Runnable() { + @Override + public void run() { + mFadeState = FADED_OUT; + } + }); } /** Fade in this TunableTvView. Fade in by decreasing the dimming. */ - public void fadeIn(int durationMillis, TimeInterpolator interpolator, - final Runnable actionAfterFade) { + public void fadeIn( + int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) { mDimScreenView.setAlpha(1f); mDimScreenView.setVisibility(View.VISIBLE); - mDimScreenView.animate() + mDimScreenView + .animate() .alpha(0f) .setDuration(durationMillis) .setInterpolator(interpolator) - .withStartAction(new Runnable() { - @Override - public void run() { - mFadeState = FADING_IN; - mActionAfterFade = actionAfterFade; - } - }) - .withEndAction(new Runnable() { - @Override - public void run() { - mFadeState = FADED_IN; - mDimScreenView.setVisibility(View.GONE); - } - }); + .withStartAction( + new Runnable() { + @Override + public void run() { + mFadeState = FADING_IN; + mActionAfterFade = actionAfterFade; + } + }) + .withEndAction( + new Runnable() { + @Override + public void run() { + mFadeState = FADED_IN; + mDimScreenView.setVisibility(View.GONE); + } + }); } /** Remove the fade effect. */ @@ -1218,20 +1268,22 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } mTimeShiftAvailable = isTimeShiftAvailable; if (isTimeShiftAvailable) { - mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() { - @Override - public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { - if (mTimeShiftListener != null && mCurrentChannel != null - && mCurrentChannel.getInputId().equals(inputId)) { - mTimeShiftListener.onRecordStartTimeChanged(timeMs); - } - } + mTvView.setTimeShiftPositionCallback( + new TvView.TimeShiftPositionCallback() { + @Override + public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { + if (mTimeShiftListener != null + && mCurrentChannel != null + && mCurrentChannel.getInputId().equals(inputId)) { + mTimeShiftListener.onRecordStartTimeChanged(timeMs); + } + } - @Override - public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { - mTimeShiftCurrentPositionMs = timeMs; - } - }); + @Override + public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { + mTimeShiftCurrentPositionMs = timeMs; + } + }); } else { mTvView.setTimeShiftPositionCallback(null); } @@ -1240,16 +1292,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } } - /** - * Returns if the time shift is available for the current channel. - */ + /** Returns if the time shift is available for the current channel. */ public boolean isTimeShiftAvailable() { return mTimeShiftAvailable; } - /** - * Plays the media, if the current input supports time-shifting. - */ + /** Plays the media, if the current input supports time-shifting. */ public void timeshiftPlay() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1260,9 +1308,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mTvView.timeShiftResume(); } - /** - * Pauses the media, if the current input supports time-shifting. - */ + /** Pauses the media, if the current input supports time-shifting. */ public void timeshiftPause() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1323,16 +1369,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mTvView.timeShiftSeekTo(timeMs); } - /** - * Returns the current playback position in milliseconds. - */ + /** Returns the current playback position in milliseconds. */ public long timeshiftGetCurrentPositionMs() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); } if (DEBUG) { - Log.d(TAG, "timeshiftGetCurrentPositionMs: current position =" - + Utils.toTimeString(mTimeShiftCurrentPositionMs)); + Log.d( + TAG, + "timeshiftGetCurrentPositionMs: current position =" + + Utils.toTimeString(mTimeShiftCurrentPositionMs)); } return mTimeShiftCurrentPositionMs; } @@ -1342,23 +1388,23 @@ public class TunableTvView extends FrameLayout implements StreamInfo { return new ImageLoader.ImageLoaderCallback(mBlockScreenView) { @Override public void onBitmapLoaded(BlockScreenView view, @Nullable Bitmap posterArt) { - if (posterArt == null || getCurrentChannel() == null + if (posterArt == null + || getCurrentChannel() == null || channelId != getCurrentChannel().getId() || !shouldShowImageForTuning()) { return; } Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt); - drawablePosterArt.mutate().setColorFilter( - mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER); + drawablePosterArt + .mutate() + .setColorFilter(mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER); view.setBackgroundImage(drawablePosterArt); } }; } - /** - * Used to receive the time-shift events. - */ - public static abstract class TimeShiftListener { + /** 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. @@ -1366,19 +1412,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo { public abstract void onAvailabilityChanged(); /** - * Called when the record start time has been changed. - * This is not called when the recorded programs is played. + * 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 static abstract class OnScreenBlockingChangedListener { - /** - * Called when the screen is blocked/unblocked. - */ + /** A listener which receives the notification when the screen is blocked/unblocked. */ + public abstract static class OnScreenBlockingChangedListener { + /** Called when the screen is blocked/unblocked. */ public abstract void onScreenBlockingChanged(boolean blocked); } @@ -1391,8 +1433,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo { @Override protected void onPostExecute(Boolean networkAvailable) { mInternetCheckTask = null; - if (!networkAvailable && isAttachedToWindow() - && !mScreenBlocked && mBlockedContentRating == null + if (!networkAvailable + && isAttachedToWindow() + && !mScreenBlocked + && mBlockedContentRating == null && mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) { mBlockScreenView.setIconVisibility(true); mBlockScreenView.setIconImage(R.drawable.ic_sad_cloud); diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java index 9324742e..58ff8c2f 100644 --- a/src/com/android/tv/ui/TvOverlayManager.java +++ b/src/com/android/tv/ui/TvOverlayManager.java @@ -32,7 +32,6 @@ import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.ViewGroup; - import com.android.tv.ApplicationSingletons; import com.android.tv.ChannelTuner; import com.android.tv.MainActivity; @@ -69,7 +68,6 @@ import com.android.tv.ui.TvTransitionManager.SceneType; import com.android.tv.ui.sidepanel.SideFragmentManager; import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment; import com.android.tv.util.TvInputManagerHelper; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -79,9 +77,7 @@ import java.util.List; import java.util.Queue; import java.util.Set; -/** - * A class responsible for the life cycle and event handling of the pop-ups over TV view. - */ +/** A class responsible for the life cycle and event handling of the pop-ups over TV view. */ @UiThread public class TvOverlayManager { private static final String TAG = "TvOverlayManager"; @@ -89,112 +85,103 @@ public class TvOverlayManager { private static final String INTRO_TRACKER_LABEL = "Intro dialog"; @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {FLAG_HIDE_OVERLAYS_DEFAULT, FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION, - FLAG_HIDE_OVERLAYS_KEEP_SCENE, FLAG_HIDE_OVERLAYS_KEEP_DIALOG, - FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY, - FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, FLAG_HIDE_OVERLAYS_KEEP_MENU, - FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT}) + @IntDef( + flag = true, + value = { + FLAG_HIDE_OVERLAYS_DEFAULT, + FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION, + FLAG_HIDE_OVERLAYS_KEEP_SCENE, + FLAG_HIDE_OVERLAYS_KEEP_DIALOG, + FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, + FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY, + FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, + FLAG_HIDE_OVERLAYS_KEEP_MENU, + FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT + } + ) private @interface HideOverlayFlag {} // FLAG_HIDE_OVERLAYs must be bitwise exclusive. - public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000; - public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010; - public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100; - public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000; - public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000; + public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000; + public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010; + public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100; + public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000; + public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000; public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000; - public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000; - public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000; - public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000; + public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000; + public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000; + public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000; private static final int MSG_OVERLAY_CLOSED = 1000; @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {OVERLAY_TYPE_NONE, OVERLAY_TYPE_MENU, OVERLAY_TYPE_SIDE_FRAGMENT, - OVERLAY_TYPE_DIALOG, OVERLAY_TYPE_GUIDE, OVERLAY_TYPE_SCENE_CHANNEL_BANNER, - OVERLAY_TYPE_SCENE_INPUT_BANNER, OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH, - OVERLAY_TYPE_SCENE_SELECT_INPUT, OVERLAY_TYPE_FRAGMENT}) + @IntDef( + flag = true, + value = { + OVERLAY_TYPE_NONE, + OVERLAY_TYPE_MENU, + OVERLAY_TYPE_SIDE_FRAGMENT, + OVERLAY_TYPE_DIALOG, + OVERLAY_TYPE_GUIDE, + OVERLAY_TYPE_SCENE_CHANNEL_BANNER, + OVERLAY_TYPE_SCENE_INPUT_BANNER, + OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH, + OVERLAY_TYPE_SCENE_SELECT_INPUT, + OVERLAY_TYPE_FRAGMENT + } + ) private @interface TvOverlayType {} // OVERLAY_TYPEs must be bitwise exclusive. - /** - * The overlay type which indicates that there are no overlays. - */ - private static final int OVERLAY_TYPE_NONE = 0b000000000; - /** - * The overlay type for menu. - */ - private static final int OVERLAY_TYPE_MENU = 0b000000001; - /** - * The overlay type for the side fragment. - */ - private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010; - /** - * The overlay type for dialog fragment. - */ - private static final int OVERLAY_TYPE_DIALOG = 0b000000100; - /** - * The overlay type for program guide. - */ - private static final int OVERLAY_TYPE_GUIDE = 0b000001000; - /** - * The overlay type for channel banner. - */ - private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000; - /** - * The overlay type for input banner. - */ - private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000; - /** - * The overlay type for keypad channel switch view. - */ + /** The overlay type which indicates that there are no overlays. */ + private static final int OVERLAY_TYPE_NONE = 0b000000000; + /** The overlay type for menu. */ + private static final int OVERLAY_TYPE_MENU = 0b000000001; + /** The overlay type for the side fragment. */ + private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010; + /** The overlay type for dialog fragment. */ + private static final int OVERLAY_TYPE_DIALOG = 0b000000100; + /** The overlay type for program guide. */ + private static final int OVERLAY_TYPE_GUIDE = 0b000001000; + /** The overlay type for channel banner. */ + private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000; + /** The overlay type for input banner. */ + private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000; + /** The overlay type for keypad channel switch view. */ private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000; - /** - * The overlay type for select input view. - */ - private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000; - /** - * The overlay type for fragment other than the side fragment and dialog fragment. - */ - private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000; + /** The overlay type for select input view. */ + private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000; + /** The overlay type for fragment other than the side fragment and dialog fragment. */ + private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000; // Used for the padded print of the overlay type. private static final int NUM_OVERLAY_TYPES = 9; @Retention(RetentionPolicy.SOURCE) - @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE, - UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, - UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK, - UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO}) + @IntDef({ + UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, + UPDATE_CHANNEL_BANNER_REASON_TUNE, + UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, + UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO, + UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK, + UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO + }) private @interface ChannelBannerUpdateReason {} - /** - * Updates channel banner because the channel banner is forced to show. - */ + /** Updates channel banner because the channel banner is forced to show. */ public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1; - /** - * Updates channel banner because of tuning. - */ + /** Updates channel banner because of tuning. */ public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2; - /** - * Updates channel banner because of fast tuning. - */ + /** Updates channel banner because of fast tuning. */ public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3; - /** - * Updates channel banner because of info updating. - */ + /** Updates channel banner because of info updating. */ public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4; - /** - * Updates channel banner because the current watched channel is locked or unlocked. - */ + /** Updates channel banner because the current watched channel is locked or unlocked. */ public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5; - /** - * Updates channel banner because of stream info updating. - */ + /** Updates channel banner because of stream info updating. */ public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6; private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources"; private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources"; private static final Set AVAILABLE_DIALOG_TAGS = new HashSet<>(); + static { AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG); AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG); @@ -232,11 +219,17 @@ public class TvOverlayManager { private OnBackStackChangedListener mOnBackStackChangedListener; - public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner, - TunableTvView tvView, TvOptionsManager optionsManager, - KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView, - InputBannerView inputBannerView, SelectInputView selectInputView, - ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment) { + public TvOverlayManager( + MainActivity mainActivity, + ChannelTuner channelTuner, + TunableTvView tvView, + TvOptionsManager optionsManager, + KeypadChannelSwitchView keypadChannelSwitchView, + ChannelBannerView channelBannerView, + InputBannerView inputBannerView, + SelectInputView selectInputView, + ViewGroup sceneContainer, + ProgramGuideSearchFragment searchFragment) { mMainActivity = mainActivity; mChannelTuner = channelTuner; ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity); @@ -248,115 +241,144 @@ public class TvOverlayManager { mSelectInputView = selectInputView; mSearchFragment = searchFragment; mTracker = singletons.getTracker(); - mTransitionManager = new TvTransitionManager(mainActivity, sceneContainer, - channelBannerView, inputBannerView, mKeypadChannelSwitchView, selectInputView); - mTransitionManager.setListener(new TvTransitionManager.Listener() { - @Override - public void onSceneChanged(int fromScene, int toScene) { - // Call onOverlayOpened first so that the listener can know that a new scene - // will be opened when the onOverlayClosed is called. - if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) { - onOverlayOpened(convertSceneToOverlayType(toScene)); - } - if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) { - onOverlayClosed(convertSceneToOverlayType(fromScene)); - } - } - }); - // Menu - MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); - mMenu = new Menu(mainActivity, tvView, optionsManager, menuView, - new MenuRowFactory(mainActivity, tvView), - new Menu.OnMenuVisibilityChangeListener() { + mTransitionManager = + new TvTransitionManager( + mainActivity, + sceneContainer, + channelBannerView, + inputBannerView, + mKeypadChannelSwitchView, + selectInputView); + mTransitionManager.setListener( + new TvTransitionManager.Listener() { @Override - public void onMenuVisibilityChange(boolean visible) { - if (visible) { - onOverlayOpened(OVERLAY_TYPE_MENU); - } else { - onOverlayClosed(OVERLAY_TYPE_MENU); + public void onSceneChanged(int fromScene, int toScene) { + // Call onOverlayOpened first so that the listener can know that a new scene + // will be opened when the onOverlayClosed is called. + if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) { + onOverlayOpened(convertSceneToOverlayType(toScene)); + } + if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) { + onOverlayClosed(convertSceneToOverlayType(fromScene)); } } }); + // Menu + MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu); + mMenu = + new Menu( + mainActivity, + tvView, + optionsManager, + menuView, + new MenuRowFactory(mainActivity, tvView), + new Menu.OnMenuVisibilityChangeListener() { + @Override + public void onMenuVisibilityChange(boolean visible) { + if (visible) { + onOverlayOpened(OVERLAY_TYPE_MENU); + } else { + onOverlayClosed(OVERLAY_TYPE_MENU); + } + } + }); mMenu.setChannelTuner(mChannelTuner); // Side Fragment - mSideFragmentManager = new SideFragmentManager(mainActivity, + mSideFragmentManager = + new SideFragmentManager( + mainActivity, + new Runnable() { + @Override + public void run() { + onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT); + hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS); + } + }, + new Runnable() { + @Override + public void run() { + showChannelBannerIfHiddenBySideFragment(); + onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); + } + }); + // Program Guide + Runnable preShowRunnable = new Runnable() { @Override public void run() { - onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT); - hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS); + onOverlayOpened(OVERLAY_TYPE_GUIDE); } - }, + }; + Runnable postHideRunnable = new Runnable() { @Override public void run() { - showChannelBannerIfHiddenBySideFragment(); - onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT); + onOverlayClosed(OVERLAY_TYPE_GUIDE); } - }); - // Program Guide - Runnable preShowRunnable = new Runnable() { - @Override - public void run() { - onOverlayOpened(OVERLAY_TYPE_GUIDE); - } - }; - Runnable postHideRunnable = new Runnable() { - @Override - public void run() { - onOverlayClosed(OVERLAY_TYPE_GUIDE); - } - }; - DvrDataManager dvrDataManager = CommonFeatures.DVR.isEnabled(mainActivity) - ? singletons.getDvrDataManager() : null; - mProgramGuide = new ProgramGuide(mainActivity, channelTuner, - singletons.getTvInputManagerHelper(), mChannelDataManager, - singletons.getProgramDataManager(), dvrDataManager, - singletons.getDvrScheduleManager(), singletons.getTracker(), preShowRunnable, - postHideRunnable); - mMainActivity.addOnActionClickListener(new OnActionClickListener() { - @Override - public boolean onActionClick(String category, int id, Bundle params) { - switch (category) { - case SetupSourcesFragment.ACTION_CATEGORY: - switch (id) { - case SetupMultiPaneFragment.ACTION_DONE: - closeSetupFragment(true); - return true; - case SetupSourcesFragment.ACTION_ONLINE_STORE: - mMainActivity.showMerchantCollection(); - return true; - case SetupSourcesFragment.ACTION_SETUP_INPUT: { - String inputId = params.getString( - SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); - TvInputInfo input = mInputManager.getTvInputInfo(inputId); - mMainActivity.startSetupActivity(input, true); - return true; - } - } - break; - case NewSourcesFragment.ACTION_CATEOGRY: - switch (id) { - case NewSourcesFragment.ACTION_SETUP: - closeNewSourcesFragment(false); - showSetupFragment(); - return true; - case NewSourcesFragment.ACTION_SKIP: - // Don't remove the fragment because new fragment will be replaced - // with this fragment. - closeNewSourcesFragment(true); - return true; + }; + DvrDataManager dvrDataManager = + CommonFeatures.DVR.isEnabled(mainActivity) ? singletons.getDvrDataManager() : null; + mProgramGuide = + new ProgramGuide( + mainActivity, + channelTuner, + singletons.getTvInputManagerHelper(), + mChannelDataManager, + singletons.getProgramDataManager(), + dvrDataManager, + singletons.getDvrScheduleManager(), + singletons.getTracker(), + preShowRunnable, + postHideRunnable); + mMainActivity.addOnActionClickListener( + new OnActionClickListener() { + @Override + public boolean onActionClick(String category, int id, Bundle params) { + switch (category) { + case SetupSourcesFragment.ACTION_CATEGORY: + switch (id) { + case SetupMultiPaneFragment.ACTION_DONE: + closeSetupFragment(true); + return true; + case SetupSourcesFragment.ACTION_ONLINE_STORE: + mMainActivity.showMerchantCollection(); + return true; + case SetupSourcesFragment.ACTION_SETUP_INPUT: + { + String inputId = + params.getString( + SetupSourcesFragment + .ACTION_PARAM_KEY_INPUT_ID); + TvInputInfo input = + mInputManager.getTvInputInfo(inputId); + mMainActivity.startSetupActivity(input, true); + return true; + } + } + break; + case NewSourcesFragment.ACTION_CATEOGRY: + switch (id) { + case NewSourcesFragment.ACTION_SETUP: + closeNewSourcesFragment(false); + showSetupFragment(); + return true; + case NewSourcesFragment.ACTION_SKIP: + // Don't remove the fragment because new fragment will be + // replaced + // with this fragment. + closeNewSourcesFragment(true); + return true; + } + break; } - break; - } - return false; - } - }); + return false; + } + }); } /** - * A method to release all the allocated resources or unregister listeners. - * This is called from {@link MainActivity#onDestroy}. + * A method to release all the allocated resources or unregister listeners. This is called from + * {@link MainActivity#onDestroy}. */ public void release() { mMenu.release(); @@ -366,30 +388,22 @@ public class TvOverlayManager { } } - /** - * Returns the instance of {@link Menu}. - */ + /** Returns the instance of {@link Menu}. */ public Menu getMenu() { return mMenu; } - /** - * Returns the instance of {@link SideFragmentManager}. - */ + /** Returns the instance of {@link SideFragmentManager}. */ public SideFragmentManager getSideFragmentManager() { return mSideFragmentManager; } - /** - * Returns the currently opened dialog. - */ + /** Returns the currently opened dialog. */ public SafeDismissDialogFragment getCurrentDialog() { return mCurrentDialog; } - /** - * Checks whether the setup fragment is active or not. - */ + /** Checks whether the setup fragment is active or not. */ public boolean isSetupFragmentActive() { // "getSetupSourcesFragment() != null" doesn't return the correct state. That's because, // when we call showSetupFragment(), we need to put off showing the fragment until the side @@ -402,9 +416,7 @@ public class TvOverlayManager { return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_SETUP_SOURCES); } - /** - * Checks whether the new sources fragment is active or not. - */ + /** Checks whether the new sources fragment is active or not. */ public boolean isNewSourcesFragmentActive() { // See the comment in "isSetupFragmentActive". return mNewSourcesFragmentActive; @@ -414,25 +426,19 @@ public class TvOverlayManager { return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_NEW_SOURCES); } - /** - * Returns the instance of {@link ProgramGuide}. - */ + /** Returns the instance of {@link ProgramGuide}. */ public ProgramGuide getProgramGuide() { return mProgramGuide; } - /** - * Shows the main menu. - */ + /** Shows the main menu. */ public void showMenu(@MenuShowReason int reason) { if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) { mMenu.show(reason); } } - /** - * Shows the play controller of the menu if the playback is paused. - */ + /** Shows the play controller of the menu if the playback is paused. */ public boolean showMenuWithTimeShiftPauseIfNeeded() { if (mMainActivity.getTimeShiftManager().isPaused()) { showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE); @@ -441,16 +447,17 @@ public class TvOverlayManager { return false; } - /** - * Shows the given dialog. - */ - public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, - boolean keepSidePanelHistory) { + /** Shows the given dialog. */ + public void showDialogFragment( + String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory) { showDialogFragment(tag, dialog, keepSidePanelHistory, false); } - public void showDialogFragment(String tag, SafeDismissDialogFragment dialog, - boolean keepSidePanelHistory, boolean keepProgramGuide) { + public void showDialogFragment( + String tag, + SafeDismissDialogFragment dialog, + boolean keepSidePanelHistory, + boolean keepProgramGuide) { int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG; if (keepSidePanelHistory) { flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY; @@ -466,8 +473,8 @@ public class TvOverlayManager { // Do not open two dialogs at the same time. if (mCurrentDialog != null) { - mPendingDialogActionQueue.offer(new PendingDialogAction(tag, dialog, - keepSidePanelHistory, keepProgramGuide)); + mPendingDialogActionQueue.offer( + new PendingDialogAction(tag, dialog, keepSidePanelHistory, keepProgramGuide)); return; } @@ -492,16 +499,17 @@ public class TvOverlayManager { // When the side panel is closing, it closes all the fragments, so the new fragment // should be opened after the side fragment becomes invisible. final FragmentManager manager = mMainActivity.getFragmentManager(); - mOnBackStackChangedListener = new OnBackStackChangedListener() { - @Override - public void onBackStackChanged() { - if (manager.getBackStackEntryCount() == 0) { - manager.removeOnBackStackChangedListener(this); - mOnBackStackChangedListener = null; - runnable.run(); - } - } - }; + mOnBackStackChangedListener = + new OnBackStackChangedListener() { + @Override + public void onBackStackChanged() { + if (manager.getBackStackEntryCount() == 0) { + manager.removeOnBackStackChangedListener(this); + mOnBackStackChangedListener = null; + runnable.run(); + } + } + }; manager.addOnBackStackChangedListener(mOnBackStackChangedListener); } else { runnable.run(); @@ -511,46 +519,54 @@ public class TvOverlayManager { private void showFragment(final Fragment fragment, final String tag) { hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); onOverlayOpened(OVERLAY_TYPE_FRAGMENT); - runAfterSideFragmentsAreClosed(new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")"); - mMainActivity.getFragmentManager().beginTransaction() - .replace(R.id.fragment_container, fragment, tag).commit(); - } - }); + runAfterSideFragmentsAreClosed( + new Runnable() { + @Override + public void run() { + if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")"); + mMainActivity + .getFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, fragment, tag) + .commit(); + } + }); } private void closeFragment(String fragmentTagToRemove) { if (DEBUG) Log.d(TAG, "closeFragment(" + fragmentTagToRemove + ")"); onOverlayClosed(OVERLAY_TYPE_FRAGMENT); if (fragmentTagToRemove != null) { - Fragment fragmentToRemove = mMainActivity.getFragmentManager() - .findFragmentByTag(fragmentTagToRemove); + Fragment fragmentToRemove = + mMainActivity.getFragmentManager().findFragmentByTag(fragmentTagToRemove); if (fragmentToRemove == null) { // If the fragment has not been added to the fragment manager yet, just remove the // listener not to add the fragment. This is needed because the side fragment is // closed asynchronously. - mMainActivity.getFragmentManager().removeOnBackStackChangedListener( - mOnBackStackChangedListener); + mMainActivity + .getFragmentManager() + .removeOnBackStackChangedListener(mOnBackStackChangedListener); mOnBackStackChangedListener = null; } else { - mMainActivity.getFragmentManager().beginTransaction().remove(fragmentToRemove) + mMainActivity + .getFragmentManager() + .beginTransaction() + .remove(fragmentToRemove) .commit(); } } } - /** - * Shows setup dialog. - */ + /** Shows setup dialog. */ public void showSetupFragment() { if (DEBUG) Log.d(TAG, "showSetupFragment"); mSetupFragmentActive = true; SetupSourcesFragment setupFragment = new SetupSourcesFragment(); - setupFragment.enableFragmentTransition(SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); + setupFragment.enableFragmentTransition( + SetupFragment.FRAGMENT_ENTER_TRANSITION + | SetupFragment.FRAGMENT_EXIT_TRANSITION + | SetupFragment.FRAGMENT_RETURN_TRANSITION + | SetupFragment.FRAGMENT_REENTER_TRANSITION); setupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END); showFragment(setupFragment, FRAGMENT_TAG_SETUP_SOURCES); } @@ -569,9 +585,7 @@ public class TvOverlayManager { } } - /** - * Shows new sources dialog. - */ + /** Shows new sources dialog. */ public void showNewSourcesFragment() { if (DEBUG) Log.d(TAG, "showNewSourcesFragment"); mNewSourcesFragmentActive = true; @@ -588,43 +602,36 @@ public class TvOverlayManager { closeFragment(removeFragment ? FRAGMENT_TAG_NEW_SOURCES : null); } - /** - * Shows DVR manager. - */ + /** Shows DVR manager. */ public void showDvrManager() { Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class); mMainActivity.startActivity(intent); } - /** - * Shows intro dialog. - */ + /** Shows intro dialog. */ public void showIntroDialog() { - if (DEBUG) Log.d(TAG,"showIntroDialog"); - showDialogFragment(FullscreenDialogFragment.DIALOG_TAG, + if (DEBUG) Log.d(TAG, "showIntroDialog"); + showDialogFragment( + FullscreenDialogFragment.DIALOG_TAG, FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL), false); } - /** - * Shows recently watched dialog. - */ + /** Shows recently watched dialog. */ public void showRecentlyWatchedDialog() { - showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG, - new RecentlyWatchedDialogFragment(), false); + showDialogFragment( + RecentlyWatchedDialogFragment.DIALOG_TAG, + new RecentlyWatchedDialogFragment(), + false); } - /** - * Shows DVR history dialog. - */ + /** Shows DVR history dialog. */ public void showDvrHistoryDialog() { - showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG, - new DvrHistoryDialogFragment(), false); + showDialogFragment( + DvrHistoryDialogFragment.DIALOG_TAG, new DvrHistoryDialogFragment(), false); } - /** - * Shows banner view. - */ + /** Shows banner view. */ public void showBanner() { mTransitionManager.goToChannelBannerScene(); } @@ -636,33 +643,28 @@ public class TvOverlayManager { */ public void showKeypadChannelSwitch(int keyCode) { if (mChannelTuner.areAllChannelsLoaded()) { - hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); mTransitionManager.goToKeypadChannelSwitchScene(); mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0); } } - /** - * Shows select input view. - */ + /** Shows select input view. */ public void showSelectInputView() { hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE); mTransitionManager.goToSelectInputScene(); } - /** - * Initializes animators if animators are not initialized yet. - */ + /** Initializes animators if animators are not initialized yet. */ public void initAnimatorIfNeeded() { mTransitionManager.initIfNeeded(); } - /** - * It is called when a SafeDismissDialogFragment is destroyed. - */ + /** It is called when a SafeDismissDialogFragment is destroyed. */ public void onDialogDestroyed() { mCurrentDialog = null; PendingDialogAction action = mPendingDialogActionQueue.poll(); @@ -673,16 +675,15 @@ public class TvOverlayManager { } } - /** - * Shows the program guide. - */ + /** Shows the program guide. */ public void showProgramGuide() { - mProgramGuide.show(new Runnable() { - @Override - public void run() { - hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE); - } - }); + mProgramGuide.show( + new Runnable() { + @Override + public void run() { + hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE); + } + }); } /** @@ -700,9 +701,7 @@ public class TvOverlayManager { } } - /** - * Sets blocking content rating of the currently playing TV channel. - */ + /** Sets blocking content rating of the currently playing TV channel. */ public void setBlockingContentRating(TvContentRating rating) { if (!mMainActivity.isChannelChangeKeyDownReceived()) { mChannelBannerView.setBlockingContentRating(rating); @@ -710,9 +709,7 @@ public class TvOverlayManager { } } - /** - * Hides all the opened overlays according to the flags. - */ + /** Hides all the opened overlays according to the flags. */ // TODO: Add test for this method. public void hideOverlays(@HideOverlayFlag int flags) { if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) { @@ -781,8 +778,8 @@ public class TvOverlayManager { } /** - * Returns true, if a main view needs to hide informational text. Specifically, when overlay - * UIs except banner is shown, the informational text needs to be hidden for clean UI. + * Returns true, if a main view needs to hide informational text. Specifically, when overlay UIs + * except banner is shown, the informational text needs to be hidden for clean UI. */ public boolean needHideTextOnMainView() { return mSideFragmentManager.isActive() @@ -793,11 +790,9 @@ public class TvOverlayManager { || mNewSourcesFragmentActive; } - /** - * Updates and shows channel banner if it's needed. - */ + /** Updates and shows channel banner if it's needed. */ public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) { - if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); + if (DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")"); if (mMainActivity.isChannelChangeKeyDownReceived() && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) { @@ -826,8 +821,9 @@ public class TvOverlayManager { } } else if (mTvView.isScreenBlocked()) { lockType = ChannelBannerView.LOCK_CHANNEL_INFO; - } else if (mTvView.isContentBlocked() || (mMainActivity.getParentalControlSettings() - .isParentalControlsEnabled() && !mTvView.isVideoOrAudioAvailable())) { + } else if (mTvView.isContentBlocked() + || (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() + && !mTvView.isVideoOrAudioAvailable())) { // If the parental control is enabled, do not show the program detail until the // video becomes available. lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL; @@ -843,19 +839,22 @@ public class TvOverlayManager { // If parental control is enabled, we shows program description when the video is // available, instead of tuning. Therefore we need to check it here if the program // description is previously hidden by parental control. - if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL && - lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) { + if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL + && lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) { mChannelBannerView.updateViews(false); } } else { - mChannelBannerView.updateViews(reason == UPDATE_CHANNEL_BANNER_REASON_TUNE - || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); + mChannelBannerView.updateViews( + reason == UPDATE_CHANNEL_BANNER_REASON_TUNE + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); } } - boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW - || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE - || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); - if (needToShowBanner && !mMainActivity.willShowOverlayUiWhenResume() + boolean needToShowBanner = + (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE + || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST); + if (needToShowBanner + && !mMainActivity.willShowOverlayUiWhenResume() && getCurrentDialog() == null && !isSetupFragmentActive() && !isNewSourcesFragmentActive()) { @@ -870,7 +869,8 @@ public class TvOverlayManager { } } - @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) { + @TvOverlayType + private int convertSceneToOverlayType(@SceneType int sceneType) { switch (sceneType) { case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER: return OVERLAY_TYPE_SCENE_CHANNEL_BANNER; @@ -940,13 +940,13 @@ public class TvOverlayManager { } private boolean isOnlyBannerOrNoneOpened() { - return (mOpenedOverlays & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER - & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) == 0; + return (mOpenedOverlays + & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER + & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) + == 0; } - /** - * Runs a given {@code action} after all the overlays are closed. - */ + /** Runs a given {@code action} after all the overlays are closed. */ public void runAfterOverlaysAreClosed(Runnable action) { if (canExecuteCloseAction()) { action.run(); @@ -955,9 +955,7 @@ public class TvOverlayManager { } } - /** - * Handles the onUserInteraction event of the {@link MainActivity}. - */ + /** Handles the onUserInteraction event of the {@link MainActivity}. */ public void onUserInteraction() { if (mSideFragmentManager.isActive()) { mSideFragmentManager.scheduleHideAll(); @@ -968,10 +966,9 @@ public class TvOverlayManager { } } - /** - * Handles the onKeyDown event of the {@link MainActivity}. - */ - @KeyHandlerResultType public int onKeyDown(int keyCode, KeyEvent event) { + /** Handles the onKeyDown event of the {@link MainActivity}. */ + @KeyHandlerResultType + public int onKeyDown(int keyCode, KeyEvent event) { if (mCurrentDialog != null) { // Consumes the keys while a Dialog is creating. return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; @@ -981,31 +978,34 @@ public class TvOverlayManager { // Consumes the keys which may trigger system's default music player. return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } - if (mMenu.isActive() || mSideFragmentManager.isActive() || mProgramGuide.isActive() - || mSetupFragmentActive || mNewSourcesFragmentActive) { + if (mMenu.isActive() + || mSideFragmentManager.isActive() + || mProgramGuide.isActive() + || mSetupFragmentActive + || mNewSourcesFragmentActive) { return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY; } if (mTransitionManager.isKeypadChannelSwitchActive()) { - return mKeypadChannelSwitchView.onKeyDown(keyCode, event) ? - MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED + return mKeypadChannelSwitchView.onKeyDown(keyCode, event) + ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; } if (mTransitionManager.isSelectInputActive()) { - return mSelectInputView.onKeyDown(keyCode, event) ? - MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED + return mSelectInputView.onKeyDown(keyCode, event) + ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; } return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; } - /** - * Handles the onKeyUp event of the {@link MainActivity}. - */ - @KeyHandlerResultType public int onKeyUp(int keyCode, KeyEvent event) { + /** Handles the onKeyUp event of the {@link MainActivity}. */ + @KeyHandlerResultType + public int onKeyUp(int keyCode, KeyEvent event) { // Handle media key here because it is related to the menu. if (isMediaStartKey(keyCode)) { // The media key should not be passed up to the system in any cases. - if (mCurrentDialog != null || mProgramGuide.isActive() + if (mCurrentDialog != null + || mProgramGuide.isActive() || mSideFragmentManager.isActive() || mSearchFragment.isVisible() || mTransitionManager.isKeypadChannelSwitchActive() @@ -1091,9 +1091,10 @@ public class TvOverlayManager { if (timeShiftManager.isPaused()) { timeShiftManager.play(); } - hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG - | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); + hideOverlays( + TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG + | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } if (mMenu.isActive()) { @@ -1109,8 +1110,8 @@ public class TvOverlayManager { mTransitionManager.goToEmptyScene(true); return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } - return mKeypadChannelSwitchView.onKeyUp(keyCode, event) ? - MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED + return mKeypadChannelSwitchView.onKeyUp(keyCode, event) + ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; } if (mTransitionManager.isSelectInputActive()) { @@ -1118,8 +1119,8 @@ public class TvOverlayManager { mTransitionManager.goToEmptyScene(true); return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } - return mSelectInputView.onKeyUp(keyCode, event) ? - MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED + return mSelectInputView.onKeyUp(keyCode, event) + ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED; } if (mSetupFragmentActive) { @@ -1139,9 +1140,7 @@ public class TvOverlayManager { return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH; } - /** - * Checks whether the given {@code keyCode} can start the system's music app or not. - */ + /** Checks whether the given {@code keyCode} can start the system's music app or not. */ private static boolean isMediaStartKey(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: @@ -1190,8 +1189,11 @@ public class TvOverlayManager { private final boolean mKeepSidePanelHistory; private final boolean mKeepProgramGuide; - PendingDialogAction(String tag, SafeDismissDialogFragment dialog, - boolean keepSidePanelHistory, boolean keepProgramGuide) { + PendingDialogAction( + String tag, + SafeDismissDialogFragment dialog, + boolean keepSidePanelHistory, + boolean keepProgramGuide) { mTag = tag; mDialog = dialog; mKeepSidePanelHistory = keepSidePanelHistory; diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java index 628bbb72..e3f72dc1 100644 --- a/src/com/android/tv/ui/TvTransitionManager.java +++ b/src/com/android/tv/ui/TvTransitionManager.java @@ -30,19 +30,23 @@ import android.view.View; import android.view.ViewGroup; 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 java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class TvTransitionManager extends TransitionManager { @Retention(RetentionPolicy.SOURCE) - @IntDef({SCENE_TYPE_EMPTY, SCENE_TYPE_CHANNEL_BANNER, SCENE_TYPE_INPUT_BANNER, - SCENE_TYPE_KEYPAD_CHANNEL_SWITCH, SCENE_TYPE_SELECT_INPUT}) + @IntDef({ + SCENE_TYPE_EMPTY, + SCENE_TYPE_CHANNEL_BANNER, + SCENE_TYPE_INPUT_BANNER, + SCENE_TYPE_KEYPAD_CHANNEL_SWITCH, + SCENE_TYPE_SELECT_INPUT + }) public @interface SceneType {} + public static final int SCENE_TYPE_EMPTY = 0; public static final int SCENE_TYPE_CHANNEL_BANNER = 1; public static final int SCENE_TYPE_INPUT_BANNER = 2; @@ -70,17 +74,24 @@ public class TvTransitionManager extends TransitionManager { private Listener mListener; - public TvTransitionManager(MainActivity mainActivity, ViewGroup sceneContainer, - ChannelBannerView channelBannerView, InputBannerView inputBannerView, - KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView) { + public TvTransitionManager( + MainActivity mainActivity, + ViewGroup sceneContainer, + ChannelBannerView channelBannerView, + InputBannerView inputBannerView, + KeypadChannelSwitchView keypadChannelSwitchView, + SelectInputView selectInputView) { mMainActivity = mainActivity; mSceneContainer = sceneContainer; mChannelBannerView = channelBannerView; mInputBannerView = inputBannerView; mKeypadChannelSwitchView = keypadChannelSwitchView; mSelectInputView = selectInputView; - mEmptyView = (FrameLayout) mMainActivity.getLayoutInflater().inflate( - R.layout.empty_info_banner, sceneContainer, false); + mEmptyView = + (FrameLayout) + mMainActivity + .getLayoutInflater() + .inflate(R.layout.empty_info_banner, sceneContainer, false); mCurrentSceneView = mEmptyView; } @@ -108,8 +119,10 @@ public class TvTransitionManager extends TransitionManager { if (mCurrentScene != mInputBannerScene) { // Show the input banner instead. LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams(); - lp.width = mCurrentScene == mSelectInputScene ? mSelectInputView.getWidth() - : FrameLayout.LayoutParams.WRAP_CONTENT; + lp.width = + mCurrentScene == mSelectInputScene + ? mSelectInputView.getWidth() + : FrameLayout.LayoutParams.WRAP_CONTENT; mInputBannerView.setLayoutParams(lp); mInputBannerView.updateLabel(); transitionTo(mInputBannerScene); @@ -154,33 +167,35 @@ public class TvTransitionManager extends TransitionManager { if (mInitialized) { return; } - mEnterAnimator = AnimatorInflater.loadAnimator(mMainActivity, - R.animator.channel_banner_enter); - mExitAnimator = AnimatorInflater.loadAnimator(mMainActivity, - R.animator.channel_banner_exit); + mEnterAnimator = + AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_enter); + mExitAnimator = + AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit); mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView); - mEmptyScene.setEnterAction(new Runnable() { - @Override - public void run() { - FrameLayout.LayoutParams emptySceneLayoutParams = - (FrameLayout.LayoutParams) mEmptyView.getLayoutParams(); - ViewGroup.MarginLayoutParams lp = - (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams(); - emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop(); - emptySceneLayoutParams.setMarginStart(lp.getMarginStart()); - emptySceneLayoutParams.height = mCurrentSceneView.getHeight(); - emptySceneLayoutParams.width = mCurrentSceneView.getWidth(); - mEmptyView.setLayoutParams(emptySceneLayoutParams); - setCurrentScene(mEmptyScene, mEmptyView); - } - }); - mEmptyScene.setExitAction(new Runnable() { - @Override - public void run() { - removeAllViewsFromOverlay(); - } - }); + mEmptyScene.setEnterAction( + new Runnable() { + @Override + public void run() { + FrameLayout.LayoutParams emptySceneLayoutParams = + (FrameLayout.LayoutParams) mEmptyView.getLayoutParams(); + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams(); + emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop(); + emptySceneLayoutParams.setMarginStart(lp.getMarginStart()); + emptySceneLayoutParams.height = mCurrentSceneView.getHeight(); + emptySceneLayoutParams.width = mCurrentSceneView.getWidth(); + mEmptyView.setLayoutParams(emptySceneLayoutParams); + setCurrentScene(mEmptyScene, mEmptyView); + } + }); + mEmptyScene.setExitAction( + new Runnable() { + @Override + public void run() { + removeAllViewsFromOverlay(); + } + }); mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView); mInputBannerScene = buildScene(mSceneContainer, mInputBannerView); @@ -189,18 +204,20 @@ public class TvTransitionManager extends TransitionManager { mCurrentScene = mEmptyScene; // Enter transitions - TransitionSet enter = new TransitionSet() - .addTransition(new SceneTransition(SceneTransition.ENTER)) - .addTransition(new Fade(Fade.IN)); + TransitionSet enter = + new TransitionSet() + .addTransition(new SceneTransition(SceneTransition.ENTER)) + .addTransition(new Fade(Fade.IN)); setTransition(mEmptyScene, mChannelBannerScene, enter); setTransition(mEmptyScene, mInputBannerScene, enter); setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter); setTransition(mEmptyScene, mSelectInputScene, enter); // Exit transitions - TransitionSet exit = new TransitionSet() - .addTransition(new SceneTransition(SceneTransition.EXIT)) - .addTransition(new Fade(Fade.OUT)); + TransitionSet exit = + new TransitionSet() + .addTransition(new SceneTransition(SceneTransition.EXIT)) + .addTransition(new Fade(Fade.OUT)); setTransition(mChannelBannerScene, mEmptyScene, exit); setTransition(mInputBannerScene, mEmptyScene, exit); setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit); @@ -220,10 +237,9 @@ public class TvTransitionManager extends TransitionManager { mInitialized = true; } - /** - * Returns the type of the given scene. - */ - @SceneType public int getSceneType(Scene scene) { + /** Returns the type of the given scene. */ + @SceneType + public int getSceneType(Scene scene) { if (scene == mChannelBannerScene) { return SCENE_TYPE_CHANNEL_BANNER; } else if (scene == mInputBannerScene) { @@ -257,21 +273,23 @@ public class TvTransitionManager extends TransitionManager { private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) { final Scene scene = new Scene(sceneRoot, (View) layout); - scene.setEnterAction(new Runnable() { - @Override - public void run() { - boolean wasEmptyScene = (mCurrentScene == mEmptyScene); - setCurrentScene(scene, (ViewGroup) layout); - layout.onEnterAction(wasEmptyScene); - } - }); - scene.setExitAction(new Runnable() { - @Override - public void run() { - removeAllViewsFromOverlay(); - layout.onExitAction(); - } - }); + scene.setEnterAction( + new Runnable() { + @Override + public void run() { + boolean wasEmptyScene = (mCurrentScene == mEmptyScene); + setCurrentScene(scene, (ViewGroup) layout); + layout.onEnterAction(wasEmptyScene); + } + }); + scene.setExitAction( + new Runnable() { + @Override + public void run() { + removeAllViewsFromOverlay(); + layout.onExitAction(); + } + }); return scene; } @@ -294,12 +312,10 @@ public class TvTransitionManager extends TransitionManager { } @Override - public void captureStartValues(TransitionValues transitionValues) { - } + public void captureStartValues(TransitionValues transitionValues) {} @Override - public void captureEndValues(TransitionValues transitionValues) { - } + public void captureEndValues(TransitionValues transitionValues) {} @Override public Animator createAnimator( @@ -311,9 +327,7 @@ public class TvTransitionManager extends TransitionManager { } } - /** - * An interface for notification of the scene transition. - */ + /** An interface for notification of the scene transition. */ public interface Listener { /** * Called when the scene changes. This method is called just before the scene transition. diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java index f042987a..23cd9718 100644 --- a/src/com/android/tv/ui/TvViewUiManager.java +++ b/src/com/android/tv/ui/TvViewUiManager.java @@ -42,7 +42,6 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; - import com.android.tv.Features; import com.android.tv.R; import com.android.tv.TvOptionsManager; @@ -50,8 +49,8 @@ import com.android.tv.data.DisplayMode; import com.android.tv.util.TvSettings; /** - * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. - * It also control the settings regarding TvView UI such as display mode. + * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. It + * also control the settings regarding TvView UI such as display mode. */ public class TvViewUiManager { private static final String TAG = "TvViewManager"; @@ -126,16 +125,19 @@ public class TvViewUiManager { private int mAppliedTvViewEndMargin; private float mAppliedVideoDisplayAspectRatio; - public TvViewUiManager(Context context, TunableTvView tvView, - FrameLayout contentView, TvOptionsManager tvOptionManager) { + public TvViewUiManager( + Context context, + TunableTvView tvView, + FrameLayout contentView, + TvOptionsManager tvOptionManager) { mContext = context; mResources = mContext.getResources(); mTvView = tvView; mContentView = contentView; mTvOptionsManager = tvOptionManager; - DisplayManager displayManager = (DisplayManager) mContext - .getSystemService(Context.DISPLAY_SERVICE); + DisplayManager displayManager = + (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); Point size = new Point(); display.getSize(size); @@ -143,8 +145,8 @@ public class TvViewUiManager { mWindowHeight = size.y; // Have an assumption that TvView Shrinking happens only in full screen. - mTvViewShrunkenStartMargin = mResources - .getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start); + mTvViewShrunkenStartMargin = + mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start); mTvViewShrunkenEndMargin = mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end) + mResources.getDimensionPixelSize(R.dimen.side_panel_width); @@ -152,10 +154,12 @@ public class TvViewUiManager { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - mLinearOutSlowIn = AnimationUtils - .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); - mFastOutLinearIn = AnimationUtils - .loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in); + mLinearOutSlowIn = + AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.linear_out_slow_in); + mFastOutLinearIn = + AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_linear_in); } public void onConfigurationChanged(final int windowWidth, final int windowHeight) { @@ -169,9 +173,9 @@ public class TvViewUiManager { } /** - * Initializes animator in advance of using the animator to improve animation performance. - * For fast first tune, it is not expected to be called in Activity.onCreate, but called - * a few seconds later after onCreate. + * Initializes animator in advance of using the animator to improve animation performance. For + * fast first tune, it is not expected to be called in Activity.onCreate, but called a few + * seconds later after onCreate. */ public void initAnimatorIfNeeded() { initTvAnimatorIfNeeded(); @@ -203,16 +207,14 @@ public class TvViewUiManager { setDisplayMode(mDisplayModeBeforeShrunken, false, true); } - /** - * Returns true, if TvView is shrunken. - */ + /** Returns true, if TvView is shrunken. */ public boolean isUnderShrunkenTvView() { return mIsUnderShrunkenTvView; } /** - * Returns true, if {@code displayMode} is available now. If screen ratio is matched to - * video ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available. + * Returns true, if {@code displayMode} is available now. If screen ratio is matched to video + * ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available. */ public boolean isDisplayModeAvailable(int displayMode) { if (displayMode == DisplayMode.MODE_NORMAL) { @@ -226,11 +228,15 @@ public class TvViewUiManager { if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) { Log.w(TAG, "Video size is currently unavailable"); if (DEBUG) { - Log.d(TAG, "isDisplayModeAvailable: " - + "viewWidth=" + viewWidth - + ", viewHeight=" + viewHeight - + ", videoDisplayAspectRatio=" + videoDisplayAspectRatio - ); + Log.d( + TAG, + "isDisplayModeAvailable: " + + "viewWidth=" + + viewWidth + + ", viewHeight=" + + viewHeight + + ", videoDisplayAspectRatio=" + + videoDisplayAspectRatio); } return false; } @@ -239,9 +245,7 @@ public class TvViewUiManager { return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON; } - /** - * Returns a constant defined in DisplayMode. - */ + /** Returns a constant defined in DisplayMode. */ public int getDisplayMode() { if (isDisplayModeAvailable(mDisplayMode)) { return mDisplayMode; @@ -264,49 +268,45 @@ public class TvViewUiManager { return prev; } - /** - * Restores the display mode to the display mode stored in preference. - */ + /** Restores the display mode to the display mode stored in preference. */ public void restoreDisplayMode(boolean animate) { - int displayMode = mSharedPreferences - .getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL); + int displayMode = + mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL); setDisplayMode(displayMode, false, animate); } - /** - * Updates TvView's aspect ratio. It should be called when video resolution is changed. - */ + /** Updates TvView's aspect ratio. It should be called when video resolution is changed. */ public void updateTvAspectRatio() { applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false); if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) { - mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration), - mFastOutLinearIn, null); + mTvView.fadeIn( + mResources.getInteger(R.integer.tvview_fade_in_duration), + mFastOutLinearIn, + null); } } - /** - * Fades in TvView. - */ + /** Fades in TvView. */ public void fadeInTvView() { if (mTvView.isFadedOut()) { - mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration), - mFastOutLinearIn, null); + mTvView.fadeIn( + mResources.getInteger(R.integer.tvview_fade_in_duration), + mFastOutLinearIn, + null); } } - /** - * Fades out TvView. - */ + /** Fades out TvView. */ public void fadeOutTvView(Runnable postAction) { if (!mTvView.isFadedOut()) { - mTvView.fadeOut(mResources.getInteger(R.integer.tvview_fade_out_duration), - mLinearOutSlowIn, postAction); + mTvView.fadeOut( + mResources.getInteger(R.integer.tvview_fade_out_duration), + mLinearOutSlowIn, + postAction); } } - /** - * This margins will be applied when applyDisplayMode is called. - */ + /** This margins will be applied when applyDisplayMode is called. */ private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) { mTvViewStartMargin = tvViewStartMargin; mTvViewEndMargin = tvViewEndMargin; @@ -316,8 +316,8 @@ public class TvViewUiManager { return mTvViewStartMargin == 0 && mTvViewEndMargin == 0; } - private void setBackgroundColor(int color, FrameLayout.LayoutParams targetLayoutParams, - boolean animate) { + private void setBackgroundColor( + int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate) { if (animate) { initBackgroundAnimatorIfNeeded(); if (mBackgroundAnimator.isStarted()) { @@ -327,12 +327,13 @@ public class TvViewUiManager { int decorViewWidth = mContentView.getWidth(); int decorViewHeight = mContentView.getHeight(); - boolean hasPillarBox = mTvView.getWidth() != decorViewWidth - || mTvView.getHeight() != decorViewHeight; - boolean willHavePillarBox = ((targetLayoutParams.width != LayoutParams.MATCH_PARENT) - && targetLayoutParams.width != decorViewWidth) || ( - (targetLayoutParams.height != LayoutParams.MATCH_PARENT) - && targetLayoutParams.height != decorViewHeight); + boolean hasPillarBox = + mTvView.getWidth() != decorViewWidth || mTvView.getHeight() != decorViewHeight; + boolean willHavePillarBox = + ((targetLayoutParams.width != LayoutParams.MATCH_PARENT) + && targetLayoutParams.width != decorViewWidth) + || ((targetLayoutParams.height != LayoutParams.MATCH_PARENT) + && targetLayoutParams.height != decorViewHeight); if (!isTvViewFullScreen() && !hasPillarBox) { // If there is no pillar box, no animation is needed. @@ -351,13 +352,27 @@ public class TvViewUiManager { mBackgroundColor = color; } - private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams, - FrameLayout.LayoutParams tvViewFrame, boolean animate) { + private void setTvViewPosition( + final FrameLayout.LayoutParams layoutParams, + FrameLayout.LayoutParams tvViewFrame, + boolean animate) { if (DEBUG) { - Log.d(TAG, "setTvViewPosition: w=" + layoutParams.width + " h=" + layoutParams.height - + " s=" + layoutParams.getMarginStart() + " t=" + layoutParams.topMargin - + " e=" + layoutParams.getMarginEnd() + " b=" + layoutParams.bottomMargin - + " animate=" + animate); + Log.d( + TAG, + "setTvViewPosition: w=" + + layoutParams.width + + " h=" + + layoutParams.height + + " s=" + + layoutParams.getMarginStart() + + " t=" + + layoutParams.topMargin + + " e=" + + layoutParams.getMarginEnd() + + " b=" + + layoutParams.bottomMargin + + " animate=" + + animate); } FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame; mTvViewLayoutParams = layoutParams; @@ -372,21 +387,25 @@ public class TvViewUiManager { mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame); } mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams); - mTvViewAnimator.setEvaluator(new TypeEvaluator() { - FrameLayout.LayoutParams lp; - @Override - public FrameLayout.LayoutParams evaluate(float fraction, - FrameLayout.LayoutParams startValue, FrameLayout.LayoutParams endValue) { - if (lp == null) { - lp = new FrameLayout.LayoutParams(0, 0); - lp.gravity = startValue.gravity; - } - interpolateMargins(lp, startValue, endValue, fraction); - return lp; - } - }); - mTvViewAnimator - .setInterpolator(isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn); + mTvViewAnimator.setEvaluator( + new TypeEvaluator() { + FrameLayout.LayoutParams lp; + + @Override + public FrameLayout.LayoutParams evaluate( + float fraction, + FrameLayout.LayoutParams startValue, + FrameLayout.LayoutParams endValue) { + if (lp == null) { + lp = new FrameLayout.LayoutParams(0, 0); + lp.gravity = startValue.gravity; + } + interpolateMargins(lp, startValue, endValue, fraction); + return lp; + } + }); + mTvViewAnimator.setInterpolator( + isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn); mTvViewAnimator.start(); } else { if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) { @@ -425,38 +444,42 @@ public class TvViewUiManager { mTvViewAnimator.setProperty( Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams")); mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration)); - mTvViewAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mCanceled = false; + mTvViewAnimator.addListener( + new AnimatorListenerAdapter() { + private boolean mCanceled = false; - @Override - public void onAnimationCancel(Animator animation) { - mCanceled = true; - } + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } - @Override - public void onAnimationEnd(Animator animation) { - if (mCanceled) { - mCanceled = false; - return; - } - mHandler.post(new Runnable() { @Override - public void run() { - setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false); + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + mCanceled = false; + return; + } + mHandler.post( + new Runnable() { + @Override + public void run() { + setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false); + } + }); + } + }); + mTvViewAnimator.addUpdateListener( + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + float fraction = animator.getAnimatedFraction(); + mLastAnimatedTvViewFrame = + (FrameLayout.LayoutParams) mTvView.getLayoutParams(); + interpolateMargins( + mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction); + mTvView.setLayoutParams(mLastAnimatedTvViewFrame); } }); - } - }); - mTvViewAnimator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - float fraction = animator.getAnimatedFraction(); - mLastAnimatedTvViewFrame = (FrameLayout.LayoutParams) mTvView.getLayoutParams(); - interpolateMargins(mLastAnimatedTvViewFrame, - mOldTvViewFrame, mTvViewFrame, fraction); - mTvView.setLayoutParams(mLastAnimatedTvViewFrame); - } - }); } private void initBackgroundAnimatorIfNeeded() { @@ -467,31 +490,33 @@ public class TvViewUiManager { mBackgroundAnimator = new ObjectAnimator(); mBackgroundAnimator.setTarget(mContentView); mBackgroundAnimator.setPropertyName("backgroundColor"); - mBackgroundAnimator - .setDuration(mResources.getInteger(R.integer.tvactivity_background_anim_duration)); - mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mHandler.post(new Runnable() { + mBackgroundAnimator.setDuration( + mResources.getInteger(R.integer.tvactivity_background_anim_duration)); + mBackgroundAnimator.addListener( + new AnimatorListenerAdapter() { @Override - public void run() { - mContentView.setBackgroundColor(mBackgroundColor); + public void onAnimationEnd(Animator animation) { + mHandler.post( + new Runnable() { + @Override + public void run() { + mContentView.setBackgroundColor(mBackgroundColor); + } + }); } }); - } - }); } - private void applyDisplayMode(float videoDisplayAspectRatio, boolean animate, - boolean forceUpdate) { + private void applyDisplayMode( + float videoDisplayAspectRatio, boolean animate, boolean forceUpdate) { if (videoDisplayAspectRatio <= 0f) { videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight; } if (mAppliedDisplayedMode == mDisplayMode && mAppliedTvViewStartMargin == mTvViewStartMargin && mAppliedTvViewEndMargin == mTvViewEndMargin - && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) < - DISPLAY_ASPECT_RATIO_EPSILON) { + && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) + < DISPLAY_ASPECT_RATIO_EPSILON) { if (!forceUpdate) { return; } @@ -507,14 +532,20 @@ public class TvViewUiManager { float availableAreaRatio = 0; if (availableAreaWidth <= 0 || availableAreaHeight <= 0) { displayMode = DisplayMode.MODE_FULL; - Log.w(TAG, "Some resolution info is missing during applyDisplayMode. (" - + "availableAreaWidth=" + availableAreaWidth + ", availableAreaHeight=" - + availableAreaHeight + ")"); + Log.w( + TAG, + "Some resolution info is missing during applyDisplayMode. (" + + "availableAreaWidth=" + + availableAreaWidth + + ", availableAreaHeight=" + + availableAreaHeight + + ")"); } else { availableAreaRatio = (float) availableAreaWidth / availableAreaHeight; } - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0, - ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity); + FrameLayout.LayoutParams layoutParams = + new FrameLayout.LayoutParams( + 0, 0, ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity); switch (displayMode) { case DisplayMode.MODE_ZOOM: if (videoDisplayAspectRatio < availableAreaRatio) { @@ -549,12 +580,18 @@ public class TvViewUiManager { int marginStart = (availableAreaWidth - layoutParams.width) / 2; layoutParams.setMarginStart(marginStart); int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2; - FrameLayout.LayoutParams tvViewFrame = createMarginLayoutParams( - mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); + FrameLayout.LayoutParams tvViewFrame = + createMarginLayoutParams( + mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); setTvViewPosition(layoutParams, tvViewFrame, animate); - setBackgroundColor(mResources.getColor(isTvViewFullScreen() - ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview, - null), layoutParams, animate); + setBackgroundColor( + mResources.getColor( + isTvViewFullScreen() + ? R.color.tvactivity_background + : R.color.tvactivity_background_on_shrunken_tvview, + null), + layoutParams, + animate); // Update the current display mode. mTvOptionsManager.onDisplayModeChanged(displayMode); @@ -564,12 +601,15 @@ public class TvViewUiManager { return (int) (start + (end - start) * fraction); } - private static void interpolateMargins(MarginLayoutParams out, - MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction) { + private static void interpolateMargins( + MarginLayoutParams out, + MarginLayoutParams startValue, + MarginLayoutParams endValue, + float fraction) { out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction); out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction); - out.setMarginStart(interpolate(startValue.getMarginStart(), endValue.getMarginStart(), - fraction)); + out.setMarginStart( + interpolate(startValue.getMarginStart(), endValue.getMarginStart(), fraction)); out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction)); out.width = interpolate(startValue.width, endValue.width, fraction); out.height = interpolate(startValue.height, endValue.height, fraction); @@ -586,4 +626,4 @@ public class TvViewUiManager { lp.height = mWindowHeight - topMargin - bottomMargin; return lp; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/ViewUtils.java b/src/com/android/tv/ui/ViewUtils.java index ac181752..f64a70b2 100644 --- a/src/com/android/tv/ui/ViewUtils.java +++ b/src/com/android/tv/ui/ViewUtils.java @@ -21,13 +21,10 @@ import android.animation.ValueAnimator; import android.util.Log; import android.view.View; import android.view.ViewGroup.LayoutParams; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -/** - * A class that includes convenience methods for view classes. - */ +/** A class that includes convenience methods for view classes. */ public class ViewUtils { private static final String TAG = "ViewUtils"; @@ -40,49 +37,49 @@ public class ViewUtils { try { method = View.class.getDeclaredMethod("setTransitionAlpha", Float.TYPE); method.invoke(v, alpha); - } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException - |InvocationTargetException e) { + } catch (NoSuchMethodException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { Log.e(TAG, "Fail to call View.setTransitionAlpha", e); } } /** * Creates an animator in view's height + * * @param target the {@link view} animator performs on. */ public static Animator createHeightAnimator( final View target, int initialHeight, int targetHeight) { ValueAnimator animator = ValueAnimator.ofInt(initialHeight, targetHeight); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - if (value == 0) { - if (target.getVisibility() != View.GONE) { - target.setVisibility(View.GONE); + animator.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + if (value == 0) { + if (target.getVisibility() != View.GONE) { + target.setVisibility(View.GONE); + } + } else { + if (target.getVisibility() != View.VISIBLE) { + target.setVisibility(View.VISIBLE); + } + setLayoutHeight(target, value); + } } - } else { - if (target.getVisibility() != View.VISIBLE) { - target.setVisibility(View.VISIBLE); - } - setLayoutHeight(target, value); - } - } - }); + }); return animator; } - /** - * Gets view's layout height. - */ + /** Gets view's layout height. */ public static int getLayoutHeight(View view) { LayoutParams layoutParams = view.getLayoutParams(); return layoutParams.height; } - /** - * Sets view's layout height. - */ + /** Sets view's layout height. */ public static void setLayoutHeight(View view, int height) { LayoutParams layoutParams = view.getLayoutParams(); if (height != layoutParams.height) { @@ -90,4 +87,4 @@ public class ViewUtils { view.setLayoutParams(layoutParams); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java index cd70a886..73c12080 100644 --- a/src/com/android/tv/ui/sidepanel/ActionItem.java +++ b/src/com/android/tv/ui/sidepanel/ActionItem.java @@ -18,7 +18,6 @@ package com.android.tv.ui.sidepanel; import android.view.View; import android.widget.TextView; - import com.android.tv.R; public abstract class ActionItem extends Item { @@ -52,4 +51,4 @@ public abstract class ActionItem extends Item { descriptionView.setVisibility(View.GONE); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java index 8389675e..07f7119f 100644 --- a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java +++ b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java @@ -19,7 +19,6 @@ package com.android.tv.ui.sidepanel; 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; @@ -34,25 +33,27 @@ public abstract class ChannelCheckItem extends CompoundButtonItem { private Channel mChannel; private TextView mProgramTitleView; private TextView mChannelNumberView; - private final ChannelListener mChannelListener = new ChannelListener() { - @Override - public void onChannelRemoved(Channel channel) { } - - @Override - public void onChannelUpdated(Channel channel) { - mChannel = channel; - } - }; - - private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener - = new OnCurrentProgramUpdatedListener() { - @Override - public void onCurrentProgramUpdated(long channelId, Program program) { - updateProgramTitle(program); - } - }; - - public ChannelCheckItem(Channel channel, + private final ChannelListener mChannelListener = + new ChannelListener() { + @Override + public void onChannelRemoved(Channel channel) {} + + @Override + public void onChannelUpdated(Channel channel) { + mChannel = channel; + } + }; + + private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener = + new OnCurrentProgramUpdatedListener() { + @Override + public void onCurrentProgramUpdated(long channelId, Program program) { + updateProgramTitle(program); + } + }; + + public ChannelCheckItem( + Channel channel, ChannelDataManager channelDataManager, ProgramDataManager programDataManager) { super(channel.getDisplayName(), ""); @@ -91,8 +92,8 @@ public abstract class ChannelCheckItem extends CompoundButtonItem { mChannelNumberView = (TextView) view.findViewById(R.id.channel_number); mProgramTitleView = (TextView) view.findViewById(R.id.program_title); mChannelDataManager.addChannelListener(mChannel.getId(), mChannelListener); - mProgramDataManager.addOnCurrentProgramUpdatedListener(mChannel.getId(), - mOnCurrentProgramUpdatedListener); + mProgramDataManager.addOnCurrentProgramUpdatedListener( + mChannel.getId(), mOnCurrentProgramUpdatedListener); } @Override @@ -105,8 +106,8 @@ public abstract class ChannelCheckItem extends CompoundButtonItem { @Override protected void onUnbind() { mChannelDataManager.removeChannelListener(mChannel.getId(), mChannelListener); - mProgramDataManager.removeOnCurrentProgramUpdatedListener(mChannel.getId(), - mOnCurrentProgramUpdatedListener); + mProgramDataManager.removeOnCurrentProgramUpdatedListener( + mChannel.getId(), mOnCurrentProgramUpdatedListener); mProgramTitleView = null; mChannelNumberView = null; super.onUnbind(); diff --git a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java index 79c2b0a7..ef828945 100644 --- a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java +++ b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java @@ -22,7 +22,6 @@ import android.view.View; import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; public class CheckBoxItem extends CompoundButtonItem { @@ -46,16 +45,17 @@ public class CheckBoxItem extends CompoundButtonItem { super.onBind(view); if (mLayoutForLargeDescription) { CompoundButton checkBox = (CompoundButton) view.findViewById(getCompoundButtonId()); - LinearLayout.LayoutParams lp = - (LinearLayout.LayoutParams) checkBox.getLayoutParams(); + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) checkBox.getLayoutParams(); lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.topMargin = view.getResources().getDimensionPixelOffset( - R.dimen.option_item_check_box_margin_top); + lp.topMargin = + view.getResources() + .getDimensionPixelOffset(R.dimen.option_item_check_box_margin_top); checkBox.setLayoutParams(lp); TypedValue outValue = new TypedValue(); - view.getResources().getValue(R.dimen.option_item_check_box_line_spacing_multiplier, - outValue, true); + view.getResources() + .getValue( + R.dimen.option_item_check_box_line_spacing_multiplier, outValue, true); TextView descriptionTextView = (TextView) view.findViewById(getDescriptionViewId()); descriptionTextView.setMaxLines(Integer.MAX_VALUE); @@ -77,4 +77,4 @@ public class CheckBoxItem extends CompoundButtonItem { protected void onSelected() { setChecked(!isChecked()); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java index 341e4350..c1e1d18a 100644 --- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java +++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java @@ -23,16 +23,14 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; import com.android.tv.util.CaptionSettings; - import java.util.ArrayList; import java.util.List; import java.util.Locale; public class ClosedCaptionFragment extends SideFragment { - private static final String TRACKER_LABEL ="closed caption" ; + private static final String TRACKER_LABEL = "closed caption"; private boolean mResetClosedCaption; private int mClosedCaptionOption; private String mClosedCaptionLanguage; @@ -66,8 +64,10 @@ public class ClosedCaptionFragment extends SideFragment { List tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_SUBTITLE); if (tracks != null && !tracks.isEmpty()) { - String selectedTrackId = captionSettings.isEnabled() ? - getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE) : null; + String selectedTrackId = + captionSettings.isEnabled() + ? getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE) + : null; ClosedCaptionOptionItem item = new ClosedCaptionOptionItem(null, null); items.add(item); if (selectedTrackId == null) { @@ -86,37 +86,41 @@ public class ClosedCaptionFragment extends SideFragment { } } if (getMainActivity().hasCaptioningSettingsActivity()) { - items.add(new ActionItem(getString(R.string.closed_caption_system_settings), - getString(R.string.closed_caption_system_settings_description)) { - @Override - protected void onSelected() { - getMainActivity().startSystemCaptioningSettingsActivity(); - } - - @Override - protected void onFocused() { - super.onFocused(); - if (mSelectedItem != null) { - getMainActivity().selectSubtitleTrack( - mSelectedItem.mOption, mSelectedItem.mTrackId); - } - } - }); + items.add( + new ActionItem( + getString(R.string.closed_caption_system_settings), + getString(R.string.closed_caption_system_settings_description)) { + @Override + protected void onSelected() { + getMainActivity().startSystemCaptioningSettingsActivity(); + } + + @Override + protected void onFocused() { + super.onFocused(); + if (mSelectedItem != null) { + getMainActivity() + .selectSubtitleTrack( + mSelectedItem.mOption, mSelectedItem.mTrackId); + } + } + }); } return items; } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onDestroyView() { if (mResetClosedCaption) { - getMainActivity().selectSubtitleLanguage(mClosedCaptionOption, mClosedCaptionLanguage, - mClosedCaptionTrackId); + getMainActivity() + .selectSubtitleLanguage( + mClosedCaptionOption, mClosedCaptionLanguage, mClosedCaptionTrackId); } super.onDestroyView(); } diff --git a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java index c2746937..cc09affb 100644 --- a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java +++ b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java @@ -19,7 +19,6 @@ package com.android.tv.ui.sidepanel; import android.view.View; import android.widget.CompoundButton; import android.widget.TextView; - import com.android.tv.R; public abstract class CompoundButtonItem extends Item { @@ -44,8 +43,8 @@ public abstract class CompoundButtonItem extends Item { mMaxLine = 0; } - public CompoundButtonItem(String checkedTitle, String uncheckedTitle, String description, - int maxLine) { + public CompoundButtonItem( + String checkedTitle, String uncheckedTitle, String description, int maxLine) { mCheckedTitle = checkedTitle; mUncheckedTitle = uncheckedTitle; mDescription = description; @@ -73,8 +72,10 @@ public abstract class CompoundButtonItem extends Item { descriptionView.setMaxLines(mMaxLine); } else { if (sDefaultMaxLine == 0) { - sDefaultMaxLine = view.getContext().getResources() - .getInteger(R.integer.option_item_description_max_lines); + sDefaultMaxLine = + view.getContext() + .getResources() + .getInteger(R.integer.option_item_description_max_lines); } descriptionView.setMaxLines(sDefaultMaxLine); } diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java index 297e69d9..ad2f13f1 100644 --- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java +++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java @@ -26,7 +26,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.SharedPreferencesUtils; @@ -35,7 +34,6 @@ import com.android.tv.data.ChannelNumber; import com.android.tv.ui.OnRepeatedKeyInterceptListener; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -67,36 +65,44 @@ public class CustomizeChannelListFragment extends SideFragment { mInitialChannelId = getMainActivity().getCurrentChannelId(); mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager); if (sGroupingType == null) { - SharedPreferences sharedPreferences = getContext().getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + SharedPreferences sharedPreferences = + getContext() + .getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, + Context.MODE_PRIVATE); sGroupingType = sharedPreferences.getInt(PREF_KEY_GROUP_SETTINGS, GROUP_BY_SOURCE); } } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list); - listView.setOnKeyInterceptListener(new OnRepeatedKeyInterceptListener(listView) { - @Override - public boolean onInterceptKeyEvent(KeyEvent event) { - // In order to send tune operation once for continuous channel up/down events, - // we only call the moveToChannel method on ACTION_UP event of channel switch keys. - if (event.getAction() == KeyEvent.ACTION_UP) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - if (mLastFocusedChannelId != Channel.INVALID_ID) { - getMainActivity().tuneToChannel( - getChannelDataManager().getChannel(mLastFocusedChannelId)); + listView.setOnKeyInterceptListener( + new OnRepeatedKeyInterceptListener(listView) { + @Override + public boolean onInterceptKeyEvent(KeyEvent event) { + // In order to send tune operation once for continuous channel up/down + // events, + // we only call the moveToChannel method on ACTION_UP event of channel + // switch keys. + if (event.getAction() == KeyEvent.ACTION_UP) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + if (mLastFocusedChannelId != Channel.INVALID_ID) { + getMainActivity() + .tuneToChannel( + getChannelDataManager() + .getChannel(mLastFocusedChannelId)); + } + break; } - break; + } + return super.onInterceptKeyEvent(event); } - } - return super.onInterceptKeyEvent(event); - } - }); + }); if (!mGroupByFragmentRunning) { getMainActivity().startShrunkenTvView(false, true); @@ -118,8 +124,8 @@ public class CustomizeChannelListFragment extends SideFragment { } mLastFocusedChannelId = mInitialChannelId; MainActivity tvActivity = getMainActivity(); - if (mLastFocusedChannelId != Channel.INVALID_ID && - mLastFocusedChannelId != tvActivity.getCurrentChannelId()) { + if (mLastFocusedChannelId != Channel.INVALID_ID + && mLastFocusedChannelId != tvActivity.getCurrentChannelId()) { tvActivity.tuneToChannel(getChannelDataManager().getChannel(mLastFocusedChannelId)); } } @@ -184,11 +190,11 @@ public class CustomizeChannelListFragment extends SideFragment { Collections.sort(channels, mChannelComparator); String inputId = null; - for (Channel channel: channels) { + for (Channel channel : channels) { if (!channel.getInputId().equals(inputId)) { inputId = channel.getInputId(); - String inputLabel = Utils.loadLabel(getActivity(), - mInputManager.getTvInputInfo(inputId)); + String inputLabel = + Utils.loadLabel(getActivity(), mInputManager.getTvInputInfo(inputId)); items.add(new DividerItem(inputLabel)); selectGroupItem = new SelectGroupItem(); items.add(selectGroupItem); @@ -204,27 +210,32 @@ public class CustomizeChannelListFragment extends SideFragment { items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_hd_sd))); SelectGroupItem selectGroupItem = null; ArrayList channels = new ArrayList<>(mChannels); - Collections.sort(channels, new Comparator() { - @Override - public int compare(Channel lhs, Channel rhs) { - boolean lhsHd = isHdChannel(lhs); - boolean rhsHd = isHdChannel(rhs); - if (lhsHd == rhsHd) { - return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); - } else { - return lhsHd ? -1 : 1; - } - } - }); + Collections.sort( + channels, + new Comparator() { + @Override + public int compare(Channel lhs, Channel rhs) { + boolean lhsHd = isHdChannel(lhs); + boolean rhsHd = isHdChannel(rhs); + if (lhsHd == rhsHd) { + return ChannelNumber.compare( + lhs.getDisplayNumber(), rhs.getDisplayNumber()); + } else { + return lhsHd ? -1 : 1; + } + } + }); Boolean isHdGroup = null; - for (Channel channel: channels) { + for (Channel channel : channels) { boolean isHd = isHdChannel(channel); if (isHdGroup == null || isHd != isHdGroup) { isHdGroup = isHd; - items.add(new DividerItem(isHd - ? getString(R.string.edit_channels_group_divider_for_hd) - : getString(R.string.edit_channels_group_divider_for_sd))); + items.add( + new DividerItem( + isHd + ? getString(R.string.edit_channels_group_divider_for_hd) + : getString(R.string.edit_channels_group_divider_for_sd))); selectGroupItem = new SelectGroupItem(); items.add(selectGroupItem); } @@ -237,8 +248,8 @@ public class CustomizeChannelListFragment extends SideFragment { private static boolean isHdChannel(Channel channel) { String videoFormat = channel.getVideoFormat(); - return videoFormat != null && - (Channels.VIDEO_FORMAT_720P.equals(videoFormat) + return videoFormat != null + && (Channels.VIDEO_FORMAT_720P.equals(videoFormat) || Channels.VIDEO_FORMAT_1080I.equals(videoFormat) || Channels.VIDEO_FORMAT_1080P.equals(videoFormat) || Channels.VIDEO_FORMAT_2160P.equals(videoFormat) @@ -274,9 +285,11 @@ public class CustomizeChannelListFragment extends SideFragment { break; } } - mTextView.setText(getString(mAllChecked - ? R.string.edit_channels_item_deselect_group - : R.string.edit_channels_item_select_group)); + mTextView.setText( + getString( + mAllChecked + ? R.string.edit_channels_item_deselect_group + : R.string.edit_channels_item_select_group)); } @Override @@ -290,9 +303,11 @@ public class CustomizeChannelListFragment extends SideFragment { } getChannelDataManager().notifyChannelBrowsableChanged(); mAllChecked = !mAllChecked; - mTextView.setText(getString(mAllChecked - ? R.string.edit_channels_item_deselect_group - : R.string.edit_channels_item_select_group)); + mTextView.setText( + getString( + mAllChecked + ? R.string.edit_channels_item_deselect_group + : R.string.edit_channels_item_select_group)); } } @@ -331,6 +346,7 @@ public class CustomizeChannelListFragment extends SideFragment { protected String getTitle() { return getString(R.string.side_panel_title_group_by); } + @Override public String getTrackerLabel() { return GroupBySubMenu.TRACKER_LABEL; @@ -339,40 +355,46 @@ public class CustomizeChannelListFragment extends SideFragment { @Override protected List getItemList() { List items = new ArrayList<>(); - items.add(new RadioButtonItem( - getString(R.string.edit_channels_group_by_sources)) { - @Override - protected void onSelected() { - super.onSelected(); - setGroupingType(GROUP_BY_SOURCE); - closeFragment(); - } - }); - items.add(new RadioButtonItem( - getString(R.string.edit_channels_group_by_hd_sd)) { - @Override - protected void onSelected() { - super.onSelected(); - setGroupingType(GROUP_BY_HD_SD); - closeFragment(); - } - }); + items.add( + new RadioButtonItem(getString(R.string.edit_channels_group_by_sources)) { + @Override + protected void onSelected() { + super.onSelected(); + setGroupingType(GROUP_BY_SOURCE); + closeFragment(); + } + }); + items.add( + new RadioButtonItem(getString(R.string.edit_channels_group_by_hd_sd)) { + @Override + protected void onSelected() { + super.onSelected(); + setGroupingType(GROUP_BY_HD_SD); + closeFragment(); + } + }); ((RadioButtonItem) items.get(sGroupingType)).setChecked(true); return items; } private void setGroupingType(int groupingType) { sGroupingType = groupingType; - SharedPreferences sharedPreferences = getContext().getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + SharedPreferences sharedPreferences = + getContext() + .getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, + Context.MODE_PRIVATE); sharedPreferences.edit().putInt(PREF_KEY_GROUP_SETTINGS, groupingType).apply(); } } private class GroupBySubMenu extends SubMenuItem { private static final String TRACKER_LABEL = "Group by"; + public GroupBySubMenu(String description) { - super(getString(R.string.edit_channels_item_group_by), description, + super( + getString(R.string.edit_channels_item_group_by), + description, getMainActivity().getOverlayManager().getSideFragmentManager()); } diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java index f633fa5a..766e206f 100644 --- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java +++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java @@ -16,27 +16,15 @@ package com.android.tv.ui.sidepanel; -import android.accounts.Account; -import android.app.Activity; -import android.support.annotation.NonNull; -import android.util.Log; -import android.widget.Toast; - import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.common.BuildConfig; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.experiments.Experiments; import com.android.tv.tuner.TunerPreferences; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; -/** - * Options for developers only - */ +/** Options for developers only */ public class DeveloperOptionFragment extends SideFragment { private static final String TAG = "DeveloperOptionFragment"; private static final String TRACKER_LABEL = "debug options"; @@ -55,36 +43,40 @@ public class DeveloperOptionFragment extends SideFragment { protected List getItemList() { List items = new ArrayList<>(); if (CommonFeatures.DVR.isEnabled(getContext())) { - items.add(new ActionItem(getString(R.string.dev_item_dvr_history)) { - @Override - protected void onSelected() { - getMainActivity().getOverlayManager().showDvrHistoryDialog(); - } - }); + items.add( + new ActionItem(getString(R.string.dev_item_dvr_history)) { + @Override + protected void onSelected() { + getMainActivity().getOverlayManager().showDvrHistoryDialog(); + } + }); } if (Utils.isDeveloper()) { - items.add(new ActionItem(getString(R.string.dev_item_watch_history)) { - @Override - protected void onSelected() { - getMainActivity().getOverlayManager().showRecentlyWatchedDialog(); - } - }); + items.add( + new ActionItem(getString(R.string.dev_item_watch_history)) { + @Override + protected void onSelected() { + getMainActivity().getOverlayManager().showRecentlyWatchedDialog(); + } + }); } - items.add(new SwitchItem(getString(R.string.dev_item_store_ts_on), - getString(R.string.dev_item_store_ts_off), - getString(R.string.dev_item_store_ts_description)) { - @Override - protected void onUpdate() { - super.onUpdate(); - setChecked(TunerPreferences.getStoreTsStream(getContext())); - } + items.add( + new SwitchItem( + getString(R.string.dev_item_store_ts_on), + getString(R.string.dev_item_store_ts_off), + getString(R.string.dev_item_store_ts_description)) { + @Override + protected void onUpdate() { + super.onUpdate(); + setChecked(TunerPreferences.getStoreTsStream(getContext())); + } - @Override - protected void onSelected() { - super.onSelected(); - TunerPreferences.setStoreTsStream(getContext(), isChecked()); - } - }); + @Override + protected void onSelected() { + super.onSelected(); + TunerPreferences.setStoreTsStream(getContext(), isChecked()); + } + }); if (Utils.isDeveloper()) { items.add( new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) { @@ -98,5 +90,4 @@ public class DeveloperOptionFragment extends SideFragment { } return items; } - -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java index 29792757..c51fb38e 100644 --- a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java +++ b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java @@ -17,11 +17,9 @@ package com.android.tv.ui.sidepanel; import android.content.Context; - import com.android.tv.R; import com.android.tv.data.DisplayMode; import com.android.tv.ui.TvViewUiManager; - import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/tv/ui/sidepanel/DividerItem.java b/src/com/android/tv/ui/sidepanel/DividerItem.java index 0edf8488..3c237ffc 100644 --- a/src/com/android/tv/ui/sidepanel/DividerItem.java +++ b/src/com/android/tv/ui/sidepanel/DividerItem.java @@ -18,14 +18,13 @@ package com.android.tv.ui.sidepanel; import android.view.View; import android.widget.TextView; - import com.android.tv.R; public class DividerItem extends Item { private TextView mTitleView; private String mTitle; - public DividerItem() { } + public DividerItem() {} public DividerItem(String title) { mTitle = title; @@ -46,8 +45,10 @@ public class DividerItem extends Item { } else { mTitleView.setVisibility(View.VISIBLE); mTitleView.setText(mTitle); - view.setMinimumHeight(view.getContext().getResources().getDimensionPixelOffset( - R.dimen.option_item_height)); + view.setMinimumHeight( + view.getContext() + .getResources() + .getDimensionPixelOffset(R.dimen.option_item_height)); } } @@ -57,5 +58,5 @@ public class DividerItem extends Item { } @Override - protected void onSelected() { } + protected void onSelected() {} } diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java index 4e47e75b..693b7536 100644 --- a/src/com/android/tv/ui/sidepanel/Item.java +++ b/src/com/android/tv/ui/sidepanel/Item.java @@ -35,9 +35,7 @@ public abstract class Item { } } - /** - * Sets the item to be clickable or not. - */ + /** Sets the item to be clickable or not. */ public void setClickable(boolean clickable) { mClickable = clickable; if (mItemView != null) { @@ -45,9 +43,7 @@ public abstract class Item { } } - /** - * Returns whether this item is enabled. - */ + /** Returns whether this item is enabled. */ public boolean isEnabled() { return mEnabled; } @@ -69,9 +65,9 @@ public abstract class Item { } /** - * Called after onBind is called and when {@link #notifyUpdated} is called. - * {@link #notifyUpdated} is usually called by {@link SideFragment#notifyItemChanged} and - * {@link SideFragment#notifyItemsChanged}. + * Called after onBind is called and when {@link #notifyUpdated} is called. {@link + * #notifyUpdated} is usually called by {@link SideFragment#notifyItemChanged} and {@link + * SideFragment#notifyItemsChanged}. */ protected void onUpdate() { setEnabledInternal(mItemView, mEnabled); @@ -80,12 +76,9 @@ public abstract class Item { protected abstract void onSelected(); - protected void onFocused() { - } + protected void onFocused() {} - /** - * Returns true if the item is bound, i.e., onBind is called. - */ + /** Returns true if the item is bound, i.e., onBind is called. */ protected boolean isBound() { return mItemView != null; } @@ -100,4 +93,4 @@ public abstract class Item { } } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java index 10c8c3e2..03b71c8c 100644 --- a/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java +++ b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java @@ -19,10 +19,8 @@ package com.android.tv.ui.sidepanel; import android.media.tv.TvTrackInfo; import android.text.TextUtils; import android.view.KeyEvent; - import com.android.tv.R; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; @@ -56,9 +54,11 @@ public class MultiAudioFragment extends SideFragment { boolean needToShowSampleRate = Utils.needToShowSampleRate(getActivity(), tracks); int pos = 0; for (final TvTrackInfo track : tracks) { - RadioButtonItem item = new MultiAudioOptionItem( - Utils.getMultiAudioString(getActivity(), track, needToShowSampleRate), - track.getId()); + RadioButtonItem item = + new MultiAudioOptionItem( + Utils.getMultiAudioString( + getActivity(), track, needToShowSampleRate), + track.getId()); if (track.getId().equals(mSelectedTrackId)) { item.setChecked(true); mInitialSelectedPosition = pos; diff --git a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java index e0477493..b6c36795 100644 --- a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java +++ b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java @@ -41,4 +41,4 @@ public class RadioButtonItem extends CompoundButtonItem { protected void onSelected() { setChecked(true); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java index 6a5b510c..2552b5dd 100644 --- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.media.tv.TvInputInfo; import android.view.View; import android.widget.Toast; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; @@ -35,13 +34,10 @@ import com.android.tv.tuner.TunerPreferences; import com.android.tv.util.PermissionUtils; import com.android.tv.util.SetupUtils; import com.android.tv.util.Utils; - import java.util.ArrayList; import java.util.List; -/** - * Shows Live TV settings. - */ +/** Shows Live TV settings. */ public class SettingsFragment extends SideFragment { private static final String TRACKER_LABEL = "settings"; @@ -58,58 +54,71 @@ public class SettingsFragment extends SideFragment { @Override protected List getItemList() { List items = new ArrayList<>(); - final Item customizeChannelListItem = new SubMenuItem( - getString(R.string.settings_channel_source_item_customize_channels), - getString(R.string.settings_channel_source_item_customize_channels_description), - getMainActivity().getOverlayManager().getSideFragmentManager()) { - @Override - protected SideFragment getFragment() { - return new CustomizeChannelListFragment(); - } + final Item customizeChannelListItem = + new SubMenuItem( + getString(R.string.settings_channel_source_item_customize_channels), + getString( + R.string + .settings_channel_source_item_customize_channels_description), + getMainActivity().getOverlayManager().getSideFragmentManager()) { + @Override + protected SideFragment getFragment() { + return new CustomizeChannelListFragment(); + } - @Override - protected void onBind(View view) { - super.onBind(view); - setEnabled(false); - } + @Override + protected void onBind(View view) { + super.onBind(view); + setEnabled(false); + } - @Override - protected void onUpdate() { - super.onUpdate(); - setEnabled(getChannelDataManager().getChannelCount() != 0); - } - }; + @Override + protected void onUpdate() { + super.onUpdate(); + setEnabled(getChannelDataManager().getChannelCount() != 0); + } + }; customizeChannelListItem.setEnabled(false); items.add(customizeChannelListItem); final MainActivity activity = getMainActivity(); - boolean hasNewInput = SetupUtils.getInstance(activity) - .hasNewInput(activity.getTvInputManagerHelper()); - items.add(new ActionItem( - getString(R.string.settings_channel_source_item_setup), - hasNewInput ? getString(R.string.settings_channel_source_item_setup_new_inputs) - : null) { - @Override - protected void onSelected() { - closeFragment(); - activity.getOverlayManager().showSetupFragment(); - } - }); + boolean hasNewInput = + SetupUtils.getInstance(activity).hasNewInput(activity.getTvInputManagerHelper()); + items.add( + new ActionItem( + getString(R.string.settings_channel_source_item_setup), + hasNewInput + ? getString(R.string.settings_channel_source_item_setup_new_inputs) + : null) { + @Override + protected void onSelected() { + closeFragment(); + activity.getOverlayManager().showSetupFragment(); + } + }); if (PermissionUtils.hasModifyParentalControls(getMainActivity())) { - items.add(new ActionItem( - getString(R.string.settings_parental_controls), getString( - activity.getParentalControlSettings().isParentalControlsEnabled() - ? R.string.option_toggle_parental_controls_on - : R.string.option_toggle_parental_controls_off)) { - @Override - protected void onSelected() { - getMainActivity().getOverlayManager() - .getSideFragmentManager().hideSidePanel(true); - PinDialogFragment fragment = PinDialogFragment - .create(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN); - getMainActivity().getOverlayManager() - .showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true); - } - }); + items.add( + new ActionItem( + getString(R.string.settings_parental_controls), + getString( + activity.getParentalControlSettings() + .isParentalControlsEnabled() + ? R.string.option_toggle_parental_controls_on + : R.string.option_toggle_parental_controls_off)) { + @Override + protected void onSelected() { + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() + .hideSidePanel(true); + PinDialogFragment fragment = + PinDialogFragment.create( + PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN); + getMainActivity() + .getOverlayManager() + .showDialogFragment( + PinDialogFragment.DIALOG_TAG, fragment, true); + } + }); } else { // Note: parental control is turned off, when MODIFY_PARENTAL_CONTROLS is not granted. // But, we may be able to turn on channel lock feature regardless of the permission. @@ -117,8 +126,10 @@ public class SettingsFragment extends SideFragment { } boolean showTrickplaySetting = false; if (TUNER.isEnabled(getContext())) { - for (TvInputInfo inputInfo : TvApplication.getSingletons(getContext()) - .getTvInputManagerHelper().getTvInputInfos(true, true)) { + for (TvInputInfo inputInfo : + TvApplication.getSingletons(getContext()) + .getTvInputManagerHelper() + .getTvInputInfos(true, true)) { if (Utils.isInternalTvInput(getContext(), inputInfo.getId())) { showTrickplaySetting = true; break; @@ -132,40 +143,45 @@ public class SettingsFragment extends SideFragment { } if (showTrickplaySetting) { items.add( - new SwitchItem(getString(R.string.settings_trickplay), + new SwitchItem( + getString(R.string.settings_trickplay), getString(R.string.settings_trickplay), getString(R.string.settings_trickplay_description), getResources().getInteger(R.integer.trickplay_description_max_lines)) { @Override protected void onUpdate() { super.onUpdate(); - boolean enabled = TunerPreferences.getTrickplaySetting(getContext()) - != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + boolean enabled = + TunerPreferences.getTrickplaySetting(getContext()) + != TunerPreferences.TRICKPLAY_SETTING_DISABLED; setChecked(enabled); } @Override protected void onSelected() { super.onSelected(); - @TunerPreferences.TrickplaySetting int setting = - isChecked() ? TunerPreferences.TRICKPLAY_SETTING_ENABLED + @TunerPreferences.TrickplaySetting + int setting = + isChecked() + ? TunerPreferences.TRICKPLAY_SETTING_ENABLED : TunerPreferences.TRICKPLAY_SETTING_DISABLED; TunerPreferences.setTrickplaySetting(getContext(), setting); } }); } - items.add(new ActionItem(getString(R.string.settings_send_feedback)) { - @Override - protected void onSelected() { - Intent intent = new Intent(Intent.ACTION_APP_ERROR); - ApplicationErrorReport report = new ApplicationErrorReport(); - report.packageName = report.processName = getContext().getPackageName(); - report.time = System.currentTimeMillis(); - report.type = ApplicationErrorReport.TYPE_NONE; - intent.putExtra(Intent.EXTRA_BUG_REPORT, report); - startActivityForResult(intent, 0); - } - }); + items.add( + new ActionItem(getString(R.string.settings_send_feedback)) { + @Override + protected void onSelected() { + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + ApplicationErrorReport report = new ApplicationErrorReport(); + report.packageName = report.processName = getContext().getPackageName(); + report.time = System.currentTimeMillis(); + report.type = ApplicationErrorReport.TYPE_NONE; + intent.putExtra(Intent.EXTRA_BUG_REPORT, report); + startActivityForResult(intent, 0); + } + }); if (Licenses.hasLicenses(getContext())) { items.add( new SubMenuItem( @@ -178,8 +194,10 @@ public class SettingsFragment extends SideFragment { }); } // Show version. - SimpleActionItem version = new SimpleActionItem(getString(R.string.settings_menu_version), - ((TvApplication) activity.getApplicationContext()).getVersionName()); + SimpleActionItem version = + new SimpleActionItem( + getString(R.string.settings_menu_version), + ((TvApplication) activity.getApplicationContext()).getVersionName()); version.setClickable(false); items.add(version); return items; diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java index 6bd921a2..0660a6f9 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragment.java +++ b/src/com/android/tv/ui/sidepanel/SideFragment.java @@ -28,18 +28,16 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.util.DurationTimer; import com.android.tv.analytics.HasTrackerLabel; import com.android.tv.analytics.Tracker; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ProgramDataManager; +import com.android.tv.util.DurationTimer; import com.android.tv.util.SystemProperties; import com.android.tv.util.ViewCache; - import java.util.List; public abstract class SideFragment extends Fragment implements HasTrackerLabel { @@ -74,8 +72,8 @@ public abstract class SideFragment extends Fragment implements H /** * @param hideKey the KeyCode used to hide the fragment - * @param debugHideKey the KeyCode used to hide the fragment if - * {@link SystemProperties#USE_DEBUG_KEYS}. + * @param debugHideKey the KeyCode used to hide the fragment if {@link + * SystemProperties#USE_DEBUG_KEYS}. */ public SideFragment(int hideKey, int debugHideKey) { mHideKey = hideKey; @@ -91,10 +89,11 @@ public abstract class SideFragment extends Fragment implements H } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = ViewCache.getInstance().getOrCreateView( - inflater, getFragmentLayoutResourceId(), container); + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = + ViewCache.getInstance() + .getOrCreateView(inflater, getFragmentLayoutResourceId(), container); TextView textView = (TextView) view.findViewById(R.id.side_panel_title); textView.setText(getTitle()); @@ -131,8 +130,8 @@ public abstract class SideFragment extends Fragment implements H public final boolean isHideKeyForThisPanel(int keyCode) { boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue(); - return mHideKey != KeyEvent.KEYCODE_UNKNOWN && - (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode)); + return mHideKey != KeyEvent.KEYCODE_UNKNOWN + && (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode)); } @Override @@ -195,16 +194,14 @@ public abstract class SideFragment extends Fragment implements H item.notifyUpdated(); } - /** - * Notifies all items of ItemAdapter has changed without structural changes. - */ + /** Notifies all items of ItemAdapter has changed without structural changes. */ protected void notifyItemsChanged() { notifyItemsChanged(0, mAdapter.getItemCount()); } /** - * Notifies some items of ItemAdapter has changed starting from position - * positionStart to the end without structural changes. + * Notifies some items of ItemAdapter has changed starting from position positionStart + * to the end without structural changes. */ protected void notifyItemsChanged(int positionStart) { notifyItemsChanged(positionStart, mAdapter.getItemCount() - positionStart); @@ -225,20 +222,20 @@ public abstract class SideFragment extends Fragment implements H } protected abstract String getTitle(); + @Override public abstract String getTrackerLabel(); + protected abstract List getItemList(); public interface SideFragmentListener { void onSideFragmentViewDestroyed(); } - /** - * Preloads the item views. - */ + /** Preloads the item views. */ public static void preloadItemViews(Context context) { - ViewCache.getInstance().putView( - context, R.layout.option_fragment, new FrameLayout(context), 1); + ViewCache.getInstance() + .putView(context, R.layout.option_fragment, new FrameLayout(context), 1); VerticalGridView fakeParent = new VerticalGridView(context); for (int id : PRELOAD_VIEW_IDS) { sRecycledViewPool.setMaxRecycledViews(id, PRELOAD_VIEW_SIZE); @@ -246,9 +243,7 @@ public abstract class SideFragment extends Fragment implements H } } - /** - * Releases the recycled view pool. - */ + /** Releases the recycled view pool. */ public static void releaseRecycledViewPool() { sRecycledViewPool.clear(); } diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java index d02d3fb7..b8482a5b 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java +++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java @@ -25,7 +25,6 @@ import android.app.FragmentTransaction; import android.os.Handler; import android.view.View; import android.view.ViewTreeObserver; - import com.android.tv.R; public class SideFragmentManager { @@ -46,16 +45,17 @@ public class SideFragmentManager { private final Animator mHideAnimator; private final Handler mHandler = new Handler(); - private final Runnable mHideAllRunnable = new Runnable() { - @Override - public void run() { - hideAll(true); - } - }; + private final Runnable mHideAllRunnable = + new Runnable() { + @Override + public void run() { + hideAll(true); + } + }; private final long mShowDurationMillis; - public SideFragmentManager(Activity activity, Runnable preShowRunnable, - Runnable postHideRunnable) { + public SideFragmentManager( + Activity activity, Runnable preShowRunnable, Runnable postHideRunnable) { mActivity = activity; mFragmentManager = mActivity.getFragmentManager(); mPreShowRunnable = preShowRunnable; @@ -66,16 +66,17 @@ public class SideFragmentManager { mShowAnimator.setTarget(mPanel); mHideAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit); mHideAnimator.setTarget(mPanel); - mHideAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Animation is still in running state at this point. - hideAllInternal(); - } - }); + mHideAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Animation is still in running state at this point. + hideAllInternal(); + } + }); - mShowDurationMillis = mActivity.getResources().getInteger( - R.integer.side_panel_show_duration); + mShowDurationMillis = + mActivity.getResources().getInteger(R.integer.side_panel_show_duration); } public int getCount() { @@ -90,16 +91,12 @@ public class SideFragmentManager { return mHideAnimator.isStarted(); } - /** - * Shows the given {@link SideFragment}. - */ + /** Shows the given {@link SideFragment}. */ public void show(SideFragment sideFragment) { show(sideFragment, true); } - /** - * Shows the given {@link SideFragment}. - */ + /** Shows the given {@link SideFragment}. */ public void show(SideFragment sideFragment, boolean showEnterAnimation) { if (isHiding()) { mHideAnimator.end(); @@ -114,7 +111,8 @@ public class SideFragmentManager { R.animator.side_panel_fragment_pop_exit); } ft.replace(R.id.side_fragment_container, sideFragment) - .addToBackStack(Integer.toString(mFragmentCount)).commit(); + .addToBackStack(Integer.toString(mFragmentCount)) + .commit(); mFragmentCount++; if (isFirst) { @@ -122,17 +120,18 @@ public class SideFragmentManager { // slide-in animation to prevent jankiness resulted by performing transition and // layouting at the same time with animation. mPanel.setVisibility(View.VISIBLE); - mShowOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this); - mShowOnGlobalLayoutListener = null; - if (mPreShowRunnable != null) { - mPreShowRunnable.run(); - } - mShowAnimator.start(); - } - }; + mShowOnGlobalLayoutListener = + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mShowOnGlobalLayoutListener = null; + if (mPreShowRunnable != null) { + mPreShowRunnable.run(); + } + mShowAnimator.start(); + } + }; mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener); } scheduleHideAll(); @@ -183,8 +182,8 @@ public class SideFragmentManager { } mPanel.setVisibility(View.GONE); - mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME, - FragmentManager.POP_BACK_STACK_INCLUSIVE); + mFragmentManager.popBackStack( + FIRST_BACKSTACK_RECORD_NAME, FragmentManager.POP_BACK_STACK_INCLUSIVE); mFragmentCount = 0; if (mPostHideRunnable != null) { @@ -193,8 +192,8 @@ public class SideFragmentManager { } /** - * Show the side panel with animation. If there are many entries in the fragment stack, - * the animation look like that there's only one fragment. + * Show the side panel with animation. If there are many entries in the fragment stack, the + * animation look like that there's only one fragment. * * @param withAnimation specifies if animation should be shown. */ @@ -211,8 +210,8 @@ public class SideFragmentManager { } /** - * Hide the side panel. This method just hide the panel and preserves the back - * stack. If you want to empty the back stack, call {@link #hideAll}. + * Hide the side panel. This method just hide the panel and preserves the back stack. If you + * want to empty the back stack, call {@link #hideAll}. */ public void hideSidePanel(boolean withAnimation) { mHandler.removeCallbacks(mHideAllRunnable); @@ -221,12 +220,13 @@ public class SideFragmentManager { AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit); hideAnimator.setTarget(mPanel); hideAnimator.start(); - hideAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mPanel.setVisibility(View.GONE); - } - }); + hideAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mPanel.setVisibility(View.GONE); + } + }); } else { mPanel.setVisibility(View.GONE); } @@ -236,21 +236,17 @@ public class SideFragmentManager { return mPanel.getVisibility() == View.VISIBLE; } - /** - * Resets the timer for hiding side fragment. - */ + /** Resets the timer for hiding side fragment. */ public void scheduleHideAll() { mHandler.removeCallbacks(mHideAllRunnable); mHandler.postDelayed(mHideAllRunnable, mShowDurationMillis); } - /** - * Should {@code keyCode} hide the current panel. - */ + /** Should {@code keyCode} hide the current panel. */ public boolean isHideKeyForCurrentPanel(int keyCode) { if (isActive()) { - SideFragment current = (SideFragment) mFragmentManager.findFragmentById( - R.id.side_fragment_container); + SideFragment current = + (SideFragment) mFragmentManager.findFragmentById(R.id.side_fragment_container); return current != null && current.isHideKeyForThisPanel(keyCode); } return false; diff --git a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java index 42553b66..4457086d 100644 --- a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java +++ b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java @@ -16,9 +16,7 @@ package com.android.tv.ui.sidepanel; -/** - * A simple item which shows title and description. - */ +/** A simple item which shows title and description. */ public class SimpleActionItem extends ActionItem { public SimpleActionItem(String title) { super(title); @@ -29,6 +27,5 @@ public class SimpleActionItem extends ActionItem { } @Override - protected void onSelected() { - } + protected void onSelected() {} } diff --git a/src/com/android/tv/ui/sidepanel/SubMenuItem.java b/src/com/android/tv/ui/sidepanel/SubMenuItem.java index 4b0e8e2c..5e4c77ca 100644 --- a/src/com/android/tv/ui/sidepanel/SubMenuItem.java +++ b/src/com/android/tv/ui/sidepanel/SubMenuItem.java @@ -16,7 +16,6 @@ package com.android.tv.ui.sidepanel; - public abstract class SubMenuItem extends ActionItem { private final SideFragmentManager mSideFragmentManager; diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java index 06591b62..8b5e1b64 100644 --- a/src/com/android/tv/ui/sidepanel/SwitchItem.java +++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java @@ -31,8 +31,8 @@ public class SwitchItem extends CompoundButtonItem { super(checkedTitle, uncheckedTitle, description); } - public SwitchItem(String checkedTitle, String uncheckedTitle, String description, - int maxLines) { + public SwitchItem( + String checkedTitle, String uncheckedTitle, String description, int maxLines) { super(checkedTitle, uncheckedTitle, description, maxLines); } @@ -50,4 +50,4 @@ public class SwitchItem extends CompoundButtonItem { protected void onSelected() { setChecked(!isChecked()); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java index ede5c268..f0396bfe 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java @@ -27,7 +27,6 @@ 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.data.Channel; import com.android.tv.data.ChannelNumber; @@ -37,7 +36,6 @@ import com.android.tv.ui.sidepanel.ChannelCheckItem; import com.android.tv.ui.sidepanel.DividerItem; import com.android.tv.ui.sidepanel.Item; import com.android.tv.ui.sidepanel.SideFragment; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -49,44 +47,52 @@ public class ChannelsBlockedFragment extends SideFragment { private final List mChannels = new ArrayList<>(); private long mLastFocusedChannelId = Channel.INVALID_ID; private int mSelectedPosition = INVALID_POSITION; - private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange, Uri uri) { - notifyItemsChanged(); - } - }; + private final ContentObserver mProgramUpdateObserver = + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + notifyItemsChanged(); + } + }; private final Item mLockAllItem = new BlockAllItem(); private final List mItems = new ArrayList<>(); @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); if (mSelectedPosition != INVALID_POSITION) { setSelectedPosition(mSelectedPosition); } VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list); - listView.setOnKeyInterceptListener(new OnRepeatedKeyInterceptListener(listView) { - @Override - public boolean onInterceptKeyEvent(KeyEvent event) { - // In order to send tune operation once for continuous channel up/down events, - // we only call the moveToChannel method on ACTION_UP event of channel switch keys. - if (event.getAction() == KeyEvent.ACTION_UP) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - if (mLastFocusedChannelId != Channel.INVALID_ID) { - getMainActivity().tuneToChannel( - getChannelDataManager().getChannel(mLastFocusedChannelId)); + listView.setOnKeyInterceptListener( + new OnRepeatedKeyInterceptListener(listView) { + @Override + public boolean onInterceptKeyEvent(KeyEvent event) { + // In order to send tune operation once for continuous channel up/down + // events, + // we only call the moveToChannel method on ACTION_UP event of channel + // switch keys. + if (event.getAction() == KeyEvent.ACTION_UP) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + if (mLastFocusedChannelId != Channel.INVALID_ID) { + getMainActivity() + .tuneToChannel( + getChannelDataManager() + .getChannel(mLastFocusedChannelId)); + } + break; } - break; + } + return super.onInterceptKeyEvent(event); } - } - return super.onInterceptKeyEvent(event); - } - }); - getActivity().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI, - true, mProgramUpdateObserver); + }); + getActivity() + .getContentResolver() + .registerContentObserver( + TvContract.Programs.CONTENT_URI, true, mProgramUpdateObserver); getMainActivity().startShrunkenTvView(true, true); return view; } @@ -115,22 +121,24 @@ public class ChannelsBlockedFragment extends SideFragment { mItems.add(mLockAllItem); mChannels.clear(); mChannels.addAll(getChannelDataManager().getChannelList()); - Collections.sort(mChannels, new Comparator() { - @Override - public int compare(Channel lhs, Channel rhs) { - if (lhs.isBrowsable() != rhs.isBrowsable()) { - return lhs.isBrowsable() ? -1 : 1; - } - return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber()); - } - }); + Collections.sort( + mChannels, + new Comparator() { + @Override + public int compare(Channel lhs, Channel rhs) { + if (lhs.isBrowsable() != rhs.isBrowsable()) { + return lhs.isBrowsable() ? -1 : 1; + } + return ChannelNumber.compare( + lhs.getDisplayNumber(), rhs.getDisplayNumber()); + } + }); final long currentChannelId = getMainActivity().getCurrentChannelId(); boolean hasHiddenChannels = false; for (Channel channel : mChannels) { if (!channel.isBrowsable() && !hasHiddenChannels) { - mItems.add(new DividerItem( - getString(R.string.option_channels_subheader_hidden))); + mItems.add(new DividerItem(getString(R.string.option_channels_subheader_hidden))); hasHiddenChannels = true; } mItems.add(new ChannelBlockedItem(channel)); @@ -186,8 +194,11 @@ public class ChannelsBlockedFragment extends SideFragment { } private void updateText() { - mTextView.setText(getString(areAllChannelsBlocked() ? - R.string.option_channels_unlock_all : R.string.option_channels_lock_all)); + mTextView.setText( + getString( + areAllChannelsBlocked() + ? R.string.option_channels_unlock_all + : R.string.option_channels_lock_all)); } private boolean areAllChannelsBlocked() { diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java index 9a4879fc..3bd066e0 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java @@ -18,7 +18,6 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.view.View; import android.widget.TextView; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.data.Channel; @@ -28,7 +27,6 @@ import com.android.tv.ui.sidepanel.Item; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.ui.sidepanel.SubMenuItem; import com.android.tv.ui.sidepanel.SwitchItem; - import java.util.ArrayList; import java.util.List; @@ -36,12 +34,13 @@ public class ParentalControlsFragment extends SideFragment { private static final String TRACKER_LABEL = "Parental controls"; private List mActionItems; - private final SideFragmentListener mSideFragmentListener = new SideFragmentListener() { - @Override - public void onSideFragmentViewDestroyed() { - notifyDataSetChanged(); - } - }; + private final SideFragmentListener mSideFragmentListener = + new SideFragmentListener() { + @Override + public void onSideFragmentViewDestroyed() { + notifyDataSetChanged(); + } + }; @Override protected String getTitle() { @@ -56,89 +55,103 @@ public class ParentalControlsFragment extends SideFragment { @Override protected List getItemList() { List items = new ArrayList<>(); - items.add(new SwitchItem(getString(R.string.option_toggle_parental_controls_on), - getString(R.string.option_toggle_parental_controls_off)) { - @Override - protected void onUpdate() { - super.onUpdate(); - setChecked(getMainActivity().getParentalControlSettings() - .isParentalControlsEnabled()); - } + items.add( + new SwitchItem( + getString(R.string.option_toggle_parental_controls_on), + getString(R.string.option_toggle_parental_controls_off)) { + @Override + protected void onUpdate() { + super.onUpdate(); + setChecked( + getMainActivity() + .getParentalControlSettings() + .isParentalControlsEnabled()); + } - @Override - protected void onSelected() { - super.onSelected(); - boolean checked = isChecked(); - getMainActivity().getParentalControlSettings().setParentalControlsEnabled(checked); - enableActionItems(checked); - } - }); + @Override + protected void onSelected() { + super.onSelected(); + boolean checked = isChecked(); + getMainActivity() + .getParentalControlSettings() + .setParentalControlsEnabled(checked); + enableActionItems(checked); + } + }); mActionItems = new ArrayList<>(); - mActionItems.add(new SubMenuItem(getString(R.string.option_channels_locked), "", - getMainActivity().getOverlayManager().getSideFragmentManager()) { - TextView mDescriptionView; - - @Override - protected SideFragment getFragment() { - SideFragment fragment = new ChannelsBlockedFragment(); - fragment.setListener(mSideFragmentListener); - return fragment; - } + mActionItems.add( + new SubMenuItem( + getString(R.string.option_channels_locked), + "", + getMainActivity().getOverlayManager().getSideFragmentManager()) { + TextView mDescriptionView; + + @Override + protected SideFragment getFragment() { + SideFragment fragment = new ChannelsBlockedFragment(); + fragment.setListener(mSideFragmentListener); + return fragment; + } - @Override - protected void onBind(View view) { - super.onBind(view); - mDescriptionView = (TextView) view.findViewById(R.id.description); - } + @Override + protected void onBind(View view) { + super.onBind(view); + mDescriptionView = (TextView) view.findViewById(R.id.description); + } - @Override - protected void onUpdate() { - super.onUpdate(); - int lockedAndBrowsableChannelCount = 0; - for (Channel channel : getChannelDataManager().getChannelList()) { - if (channel.isLocked() && channel.isBrowsable()) { - ++lockedAndBrowsableChannelCount; + @Override + protected void onUpdate() { + super.onUpdate(); + int lockedAndBrowsableChannelCount = 0; + for (Channel channel : getChannelDataManager().getChannelList()) { + if (channel.isLocked() && channel.isBrowsable()) { + ++lockedAndBrowsableChannelCount; + } + } + if (lockedAndBrowsableChannelCount > 0) { + mDescriptionView.setText( + Integer.toString(lockedAndBrowsableChannelCount)); + } else { + mDescriptionView.setText( + getMainActivity().getString(R.string.option_no_locked_channel)); + } } - } - if (lockedAndBrowsableChannelCount > 0) { - mDescriptionView.setText(Integer.toString(lockedAndBrowsableChannelCount)); - } else { - mDescriptionView.setText( - getMainActivity().getString(R.string.option_no_locked_channel)); - } - } - @Override - protected void onUnbind() { - super.onUnbind(); - mDescriptionView = null; - } - }); - mActionItems.add(new SubMenuItem(getString(R.string.option_program_restrictions), - ProgramRestrictionsFragment.getDescription(getMainActivity()), - getMainActivity().getOverlayManager().getSideFragmentManager()) { - @Override - protected SideFragment getFragment() { - SideFragment fragment = new ProgramRestrictionsFragment(); - fragment.setListener(mSideFragmentListener); - return fragment; - } - }); - mActionItems.add(new ActionItem(getString(R.string.option_change_pin)) { - @Override - protected void onSelected() { - final MainActivity tvActivity = getMainActivity(); - tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true); - PinDialogFragment fragment = PinDialogFragment.create( - PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN); - tvActivity.getOverlayManager().showDialogFragment(PinDialogFragment.DIALOG_TAG, - fragment, true); - } - }); + @Override + protected void onUnbind() { + super.onUnbind(); + mDescriptionView = null; + } + }); + mActionItems.add( + new SubMenuItem( + getString(R.string.option_program_restrictions), + ProgramRestrictionsFragment.getDescription(getMainActivity()), + getMainActivity().getOverlayManager().getSideFragmentManager()) { + @Override + protected SideFragment getFragment() { + SideFragment fragment = new ProgramRestrictionsFragment(); + fragment.setListener(mSideFragmentListener); + return fragment; + } + }); + mActionItems.add( + new ActionItem(getString(R.string.option_change_pin)) { + @Override + protected void onSelected() { + final MainActivity tvActivity = getMainActivity(); + tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true); + PinDialogFragment fragment = + PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN); + tvActivity + .getOverlayManager() + .showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true); + } + }); items.addAll(mActionItems); - enableActionItems(getMainActivity().getParentalControlSettings() - .isParentalControlsEnabled()); + enableActionItems( + getMainActivity().getParentalControlSettings().isParentalControlsEnabled()); return items; } diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java index 1df7fe59..ead3c105 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java @@ -21,19 +21,19 @@ import com.android.tv.R; import com.android.tv.ui.sidepanel.Item; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.ui.sidepanel.SubMenuItem; - import java.util.ArrayList; import java.util.List; public class ProgramRestrictionsFragment extends SideFragment { private static final String TRACKER_LABEL = "Program restrictions"; - private final SideFragmentListener mSideFragmentListener = new SideFragmentListener() { - @Override - public void onSideFragmentViewDestroyed() { - notifyDataSetChanged(); - } - }; + private final SideFragmentListener mSideFragmentListener = + new SideFragmentListener() { + @Override + public void onSideFragmentViewDestroyed() { + notifyDataSetChanged(); + } + }; public static String getDescription(MainActivity tvActivity) { return RatingsFragment.getDescription(tvActivity); @@ -53,33 +53,37 @@ public class ProgramRestrictionsFragment extends SideFragment { protected List getItemList() { List items = new ArrayList<>(); - items.add(new SubMenuItem(getString(R.string.option_country_rating_systems), - RatingSystemsFragment.getDescription(getMainActivity()), - getMainActivity().getOverlayManager().getSideFragmentManager()) { - @Override - protected SideFragment getFragment() { - SideFragment fragment = new RatingSystemsFragment(); - fragment.setListener(mSideFragmentListener); - return fragment; - } - }); + items.add( + new SubMenuItem( + getString(R.string.option_country_rating_systems), + RatingSystemsFragment.getDescription(getMainActivity()), + getMainActivity().getOverlayManager().getSideFragmentManager()) { + @Override + protected SideFragment getFragment() { + SideFragment fragment = new RatingSystemsFragment(); + fragment.setListener(mSideFragmentListener); + return fragment; + } + }); String ratingsDescription = RatingsFragment.getDescription(getMainActivity()); - SubMenuItem ratingsItem = new SubMenuItem(getString(R.string.option_ratings), - ratingsDescription, - getMainActivity().getOverlayManager().getSideFragmentManager()) { - @Override - protected SideFragment getFragment() { - SideFragment fragment = new RatingsFragment(); - fragment.setListener(mSideFragmentListener); - return fragment; - } - }; + SubMenuItem ratingsItem = + new SubMenuItem( + getString(R.string.option_ratings), + ratingsDescription, + getMainActivity().getOverlayManager().getSideFragmentManager()) { + @Override + protected SideFragment getFragment() { + SideFragment fragment = new RatingsFragment(); + fragment.setListener(mSideFragmentListener); + return fragment; + } + }; // When "None" is selected for rating systems, disable the Ratings option. - if (RatingSystemsFragment.getDescription(getMainActivity()).equals( - getString(R.string.option_no_enabled_rating_system))) { + if (RatingSystemsFragment.getDescription(getMainActivity()) + .equals(getString(R.string.option_no_enabled_rating_system))) { ratingsItem.setEnabled(false); } items.add(ratingsItem); return items; } -} \ No newline at end of file +} diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java index ba9adc50..e0f546f9 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java @@ -17,7 +17,6 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.os.Bundle; - import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.parental.ContentRatingSystem; @@ -28,7 +27,6 @@ import com.android.tv.ui.sidepanel.CheckBoxItem; import com.android.tv.ui.sidepanel.Item; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.util.TvSettings; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -57,7 +55,8 @@ public class RatingSystemsFragment extends SideFragment { builder.append(s.getDisplayName()); builder.append(", "); } - return builder.length() > 0 ? builder.substring(0, builder.length() - 2) + return builder.length() > 0 + ? builder.substring(0, builder.length() - 2) : tvActivity.getString(R.string.option_no_enabled_rating_system); } @@ -85,7 +84,8 @@ public class RatingSystemsFragment extends SideFragment { // Add default, custom and preselected content rating systems to the "short" list. for (ContentRatingSystem s : contentRatingSystems) { - if (!s.isCustom() && s.getCountries() != null + if (!s.isCustom() + && s.getCountries() != null && s.getCountries().contains(Locale.getDefault().getCountry())) { items.add(new RatingSystemItem(s)); } else if (s.isCustom() || parentalControlSettings.isContentRatingSystemEnabled(s)) { @@ -117,12 +117,13 @@ public class RatingSystemsFragment extends SideFragment { allItems.addAll(itemsHiddenMultipleCountries); // Add "See All" to the "short" list. - items.add(new ActionItem(getString(R.string.option_see_all_rating_systems)) { - @Override - protected void onSelected() { - setItems(allItems); - } - }); + items.add( + new ActionItem(getString(R.string.option_see_all_rating_systems)) { + @Override + protected void onSelected() { + setItems(allItems); + } + }); return items; } @@ -136,7 +137,8 @@ public class RatingSystemsFragment extends SideFragment { ContentRatingsManager manager = tvActivity.getContentRatingsManager(); ParentalControlSettings settings = tvActivity.getParentalControlSettings(); for (ContentRatingSystem s : contentRatingSystems) { - if (!s.isCustom() && s.getCountries() != null + if (!s.isCustom() + && s.getCountries() != null && s.getCountries().contains(Locale.getDefault().getCountry())) { settings.setContentRatingSystemEnabled(manager, s, true); } @@ -158,16 +160,21 @@ public class RatingSystemsFragment extends SideFragment { @Override protected void onUpdate() { super.onUpdate(); - setChecked(getMainActivity().getParentalControlSettings() - .isContentRatingSystemEnabled(mContentRatingSystem)); + setChecked( + getMainActivity() + .getParentalControlSettings() + .isContentRatingSystemEnabled(mContentRatingSystem)); } @Override protected void onSelected() { super.onSelected(); - getMainActivity().getParentalControlSettings().setContentRatingSystemEnabled( - getMainActivity().getContentRatingsManager(), mContentRatingSystem, - isChecked()); + getMainActivity() + .getParentalControlSettings() + .setContentRatingSystemEnabled( + getMainActivity().getContentRatingsManager(), + mContentRatingSystem, + isChecked()); } } } diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java index 7c8cecbe..882843c2 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java @@ -52,26 +52,23 @@ public class RatingsFragment extends SideFragment { static { sLevelResourceIdMap = new SparseIntArray(5); - sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, - R.string.option_rating_none); - sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, - R.string.option_rating_high); - sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_MEDIUM, - R.string.option_rating_medium); - sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, - R.string.option_rating_low); - sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_CUSTOM, - R.string.option_rating_custom); + sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, R.string.option_rating_none); + sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high); + sLevelResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium); + sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low); + sLevelResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom); sDescriptionResourceIdMap = new SparseIntArray(sLevelResourceIdMap.size()); - sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, - R.string.option_rating_high_description); - sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_MEDIUM, - R.string.option_rating_medium_description); - sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, - R.string.option_rating_low_description); - sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_CUSTOM, - R.string.option_rating_custom_description); + sDescriptionResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high_description); + sDescriptionResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium_description); + sDescriptionResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low_description); + sDescriptionResourceIdMap.put( + TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom_description); } private final List mRatingLevelItems = new ArrayList<>(); @@ -81,8 +78,8 @@ public class RatingsFragment extends SideFragment { private ParentalControlSettings mParentalControlSettings; public static String getDescription(MainActivity tvActivity) { - @ContentRatingLevel int currentLevel = - tvActivity.getParentalControlSettings().getContentRatingLevel(); + @ContentRatingLevel + int currentLevel = tvActivity.getParentalControlSettings().getContentRatingLevel(); if (sLevelResourceIdMap.indexOfKey(currentLevel) >= 0) { return tvActivity.getString(sLevelResourceIdMap.get(currentLevel)); } @@ -128,9 +125,10 @@ public class RatingsFragment extends SideFragment { boolean hasSubRating = false; items.add(new DividerItem(s.getDisplayName())); for (Rating rating : s.getRatings()) { - RatingItem item = rating.getSubRatings().isEmpty() ? - new RatingItem(s, rating) : - new RatingWithSubItem(s, rating); + RatingItem item = + rating.getSubRatings().isEmpty() + ? new RatingItem(s, rating) + : new RatingWithSubItem(s, rating); items.add(item); if (rating.getSubRatings().isEmpty()) { ratingItems.add(item); @@ -201,15 +199,18 @@ public class RatingsFragment extends SideFragment { } } - private void updateDependentRatingItems(ContentRatingSystem.Order order, - int selectedRatingOrderIndex, String contentRatingSystemId, boolean isChecked) { + private void updateDependentRatingItems( + ContentRatingSystem.Order order, + int selectedRatingOrderIndex, + String contentRatingSystemId, + boolean isChecked) { List ratingItems = mContentRatingSystemItemMap.get(contentRatingSystemId); if (ratingItems != null) { for (RatingItem item : ratingItems) { int ratingOrderIndex = item.getRatingOrderIndex(order); if (ratingOrderIndex != -1 && ((ratingOrderIndex > selectedRatingOrderIndex && isChecked) - || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) { + || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) { item.setRatingBlocked(isChecked); } } @@ -220,9 +221,11 @@ public class RatingsFragment extends SideFragment { private final int mRatingLevel; private RatingLevelItem(int ratingLevel) { - super(getString(sLevelResourceIdMap.get(ratingLevel)), - (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0) ? - getString(sDescriptionResourceIdMap.get(ratingLevel)) : null); + super( + getString(sLevelResourceIdMap.get(ratingLevel)), + (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0) + ? getString(sDescriptionResourceIdMap.get(ratingLevel)) + : null); mRatingLevel = ratingLevel; } @@ -302,8 +305,11 @@ public class RatingsFragment extends SideFragment { } // Automatically check/uncheck dependent ratings. for (int i = 0; i < mOrders.size(); i++) { - updateDependentRatingItems(mOrders.get(i), mOrderIndexes.get(i), - mContentRatingSystem.getId(), isChecked()); + updateDependentRatingItems( + mOrders.get(i), + mOrderIndexes.get(i), + mContentRatingSystem.getId(), + isChecked()); } } @@ -337,14 +343,16 @@ public class RatingsFragment extends SideFragment { @Override protected void onSelected() { - getMainActivity().getOverlayManager().getSideFragmentManager() + getMainActivity() + .getOverlayManager() + .getSideFragmentManager() .show(SubRatingsFragment.create(mContentRatingSystem, mRating.getName())); } @Override protected int getButtonDrawable() { - int blockedStatus = mParentalControlSettings.getBlockedStatus( - mContentRatingSystem, mRating); + int blockedStatus = + mParentalControlSettings.getBlockedStatus(mContentRatingSystem, mRating); if (blockedStatus == ParentalControlSettings.RATING_BLOCKED) { return R.drawable.btn_lock_material; } else if (blockedStatus == ParentalControlSettings.RATING_BLOCKED_PARTIAL) { @@ -354,11 +362,9 @@ public class RatingsFragment extends SideFragment { } } - /** - * Opens a dialog showing the sources of the rating descriptions. - */ + /** Opens a dialog showing the sources of the rating descriptions. */ public static class AttributionItem extends Item { - public final static String DIALOG_TAG = AttributionItem.class.getSimpleName(); + public static final String DIALOG_TAG = AttributionItem.class.getSimpleName(); public static final String TRACKER_LABEL = "Sources for content rating systems"; private final MainActivity mMainActivity; @@ -373,9 +379,11 @@ public class RatingsFragment extends SideFragment { @Override protected void onSelected() { - WebDialogFragment dialog = WebDialogFragment.newInstance( - LicenseUtils.RATING_SOURCE_FILE, - mMainActivity.getString(R.string.option_attribution), TRACKER_LABEL); + WebDialogFragment dialog = + WebDialogFragment.newInstance( + LicenseUtils.RATING_SOURCE_FILE, + mMainActivity.getString(R.string.option_attribution), + TRACKER_LABEL); mMainActivity.getOverlayManager().showDialogFragment(DIALOG_TAG, dialog, false); } } diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java index 4634b74c..b9b03ed1 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java @@ -21,7 +21,6 @@ import android.os.Bundle; import android.view.View; import android.widget.CompoundButton; import android.widget.ImageView; - import com.android.tv.R; import com.android.tv.parental.ContentRatingSystem; import com.android.tv.parental.ContentRatingSystem.Rating; @@ -30,7 +29,6 @@ import com.android.tv.ui.sidepanel.CheckBoxItem; import com.android.tv.ui.sidepanel.DividerItem; import com.android.tv.ui.sidepanel.Item; import com.android.tv.ui.sidepanel.SideFragment; - import java.util.ArrayList; import java.util.List; @@ -44,8 +42,8 @@ public class SubRatingsFragment extends SideFragment { private Rating mRating; private final List mSubRatingItems = new ArrayList<>(); - public static SubRatingsFragment create(ContentRatingSystem contentRatingSystem, - String ratingName) { + public static SubRatingsFragment create( + ContentRatingSystem contentRatingSystem, String ratingName) { SubRatingsFragment fragment = new SubRatingsFragment(); Bundle args = new Bundle(); args.putString(ARGS_CONTENT_RATING_SYSTEM_ID, contentRatingSystem.getId()); @@ -57,8 +55,11 @@ public class SubRatingsFragment extends SideFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mContentRatingSystem = getMainActivity().getContentRatingsManager() - .getContentRatingSystem(getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID)); + mContentRatingSystem = + getMainActivity() + .getContentRatingsManager() + .getContentRatingSystem( + getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID)); if (mContentRatingSystem != null) { mRating = mContentRatingSystem.getRating(getArguments().getString(ARGS_RATING_NAME)); } @@ -194,22 +195,26 @@ public class SubRatingsFragment extends SideFragment { } private boolean isRatingEnabled() { - return getMainActivity().getParentalControlSettings() + return getMainActivity() + .getParentalControlSettings() .isRatingBlocked(mContentRatingSystem, mRating); } private boolean isSubRatingEnabled(SubRating subRating) { - return getMainActivity().getParentalControlSettings() + return getMainActivity() + .getParentalControlSettings() .isSubRatingEnabled(mContentRatingSystem, mRating, subRating); } private void setRatingEnabled(boolean enabled) { - getMainActivity().getParentalControlSettings() + getMainActivity() + .getParentalControlSettings() .setRatingBlocked(mContentRatingSystem, mRating, enabled); } private void setSubRatingEnabled(SubRating subRating, boolean enabled) { - getMainActivity().getParentalControlSettings() + getMainActivity() + .getParentalControlSettings() .setSubRatingBlocked(mContentRatingSystem, mRating, subRating, enabled); } } diff --git a/src/com/android/tv/util/AccountHelper.java b/src/com/android/tv/util/AccountHelper.java index ece13de1..a3e6ad58 100644 --- a/src/com/android/tv/util/AccountHelper.java +++ b/src/com/android/tv/util/AccountHelper.java @@ -19,17 +19,10 @@ package com.android.tv.util; import android.accounts.Account; import android.content.Context; import android.content.SharedPreferences; -import android.os.RemoteException; import android.preference.PreferenceManager; import android.support.annotation.Nullable; -import android.util.Log; - -import java.util.Arrays; - -/** - * Helper methods for getting and selecting a user account. - */ +/** Helper methods for getting and selecting a user account. */ public class AccountHelper { private static final String TAG = "AccountHelper"; private static final boolean DEBUG = false; @@ -38,17 +31,14 @@ public class AccountHelper { private final Context mContext; private final SharedPreferences mDefaultPreferences; - @Nullable - private Account mSelectedAccount; + @Nullable private Account mSelectedAccount; public AccountHelper(Context context) { mContext = context.getApplicationContext(); mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); } - /** - * Returns the currently selected account or {@code null} if none is selected. - */ + /** Returns the currently selected account or {@code null} if none is selected. */ @Nullable public Account getSelectedAccount() { String accountId = mDefaultPreferences.getString(SELECTED_ACCOUNT, null); @@ -67,9 +57,7 @@ public class AccountHelper { return mSelectedAccount; } - /** - * Returns all eligible accounts . - */ + /** Returns all eligible accounts . */ private Account[] getEligibleAccounts() { return new Account[0]; } @@ -99,13 +87,10 @@ public class AccountHelper { return accounts.length == 0 ? null : accounts[0]; } - /** - * Sets the given account as the selected account. - */ + /** Sets the given account as the selected account. */ private void selectAccount(Account account) { - SharedPreferences defaultPreferences = PreferenceManager - .getDefaultSharedPreferences(mContext); + SharedPreferences defaultPreferences = + PreferenceManager.getDefaultSharedPreferences(mContext); defaultPreferences.edit().putString(SELECTED_ACCOUNT, account.name).commit(); } } - diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java index 477412e4..376fcc70 100644 --- a/src/com/android/tv/util/AsyncDbTask.java +++ b/src/com/android/tv/util/AsyncDbTask.java @@ -27,12 +27,10 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; - import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.dvr.data.RecordedProgram; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -54,14 +52,12 @@ public abstract class AsyncDbTask private static final String TAG = "AsyncDbTask"; private static final boolean DEBUG = false; - private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory( - AsyncDbTask.class.getSimpleName()); - private static final ExecutorService DB_EXECUTOR = Executors - .newSingleThreadExecutor(THREAD_FACTORY); + private static final NamedThreadFactory THREAD_FACTORY = + new NamedThreadFactory(AsyncDbTask.class.getSimpleName()); + private static final ExecutorService DB_EXECUTOR = + Executors.newSingleThreadExecutor(THREAD_FACTORY); - /** - * Returns the single tread executor used for DbTasks. - */ + /** Returns the single tread executor used for DbTasks. */ public static ExecutorService getExecutor() { return DB_EXECUTOR; } @@ -72,9 +68,8 @@ public abstract class AsyncDbTask *

The command will be executed by {@link #getExecutor()}. * * @param command the runnable task - * @throws RejectedExecutionException if this task cannot be - * accepted for execution - * @throws NullPointerException if command is null + * @throws RejectedExecutionException if this task cannot be accepted for execution + * @throws NullPointerException if command is null */ public static void executeOnDbThread(Runnable command) { DB_EXECUTOR.execute(command); @@ -84,8 +79,8 @@ public abstract class AsyncDbTask * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[], * String)}. * - *

{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} - * which is implemented by subclasses. + *

{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} which + * is implemented by subclasses. * * @param the type of result returned by {@link #onQuery(Cursor)} */ @@ -97,9 +92,13 @@ public abstract class AsyncDbTask private final String[] mSelectionArgs; private final String mOrderBy; - - public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy) { + public AsyncQueryTask( + ContentResolver contentResolver, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String orderBy) { mContentResolver = contentResolver; mUri = uri; mProjection = projection; @@ -111,10 +110,12 @@ public abstract class AsyncDbTask @Override protected final Result doInBackground(Void... params) { if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) { - IllegalStateException e = new IllegalStateException(this - + " should only be executed using executeOnDbThread, " - + "but it was called on thread " - + Thread.currentThread()); + IllegalStateException e = + new IllegalStateException( + this + + " should only be executed using executeOnDbThread, " + + "but it was called on thread " + + Thread.currentThread()); Log.w(TAG, e); if (DEBUG) { throw e; @@ -128,8 +129,9 @@ public abstract class AsyncDbTask if (DEBUG) { Log.v(TAG, "Starting query for " + this); } - try (Cursor c = mContentResolver - .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) { + try (Cursor c = + mContentResolver.query( + mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) { if (c != null && !isCancelled()) { Result result = onQuery(c); if (DEBUG) { @@ -176,13 +178,24 @@ public abstract class AsyncDbTask public abstract static class AsyncQueryListTask extends AsyncQueryTask> { private final CursorFilter mFilter; - public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy) { + public AsyncQueryListTask( + ContentResolver contentResolver, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String orderBy) { this(contentResolver, uri, projection, selection, selectionArgs, orderBy, null); } - public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy, CursorFilter filter) { + public AsyncQueryListTask( + ContentResolver contentResolver, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String orderBy, + CursorFilter filter) { super(contentResolver, uri, projection, selection, selectionArgs, orderBy); mFilter = filter; } @@ -228,8 +241,13 @@ public abstract class AsyncDbTask */ public abstract static class AsyncQueryItemTask extends AsyncQueryTask { - public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, - String selection, String[] selectionArgs, String orderBy) { + public AsyncQueryItemTask( + ContentResolver contentResolver, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String orderBy) { super(contentResolver, uri, projection, selection, selectionArgs, orderBy); } @@ -251,7 +269,6 @@ public abstract class AsyncDbTask } return null; } - } /** @@ -268,14 +285,17 @@ public abstract class AsyncDbTask protected abstract T fromCursor(Cursor c); } - /** - * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. - */ + /** Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. */ public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask { public AsyncChannelQueryTask(ContentResolver contentResolver) { - super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION, - null, null, null); + super( + contentResolver, + TvContract.Channels.CONTENT_URI, + Channel.PROJECTION, + null, + null, + null); } @Override @@ -284,17 +304,26 @@ public abstract class AsyncDbTask } } - /** - * Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. - */ + /** Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. */ public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask { public AsyncProgramQueryTask(ContentResolver contentResolver) { super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null); } - public AsyncProgramQueryTask(ContentResolver contentResolver, Uri uri, String selection, - String[] selectionArgs, String sortOrder, CursorFilter filter) { - super(contentResolver, uri, Program.PROJECTION, selection, selectionArgs, sortOrder, + public AsyncProgramQueryTask( + ContentResolver contentResolver, + Uri uri, + String selection, + String[] selectionArgs, + String sortOrder, + CursorFilter filter) { + super( + contentResolver, + uri, + Program.PROJECTION, + selection, + selectionArgs, + sortOrder, filter); } @@ -304,9 +333,7 @@ public abstract class AsyncDbTask } } - /** - * Gets an {@link List} of {@link TvContract.RecordedPrograms}s. - */ + /** Gets an {@link List} of {@link TvContract.RecordedPrograms}s. */ public abstract static class AsyncRecordedProgramQueryTask extends AsyncQueryListTask { public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) { @@ -319,9 +346,7 @@ public abstract class AsyncDbTask } } - /** - * Execute the task on the {@link #DB_EXECUTOR} thread. - */ + /** Execute the task on the {@link #DB_EXECUTOR} thread. */ @SafeVarargs @MainThread public final void executeOnDbThread(Params... params) { @@ -330,20 +355,25 @@ public abstract class AsyncDbTask /** * Gets an {@link List} of {@link Program}s for a given channel and period {@link - * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is - * {@code null}, then all the programs is queried. + * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is {@code + * null}, then all the programs is queried. */ public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask { protected final Range mPeriod; protected final long mChannelId; - public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, - @Nullable Range period) { - super(contentResolver, period == null - ? TvContract.buildProgramsUriForChannel(channelId) - : TvContract.buildProgramsUriForChannel(channelId, period.getLower(), - period.getUpper()), - null, null, null, null); + public LoadProgramsForChannelTask( + ContentResolver contentResolver, long channelId, @Nullable Range period) { + super( + contentResolver, + period == null + ? TvContract.buildProgramsUriForChannel(channelId) + : TvContract.buildProgramsUriForChannel( + channelId, period.getLower(), period.getUpper()), + null, + null, + null, + null); mPeriod = period; mChannelId = channelId; } @@ -357,14 +387,17 @@ public abstract class AsyncDbTask } } - /** - * Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. - */ + /** Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. */ public static class AsyncQueryProgramTask extends AsyncQueryItemTask { public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) { - super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null, - null, null); + super( + contentResolver, + TvContract.buildProgramUri(programId), + Program.PROJECTION, + null, + null, + null); } @Override @@ -373,8 +406,6 @@ public abstract class AsyncDbTask } } - /** - * An interface which filters the row. - */ - public interface CursorFilter extends Filter { } + /** An interface which filters the row. */ + public interface CursorFilter extends Filter {} } diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/BitmapUtils.java index fbaab023..6902a6fe 100644 --- a/src/com/android/tv/util/BitmapUtils.java +++ b/src/com/android/tv/util/BitmapUtils.java @@ -29,7 +29,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; - import java.io.BufferedInputStream; import java.io.Closeable; import java.io.IOException; @@ -45,12 +44,14 @@ public final class BitmapUtils { // The value of 64K, for MARK_READ_LIMIT, is chosen to be eight times the default buffer size // of BufferedInputStream (8K) allowing it to double its buffers three times. Also it is a // fairly reasonable value, not using too much memory and being large enough for most cases. - private static final int MARK_READ_LIMIT = 64 * 1024; // 64K + private static final int MARK_READ_LIMIT = 64 * 1024; // 64K - private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec - private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec + private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec + private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec - private BitmapUtils() { /* cannot be instantiated */ } + private BitmapUtils() { + /* cannot be instantiated */ + } public static Bitmap scaleBitmap(Bitmap bm, int maxWidth, int maxHeight) { Rect rect = calculateNewSize(bm, maxWidth, maxHeight); @@ -59,7 +60,8 @@ public final class BitmapUtils { public static Bitmap getScaledMutableBitmap(Bitmap bm, int maxWidth, int maxHeight) { Bitmap scaledBitmap = scaleBitmap(bm, maxWidth, maxHeight); - return scaledBitmap.isMutable() ? scaledBitmap + return scaledBitmap.isMutable() + ? scaledBitmap : scaledBitmap.copy(Bitmap.Config.ARGB_8888, true); } @@ -77,17 +79,17 @@ public final class BitmapUtils { return rect; } - public static ScaledBitmapInfo createScaledBitmapInfo(String id, Bitmap bm, int maxWidth, - int maxHeight) { - return new ScaledBitmapInfo(id, scaleBitmap(bm, maxWidth, maxHeight), + public static ScaledBitmapInfo createScaledBitmapInfo( + String id, Bitmap bm, int maxWidth, int maxHeight) { + return new ScaledBitmapInfo( + id, + scaleBitmap(bm, maxWidth, maxHeight), calculateInSampleSize(bm.getWidth(), bm.getHeight(), maxWidth, maxHeight)); } - /** - * Decode large sized bitmap into requested size. - */ - public static ScaledBitmapInfo decodeSampledBitmapFromUriString(Context context, - String uriString, int reqWidth, int reqHeight) { + /** Decode large sized bitmap into requested size. */ + public static ScaledBitmapInfo decodeSampledBitmapFromUriString( + Context context, String uriString, int reqWidth, int reqHeight) { if (TextUtils.isEmpty(uriString)) { return null; } @@ -162,8 +164,8 @@ public final class BitmapUtils { return urlConnection; } - private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, - int reqHeight) { + private static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); } @@ -187,7 +189,7 @@ public final class BitmapUtils { closeable.close(); } catch (IOException e) { // Log and continue. - Log.w(TAG,"Error closing " + closeable, e); + Log.w(TAG, "Error closing " + closeable, e); } } if (urlConnection instanceof HttpURLConnection) { @@ -195,21 +197,13 @@ public final class BitmapUtils { } } - /** - * A wrapper class which contains the loaded bitmap and the scaling information. - */ + /** A wrapper class which contains the loaded bitmap and the scaling information. */ public static class ScaledBitmapInfo { - /** - * The id of bitmap, usually this is the URI of the original. - */ - @NonNull - public final String id; + /** The id of bitmap, usually this is the URI of the original. */ + @NonNull public final String id; - /** - * The loaded bitmap object. - */ - @NonNull - public final Bitmap bitmap; + /** The loaded bitmap object. */ + @NonNull public final Bitmap bitmap; /** * The scaling factor to the original bitmap. It should be an positive integer. @@ -222,8 +216,8 @@ public final class BitmapUtils { * A constructor. * * @param bitmap The loaded bitmap object. - * @param inSampleSize The sampling size. - * See {@link android.graphics.BitmapFactory.Options#inSampleSize} + * @param inSampleSize The sampling size. See {@link + * android.graphics.BitmapFactory.Options#inSampleSize} */ public ScaledBitmapInfo(@NonNull String id, @NonNull Bitmap bitmap, int inSampleSize) { this.id = id; @@ -232,10 +226,9 @@ public final class BitmapUtils { } /** - * Checks if the bitmap needs to be reloaded. The scaling is performed by power 2. - * The bitmap can be reloaded only if the required width or height is greater then or equal - * to the existing bitmap. - * If the full sized bitmap is already loaded, returns {@code false}. + * Checks if the bitmap needs to be reloaded. The scaling is performed by power 2. The + * bitmap can be reloaded only if the required width or height is greater then or equal to + * the existing bitmap. If the full sized bitmap is already loaded, returns {@code false}. * * @see android.graphics.BitmapFactory.Options#inSampleSize */ @@ -245,26 +238,41 @@ public final class BitmapUtils { return false; } Rect size = calculateNewSize(this.bitmap, reqWidth, reqHeight); - boolean reload = (size.right >= bitmap.getWidth() * 2 - || size.bottom >= bitmap.getHeight() * 2); + boolean reload = + (size.right >= bitmap.getWidth() * 2 || size.bottom >= bitmap.getHeight() * 2); if (DEBUG) { - Log.d(TAG, "needToReload(" + reqWidth + ", " + reqHeight + ")=" + reload - + " because the new size would be " + size + " for " + this); + Log.d( + TAG, + "needToReload(" + + reqWidth + + ", " + + reqHeight + + ")=" + + reload + + " because the new size would be " + + size + + " for " + + this); } return reload; } - /** - * Returns {@code true} if a request the size of {@code other} would need a reload. - */ - public boolean needToReload(ScaledBitmapInfo other){ + /** Returns {@code true} if a request the size of {@code other} would need a reload. */ + public boolean needToReload(ScaledBitmapInfo other) { return needToReload(other.bitmap.getWidth(), other.bitmap.getHeight()); } @Override public String toString() { - return "ScaledBitmapInfo[" + id + "](in=" + inSampleSize + ", w=" + bitmap.getWidth() - + ", h=" + bitmap.getHeight() + ")"; + return "ScaledBitmapInfo[" + + id + + "](in=" + + inSampleSize + + ", w=" + + bitmap.getWidth() + + ", h=" + + bitmap.getHeight() + + ")"; } } diff --git a/src/com/android/tv/util/CaptionSettings.java b/src/com/android/tv/util/CaptionSettings.java index 3b38905b..6d7e9901 100644 --- a/src/com/android/tv/util/CaptionSettings.java +++ b/src/com/android/tv/util/CaptionSettings.java @@ -18,7 +18,6 @@ package com.android.tv.util; import android.content.Context; import android.view.accessibility.CaptioningManager; - import java.util.Locale; public class CaptionSettings { @@ -32,8 +31,8 @@ public class CaptionSettings { private String mTrackId; public CaptionSettings(Context context) { - mCaptioningManager = (CaptioningManager) context.getSystemService( - Context.CAPTIONING_SERVICE); + mCaptioningManager = + (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); } public final String getSystemLanguage() { @@ -84,16 +83,12 @@ public class CaptionSettings { mLanguage = language; } - /** - * Returns the track ID to be used as an alternative key. - */ + /** Returns the track ID to be used as an alternative key. */ public String getTrackId() { return mTrackId; } - /** - * Sets the track ID to be used as an alternative key. - */ + /** Sets the track ID to be used as an alternative key. */ public void setTrackId(String trackId) { mTrackId = trackId; } diff --git a/src/com/android/tv/util/Clock.java b/src/com/android/tv/util/Clock.java index c5e96431..0004a669 100644 --- a/src/com/android/tv/util/Clock.java +++ b/src/com/android/tv/util/Clock.java @@ -18,13 +18,13 @@ package com.android.tv.util; import android.os.SystemClock; /** - * An interface through which system clocks can be read. The {@link #SYSTEM} implementation - * must be used for all non-test cases. + * An interface through which system clocks can be read. The {@link #SYSTEM} implementation must be + * used for all non-test cases. */ public interface Clock { /** - * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. - * See {@link System#currentTimeMillis()}. + * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. See {@link + * System#currentTimeMillis()}. */ long currentTimeMillis(); @@ -43,23 +43,22 @@ public interface Clock { */ void sleep(long ms); - /** - * The default implementation of Clock. - */ - Clock SYSTEM = new Clock() { - @Override - public long currentTimeMillis() { - return System.currentTimeMillis(); - } + /** The default implementation of Clock. */ + Clock SYSTEM = + new Clock() { + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } - @Override - public long elapsedRealtime() { - return SystemClock.elapsedRealtime(); - } + @Override + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } - @Override - public void sleep(long ms) { - SystemClock.sleep(ms); - } - }; + @Override + public void sleep(long ms) { + SystemClock.sleep(ms); + } + }; } diff --git a/src/com/android/tv/util/CompositeComparator.java b/src/com/android/tv/util/CompositeComparator.java index 47cf50fe..ccf4e944 100644 --- a/src/com/android/tv/util/CompositeComparator.java +++ b/src/com/android/tv/util/CompositeComparator.java @@ -18,9 +18,7 @@ package com.android.tv.util; import java.util.Comparator; -/** - * A comparator which runs multiple comparators sequentially. - */ +/** A comparator which runs multiple comparators sequentially. */ public class CompositeComparator implements Comparator { private final Comparator[] mComparators; diff --git a/src/com/android/tv/util/Debug.java b/src/com/android/tv/util/Debug.java index 67a2683d..422a61e3 100644 --- a/src/com/android/tv/util/Debug.java +++ b/src/com/android/tv/util/Debug.java @@ -20,28 +20,20 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -/** - * A class only for help developers. - */ +/** A class only for help developers. */ public class Debug { /** - * A threshold of start up time, when the start up time of Live TV is more than it, - * a warning will show to the developer. + * A threshold of start up time, when the start up time of Live TV is more than it, a warning + * will show to the developer. */ public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6); - /** - * Tag for measuring start up time of Live TV. - */ + /** Tag for measuring start up time of Live TV. */ public static final String TAG_START_UP_TIMER = "start_up_timer"; - /** - * A global map for duration timers. - */ - private final static Map sTimerMap = new HashMap<>(); + /** A global map for duration timers. */ + private static final Map sTimerMap = new HashMap<>(); - /** - * Returns the global duration timer by tag. - */ + /** Returns the global duration timer by tag. */ public static DurationTimer getTimer(String tag) { if (sTimerMap.get(tag) != null) { return sTimerMap.get(tag); @@ -51,9 +43,7 @@ public class Debug { return timer; } - /** - * Removes the global duration timer by tag. - */ + /** Removes the global duration timer by tag. */ public static DurationTimer removeTimer(String tag) { return sTimerMap.remove(tag); } diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/util/DurationTimer.java index 1f057bf6..6aabf37b 100644 --- a/src/com/android/tv/util/DurationTimer.java +++ b/src/com/android/tv/util/DurationTimer.java @@ -18,12 +18,9 @@ package com.android.tv.util; import android.os.SystemClock; import android.util.Log; - import com.android.tv.common.BuildConfig; -/** - * Times a duration. - */ +/** Times a duration. */ public final class DurationTimer { private static final String TAG = "DurationTimer"; public static final long TIME_NOT_SET = -1; @@ -32,30 +29,24 @@ public final class DurationTimer { private String mTag = TAG; private boolean mLogEngOnly; - public DurationTimer() { } + public DurationTimer() {} public DurationTimer(String tag, boolean logEngOnly) { mTag = tag; mLogEngOnly = logEngOnly; } - /** - * Returns true if the timer is running. - */ + /** Returns true if the timer is running. */ public boolean isRunning() { return mStartTimeMs != TIME_NOT_SET; } - /** - * Start the timer. - */ + /** Start the timer. */ public void start() { mStartTimeMs = SystemClock.elapsedRealtime(); } - /** - * Returns true if timer is started. - */ + /** Returns true if timer is started. */ public boolean isStarted() { return mStartTimeMs != TIME_NOT_SET; } @@ -72,7 +63,7 @@ public final class DurationTimer { * Stops the timer and resets its value to {@link #TIME_NOT_SET}. * * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not - * running. + * running. */ public long reset() { long duration = getDuration(); @@ -80,9 +71,7 @@ public final class DurationTimer { return duration; } - /** - * Adds information and duration time to the log. - */ + /** Adds information and duration time to the log. */ public void log(String message) { if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) { Log.i(mTag, message + " : " + getDuration() + "ms"); diff --git a/src/com/android/tv/util/Filter.java b/src/com/android/tv/util/Filter.java index d5b356e4..3e24a496 100644 --- a/src/com/android/tv/util/Filter.java +++ b/src/com/android/tv/util/Filter.java @@ -16,12 +16,8 @@ package com.android.tv.util; -/** - * Interface to decide whether an input is filtered out or not. - */ +/** Interface to decide whether an input is filtered out or not. */ public interface Filter { - /** - * Returns true, if {@code input} is acceptable. - */ + /** Returns true, if {@code input} is acceptable. */ boolean filter(T input); } diff --git a/src/com/android/tv/util/ImageCache.java b/src/com/android/tv/util/ImageCache.java index b413c364..1017cb14 100644 --- a/src/com/android/tv/util/ImageCache.java +++ b/src/com/android/tv/util/ImageCache.java @@ -19,13 +19,10 @@ package com.android.tv.util; 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; -/** - * A convenience class for caching bitmap. - */ +/** A convenience class for caching bitmap. */ public class ImageCache implements MemoryManageable { private static final float MAX_CACHE_SIZE_PERCENT = 0.8f; private static final float MIN_CACHE_SIZE_PERCENT = 0.05f; @@ -48,16 +45,17 @@ public class ImageCache implements MemoryManageable { if (DEBUG) { Log.d(TAG, "Memory cache created (size = " + memCacheSize + " Kbytes)"); } - mMemoryCache = new LruCache(memCacheSize) { - /** - * Measure item size in kilobytes rather than units which is more practical for a bitmap - * cache - */ - @Override - protected int sizeOf(String key, ScaledBitmapInfo bitmapInfo) { - return (bitmapInfo.bitmap.getByteCount() + 1023) / 1024; - } - }; + mMemoryCache = + new LruCache(memCacheSize) { + /** + * Measure item size in kilobytes rather than units which is more practical for + * a bitmap cache + */ + @Override + protected int sizeOf(String key, ScaledBitmapInfo bitmapInfo) { + return (bitmapInfo.bitmap.getByteCount() + 1023) / 1024; + } + }; } private static ImageCache sImageCache; @@ -67,7 +65,7 @@ public class ImageCache implements MemoryManageable { * param. * * @param memCacheSizePercent The cache size as a percent of available app memory. Should be in - * range of MIN_CACHE_SIZE_PERCENT(0.05) ~ MAX_CACHE_SIZE_PERCENT(0.8). + * range of MIN_CACHE_SIZE_PERCENT(0.05) ~ MAX_CACHE_SIZE_PERCENT(0.8). * @return An existing retained ImageCache object or a new one if one did not exist */ public static synchronized ImageCache getInstance(float memCacheSizePercent) { @@ -82,7 +80,6 @@ public class ImageCache implements MemoryManageable { return new ImageCache(memCacheSizePercent); } - /** * Returns an existing ImageCache, if it doesn't exist, a new one is created using * DEFAULT_CACHE_SIZE_PERCENT (0.1). @@ -96,8 +93,8 @@ public class ImageCache implements MemoryManageable { /** * Adds a bitmap to memory cache. * - *

If there is an existing bitmap only replace it if - * {@link ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true. + *

If there is an existing bitmap only replace it if {@link + * ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true. * * @param bitmapInfo The {@link ScaledBitmapInfo} object to store */ @@ -112,14 +109,25 @@ public class ImageCache implements MemoryManageable { if (old != null && !old.needToReload(bitmapInfo)) { mMemoryCache.put(key, old); if (DEBUG) { - Log.d(TAG, - "Kept original " + old + " in memory cache because it was larger than " - + bitmapInfo + "."); + Log.d( + TAG, + "Kept original " + + old + + " in memory cache because it was larger than " + + bitmapInfo + + "."); } } else { if (DEBUG) { - Log.d(TAG, "Add " + bitmapInfo + " to memory cache. Current size is " + - mMemoryCache.size() + " / " + mMemoryCache.maxSize() + " Kbytes"); + Log.d( + TAG, + "Add " + + bitmapInfo + + " to memory cache. Current size is " + + mMemoryCache.size() + + " / " + + mMemoryCache.maxSize() + + " Kbytes"); } } } @@ -158,19 +166,21 @@ public class ImageCache implements MemoryManageable { * Calculates the memory cache size based on a percentage of the max available VM memory. Eg. * setting percent to 0.2 would set the memory cache to one fifth of the available memory. * Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8. memCacheSize is stored - * in kilobytes instead of bytes as this will eventually be passed to construct a LruCache - * which takes an int in its constructor. This value should be chosen carefully based on a - * number of factors Refer to the corresponding Android Training class for more discussion: + * in kilobytes instead of bytes as this will eventually be passed to construct a LruCache which + * takes an int in its constructor. This value should be chosen carefully based on a number of + * factors Refer to the corresponding Android Training class for more discussion: * http://developer.android.com/training/displaying-bitmaps/ * * @param percent Percent of available app memory to use to size memory cache. */ public static int calculateMemCacheSize(float percent) { if (percent < MIN_CACHE_SIZE_PERCENT || percent > MAX_CACHE_SIZE_PERCENT) { - throw new IllegalArgumentException("setMemCacheSizePercent - percent must be " - + "between 0.05 and 0.8 (inclusive)"); + throw new IllegalArgumentException( + "setMemCacheSizePercent - percent must be " + + "between 0.05 and 0.8 (inclusive)"); } - return Math.max(MIN_CACHE_SIZE_KBYTES, + return Math.max( + MIN_CACHE_SIZE_KBYTES, Math.round(percent * Runtime.getRuntime().maxMemory() / 1024)); } diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java index 86bb94c1..9b4d2a70 100644 --- a/src/com/android/tv/util/ImageLoader.java +++ b/src/com/android/tv/util/ImageLoader.java @@ -30,10 +30,8 @@ import android.support.annotation.UiThread; import android.support.annotation.WorkerThread; import android.util.ArraySet; import android.util.Log; - import com.android.tv.R; import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; - import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; @@ -47,8 +45,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** - * This class wraps up completing some arbitrary long running work when loading a bitmap. It - * handles things like using a memory cache, running the work in a background thread. + * This class wraps up completing some arbitrary long running work when loading a bitmap. It handles + * things like using a memory cache, running the work in a background thread. */ public final class ImageLoader { private static final String TAG = "ImageLoader"; @@ -69,19 +67,23 @@ public final class ImageLoader { /** * An private {@link Executor} that can be used to execute tasks in parallel. * - *

{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} - * Since we do a lot of concurrent image loading we can exhaust a thread pool. - * ImageLoader catches the error, and just leaves the image blank. - * However other tasks will fail and crash the application. + *

{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} Since we do a + * lot of concurrent image loading we can exhaust a thread pool. ImageLoader catches the error, + * and just leaves the image blank. However other tasks will fail and crash the application. * *

Using a separate thread pool prevents image loading from causing other tasks to fail. */ private static final Executor IMAGE_THREAD_POOL_EXECUTOR; static { - ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, - MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, - sThreadFactory); + ThreadPoolExecutor threadPoolExecutor = + new ThreadPoolExecutor( + CORE_POOL_SIZE, + MAXIMUM_POOL_SIZE, + KEEP_ALIVE_SECONDS, + TimeUnit.SECONDS, + sPoolWorkQueue, + sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); IMAGE_THREAD_POOL_EXECUTOR = threadPoolExecutor; } @@ -91,28 +93,26 @@ public final class ImageLoader { /** * Handles when image loading is finished. * - *

Use this to prevent leaking an Activity or other Context while image loading is - * still pending. When you extend this class you MUST NOT use a non static - * inner class, or the containing object will still be leaked. + *

Use this to prevent leaking an Activity or other Context while image loading is still + * pending. When you extend this class you MUST NOT use a non static inner + * class, or the containing object will still be leaked. */ @UiThread - public static abstract class ImageLoaderCallback { + public abstract static class ImageLoaderCallback { private final WeakReference mWeakReference; /** * Creates an callback keeping a weak reference to {@code referent}. * - *

If the "referent" is no longer valid, it no longer makes sense to run the - * callback. The referent is the View, or Activity or whatever that actually needs to - * receive the Bitmap. If the referent has been GC, then no need to run the callback. + *

If the "referent" is no longer valid, it no longer makes sense to run the callback. + * The referent is the View, or Activity or whatever that actually needs to receive the + * Bitmap. If the referent has been GC, then no need to run the callback. */ public ImageLoaderCallback(T referent) { mWeakReference = new WeakReference<>(referent); } - /** - * Called when bitmap is loaded. - */ + /** Called when bitmap is loaded. */ private void onBitmapLoaded(@Nullable Bitmap bitmap) { T referent = mWeakReference.get(); if (referent != null) { @@ -122,9 +122,7 @@ public final class ImageLoader { } } - /** - * Called when bitmap is loaded if the weak reference is still valid. - */ + /** Called when bitmap is loaded if the weak reference is still valid. */ public abstract void onBitmapLoaded(T referent, @Nullable Bitmap bitmap); } @@ -134,62 +132,80 @@ public final class ImageLoader { * Preload a bitmap image into the cache. * *

Not to make heavy CPU load, AsyncTask.SERIAL_EXECUTOR is used for the image loading. + * *

This method is thread safe. */ - public static void prefetchBitmap(Context context, final String uriString, final int maxWidth, - final int maxHeight) { + public static void prefetchBitmap( + Context context, final String uriString, final int maxWidth, final int maxHeight) { if (DEBUG) Log.d(TAG, "prefetchBitmap() " + uriString); if (Looper.getMainLooper() == Looper.myLooper()) { doLoadBitmap(context, uriString, maxWidth, maxHeight, null, AsyncTask.SERIAL_EXECUTOR); } else { final Context appContext = context.getApplicationContext(); - getMainHandler().post(new Runnable() { - @Override - @MainThread - public void run() { - // Calling from the main thread prevents a ConcurrentModificationException - // in LoadBitmapTask.onPostExecute - doLoadBitmap(appContext, uriString, maxWidth, maxHeight, null, - AsyncTask.SERIAL_EXECUTOR); - } - }); + getMainHandler() + .post( + new Runnable() { + @Override + @MainThread + public void run() { + // Calling from the main thread prevents a + // ConcurrentModificationException + // in LoadBitmapTask.onPostExecute + doLoadBitmap( + appContext, + uriString, + maxWidth, + maxHeight, + null, + AsyncTask.SERIAL_EXECUTOR); + } + }); } } /** * Load a bitmap image with the cache using a ContentResolver. * - *

Note that the callback will be called synchronously if the bitmap already is in - * the cache. + *

Note that the callback will be called synchronously if the bitmap already is in the + * cache. * * @return {@code true} if the load is complete and the callback is executed. */ @UiThread - public static boolean loadBitmap(Context context, String uriString, - ImageLoaderCallback callback) { + public static boolean loadBitmap( + Context context, String uriString, ImageLoaderCallback callback) { return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback); } /** * Load a bitmap image with the cache and resize it with given params. * - *

Note that the callback will be called synchronously if the bitmap already is in - * the cache. + *

Note that the callback will be called synchronously if the bitmap already is in the + * cache. * * @return {@code true} if the load is complete and the callback is executed. */ @UiThread - public static boolean loadBitmap(Context context, String uriString, int maxWidth, int maxHeight, + public static boolean loadBitmap( + Context context, + String uriString, + int maxWidth, + int maxHeight, ImageLoaderCallback callback) { if (DEBUG) { Log.d(TAG, "loadBitmap() " + uriString); } - return doLoadBitmap(context, uriString, maxWidth, maxHeight, callback, - IMAGE_THREAD_POOL_EXECUTOR); + return doLoadBitmap( + context, uriString, maxWidth, maxHeight, callback, IMAGE_THREAD_POOL_EXECUTOR); } - private static boolean doLoadBitmap(Context context, String uriString, - int maxWidth, int maxHeight, ImageLoaderCallback callback, Executor executor) { + private static boolean doLoadBitmap( + Context context, + String uriString, + int maxWidth, + int maxHeight, + ImageLoaderCallback callback, + Executor executor) { // Check the cache before creating a Task. The cache will be checked again in doLoadBitmap // but checking a cache is much cheaper than creating an new task. ImageCache imageCache = ImageCache.getInstance(); @@ -200,7 +216,9 @@ public final class ImageLoader { } return true; } - return doLoadBitmap(callback, executor, + return doLoadBitmap( + callback, + executor, new LoadBitmapFromUriTask(context, imageCache, uriString, maxWidth, maxHeight)); } @@ -219,12 +237,10 @@ public final class ImageLoader { return doLoadBitmap(callback, IMAGE_THREAD_POOL_EXECUTOR, loadBitmapTask); } - /** - * @return {@code true} if the load is complete and the callback is executed. - */ + /** @return {@code true} if the load is complete and the callback is executed. */ @UiThread - private static boolean doLoadBitmap(ImageLoaderCallback callback, Executor executor, - LoadBitmapTask loadBitmapTask) { + private static boolean doLoadBitmap( + ImageLoaderCallback callback, Executor executor, LoadBitmapTask loadBitmapTask) { ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache(); boolean needToReload = loadBitmapTask.isReloadNeeded(); if (bitmapInfo != null && !needToReload) { @@ -259,7 +275,7 @@ public final class ImageLoader { * *

Implement {@link #doGetBitmapInBackground} to do the actual loading. */ - public static abstract class LoadBitmapTask extends AsyncTask { + public abstract static class LoadBitmapTask extends AsyncTask { protected final Context mAppContext; protected final int mMaxWidth; protected final int mMaxHeight; @@ -273,24 +289,28 @@ public final class ImageLoader { */ private boolean isReloadNeeded() { ScaledBitmapInfo bitmapInfo = getFromCache(); - boolean needToReload = bitmapInfo != null && bitmapInfo - .needToReload(mMaxWidth, mMaxHeight); + boolean needToReload = + bitmapInfo != null && bitmapInfo.needToReload(mMaxWidth, mMaxHeight); if (DEBUG) { if (needToReload) { - Log.d(TAG, "Bitmap needs to be reloaded. {" - + "originalWidth=" + bitmapInfo.bitmap.getWidth() - + ", originalHeight=" + bitmapInfo.bitmap.getHeight() - + ", reqWidth=" + mMaxWidth - + ", reqHeight=" + mMaxHeight - + "}"); + Log.d( + TAG, + "Bitmap needs to be reloaded. {" + + "originalWidth=" + + bitmapInfo.bitmap.getWidth() + + ", originalHeight=" + + bitmapInfo.bitmap.getHeight() + + ", reqWidth=" + + mMaxWidth + + ", reqHeight=" + + mMaxHeight + + "}"); } } return needToReload; } - /** - * Checks if a reload would be needed if the results of other was available. - */ + /** Checks if a reload would be needed if the results of other was available. */ private boolean isReloadNeeded(LoadBitmapTask other) { return (other.mMaxHeight != Integer.MAX_VALUE && mMaxHeight >= other.mMaxHeight * 2) || (other.mMaxWidth != Integer.MAX_VALUE && mMaxWidth >= other.mMaxWidth * 2); @@ -301,11 +321,14 @@ public final class ImageLoader { return mImageCache.get(mKey); } - public LoadBitmapTask(Context context, ImageCache imageCache, String key, int maxHeight, - int maxWidth) { + public LoadBitmapTask( + Context context, ImageCache imageCache, String key, int maxHeight, int maxWidth) { if (maxWidth == 0 || maxHeight == 0) { throw new IllegalArgumentException( - "Image size should not be 0. {width=" + maxWidth + ", height=" + maxHeight + "Image size should not be 0. {width=" + + maxWidth + + ", height=" + + maxHeight + "}"); } mAppContext = context.getApplicationContext(); @@ -315,9 +338,7 @@ public final class ImageLoader { mMaxWidth = maxWidth; } - /** - * Loads the bitmap returning a possibly scaled down version. - */ + /** Loads the bitmap returning a possibly scaled down version. */ @Nullable @WorkerThread public abstract ScaledBitmapInfo doGetBitmapInBackground(); @@ -352,40 +373,48 @@ public final class ImageLoader { @Override public String toString() { - return this.getClass().getSimpleName() + "(" + mKey + " " + mMaxWidth + "x" + mMaxHeight + return this.getClass().getSimpleName() + + "(" + + mKey + + " " + + mMaxWidth + + "x" + + mMaxHeight + ")"; } } private static final class LoadBitmapFromUriTask extends LoadBitmapTask { - private LoadBitmapFromUriTask(Context context, ImageCache imageCache, String uriString, - int maxWidth, int maxHeight) { + private LoadBitmapFromUriTask( + Context context, + ImageCache imageCache, + String uriString, + int maxWidth, + int maxHeight) { super(context, imageCache, uriString, maxHeight, maxWidth); } @Override @Nullable public final ScaledBitmapInfo doGetBitmapInBackground() { - return BitmapUtils - .decodeSampledBitmapFromUriString(mAppContext, getKey(), mMaxWidth, mMaxHeight); + return BitmapUtils.decodeSampledBitmapFromUriString( + mAppContext, getKey(), mMaxWidth, mMaxHeight); } } - /** - * Loads and caches the logo for a given {@link TvInputInfo} - */ + /** Loads and caches the logo for a given {@link TvInputInfo} */ public static final class LoadTvInputLogoTask extends LoadBitmapTask { private final TvInputInfo mInfo; public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) { - super(context, + super( + context, cache, getTvInputLogoKey(info.getId()), context.getResources() .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size), context.getResources() - .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size) - ); + .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size)); mInfo = info; } @@ -403,9 +432,7 @@ public final class ImageLoader { return BitmapUtils.createScaledBitmapInfo(getKey(), original, mMaxWidth, mMaxHeight); } - /** - * Returns key of TV input logo. - */ + /** Returns key of TV input logo. */ public static String getTvInputLogoKey(String inputId) { return inputId + "-logo"; } @@ -418,6 +445,5 @@ public final class ImageLoader { return sMainHandler; } - private ImageLoader() { - } + private ImageLoader() {} } diff --git a/src/com/android/tv/util/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java index d5d7bee3..a960c616 100644 --- a/src/com/android/tv/util/LocationUtils.java +++ b/src/com/android/tv/util/LocationUtils.java @@ -16,9 +16,7 @@ package com.android.tv.util; -import android.Manifest; import android.content.Context; -import android.content.pm.PackageManager; import android.location.Address; import android.location.Geocoder; import android.location.Location; @@ -28,16 +26,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; - import com.android.tv.tuner.util.PostalCodeUtils; - import java.io.IOException; import java.util.List; import java.util.Locale; -/** - * A utility class to get the current location. - */ +/** A utility class to get the current location. */ public class LocationUtils { private static final String TAG = "LocationUtils"; private static final boolean DEBUG = false; @@ -47,11 +41,9 @@ public class LocationUtils { private static String sCountry; private static IOException sError; - /** - * Checks the current location. - */ - public static synchronized Address getCurrentAddress(Context context) throws IOException, - SecurityException { + /** Checks the current location. */ + public static synchronized Address getCurrentAddress(Context context) + throws IOException, SecurityException { if (sAddress != null) { return sAddress; } @@ -84,8 +76,8 @@ public class LocationUtils { } Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault()); try { - List

addresses = geocoder.getFromLocation( - location.getLatitude(), location.getLongitude(), 1); + List
addresses = + geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1); if (addresses != null && !addresses.isEmpty()) { sAddress = addresses.get(0); if (DEBUG) Log.d(TAG, "Got " + sAddress); @@ -104,31 +96,33 @@ public class LocationUtils { } } - private LocationUtils() { } + private LocationUtils() {} private static class LocationUtilsHelper { - private static final LocationListener LOCATION_LISTENER = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - updateAddress(location); - } + private static final LocationListener LOCATION_LISTENER = + new LocationListener() { + @Override + public void onLocationChanged(Location location) { + updateAddress(location); + } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} - @Override - public void onProviderEnabled(String provider) { } + @Override + public void onProviderEnabled(String provider) {} - @Override - public void onProviderDisabled(String provider) { } - }; + @Override + public void onProviderDisabled(String provider) {} + }; private static LocationManager sLocationManager; public static void startLocationUpdates() { if (sLocationManager == null) { - sLocationManager = (LocationManager) sApplicationContext.getSystemService( - Context.LOCATION_SERVICE); + sLocationManager = + (LocationManager) + sApplicationContext.getSystemService(Context.LOCATION_SERVICE); try { sLocationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null); diff --git a/src/com/android/tv/util/MainThreadExecutor.java b/src/com/android/tv/util/MainThreadExecutor.java index ce8f8ff3..5102ddbd 100644 --- a/src/com/android/tv/util/MainThreadExecutor.java +++ b/src/com/android/tv/util/MainThreadExecutor.java @@ -18,7 +18,6 @@ package com.android.tv.util; import android.os.Handler; import android.os.Looper; - import java.util.List; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.TimeUnit; @@ -26,11 +25,11 @@ import java.util.concurrent.TimeUnit; /** * An executor service that executes its tasks on the main thread. * - * Shutting down this executor is not supported. + *

Shutting down this executor is not supported. */ public class MainThreadExecutor extends AbstractExecutorService { - private final static MainThreadExecutor INSTANCE = new MainThreadExecutor(); + private static final MainThreadExecutor INSTANCE = new MainThreadExecutor(); public static MainThreadExecutor getInstance() { return INSTANCE; @@ -47,18 +46,14 @@ public class MainThreadExecutor extends AbstractExecutorService { } } - /** - * Not supported and throws an exception when used. - */ + /** Not supported and throws an exception when used. */ @Override @Deprecated public void shutdown() { throw new UnsupportedOperationException(); } - /** - * Not supported and throws an exception when used. - */ + /** Not supported and throws an exception when used. */ @Override @Deprecated public List shutdownNow() { @@ -75,12 +70,10 @@ public class MainThreadExecutor extends AbstractExecutorService { return false; } - /** - * Not supported and throws an exception when used. - */ + /** Not supported and throws an exception when used. */ @Override @Deprecated public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/util/MultiLongSparseArray.java b/src/com/android/tv/util/MultiLongSparseArray.java index 1d5fa80b..a456df91 100644 --- a/src/com/android/tv/util/MultiLongSparseArray.java +++ b/src/com/android/tv/util/MultiLongSparseArray.java @@ -19,7 +19,6 @@ package com.android.tv.util; import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.LongSparseArray; - import java.util.Collections; import java.util.Set; @@ -29,8 +28,7 @@ import java.util.Set; *

This has the same memory and performance trade offs listed in {@link LongSparseArray}. */ public class MultiLongSparseArray { - @VisibleForTesting - static final int DEFAULT_MAX_EMPTIES_KEPT = 4; + @VisibleForTesting static final int DEFAULT_MAX_EMPTIES_KEPT = 4; private final LongSparseArray> mSparseArray; private final Set[] mEmptySets; private int mEmptyIndex = -1; @@ -46,9 +44,8 @@ public class MultiLongSparseArray { } /** - * Adds a mapping from the specified key to the specified value, - * replacing the previous mapping from the specified key if there - * was one. + * Adds a mapping from the specified key to the specified value, replacing the previous mapping + * from the specified key if there was one. */ public void put(long key, T value) { Set values = mSparseArray.get(key); @@ -59,9 +56,7 @@ public class MultiLongSparseArray { values.add(value); } - /** - * Removes the value at the specified index. - */ + /** Removes the value at the specified index. */ public void remove(long key, T value) { Set values = mSparseArray.get(key); if (values != null) { @@ -74,17 +69,15 @@ public class MultiLongSparseArray { } /** - * Gets the set of Objects mapped from the specified key, or an empty set - * if no such mapping has been made. + * Gets the set of Objects mapped from the specified key, or an empty set if no such mapping has + * been made. */ public Iterable get(long key) { Set values = mSparseArray.get(key); return values == null ? Collections.EMPTY_SET : values; } - /** - * Clears cached empty sets. - */ + /** Clears cached empty sets. */ public void clearEmptyCache() { while (mEmptyIndex >= 0) { mEmptySets[mEmptyIndex--] = null; diff --git a/src/com/android/tv/util/NamedThreadFactory.java b/src/com/android/tv/util/NamedThreadFactory.java index fcdde952..264b8b3f 100644 --- a/src/com/android/tv/util/NamedThreadFactory.java +++ b/src/com/android/tv/util/NamedThreadFactory.java @@ -17,14 +17,11 @@ package com.android.tv.util; import android.support.annotation.NonNull; - import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -/** - * A thread factory that creates threads with a suffix. - */ +/** A thread factory that creates threads with a suffix. */ public class NamedThreadFactory implements ThreadFactory { private final AtomicInteger mCount = new AtomicInteger(0); private final ThreadFactory mDefaultThreadFactory; diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/src/com/android/tv/util/NetworkTrafficTags.java index 2dca613c..85ecde5b 100644 --- a/src/com/android/tv/util/NetworkTrafficTags.java +++ b/src/com/android/tv/util/NetworkTrafficTags.java @@ -18,7 +18,6 @@ package com.android.tv.util; import android.net.TrafficStats; import android.support.annotation.NonNull; - import java.util.concurrent.Executor; /** Constants for tagging network traffic in the Live channels app. */ diff --git a/src/com/android/tv/util/NetworkUtils.java b/src/com/android/tv/util/NetworkUtils.java index ed3ce383..94581d5a 100644 --- a/src/com/android/tv/util/NetworkUtils.java +++ b/src/com/android/tv/util/NetworkUtils.java @@ -20,21 +20,16 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; - import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; -/** - * A utility class to check the connectivity. - */ +/** A utility class to check the connectivity. */ @WorkerThread public class NetworkUtils { private static final String GENERATE_204 = "http://clients3.google.com/generate_204"; - /** - * Checks if the internet connection is available. - */ + /** Checks if the internet connection is available. */ public static boolean isNetworkAvailable(@Nullable ConnectivityManager connectivityManager) { if (connectivityManager == null) { return false; @@ -62,5 +57,5 @@ public class NetworkUtils { return false; } - private NetworkUtils() { } + private NetworkUtils() {} } diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java index 49b02b82..3b72e091 100644 --- a/src/com/android/tv/util/OnboardingUtils.java +++ b/src/com/android/tv/util/OnboardingUtils.java @@ -21,9 +21,7 @@ import android.content.Intent; import android.net.Uri; import android.preference.PreferenceManager; -/** - * A utility class related to onboarding experience. - */ +/** A utility class related to onboarding experience. */ public final class OnboardingUtils { private static final String PREF_KEY_IS_FIRST_BOOT = "pref_onbaording_is_first_boot"; private static final String PREF_KEY_ONBOARDING_VERSION_CODE = "pref_onbaording_versionCode"; @@ -31,23 +29,17 @@ public final class OnboardingUtils { private static final String MERCHANT_COLLECTION_URL_STRING = getMerchantCollectionUrl(); - /** - * Intent to show merchant collection in online store. - */ - public static final Intent ONLINE_STORE_INTENT = new Intent(Intent.ACTION_VIEW, - Uri.parse(MERCHANT_COLLECTION_URL_STRING)); + /** Intent to show merchant collection in online store. */ + public static final Intent ONLINE_STORE_INTENT = + new Intent(Intent.ACTION_VIEW, Uri.parse(MERCHANT_COLLECTION_URL_STRING)); - /** - * Checks if this is the first boot after the onboarding experience has been applied. - */ + /** Checks if this is the first boot after the onboarding experience has been applied. */ public static boolean isFirstBoot(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(PREF_KEY_IS_FIRST_BOOT, true); } - /** - * Marks that the first boot has been completed. - */ + /** Marks that the first boot has been completed. */ public static void setFirstBootCompleted(Context context) { PreferenceManager.getDefaultSharedPreferences(context) .edit() @@ -56,27 +48,28 @@ public final class OnboardingUtils { } /** - * Checks if this is the first run of {@link com.android.tv.MainActivity} with the - * current onboarding version. + * Checks if this is the first run of {@link com.android.tv.MainActivity} with the current + * onboarding version. */ public static boolean isFirstRunWithCurrentVersion(Context context) { - int versionCode = PreferenceManager.getDefaultSharedPreferences(context) - .getInt(PREF_KEY_ONBOARDING_VERSION_CODE, 0); + int versionCode = + PreferenceManager.getDefaultSharedPreferences(context) + .getInt(PREF_KEY_ONBOARDING_VERSION_CODE, 0); return versionCode != ONBOARDING_VERSION; } /** - * Marks that the first run of {@link com.android.tv.MainActivity} with the current - * onboarding version has been completed. + * Marks that the first run of {@link com.android.tv.MainActivity} with the current onboarding + * version has been completed. */ public static void setFirstRunWithCurrentVersionCompleted(Context context) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putInt(PREF_KEY_ONBOARDING_VERSION_CODE, ONBOARDING_VERSION).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putInt(PREF_KEY_ONBOARDING_VERSION_CODE, ONBOARDING_VERSION) + .apply(); } - /** - * Returns merchant collection URL. - */ + /** Returns merchant collection URL. */ private static String getMerchantCollectionUrl() { return "TODO: add a merchant collection url"; } diff --git a/src/com/android/tv/util/Partner.java b/src/com/android/tv/util/Partner.java index e3688392..c5e9aad2 100644 --- a/src/com/android/tv/util/Partner.java +++ b/src/com/android/tv/util/Partner.java @@ -26,7 +26,6 @@ import android.content.res.Resources; import android.media.tv.TvInputInfo; import android.text.TextUtils; import android.util.Log; - import java.util.HashMap; import java.util.Map; @@ -42,6 +41,7 @@ public class Partner { /** ID tags for device input types */ public static final String INPUT_TYPE_BUNDLED_TUNER = "input_type_combined_tuners"; + public static final String INPUT_TYPE_TUNER = "input_type_tuner"; public static final String INPUT_TYPE_CEC_LOGICAL = "input_type_cec_logical"; public static final String INPUT_TYPE_CEC_RECORDER = "input_type_cec_recorder"; @@ -68,6 +68,7 @@ public class Partner { private final Resources mResources; private static final Map INPUT_TYPE_MAP = new HashMap<>(); + static { INPUT_TYPE_MAP.put(INPUT_TYPE_BUNDLED_TUNER, TvInputManagerHelper.TYPE_BUNDLED_TUNER); INPUT_TYPE_MAP.put(INPUT_TYPE_TUNER, TvInputInfo.TYPE_TUNER); diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java index a355be99..b3e4e3a2 100644 --- a/src/com/android/tv/util/PermissionUtils.java +++ b/src/com/android/tv/util/PermissionUtils.java @@ -3,13 +3,9 @@ package com.android.tv.util; import android.content.Context; import android.content.pm.PackageManager; -/** - * Util class to handle permissions. - */ +/** Util class to handle permissions. */ public class PermissionUtils { - /** - * Permission to read the TV listings. - */ + /** Permission to read the TV listings. */ public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; private static Boolean sHasAccessAllEpgPermission; @@ -18,27 +14,29 @@ public class PermissionUtils { public static boolean hasAccessAllEpg(Context context) { if (sHasAccessAllEpgPermission == null) { - sHasAccessAllEpgPermission = context.checkSelfPermission( - "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA") - == PackageManager.PERMISSION_GRANTED; + sHasAccessAllEpgPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA") + == PackageManager.PERMISSION_GRANTED; } return sHasAccessAllEpgPermission; } public static boolean hasAccessWatchedHistory(Context context) { if (sHasAccessWatchedHistoryPermission == null) { - sHasAccessWatchedHistoryPermission = context.checkSelfPermission( - "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") - == PackageManager.PERMISSION_GRANTED; + sHasAccessWatchedHistoryPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") + == PackageManager.PERMISSION_GRANTED; } return sHasAccessWatchedHistoryPermission; } public static boolean hasModifyParentalControls(Context context) { if (sHasModifyParentalControlsPermission == null) { - sHasModifyParentalControlsPermission = context.checkSelfPermission( - "android.permission.MODIFY_PARENTAL_CONTROLS") - == PackageManager.PERMISSION_GRANTED; + sHasModifyParentalControlsPermission = + context.checkSelfPermission("android.permission.MODIFY_PARENTAL_CONTROLS") + == PackageManager.PERMISSION_GRANTED; } return sHasModifyParentalControlsPermission; } diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java index 8b45131b..c1b724a2 100644 --- a/src/com/android/tv/util/RecurringRunner.java +++ b/src/com/android/tv/util/RecurringRunner.java @@ -22,10 +22,8 @@ import android.os.AsyncTask; import android.os.Handler; import android.support.annotation.WorkerThread; import android.util.Log; - import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.common.SoftPreconditions; - import java.util.Date; /** @@ -46,8 +44,8 @@ public final class RecurringRunner { private final String mName; private boolean mRunning; - public RecurringRunner(Context context, long intervalMs, Runnable runnable, - Runnable onStopRunnable) { + public RecurringRunner( + Context context, long intervalMs, Runnable runnable, Runnable onStopRunnable) { mContext = context.getApplicationContext(); mRunnable = runnable; mOnStopRunnable = onStopRunnable; @@ -99,18 +97,21 @@ public final class RecurringRunner { // Run it anyways even if it is in the past if (DEBUG) Log.i(TAG, "Next run of " + mName + " at " + new Date(next)); long delay = Math.max(next - now, 0); - boolean posted = mHandler.postDelayed(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.i(TAG, "Starting " + mName); - mRunnable.run(); - } catch (Exception e) { - Log.w(TAG, "Error running " + mName, e); - } - postAt(resetNextRunTime()); - } - }, delay); + boolean posted = + mHandler.postDelayed( + new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.i(TAG, "Starting " + mName); + mRunnable.run(); + } catch (Exception e) { + Log.w(TAG, "Error running " + mName, e); + } + postAt(resetNextRunTime()); + } + }, + delay); if (!posted) { Log.w(TAG, "Scheduling a future run of " + mName + " at " + new Date(next) + "failed"); } @@ -118,8 +119,8 @@ public final class RecurringRunner { } private SharedPreferences getSharedPreferences() { - return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, - Context.MODE_PRIVATE); + return mContext.getSharedPreferences( + SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); } @WorkerThread diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java index 32e3a81f..a1ff192b 100644 --- a/src/com/android/tv/util/SetupUtils.java +++ b/src/com/android/tv/util/SetupUtils.java @@ -31,21 +31,17 @@ import android.support.annotation.UiThread; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.tuner.tvinput.TunerTvInputService; - import java.util.Collections; import java.util.HashSet; import java.util.Set; -/** - * A utility class related to input setup. - */ +/** A utility class related to input setup. */ public class SetupUtils { private static final String TAG = "SetupUtils"; private static final boolean DEBUG = false; @@ -72,22 +68,21 @@ public class SetupUtils { mTvApplication = tvApplication; mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication); mSetUpInputs = new ArraySet<>(); - mSetUpInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, - Collections.emptySet())); + mSetUpInputs.addAll( + mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.emptySet())); mKnownInputs = new ArraySet<>(); - mKnownInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, - Collections.emptySet())); + mKnownInputs.addAll( + mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, Collections.emptySet())); mRecognizedInputs = new ArraySet<>(); - mRecognizedInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, - mKnownInputs)); + mRecognizedInputs.addAll( + mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs)); mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true); - mTunerInputId = TvContract.buildInputId(new ComponentName(tvApplication, - TunerTvInputService.class)); + mTunerInputId = + TvContract.buildInputId( + new ComponentName(tvApplication, TunerTvInputService.class)); } - /** - * Gets an instance of {@link SetupUtils}. - */ + /** Gets an instance of {@link SetupUtils}. */ public static SetupUtils getInstance(Context context) { if (sSetupUtils != null) { return sSetupUtils; @@ -96,11 +91,9 @@ public class SetupUtils { return sSetupUtils; } - /** - * Additional work after the setup of TV input. - */ - public void onTvInputSetupFinished(final String inputId, - @Nullable final Runnable postRunnable) { + /** Additional work after the setup of TV input. */ + public void onTvInputSetupFinished( + final String inputId, @Nullable final Runnable postRunnable) { // When TIS adds several channels, ChannelDataManager.Listener.onChannelList // Updated() can be called several times. In this case, it is hard to detect // which one is the last callback. To reduce error prune, we update channel @@ -108,61 +101,61 @@ public class SetupUtils { onSetupDone(inputId); final ChannelDataManager manager = mTvApplication.getChannelDataManager(); if (!manager.isDbLoadFinished()) { - manager.addListener(new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - manager.removeListener(this); - updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); - } + manager.addListener( + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + manager.removeListener(this); + updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); + } - @Override - public void onChannelListUpdated() { } + @Override + public void onChannelListUpdated() {} - @Override - public void onChannelBrowsableChanged() { } - }); + @Override + public void onChannelBrowsableChanged() {} + }); } else { updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); } } - private static void updateChannelsAfterSetup(Context context, final String inputId, - final Runnable postRunnable) { + private static void updateChannelsAfterSetup( + Context context, final String inputId, final Runnable postRunnable) { ApplicationSingletons appSingletons = TvApplication.getSingletons(context); final ChannelDataManager manager = appSingletons.getChannelDataManager(); - manager.updateChannels(new Runnable() { - @Override - public void run() { - Channel firstChannelForInput = null; - boolean browsableChanged = false; - for (Channel channel : manager.getChannelList()) { - if (channel.getInputId().equals(inputId)) { - if (!channel.isBrowsable()) { - manager.updateBrowsable(channel.getId(), true, true); - browsableChanged = true; + manager.updateChannels( + new Runnable() { + @Override + public void run() { + Channel firstChannelForInput = null; + boolean browsableChanged = false; + for (Channel channel : manager.getChannelList()) { + if (channel.getInputId().equals(inputId)) { + if (!channel.isBrowsable()) { + manager.updateBrowsable(channel.getId(), true, true); + browsableChanged = true; + } + if (firstChannelForInput == null) { + firstChannelForInput = channel; + } + } + } + if (firstChannelForInput != null) { + Utils.setLastWatchedChannel(context, firstChannelForInput); } - if (firstChannelForInput == null) { - firstChannelForInput = channel; + if (browsableChanged) { + manager.notifyChannelBrowsableChanged(); + manager.applyUpdatedValuesToDb(); + } + if (postRunnable != null) { + postRunnable.run(); } } - } - if (firstChannelForInput != null) { - Utils.setLastWatchedChannel(context, firstChannelForInput); - } - if (browsableChanged) { - manager.notifyChannelBrowsableChanged(); - manager.applyUpdatedValuesToDb(); - } - if (postRunnable != null) { - postRunnable.run(); - } - } - }); + }); } - /** - * Marks the channels in newly installed inputs browsable. - */ + /** Marks the channels in newly installed inputs browsable. */ @UiThread public void markNewChannelsBrowsable() { Set newInputsWithChannels = new HashSet<>(); @@ -175,9 +168,13 @@ public class SetupUtils { onSetupDone(inputId); newInputsWithChannels.add(inputId); if (DEBUG) { - Log.d(TAG, "New input " + inputId + " has " - + channelDataManager.getChannelCountForInput(inputId) - + " channels"); + Log.d( + TAG, + "New input " + + inputId + + " has " + + channelDataManager.getChannelCountForInput(inputId) + + " channels"); } } } @@ -195,9 +192,7 @@ public class SetupUtils { return mIsFirstTune; } - /** - * Returns true, if the input with {@code inputId} is newly installed. - */ + /** Returns true, if the input with {@code inputId} is newly installed. */ public boolean isNewInput(String inputId) { return !mKnownInputs.contains(inputId); } @@ -209,13 +204,14 @@ public class SetupUtils { public void markAsKnownInput(String inputId) { mKnownInputs.add(inputId); mRecognizedInputs.add(inputId); - mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) - .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply(); + mSharedPreferences + .edit() + .putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) + .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) + .apply(); } - /** - * Returns {@code true}, if {@code inputId}'s setup has been done before. - */ + /** Returns {@code true}, if {@code inputId}'s setup has been done before. */ public boolean isSetupDone(String inputId) { boolean done = mSetUpInputs.contains(inputId); if (DEBUG) { @@ -224,9 +220,7 @@ public class SetupUtils { return done; } - /** - * Returns true, if there is any newly installed input. - */ + /** Returns true, if there is any newly installed input. */ public boolean hasNewInput(TvInputManagerHelper inputManager) { for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { if (isNewInput(input.getId())) { @@ -236,9 +230,7 @@ public class SetupUtils { return false; } - /** - * Checks whether the given input is already recognized by the user or not. - */ + /** Checks whether the given input is already recognized by the user or not. */ private boolean isRecognizedInput(String inputId) { return mRecognizedInputs.contains(inputId); } @@ -251,13 +243,13 @@ public class SetupUtils { for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { mRecognizedInputs.add(input.getId()); } - mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) + mSharedPreferences + .edit() + .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) .apply(); } - /** - * Checks whether there are any unrecognized inputs. - */ + /** Checks whether there are any unrecognized inputs. */ public boolean hasUnrecognizedInput(TvInputManagerHelper inputManager) { for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) { if (!isRecognizedInput(input.getId())) { @@ -276,8 +268,8 @@ public class SetupUtils { // Find all already-verified packages. Set setUpPackages = new HashSet<>(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - for (String input : sp.getStringSet(PREF_KEY_SET_UP_INPUTS, - Collections.emptySet())) { + for (String input : + sp.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.emptySet())) { if (!TextUtils.isEmpty(input)) { ComponentName componentName = ComponentName.unflattenFromString(input); if (componentName != null) { @@ -299,23 +291,28 @@ public class SetupUtils { */ public static void grantEpgPermission(Context context, String packageName) { if (DEBUG) { - Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName - + ")"); + Log.d( + TAG, + "grantEpgPermission(context=" + context + ", packageName=" + packageName + ")"); } try { - int modeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; + int modeFlags = + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; context.grantUriPermission(packageName, TvContract.Channels.CONTENT_URI, modeFlags); context.grantUriPermission(packageName, TvContract.Programs.CONTENT_URI, modeFlags); } catch (SecurityException e) { - Log.e(TAG, "Either TvProvider does not allow granting of Uri permissions or the app" - + " does not have permission.", e); + Log.e( + TAG, + "Either TvProvider does not allow granting of Uri permissions or the app" + + " does not have permission.", + e); } } /** - * Called when Live channels app is launched. Once it is called, {@link - * #isFirstTune} will return false. + * Called when Live channels app is launched. Once it is called, {@link #isFirstTune} will + * return false. */ public void onTuned() { if (!mIsFirstTune) { @@ -325,9 +322,7 @@ public class SetupUtils { mSharedPreferences.edit().putBoolean(PREF_KEY_IS_FIRST_TUNE, false).apply(); } - /** - * Called when input list is changed. It mainly handles input removals. - */ + /** Called when input list is changed. It mainly handles input removals. */ public void onInputListUpdated(TvInputManager manager) { // mRecognizedInputs > mKnownInputs > mSetUpInputs. Set removedInputList = new HashSet<>(mRecognizedInputs); @@ -345,9 +340,11 @@ public class SetupUtils { try { // Just after booting, input list from TvInputManager are not reliable. // So we need to double-check package existence. b/29034900 - mTvApplication.getPackageManager().getPackageInfo( - ComponentName.unflattenFromString(input) - .getPackageName(), PackageManager.GET_ACTIVITIES); + mTvApplication + .getPackageManager() + .getPackageInfo( + ComponentName.unflattenFromString(input).getPackageName(), + PackageManager.GET_ACTIVITIES); Log.i(TAG, "TV input (" + input + ") is removed but package is not deleted"); } catch (NameNotFoundException e) { Log.i(TAG, "TV input (" + input + ") and its package are removed"); @@ -358,9 +355,12 @@ public class SetupUtils { } } if (inputPackageDeleted) { - mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs) + mSharedPreferences + .edit() + .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs) .putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs) - .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply(); + .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) + .apply(); } } } @@ -375,7 +375,9 @@ public class SetupUtils { if (!mRecognizedInputs.contains(inputId)) { Log.i(TAG, "An unrecognized input's setup has been done. inputId=" + inputId); mRecognizedInputs.add(inputId); - mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) + mSharedPreferences + .edit() + .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs) .apply(); } if (!mKnownInputs.contains(inputId)) { @@ -388,4 +390,4 @@ public class SetupUtils { mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply(); } } -} \ No newline at end of file +} diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/util/StringUtils.java index 659807e2..eeaf33a6 100644 --- a/src/com/android/tv/util/StringUtils.java +++ b/src/com/android/tv/util/StringUtils.java @@ -16,16 +16,12 @@ package com.android.tv.util; -/** - * Utility class for handling {@link String}. - */ +/** Utility class for handling {@link String}. */ public final class StringUtils { - private StringUtils() { } + private StringUtils() {} - /** - * Returns compares two strings lexicographically and handles null values quietly. - */ + /** Returns compares two strings lexicographically and handles null values quietly. */ public static int compare(String a, String b) { if (a == null) { return b == null ? 0 : -1; diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java index e737f233..e1b8a398 100644 --- a/src/com/android/tv/util/SystemProperties.java +++ b/src/com/android/tv/util/SystemProperties.java @@ -18,50 +18,35 @@ package com.android.tv.util; import com.android.tv.common.BooleanSystemProperty; -/** - * A convenience class for getting TV related system properties. - */ +/** A convenience class for getting TV related system properties. */ public final class SystemProperties { - /** - * Allow Google Analytics for eng builds. - */ - public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = new BooleanSystemProperty( - "tv_allow_analytics_in_eng", false); + /** Allow Google Analytics for eng builds. */ + public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = + new BooleanSystemProperty("tv_allow_analytics_in_eng", false); - /** - * Allow Strict mode for debug builds. - */ - public static final BooleanSystemProperty ALLOW_STRICT_MODE = new BooleanSystemProperty( - "tv_allow_strict_mode", true); + /** Allow Strict mode for debug builds. */ + public static final BooleanSystemProperty ALLOW_STRICT_MODE = + new BooleanSystemProperty("tv_allow_strict_mode", true); - /** - * When true {@link android.view.KeyEvent}s are logged. Defaults to false. - */ - public static final BooleanSystemProperty LOG_KEYEVENT = new BooleanSystemProperty( - "tv_log_keyevent", false); - /** - * When true debug keys are used. Defaults to false. - */ - public static final BooleanSystemProperty USE_DEBUG_KEYS = new BooleanSystemProperty( - "tv_use_debug_keys", false); + /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */ + public static final BooleanSystemProperty LOG_KEYEVENT = + new BooleanSystemProperty("tv_log_keyevent", false); + /** When true debug keys are used. Defaults to false. */ + public static final BooleanSystemProperty USE_DEBUG_KEYS = + new BooleanSystemProperty("tv_use_debug_keys", false); - /** - * Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. - */ - public static final BooleanSystemProperty USE_TRACKER = new BooleanSystemProperty( - "tv_use_tracker", true); + /** Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. */ + public static final BooleanSystemProperty USE_TRACKER = + new BooleanSystemProperty("tv_use_tracker", true); static { updateSystemProperties(); } - private SystemProperties() { - } + private SystemProperties() {} - /** - * Update the TV related system properties. - */ + /** Update the TV related system properties. */ public static void updateSystemProperties() { BooleanSystemProperty.resetAll(); } diff --git a/src/com/android/tv/util/TimeShiftUtils.java b/src/com/android/tv/util/TimeShiftUtils.java index 8038a78f..977f3333 100644 --- a/src/com/android/tv/util/TimeShiftUtils.java +++ b/src/com/android/tv/util/TimeShiftUtils.java @@ -18,9 +18,7 @@ package com.android.tv.util; import java.util.concurrent.TimeUnit; -/** - * A class that includes convenience methods for time shift plays. - */ +/** A class that includes convenience methods for time shift plays. */ public class TimeShiftUtils { private static final String TAG = "TimeShiftUtils"; private static final boolean DEBUG = false; @@ -30,8 +28,8 @@ public class TimeShiftUtils { private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128}; /** - * The maximum play speed level support by time shift play. In other words, the valid - * speed levels are ranged from 0 to MAX_SPEED_LEVEL (included). + * The maximum play speed level support by time shift play. In other words, the valid speed + * levels are ranged from 0 to MAX_SPEED_LEVEL (included). */ public static final int MAX_SPEED_LEVEL = SHORT_PROGRAM_SPEED_FACTORS.length - 1; @@ -45,17 +43,19 @@ public class TimeShiftUtils { */ public static int getPlaybackSpeed(int speedLevel, long programDurationMillis) throws IndexOutOfBoundsException { - return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) ? - LONG_PROGRAM_SPEED_FACTORS[speedLevel] : SHORT_PROGRAM_SPEED_FACTORS[speedLevel]; + return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) + ? LONG_PROGRAM_SPEED_FACTORS[speedLevel] + : SHORT_PROGRAM_SPEED_FACTORS[speedLevel]; } /** * Returns the maxium possible play speed according to the program's length. + * * @param programDurationMillis the length of program under playing. */ public static int getMaxPlaybackSpeed(long programDurationMillis) { - return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) ? - LONG_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL] + return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) + ? LONG_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL] : SHORT_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL]; } } diff --git a/src/com/android/tv/util/ToastUtils.java b/src/com/android/tv/util/ToastUtils.java index 34346b2a..a25653f6 100644 --- a/src/com/android/tv/util/ToastUtils.java +++ b/src/com/android/tv/util/ToastUtils.java @@ -19,18 +19,13 @@ package com.android.tv.util; import android.content.Context; import android.support.annotation.MainThread; import android.widget.Toast; - import java.lang.ref.WeakReference; -/** - * A utility class for the toast message. - */ +/** A utility class for the toast message. */ public class ToastUtils { private static WeakReference sToast; - /** - * Shows the toast message after canceling the previous one. - */ + /** Shows the toast message after canceling the previous one. */ @MainThread public static void show(Context context, CharSequence text, int duration) { if (sToast != null && sToast.get() != null) { diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index 730a985b..e97bc4f9 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -29,13 +29,11 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; - import com.android.tv.Features; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvCommonUtils; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -49,10 +47,9 @@ public class TvInputManagerHelper { private static final String TAG = "TvInputManagerHelper"; private static final boolean DEBUG = false; - /** - * Types of HDMI device and bundled tuner. - */ + /** Types of HDMI device and bundled tuner. */ public static final int TYPE_CEC_DEVICE = -2; + public static final int TYPE_BUNDLED_TUNER = -3; public static final int TYPE_CEC_DEVICE_RECORDER = -4; public static final int TYPE_CEC_DEVICE_PLAYBACK = -5; @@ -60,14 +57,12 @@ public class TvInputManagerHelper { private static final String PERMISSION_ACCESS_ALL_EPG_DATA = "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; - private static final String [] mPhysicalTunerBlackList = { - }; + private static final String[] mPhysicalTunerBlackList = {}; private static final String META_LABEL_SORT_KEY = "input_sort_key"; - /** - * The default tv input priority to show. - */ + /** The default tv input priority to show. */ private static final ArrayList DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>(); + static { DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER); DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER); @@ -86,11 +81,10 @@ public class TvInputManagerHelper { DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER); } - private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = { - }; + private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {}; private static final String[] TESTABLE_INPUTS = { - "com.android.tv.testinput/.TestTvInputService" + "com.android.tv.testinput/.TestTvInputService" }; private final Context mContext; @@ -106,100 +100,103 @@ public class TvInputManagerHelper { private final Map mTvInputApplicationIcons = new ArrayMap<>(); private final Map mTvInputAppliactionBanners = new ArrayMap<>(); - private final TvInputCallback mInternalCallback = new TvInputCallback() { - @Override - public void onInputStateChanged(String inputId, int state) { - if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state); - if (isInBlackList(inputId)) { - return; - } - mInputStateMap.put(inputId, state); - for (TvInputCallback callback : mCallbacks) { - callback.onInputStateChanged(inputId, state); - } - } + private final TvInputCallback mInternalCallback = + new TvInputCallback() { + @Override + public void onInputStateChanged(String inputId, int state) { + if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state); + if (isInBlackList(inputId)) { + return; + } + mInputStateMap.put(inputId, state); + for (TvInputCallback callback : mCallbacks) { + callback.onInputStateChanged(inputId, state); + } + } - @Override - public void onInputAdded(String inputId) { - if (DEBUG) Log.d(TAG, "onInputAdded " + inputId); - if (isInBlackList(inputId)) { - return; - } - TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); - if (info != null) { - mInputMap.put(inputId, info); - mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); - CharSequence inputCustomLabel = info.loadCustomLabel(mContext); - if (inputCustomLabel != null) { - mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + @Override + public void onInputAdded(String inputId) { + if (DEBUG) Log.d(TAG, "onInputAdded " + inputId); + if (isInBlackList(inputId)) { + return; + } + TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); + if (info != null) { + mInputMap.put(inputId, info); + mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = info.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + } + mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId)); + mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info)); + } + mContentRatingsManager.update(); + for (TvInputCallback callback : mCallbacks) { + callback.onInputAdded(inputId); + } } - mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId)); - mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info)); - } - mContentRatingsManager.update(); - for (TvInputCallback callback : mCallbacks) { - callback.onInputAdded(inputId); - } - } - @Override - public void onInputRemoved(String inputId) { - if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); - mInputMap.remove(inputId); - mTvInputLabels.remove(inputId); - mTvInputCustomLabels.remove(inputId); - mTvInputApplicationLabels.remove(inputId); - mTvInputApplicationIcons.remove(inputId); - mTvInputAppliactionBanners.remove(inputId); - mInputStateMap.remove(inputId); - mInputIdToPartnerInputMap.remove(inputId); - mContentRatingsManager.update(); - for (TvInputCallback callback : mCallbacks) { - callback.onInputRemoved(inputId); - } - ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey( - inputId)); - } + @Override + public void onInputRemoved(String inputId) { + if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId); + mInputMap.remove(inputId); + mTvInputLabels.remove(inputId); + mTvInputCustomLabels.remove(inputId); + mTvInputApplicationLabels.remove(inputId); + mTvInputApplicationIcons.remove(inputId); + mTvInputAppliactionBanners.remove(inputId); + mInputStateMap.remove(inputId); + mInputIdToPartnerInputMap.remove(inputId); + mContentRatingsManager.update(); + for (TvInputCallback callback : mCallbacks) { + callback.onInputRemoved(inputId); + } + ImageCache.getInstance() + .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId)); + } - @Override - public void onInputUpdated(String inputId) { - if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId); - if (isInBlackList(inputId)) { - return; - } - TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); - mInputMap.put(inputId, info); - mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); - CharSequence inputCustomLabel = info.loadCustomLabel(mContext); - if (inputCustomLabel != null) { - mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); - } - mTvInputApplicationLabels.remove(inputId); - mTvInputApplicationIcons.remove(inputId); - mTvInputAppliactionBanners.remove(inputId); - for (TvInputCallback callback : mCallbacks) { - callback.onInputUpdated(inputId); - } - ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey( - inputId)); - } + @Override + public void onInputUpdated(String inputId) { + if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId); + if (isInBlackList(inputId)) { + return; + } + TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); + mInputMap.put(inputId, info); + mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = info.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); + } + mTvInputApplicationLabels.remove(inputId); + mTvInputApplicationIcons.remove(inputId); + mTvInputAppliactionBanners.remove(inputId); + for (TvInputCallback callback : mCallbacks) { + callback.onInputUpdated(inputId); + } + ImageCache.getInstance() + .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId)); + } - @Override - public void onTvInputInfoUpdated(TvInputInfo inputInfo) { - if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo); - mInputMap.put(inputInfo.getId(), inputInfo); - mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString()); - CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext); - if (inputCustomLabel != null) { - mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString()); - } - for (TvInputCallback callback : mCallbacks) { - callback.onTvInputInfoUpdated(inputInfo); - } - ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey( - inputInfo.getId())); - } - }; + @Override + public void onTvInputInfoUpdated(TvInputInfo inputInfo) { + if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo); + mInputMap.put(inputInfo.getId(), inputInfo); + mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString()); + CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext); + if (inputCustomLabel != null) { + mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString()); + } + for (TvInputCallback callback : mCallbacks) { + callback.onTvInputInfoUpdated(inputInfo); + } + ImageCache.getInstance() + .remove( + ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey( + inputInfo.getId())); + } + }; private final Handler mHandler = new Handler(); private boolean mStarted; @@ -247,7 +244,9 @@ public class TvInputManagerHelper { mInputStateMap.put(inputId, state); mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input)); } - SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG, + SoftPreconditions.checkState( + mInputStateMap.size() == mInputMap.size(), + TAG, "mInputStateMap not the same size as mInputMap"); mContentRatingsManager.update(); } @@ -264,13 +263,12 @@ public class TvInputManagerHelper { mTvInputCustomLabels.clear(); mTvInputApplicationLabels.clear(); mTvInputApplicationIcons.clear(); - mTvInputAppliactionBanners.clear();; + mTvInputAppliactionBanners.clear(); + ; mInputIdToPartnerInputMap.clear(); } - /** - * Clears the TvInput labels map. - */ + /** Clears the TvInput labels map. */ public void clearTvInputLabels() { mTvInputLabels.clear(); mTvInputCustomLabels.clear(); @@ -294,8 +292,8 @@ public class TvInputManagerHelper { } /** - * Returns the default comparator for {@link TvInputInfo}. - * See {@link InputComparatorInternal} for detail. + * Returns the default comparator for {@link TvInputInfo}. See {@link InputComparatorInternal} + * for detail. */ public Comparator getDefaultTvInputInfoComparator() { return mTvInputInfoComparator; @@ -304,35 +302,31 @@ public class TvInputManagerHelper { /** * Checks if the input is from a partner. * - * It's visible for comparator test. - * Package private is enough for this method, but public is necessary to workaround mockito - * bug. + *

It's visible for comparator test. Package private is enough for this method, but public is + * necessary to workaround mockito bug. */ @VisibleForTesting public boolean isPartnerInput(TvInputInfo inputInfo) { return isSystemInput(inputInfo) && !isBundledInput(inputInfo); } - /** - * Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. - */ + /** Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. */ public boolean isSystemInput(TvInputInfo inputInfo) { return inputInfo != null - && (inputInfo.getServiceInfo().applicationInfo.flags - & ApplicationInfo.FLAG_SYSTEM) != 0; + && (inputInfo.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) + != 0; } - /** - * Is the input one known bundled inputs not written by OEM/SOCs. - */ + /** Is the input one known bundled inputs not written by OEM/SOCs. */ public boolean isBundledInput(TvInputInfo inputInfo) { - return inputInfo != null && Utils.isInBundledPackageSet(inputInfo.getServiceInfo() - .applicationInfo.packageName); + return inputInfo != null + && Utils.isInBundledPackageSet( + inputInfo.getServiceInfo().applicationInfo.packageName); } /** - * Returns if the given input is bundled and written by OEM/SOCs. - * This returns the cached result. + * Returns if the given input is bundled and written by OEM/SOCs. This returns the cached + * result. */ public boolean isPartnerInput(String inputId) { Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId); @@ -348,9 +342,7 @@ public class TvInputManagerHelper { return mTvInputManager != null; } - /** - * Loads label of {@code info}. - */ + /** Loads label of {@code info}. */ public String loadLabel(TvInputInfo info) { String label = mTvInputLabels.get(info.getId()); if (label == null) { @@ -360,9 +352,7 @@ public class TvInputManagerHelper { return label; } - /** - * Loads custom label of {@code info} - */ + /** Loads custom label of {@code info} */ public String loadCustomLabel(TvInputInfo info) { String customLabel = mTvInputCustomLabels.get(info.getId()); if (customLabel == null) { @@ -375,60 +365,46 @@ public class TvInputManagerHelper { return customLabel; } - /** - * Gets the tv input application's label. - */ + /** Gets the tv input application's label. */ public CharSequence getTvInputApplicationLabel(CharSequence inputId) { return mTvInputApplicationLabels.get(inputId); } - /** - * Stores the tv input application's label. - */ + /** Stores the tv input application's label. */ public void setTvInputApplicationLabel(String inputId, CharSequence label) { mTvInputApplicationLabels.put(inputId, label); } - /** - * Gets the tv input application's icon. - */ + /** Gets the tv input application's icon. */ public Drawable getTvInputApplicationIcon(String inputId) { return mTvInputApplicationIcons.get(inputId); } - /** - * Stores the tv input application's icon. - */ + /** Stores the tv input application's icon. */ public void setTvInputApplicationIcon(String inputId, Drawable icon) { mTvInputApplicationIcons.put(inputId, icon); } - /** - * Gets the tv input application's banner. - */ + /** Gets the tv input application's banner. */ public Drawable getTvInputApplicationBanner(String inputId) { return mTvInputAppliactionBanners.get(inputId); } - /** - * Stores the tv input application's banner. - */ + /** Stores the tv input application's banner. */ public void setTvInputApplicationBanner(String inputId, Drawable banner) { mTvInputAppliactionBanners.put(inputId, banner); } - /** - * Returns if TV input exists with the input id. - */ + /** Returns if TV input exists with the input id. */ public boolean hasTvInputInfo(String inputId) { - SoftPreconditions.checkState(mStarted, TAG, - "hasTvInputInfo() called before TvInputManagerHelper was started."); + SoftPreconditions.checkState( + mStarted, TAG, "hasTvInputInfo() called before TvInputManagerHelper was started."); return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null; } public TvInputInfo getTvInputInfo(String inputId) { - SoftPreconditions.checkState(mStarted, TAG, - "getTvInputInfo() called before TvInputManagerHelper was started."); + SoftPreconditions.checkState( + mStarted, TAG, "getTvInputInfo() called before TvInputManagerHelper was started."); if (!mStarted) { return null; } @@ -461,7 +437,6 @@ public class TvInputManagerHelper { SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started"); if (!mStarted) { return TvInputManager.INPUT_STATE_DISCONNECTED; - } Integer state = mInputStateMap.get(inputId); if (state == null) { @@ -483,16 +458,13 @@ public class TvInputManagerHelper { return mParentalControlSettings; } - /** - * Returns a ContentRatingsManager instance for a given application context. - */ + /** Returns a ContentRatingsManager instance for a given application context. */ public ContentRatingsManager getContentRatingsManager() { return mContentRatingsManager; } private int getInputSortKey(TvInputInfo input) { - return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, - Integer.MAX_VALUE); + return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, Integer.MAX_VALUE); } private boolean isInputPhysicalTuner(TvInputInfo input) { @@ -504,15 +476,20 @@ public class TvInputManagerHelper { if (input.createSetupIntent() == null) { return false; } else { - boolean mayBeTunerInput = mPackageManager.checkPermission( - PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName) - == PackageManager.PERMISSION_GRANTED; + boolean mayBeTunerInput = + mPackageManager.checkPermission( + PERMISSION_ACCESS_ALL_EPG_DATA, + input.getServiceInfo().packageName) + == PackageManager.PERMISSION_GRANTED; if (!mayBeTunerInput) { try { - ApplicationInfo ai = mPackageManager.getApplicationInfo( - input.getServiceInfo().packageName, 0); - if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM - | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) { + ApplicationInfo ai = + mPackageManager.getApplicationInfo( + input.getServiceInfo().packageName, 0); + if ((ai.flags + & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) + == 0) { return false; } } catch (PackageManager.NameNotFoundException e) { @@ -545,10 +522,9 @@ public class TvInputManagerHelper { /** * Default comparator for TvInputInfo. * - * It's static class that accepts {@link TvInputManagerHelper} as parameter to test. - * To test comparator, we need to mock API in parent class such as {@link #isPartnerInput}, - * but it's impossible for an inner class to use mocked methods. - * (i.e. Mockito's spy doesn't work) + *

It's static class that accepts {@link TvInputManagerHelper} as parameter to test. To test + * comparator, we need to mock API in parent class such as {@link #isPartnerInput}, but it's + * impossible for an inner class to use mocked methods. (i.e. Mockito's spy doesn't work) */ @VisibleForTesting static class InputComparatorInternal implements Comparator { @@ -568,8 +544,8 @@ public class TvInputManagerHelper { } /** - * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of - * TV inputs. + * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of TV + * inputs. */ public static class HardwareInputComparator implements Comparator { private Map mTypePriorities = new HashMap<>(); @@ -591,10 +567,12 @@ public class TvInputManagerHelper { return -1; } - boolean enabledL = (mTvInputManagerHelper.getInputState(lhs) - != TvInputManager.INPUT_STATE_DISCONNECTED); - boolean enabledR = (mTvInputManagerHelper.getInputState(rhs) - != TvInputManager.INPUT_STATE_DISCONNECTED); + boolean enabledL = + (mTvInputManagerHelper.getInputState(lhs) + != TvInputManager.INPUT_STATE_DISCONNECTED); + boolean enabledR = + (mTvInputManagerHelper.getInputState(rhs) + != TvInputManager.INPUT_STATE_DISCONNECTED); if (enabledL != enabledR) { return enabledL ? -1 : 1; } @@ -620,11 +598,13 @@ public class TvInputManagerHelper { return sortKeyR - sortKeyL; } - String parentLabelL = lhs.getParentId() != null - ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId())) + String parentLabelL = + lhs.getParentId() != null + ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId())) : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId())); - String parentLabelR = rhs.getParentId() != null - ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId())) + String parentLabelR = + rhs.getParentId() != null + ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId())) : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId())); if (!TextUtils.equals(parentLabelL, parentLabelR)) { diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java index c5fde317..ae79e7e5 100644 --- a/src/com/android/tv/util/TvSettings.java +++ b/src/com/android/tv/util/TvSettings.java @@ -21,7 +21,6 @@ import android.content.SharedPreferences; import android.media.tv.TvTrackInfo; import android.preference.PreferenceManager; import android.support.annotation.IntDef; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -29,11 +28,11 @@ import java.util.HashSet; import java.util.Set; /** - * A class about the constants for TV settings. - * Objects that are returned from the various {@code get} methods must be treated as immutable. + * A class about the constants for TV settings. Objects that are returned from the various {@code + * get} methods must be treated as immutable. */ public final class TvSettings { - public static final String PREF_DISPLAY_MODE = "display_mode"; // int value + public static final String PREF_DISPLAY_MODE = "display_mode"; // int value public static final String PREF_PIN = "pin"; // 4-digit string value. Otherwise, it's not set. // Multi-track audio settings @@ -56,9 +55,14 @@ public final class TvSettings { @Retention(RetentionPolicy.SOURCE) @IntDef({ - CONTENT_RATING_LEVEL_NONE, CONTENT_RATING_LEVEL_HIGH, CONTENT_RATING_LEVEL_MEDIUM, - CONTENT_RATING_LEVEL_LOW, CONTENT_RATING_LEVEL_CUSTOM }) + CONTENT_RATING_LEVEL_NONE, + CONTENT_RATING_LEVEL_HIGH, + CONTENT_RATING_LEVEL_MEDIUM, + CONTENT_RATING_LEVEL_LOW, + CONTENT_RATING_LEVEL_CUSTOM + }) public @interface ContentRatingLevel {} + public static final int CONTENT_RATING_LEVEL_NONE = 0; public static final int CONTENT_RATING_LEVEL_HIGH = 1; public static final int CONTENT_RATING_LEVEL_MEDIUM = 2; @@ -69,61 +73,74 @@ public final class TvSettings { // Multi-track audio settings public static String getMultiAudioId(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getString( - PREF_MULTI_AUDIO_ID, null); + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(PREF_MULTI_AUDIO_ID, null); } public static void setMultiAudioId(Context context, String language) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putString( - PREF_MULTI_AUDIO_ID, language).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(PREF_MULTI_AUDIO_ID, language) + .apply(); } public static String getMultiAudioLanguage(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getString( - PREF_MULTI_AUDIO_LANGUAGE, null); + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(PREF_MULTI_AUDIO_LANGUAGE, null); } public static void setMultiAudioLanguage(Context context, String language) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putString( - PREF_MULTI_AUDIO_LANGUAGE, language).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(PREF_MULTI_AUDIO_LANGUAGE, language) + .apply(); } public static int getMultiAudioChannelCount(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getInt( - PREF_MULTI_AUDIO_CHANNEL_COUNT, 0); + return PreferenceManager.getDefaultSharedPreferences(context) + .getInt(PREF_MULTI_AUDIO_CHANNEL_COUNT, 0); } public static void setMultiAudioChannelCount(Context context, int channelCount) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putInt( - PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putInt(PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount) + .apply(); } - public static void setDvrPlaybackTrackSettings(Context context, int trackType, - TvTrackInfo info) { + public static void setDvrPlaybackTrackSettings( + Context context, int trackType, TvTrackInfo info) { if (trackType == TvTrackInfo.TYPE_AUDIO) { if (info == null) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .remove(PREF_DVR_MULTI_AUDIO_ID).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .remove(PREF_DVR_MULTI_AUDIO_ID) + .apply(); } else { - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .putString(PREF_DVR_MULTI_AUDIO_LANGUAGE, info.getLanguage()) .putInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, info.getAudioChannelCount()) - .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId()).apply(); + .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId()) + .apply(); } } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { if (info == null) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .remove(PREF_DVR_SUBTITLE_ID).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .remove(PREF_DVR_SUBTITLE_ID) + .apply(); } else { - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .putString(PREF_DVR_SUBTITLE_LANGUAGE, info.getLanguage()) - .putString(PREF_DVR_SUBTITLE_ID, info.getId()).apply(); + .putString(PREF_DVR_SUBTITLE_ID, info.getId()) + .apply(); } } } - public static TvTrackInfo getDvrPlaybackTrackSettings(Context context, - int trackType) { + public static TvTrackInfo getDvrPlaybackTrackSettings(Context context, int trackType) { String language; String trackId; int channelCount; @@ -136,7 +153,9 @@ public final class TvSettings { language = pref.getString(PREF_DVR_MULTI_AUDIO_LANGUAGE, null); channelCount = pref.getInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, 0); return new TvTrackInfo.Builder(trackType, trackId) - .setLanguage(language).setAudioChannelCount(channelCount).build(); + .setLanguage(language) + .setAudioChannelCount(channelCount) + .build(); } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) { trackId = pref.getString(PREF_DVR_SUBTITLE_ID, null); if (trackId == null) { @@ -153,16 +172,20 @@ public final class TvSettings { public static void addContentRatingSystem(Context context, String id) { Set contentRatingSystemSet = getContentRatingSystemSet(context); if (contentRatingSystemSet.add(id)) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet) + .apply(); } } public static void removeContentRatingSystem(Context context, String id) { Set contentRatingSystemSet = getContentRatingSystemSet(context); if (contentRatingSystemSet.remove(id)) { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet) + .apply(); } } @@ -176,25 +199,28 @@ public final class TvSettings { */ public static boolean isContentRatingSystemSet(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) - .getStringSet(PREF_CONTENT_RATING_SYSTEMS, null) != null; + .getStringSet(PREF_CONTENT_RATING_SYSTEMS, null) + != null; } private static Set getContentRatingSystemSet(Context context) { - return new HashSet<>(PreferenceManager.getDefaultSharedPreferences(context) - .getStringSet(PREF_CONTENT_RATING_SYSTEMS, Collections.emptySet())); + return new HashSet<>( + PreferenceManager.getDefaultSharedPreferences(context) + .getStringSet(PREF_CONTENT_RATING_SYSTEMS, Collections.emptySet())); } @ContentRatingLevel @SuppressWarnings("ResourceType") public static int getContentRatingLevel(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getInt( - PREF_CONTENT_RATING_LEVEL, CONTENT_RATING_LEVEL_NONE); + return PreferenceManager.getDefaultSharedPreferences(context) + .getInt(PREF_CONTENT_RATING_LEVEL, CONTENT_RATING_LEVEL_NONE); } - public static void setContentRatingLevel(Context context, - @ContentRatingLevel int level) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putInt( - PREF_CONTENT_RATING_LEVEL, level).apply(); + public static void setContentRatingLevel(Context context, @ContentRatingLevel int level) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putInt(PREF_CONTENT_RATING_LEVEL, level) + .apply(); } /** @@ -202,8 +228,8 @@ public final class TvSettings { * repeatedly). */ public static long getDisablePinUntil(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getLong( - PREF_DISABLE_PIN_UNTIL, 0); + return PreferenceManager.getDefaultSharedPreferences(context) + .getLong(PREF_DISABLE_PIN_UNTIL, 0); } /** @@ -211,7 +237,9 @@ public final class TvSettings { * repeatedly). */ public static void setDisablePinUntil(Context context, long timeMillis) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putLong( - PREF_DISABLE_PIN_UNTIL, timeMillis).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong(PREF_DISABLE_PIN_UNTIL, timeMillis) + .apply(); } -} \ No newline at end of file +} diff --git a/src/com/android/tv/util/TvTrackInfoUtils.java b/src/com/android/tv/util/TvTrackInfoUtils.java index 667cc9bf..09874502 100644 --- a/src/com/android/tv/util/TvTrackInfoUtils.java +++ b/src/com/android/tv/util/TvTrackInfoUtils.java @@ -16,27 +16,24 @@ package com.android.tv.util; import android.media.tv.TvTrackInfo; - import java.util.Comparator; import java.util.List; -/** - * Static utilities for {@link TvTrackInfo}. - */ +/** Static utilities for {@link TvTrackInfo}. */ public class TvTrackInfoUtils { /** * Compares how closely two {@link android.media.tv.TvTrackInfo}s match {@code language}, {@code * channelCount} and {@code id} in that precedence. * - * @param id The track id to match. - * @param language The language to match. + * @param id The track id to match. + * @param language The language to match. * @param channelCount The channel count to match. * @return -1 if lhs is a worse match, 0 if lhs and rhs match equally and 1 if lhs is a better - * match. + * match. */ - public static Comparator createComparator(final String id, final String language, - final int channelCount) { + public static Comparator createComparator( + final String id, final String language, final int channelCount) { return new Comparator() { @Override @@ -52,15 +49,17 @@ public class TvTrackInfoUtils { } // Assumes {@code null} language matches to any language since it means user hasn't // selected any track before or selected a track without language information. - boolean lhsLangMatch = language == null || Utils.isEqualLanguage(lhs.getLanguage(), - language); - boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(), - language); + boolean lhsLangMatch = + language == null || Utils.isEqualLanguage(lhs.getLanguage(), language); + boolean rhsLangMatch = + language == null || Utils.isEqualLanguage(rhs.getLanguage(), language); if (lhsLangMatch && rhsLangMatch) { - boolean lhsCountMatch = lhs.getType() != TvTrackInfo.TYPE_AUDIO - || lhs.getAudioChannelCount() == channelCount; - boolean rhsCountMatch = rhs.getType() != TvTrackInfo.TYPE_AUDIO - || rhs.getAudioChannelCount() == channelCount; + boolean lhsCountMatch = + lhs.getType() != TvTrackInfo.TYPE_AUDIO + || lhs.getAudioChannelCount() == channelCount; + boolean rhsCountMatch = + rhs.getType() != TvTrackInfo.TYPE_AUDIO + || rhs.getAudioChannelCount() == channelCount; if (lhsCountMatch && rhsCountMatch) { return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id)); } else { @@ -74,16 +73,16 @@ public class TvTrackInfoUtils { } /** - * Selects the best TvTrackInfo available or the first if none matches. + * Selects the best TvTrackInfo available or the first if none matches. * - * @param tracks The tracks to choose from - * @param id The track id to match. - * @param language The language to match. + * @param tracks The tracks to choose from + * @param id The track id to match. + * @param language The language to match. * @param channelCount The channel count to match. * @return the best matching track or the first one if none matches. */ - public static TvTrackInfo getBestTrackInfo(List tracks, String id, String language, - int channelCount) { + public static TvTrackInfo getBestTrackInfo( + List tracks, String id, String language, int channelCount) { if (tracks == null) { return null; } @@ -97,6 +96,5 @@ public class TvTrackInfoUtils { return best; } - private TvTrackInfoUtils() { - } -} \ No newline at end of file + private TvTrackInfoUtils() {} +} diff --git a/src/com/android/tv/util/TvUriMatcher.java b/src/com/android/tv/util/TvUriMatcher.java index 3d91cdad..9e74117e 100644 --- a/src/com/android/tv/util/TvUriMatcher.java +++ b/src/com/android/tv/util/TvUriMatcher.java @@ -21,22 +21,25 @@ import android.content.UriMatcher; import android.media.tv.TvContract; import android.net.Uri; import android.support.annotation.IntDef; - import com.android.tv.search.LocalSearchProvider; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** - * Utility class to aid in matching URIs in TvProvider. - */ +/** Utility class to aid in matching URIs in TvProvider. */ public class TvUriMatcher { private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); @Retention(RetentionPolicy.SOURCE) - @IntDef({MATCH_CHANNEL, MATCH_CHANNEL_ID, MATCH_PROGRAM, MATCH_PROGRAM_ID, - MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID, - MATCH_ON_DEVICE_SEARCH}) + @IntDef({ + MATCH_CHANNEL, + MATCH_CHANNEL_ID, + MATCH_PROGRAM, + MATCH_PROGRAM_ID, + MATCH_RECORDED_PROGRAM, + MATCH_RECORDED_PROGRAM_ID, + MATCH_WATCHED_PROGRAM_ID, + MATCH_ON_DEVICE_SEARCH + }) private @interface TvProviderUriMatchCode {} /** The code for the channels URI. */ public static final int MATCH_CHANNEL = 1; @@ -54,6 +57,7 @@ public class TvUriMatcher { public static final int MATCH_WATCHED_PROGRAM_ID = 7; /** The code for the on-device search URI. */ public static final int MATCH_ON_DEVICE_SEARCH = 8; + static { URI_MATCHER.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL); URI_MATCHER.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID); @@ -62,11 +66,13 @@ public class TvUriMatcher { URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM); URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID); URI_MATCHER.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID); - URI_MATCHER.addURI(LocalSearchProvider.AUTHORITY, - SearchManager.SUGGEST_URI_PATH_QUERY + "/*", MATCH_ON_DEVICE_SEARCH); + URI_MATCHER.addURI( + LocalSearchProvider.AUTHORITY, + SearchManager.SUGGEST_URI_PATH_QUERY + "/*", + MATCH_ON_DEVICE_SEARCH); } - private TvUriMatcher() { } + private TvUriMatcher() {} /** * Try to match against the path in a url. @@ -74,7 +80,8 @@ public class TvUriMatcher { * @see UriMatcher#match */ @SuppressWarnings("WrongConstant") - @TvProviderUriMatchCode public static int match(Uri uri) { + @TvProviderUriMatchCode + public static int match(Uri uri) { return URI_MATCHER.match(uri); } } diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java index d11bab3c..ac3be643 100644 --- a/src/com/android/tv/util/Utils.java +++ b/src/com/android/tv/util/Utils.java @@ -41,7 +41,6 @@ import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; import android.view.View; - import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.TvApplication; @@ -52,7 +51,6 @@ import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; import com.android.tv.experiments.Experiments; - import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -69,18 +67,16 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -/** - * A class that includes convenience methods for accessing TvProvider database. - */ +/** A class that includes convenience methods for accessing TvProvider database. */ public class Utils { private static final String TAG = "Utils"; private static final boolean DEBUG = false; - private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", - Locale.US); + private static final SimpleDateFormat ISO_8601 = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); public static final String EXTRA_KEY_ACTION = "action"; - public static final String EXTRA_ACTION_SHOW_TV_INPUT ="show_tv_input"; + public static final String EXTRA_ACTION_SHOW_TV_INPUT = "show_tv_input"; public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher"; public static final String EXTRA_KEY_RECORDED_PROGRAM_ID = "recorded_program_id"; public static final String EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME = "recorded_program_seek_time"; @@ -97,8 +93,7 @@ public class Utils { private static final String PREF_KEY_LAST_WATCHED_CHANNEL_URI = "last_watched_channel_uri"; private static final String PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID = "last_watched_tuner_input_id"; - private static final String PREF_KEY_RECORDING_FAILED_REASONS = - "recording_failed_reasons"; + private static final String PREF_KEY_RECORDING_FAILED_REASONS = "recording_failed_reasons"; private static final String PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET = "failed_scheduled_recording_info_set"; @@ -150,13 +145,11 @@ public class Utils { } } - private Utils() { - } + private Utils() {} public static String buildSelectionForIds(String idName, List ids) { StringBuilder sb = new StringBuilder(); - sb.append(idName).append(" in (") - .append(ids.get(0)); + sb.append(idName).append(" in (").append(ids.get(0)); for (int i = 1; i < ids.size(); ++i) { sb.append(",").append(ids.get(i)); } @@ -171,8 +164,8 @@ public class Utils { } Uri channelUri = TvContract.buildChannelUri(channelId); String[] projection = {TvContract.Channels.COLUMN_INPUT_ID}; - try (Cursor cursor = context.getContentResolver() - .query(channelUri, projection, null, null, null)) { + try (Cursor cursor = + context.getContentResolver().query(channelUri, projection, null, null, null)) { if (cursor != null && cursor.moveToNext()) { return Utils.intern(cursor.getString(0)); } @@ -185,60 +178,61 @@ public class Utils { Log.e(TAG, "setLastWatchedChannel: channel cannot be null"); return; } - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, channel.getUri().toString()).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, channel.getUri().toString()) + .apply(); if (!channel.isPassthrough()) { long channelId = channel.getId(); if (channel.getId() < 0) { throw new IllegalArgumentException("channelId should be equal to or larger than 0"); } - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId) - .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(), + .putLong( + PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(), channelId) .putString(PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID, channel.getInputId()) .apply(); } } - /** - * Sets recording failed reason. - */ + /** Sets recording failed reason. */ public static void setRecordingFailedReason(Context context, int reason) { long reasons = getRecordingFailedReasons(context) | 0x1 << reason; - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .putLong(PREF_KEY_RECORDING_FAILED_REASONS, reasons) .apply(); } - /** - * Adds the info of failed scheduled recording. - */ - public static void addFailedScheduledRecordingInfo(Context context, - String scheduledRecordingInfo) { + /** Adds the info of failed scheduled recording. */ + public static void addFailedScheduledRecordingInfo( + Context context, String scheduledRecordingInfo) { Set failedScheduledRecordingInfoSet = getFailedScheduledRecordingInfoSet(context); failedScheduledRecordingInfoSet.add(scheduledRecordingInfo); - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putStringSet( + PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, failedScheduledRecordingInfoSet) .apply(); } - /** - * Clears the failed scheduled recording info set. - */ + /** Clears the failed scheduled recording info set. */ public static void clearFailedScheduledRecordingInfoSet(Context context) { - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .remove(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET) .apply(); } - /** - * Clears recording failed reason. - */ + /** Clears recording failed reason. */ public static void clearRecordingFailedReason(Context context, int reason) { long reasons = getRecordingFailedReasons(context) & ~(0x1 << reason); - PreferenceManager.getDefaultSharedPreferences(context).edit() + PreferenceManager.getDefaultSharedPreferences(context) + .edit() .putLong(PREF_KEY_RECORDING_FAILED_REASONS, reasons) .apply(); } @@ -258,9 +252,7 @@ public class Utils { .getString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, null); } - /** - * Returns the last watched tuner input id. - */ + /** Returns the last watched tuner input id. */ public static String getLastWatchedTunerInputId(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) .getString(PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID, null); @@ -268,32 +260,28 @@ public class Utils { private static long getRecordingFailedReasons(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) - .getLong(PREF_KEY_RECORDING_FAILED_REASONS, - RECORDING_FAILED_REASON_NONE); + .getLong(PREF_KEY_RECORDING_FAILED_REASONS, RECORDING_FAILED_REASON_NONE); } - /** - * Returns the failed scheduled recordings info set. - */ + /** Returns the failed scheduled recordings info set. */ public static Set getFailedScheduledRecordingInfoSet(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) .getStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, new HashSet<>()); } - /** - * Checks do recording failed reason exist. - */ + /** Checks do recording failed reason exist. */ public static boolean hasRecordingFailedReason(Context context, int reason) { long reasons = getRecordingFailedReasons(context); return (reasons & 0x1 << reason) != 0; } /** - * Returns {@code true}, if {@code uri} specifies an input, which is usually generated - * from {@link TvContract#buildChannelsUriForInput}. + * Returns {@code true}, if {@code uri} specifies an input, which is usually generated from + * {@link TvContract#buildChannelsUriForInput}. */ public static boolean isChannelUriForInput(Uri uri) { - return isTvUri(uri) && PATH_CHANNEL.equals(uri.getPathSegments().get(0)) + return isTvUri(uri) + && PATH_CHANNEL.equals(uri.getPathSegments().get(0)) && !TextUtils.isEmpty(uri.getQueryParameter("input")); } @@ -314,7 +302,8 @@ public class Utils { } private static boolean isTvUri(Uri uri) { - return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) + return uri != null + && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && TvContract.AUTHORITY.equals(uri.getAuthority()); } @@ -323,23 +312,17 @@ public class Utils { return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0)); } - /** - * Returns {@code true}, if {@code uri} is a programs URI. - */ + /** Returns {@code true}, if {@code uri} is a programs URI. */ public static boolean isProgramsUri(Uri uri) { return isTvUri(uri) && PATH_PROGRAM.equals(uri.getPathSegments().get(0)); } - /** - * Returns {@code true}, if {@code uri} is a programs URI. - */ + /** Returns {@code true}, if {@code uri} is a programs URI. */ public static boolean isRecordedProgramsUri(Uri uri) { return isTvUri(uri) && PATH_RECORDED_PROGRAM.equals(uri.getPathSegments().get(0)); } - /** - * Gets the info of the program on particular time. - */ + /** Gets the info of the program on particular time. */ @WorkerThread public static Program getProgramAt(Context context, long channelId, long timeMs) { if (channelId == Channel.INVALID_ID) { @@ -355,10 +338,11 @@ public class Utils { Log.w(TAG, message); } } - Uri uri = TvContract.buildProgramsUriForChannel(TvContract.buildChannelUri(channelId), - timeMs, timeMs); - try (Cursor cursor = context.getContentResolver().query(uri, Program.PROJECTION, - null, null, null)) { + Uri uri = + TvContract.buildProgramsUriForChannel( + TvContract.buildChannelUri(channelId), timeMs, timeMs); + try (Cursor cursor = + context.getContentResolver().query(uri, Program.PROJECTION, null, null, null)) { if (cursor != null && cursor.moveToNext()) { return Program.fromCursor(cursor); } @@ -366,55 +350,72 @@ public class Utils { return null; } - /** - * Gets the info of the current program. - */ + /** Gets the info of the current program. */ @WorkerThread public static Program getCurrentProgram(Context context, long channelId) { return getProgramAt(context, channelId, System.currentTimeMillis()); } - /** - * Returns the round off minutes when convert milliseconds to minutes. - */ + /** Returns the round off minutes when convert milliseconds to minutes. */ public static int getRoundOffMinsFromMs(long millis) { // Round off the result by adding half minute to the original ms. return (int) TimeUnit.MILLISECONDS.toMinutes(millis + HALF_MINUTE_MS); } /** - * Returns duration string according to the date & time format. - * If {@code startUtcMillis} and {@code endUtcMills} are equal, - * formatted time will be returned instead. + * Returns duration string according to the date & time format. If {@code startUtcMillis} and + * {@code endUtcMills} are equal, formatted time will be returned instead. * * @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. + * @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, long startUtcMillis, long endUtcMillis, boolean useShortFormat) { - return getDurationString(context, System.currentTimeMillis(), startUtcMillis, endUtcMillis, - useShortFormat, 0); + return getDurationString( + context, + System.currentTimeMillis(), + startUtcMillis, + endUtcMillis, + useShortFormat, + 0); } @VisibleForTesting - static String getDurationString(Context context, long baseMillis, long startUtcMillis, - long endUtcMillis, boolean useShortFormat, int flag) { - return getDurationString(context, startUtcMillis, endUtcMillis, - useShortFormat, !isInGivenDay(baseMillis, startUtcMillis), true, flag); - } - - /** - * Returns duration string according to the time format, may not contain date information. - * Note: At least one of showDate and showTime should be true. + static String getDurationString( + Context context, + long baseMillis, + long startUtcMillis, + long endUtcMillis, + boolean useShortFormat, + int flag) { + return getDurationString( + context, + startUtcMillis, + endUtcMillis, + useShortFormat, + !isInGivenDay(baseMillis, startUtcMillis), + true, + flag); + } + + /** + * Returns duration string according to the time format, may not contain date information. Note: + * At least one of showDate and showTime should be true. */ - public static String getDurationString(Context context, long startUtcMillis, long endUtcMillis, - boolean useShortFormat, boolean showDate, boolean showTime, int flag) { - flag |= DateUtils.FORMAT_ABBREV_MONTH - | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0); + public static String getDurationString( + Context context, + long startUtcMillis, + long endUtcMillis, + boolean useShortFormat, + boolean showDate, + boolean showTime, + int flag) { + flag |= + DateUtils.FORMAT_ABBREV_MONTH + | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0); SoftPreconditions.checkArgument(showTime || showDate); if (showTime) { flag |= DateUtils.FORMAT_SHOW_TIME; @@ -431,20 +432,21 @@ public class Utils { // Do not show date for short format. // Subtracting one day is needed because {@link DateUtils@formatDateRange} // automatically shows date if the duration covers multiple days. - return DateUtils.formatDateRange(context, - startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag); + return DateUtils.formatDateRange( + context, startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag); } } // Workaround of b/28740989. // Add 1 msec to endUtcMillis to avoid DateUtils' bug with a duration of 12:00AM~12:00AM. String dateRange = DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis, flag); - return startUtcMillis == endUtcMillis || dateRange.contains("–") ? dateRange + return startUtcMillis == endUtcMillis || dateRange.contains("–") + ? dateRange : DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis + 1, flag); } /** - * Checks if two given time (in milliseconds) are in the same day with regard to the - * locale timezone. + * Checks if two given time (in milliseconds) are in the same day with regard to the locale + * timezone. */ public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) { TimeZone timeZone = Calendar.getInstance().getTimeZone(); @@ -456,9 +458,7 @@ public class Utils { == Utils.floorTime(subjectTimeInMillis + offset, ONE_DAY_MS); } - /** - * Calculate how many days between two milliseconds. - */ + /** Calculate how many days between two milliseconds. */ public static int computeDateDifference(long startTimeMs, long endTimeMs) { Calendar calFrom = Calendar.getInstance(); Calendar calTo = Calendar.getInstance(); @@ -476,9 +476,7 @@ public class Utils { cal.set(Calendar.MILLISECOND, 0); } - /** - * Returns the last millisecond of a day which the millis belongs to. - */ + /** 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)); @@ -494,7 +492,7 @@ public class Utils { return ""; } - for (AspectRatio ratio: AspectRatio.values()) { + for (AspectRatio ratio : AspectRatio.values()) { if (Math.abs((float) ratio.height / ratio.width - (float) height / width) < 0.05f) { return ratio.toString(); } @@ -531,11 +529,9 @@ public class Utils { public static String getVideoDefinitionLevelString(Context context, int videoFormat) { switch (videoFormat) { case StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD: - return context.getResources().getString( - R.string.video_definition_level_ultra_hd); + return context.getResources().getString(R.string.video_definition_level_ultra_hd); case StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD: - return context.getResources().getString( - R.string.video_definition_level_full_hd); + return context.getResources().getString(R.string.video_definition_level_full_hd); case StreamInfo.VIDEO_DEFINITION_LEVEL_HD: return context.getResources().getString(R.string.video_definition_level_hd); case StreamInfo.VIDEO_DEFINITION_LEVEL_SD: @@ -570,8 +566,8 @@ public class Utils { return false; } - public static String getMultiAudioString(Context context, TvTrackInfo track, - boolean showSampleRate) { + public static String getMultiAudioString( + Context context, TvTrackInfo track, boolean showSampleRate) { if (track.getType() != TvTrackInfo.TYPE_AUDIO) { throw new IllegalArgumentException("Not an audio track: " + track); } @@ -600,11 +596,17 @@ public class Utils { break; default: if (track.getAudioChannelCount() > 0) { - metadata.append(context.getString(R.string.multi_audio_channel_suffix, - track.getAudioChannelCount())); + metadata.append( + context.getString( + R.string.multi_audio_channel_suffix, + track.getAudioChannelCount())); } else { - Log.d(TAG, "Invalid audio channel count (" + track.getAudioChannelCount() - + ") found for the audio track: " + track); + Log.d( + TAG, + "Invalid audio channel count (" + + track.getAudioChannelCount() + + ") found for the audio track: " + + track); } break; } @@ -628,8 +630,8 @@ public class Utils { if (metadata.length() == 0) { return language; } - return context.getString(R.string.multi_audio_display_string_with_channel, language, - metadata.toString()); + return context.getString( + R.string.multi_audio_display_string_with_channel, language, metadata.toString()); } public static boolean isEqualLanguage(String lang1, String lang2) { @@ -647,13 +649,13 @@ public class Utils { } public static boolean isIntentAvailable(Context context, Intent intent) { - return context.getPackageManager().queryIntentActivities( - intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; + return context.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + .size() + > 0; } - /** - * Returns the label for a given input. Returns the custom label, if any. - */ + /** Returns the label for a given input. Returns the custom label, if any. */ public static String loadLabel(Context context, TvInputInfo input) { if (input == null) { return null; @@ -668,9 +670,7 @@ public class Utils { return label; } - /** - * Enable all channels synchronously. - */ + /** Enable all channels synchronously. */ @WorkerThread public static void enableAllChannels(Context context) { ContentValues values = new ContentValues(); @@ -681,31 +681,30 @@ public class Utils { /** * Converts time in milliseconds to a String. * - * @param fullFormat {@code true} for returning date string with a full format - * (e.g., Mon Aug 15 20:08:35 GMT 2016). {@code false} for a short format, - * {e.g., [8/15/16] 8:08 AM}, in which date information would only appears - * when the target time is not today. + * @param fullFormat {@code true} for returning date string with a full format (e.g., Mon Aug 15 + * 20:08:35 GMT 2016). {@code false} for a short format, {e.g., [8/15/16] 8:08 AM}, in which + * date information would only appears when the target time is not today. */ public static String toTimeString(long timeMillis, boolean fullFormat) { if (fullFormat) { return new Date(timeMillis).toString(); } else { long currentTime = System.currentTimeMillis(); - return (String) DateUtils.formatSameDayTime(timeMillis, System.currentTimeMillis(), - SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); + return (String) + DateUtils.formatSameDayTime( + timeMillis, + System.currentTimeMillis(), + SimpleDateFormat.SHORT, + SimpleDateFormat.SHORT); } } - /** - * Converts time in milliseconds to a String. - */ + /** Converts time in milliseconds to a String. */ public static String toTimeString(long timeMillis) { return toTimeString(timeMillis, true); } - /** - * Converts time in milliseconds to a ISO 8061 string. - */ + /** Converts time in milliseconds to a ISO 8061 string. */ public static String toIsoDateTimeString(long timeMillis) { return ISO_8601.format(new Date(timeMillis)); } @@ -715,12 +714,19 @@ public class Utils { */ public static String toRectString(View view) { return "{" - + "l=" + view.getLeft() - + ",r=" + view.getRight() - + ",t=" + view.getTop() - + ",b=" + view.getBottom() - + ",w=" + view.getWidth() - + ",h=" + view.getHeight() + "}"; + + "l=" + + view.getLeft() + + ",r=" + + view.getRight() + + ",t=" + + view.getTop() + + ",b=" + + view.getBottom() + + ",w=" + + view.getWidth() + + ",h=" + + view.getHeight() + + "}"; } /** @@ -732,16 +738,14 @@ public class Utils { } /** - * Ceils time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is - * one hour (60 * 60 * 1000), then the output will be 6:00:00. + * Ceils time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is one + * hour (60 * 60 * 1000), then the output will be 6:00:00. */ public static long ceilTime(long timeMs, long timeUnit) { return timeMs + timeUnit - (timeMs % timeUnit); } - /** - * Returns an {@link String#intern() interned} string or null if the input is null. - */ + /** Returns an {@link String#intern() interned} string or null if the input is null. */ @Nullable public static String intern(@Nullable String string) { return string == null ? null : string.intern(); @@ -749,6 +753,7 @@ public class Utils { /** * Check if the index is valid for the collection, + * * @param collection the collection * @param index the index position to test * @return index >= 0 && index < collection.size(). @@ -757,9 +762,7 @@ public class Utils { return collection != null && (index >= 0 && index < collection.size()); } - /** - * Returns a localized version of the text resource specified by resourceId. - */ + /** Returns a localized version of the text resource specified by resourceId. */ public static CharSequence getTextForLocale(Context context, Locale locale, int resourceId) { if (locale.equals(context.getResources().getConfiguration().locale)) { return context.getText(resourceId); @@ -769,12 +772,12 @@ public class Utils { return context.createConfigurationContext(config).getText(resourceId); } - /** - * Checks where there is any internal TV input. - */ + /** Checks where there is any internal TV input. */ public static boolean hasInternalTvInputs(Context context, boolean tunerInputOnly) { - for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper() - .getTvInputInfos(true, tunerInputOnly)) { + for (TvInputInfo input : + TvApplication.getSingletons(context) + .getTvInputManagerHelper() + .getTvInputInfos(true, tunerInputOnly)) { if (isInternalTvInput(context, input.getId())) { return true; } @@ -782,13 +785,13 @@ public class Utils { return false; } - /** - * Returns the internal TV inputs. - */ + /** Returns the internal TV inputs. */ public static List getInternalTvInputs(Context context, boolean tunerInputOnly) { List inputs = new ArrayList<>(); - for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper() - .getTvInputInfos(true, tunerInputOnly)) { + for (TvInputInfo input : + TvApplication.getSingletons(context) + .getTvInputManagerHelper() + .getTvInputInfos(true, tunerInputOnly)) { if (isInternalTvInput(context, input.getId())) { inputs.add(input); } @@ -796,17 +799,13 @@ public class Utils { return inputs; } - /** - * Checks whether the input is internal or not. - */ + /** Checks whether the input is internal or not. */ public static boolean isInternalTvInput(Context context, String inputId) { - return context.getPackageName().equals(ComponentName.unflattenFromString(inputId) - .getPackageName()); + return context.getPackageName() + .equals(ComponentName.unflattenFromString(inputId).getPackageName()); } - /** - * Returns the TV input for the given {@code program}. - */ + /** Returns the TV input for the given {@code program}. */ @Nullable public static TvInputInfo getTvInputInfoForProgram(Context context, Program program) { if (!Program.isValid(program)) { @@ -815,9 +814,7 @@ public class Utils { return getTvInputInfoForChannelId(context, program.getChannelId()); } - /** - * Returns the TV input for the given channel ID. - */ + /** Returns the TV input for the given channel ID. */ @Nullable public static TvInputInfo getTvInputInfoForChannelId(Context context, long channelId) { ApplicationSingletons appSingletons = TvApplication.getSingletons(context); @@ -828,18 +825,15 @@ public class Utils { return appSingletons.getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); } - /** - * Returns the {@link TvInputInfo} for the given input ID. - */ + /** Returns the {@link TvInputInfo} for the given input ID. */ @Nullable public static TvInputInfo getTvInputInfoForInputId(Context context, String inputId) { - return TvApplication.getSingletons(context).getTvInputManagerHelper() + return TvApplication.getSingletons(context) + .getTvInputManagerHelper() .getTvInputInfo(inputId); } - /** - * Deletes a file or a directory. - */ + /** Deletes a file or a directory. */ public static void deleteDirOrFile(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) { for (File child : fileOrDirectory.listFiles()) { @@ -849,16 +843,12 @@ public class Utils { fileOrDirectory.delete(); } - /** - * Checks whether a given package is in our bundled package set. - */ + /** Checks whether a given package is in our bundled package set. */ public static boolean isInBundledPackageSet(String packageName) { return BUNDLED_PACKAGE_SET.contains(packageName); } - /** - * Checks whether a given input is a bundled input. - */ + /** Checks whether a given input is a bundled input. */ public static boolean isBundledInput(String inputId) { for (String prefix : BUNDLED_PACKAGE_SET) { if (inputId.startsWith(prefix + "/")) { @@ -868,9 +858,7 @@ public class Utils { return false; } - /** - * Returns the canonical genre ID's from the {@code genres}. - */ + /** Returns the canonical genre ID's from the {@code genres}. */ public static int[] getCanonicalGenreIds(String genres) { if (TextUtils.isEmpty(genres)) { return null; @@ -878,9 +866,7 @@ public class Utils { return getCanonicalGenreIds(Genres.decode(genres)); } - /** - * Returns the canonical genre ID's from the {@code genres}. - */ + /** Returns the canonical genre ID's from the {@code genres}. */ public static int[] getCanonicalGenreIds(String[] canonicalGenres) { if (canonicalGenres != null && canonicalGenres.length > 0) { int[] results = new int[canonicalGenres.length]; @@ -901,9 +887,7 @@ public class Utils { return null; } - /** - * Returns the canonical genres for database. - */ + /** Returns the canonical genres for database. */ public static String getCanonicalGenre(int[] canonicalGenreIds) { if (canonicalGenreIds == null || canonicalGenreIds.length == 0) { return null; @@ -915,16 +899,14 @@ public class Utils { return Genres.encode(genres); } - /** - * Returns true if the current user is a developer. - */ + /** Returns true if the current user is a developer. */ public static boolean isDeveloper() { return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get(); } /** - * Runs the method in main thread. If the current thread is not main thread, block it util - * the method is finished. + * Runs the method in main thread. If the current thread is not main thread, block it util the + * method is finished. */ public static void runInMainThreadAndWait(Runnable runnable) { if (Looper.myLooper() == Looper.getMainLooper()) { diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java index ed9a8ff6..2d5ecfe6 100644 --- a/src/com/android/tv/util/ViewCache.java +++ b/src/com/android/tv/util/ViewCache.java @@ -5,22 +5,17 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import java.util.ArrayList; -/** - * A cache for the views. - */ +/** A cache for the views. */ public class ViewCache { - private final static SparseArray> mViews = new SparseArray(); + private static final SparseArray> mViews = new SparseArray(); private static ViewCache sViewCache; - private ViewCache() { } + private ViewCache() {} - /** - * Returns an instance of the view cache. - */ + /** Returns an instance of the view cache. */ public static ViewCache getInstance() { if (sViewCache == null) { sViewCache = new ViewCache(); @@ -28,16 +23,12 @@ public class ViewCache { return sViewCache; } - /** - * Returns if the view cache is empty. - */ + /** Returns if the view cache is empty. */ public boolean isEmpty() { return mViews.size() == 0; } - /** - * Stores a view into this view cache. - */ + /** Stores a view into this view cache. */ public void putView(int resId, View view) { ArrayList views = mViews.get(resId); if (views == null) { @@ -47,9 +38,7 @@ public class ViewCache { views.add(view); } - /** - * Stores multi specific views into the view cache. - */ + /** Stores multi specific views into the view cache. */ public void putView(Context context, int resId, ViewGroup fakeParent, int num) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -64,9 +53,7 @@ public class ViewCache { } } - /** - * Returns the view for specific resource id. - */ + /** Returns the view for specific resource id. */ public View getView(int resId) { ArrayList views = mViews.get(resId); if (views != null && !views.isEmpty()) { @@ -80,9 +67,7 @@ public class ViewCache { } } - /** - * Returns the view if exists, or create a new view for the specific resource id. - */ + /** Returns the view if exists, or create a new view for the specific resource id. */ public View getOrCreateView(LayoutInflater inflater, int resId, ViewGroup container) { View view = getView(resId); if (view == null) { @@ -91,9 +76,7 @@ public class ViewCache { return view; } - /** - * Clears the view cache. - */ + /** Clears the view cache. */ public void clear() { mViews.clear(); } diff --git a/tests/common/src/com/android/tv/input/TunerHelper.java b/tests/common/src/com/android/tv/input/TunerHelper.java index 126d5027..08c4b041 100644 --- a/tests/common/src/com/android/tv/input/TunerHelper.java +++ b/tests/common/src/com/android/tv/input/TunerHelper.java @@ -19,14 +19,11 @@ package com.android.tv.input; import android.net.Uri; import android.support.annotation.Nullable; import android.util.Log; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; -/** - * A class to manage fake tuners for the tune and the recording. - */ +/** A class to manage fake tuners for the tune and the recording. */ public class TunerHelper { private static final String TAG = "TunerHelper"; private static final boolean DEBUG = false; @@ -38,9 +35,7 @@ public class TunerHelper { mTunerCount = tunerCount; } - /** - * Checks whether there are available tuners for the recording. - */ + /** Checks whether there are available tuners for the recording. */ public boolean tunerAvailableForRecording() { if (mTuners.size() < mTunerCount) { return true; @@ -54,8 +49,8 @@ public class TunerHelper { } /** - * Checks whether there is available tuner. - * If there's available tuner, it is assigned to the channel. + * Checks whether there is available tuner. If there's available tuner, it is assigned to the + * channel. */ public boolean tune(@Nullable Uri channelUri, boolean forRecording) { if (channelUri == null) { @@ -82,9 +77,7 @@ public class TunerHelper { return false; } - /** - * Releases the tuner which was being used for the tune. - */ + /** Releases the tuner which was being used for the tune. */ public void stopTune(@Nullable Uri channelUri) { if (channelUri == null) { return; @@ -110,9 +103,7 @@ public class TunerHelper { } } - /** - * Releases the tuner which was being used for the recording. - */ + /** Releases the tuner which was being used for the recording. */ public void stopRecording(@Nullable Uri channelUri) { if (channelUri == null) { return; @@ -146,7 +137,7 @@ public class TunerHelper { public boolean tuning; public boolean recording; - public Tuner (Uri channelUri, boolean forRecording) { + public Tuner(Uri channelUri, boolean forRecording) { this.channelUri = channelUri; this.tuning = !forRecording; this.recording = forRecording; diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/ChannelInfo.java index 946c0b55..b28ac9fb 100644 --- a/tests/common/src/com/android/tv/testing/ChannelInfo.java +++ b/tests/common/src/com/android/tv/testing/ChannelInfo.java @@ -23,14 +23,12 @@ import android.media.tv.TvContract; import android.net.Uri; import android.support.annotation.Nullable; import android.util.SparseArray; - import java.util.Objects; -/** - * Channel Information. - */ +/** Channel Information. */ public final class ChannelInfo { private static final SparseArray VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>(); + static { VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P); @@ -41,9 +39,9 @@ public final class ChannelInfo { } public static final String[] PROJECTION = { - TvContract.Channels.COLUMN_DISPLAY_NUMBER, - TvContract.Channels.COLUMN_DISPLAY_NAME, - TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, + TvContract.Channels.COLUMN_DISPLAY_NUMBER, + TvContract.Channels.COLUMN_DISPLAY_NAME, + TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, }; public final String number; @@ -67,14 +65,15 @@ public final class ChannelInfo { * Create a channel info for TVTestInput. * * @param context a context to insert logo. It can be null if logo isn't needed. - * @param channelNumber a channel number to be use as an identifier. - * {@link #originalNetworkId} will be assigned the same value, too. + * @param channelNumber a channel number to be use as an identifier. {@link #originalNetworkId} + * will be assigned the same value, too. */ public static ChannelInfo create(@Nullable Context context, int channelNumber) { - Builder builder = new Builder() - .setNumber(String.valueOf(channelNumber)) - .setName("Channel " + channelNumber) - .setOriginalNetworkId(channelNumber); + Builder builder = + new Builder() + .setNumber(String.valueOf(channelNumber)) + .setName("Channel " + channelNumber) + .setOriginalNetworkId(channelNumber); if (context != null) { // tests/input/tools/get_test_logos.sh only stores 1000 logos. builder.setLogoUrl(getUriStringForChannelLogo(context, channelNumber)); @@ -88,7 +87,9 @@ public final class ChannelInfo { .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(context.getPackageName()) .path("drawable") - .appendPath("ch_" + index + "_logo").build().toString(); + .appendPath("ch_" + index + "_logo") + .build() + .toString(); } public static ChannelInfo fromCursor(Cursor c) { @@ -109,10 +110,22 @@ public final class ChannelInfo { return builder.build(); } - private ChannelInfo(String number, String name, String logoUrl, int originalNetworkId, - int videoWidth, int videoHeight, float videoPixelAspectRatio, int audioChannel, - int audioLanguageCount, boolean hasClosedCaption, ProgramInfo program, - String appLinkText, int appLinkColor, String appLinkIconUri, String appLinkPosterArtUri, + private ChannelInfo( + String number, + String name, + String logoUrl, + int originalNetworkId, + int videoWidth, + int videoHeight, + float videoPixelAspectRatio, + int audioChannel, + int audioLanguageCount, + boolean hasClosedCaption, + ProgramInfo program, + String appLinkText, + int appLinkColor, + String appLinkIconUri, + String appLinkPosterArtUri, String appLinkIntentUri) { this.number = number; this.name = name; @@ -139,20 +152,35 @@ public final class ChannelInfo { @Override public String toString() { return "Channel{" - + "number=" + number - + ", name=" + name - + ", logoUri=" + logoUrl - + ", originalNetworkId=" + originalNetworkId - + ", videoWidth=" + videoWidth - + ", videoHeight=" + videoHeight - + ", audioChannel=" + audioChannel - + ", audioLanguageCount=" + audioLanguageCount - + ", hasClosedCaption=" + hasClosedCaption - + ", appLinkText=" + appLinkText - + ", appLinkColor=" + appLinkColor - + ", appLinkIconUri=" + appLinkIconUri - + ", appLinkPosterArtUri=" + appLinkPosterArtUri - + ", appLinkIntentUri=" + appLinkIntentUri + "}"; + + "number=" + + number + + ", name=" + + name + + ", logoUri=" + + logoUrl + + ", originalNetworkId=" + + originalNetworkId + + ", videoWidth=" + + videoWidth + + ", videoHeight=" + + videoHeight + + ", audioChannel=" + + audioChannel + + ", audioLanguageCount=" + + audioLanguageCount + + ", hasClosedCaption=" + + hasClosedCaption + + ", appLinkText=" + + appLinkText + + ", appLinkColor=" + + appLinkColor + + ", appLinkIconUri=" + + appLinkIconUri + + ", appLinkPosterArtUri=" + + appLinkPosterArtUri + + ", appLinkIntentUri=" + + appLinkIntentUri + + "}"; } @Override @@ -164,21 +192,21 @@ public final class ChannelInfo { return false; } ChannelInfo that = (ChannelInfo) o; - return Objects.equals(originalNetworkId, that.originalNetworkId) && - Objects.equals(videoWidth, that.videoWidth) && - Objects.equals(videoHeight, that.videoHeight) && - Objects.equals(audioChannel, that.audioChannel) && - Objects.equals(audioLanguageCount, that.audioLanguageCount) && - Objects.equals(hasClosedCaption, that.hasClosedCaption) && - Objects.equals(appLinkColor, that.appLinkColor) && - Objects.equals(number, that.number) && - Objects.equals(name, that.name) && - Objects.equals(logoUrl, that.logoUrl) && - Objects.equals(program, that.program) && - Objects.equals(appLinkText, that.appLinkText) && - Objects.equals(appLinkIconUri, that.appLinkIconUri) && - Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri) && - Objects.equals(appLinkIntentUri, that.appLinkIntentUri); + return Objects.equals(originalNetworkId, that.originalNetworkId) + && Objects.equals(videoWidth, that.videoWidth) + && Objects.equals(videoHeight, that.videoHeight) + && Objects.equals(audioChannel, that.audioChannel) + && Objects.equals(audioLanguageCount, that.audioLanguageCount) + && Objects.equals(hasClosedCaption, that.hasClosedCaption) + && Objects.equals(appLinkColor, that.appLinkColor) + && Objects.equals(number, that.number) + && Objects.equals(name, that.name) + && Objects.equals(logoUrl, that.logoUrl) + && Objects.equals(program, that.program) + && Objects.equals(appLinkText, that.appLinkText) + && Objects.equals(appLinkIconUri, that.appLinkIconUri) + && Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri) + && Objects.equals(appLinkIntentUri, that.appLinkIntentUri); } @Override @@ -186,17 +214,15 @@ public final class ChannelInfo { return Objects.hash(number, name, originalNetworkId); } - /** - * Builder class for {@code ChannelInfo}. - */ + /** Builder class for {@code ChannelInfo}. */ public static class Builder { private String mNumber; private String mName; private String mLogoUrl = null; private int mOriginalNetworkId; - private int mVideoWidth = 1920; // Width for HD video. - private int mVideoHeight = 1080; // Height for HD video. - private float mVideoPixelAspectRatio = 1.0f; //default value + private int mVideoWidth = 1920; // Width for HD video. + private int mVideoHeight = 1080; // Height for HD video. + private float mVideoPixelAspectRatio = 1.0f; // default value private int mAudioChannel; private int mAudioLanguageCount; private boolean mHasClosedCaption; @@ -207,8 +233,7 @@ public final class ChannelInfo { private String mAppLinkPosterArtUri; private String mAppLinkIntentUri; - public Builder() { - } + public Builder() {} public Builder(ChannelInfo other) { mNumber = other.number; @@ -305,11 +330,23 @@ public final class ChannelInfo { } public ChannelInfo build() { - return new ChannelInfo(mNumber, mName, mLogoUrl, mOriginalNetworkId, - mVideoWidth, mVideoHeight, mVideoPixelAspectRatio, mAudioChannel, - mAudioLanguageCount, mHasClosedCaption, mProgram, mAppLinkText, mAppLinkColor, - mAppLinkIconUri, mAppLinkPosterArtUri, mAppLinkIntentUri); - + return new ChannelInfo( + mNumber, + mName, + mLogoUrl, + mOriginalNetworkId, + mVideoWidth, + mVideoHeight, + mVideoPixelAspectRatio, + mAudioChannel, + mAudioLanguageCount, + mHasClosedCaption, + mProgram, + mAppLinkText, + mAppLinkColor, + mAppLinkIconUri, + mAppLinkPosterArtUri, + mAppLinkIntentUri); } } } diff --git a/tests/common/src/com/android/tv/testing/ChannelUtils.java b/tests/common/src/com/android/tv/testing/ChannelUtils.java index bfb766d6..b9c48f37 100644 --- a/tests/common/src/com/android/tv/testing/ChannelUtils.java +++ b/tests/common/src/com/android/tv/testing/ChannelUtils.java @@ -27,7 +27,6 @@ import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -35,16 +34,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * Static helper methods for working with {@link android.media.tv.TvContract}. - */ +/** Static helper methods for working with {@link android.media.tv.TvContract}. */ public class ChannelUtils { private static final String TAG = "ChannelUtils"; private static final boolean DEBUG = false; /** - * Query and return the map of (channel_id, ChannelInfo). - * See: {@link ChannelInfo#fromCursor(Cursor)}. + * Query and return the map of (channel_id, ChannelInfo). See: {@link + * ChannelInfo#fromCursor(Cursor)}. */ @WorkerThread public static Map queryChannelInfoMapForTvInput( @@ -55,8 +52,8 @@ public class ChannelUtils { String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; projections[0] = Channels._ID; System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); - try (Cursor cursor = context.getContentResolver() - .query(uri, projections, null, null, null)) { + try (Cursor cursor = + context.getContentResolver().query(uri, projections, null, null, null)) { if (cursor != null) { while (cursor.moveToNext()) { map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); @@ -113,10 +110,10 @@ public class ChannelUtils { Long rowId = existingChannelsMap.get(channel.originalNetworkId); Uri uri; if (rowId == null) { - if (DEBUG) Log.d(TAG, "Inserting "+ channel); + if (DEBUG) Log.d(TAG, "Inserting " + channel); uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); } else { - if (DEBUG) Log.d(TAG, "Updating "+ channel); + if (DEBUG) Log.d(TAG, "Updating " + channel); uri = TvContract.buildChannelUri(rowId); resolver.update(uri, values, null, null); existingChannelsMap.remove(channel.originalNetworkId); diff --git a/tests/common/src/com/android/tv/testing/ComparableTester.java b/tests/common/src/com/android/tv/testing/ComparableTester.java index fe6e72f5..4328deb9 100644 --- a/tests/common/src/com/android/tv/testing/ComparableTester.java +++ b/tests/common/src/com/android/tv/testing/ComparableTester.java @@ -16,22 +16,19 @@ package com.android.tv.testing; -import junit.framework.Assert; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import junit.framework.Assert; /** * Tester for {@link java.lang.Comparable}s. * - *

- * To use, create a new {@link ComparableTester} and add comparable groups - * where each group contains objects that are - * {@link java.util.Comparator#compare(Object, Object)} == 0 to each other. - * Groups are added in order asserting that all earlier groups have compare < 0 - * for all later groups. + *

To use, create a new {@link ComparableTester} and add comparable groups where each group + * contains objects that are {@link java.util.Comparator#compare(Object, Object)} == 0 to each + * other. Groups are added in order asserting that all earlier groups have compare < 0 for all later + * groups. * *

{@code
  * new ComparableTester()
@@ -39,8 +36,7 @@ import java.util.List;
  *     .addEquivalentGroup("World", "wORLD")
  *     .addEquivalentGroup("ZEBRA")
  *     .test();
- * }
- * 
+ * } * * @param the type of objects to compare. */ @@ -74,8 +70,8 @@ public class ComparableTester> { assertMore(more, less, moreGroup, lessGroup); } - private void assertLess(int left, int right, Collection leftGroup, - Collection rightGroup) { + private void assertLess( + int left, int right, Collection leftGroup, Collection rightGroup) { int leftSub = 0; for (T leftItem : leftGroup) { int rightSub = 0; @@ -83,14 +79,22 @@ public class ComparableTester> { for (T rightItem : rightGroup) { String rightName = "Item[" + right + "," + (rightSub++) + "]"; Assert.assertEquals( - leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem - + " is <0", true, leftItem.compareTo(rightItem) < 0); + leftName + + " " + + leftItem + + " compareTo " + + rightName + + " " + + rightItem + + " is <0", + true, + leftItem.compareTo(rightItem) < 0); } } } - private void assertMore(int left, int right, Collection leftGroup, - Collection rightGroup) { + private void assertMore( + int left, int right, Collection leftGroup, Collection rightGroup) { int leftSub = 0; for (T leftItem : leftGroup) { int rightSub = 0; @@ -98,8 +102,16 @@ public class ComparableTester> { for (T rightItem : rightGroup) { String rightName = "Item[" + right + "," + (rightSub++) + "]"; Assert.assertEquals( - leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem - + " is >0", true, leftItem.compareTo(rightItem) > 0); + leftName + + " " + + leftItem + + " compareTo " + + rightName + + " " + + rightItem + + " is >0", + true, + leftItem.compareTo(rightItem) > 0); } } } diff --git a/tests/common/src/com/android/tv/testing/ComparatorTester.java b/tests/common/src/com/android/tv/testing/ComparatorTester.java index 3774532f..6ebd8b4e 100644 --- a/tests/common/src/com/android/tv/testing/ComparatorTester.java +++ b/tests/common/src/com/android/tv/testing/ComparatorTester.java @@ -27,12 +27,9 @@ import java.util.List; /** * Tester for {@link Comparator} relationships between groups of T. * - *

- * To use, create a new {@link ComparatorTester} and add comparable groups - * where each group contains objects that are - * {@link Comparator#compare(Object, Object)} == 0 to each other. - * Groups are added in order asserting that all earlier groups have compare < 0 - * for all later groups. + *

To use, create a new {@link ComparatorTester} and add comparable groups where each group + * contains objects that are {@link Comparator#compare(Object, Object)} == 0 to each other. Groups + * are added in order asserting that all earlier groups have compare < 0 for all later groups. * *

{@code
  * ComparatorTester
@@ -41,8 +38,7 @@ import java.util.List;
  *     .addComparableGroup("World", "wORLD")
  *     .addComparableGroup("ZEBRA")
  *     .test();
- * }
- * 
+ * } * * @param the type of objects to compare. */ @@ -52,7 +48,6 @@ public class ComparatorTester { private final Comparator comparator; - public static ComparatorTester withoutEqualsTest(Comparator comparator) { return new ComparatorTester<>(comparator); } @@ -80,7 +75,7 @@ public class ComparatorTester { assertOrder(i, j, currentGroup, rhs); } } - //TODO: also test equals + // TODO: also test equals } private void assertOrder(int less, int more, List lessGroup, List moreGroup) { @@ -88,30 +83,48 @@ public class ComparatorTester { assertMore(more, less, moreGroup, lessGroup); } - private void assertLess(int left, int right, Collection leftGroup, - Collection rightGroup) { + private void assertLess( + int left, int right, Collection leftGroup, Collection rightGroup) { int leftSub = 0; for (T leftItem : leftGroup) { int rightSub = 0; for (T rightItem : rightGroup) { String leftName = "Item[" + left + "," + (leftSub++) + "]"; String rName = "Item[" + right + "," + (rightSub++) + "]"; - assertEquals(leftName + " " + leftItem + " compareTo " + rName + " " + rightItem - + " is <0", true, comparator.compare(leftItem, rightItem) < 0); + assertEquals( + leftName + + " " + + leftItem + + " compareTo " + + rName + + " " + + rightItem + + " is <0", + true, + comparator.compare(leftItem, rightItem) < 0); } } } - private void assertMore(int left, int right, Collection leftGroup, - Collection rightGroup) { + private void assertMore( + int left, int right, Collection leftGroup, Collection rightGroup) { int leftSub = 0; for (T leftItem : leftGroup) { int rightSub = 0; for (T rightItem : rightGroup) { String leftName = "Item[" + left + "," + (leftSub++) + "]"; String rName = "Item[" + right + "," + (rightSub++) + "]"; - assertEquals(leftName + " " + leftItem + " compareTo " + rName + " " + rightItem - + " is >0", true, comparator.compare(leftItem, rightItem) > 0); + assertEquals( + leftName + + " " + + leftItem + + " compareTo " + + rName + + " " + + rightItem + + " is >0", + true, + comparator.compare(leftItem, rightItem) > 0); } } } @@ -120,9 +133,11 @@ public class ComparatorTester { // Test everything against everything in both directions, including against itself. for (T leftItem : group) { for (T rightItem : group) { - assertEquals(leftItem + " compareTo " + rightItem, 0, + assertEquals( + leftItem + " compareTo " + rightItem, + 0, comparator.compare(leftItem, rightItem)); } } } -} \ No newline at end of file +} diff --git a/tests/common/src/com/android/tv/testing/Constants.java b/tests/common/src/com/android/tv/testing/Constants.java index 4c9cb5fb..879a88b4 100644 --- a/tests/common/src/com/android/tv/testing/Constants.java +++ b/tests/common/src/com/android/tv/testing/Constants.java @@ -17,26 +17,31 @@ package com.android.tv.testing; import android.media.tv.TvTrackInfo; -/** - * Constants for testing. - */ +/** Constants for testing. */ public final class Constants { public static final int FUNC_TEST_CHANNEL_COUNT = 100; public static final int UNIT_TEST_CHANNEL_COUNT = 4; public static final int JANK_TEST_CHANNEL_COUNT = 500; // TODO: increase to 1000 see b/23526997 - public static final TvTrackInfo EN_STEREO_AUDIO_TRACK = new TvTrackInfo.Builder( - TvTrackInfo.TYPE_AUDIO, "English Stereo Audio").setLanguage("en") - .setAudioChannelCount(2).build(); - public static final TvTrackInfo GENERIC_AUDIO_TRACK = new TvTrackInfo.Builder( - TvTrackInfo.TYPE_AUDIO, "Generic Audio").build(); + public static final TvTrackInfo EN_STEREO_AUDIO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "English Stereo Audio") + .setLanguage("en") + .setAudioChannelCount(2) + .build(); + public static final TvTrackInfo GENERIC_AUDIO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "Generic Audio").build(); - public static final TvTrackInfo FHD1080P50_VIDEO_TRACK = new TvTrackInfo.Builder( - TvTrackInfo.TYPE_VIDEO, "FHD Video").setVideoHeight(1080).setVideoWidth(1920) - .setVideoFrameRate(50).build(); - public static final TvTrackInfo SVGA_VIDEO_TRACK = new TvTrackInfo.Builder( - TvTrackInfo.TYPE_VIDEO, "SVGA Video").setVideoHeight(600).setVideoWidth(800).build(); + public static final TvTrackInfo FHD1080P50_VIDEO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "FHD Video") + .setVideoHeight(1080) + .setVideoWidth(1920) + .setVideoFrameRate(50) + .build(); + public static final TvTrackInfo SVGA_VIDEO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "SVGA Video") + .setVideoHeight(600) + .setVideoWidth(800) + .build(); - private Constants() { - } + private Constants() {} } diff --git a/tests/common/src/com/android/tv/testing/FakeClock.java b/tests/common/src/com/android/tv/testing/FakeClock.java index d88e53a8..fd6165cf 100644 --- a/tests/common/src/com/android/tv/testing/FakeClock.java +++ b/tests/common/src/com/android/tv/testing/FakeClock.java @@ -17,7 +17,6 @@ package com.android.tv.testing; import com.android.tv.util.Clock; - import java.util.concurrent.TimeUnit; /** @@ -27,22 +26,17 @@ import java.util.concurrent.TimeUnit; * {@link #sleep(long)} is called. */ public class FakeClock implements Clock { - /** - * Creates a fake clock with the time set to now and the boot time set to now - 100,000. - */ + /** Creates a fake clock with the time set to now and the boot time set to now - 100,000. */ public static FakeClock createWithCurrentTime() { long now = System.currentTimeMillis(); return new FakeClock(now, now - 100_000); } - /** - * Creates a fake clock with the time set to zero. - */ + /** Creates a fake clock with the time set to zero. */ public static FakeClock createWithTimeOne() { return new FakeClock(1L, 0L); } - private long mCurrentTimeMillis; private long mBootTimeMillis; @@ -95,9 +89,7 @@ public class FakeClock implements Clock { return mCurrentTimeMillis - mBootTimeMillis; } - /** - * Sleep does not block it just updates the current time. - */ + /** Sleep does not block it just updates the current time. */ @Override public void sleep(long ms) { // TODO: implement blocking if needed. diff --git a/tests/common/src/com/android/tv/testing/ProgramInfo.java b/tests/common/src/com/android/tv/testing/ProgramInfo.java index b1aaea6b..08742aed 100644 --- a/tests/common/src/com/android/tv/testing/ProgramInfo.java +++ b/tests/common/src/com/android/tv/testing/ProgramInfo.java @@ -20,55 +20,56 @@ import android.content.Context; import android.database.Cursor; import android.media.tv.TvContentRating; import android.media.tv.TvContract; - import java.util.Objects; import java.util.concurrent.TimeUnit; public final class ProgramInfo { - /** - * If this is specify for title, it will be generated by adding index. - */ + /** If this is specify for title, it will be generated by adding index. */ public static final String GEN_TITLE = ""; /** - * If this is specify for episode title, it will be generated by adding index. - * Also, season and episode numbers would be generated, too. - * see: {@link #build} for detail. + * If this is specify for episode title, it will be generated by adding index. Also, season and + * episode numbers would be generated, too. see: {@link #build} for detail. */ public static final String GEN_EPISODE = ""; + private static final int SEASON_MAX = 10; private static final int EPISODE_MAX = 12; /** - * If this is specify for poster art, - * it will be selected one of {@link #POSTER_ARTS_RES} in order. + * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in + * order. */ public static final String GEN_POSTER = "GEN"; + private static final int[] POSTER_ARTS_RES = { - 0, - R.drawable.blue, - R.drawable.red_large, - R.drawable.green, - R.drawable.red, - R.drawable.green_large, - R.drawable.blue_small}; + 0, + R.drawable.blue, + R.drawable.red_large, + R.drawable.green, + R.drawable.red, + R.drawable.green_large, + R.drawable.blue_small + }; /** - * If this is specified for duration, - * it will be selected one of {@link #DURATIONS_MS} in order. + * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order. */ public static final int GEN_DURATION = -1; + private static final long[] DURATIONS_MS = { - TimeUnit.MINUTES.toMillis(15), - TimeUnit.MINUTES.toMillis(45), - TimeUnit.MINUTES.toMillis(90), - TimeUnit.MINUTES.toMillis(60), - TimeUnit.MINUTES.toMillis(30), - TimeUnit.MINUTES.toMillis(45), - TimeUnit.MINUTES.toMillis(60), - TimeUnit.MINUTES.toMillis(90), - TimeUnit.HOURS.toMillis(5)}; + TimeUnit.MINUTES.toMillis(15), + TimeUnit.MINUTES.toMillis(45), + TimeUnit.MINUTES.toMillis(90), + TimeUnit.MINUTES.toMillis(60), + TimeUnit.MINUTES.toMillis(30), + TimeUnit.MINUTES.toMillis(45), + TimeUnit.MINUTES.toMillis(60), + TimeUnit.MINUTES.toMillis(90), + TimeUnit.HOURS.toMillis(5) + }; private static long DURATIONS_SUM_MS; + static { DURATIONS_SUM_MS = 0; for (long duration : DURATIONS_MS) { @@ -76,18 +77,17 @@ public final class ProgramInfo { } } - /** - * If this is specified for genre, - * it will be selected one of {@link #GENRES} in order. - */ + /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */ public static final String GEN_GENRE = "GEN"; + private static final String[] GENRES = { - "", - TvContract.Programs.Genres.SPORTS, - TvContract.Programs.Genres.NEWS, - TvContract.Programs.Genres.SHOPPING, - TvContract.Programs.Genres.DRAMA, - TvContract.Programs.Genres.ENTERTAINMENT}; + "", + TvContract.Programs.Genres.SPORTS, + TvContract.Programs.Genres.NEWS, + TvContract.Programs.Genres.SHOPPING, + TvContract.Programs.Genres.DRAMA, + TvContract.Programs.Genres.ENTERTAINMENT + }; public final String title; public final String episode; @@ -118,9 +118,17 @@ public final class ProgramInfo { return builder.build(); } - public ProgramInfo(String title, String episode, int seasonNumber, int episodeNumber, - String posterArtUri, String description, long durationMs, - TvContentRating[] contentRatings, String genre, String resourceUri) { + public ProgramInfo( + String title, + String episode, + int seasonNumber, + int episodeNumber, + String posterArtUri, + String description, + long durationMs, + TvContentRating[] contentRatings, + String genre, + String resourceUri) { this.title = title; this.episode = episode; this.seasonNumber = seasonNumber; @@ -141,8 +149,9 @@ public final class ProgramInfo { } /** - * Get index of the program whose start time equals or less than {@code timeMs} and - * end time more than {@code timeMs}. + * Get index of the program whose start time equals or less than {@code timeMs} and end time + * more than {@code timeMs}. + * * @param timeMs target time in millis to find a program. * @param channelId used to add complexity to the index between two consequence channels. */ @@ -162,14 +171,16 @@ public final class ProgramInfo { /** * Returns the start time for the program with the position. + * * @param index index returned by {@link #getIndex} */ public long getStartTimeMs(int index, long channelId) { if (durationMs != GEN_DURATION) { return index * durationMs; } - long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))] - + (index / DURATIONS_MS.length) * DURATIONS_SUM_MS; + long startTimeMs = + channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))] + + (index / DURATIONS_MS.length) * DURATIONS_SUM_MS; for (int i = 0; i < index % DURATIONS_MS.length; i++) { startTimeMs += DURATIONS_MS[i]; } @@ -177,9 +188,9 @@ public final class ProgramInfo { } /** - * Return complete {@link ProgramInfo} with the generated value. - * See: {@link #GEN_TITLE}, {@link #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, - * {@link #GEN_GENRE}. + * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link + * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}. + * * @param index index returned by {@link #getIndex} */ public ProgramInfo build(Context context, int index) { @@ -196,8 +207,8 @@ public final class ProgramInfo { episode != null ? (index % SEASON_MAX + 1) : seasonNumber, episode != null ? (index % EPISODE_MAX + 1) : episodeNumber, GEN_POSTER.equals(posterArtUri) - ? Utils.getUriStringForResource(context, - POSTER_ARTS_RES[index % POSTER_ARTS_RES.length]) + ? Utils.getUriStringForResource( + context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length]) : posterArtUri, description, durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs, @@ -208,9 +219,13 @@ public final class ProgramInfo { @Override public String toString() { - return "ProgramInfo{title=" + title - + ", episode=" + episode - + ", durationMs=" + durationMs + "}"; + return "ProgramInfo{title=" + + title + + ", episode=" + + episode + + ", durationMs=" + + durationMs + + "}"; } @Override @@ -222,16 +237,16 @@ public final class ProgramInfo { return false; } ProgramInfo that = (ProgramInfo) o; - return Objects.equals(seasonNumber, that.seasonNumber) && - Objects.equals(episodeNumber, that.episodeNumber) && - Objects.equals(durationMs, that.durationMs) && - Objects.equals(title, that.title) && - Objects.equals(episode, that.episode) && - Objects.equals(posterArtUri, that.posterArtUri) && - Objects.equals(description, that.description) && - Objects.equals(genre, that.genre) && - Objects.equals(contentRatings, that.contentRatings) && - Objects.equals(resourceUri, that.resourceUri); + return Objects.equals(seasonNumber, that.seasonNumber) + && Objects.equals(episodeNumber, that.episodeNumber) + && Objects.equals(durationMs, that.durationMs) + && Objects.equals(title, that.title) + && Objects.equals(episode, that.episode) + && Objects.equals(posterArtUri, that.posterArtUri) + && Objects.equals(description, that.description) + && Objects.equals(genre, that.genre) + && Objects.equals(contentRatings, that.contentRatings) + && Objects.equals(resourceUri, that.resourceUri); } @Override @@ -302,8 +317,17 @@ public final class ProgramInfo { } public ProgramInfo build() { - return new ProgramInfo(mTitle, mEpisode, mSeasonNumber, mEpisodeNumber, mPosterArtUri, - mDescription, mDurationMs, mContentRatings, mGenre, mResourceUri); + return new ProgramInfo( + mTitle, + mEpisode, + mSeasonNumber, + mEpisodeNumber, + mPosterArtUri, + mDescription, + mDurationMs, + mContentRatings, + mGenre, + mResourceUri); } } } diff --git a/tests/common/src/com/android/tv/testing/ProgramUtils.java b/tests/common/src/com/android/tv/testing/ProgramUtils.java index 08c6a033..3be9d10c 100644 --- a/tests/common/src/com/android/tv/testing/ProgramUtils.java +++ b/tests/common/src/com/android/tv/testing/ProgramUtils.java @@ -25,9 +25,7 @@ import android.media.tv.TvContract; import android.media.tv.TvContract.Programs; import android.net.Uri; import android.util.Log; - import com.android.tv.common.TvContentRatingCache; - import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -40,9 +38,8 @@ public class ProgramUtils { private static final int MAX_DB_INSERT_COUNT_AT_ONCE = 500; /** - * Populate programs by repeating given program information. - * This method will populate programs without any gap nor overlapping - * starting from the current time. + * Populate programs by repeating given program information. This method will populate programs + * without any gap nor overlapping starting from the current time. */ public static void populatePrograms(Context context, Uri channelUri, ProgramInfo program) { ContentValues values = new ContentValues(); @@ -50,7 +47,8 @@ public class ProgramUtils { values.put(Programs.COLUMN_CHANNEL_ID, channelId); values.put(Programs.COLUMN_SHORT_DESCRIPTION, program.description); - values.put(Programs.COLUMN_CONTENT_RATING, + values.put( + Programs.COLUMN_CONTENT_RATING, TvContentRatingCache.contentRatingsToString(program.contentRatings)); long currentTimeMs = System.currentTimeMillis(); @@ -81,11 +79,12 @@ public class ProgramUtils { list.add(new ContentValues(values)); timeMs += programAt.durationMs; - if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE - || timeMs >= targetEndTimeMs) { + if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) { try { - context.getContentResolver().bulkInsert(Programs.CONTENT_URI, - list.toArray(new ContentValues[list.size()])); + context.getContentResolver() + .bulkInsert( + Programs.CONTENT_URI, + list.toArray(new ContentValues[list.size()])); } catch (SQLiteException e) { Log.e(TAG, "Can't insert EPG.", e); return; diff --git a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java b/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java index c4c96fed..30382209 100644 --- a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java +++ b/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java @@ -18,16 +18,17 @@ package com.android.tv.testing; import android.media.tv.TvContentRating; -/** - * Constants for the content rating strings. - */ +/** Constants for the content rating strings. */ public final class TvContentRatingConstants { /** * A content rating object. * *

Domain: com.android.tv + * *

Rating system: US_TV + * *

Rating: US_TV_Y7 + * *

Sub ratings: US_TV_FV */ public static final TvContentRating CONTENT_RATING_US_TV_Y7_US_TV_FV = @@ -39,7 +40,9 @@ public final class TvContentRatingConstants { * A content rating object. * *

Domain: com.android.tv + * *

Rating system: US_TV + * *

Rating: US_TV_MA */ public static final TvContentRating CONTENT_RATING_US_TV_MA = @@ -51,11 +54,14 @@ public final class TvContentRatingConstants { * A content rating object. * *

Domain: com.android.tv + * *

Rating system: US_TV + * *

Rating: US_TV_PG + * *

Sub ratings: US_TV_L, US_TV_S */ public static final TvContentRating CONTENT_RATING_US_TV_PG_US_TV_L_US_TV_S = - TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_PG", "US_TV_L", - "US_TV_S"); + TvContentRating.createRating( + "com.android.tv", "US_TV", "US_TV_PG", "US_TV_L", "US_TV_S"); } diff --git a/tests/common/src/com/android/tv/testing/Utils.java b/tests/common/src/com/android/tv/testing/Utils.java index b2b4036e..a250995a 100644 --- a/tests/common/src/com/android/tv/testing/Utils.java +++ b/tests/common/src/com/android/tv/testing/Utils.java @@ -26,9 +26,7 @@ import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.net.Uri; import android.util.Log; - import com.android.tv.common.TvCommonUtils; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -45,7 +43,7 @@ import java.util.Random; * @see TvCommonUtils#isRunningInTest */ public final class Utils { - private static final String TAG ="Utils"; + private static final String TAG = "Utils"; private static final long DEFAULT_RANDOM_SEED = getSeed(); @@ -55,10 +53,12 @@ public final class Utils { } Resources res = context.getResources(); return new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(res.getResourcePackageName(resId)) - .path(res.getResourceTypeName(resId)) - .appendPath(res.getResourceEntryName(resId)).build().toString(); + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(res.getResourcePackageName(resId)) + .path(res.getResourceTypeName(resId)) + .appendPath(res.getResourceEntryName(resId)) + .build() + .toString(); } public static void copy(InputStream is, OutputStream os) throws IOException { @@ -91,8 +91,8 @@ public final class Utils { } /** - * Return the Random class which is needed to make random data for testing. - * Default seed of the random is today's date. + * Return the Random class which is needed to make random data for testing. Default seed of the + * random is today's date. */ public static Random createTestRandom() { return new Random(DEFAULT_RANDOM_SEED); @@ -108,13 +108,10 @@ public final class Utils { private Utils() {} - /** - * Checks whether TvActivity is enabled or not. - */ + /** Checks whether TvActivity is enabled or not. */ public static boolean isTvActivityEnabled(Context context) { PackageManager pm = context.getPackageManager(); - ComponentName name = new ComponentName("com.android.tv", - "com.android.tv.TvActivity"); + ComponentName name = new ComponentName("com.android.tv", "com.android.tv.TvActivity"); int enabled = pm.getComponentEnabledSetting(name); return enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; diff --git a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java index a9bfa97a..72bac8fc 100644 --- a/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java +++ b/tests/common/src/com/android/tv/testing/dvr/RecordingTestUtils.java @@ -17,40 +17,37 @@ package com.android.tv.testing.dvr; import com.android.tv.dvr.data.ScheduledRecording; - import junit.framework.Assert; -/** - * Static utils for using {@link ScheduledRecording} in tests. - */ +/** Static utils for using {@link ScheduledRecording} in tests. */ public final class RecordingTestUtils { private static final String INPUT_ID = "input_id"; private static final int CHANNEL_ID = 273; - public static ScheduledRecording createTestRecordingWithIdAndPeriod(long id, String inputId, - long channelId, long startTime, long endTime) { + public static ScheduledRecording createTestRecordingWithIdAndPeriod( + long id, String inputId, long channelId, long startTime, long endTime) { return ScheduledRecording.builder(inputId, channelId, startTime, endTime) .setId(id) .setChannelId(channelId) .build(); } - public static ScheduledRecording createTestRecordingWithPeriod(String inputId, - long channelId, long startTime, long endTime) { - return createTestRecordingWithIdAndPeriod(ScheduledRecording.ID_NOT_SET, inputId, channelId, - startTime, endTime); + public static ScheduledRecording createTestRecordingWithPeriod( + String inputId, long channelId, long startTime, long endTime) { + return createTestRecordingWithIdAndPeriod( + ScheduledRecording.ID_NOT_SET, inputId, channelId, startTime, endTime); } - public static ScheduledRecording createTestRecordingWithPriorityAndPeriod(long channelId, - long priority, long startTime, long endTime) { + public static ScheduledRecording createTestRecordingWithPriorityAndPeriod( + long channelId, long priority, long startTime, long endTime) { return ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, startTime, endTime) .setChannelId(channelId) .setPriority(priority) .build(); } - public static ScheduledRecording createTestRecordingWithIdAndPriorityAndPeriod(long id, - long channelId, long priority, long startTime, long endTime) { + public static ScheduledRecording createTestRecordingWithIdAndPriorityAndPeriod( + long id, long channelId, long priority, long startTime, long endTime) { return ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, startTime, endTime) .setId(id) .setChannelId(channelId) @@ -58,11 +55,12 @@ public final class RecordingTestUtils { .build(); } - public static ScheduledRecording normalizePriority(ScheduledRecording orig){ + public static ScheduledRecording normalizePriority(ScheduledRecording orig) { return ScheduledRecording.buildFrom(orig).setPriority(orig.getId()).build(); } - public static void assertRecordingEquals(ScheduledRecording expected, ScheduledRecording actual) { + public static void assertRecordingEquals( + ScheduledRecording expected, ScheduledRecording actual) { Assert.assertEquals("id", expected.getId(), actual.getId()); Assert.assertEquals("channel", expected.getChannelId(), actual.getChannelId()); Assert.assertEquals("programId", expected.getProgramId(), actual.getProgramId()); @@ -70,9 +68,11 @@ public final class RecordingTestUtils { Assert.assertEquals("start time", expected.getStartTimeMs(), actual.getStartTimeMs()); Assert.assertEquals("end time", expected.getEndTimeMs(), actual.getEndTimeMs()); Assert.assertEquals("state", expected.getState(), actual.getState()); - Assert.assertEquals("parent series recording", expected.getSeriesRecordingId(), + Assert.assertEquals( + "parent series recording", + expected.getSeriesRecordingId(), actual.getSeriesRecordingId()); } - private RecordingTestUtils() { } + private RecordingTestUtils() {} } diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java index 1b8f63cd..c6f67b0b 100644 --- a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java +++ b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java @@ -16,24 +16,16 @@ package com.android.tv.testing.testinput; import android.media.tv.TvTrackInfo; - import com.android.tv.testing.Constants; - import java.util.Collections; import java.util.List; -/** - * Versioned state information for a channel. - */ +/** Versioned state information for a channel. */ public class ChannelState { - /** - * The video track a channel has by default. - */ + /** The video track a channel has by default. */ public static final TvTrackInfo DEFAULT_VIDEO_TRACK = Constants.FHD1080P50_VIDEO_TRACK; - /** - * The video track a channel has by default. - */ + /** The video track a channel has by default. */ public static final TvTrackInfo DEFAULT_AUDIO_TRACK = Constants.EN_STEREO_AUDIO_TRACK; /** * The channel is "tuned" and video available. @@ -47,27 +39,23 @@ public class ChannelState { * Default ChannelState with version @{value #CHANNEL_VERSION_DEFAULT} and default {@link * ChannelStateData}. */ - public static final ChannelState DEFAULT = new ChannelState(CHANNEL_VERSION_DEFAULT, - new ChannelStateData()); + public static final ChannelState DEFAULT = + new ChannelState(CHANNEL_VERSION_DEFAULT, new ChannelStateData()); + private final int mVersion; private final ChannelStateData mData; - private ChannelState(int version, ChannelStateData channelStateData) { mVersion = version; mData = channelStateData; } - /** - * Returns the id of the selected audio track, or null if none is selected. - */ + /** Returns the id of the selected audio track, or null if none is selected. */ public String getSelectedAudioTrackId() { return mData.mSelectedAudioTrackId; } - /** - * Returns the id of the selected audio track, or null if none is selected. - */ + /** Returns the id of the selected audio track, or null if none is selected. */ public String getSelectedVideoTrackId() { return mData.mSelectedVideoTrackId; } @@ -82,9 +70,8 @@ public class ChannelState { } /** - * Tune status is either {@link #TUNE_STATUS_VIDEO_AVAILABLE} or a {@link - * android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) video unavailable - * reason} + * Tune status is either {@link #TUNE_STATUS_VIDEO_AVAILABLE} or a {@link + * android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) video unavailable reason} */ public int getTuneStatus() { return mData.mTuneStatus; diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java index 9bac9d12..cdeb1f5c 100644 --- a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java +++ b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java @@ -19,25 +19,23 @@ package com.android.tv.testing.testinput; import android.media.tv.TvTrackInfo; import android.os.Parcel; import android.os.Parcelable; - import java.util.ArrayList; import java.util.List; -/** - * Mutable unversioned channel state. - */ +/** Mutable unversioned channel state. */ public final class ChannelStateData implements Parcelable { - public static final Creator CREATOR = new Creator() { - @Override - public ChannelStateData createFromParcel(Parcel in) { - return new ChannelStateData(in); - } + public static final Creator CREATOR = + new Creator() { + @Override + public ChannelStateData createFromParcel(Parcel in) { + return new ChannelStateData(in); + } - @Override - public ChannelStateData[] newArray(int size) { - return new ChannelStateData[size]; - } - }; + @Override + public ChannelStateData[] newArray(int size) { + return new ChannelStateData[size]; + } + }; public final List mTvTrackInfos = new ArrayList<>(); public int mTuneStatus = ChannelState.TUNE_STATUS_VIDEO_AVAILABLE; @@ -71,9 +69,6 @@ public final class ChannelStateData implements Parcelable { @Override public String toString() { - return "{" - + "tune=" + mTuneStatus - + ", tracks=" + mTvTrackInfos - + "}"; + return "{" + "tune=" + mTuneStatus + ", tracks=" + mTvTrackInfos + "}"; } } diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java index 9b3f8835..82b4614e 100644 --- a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java +++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java @@ -21,7 +21,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; - import com.android.tv.testing.ChannelInfo; /** @@ -47,9 +46,7 @@ public class TestInputControlConnection implements ServiceConnection { mControl = null; } - /** - * Is the service currently connected. - */ + /** Is the service currently connected. */ public boolean isBound() { return mControl != null; } @@ -58,7 +55,7 @@ public class TestInputControlConnection implements ServiceConnection { * Update the state of the channel. * * @param channel the channel to update. - * @param data the new state for the channel. + * @param data the new state for the channel. */ public void updateChannelState(ChannelInfo channel, ChannelStateData data) { waitUntilBound(); @@ -69,9 +66,7 @@ public class TestInputControlConnection implements ServiceConnection { } } - /** - * Sleep until {@link #isBound()} is true; - */ + /** Sleep until {@link #isBound()} is true; */ public void waitUntilBound() { while (!isBound()) { SystemClock.sleep(BOUND_CHECK_INTERVAL_MS); diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java index 54aacf20..330afa9b 100644 --- a/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java +++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java @@ -18,16 +18,16 @@ package com.android.tv.testing.testinput; import android.content.ComponentName; import android.content.Intent; -/** - * Static utils for {@link ITestInputControl}. - */ +/** Static utils for {@link ITestInputControl}. */ public final class TestInputControlUtils { public static Intent createIntent() { - return new Intent().setComponent(new ComponentName("com.android.tv.testinput", - "com.android.tv.testinput.TestInputControlService")); + return new Intent() + .setComponent( + new ComponentName( + "com.android.tv.testinput", + "com.android.tv.testinput.TestInputControlService")); } - private TestInputControlUtils() { - } + private TestInputControlUtils() {} } diff --git a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java index 498addfd..0c768b21 100644 --- a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java +++ b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java @@ -17,23 +17,20 @@ package com.android.tv.testing.testinput; import com.android.tv.testing.ChannelInfo; -/** - * Constants for interacting with TvTestInput. - */ +/** Constants for interacting with TvTestInput. */ public final class TvTestInputConstants { /** * Channel 1. * - *

By convention Channel 1 should not be changed. Test often start by tuning to this - * channel. + *

By convention Channel 1 should not be changed. Test often start by tuning to this channel. */ public static final ChannelInfo CH_1_DEFAULT_DONT_MODIFY = ChannelInfo.create(null, 1); /** * Channel 2. * - *

By convention the state of Channel 2 is changed by tests. Testcases should explicitly - * set the state of this channel before using it in tests. + *

By convention the state of Channel 2 is changed by tests. Testcases should explicitly set + * the state of this channel before using it in tests. */ public static final ChannelInfo CH_2 = ChannelInfo.create(null, 2); } diff --git a/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java index 3a2f5509..21b05d67 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java @@ -18,9 +18,7 @@ package com.android.tv.testing.uihelper; import android.content.res.Resources; import android.support.test.uiautomator.UiDevice; -/** - * Base class for building UiAutomator Helper classes. - */ +/** Base class for building UiAutomator Helper classes. */ public abstract class BaseUiDeviceHelper { protected final UiDevice mUiDevice; protected final Resources mTargetResources; diff --git a/tests/common/src/com/android/tv/testing/uihelper/ByResource.java b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java index a76ee1d3..47b8d9f9 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/ByResource.java +++ b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java @@ -19,9 +19,7 @@ import android.content.res.Resources; import android.support.test.uiautomator.By; import android.support.test.uiautomator.BySelector; -/** - * Convenience methods for creating {@link BySelector}s using resource ids. - */ +/** Convenience methods for creating {@link BySelector}s using resource ids. */ public final class ByResource { /** @@ -44,6 +42,5 @@ public final class ByResource { return By.text(text); } - private ByResource() { - } + private ByResource() {} } diff --git a/tests/common/src/com/android/tv/testing/uihelper/Constants.java b/tests/common/src/com/android/tv/testing/uihelper/Constants.java index 8dd8e14a..77cb4c90 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/Constants.java +++ b/tests/common/src/com/android/tv/testing/uihelper/Constants.java @@ -35,6 +35,5 @@ public final class Constants { public static final BySelector DVR_SCHEDULES = By.res(TV_APP_PACKAGE, "dvr_schedules"); public static final BySelector FOCUSED_VIEW = By.focused(true); - private Constants() { - } + private Constants() {} } diff --git a/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java b/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java index 9e4040a8..2ac4b648 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java @@ -24,12 +24,9 @@ import android.content.res.Resources; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; - import com.android.tv.R; -/** - * Helper for testing {@link DialogFragment}s. - */ +/** Helper for testing {@link DialogFragment}s. */ public class DialogHelper extends BaseUiDeviceHelper { private final BySelector byPinDialog; @@ -39,7 +36,9 @@ public class DialogHelper extends BaseUiDeviceHelper { } public void assertWaitForPinDialogOpen() { - assertWaitForCondition(mUiDevice, Until.hasObject(byPinDialog), + assertWaitForCondition( + mUiDevice, + Until.hasObject(byPinDialog), Constants.MAX_SHOW_DELAY_MILLIS + mTargetResources.getInteger(R.integer.pin_dialog_anim_duration)); } diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java index 1dc0f020..8c4e6e4b 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java @@ -11,31 +11,27 @@ import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.util.Log; - import com.android.tv.testing.Utils; - import junit.framework.Assert; -/** - * Helper for testing the Live TV Application. - */ +/** Helper for testing the Live TV Application. */ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper { private static final String TAG = "LiveChannelsUiDevice"; private static final int APPLICATION_START_TIMEOUT_MSEC = 5000; private final Context mContext; - public LiveChannelsUiDeviceHelper(UiDevice uiDevice, Resources targetResources, - Context context) { + public LiveChannelsUiDeviceHelper( + UiDevice uiDevice, Resources targetResources, Context context) { super(uiDevice, targetResources); mContext = context; } public void assertAppStarted() { assertTrue("TvActivity should be enabled.", Utils.isTvActivityEnabled(mContext)); - Intent intent = mContext.getPackageManager() - .getLaunchIntentForPackage(Constants.TV_APP_PACKAGE); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances + Intent intent = + mContext.getPackageManager().getLaunchIntentForPackage(Constants.TV_APP_PACKAGE); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances mContext.startActivity(intent); // Wait for idle state before checking the channel banner because waitForCondition() has // timeout. @@ -43,8 +39,10 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper { // Make sure that the activity is resumed. waitForCondition(mUiDevice, Until.hasObject(Constants.TV_VIEW)); - Assert.assertTrue(Constants.TV_APP_PACKAGE + " did not start", mUiDevice - .wait(Until.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0)), + Assert.assertTrue( + Constants.TV_APP_PACKAGE + " did not start", + mUiDevice.wait( + Until.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0)), APPLICATION_START_TIMEOUT_MSEC)); BySelector welcome = ByResource.id(mTargetResources, com.android.tv.R.id.intro); if (mUiDevice.hasObject(welcome)) { @@ -54,9 +52,9 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper { } public void assertAppStopped() { - while(mUiDevice.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0))) { + while (mUiDevice.hasObject(By.pkg(Constants.TV_APP_PACKAGE).depth(0))) { mUiDevice.pressBack(); mUiDevice.waitForIdle(); } } -} \ No newline at end of file +} diff --git a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java index 80d53242..c8ea85ac 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java @@ -25,33 +25,30 @@ import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; - import com.android.tv.R; - import junit.framework.Assert; -/** - * Helper for testing {@link com.android.tv.menu.Menu}. - */ +/** Helper for testing {@link com.android.tv.menu.Menu}. */ public class MenuHelper extends BaseUiDeviceHelper { private final BySelector byChannels; public MenuHelper(UiDevice uiDevice, Resources targetResources) { super(uiDevice, targetResources); - byChannels = ByResource.id(mTargetResources, R.id.item_list) - .hasDescendant(ByResource.text(mTargetResources, R.string.menu_title_channels)); + byChannels = + ByResource.id(mTargetResources, R.id.item_list) + .hasDescendant( + ByResource.text(mTargetResources, R.string.menu_title_channels)); } public BySelector getByChannels() { return byChannels; } - /** - * Navigate to the menu item with the text {@code itemTextResId} in the row with text - * {@code rowTitleResId}. - *

- * Fails if the menu item can not be navigated to. + * Navigate to the menu item with the text {@code itemTextResId} in the row with text {@code + * rowTitleResId}. + * + *

Fails if the menu item can not be navigated to. * * @param rowTitleResId the resource id of the string in the desired row title. * @param itemTextResId the resource id of the string in the desired item. @@ -62,17 +59,20 @@ public class MenuHelper extends BaseUiDeviceHelper { BySelector byListView = ByResource.id(mTargetResources, R.id.list_view); UiObject2 listView = row.findObject(byListView); Assert.assertNotNull( - "Menu row '" + mTargetResources.getString(rowTitleResId) + "' does not have a " - + byListView, listView); + "Menu row '" + + mTargetResources.getString(rowTitleResId) + + "' does not have a " + + byListView, + listView); return assertNavigateToRowItem(listView, itemTextResId); } /** * Navigate to the menu row with the text title {@code rowTitleResId}. - *

- * Fails if the menu row can not be navigated to. - * We can't navigate to the Play controls row with this method, because the row doesn't have the - * title when it is selected. Use {@link #assertNavigateToPlayControlsRow} for the row instead. + * + *

Fails if the menu row can not be navigated to. We can't navigate to the Play controls row + * with this method, because the row doesn't have the title when it is selected. Use {@link + * #assertNavigateToPlayControlsRow} for the row instead. * * @param rowTitleResId the resource id of the string in the desired row title. * @return the row navigated to. @@ -82,14 +82,17 @@ public class MenuHelper extends BaseUiDeviceHelper { UiObject2 menu = mUiDevice.findObject(MENU); // TODO: handle play controls. They have a different dom structure and navigation sometimes // can get stuck on that row. - return UiDeviceAsserts.assertNavigateTo(mUiDevice, menu, - By.hasDescendant(ByResource.text(mTargetResources, rowTitleResId)), Direction.DOWN); + return UiDeviceAsserts.assertNavigateTo( + mUiDevice, + menu, + By.hasDescendant(ByResource.text(mTargetResources, rowTitleResId)), + Direction.DOWN); } /** * Navigate to the Play controls row. - *

- * Fails if the row can not be navigated to. + * + *

Fails if the row can not be navigated to. * * @see #assertNavigateToRow */ @@ -103,27 +106,28 @@ public class MenuHelper extends BaseUiDeviceHelper { /** * Navigate to the menu item in the given {@code row} with the text {@code itemTextResId} . - *

- * Fails if the menu item can not be navigated to. * - * @param row the container to look for menu items in. + *

Fails if the menu item can not be navigated to. + * + * @param row the container to look for menu items in. * @param itemTextResId the resource id of the string in the desired item. * @return the item navigated to. */ public UiObject2 assertNavigateToRowItem(UiObject2 row, int itemTextResId) { - return UiDeviceAsserts.assertNavigateTo(mUiDevice, row, + return UiDeviceAsserts.assertNavigateTo( + mUiDevice, + row, By.hasDescendant(ByResource.text(mTargetResources, itemTextResId)), Direction.RIGHT); } public UiObject2 assertPressOptionsSettings() { - return assertPressMenuItem(R.string.menu_title_options, - R.string.options_item_settings); + return assertPressMenuItem(R.string.menu_title_options, R.string.options_item_settings); } public UiObject2 assertPressOptionsClosedCaptions() { - return assertPressMenuItem(R.string.menu_title_options, - R.string.options_item_closed_caption); + return assertPressMenuItem( + R.string.menu_title_options, R.string.options_item_closed_caption); } public UiObject2 assertPressOptionsDisplayMode() { @@ -135,20 +139,19 @@ public class MenuHelper extends BaseUiDeviceHelper { } public UiObject2 assertPressProgramGuide() { - return assertPressMenuItem(R.string.menu_title_channels, - R.string.channels_item_program_guide); + return assertPressMenuItem( + R.string.menu_title_channels, R.string.channels_item_program_guide); } public UiObject2 assertPressDvrLibrary() { - return assertPressMenuItem(R.string.menu_title_channels, - R.string.channels_item_dvr); + return assertPressMenuItem(R.string.menu_title_channels, R.string.channels_item_dvr); } /** - * Navigate to the menu item with the text {@code itemTextResId} in the row with text - * {@code rowTitleResId}. - *

- * Fails if the menu item can not be navigated to. + * Navigate to the menu item with the text {@code itemTextResId} in the row with text {@code + * rowTitleResId}. + * + *

Fails if the menu item can not be navigated to. * * @param rowTitleResId the resource id of the string in the desired row title. * @param itemTextResId the resource id of the string in the desired item. @@ -161,17 +164,15 @@ public class MenuHelper extends BaseUiDeviceHelper { return item; } - /** - * Waits until the menu is visible. - */ + /** Waits until the menu is visible. */ public void assertWaitForMenu() { UiDeviceAsserts.assertWaitForCondition(mUiDevice, Until.hasObject(MENU)); } /** - * Show the menu. - *

- * Fails if the menu does not appear in {@link Constants#MAX_SHOW_DELAY_MILLIS}. + * Show the menu. + * + *

Fails if the menu does not appear in {@link Constants#MAX_SHOW_DELAY_MILLIS}. */ public void showMenu() { if (!mUiDevice.hasObject(MENU)) { diff --git a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java index 98a19a41..ba015260 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java @@ -22,15 +22,11 @@ import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; - import com.android.tv.R; import com.android.tv.ui.sidepanel.SideFragment; - import junit.framework.Assert; -/** - * Helper for testing {@link SideFragment}s. - */ +/** Helper for testing {@link SideFragment}s. */ public class SidePanelHelper extends BaseUiDeviceHelper { public SidePanelHelper(UiDevice uiDevice, Resources targetResources) { @@ -54,6 +50,7 @@ public class SidePanelHelper extends BaseUiDeviceHelper { String title = mTargetResources.getString(resId); return assertNavigateToItem(title, direction); } + public UiObject2 assertNavigateToItem(String title) { return assertNavigateToItem(title, Direction.DOWN); } @@ -63,7 +60,7 @@ public class SidePanelHelper extends BaseUiDeviceHelper { UiObject2 sidePanelList = mUiDevice.findObject(sidePanelSelector); Assert.assertNotNull(sidePanelSelector + " not found", sidePanelList); - return UiDeviceAsserts.assertNavigateTo(mUiDevice, sidePanelList, - By.hasDescendant(By.text(title)), direction); + return UiDeviceAsserts.assertNavigateTo( + mUiDevice, sidePanelList, By.hasDescendant(By.text(title)), direction); } } diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java index c096d7d2..28ea163e 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java @@ -27,12 +27,9 @@ import android.support.test.uiautomator.SearchCondition; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; - import junit.framework.Assert; -/** - * Asserts for {@link UiDevice}s. - */ +/** Asserts for {@link UiDevice}s. */ public final class UiDeviceAsserts { public static void assertHas(UiDevice uiDevice, BySelector bySelector, boolean expected) { @@ -46,25 +43,25 @@ public final class UiDeviceAsserts { } /** - * Assert that {@code searchCondition} becomes true within - * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds. + * Assert that {@code searchCondition} becomes true within {@value + * Constants#MAX_SHOW_DELAY_MILLIS} milliseconds. * - * @param uiDevice the device under test. + * @param uiDevice the device under test. * @param searchCondition the condition to wait for. */ - public static void assertWaitForCondition(UiDevice uiDevice, - SearchCondition searchCondition) { + public static void assertWaitForCondition( + UiDevice uiDevice, SearchCondition searchCondition) { assertWaitForCondition(uiDevice, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS); } /** * Assert that {@code searchCondition} becomes true within {@code timeout} milliseconds. * - * @param uiDevice the device under test. + * @param uiDevice the device under test. * @param searchCondition the condition to wait for. */ - public static void assertWaitForCondition(UiDevice uiDevice, - SearchCondition searchCondition, long timeout) { + public static void assertWaitForCondition( + UiDevice uiDevice, SearchCondition searchCondition, long timeout) { boolean result = waitForCondition(uiDevice, searchCondition, timeout); assertTrue(searchCondition + " not true after " + timeout / 1000.0 + " seconds.", result); } @@ -72,52 +69,55 @@ public final class UiDeviceAsserts { /** * Wait until {@code searchCondition} becomes true. * - * @param uiDevice The device under test. + * @param uiDevice The device under test. * @param searchCondition The condition to wait for. * @return {@code true} if the condition is met, otherwise {@code false}. */ - public static boolean waitForCondition(UiDevice uiDevice, - SearchCondition searchCondition) { + public static boolean waitForCondition( + UiDevice uiDevice, SearchCondition searchCondition) { return waitForCondition(uiDevice, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS); } - private static boolean waitForCondition(UiDevice uiDevice, - SearchCondition searchCondition, long timeout) { - long adjustedTimeout = timeout + Math.max(Constants.MIN_EXTRA_TIMEOUT, - (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT)); + private static boolean waitForCondition( + UiDevice uiDevice, SearchCondition searchCondition, long timeout) { + long adjustedTimeout = + timeout + + Math.max( + Constants.MIN_EXTRA_TIMEOUT, + (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT)); return uiDevice.wait(searchCondition, adjustedTimeout); } /** * Navigates through the focus items in a container returning the container child that has a * descendant matching the {@code selector}. - *

- * The navigation starts in the {@code direction} specified and - * {@link Direction#reverse(Direction) reverses} once if needed. Fails if there is not a - * focused - * descendant, or if after completing both directions no focused child has a descendant - * matching + * + *

The navigation starts in the {@code direction} specified and {@link + * Direction#reverse(Direction) reverses} once if needed. Fails if there is not a focused + * descendant, or if after completing both directions no focused child has a descendant matching * {@code selector}. - *

- * Fails if the menu item can not be navigated to. * - * @param uiDevice the device under test. + *

Fails if the menu item can not be navigated to. + * + * @param uiDevice the device under test. * @param container contains children to navigate over. - * @param selector the selector for the object to navigate to. + * @param selector the selector for the object to navigate to. * @param direction the direction to start navigating. * @return the object navigated to. */ - public static UiObject2 assertNavigateTo(UiDevice uiDevice, UiObject2 container, - BySelector selector, Direction direction) { + public static UiObject2 assertNavigateTo( + UiDevice uiDevice, UiObject2 container, BySelector selector, Direction direction) { int count = 0; while (count < 2) { BySelector hasFocusedDescendant = By.hasDescendant(FOCUSED_VIEW); UiObject2 focusedChild = null; - SearchCondition untilHasFocusedDescendant = Until - .hasObject(hasFocusedDescendant); + SearchCondition untilHasFocusedDescendant = + Until.hasObject(hasFocusedDescendant); - boolean result = container.wait(untilHasFocusedDescendant, - UiObject2Asserts.getAdjustedTimeout(Constants.MAX_SHOW_DELAY_MILLIS)); + boolean result = + container.wait( + untilHasFocusedDescendant, + UiObject2Asserts.getAdjustedTimeout(Constants.MAX_SHOW_DELAY_MILLIS)); if (!result) { // HACK: Try direction anyways because play control does not always have a // focused item. @@ -147,6 +147,5 @@ public final class UiDeviceAsserts { return null; } - private UiDeviceAsserts() { - } + private UiDeviceAsserts() {} } diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java index 98eff906..9bd04a40 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java @@ -27,9 +27,7 @@ import android.support.test.uiautomator.UiDevice; import android.view.InputDevice; import android.view.KeyEvent; -/** - * Static utility methods for {@link UiDevice}. - */ +/** Static utility methods for {@link UiDevice}. */ public final class UiDeviceUtils { public static void pressDpad(UiDevice uiDevice, Direction direction) { @@ -51,7 +49,6 @@ public final class UiDeviceUtils { } } - public static void pressKeys(UiDevice uiDevice, int... keyCodes) { for (int k : keyCodes) { uiDevice.pressKeyCode(k); @@ -60,8 +57,8 @@ public final class UiDeviceUtils { /** * Parses the string and sends the corresponding individual key presses. - *

- * Note: only handles 0-9, '.', and '-'. + * + *

Note: only handles 0-9, '.', and '-'. */ public static void pressKeys(UiDevice uiDevice, String keys) { for (char c : keys.toCharArray()) { @@ -78,8 +75,8 @@ public final class UiDeviceUtils { } /** - * Sends the DPAD Center key presses with the {@code repeat} count. - * TODO: Remove instrumentation argument once migrated to JUnit4. + * Sends the DPAD Center key presses with the {@code repeat} count. TODO: Remove instrumentation + * argument once migrated to JUnit4. */ public static void pressDPadCenter(Instrumentation instrumentation, int repeat) { pressKey(instrumentation, KeyEvent.KEYCODE_DPAD_CENTER, repeat); @@ -97,27 +94,38 @@ public final class UiDeviceUtils { assertPressKeyUp(instrumentation, keyCode, true); } - private static void assertPressKeyDown(Instrumentation instrumentation, int keyCode, - boolean sync) { + private static void assertPressKeyDown( + Instrumentation instrumentation, int keyCode, boolean sync) { assertPressKey(instrumentation, KeyEvent.ACTION_DOWN, keyCode, sync); } - private static void assertPressKeyUp(Instrumentation instrumentation, int keyCode, - boolean sync) { + private static void assertPressKeyUp( + Instrumentation instrumentation, int keyCode, boolean sync) { assertPressKey(instrumentation, KeyEvent.ACTION_UP, keyCode, sync); } - private static void assertPressKey(Instrumentation instrumentation, int action, int keyCode, - boolean sync) { + private static void assertPressKey( + Instrumentation instrumentation, int action, int keyCode, boolean sync) { long eventTime = SystemClock.uptimeMillis(); - KeyEvent event = new KeyEvent(eventTime, eventTime, action, keyCode, 0, 0, -1, 0, 0, - InputDevice.SOURCE_KEYBOARD); - assertTrue("Failed to inject key up event:" + event, + KeyEvent event = + new KeyEvent( + eventTime, + eventTime, + action, + keyCode, + 0, + 0, + -1, + 0, + 0, + InputDevice.SOURCE_KEYBOARD); + assertTrue( + "Failed to inject key up event:" + event, injectEvent(instrumentation, event, sync)); } - private static boolean injectEvent(Instrumentation instrumentation, KeyEvent event, - boolean sync) { + private static boolean injectEvent( + Instrumentation instrumentation, KeyEvent event, boolean sync) { return getUiAutomation(instrumentation).injectInputEvent(event, sync); } @@ -130,6 +138,5 @@ public final class UiDeviceUtils { } } - private UiDeviceUtils() { - } + private UiDeviceUtils() {} } diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java index aba29f0e..ee02d7f7 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java @@ -20,41 +20,40 @@ import static junit.framework.Assert.assertTrue; import android.support.test.uiautomator.SearchCondition; import android.support.test.uiautomator.UiObject2; -/** - * Asserts for {@link UiObject2}s. - */ +/** Asserts for {@link UiObject2}s. */ public final class UiObject2Asserts { /** - * Assert that {@code searchCondition} becomes true within - * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds. + * Assert that {@code searchCondition} becomes true within {@value + * Constants#MAX_SHOW_DELAY_MILLIS} milliseconds. * - * @param uiObject the device under test. + * @param uiObject the device under test. * @param searchCondition the condition to wait for. */ - public static void assertWaitForCondition(UiObject2 uiObject, - SearchCondition searchCondition) { + public static void assertWaitForCondition( + UiObject2 uiObject, SearchCondition searchCondition) { assertWaitForCondition(uiObject, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS); } /** * Assert that {@code searchCondition} becomes true within {@code timeout} milliseconds. * - * @param uiObject the device under test. + * @param uiObject the device under test. * @param searchCondition the condition to wait for. */ - public static void assertWaitForCondition(UiObject2 uiObject, - SearchCondition searchCondition, long timeout) { + public static void assertWaitForCondition( + UiObject2 uiObject, SearchCondition searchCondition, long timeout) { long adjustedTimeout = getAdjustedTimeout(timeout); boolean result = uiObject.wait(searchCondition, adjustedTimeout); assertTrue(searchCondition + " not true after " + timeout / 1000.0 + " seconds.", result); } public static long getAdjustedTimeout(long timeout) { - return timeout + Math.max( - Constants.MIN_EXTRA_TIMEOUT, (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT)); + return timeout + + Math.max( + Constants.MIN_EXTRA_TIMEOUT, + (long) (timeout * Constants.EXTRA_TIMEOUT_PERCENT)); } - private UiObject2Asserts() { - } + private UiObject2Asserts() {} } diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java index 2a997a67..2f3779c5 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java +++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java @@ -19,9 +19,7 @@ import android.graphics.Point; import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiObject2; -/** - * Static utility methods for {@link UiObject2}s. - */ +/** Static utility methods for {@link UiObject2}s. */ public class UiObject2Utils { public static boolean hasSiblingInDirection(UiObject2 theUiObject, Direction direction) { @@ -56,6 +54,5 @@ public class UiObject2Utils { return false; } - private UiObject2Utils() { - } + private UiObject2Utils() {} } diff --git a/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java b/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java index d8a4aec1..28e2571e 100644 --- a/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java +++ b/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java @@ -20,7 +20,6 @@ import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondi import android.support.test.filters.SmallTest; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.Constants; @@ -33,8 +32,9 @@ public class ChannelBannerViewTest extends LiveChannelsTestCase { protected void setUp() throws Exception { super.setUp(); mLiveChannelsHelper.assertAppStarted(); - mShowDurationMillis = mTargetResources.getInteger(R.integer.channel_banner_show_duration) - + Constants.MAX_SHOW_DELAY_MILLIS; + mShowDurationMillis = + mTargetResources.getInteger(R.integer.channel_banner_show_duration) + + Constants.MAX_SHOW_DELAY_MILLIS; } public void testChannelBannerAppearDisappear() { diff --git a/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java index cfa5eda7..d24ba58d 100644 --- a/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java +++ b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java @@ -20,14 +20,11 @@ import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondi import android.support.test.filters.LargeTest; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; import com.android.tv.testing.uihelper.UiDeviceUtils; -/** - * Tests for channel sources. - */ +/** Tests for channel sources. */ @LargeTest public class ChannelSourcesTest extends LiveChannelsTestCase { private BySelector mBySettingsSidePanel; @@ -35,11 +32,11 @@ public class ChannelSourcesTest extends LiveChannelsTestCase { @Override protected void setUp() throws Exception { super.setUp(); - mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_settings); + mBySettingsSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_settings); } - //TODO: create a cancelable test channel setup. + // TODO: create a cancelable test channel setup. public void testSetup_cancel() { mLiveChannelsHelper.assertAppStarted(); @@ -49,7 +46,8 @@ public class ChannelSourcesTest extends LiveChannelsTestCase { mSidePanelHelper.assertNavigateToItem(R.string.settings_channel_source_item_setup); mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, + assertWaitForCondition( + mDevice, Until.hasObject(ByResource.text(mTargetResources, R.string.setup_sources_text))); mDevice.pressBack(); } @@ -63,7 +61,8 @@ public class ChannelSourcesTest extends LiveChannelsTestCase { mSidePanelHelper.assertNavigateToItem(R.string.settings_channel_source_item_setup); UiDeviceUtils.pressDPadCenter(getInstrumentation(), 2); - assertWaitForCondition(mDevice, + assertWaitForCondition( + mDevice, Until.hasObject(ByResource.text(mTargetResources, R.string.setup_sources_text))); mDevice.pressBack(); } diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java index 48224123..36a3b07b 100644 --- a/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java +++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java @@ -22,16 +22,13 @@ import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondi import android.support.test.filters.LargeTest; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.testing.uihelper.Constants; import com.android.tv.testing.uihelper.DialogHelper; -/** - * Basic tests for the LiveChannels app. - */ +/** Basic tests for the LiveChannels app. */ @LargeTest public class LiveChannelsAppTest extends LiveChannelsTestCase { private BySelector mBySettingsSidePanel; @@ -42,14 +39,15 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { mLiveChannelsHelper.assertAppStarted(); pressKeysForChannel(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY); getInstrumentation().waitForIdleSync(); - mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_settings); + mBySettingsSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_settings); } public void testSettingsCancel() { mMenuHelper.assertPressOptionsSettings(); - BySelector byChannelSourcesSidePanel = mSidePanelHelper - .bySidePanelTitled(R.string.settings_channel_source_item_customize_channels); + BySelector byChannelSourcesSidePanel = + mSidePanelHelper.bySidePanelTitled( + R.string.settings_channel_source_item_customize_channels); assertWaitForCondition(mDevice, Until.hasObject(byChannelSourcesSidePanel)); mDevice.pressBack(); assertWaitForCondition(mDevice, Until.gone(byChannelSourcesSidePanel)); @@ -58,8 +56,8 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { public void testClosedCaptionsCancel() { mMenuHelper.assertPressOptionsClosedCaptions(); - BySelector byClosedCaptionSidePanel = mSidePanelHelper - .bySidePanelTitled(R.string.side_panel_title_closed_caption); + BySelector byClosedCaptionSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_closed_caption); assertWaitForCondition(mDevice, Until.hasObject(byClosedCaptionSidePanel)); mDevice.pressBack(); assertWaitForCondition(mDevice, Until.gone(byClosedCaptionSidePanel)); @@ -69,13 +67,12 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { public void testDisplayModeCancel() { ChannelStateData data = new ChannelStateData(); data.mTvTrackInfos.add(com.android.tv.testing.Constants.SVGA_VIDEO_TRACK); - data.mSelectedVideoTrackId = com.android.tv.testing.Constants.SVGA_VIDEO_TRACK - .getId(); + data.mSelectedVideoTrackId = com.android.tv.testing.Constants.SVGA_VIDEO_TRACK.getId(); updateThenTune(data, TvTestInputConstants.CH_2); mMenuHelper.assertPressOptionsDisplayMode(); - BySelector byDisplayModeSidePanel = mSidePanelHelper - .bySidePanelTitled(R.string.side_panel_title_display_mode); + BySelector byDisplayModeSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_display_mode); assertWaitForCondition(mDevice, Until.hasObject(byDisplayModeSidePanel)); mDevice.pressBack(); assertWaitForCondition(mDevice, Until.gone(byDisplayModeSidePanel)); @@ -95,8 +92,8 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { updateThenTune(data, TvTestInputConstants.CH_2); mMenuHelper.assertPressOptionsMultiAudio(); - BySelector byMultiAudioSidePanel = mSidePanelHelper - .bySidePanelTitled(R.string.side_panel_title_multi_audio); + BySelector byMultiAudioSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_multi_audio); assertWaitForCondition(mDevice, Until.hasObject(byMultiAudioSidePanel)); mDevice.pressBack(); assertWaitForCondition(mDevice, Until.gone(byMultiAudioSidePanel)); diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java index e306e6c6..9a1bc043 100644 --- a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java +++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.test.InstrumentationTestCase; - import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TestInputControlConnection; @@ -34,9 +33,7 @@ import com.android.tv.testing.uihelper.MenuHelper; import com.android.tv.testing.uihelper.SidePanelHelper; import com.android.tv.testing.uihelper.UiDeviceUtils; -/** - * Base test case for LiveChannel UI tests. - */ +/** Base test case for LiveChannel UI tests. */ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { protected final TestInputControlConnection mConnection = new TestInputControlConnection(); @@ -50,8 +47,8 @@ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { protected void setUp() throws Exception { super.setUp(); Context context = getInstrumentation().getContext(); - context.bindService(TestInputControlUtils.createIntent(), mConnection, - Context.BIND_AUTO_CREATE); + context.bindService( + TestInputControlUtils.createIntent(), mConnection, Context.BIND_AUTO_CREATE); mDevice = UiDevice.getInstance(getInstrumentation()); mTargetResources = getInstrumentation().getTargetContext().getResources(); mMenuHelper = new MenuHelper(mDevice, mTargetResources); @@ -69,8 +66,9 @@ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { // Clear any side panel, menu, ... // Scene container should not be checked here because pressing the BACK key in some scenes // might launch the home screen. - if (mDevice.hasObject(Constants.SIDE_PANEL) || mDevice.hasObject(Constants.MENU) || mDevice - .hasObject(Constants.PROGRAM_GUIDE)) { + if (mDevice.hasObject(Constants.SIDE_PANEL) + || mDevice.hasObject(Constants.MENU) + || mDevice.hasObject(Constants.PROGRAM_GUIDE)) { mDevice.pressBack(); } // To destroy the activity to make sure next test case's activity launch check works well. @@ -79,8 +77,7 @@ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { } /** - * Send the keys for the channel number of {@code channel} and press the DPAD - * center. + * Send the keys for the channel number of {@code channel} and press the DPAD center. * *

Usually this will tune to the given channel. */ @@ -93,7 +90,7 @@ public abstract class LiveChannelsTestCase extends InstrumentationTestCase { /** * Update the channel state to {@code data} then tune to that channel. * - * @param data the state to update the channel with. + * @param data the state to update the channel with. * @param channel the channel to tune to */ protected void updateThenTune(ChannelStateData data, ChannelInfo channel) { diff --git a/tests/func/src/com/android/tv/tests/ui/ParentalControlsTest.java b/tests/func/src/com/android/tv/tests/ui/ParentalControlsTest.java index 93d14bde..f6e774ca 100644 --- a/tests/func/src/com/android/tv/tests/ui/ParentalControlsTest.java +++ b/tests/func/src/com/android/tv/tests/ui/ParentalControlsTest.java @@ -22,7 +22,6 @@ import android.support.test.filters.SmallTest; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; import com.android.tv.testing.uihelper.DialogHelper; @@ -36,8 +35,8 @@ public class ParentalControlsTest extends LiveChannelsTestCase { protected void setUp() throws Exception { super.setUp(); mLiveChannelsHelper.assertAppStarted(); - mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_settings); + mBySettingsSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_settings); prepareParentalControl(); } @@ -49,8 +48,8 @@ public class ParentalControlsTest extends LiveChannelsTestCase { public void testRatingDependentSelect() { // Show ratings fragment. - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.option_program_restrictions); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.option_program_restrictions); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); mSidePanelHelper.assertNavigateToItem(R.string.option_ratings); mDevice.pressDPadCenter(); @@ -63,8 +62,10 @@ public class ParentalControlsTest extends LiveChannelsTestCase { int maxAge = 20; int minAge = 4; for (int age = minAge; age <= maxAge; age++) { - UiObject2 ratingCheckBox = mSidePanelHelper.assertNavigateToItem(String.valueOf(age)) - .findObject(ByResource.id(mTargetResources, R.id.check_box)); + UiObject2 ratingCheckBox = + mSidePanelHelper + .assertNavigateToItem(String.valueOf(age)) + .findObject(ByResource.id(mTargetResources, R.id.check_box)); if (ratingCheckBox.isChecked()) { mDevice.pressDPadCenter(); } @@ -80,26 +81,29 @@ public class ParentalControlsTest extends LiveChannelsTestCase { getInstrumentation().waitForIdleSync(); } - private void assertRatingViewIsChecked(int minAge, int maxAge, int selectedAge, - boolean expectedValue) { + private void assertRatingViewIsChecked( + int minAge, int maxAge, int selectedAge, boolean expectedValue) { for (int age = minAge; age <= maxAge; age++) { - UiObject2 ratingCheckBox = mSidePanelHelper.assertNavigateToItem(String.valueOf(age)) - .findObject(ByResource.id(mTargetResources, R.id.check_box)); + UiObject2 ratingCheckBox = + mSidePanelHelper + .assertNavigateToItem(String.valueOf(age)) + .findObject(ByResource.id(mTargetResources, R.id.check_box)); if (age < selectedAge) { assertTrue("The lower rating age should be unblocked", !ratingCheckBox.isChecked()); } else if (age > selectedAge) { assertTrue("The higher rating age should be blocked", ratingCheckBox.isChecked()); } else { - assertEquals("The rating for age " + selectedAge + " isBlocked ", expectedValue, + assertEquals( + "The rating for age " + selectedAge + " isBlocked ", + expectedValue, ratingCheckBox.isChecked()); } } } /** - * Prepare the need for testRatingDependentSelect. - * 1. Turn on parental control if it's off. - * 2. Make sure Japan rating system is selected. + * Prepare the need for testRatingDependentSelect. 1. Turn on parental control if it's off. 2. + * Make sure Japan rating system is selected. */ private void prepareParentalControl() { showParentalControl(); @@ -107,18 +111,20 @@ public class ParentalControlsTest extends LiveChannelsTestCase { // Show all rating systems. mSidePanelHelper.assertNavigateToItem(R.string.option_program_restrictions); mDevice.pressDPadCenter(); - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.option_program_restrictions); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.option_program_restrictions); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); mSidePanelHelper.assertNavigateToItem(R.string.option_country_rating_systems); mDevice.pressDPadCenter(); bySidePanel = mSidePanelHelper.bySidePanelTitled(R.string.option_country_rating_systems); - assertWaitForCondition(mDevice,Until.hasObject(bySidePanel)); + assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); mSidePanelHelper.assertNavigateToItem(R.string.option_see_all_rating_systems); mDevice.pressDPadCenter(); // Make sure Japan rating system is selected. - UiObject2 ratingSystemCheckBox = mSidePanelHelper.assertNavigateToItem("Japan") - .findObject(ByResource.id(mTargetResources, R.id.check_box)); + UiObject2 ratingSystemCheckBox = + mSidePanelHelper + .assertNavigateToItem("Japan") + .findObject(ByResource.id(mTargetResources, R.id.check_box)); if (!ratingSystemCheckBox.isChecked()) { mDevice.pressDPadCenter(); getInstrumentation().waitForIdleSync(); @@ -147,8 +153,8 @@ public class ParentalControlsTest extends LiveChannelsTestCase { dialogHelper.assertWaitForPinDialogOpen(); dialogHelper.enterPinCodes(); dialogHelper.assertWaitForPinDialogClose(); - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.menu_parental_controls); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.menu_parental_controls); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); } } diff --git a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java index 82c6a810..265e85e4 100644 --- a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java +++ b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java @@ -26,7 +26,6 @@ import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; import android.view.KeyEvent; - import com.android.tv.R; import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.testing.uihelper.DialogHelper; @@ -47,13 +46,11 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { // Tune to a new channel to ensure that the channel is changed. mDevice.pressDPadUp(); getInstrumentation().waitForIdleSync(); - mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_settings); + mBySettingsSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_settings); } - /** - * Test the normal case. The play/pause button should have focus initially. - */ + /** Test the normal case. The play/pause button should have focus initially. */ public void testFocusedViewInNormalCase() { mMenuHelper.showMenu(); mMenuHelper.assertNavigateToPlayControlsRow(); @@ -62,9 +59,8 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { } /** - * Tests the case when the forwarding action is disabled. - * In this case, the button corresponding to the action is disabled, so play/pause button should - * have the focus. + * Tests the case when the forwarding action is disabled. In this case, the button corresponding + * to the action is disabled, so play/pause button should have the focus. */ public void testFocusedViewWithDisabledActionForward() { // Fast forward button @@ -103,8 +99,8 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { dialogHelper.assertWaitForPinDialogOpen(); dialogHelper.enterPinCodes(); dialogHelper.assertWaitForPinDialogClose(); - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.menu_parental_controls); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.menu_parental_controls); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); mDevice.pressEnter(); mDevice.pressEnter(); diff --git a/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java b/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java index 06c76b3b..59df5b9f 100644 --- a/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java +++ b/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java @@ -20,21 +20,17 @@ import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondi import android.support.test.filters.LargeTest; import android.support.test.uiautomator.Until; - import com.android.tv.guide.ProgramGuide; import com.android.tv.testing.uihelper.Constants; -/** - * Tests for {@link ProgramGuide}. - */ +/** Tests for {@link ProgramGuide}. */ @LargeTest public class ProgramGuideTest extends LiveChannelsTestCase { public void testCancel() { mLiveChannelsHelper.assertAppStarted(); mMenuHelper.assertPressProgramGuide(); - assertWaitForCondition(mDevice, - Until.hasObject(Constants.PROGRAM_GUIDE)); + assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE)); mDevice.pressBack(); assertWaitForCondition(mDevice, Until.gone(Constants.PROGRAM_GUIDE)); assertHas(mDevice, Constants.MENU, false); diff --git a/tests/func/src/com/android/tv/tests/ui/TimeoutTest.java b/tests/func/src/com/android/tv/tests/ui/TimeoutTest.java index 9bf2ac50..09695dbd 100644 --- a/tests/func/src/com/android/tv/tests/ui/TimeoutTest.java +++ b/tests/func/src/com/android/tv/tests/ui/TimeoutTest.java @@ -20,14 +20,13 @@ import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondi import android.support.test.filters.LargeTest; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.Constants; /** * Test timeout events like the menu despairing after no input. - *

- * WARNING some of these timeouts are 60 seconds. These tests will take a long time + * + *

WARNING some of these timeouts are 60 seconds. These tests will take a long time * complete. */ @LargeTest @@ -38,16 +37,19 @@ public class TimeoutTest extends LiveChannelsTestCase { mDevice.pressMenu(); assertWaitForCondition(mDevice, Until.hasObject(Constants.MENU)); - assertWaitForCondition(mDevice, Until.gone(Constants.MENU), + assertWaitForCondition( + mDevice, + Until.gone(Constants.MENU), mTargetResources.getInteger(R.integer.menu_show_duration)); } public void testProgramGuide() { mLiveChannelsHelper.assertAppStarted(); mMenuHelper.assertPressProgramGuide(); - assertWaitForCondition(mDevice, - Until.hasObject(Constants.PROGRAM_GUIDE)); - assertWaitForCondition(mDevice, Until.gone(Constants.PROGRAM_GUIDE), + assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE)); + assertWaitForCondition( + mDevice, + Until.gone(Constants.PROGRAM_GUIDE), mTargetResources.getInteger(R.integer.program_guide_show_duration)); assertHas(mDevice, Constants.MENU, false); } diff --git a/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java b/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java index d88e67ad..bc37143f 100644 --- a/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java +++ b/tests/func/src/com/android/tv/tests/ui/dvr/DvrLibraryTest.java @@ -25,14 +25,11 @@ import android.support.test.filters.MediumTest; import android.support.test.filters.SdkSuppress; import android.support.test.uiautomator.By; import android.support.test.uiautomator.BySelector; -import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; import com.android.tv.testing.uihelper.Constants; import com.android.tv.tests.ui.LiveChannelsTestCase; - import java.util.regex.Pattern; @MediumTest @@ -49,11 +46,15 @@ public class DvrLibraryTest extends LiveChannelsTestCase { protected void setUp() throws Exception { super.setUp(); mRecentRow = By.hasDescendant(ByResource.text(mTargetResources, R.string.dvr_main_recent)); - mScheduledRow = By.hasDescendant( - ByResource.text(mTargetResources, R.string.dvr_main_scheduled)); + mScheduledRow = + By.hasDescendant(ByResource.text(mTargetResources, R.string.dvr_main_scheduled)); mSeriesRow = By.hasDescendant(ByResource.text(mTargetResources, R.string.dvr_main_series)); - mFullScheduleCard = By.focusable(true).hasDescendant( - ByResource.text(mTargetResources, R.string.dvr_full_schedule_card_view_title)); + mFullScheduleCard = + By.focusable(true) + .hasDescendant( + ByResource.text( + mTargetResources, + R.string.dvr_full_schedule_card_view_title)); mLiveChannelsHelper.assertAppStarted(); } @@ -81,7 +82,9 @@ public class DvrLibraryTest extends LiveChannelsTestCase { // Empty schedules screen should be shown. assertHas(mDevice, Constants.DVR_SCHEDULES, true); - assertHas(mDevice, ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), + assertHas( + mDevice, + ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), true); // Close the DVR library. @@ -92,26 +95,44 @@ public class DvrLibraryTest extends LiveChannelsTestCase { } public void testScheduleRecordings() { - BySelector newScheduleCard = By.focusable(true).hasDescendant( - By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.textEndsWith("today")); - BySelector seriesCardWithOneSchedule = By.focusable(true).hasDescendant( - By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.text(mTargetResources - .getQuantityString(R.plurals.dvr_count_scheduled_recordings, 1, 1))); - BySelector seriesCardWithOneRecordedProgram = By.focusable(true).hasDescendant( - By.textStartsWith(PROGRAM_NAME_PREFIX)).hasDescendant(By.text(mTargetResources - .getQuantityString(R.plurals.dvr_count_new_recordings, 1, 1))); - Pattern watchButton = Pattern.compile("^" + mTargetResources - .getString(R.string.dvr_detail_watch).toUpperCase() + "\n.*$"); + BySelector newScheduleCard = + By.focusable(true) + .hasDescendant(By.textStartsWith(PROGRAM_NAME_PREFIX)) + .hasDescendant(By.textEndsWith("today")); + BySelector seriesCardWithOneSchedule = + By.focusable(true) + .hasDescendant(By.textStartsWith(PROGRAM_NAME_PREFIX)) + .hasDescendant( + By.text( + mTargetResources.getQuantityString( + R.plurals.dvr_count_scheduled_recordings, 1, 1))); + BySelector seriesCardWithOneRecordedProgram = + By.focusable(true) + .hasDescendant(By.textStartsWith(PROGRAM_NAME_PREFIX)) + .hasDescendant( + By.text( + mTargetResources.getQuantityString( + R.plurals.dvr_count_new_recordings, 1, 1))); + Pattern watchButton = + Pattern.compile( + "^" + + mTargetResources + .getString(R.string.dvr_detail_watch) + .toUpperCase() + + "\n.*$"); mMenuHelper.showMenu(); mMenuHelper.assertNavigateToPlayControlsRow(); mDevice.pressDPadRight(); mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, Until.hasObject( - ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); + assertWaitForCondition( + mDevice, + Until.hasObject( + ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, Until.gone( - ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); + assertWaitForCondition( + mDevice, + Until.gone(ByResource.text(mTargetResources, R.string.dvr_action_record_episode))); mMenuHelper.assertPressDvrLibrary(); assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); @@ -132,7 +153,9 @@ public class DvrLibraryTest extends LiveChannelsTestCase { mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); assertHas(mDevice, Constants.DVR_SCHEDULES, true); - assertHas(mDevice, ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), + assertHas( + mDevice, + ByResource.text(mTargetResources, R.string.dvr_schedules_empty_state), false); assertHas(mDevice, By.textStartsWith(programName), true); @@ -146,11 +169,21 @@ public class DvrLibraryTest extends LiveChannelsTestCase { assertWaitUntilFocused(mDevice, seriesCardWithOneSchedule); mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_view_schedule).toUpperCase()), true); + assertHas( + mDevice, + By.text( + mTargetResources + .getString(R.string.dvr_detail_view_schedule) + .toUpperCase()), + true); assertHas(mDevice, By.text(watchButton), false); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_series_delete).toUpperCase()), false); + assertHas( + mDevice, + By.text( + mTargetResources + .getString(R.string.dvr_detail_series_delete) + .toUpperCase()), + false); // Clicks the new schedule, the detail page should be shown with "Stop recording" button. mDevice.pressBack(); @@ -160,16 +193,22 @@ public class DvrLibraryTest extends LiveChannelsTestCase { assertWaitUntilFocused(mDevice, newScheduleCard); mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_stop_recording).toUpperCase()), true); + assertHas( + mDevice, + By.text( + mTargetResources + .getString(R.string.dvr_detail_stop_recording) + .toUpperCase()), + true); // Stops the recording mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, Until.hasObject( - ByResource.text(mTargetResources, R.string.dvr_action_stop))); + assertWaitForCondition( + mDevice, + Until.hasObject(ByResource.text(mTargetResources, R.string.dvr_action_stop))); mDevice.pressDPadCenter(); - assertWaitForCondition(mDevice, Until.gone( - ByResource.text(mTargetResources, R.string.dvr_action_stop))); + assertWaitForCondition( + mDevice, Until.gone(ByResource.text(mTargetResources, R.string.dvr_action_stop))); assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); assertWaitUntilFocused(mDevice, mFullScheduleCard); @@ -179,10 +218,20 @@ public class DvrLibraryTest extends LiveChannelsTestCase { mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); assertHas(mDevice, By.text(watchButton), true); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_view_schedule).toUpperCase()), true); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_series_delete).toUpperCase()), true); + assertHas( + mDevice, + By.text( + mTargetResources + .getString(R.string.dvr_detail_view_schedule) + .toUpperCase()), + true); + assertHas( + mDevice, + By.text( + mTargetResources + .getString(R.string.dvr_detail_series_delete) + .toUpperCase()), + true); // Moves to the recent row and clicks the recent recorded program. mDevice.pressBack(); @@ -194,15 +243,20 @@ public class DvrLibraryTest extends LiveChannelsTestCase { assertWaitUntilFocused(mDevice, By.focusable(true).hasDescendant(By.text(programName))); mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.gone(Constants.DVR_LIBRARY)); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_watch).toUpperCase()), true); - assertHas(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_delete).toUpperCase()), true); + assertHas( + mDevice, + By.text(mTargetResources.getString(R.string.dvr_detail_watch).toUpperCase()), + true); + assertHas( + mDevice, + By.text(mTargetResources.getString(R.string.dvr_detail_delete).toUpperCase()), + true); // Moves to the delete button and clicks to remove the recorded program. mDevice.pressDPadRight(); - assertWaitUntilFocused(mDevice, By.text(mTargetResources - .getString(R.string.dvr_detail_delete).toUpperCase())); + assertWaitUntilFocused( + mDevice, + By.text(mTargetResources.getString(R.string.dvr_detail_delete).toUpperCase())); mDevice.pressDPadCenter(); assertWaitForCondition(mDevice, Until.hasObject(Constants.DVR_LIBRARY)); assertWaitUntilFocused(mDevice, mFullScheduleCard); diff --git a/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java b/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java index deeb9bfd..35a8abe4 100644 --- a/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java +++ b/tests/func/src/com/android/tv/tests/ui/sidepanel/CustomizeChannelListFragmentTest.java @@ -24,7 +24,6 @@ import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.Constants; import com.android.tv.tests.ui.LiveChannelsTestCase; @@ -45,8 +44,8 @@ public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { pressKeysForChannel(com.android.tv.testing.testinput.TvTestInputConstants.CH_2); // Wait until KeypadChannelSwitchView closes. assertWaitForCondition(mDevice, Until.hasObject(Constants.CHANNEL_BANNER)); - mBySettingsSidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_settings); + mBySettingsSidePanel = + mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_settings); } private void assertShrunkenTvView(boolean shrunkenExpected) { @@ -65,8 +64,9 @@ public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { mSidePanelHelper.assertNavigateToItem( R.string.settings_channel_source_item_customize_channels); mDevice.pressDPadCenter(); - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_edit_channels_for_an_input); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); assertShrunkenTvView(true); @@ -79,8 +79,9 @@ public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { // Back to customize channel list fragment mDevice.pressBack(); - bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_edit_channels_for_an_input); + bySidePanel = + mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); assertShrunkenTvView(true); @@ -97,8 +98,9 @@ public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { mSidePanelHelper.assertNavigateToItem( R.string.settings_channel_source_item_customize_channels); mDevice.pressDPadCenter(); - BySelector bySidePanel = mSidePanelHelper.bySidePanelTitled( - R.string.side_panel_title_edit_channels_for_an_input); + BySelector bySidePanel = + mSidePanelHelper.bySidePanelTitled( + R.string.side_panel_title_edit_channels_for_an_input); assertWaitForCondition(mDevice, Until.hasObject(bySidePanel)); assertShrunkenTvView(true); @@ -110,7 +112,9 @@ public class CustomizeChannelListFragmentTest extends LiveChannelsTestCase { assertShrunkenTvView(true); // Wait for time-out to return to the main menu. - assertWaitForCondition(mDevice, Until.gone(bySidePanel), + assertWaitForCondition( + mDevice, + Until.gone(bySidePanel), mTargetResources.getInteger(R.integer.side_panel_show_duration)); assertShrunkenTvView(false); } diff --git a/tests/input/src/com/android/tv/testinput/TestInputControl.java b/tests/input/src/com/android/tv/testinput/TestInputControl.java index cd85c86e..54845b80 100644 --- a/tests/input/src/com/android/tv/testinput/TestInputControl.java +++ b/tests/input/src/com/android/tv/testinput/TestInputControl.java @@ -21,28 +21,26 @@ import android.net.Uri; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; - import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.ChannelUtils; import com.android.tv.testing.testinput.ChannelState; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.ITestInputControl; - import java.util.Map; /** * Maintains state for the {@link TestTvInputService}. * - *

Maintains the current state for every channel. A default is sent if the state is not + *

Maintains the current state for every channel. A default is sent if the state is not * explicitly set. The state is versioned so TestTvInputService can tell if onNotifyXXX events need * to be sent. * - *

Test update the state using @{link ITestInputControl} via {@link TestInputControlService}. + *

Test update the state using @{link ITestInputControl} via {@link TestInputControlService}. */ class TestInputControl extends ITestInputControl.Stub { - private final static String TAG = "TestInputControl"; - private final static TestInputControl INSTANCE = new TestInputControl(); + private static final String TAG = "TestInputControl"; + private static final TestInputControl INSTANCE = new TestInputControl(); private final LongSparseArray mId2ChannelInfoMap = new LongSparseArray<>(); private final LongSparseArray mOrigId2StateMap = new LongSparseArray<>(); @@ -50,8 +48,7 @@ class TestInputControl extends ITestInputControl.Stub { private java.lang.String mInputId; private boolean initialized; - private TestInputControl() { - } + private TestInputControl() {} public static TestInputControl getInstance() { return INSTANCE; @@ -73,8 +70,13 @@ class TestInputControl extends ITestInputControl.Stub { for (Long channelId : channelIdToInfoMap.keySet()) { mId2ChannelInfoMap.put(channelId, channelIdToInfoMap.get(channelId)); } - Log.i(TAG, "Initialized channel map for " + mInputId + " with " + mId2ChannelInfoMap.size() - + " channels"); + Log.i( + TAG, + "Initialized channel map for " + + mInputId + + " with " + + mId2ChannelInfoMap.size() + + " channels"); } public ChannelInfo getChannelInfo(Uri channelUri) { diff --git a/tests/input/src/com/android/tv/testinput/TestInputControlService.java b/tests/input/src/com/android/tv/testinput/TestInputControlService.java index 4a5668cc..4a1306ed 100644 --- a/tests/input/src/com/android/tv/testinput/TestInputControlService.java +++ b/tests/input/src/com/android/tv/testinput/TestInputControlService.java @@ -20,8 +20,8 @@ import android.content.Intent; import android.os.IBinder; /** - * Testcases communicate to the {@link TestInputControl} via - * {@link com.android.tv.testing.testinput.ITestInputControl}. + * Testcases communicate to the {@link TestInputControl} via {@link + * com.android.tv.testing.testinput.ITestInputControl}. */ public class TestInputControlService extends Service { diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java index 621ceacb..6e9405d8 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputService.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java @@ -41,17 +41,13 @@ import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.Surface; - import com.android.tv.input.TunerHelper; import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.testinput.ChannelState; - import java.util.Date; import java.util.concurrent.TimeUnit; -/** - * Simple TV input service which provides test channels. - */ +/** Simple TV input service which provides test channels. */ public class TestTvInputService extends TvInputService { private static final String TAG = "TestTvInputService"; private static final int REFRESH_DELAY_MS = 1000 / 5; @@ -93,9 +89,7 @@ public class TestTvInputService extends TvInputService { return new SimpleRecordingSessionImpl(this, inputId); } - /** - * Simple session implementation that just display some text. - */ + /** Simple session implementation that just display some text. */ private class SimpleSessionImpl extends Session { private static final int MSG_SEEK = 1000; private static final int SEEK_DELAY_MS = 300; @@ -118,28 +112,36 @@ public class TestTvInputService extends TvInputService { // The current playback speed rate. private float mSpeed; - private final Handler mHandler = new Handler(Looper.myLooper()) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_SEEK) { - // Actually, this input doesn't play any videos, it just shows the image. - // So we should simulate the playback here by changing the current playback - // position periodically in order to test the time shift. - // If the playback is paused, the current playback position doesn't need to be - // changed. - if (mPausedTimeMs == 0) { - long currentTimeMs = System.currentTimeMillis(); - mCurrentPositionMs += (long) ((currentTimeMs - - mLastCurrentPositionUpdateTimeMs) * mSpeed); - mCurrentPositionMs = Math.max(mRecordStartTimeMs, - Math.min(mCurrentPositionMs, currentTimeMs)); - mLastCurrentPositionUpdateTimeMs = currentTimeMs; + private final Handler mHandler = + new Handler(Looper.myLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_SEEK) { + // Actually, this input doesn't play any videos, it just shows the + // image. + // So we should simulate the playback here by changing the current + // playback + // position periodically in order to test the time shift. + // If the playback is paused, the current playback position doesn't need + // to be + // changed. + if (mPausedTimeMs == 0) { + long currentTimeMs = System.currentTimeMillis(); + mCurrentPositionMs += + (long) + ((currentTimeMs - mLastCurrentPositionUpdateTimeMs) + * mSpeed); + mCurrentPositionMs = + Math.max( + mRecordStartTimeMs, + Math.min(mCurrentPositionMs, currentTimeMs)); + mLastCurrentPositionUpdateTimeMs = currentTimeMs; + } + sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS); + } + super.handleMessage(msg); } - sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS); - } - super.handleMessage(msg); - } - }; + }; SimpleSessionImpl(Context context) { super(context); @@ -213,7 +215,8 @@ public class TestTvInputService extends TvInputService { mChannelUri = channelUri; ChannelInfo info = mBackend.getChannelInfo(channelUri); synchronized (mDrawRunnable) { - if (info == null || mChannel == null + if (info == null + || mChannel == null || mChannel.originalNetworkId != info.originalNetworkId) { mCurrentState = null; } @@ -231,8 +234,9 @@ public class TestTvInputService extends TvInputService { Log.i(TAG, "Tuning to " + mChannel); } notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); - mRecordStartTimeMs = mCurrentPositionMs = mLastCurrentPositionUpdateTimeMs - = System.currentTimeMillis(); + mRecordStartTimeMs = + mCurrentPositionMs = + mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis(); mPausedTimeMs = 0; mHandler.sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS); mSpeed = 1; @@ -269,8 +273,8 @@ public class TestTvInputService extends TvInputService { @Override public void onTimeShiftPause() { - mCurrentPositionMs = mPausedTimeMs = mLastCurrentPositionUpdateTimeMs - = System.currentTimeMillis(); + mCurrentPositionMs = + mPausedTimeMs = mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis(); } @Override @@ -283,8 +287,9 @@ public class TestTvInputService extends TvInputService { @Override public void onTimeShiftSeekTo(long timeMs) { mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis(); - mCurrentPositionMs = Math.max(mRecordStartTimeMs, - Math.min(timeMs, mLastCurrentPositionUpdateTimeMs)); + mCurrentPositionMs = + Math.max( + mRecordStartTimeMs, Math.min(timeMs, mLastCurrentPositionUpdateTimeMs)); } @Override @@ -354,14 +359,14 @@ public class TestTvInputService extends TvInputService { } } - private void update(ChannelState oldState, ChannelState newState, - ChannelInfo currentChannel) { + private void update( + ChannelState oldState, ChannelState newState, ChannelInfo currentChannel) { Log.i(TAG, "Updating channel " + currentChannel.number + " state to " + newState); notifyTracksChanged(newState.getTrackInfoList()); if (oldState == null || oldState.getTuneStatus() != newState.getTuneStatus()) { if (newState.getTuneStatus() == ChannelState.TUNE_STATUS_VIDEO_AVAILABLE) { notifyVideoAvailable(); - //TODO handle parental controls. + // TODO handle parental controls. notifyContentAllowed(); setAudioTrack(newState.getSelectedAudioTrackId()); setVideoTrack(newState.getSelectedVideoTrackId()); @@ -379,20 +384,20 @@ public class TestTvInputService extends TvInputService { private class SimpleRecordingSessionImpl extends RecordingSession { private final String[] PROGRAM_PROJECTION = { - Programs.COLUMN_TITLE, - Programs.COLUMN_EPISODE_TITLE, - Programs.COLUMN_SHORT_DESCRIPTION, - Programs.COLUMN_POSTER_ART_URI, - Programs.COLUMN_THUMBNAIL_URI, - Programs.COLUMN_CANONICAL_GENRE, - Programs.COLUMN_CONTENT_RATING, - Programs.COLUMN_START_TIME_UTC_MILLIS, - Programs.COLUMN_END_TIME_UTC_MILLIS, - Programs.COLUMN_VIDEO_WIDTH, - Programs.COLUMN_VIDEO_HEIGHT, - Programs.COLUMN_SEASON_DISPLAY_NUMBER, - Programs.COLUMN_SEASON_TITLE, - Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + Programs.COLUMN_TITLE, + Programs.COLUMN_EPISODE_TITLE, + Programs.COLUMN_SHORT_DESCRIPTION, + Programs.COLUMN_POSTER_ART_URI, + Programs.COLUMN_THUMBNAIL_URI, + Programs.COLUMN_CANONICAL_GENRE, + Programs.COLUMN_CONTENT_RATING, + Programs.COLUMN_START_TIME_UTC_MILLIS, + Programs.COLUMN_END_TIME_UTC_MILLIS, + Programs.COLUMN_VIDEO_WIDTH, + Programs.COLUMN_VIDEO_HEIGHT, + Programs.COLUMN_SEASON_DISPLAY_NUMBER, + Programs.COLUMN_SEASON_TITLE, + Programs.COLUMN_EPISODE_DISPLAY_NUMBER, }; private final String mInputId; @@ -442,8 +447,14 @@ public class TestTvInputService extends TvInputService { long time = System.currentTimeMillis(); if (programHintUri != null) { // Retrieves program info from mProgramHintUri - try (Cursor c = getContentResolver().query(programHintUri, - PROGRAM_PROJECTION, null, null, null)) { + try (Cursor c = + getContentResolver() + .query( + programHintUri, + PROGRAM_PROJECTION, + null, + null, + null)) { if (c != null && c.getCount() > 0) { storeRecordedProgram(c, startTime, endTime); return null; @@ -453,11 +464,19 @@ public class TestTvInputService extends TvInputService { } } // Retrieves the current program - try (Cursor c = getContentResolver().query( - TvContract.buildProgramsUriForChannel(channelUri, startTime, - endTime - startTime < MAX_COMMAND_DELAY ? startTime : - endTime - MAX_COMMAND_DELAY), - PROGRAM_PROJECTION, null, null, null)) { + try (Cursor c = + getContentResolver() + .query( + TvContract.buildProgramsUriForChannel( + channelUri, + startTime, + endTime - startTime < MAX_COMMAND_DELAY + ? startTime + : endTime - MAX_COMMAND_DELAY), + PROGRAM_PROJECTION, + null, + null, + null)) { if (c != null && c.getCount() == 1) { storeRecordedProgram(c, startTime, endTime); return null; @@ -472,10 +491,9 @@ public class TestTvInputService extends TvInputService { private void storeRecordedProgram(Cursor c, long startTime, long endTime) { ContentValues values = new ContentValues(); values.put(RecordedPrograms.COLUMN_INPUT_ID, mInputId); - values.put(RecordedPrograms.COLUMN_CHANNEL_ID, - ContentUris.parseId(channelUri)); - values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, - endTime - startTime); + values.put(RecordedPrograms.COLUMN_CHANNEL_ID, ContentUris.parseId(channelUri)); + values.put( + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - startTime); if (c != null) { int index = 0; c.moveToNext(); @@ -492,15 +510,15 @@ public class TestTvInputService extends TvInputService { values.put(Programs.COLUMN_VIDEO_HEIGHT, c.getLong(index++)); values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, c.getString(index++)); values.put(Programs.COLUMN_SEASON_TITLE, c.getString(index++)); - values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, - c.getString(index++)); + values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, c.getString(index++)); } else { values.put(RecordedPrograms.COLUMN_TITLE, "No program info"); values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime); values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime); } - Uri uri = getContentResolver() - .insert(TvContract.RecordedPrograms.CONTENT_URI, values); + Uri uri = + getContentResolver() + .insert(TvContract.RecordedPrograms.CONTENT_URI, values); notifyRecordingStopped(uri); } }.execute(); diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java index a793ac71..ce18ff81 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java @@ -26,20 +26,16 @@ import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.util.Log; - import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.ChannelUtils; import com.android.tv.testing.Constants; import com.android.tv.testing.ProgramInfo; import com.android.tv.testing.ProgramUtils; - import java.util.ArrayList; import java.util.List; import java.util.Map; -/** - * The setup activity for {@link TestTvInputService}. - */ +/** The setup activity for {@link TestTvInputService}. */ public class TestTvInputSetupActivity extends Activity { private static final String TAG = "TestTvInputSetup"; private String mInputId; @@ -71,34 +67,40 @@ public class TestTvInputSetupActivity extends Activity { ChannelUtils.queryChannelInfoMapForTvInput(context, inputId); for (Long channelId : channelIdToInfoMap.keySet()) { ProgramInfo programInfo = ProgramInfo.create(); - ProgramUtils.populatePrograms(context, TvContract.buildChannelUri(channelId), - programInfo); + ProgramUtils.populatePrograms( + context, TvContract.buildChannelUri(channelId), programInfo); } } public static class MyAlertDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()).setTitle(R.string.simple_setup_title) + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.simple_setup_title) .setMessage(R.string.simple_setup_message) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - // TODO: add UI to ask how many channels - ((TestTvInputSetupActivity) getActivity()) - .registerChannels(Constants.UNIT_TEST_CHANNEL_COUNT); - // Sets the results so that the application can process the - // registered channels properly. - getActivity().setResult(Activity.RESULT_OK); - getActivity().finish(); - } - }).setNegativeButton(android.R.string.cancel, + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + // TODO: add UI to ask how many channels + ((TestTvInputSetupActivity) getActivity()) + .registerChannels(Constants.UNIT_TEST_CHANNEL_COUNT); + // Sets the results so that the application can process the + // registered channels properly. + getActivity().setResult(Activity.RESULT_OK); + getActivity().finish(); + } + }) + .setNegativeButton( + android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { getActivity().finish(); } - }).create(); + }) + .create(); } } } diff --git a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java index 48e485c5..498e25dc 100644 --- a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java +++ b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java @@ -21,22 +21,24 @@ import android.app.Instrumentation; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; - import com.android.tv.testing.Constants; import com.android.tv.testinput.TestTvInputService; import com.android.tv.testinput.TestTvInputSetupActivity; /** - * An instrumentation utility to set up the needed inputs, channels, programs and other settings - * for automated unit tests. + * An instrumentation utility to set up the needed inputs, channels, programs and other settings for + * automated unit tests. + * + *

* - *

{@code
+ * 
{@code
  * adb shell am instrument \
  *   -e testSetupMode {func,jank,unit} \
  *   -w com.android.tv.testinput/.instrument.TestSetupInstrumentation
  * }
* *

Optional arguments are: + * *

  *     -e channelCount number
  * 
@@ -82,23 +84,26 @@ public class TestSetupInstrumentation extends Instrumentation { private void setup() throws TestSetupException { final String testSetupMode = mArguments.getString(TEST_SETUP_MODE_ARG); if (TextUtils.isEmpty(testSetupMode)) { - Log.i(TAG, "Performing no setup actions because " + TEST_SETUP_MODE_ARG - + " was not passed as an argument"); + Log.i( + TAG, + "Performing no setup actions because " + + TEST_SETUP_MODE_ARG + + " was not passed as an argument"); } else { Log.i(TAG, "Running setup for " + testSetupMode + " tests."); int channelCount; switch (testSetupMode) { case "func": - channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG, - Constants.FUNC_TEST_CHANNEL_COUNT); + channelCount = + getArgumentAsInt(CHANNEL_COUNT_ARG, Constants.FUNC_TEST_CHANNEL_COUNT); break; case "jank": - channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG, - Constants.JANK_TEST_CHANNEL_COUNT); + channelCount = + getArgumentAsInt(CHANNEL_COUNT_ARG, Constants.JANK_TEST_CHANNEL_COUNT); break; case "unit": - channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG, - Constants.UNIT_TEST_CHANNEL_COUNT); + channelCount = + getArgumentAsInt(CHANNEL_COUNT_ARG, Constants.UNIT_TEST_CHANNEL_COUNT); break; default: throw new TestSetupException( @@ -114,8 +119,14 @@ public class TestSetupInstrumentation extends Instrumentation { try { return Integer.parseInt(stringValue); } catch (NumberFormatException e) { - Log.w(TAG, "Unable to parse arg " + arg + " with value " + stringValue - + " to a integer.", e); + Log.w( + TAG, + "Unable to parse arg " + + arg + + " with value " + + stringValue + + " to a integer.", + e); } } return defaultValue; diff --git a/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java b/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java index ef936e32..eee2328b 100644 --- a/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/ChannelZappingJankTest.java @@ -19,9 +19,7 @@ import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; -/** - * Jank tests for channel zapping. - */ +/** Jank tests for channel zapping. */ @MediumTest public class ChannelZappingJankTest extends LiveChannelsTestCase { private static final String TAG = "ChannelZappingJankTest"; @@ -29,15 +27,17 @@ public class ChannelZappingJankTest extends LiveChannelsTestCase { private static final String STARTING_CHANNEL = "13"; /** - * The minimum number of frames expected during each jank test. - * If there is less the test will fail. To be safe we loop the action in each test to create - * twice this many frames under normal conditions. - *

At least 100 frams should be chosen so there will be enough frame - * for the 90th, 95th, and 98th percentile measurements are significant. + * The minimum number of frames expected during each jank test. If there is less the test will + * fail. To be safe we loop the action in each test to create twice this many frames under + * normal conditions. + * + *

At least 100 frams should be chosen so there will be enough frame for the 90th, 95th, and + * 98th percentile measurements are significant. * * @see Jank Test Helper Best Practices */ private static final int EXPECTED_FRAMES = 100; + private static final int WARM_UP_CHANNEL_ZAPPING_COUNT = 2; @Override @@ -46,11 +46,10 @@ public class ChannelZappingJankTest extends LiveChannelsTestCase { Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } - @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeTest = "warmChannelZapping") + @JankTest(expectedFrames = EXPECTED_FRAMES, beforeTest = "warmChannelZapping") @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testChannelZapping() { - int frameCountForOneChannelZapping = 40; // measured by hand + int frameCountForOneChannelZapping = 40; // measured by hand int repeat = EXPECTED_FRAMES * 2 / frameCountForOneChannelZapping; for (int i = 0; i < repeat; i++) { mDevice.pressDPadUp(); diff --git a/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java b/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java index 6de01036..507e9dd5 100644 --- a/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java +++ b/tests/jank/src/com/android/tv/tests/jank/LiveChannelsTestCase.java @@ -18,12 +18,9 @@ package com.android.tv.tests.jank; import android.content.res.Resources; import android.support.test.jank.JankTestBase; import android.support.test.uiautomator.UiDevice; - import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper; -/** - * Base test case for LiveChannel jank tests. - */ +/** Base test case for LiveChannel jank tests. */ abstract class LiveChannelsTestCase extends JankTestBase { protected UiDevice mDevice; protected Resources mTargetResources; @@ -34,8 +31,9 @@ abstract class LiveChannelsTestCase extends JankTestBase { super.setUp(); mDevice = UiDevice.getInstance(getInstrumentation()); mTargetResources = getInstrumentation().getTargetContext().getResources(); - mLiveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, mTargetResources, - getInstrumentation().getContext()); + mLiveChannelsHelper = + new LiveChannelsUiDeviceHelper( + mDevice, mTargetResources, getInstrumentation().getContext()); mLiveChannelsHelper.assertAppStarted(); } diff --git a/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java b/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java index 411a0bb9..ea80eb3d 100644 --- a/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/MenuJankTest.java @@ -18,26 +18,25 @@ package com.android.tv.tests.jank; import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; - import com.android.tv.testing.uihelper.MenuHelper; -/** - * Jank tests for the program guide. - */ +/** Jank tests for the program guide. */ @MediumTest public class MenuJankTest extends LiveChannelsTestCase { private static final String STARTING_CHANNEL = "1"; /** - * The minimum number of frames expected during each jank test. - * If there is less the test will fail. To be safe we loop the action in each test to create - * twice this many frames under normal conditions. + * The minimum number of frames expected during each jank test. If there is less the test will + * fail. To be safe we loop the action in each test to create twice this many frames under + * normal conditions. + * *

200 is chosen so there will be enough frame for the 90th, 95th, and 98th percentile * measurements are significant. * * @see Jank Test Helper Best Practices */ private static final int EXPECTED_FRAMES = 200; + protected MenuHelper mMenuHelper; @Override @@ -47,8 +46,7 @@ public class MenuJankTest extends LiveChannelsTestCase { Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } - @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeTest = "fillTheMenuRowWithPreviousChannels") + @JankTest(expectedFrames = EXPECTED_FRAMES, beforeTest = "fillTheMenuRowWithPreviousChannels") @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testShowMenu() { int frames = 40; // measured by hand. diff --git a/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java index d8860dd7..57d38ba9 100644 --- a/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java +++ b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java @@ -21,23 +21,21 @@ import android.support.test.filters.MediumTest; import android.support.test.jank.GfxMonitor; import android.support.test.jank.JankTest; import android.support.test.uiautomator.Until; - import com.android.tv.R; import com.android.tv.testing.uihelper.ByResource; import com.android.tv.testing.uihelper.Constants; import com.android.tv.testing.uihelper.MenuHelper; -/** - * Jank tests for the program guide. - */ +/** Jank tests for the program guide. */ @MediumTest public class ProgramGuideJankTest extends LiveChannelsTestCase { private static final String STARTING_CHANNEL = "13"; /** - * The minimum number of frames expected during each jank test. - * If there is less the test will fail. To be safe we loop the action in each test to create - * twice this many frames under normal conditions. + * The minimum number of frames expected during each jank test. If there is less the test will + * fail. To be safe we loop the action in each test to create twice this many frames under + * normal conditions. + * *

200 is chosen so there will be enough frame for the 90th, 95th, and 98th percentile * measurements are significant. * @@ -54,8 +52,7 @@ public class ProgramGuideJankTest extends LiveChannelsTestCase { Utils.pressKeysForChannelNumber(STARTING_CHANNEL, mDevice); } - @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeTest = "warmProgramGuide") + @JankTest(expectedFrames = EXPECTED_FRAMES, beforeTest = "warmProgramGuide") @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testShowClearProgramGuide() { int frames = 53; // measured by hand @@ -66,24 +63,28 @@ public class ProgramGuideJankTest extends LiveChannelsTestCase { } } - @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeLoop = "showAndFocusProgramGuide", - afterLoop = "clearProgramGuide") + @JankTest( + expectedFrames = EXPECTED_FRAMES, + beforeLoop = "showAndFocusProgramGuide", + afterLoop = "clearProgramGuide" + ) @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testScrollDown() { - int frames = 20; // measured by hand + int frames = 20; // measured by hand int repeat = EXPECTED_FRAMES * 2 / frames; for (int i = 0; i < repeat; i++) { mDevice.pressDPadDown(); } } - @JankTest(expectedFrames = EXPECTED_FRAMES, - beforeLoop = "showAndFocusProgramGuide", - afterLoop = "clearProgramGuide") + @JankTest( + expectedFrames = EXPECTED_FRAMES, + beforeLoop = "showAndFocusProgramGuide", + afterLoop = "clearProgramGuide" + ) @GfxMonitor(processName = Utils.LIVE_CHANNELS_PROCESS_NAME) public void testScrollRight() { - int frames = 30; // measured by hand + int frames = 30; // measured by hand int repeat = EXPECTED_FRAMES * 2 / frames; for (int i = 0; i < repeat; i++) { mDevice.pressDPadRight(); @@ -92,8 +93,8 @@ public class ProgramGuideJankTest extends LiveChannelsTestCase { private void selectProgramGuideMenuItem() { mMenuHelper.showMenu(); - mMenuHelper.assertNavigateToMenuItem(R.string.menu_title_channels, - R.string.channels_item_program_guide); + mMenuHelper.assertNavigateToMenuItem( + R.string.menu_title_channels, R.string.channels_item_program_guide); mDevice.waitForIdle(); } diff --git a/tests/jank/src/com/android/tv/tests/jank/Utils.java b/tests/jank/src/com/android/tv/tests/jank/Utils.java index cd1f7eff..4ad0f643 100644 --- a/tests/jank/src/com/android/tv/tests/jank/Utils.java +++ b/tests/jank/src/com/android/tv/tests/jank/Utils.java @@ -15,19 +15,16 @@ */ package com.android.tv.tests.jank; -import com.android.tv.testing.uihelper.UiDeviceUtils; - import android.support.test.uiautomator.UiDevice; +import com.android.tv.testing.uihelper.UiDeviceUtils; public final class Utils { /** Live TV process name */ public static final String LIVE_CHANNELS_PROCESS_NAME = "com.android.tv"; - private Utils() { } + private Utils() {} - /** - * Presses channel number to tune to {@code channel}. - */ + /** Presses channel number to tune to {@code channel}. */ public static void pressKeysForChannelNumber(String channel, UiDevice uiDevice) { UiDeviceUtils.pressKeys(uiDevice, channel); uiDevice.pressDPadCenter(); diff --git a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java index e6f1af7e..4e280d1a 100644 --- a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java +++ b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java @@ -21,7 +21,6 @@ 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.testing.ChannelInfo; @@ -29,15 +28,11 @@ import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TestInputControlConnection; import com.android.tv.testing.testinput.TestInputControlUtils; import com.android.tv.testing.testinput.TvTestInputConstants; - +import java.util.List; import org.junit.Before; import org.junit.Rule; -import java.util.List; - -/** - * Base TestCase for tests that need a {@link MainActivity}. - */ +/** Base TestCase for tests that need a {@link MainActivity}. */ public abstract class BaseMainActivityTestCase { private static final String TAG = "BaseMainActivityTest"; private static final int CHANNEL_LOADING_CHECK_INTERVAL_MS = 10; @@ -54,8 +49,11 @@ public abstract class BaseMainActivityTestCase { public void setUp() { mActivity = mActivityTestRule.getActivity(); // TODO: ensure the SampleInputs are setup. - getInstrumentation().getTargetContext() - .bindService(TestInputControlUtils.createIntent(), mConnection, + getInstrumentation() + .getTargetContext() + .bindService( + TestInputControlUtils.createIntent(), + mConnection, Context.BIND_AUTO_CREATE); } @@ -73,17 +71,17 @@ public abstract class BaseMainActivityTestCase { */ protected void tuneToChannel(final Channel channel) { // Run on UI thread so views can be modified - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mActivity.tuneToChannel(channel); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mActivity.tuneToChannel(channel); + } + }); } - /** - * Sleep until @{@link ChannelDataManager#isDbLoadFinished()} is true. - */ + /** Sleep until @{@link ChannelDataManager#isDbLoadFinished()} is true. */ protected void waitUntilChannelLoadingFinish() { ChannelDataManager channelDataManager = mActivity.getChannelDataManager(); while (!channelDataManager.isDbLoadFinished()) { @@ -102,9 +100,7 @@ public abstract class BaseMainActivityTestCase { tuneToChannel(c); } - /** - * Tune to channel. - */ + /** Tune to channel. */ protected void tuneToChannel(ChannelInfo channel) { tuneToChannel(channel.name); } @@ -112,13 +108,14 @@ public abstract class BaseMainActivityTestCase { /** * Update the channel state to {@code data} then tune to that channel. * - * @param data the state to update the channel with. + * @param data the state to update the channel with. * @param channel the channel to tune to */ protected void updateThenTune(ChannelStateData data, ChannelInfo channel) { if (channel.equals(TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY)) { throw new IllegalArgumentException( - "By convention " + TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY.name + "By convention " + + TvTestInputConstants.CH_1_DEFAULT_DONT_MODIFY.name + " should not be modified."); } mConnection.updateChannelState(channel, data); @@ -128,7 +125,7 @@ public abstract class BaseMainActivityTestCase { private Channel findChannelWithName(String displayName) { waitUntilChannelLoadingFinish(); Channel channel = null; - List channelList = mActivity.getChannelDataManager().getChannelList(); + List channelList = mActivity.getChannelDataManager().getChannelList(); for (Channel c : channelList) { if (TextUtils.equals(c.getDisplayName(), displayName)) { channel = c; diff --git a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java index f2917181..6a48e635 100644 --- a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java +++ b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertNotSame; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.MediumTest; - import org.junit.Before; import org.junit.Test; @@ -77,8 +76,8 @@ public class CurrentPositionMediatorTest extends BaseMainActivityTestCase { assertCurrentPositionMediator(INVALID_TIME, newCurrentTimeMs); } - private void assertCurrentPositionMediator(long expectedSeekRequestTimeMs, - long expectedCurrentPositionMs) { + private void assertCurrentPositionMediator( + long expectedSeekRequestTimeMs, long expectedCurrentPositionMs) { assertEquals("Seek request time", expectedSeekRequestTimeMs, mMediator.mSeekRequestTimeMs); assertEquals("Current position", expectedCurrentPositionMs, mMediator.mCurrentPositionMs); } diff --git a/tests/unit/src/com/android/tv/FeaturesTest.java b/tests/unit/src/com/android/tv/FeaturesTest.java index 9d61e757..b6be6866 100644 --- a/tests/unit/src/com/android/tv/FeaturesTest.java +++ b/tests/unit/src/com/android/tv/FeaturesTest.java @@ -19,12 +19,9 @@ package com.android.tv; import static org.junit.Assert.assertFalse; import android.support.test.filters.SmallTest; - import org.junit.Test; -/** - * Test for features. - */ +/** Test for features. */ @SmallTest public class FeaturesTest { @Test diff --git a/tests/unit/src/com/android/tv/MainActivityTest.java b/tests/unit/src/com/android/tv/MainActivityTest.java index 15805032..00aa79d8 100644 --- a/tests/unit/src/com/android/tv/MainActivityTest.java +++ b/tests/unit/src/com/android/tv/MainActivityTest.java @@ -22,18 +22,13 @@ import static org.junit.Assert.assertTrue; import android.support.test.filters.MediumTest; import android.view.View; import android.widget.TextView; - import com.android.tv.data.Channel; import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.ui.ChannelBannerView; - -import org.junit.Test; - import java.util.List; +import org.junit.Test; -/** - * Tests for {@link MainActivity}. - */ +/** Tests for {@link MainActivity}. */ @MediumTest public class MainActivityTest extends BaseMainActivityTestCase { @Test @@ -61,12 +56,14 @@ public class MainActivityTest extends BaseMainActivityTestCase { private void showProgramGuide() { // Run on UI thread so views can be modified - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mActivity.getOverlayManager().showProgramGuide(); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mActivity.getOverlayManager().showProgramGuide(); + } + }); } private void assertChannelName(String displayName) { @@ -83,10 +80,11 @@ public class MainActivityTest extends BaseMainActivityTestCase { return (ChannelBannerView) v; } - private View assertExpectedBannerSceneClassShown(Class expectedClass, - boolean expectedShown) { - View v = assertViewIsShown(expectedClass.getSimpleName(), R.id.scene_transition_common, - expectedShown); + private View assertExpectedBannerSceneClassShown( + Class expectedClass, boolean expectedShown) { + View v = + assertViewIsShown( + expectedClass.getSimpleName(), R.id.scene_transition_common, expectedShown); if (v != null) { assertEquals(expectedClass, v.getClass()); } diff --git a/tests/unit/src/com/android/tv/TimeShiftManagerTest.java b/tests/unit/src/com/android/tv/TimeShiftManagerTest.java index 052b5d19..7ad6bdbf 100644 --- a/tests/unit/src/com/android/tv/TimeShiftManagerTest.java +++ b/tests/unit/src/com/android/tv/TimeShiftManagerTest.java @@ -25,7 +25,6 @@ import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND; import static org.junit.Assert.assertEquals; import android.support.test.filters.MediumTest; - import org.junit.Before; import org.junit.Test; @@ -85,19 +84,36 @@ public class TimeShiftManagerTest extends BaseMainActivityTestCase { mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled); } - private void assertActionState(boolean playEnabled, boolean pauseEnabled, boolean rewindEnabled, - boolean fastForwardEnabled, boolean jumpToPreviousEnabled, boolean jumpToNextEnabled) { - assertEquals("Play Action", playEnabled, + private void assertActionState( + boolean playEnabled, + boolean pauseEnabled, + boolean rewindEnabled, + boolean fastForwardEnabled, + boolean jumpToPreviousEnabled, + boolean jumpToNextEnabled) { + assertEquals( + "Play Action", + playEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY)); - assertEquals("Pause Action", pauseEnabled, + assertEquals( + "Pause Action", + pauseEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE)); - assertEquals("Rewind Action", rewindEnabled, + assertEquals( + "Rewind Action", + rewindEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)); - assertEquals("Fast Forward Action", fastForwardEnabled, + assertEquals( + "Fast Forward Action", + fastForwardEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)); - assertEquals("Jump To Previous Action", jumpToPreviousEnabled, + assertEquals( + "Jump To Previous Action", + jumpToPreviousEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)); - assertEquals("Jump To Next Action", jumpToNextEnabled, + assertEquals( + "Jump To Next Action", + jumpToNextEnabled, mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)); } } diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java index 7a4a4982..0adfd1be 100644 --- a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java @@ -39,28 +39,25 @@ import android.test.mock.MockCursor; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; - import com.android.tv.testing.ChannelInfo; import com.android.tv.testing.Constants; import com.android.tv.util.TvInputManagerHelper; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Matchers; -import org.mockito.Mockito; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.Mockito; /** * Test for {@link ChannelDataManager} * - * A test method may include tests for multiple methods to minimize the DB access. - * Note that all the methods of {@link ChannelDataManager} should be called from the UI thread. + *

A test method may include tests for multiple methods to minimize the DB access. Note that all + * the methods of {@link ChannelDataManager} should be called from the UI thread. */ @SmallTest public class ChannelDataManagerTest { @@ -86,46 +83,57 @@ public class ChannelDataManagerTest { mContentResolver = new FakeContentResolver(); mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); mListener = new TestChannelDataManagerListener(); - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class); - Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())).thenReturn(true); - mChannelDataManager = new ChannelDataManager(getTargetContext(), mockHelper, - mContentResolver); - mChannelDataManager.addListener(mListener); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + TvInputManagerHelper mockHelper = + Mockito.mock(TvInputManagerHelper.class); + Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())) + .thenReturn(true); + mChannelDataManager = + new ChannelDataManager( + getTargetContext(), mockHelper, mContentResolver); + mChannelDataManager.addListener(mListener); + } + }); } @After public void tearDown() { - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mChannelDataManager.stop(); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mChannelDataManager.stop(); + } + }); } private void startAndWaitForComplete() throws InterruptedException { - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mChannelDataManager.start(); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mChannelDataManager.start(); + } + }); assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); } private void restart() throws InterruptedException { - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mChannelDataManager.stop(); - mListener.reset(); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mChannelDataManager.stop(); + mListener.reset(); + } + }); startAndWaitForComplete(); } @@ -136,10 +144,8 @@ public class ChannelDataManagerTest { } /** - * Test for following methods - * - {@link ChannelDataManager#getChannelCount} - * - {@link ChannelDataManager#getChannelList} - * - {@link ChannelDataManager#getChannel} + * Test for following methods - {@link ChannelDataManager#getChannelCount} - {@link + * ChannelDataManager#getChannelList} - {@link ChannelDataManager#getChannel} */ @Test public void testGetChannels() throws InterruptedException { @@ -173,9 +179,7 @@ public class ChannelDataManagerTest { } } - /** - * Test for {@link ChannelDataManager#getChannelCount} when no channel is available. - */ + /** Test for {@link ChannelDataManager#getChannelCount} when no channel is available. */ @Test public void testGetChannels_noChannels() throws InterruptedException { mContentProvider.clear(); @@ -184,9 +188,8 @@ public class ChannelDataManagerTest { } /** - * Test for following methods and channel listener with notifying change. - * - {@link ChannelDataManager#updateBrowsable} - * - {@link ChannelDataManager#applyUpdatedValuesToDb} + * Test for following methods and channel listener with notifying change. - {@link + * ChannelDataManager#updateBrowsable} - {@link ChannelDataManager#applyUpdatedValuesToDb} */ @Test public void testBrowsable() throws InterruptedException { @@ -226,9 +229,8 @@ public class ChannelDataManagerTest { } /** - * Test for following methods and channel listener without notifying change. - * - {@link ChannelDataManager#updateBrowsable} - * - {@link ChannelDataManager#applyUpdatedValuesToDb} + * Test for following methods and channel listener without notifying change. - {@link + * ChannelDataManager#updateBrowsable} - {@link ChannelDataManager#applyUpdatedValuesToDb} */ @Test public void testBrowsable_skipNotification() throws InterruptedException { @@ -263,9 +265,8 @@ public class ChannelDataManagerTest { } /** - * Test for following methods and channel listener. - * - {@link ChannelDataManager#updateLocked} - * - {@link ChannelDataManager#applyUpdatedValuesToDb} + * Test for following methods and channel listener. - {@link ChannelDataManager#updateLocked} - + * {@link ChannelDataManager#applyUpdatedValuesToDb} */ @Test public void testLocked() throws InterruptedException { @@ -295,9 +296,7 @@ public class ChannelDataManagerTest { mChannelDataManager.updateLocked(channel.getId(), false); } - /** - * Test ChannelDataManager when channels in TvContract are updated, removed, or added. - */ + /** Test ChannelDataManager when channels in TvContract are updated, removed, or added. */ @Test public void testChannelListChanged() throws InterruptedException { startAndWaitForComplete(); @@ -329,8 +328,7 @@ public class ChannelDataManagerTest { assertEquals(testChannelId, updatedChannel.getId()); assertEquals(testChannelInfo.number, updatedChannel.getDisplayNumber()); assertEquals(newName, updatedChannel.getDisplayName()); - assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1, - mChannelDataManager.getChannelCount()); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1, mChannelDataManager.getChannelCount()); // Test channel remove. mListener.reset(); @@ -352,6 +350,7 @@ public class ChannelDataManagerTest { public ChannelInfo channelInfo; public boolean browsable; public boolean locked; + public ChannelInfoWrapper(ChannelInfo channelInfo) { this.channelInfo = channelInfo; browsable = true; @@ -366,8 +365,14 @@ public class ChannelDataManagerTest { public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { super.notifyChange(uri, observer, syncToNetwork); if (DEBUG) { - Log.d(TAG, "onChanged(uri=" + uri + ", observer=" + observer + ") - Notification " - + (mNotifyDisabled ? "disabled" : "enabled")); + Log.d( + TAG, + "onChanged(uri=" + + uri + + ", observer=" + + observer + + ") - Notification " + + (mNotifyDisabled ? "disabled" : "enabled")); } if (mNotifyDisabled) { return; @@ -390,19 +395,23 @@ public class ChannelDataManagerTest { public FakeContentProvider(Context context) { super(context); for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { - mChannelInfoList.put(i, - new ChannelInfoWrapper(ChannelInfo.create(getTargetContext(), i))); + mChannelInfoList.put( + i, new ChannelInfoWrapper(ChannelInfo.create(getTargetContext(), i))); } } /** - * Implementation of {@link ContentProvider#query}. - * This assumes that {@link ChannelDataManager} queries channels - * with empty {@code selection}. (i.e. channels are always queries for all) + * Implementation of {@link ContentProvider#query}. This assumes that {@link + * ChannelDataManager} queries channels with empty {@code selection}. (i.e. channels are + * always queries for all) */ @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] - selectionArgs, String sortOrder) { + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { if (DEBUG) { Log.d(TAG, "dump query"); Log.d(TAG, " uri=" + uri); @@ -414,9 +423,8 @@ public class ChannelDataManagerTest { } /** - * Implementation of {@link ContentProvider#update}. - * This assumes that {@link ChannelDataManager} update channels - * only for changing browsable and locked. + * Implementation of {@link ContentProvider#update}. This assumes that {@link + * ChannelDataManager} update channels only for changing browsable and locked. */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { @@ -434,8 +442,9 @@ public class ChannelDataManagerTest { } } else { // See {@link Utils#buildSelectionForIds} for the syntax. - String selectionForId = selection.substring( - selection.indexOf("(") + 1, selection.lastIndexOf(")")); + String selectionForId = + selection.substring( + selection.indexOf("(") + 1, selection.lastIndexOf(")")); String[] ids = selectionForId.split(", "); if (ids != null) { for (String id : ids) { @@ -476,27 +485,25 @@ public class ChannelDataManagerTest { } /** - * Simulates channel data insert. - * This assigns original network ID (the same with channel number) to channel ID. + * Simulates channel data insert. This assigns original network ID (the same with channel + * number) to channel ID. */ public void simulateInsert(ChannelInfo testChannelInfo) { long channelId = testChannelInfo.originalNetworkId; - mChannelInfoList.put((int) channelId, new ChannelInfoWrapper( - ChannelInfo.create(getTargetContext(), (int) channelId))); + mChannelInfoList.put( + (int) channelId, + new ChannelInfoWrapper( + ChannelInfo.create(getTargetContext(), (int) channelId))); mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); } - /** - * Simulates channel data delete. - */ + /** Simulates channel data delete. */ public void simulateDelete(long channelId) { mChannelInfoList.remove((int) channelId); mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); } - /** - * Simulates channel data update. - */ + /** Simulates channel data update. */ public void simulateUpdate(long channelId, String newName) { ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId); ChannelInfo.Builder builder = new ChannelInfo.Builder(channel.channelInfo); @@ -506,7 +513,8 @@ public class ChannelDataManagerTest { } private void assertChannelUri(Uri uri) { - assertTrue("Uri(" + uri + ") isn't channel uri", + assertTrue( + "Uri(" + uri + ") isn't channel uri", uri.toString().startsWith(Channels.CONTENT_URI.toString())); } @@ -528,15 +536,16 @@ public class ChannelDataManagerTest { } private class FakeCursor extends MockCursor { - private final String[] ALL_COLUMNS = { - Channels._ID, - Channels.COLUMN_DISPLAY_NAME, - Channels.COLUMN_DISPLAY_NUMBER, - Channels.COLUMN_INPUT_ID, - Channels.COLUMN_VIDEO_FORMAT, - Channels.COLUMN_ORIGINAL_NETWORK_ID, - COLUMN_BROWSABLE, - COLUMN_LOCKED}; + private final String[] ALL_COLUMNS = { + Channels._ID, + Channels.COLUMN_DISPLAY_NAME, + Channels.COLUMN_DISPLAY_NUMBER, + Channels.COLUMN_INPUT_ID, + Channels.COLUMN_VIDEO_FORMAT, + Channels.COLUMN_ORIGINAL_NETWORK_ID, + COLUMN_BROWSABLE, + COLUMN_LOCKED + }; private final String[] mColumns; private int mPosition; diff --git a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java index 827dcdbd..86d27efa 100644 --- a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java @@ -20,27 +20,21 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import android.support.test.filters.SmallTest; - import com.android.tv.testing.ComparableTester; - import org.junit.Test; -/** - * Tests for {@link ChannelNumber}. - */ +/** Tests for {@link ChannelNumber}. */ @SmallTest public class ChannelNumberTest { - /** - * Test method for {@link ChannelNumber#ChannelNumber()}. - */ + /** Test method for {@link ChannelNumber#ChannelNumber()}. */ @Test public void testChannelNumber() { assertChannelEquals(new ChannelNumber(), "", false, ""); } /** - * Test method for - * {@link com.android.tv.data.ChannelNumber#parseChannelNumber(java.lang.String)}. + * Test method for {@link + * com.android.tv.data.ChannelNumber#parseChannelNumber(java.lang.String)}. */ @Test public void testParseChannelNumber() { @@ -55,9 +49,7 @@ public class ChannelNumberTest { assertChannelEquals(parseChannelNumber("5-6"), "5", true, "6"); } - /** - * Test method for {@link ChannelNumber#compareTo(com.android.tv.data.ChannelNumber)}. - */ + /** Test method for {@link ChannelNumber#compareTo(com.android.tv.data.ChannelNumber)}. */ @Test public void testCompareTo() { new ComparableTester() @@ -73,9 +65,7 @@ public class ChannelNumberTest { .test(); } - /** - * Test method for {@link ChannelNumber#compare(java.lang.String, java.lang.String)}. - */ + /** Test method for {@link ChannelNumber#compare(java.lang.String, java.lang.String)}. */ @Test public void testCompare() { // Only need to test nulls, the reset is tested by testCompareTo @@ -87,11 +77,13 @@ public class ChannelNumberTest { assertEquals("compareTo(1,null)>0", true, ChannelNumber.compare("1", null) > 0); } - private void assertChannelEquals(ChannelNumber actual, String expectedMajor, - boolean expectedHasDelimiter, String expectedMinor) { + private void assertChannelEquals( + ChannelNumber actual, + String expectedMajor, + boolean expectedHasDelimiter, + String expectedMinor) { assertEquals(actual + " major", actual.majorNumber, expectedMajor); assertEquals(actual + " hasDelimiter", actual.hasDelimiter, expectedHasDelimiter); assertEquals(actual + " minor", actual.minorNumber, expectedMinor); } - } diff --git a/tests/unit/src/com/android/tv/data/ChannelTest.java b/tests/unit/src/com/android/tv/data/ChannelTest.java index d270e277..a7bac9fb 100644 --- a/tests/unit/src/com/android/tv/data/ChannelTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelTest.java @@ -25,10 +25,9 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.test.filters.SmallTest; - import com.android.tv.testing.ComparatorTester; import com.android.tv.util.TvInputManagerHelper; - +import java.util.Comparator; import org.junit.Before; import org.junit.Test; import org.mockito.Matchers; @@ -36,25 +35,19 @@ import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.util.Comparator; - -/** - * Tests for {@link Channel}. - */ +/** Tests for {@link Channel}. */ @SmallTest public class ChannelTest { // 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"; + private static final String INVALID_TV_INPUT_PACKAGE_NAME = "com.android.tv.invalid_tv_input"; // Used for testing TV inputs defined inside of Live TV. private static final String LIVE_CHANNELS_PACKAGE_NAME = "com.android.tv"; // Used for testing a TV input which doesn't have its leanback launcher activity. private static final String NONE_LEANBACK_TV_INPUT_PACKAGE_NAME = "com.android.tv.none_leanback_tv_input"; // Used for testing a TV input which has its leanback launcher activity. - private static final String LEANBACK_TV_INPUT_PACKAGE_NAME = - "com.android.tv.leanback_tv_input"; + private static final String LEANBACK_TV_INPUT_PACKAGE_NAME = "com.android.tv.leanback_tv_input"; private static final String TEST_APP_LINK_TEXT = "test_app_link_text"; private static final String PARTNER_INPUT_ID = "partner"; private static final ActivityInfo TEST_ACTIVITY_INFO = new ActivityInfo(); @@ -77,28 +70,47 @@ public class ChannelTest { new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test")); PackageManager mockPackageManager = Mockito.mock(PackageManager.class); - Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage( - INVALID_TV_INPUT_PACKAGE_NAME)).thenReturn(null); - Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage( - LIVE_CHANNELS_PACKAGE_NAME)).thenReturn(liveChannelsIntent); - Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage( - NONE_LEANBACK_TV_INPUT_PACKAGE_NAME)).thenReturn(null); - Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage( - LEANBACK_TV_INPUT_PACKAGE_NAME)).thenReturn(leanbackTvInputIntent); + Mockito.when( + mockPackageManager.getLeanbackLaunchIntentForPackage( + INVALID_TV_INPUT_PACKAGE_NAME)) + .thenReturn(null); + Mockito.when( + mockPackageManager.getLeanbackLaunchIntentForPackage( + LIVE_CHANNELS_PACKAGE_NAME)) + .thenReturn(liveChannelsIntent); + Mockito.when( + mockPackageManager.getLeanbackLaunchIntentForPackage( + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME)) + .thenReturn(null); + Mockito.when( + mockPackageManager.getLeanbackLaunchIntentForPackage( + LEANBACK_TV_INPUT_PACKAGE_NAME)) + .thenReturn(leanbackTvInputIntent); // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo(). - Mockito.doAnswer(new Answer() { - @Override - public ActivityInfo answer(InvocationOnMock invocation) { - // We only check the package name, since the class name can be changed - // when an intent is changed to an uri and created from the uri. - // (ex, ".className" -> "packageName.className") - return mValidIntent.getComponent().getPackageName().equals( - ((ComponentName)invocation.getArguments()[0]).getPackageName()) - ? TEST_ACTIVITY_INFO : null; - } - }).when(mockPackageManager).getActivityInfo(Mockito.any(), Mockito.anyInt()); + Mockito.doAnswer( + new Answer() { + @Override + public ActivityInfo answer(InvocationOnMock invocation) { + // We only check the package name, since the class name can be + // changed + // when an intent is changed to an uri and created from the uri. + // (ex, ".className" -> "packageName.className") + return mValidIntent + .getComponent() + .getPackageName() + .equals( + ((ComponentName) + invocation + .getArguments()[0]) + .getPackageName()) + ? TEST_ACTIVITY_INFO + : null; + } + }) + .when(mockPackageManager) + .getActivityInfo(Mockito.any(), Mockito.anyInt()); mMockContext = Mockito.mock(Context.class); Mockito.when(mMockContext.getApplicationContext()).thenReturn(mMockContext); @@ -110,98 +122,141 @@ public class ChannelTest { public void testGetAppLinkType_NoText_NoIntent() { assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, null); assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, null); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, - null); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); } public void testGetAppLinkType_NoText_InvalidIntent() { - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, - mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, - mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, - mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, + null, mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); } public void testGetAppLinkType_NoText_ValidIntent() { - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, - mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, - mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, - mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, + null, mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mValidIntent); } public void testGetAppLinkType_HasText_NoIntent() { - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, null); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, - TEST_APP_LINK_TEXT, null); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, null); - assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, null); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + INVALID_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + null); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + null); + assertAppLinkType( + Channel.APP_LINK_TYPE_APP, + LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + null); } public void testGetAppLinkType_HasText_InvalidIntent() { - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mInvalidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + INVALID_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + LIVE_CHANNELS_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_NONE, + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mInvalidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_APP, + LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mInvalidIntent); } public void testGetAppLinkType_HasText_ValidIntent() { - assertAppLinkType(Channel.APP_LINK_TYPE_CHANNEL, INVALID_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_CHANNEL, LIVE_CHANNELS_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_CHANNEL, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mValidIntent); - assertAppLinkType(Channel.APP_LINK_TYPE_CHANNEL, LEANBACK_TV_INPUT_PACKAGE_NAME, - TEST_APP_LINK_TEXT, mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_CHANNEL, + INVALID_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_CHANNEL, + LIVE_CHANNELS_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_CHANNEL, + NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mValidIntent); + assertAppLinkType( + Channel.APP_LINK_TYPE_CHANNEL, + LEANBACK_TV_INPUT_PACKAGE_NAME, + TEST_APP_LINK_TEXT, + mValidIntent); } - private void assertAppLinkType(int expectedType, String inputPackageName, String appLinkText, - Intent appLinkIntent) { + 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 // used when the URI is created. - Channel testChannel = new Channel.Builder() - .setPackageName(inputPackageName) - .setAppLinkText(appLinkText) - .setAppLinkIntentUri(appLinkIntent == null ? null : appLinkIntent.toUri( - Intent.URI_INTENT_SCHEME)) - .build(); - assertEquals("Unexpected app-link type for for " + testChannel, - expectedType, testChannel.getAppLinkType(mMockContext)); + Channel testChannel = + new Channel.Builder() + .setPackageName(inputPackageName) + .setAppLinkText(appLinkText) + .setAppLinkIntentUri( + appLinkIntent == null + ? null + : appLinkIntent.toUri(Intent.URI_INTENT_SCHEME)) + .build(); + assertEquals( + "Unexpected app-link type for for " + testChannel, + expectedType, + testChannel.getAppLinkType(mMockContext)); } public void testComparator() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); - Mockito.when(manager.isPartnerInput(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - String inputId = (String) invocation.getArguments()[0]; - return PARTNER_INPUT_ID.equals(inputId); - } - }); + Mockito.when(manager.isPartnerInput(Matchers.anyString())) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + return PARTNER_INPUT_ID.equals(inputId); + } + }); Comparator comparator = new TestChannelComparator(manager); - ComparatorTester comparatorTester = - ComparatorTester.withoutEqualsTest(comparator); + ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( new Channel.Builder().setInputId(PARTNER_INPUT_ID).build()); - comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("1").build()); + comparatorTester.addComparableGroup(new Channel.Builder().setInputId("1").build()); comparatorTester.addComparableGroup( new Channel.Builder().setInputId("1").setDisplayNumber("2").build()); comparatorTester.addComparableGroup( @@ -209,12 +264,21 @@ public class ChannelTest { // display name does not affect comparator comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("2").setDisplayNumber("1.62") - .setDisplayName("test1").build(), - new Channel.Builder().setInputId("2").setDisplayNumber("1.62") - .setDisplayName("test2").build(), - new Channel.Builder().setInputId("2").setDisplayNumber("1.62") - .setDisplayName("test3").build()); + new Channel.Builder() + .setInputId("2") + .setDisplayNumber("1.62") + .setDisplayName("test1") + .build(), + new Channel.Builder() + .setInputId("2") + .setDisplayNumber("1.62") + .setDisplayName("test2") + .build(), + new Channel.Builder() + .setInputId("2") + .setDisplayNumber("1.62") + .setDisplayName("test3") + .build()); comparatorTester.addComparableGroup( new Channel.Builder().setInputId("2").setDisplayNumber("2.0").build()); // Numeric display number sorting @@ -226,22 +290,22 @@ public class ChannelTest { /** * Test Input Label handled by {@link com.android.tv.data.Channel.DefaultComparator}. * - *

Sort partner inputs first, then sort by input label, then by input id. - * See b/23031603. + *

Sort partner inputs first, then sort by input label, then by input id. See b/23031603. */ public void testComparatorLabel() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); - Mockito.when(manager.isPartnerInput(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - String inputId = (String) invocation.getArguments()[0]; - return PARTNER_INPUT_ID.equals(inputId); - } - }); + Mockito.when(manager.isPartnerInput(Matchers.anyString())) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + return PARTNER_INPUT_ID.equals(inputId); + } + }); Comparator comparator = new ChannelComparatorWithDescriptionAsLabel(manager); - ComparatorTester comparatorTester = - ComparatorTester.withoutEqualsTest(comparator); + ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( new Channel.Builder().setInputId(PARTNER_INPUT_ID).setDescription("A").build()); diff --git a/tests/unit/src/com/android/tv/data/GenreItemTest.java b/tests/unit/src/com/android/tv/data/GenreItemTest.java index fdbcb599..dbf99eac 100644 --- a/tests/unit/src/com/android/tv/data/GenreItemTest.java +++ b/tests/unit/src/com/android/tv/data/GenreItemTest.java @@ -25,12 +25,9 @@ import static org.junit.Assert.assertTrue; import android.media.tv.TvContract.Programs.Genres; import android.os.Build; import android.support.test.filters.SmallTest; - import org.junit.Test; -/** - * Tests for {@link Channel}. - */ +/** Tests for {@link Channel}. */ @SmallTest public class GenreItemTest { private static final String INVALID_GENRE = "INVALID GENRE"; diff --git a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java index 5457051f..2b75ee4b 100644 --- a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java @@ -35,25 +35,20 @@ import android.test.mock.MockContentResolver; import android.test.mock.MockCursor; import android.util.Log; import android.util.SparseArray; - import com.android.tv.testing.Constants; import com.android.tv.testing.FakeClock; import com.android.tv.testing.ProgramInfo; import com.android.tv.util.Utils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; -/** - * Test for {@link com.android.tv.data.ProgramDataManager} - */ +/** Test for {@link com.android.tv.data.ProgramDataManager} */ @SmallTest public class ProgramDataManagerTest { private static final boolean DEBUG = false; @@ -85,8 +80,8 @@ public class ProgramDataManagerTest { mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); - mProgramDataManager = new ProgramDataManager( - mContentResolver, mClock, mHandlerThread.getLooper()); + mProgramDataManager = + new ProgramDataManager(mContentResolver, mClock, mHandlerThread.getLooper()); mProgramDataManager.setPrefetchEnabled(true); mProgramDataManager.addListener(mListener); } @@ -102,9 +97,7 @@ public class ProgramDataManagerTest { assertTrue(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); } - /** - * Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. - */ + /** Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. */ @Test public void testProgramUtils() { ProgramInfo stub = ProgramInfo.create(); @@ -120,11 +113,9 @@ public class ProgramDataManagerTest { /** * Test for following methods. * - *

- * {@link ProgramDataManager#getCurrentProgram(long)}, - * {@link ProgramDataManager#getPrograms(long, long)}, - * {@link ProgramDataManager#setPrefetchTimeRange(long)}. - *

+ *

{@link ProgramDataManager#getCurrentProgram(long)}, {@link + * ProgramDataManager#getPrograms(long, long)}, {@link + * ProgramDataManager#setPrefetchTimeRange(long)}. */ @Test public void testGetPrograms() throws InterruptedException { @@ -139,8 +130,9 @@ public class ProgramDataManagerTest { for (long channelId = 1; channelId <= Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) { Program currentProgram = mProgramDataManager.getCurrentProgram(channelId); // Test {@link ProgramDataManager#getCurrentProgram(long)}. - assertTrue(currentProgram.getStartTimeUtcMillis() <= mClock.currentTimeMillis() - && mClock.currentTimeMillis() <= currentProgram.getEndTimeUtcMillis()); + assertTrue( + currentProgram.getStartTimeUtcMillis() <= mClock.currentTimeMillis() + && mClock.currentTimeMillis() <= currentProgram.getEndTimeUtcMillis()); // Test {@link ProgramDataManager#getPrograms(long)}. // Case #1: Normal case @@ -160,8 +152,9 @@ public class ProgramDataManagerTest { assertEquals(startTimeMs, programs.get(0).getStartTimeUtcMillis()); // Test {@link ProgramDataManager#setPrefetchTimeRange(long)}. - programs = mProgramDataManager.getPrograms(channelId, - prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1)); + programs = + mProgramDataManager.getPrograms( + channelId, prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1)); for (Program program : programs) { assertTrue(program.getEndTimeUtcMillis() >= prefetchTimeRangeStartMs); } @@ -171,10 +164,8 @@ public class ProgramDataManagerTest { /** * Test for following methods. * - *

- * {@link ProgramDataManager#addOnCurrentProgramUpdatedListener}, - * {@link ProgramDataManager#removeOnCurrentProgramUpdatedListener}. - *

+ *

{@link ProgramDataManager#addOnCurrentProgramUpdatedListener}, {@link + * ProgramDataManager#removeOnCurrentProgramUpdatedListener}. */ @Test public void testCurrentProgramListener() throws InterruptedException { @@ -203,9 +194,7 @@ public class ProgramDataManagerTest { assertEquals(listener.updatedProgram, currentProgram); } - /** - * Test if program data is refreshed after the program insertion. - */ + /** Test if program data is refreshed after the program insertion. */ @Test public void testContentProviderUpdate() throws InterruptedException { final long testChannelId = 1; @@ -225,9 +214,7 @@ public class ProgramDataManagerTest { lastProgramEndTime < programList.get(programList.size() - 1).getEndTimeUtcMillis()); } - /** - * Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. - */ + /** Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. */ @Test public void testSetPauseProgramUpdate() throws InterruptedException { final long testChannelId = 1; @@ -237,17 +224,19 @@ public class ProgramDataManagerTest { mListener.reset(); mProgramDataManager.setPauseProgramUpdate(true); mContentProvider.simulateAppend(testChannelId); - assertFalse(mListener.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, - TimeUnit.MILLISECONDS)); + assertFalse( + mListener.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, TimeUnit.MILLISECONDS)); } - public static void assertProgramEquals(long expectedStartTime, ProgramInfo expectedInfo, - Program actualProgram) { + public static void assertProgramEquals( + long expectedStartTime, ProgramInfo expectedInfo, Program actualProgram) { assertEquals("title", expectedInfo.title, actualProgram.getTitle()); assertEquals("episode", expectedInfo.episode, actualProgram.getEpisodeTitle()); assertEquals("description", expectedInfo.description, actualProgram.getDescription()); assertEquals("startTime", expectedStartTime, actualProgram.getStartTimeUtcMillis()); - assertEquals("endTime", expectedStartTime + expectedInfo.durationMs, + assertEquals( + "endTime", + expectedStartTime + expectedInfo.durationMs, actualProgram.getEndTimeUtcMillis()); } @@ -285,16 +274,17 @@ public class ProgramDataManagerTest { /** * Constructor for FakeContentProvider - *

- * This initializes program info assuming that - * channel IDs are 1, 2, 3, ... {@link Constants#UNIT_TEST_CHANNEL_COUNT}. - *

+ * + *

This initializes program info assuming that channel IDs are 1, 2, 3, ... {@link + * Constants#UNIT_TEST_CHANNEL_COUNT}. */ public FakeContentProvider(Context context) { super(context); - long startTimeMs = Utils.floorTime( - mClock.currentTimeMillis() - ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS, - ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS); + long startTimeMs = + Utils.floorTime( + mClock.currentTimeMillis() + - ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS, + ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS); long endTimeMs = startTimeMs + (ProgramDataManager.PROGRAM_GUIDE_MAX_TIME_RANGE / 2); for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { List programInfoList = new ArrayList<>(); @@ -313,8 +303,12 @@ public class ProgramDataManagerTest { } @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { if (DEBUG) { Log.d(TAG, "dump query"); Log.d(TAG, " uri=" + uri); @@ -337,10 +331,10 @@ public class ProgramDataManagerTest { } /** - * Simulate program data appends at the end of the existing programs. - * This appends programs until the maximum program query range - * ({@link ProgramDataManager#PROGRAM_GUIDE_MAX_TIME_RANGE}) - * where we started with the inserting half of it. + * Simulate program data appends at the end of the existing programs. This appends programs + * until the maximum program query range ({@link + * ProgramDataManager#PROGRAM_GUIDE_MAX_TIME_RANGE}) where we started with the inserting + * half of it. */ public void simulateAppend(long channelId) { long endTimeMs = @@ -352,10 +346,13 @@ public class ProgramDataManagerTest { ProgramInfo stub = ProgramInfo.create(); ProgramInfoWrapper last = programList.get(programList.size() - 1); while (last.startTimeMs < endTimeMs) { - ProgramInfo nextProgramInfo = stub.build(InstrumentationRegistry.getContext(), - last.index + 1); - ProgramInfoWrapper next = new ProgramInfoWrapper(last.index + 1, - last.startTimeMs + last.programInfo.durationMs, nextProgramInfo); + ProgramInfo nextProgramInfo = + stub.build(InstrumentationRegistry.getContext(), last.index + 1); + ProgramInfoWrapper next = + new ProgramInfoWrapper( + last.index + 1, + last.startTimeMs + last.programInfo.durationMs, + nextProgramInfo); programList.add(next); last = next; } @@ -363,7 +360,8 @@ public class ProgramDataManagerTest { } private void assertProgramUri(Uri uri) { - assertTrue("Uri(" + uri + ") isn't channel uri", + assertTrue( + "Uri(" + uri + ") isn't channel uri", uri.toString().startsWith(TvContract.Programs.CONTENT_URI.toString())); } @@ -377,13 +375,14 @@ public class ProgramDataManagerTest { } private class FakeCursor extends MockCursor { - private final String[] ALL_COLUMNS = { - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_EPISODE_TITLE, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS}; + private final String[] ALL_COLUMNS = { + TvContract.Programs.COLUMN_CHANNEL_ID, + TvContract.Programs.COLUMN_TITLE, + TvContract.Programs.COLUMN_SHORT_DESCRIPTION, + TvContract.Programs.COLUMN_EPISODE_TITLE, + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + }; private final String[] mColumns; private final boolean mIsQueryForSingleChannel; private final long mStartTimeMs; @@ -395,10 +394,11 @@ public class ProgramDataManagerTest { /** * Constructor - * @param columns the same as projection passed from {@link FakeContentProvider#query}. - * Can be null for query all. - * @param channelId channel ID to query programs belongs to the specified channel. - * Can be negative to indicate all channels. + * + * @param columns the same as projection passed from {@link FakeContentProvider#query}. Can + * be null for query all. + * @param channelId channel ID to query programs belongs to the specified channel. Can be + * negative to indicate all channels. * @param startTimeMs start of the time range to query programs. * @param endTimeMs end of the time range to query programs. */ @@ -418,9 +418,18 @@ public class ProgramDataManagerTest { mChannelId = channelId; mProgramPosition = -1; if (DEBUG) { - Log.d(TAG, "FakeCursor(columns=" + Arrays.toString(columns) - + ", channelId=" + channelId + ", startTimeMs=" + startTimeMs - + ", endTimeMs=" + endTimeMs + ") has mCount=" + mCount); + Log.d( + TAG, + "FakeCursor(columns=" + + Arrays.toString(columns) + + ", channelId=" + + channelId + + ", startTimeMs=" + + startTimeMs + + ", endTimeMs=" + + endTimeMs + + ") has mCount=" + + mCount); } } @@ -526,8 +535,8 @@ public class ProgramDataManagerTest { } } - private class TestProgramDataManagerOnCurrentProgramUpdatedListener implements - OnCurrentProgramUpdatedListener { + private class TestProgramDataManagerOnCurrentProgramUpdatedListener + implements OnCurrentProgramUpdatedListener { public final CountDownLatch currentProgramUpdatedLatch = new CountDownLatch(1); public long updatedChannelId = -1; public Program updatedProgram = null; diff --git a/tests/unit/src/com/android/tv/data/ProgramTest.java b/tests/unit/src/com/android/tv/data/ProgramTest.java index 1d1f6c10..a69688d2 100644 --- a/tests/unit/src/com/android/tv/data/ProgramTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramTest.java @@ -25,18 +25,13 @@ import android.media.tv.TvContentRating; import android.media.tv.TvContract.Programs.Genres; import android.os.Parcel; import android.support.test.filters.SmallTest; - import com.android.tv.data.Program.CriticScore; - -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.junit.Test; -/** - * Tests for {@link Program}. - */ +/** Tests for {@link Program}. */ @SmallTest public class ProgramTest { private static final int NOT_FOUND_GENRE = 987; @@ -53,9 +48,7 @@ public class ProgramTest { @Test public void testNoGenres() { - Program program = new Program.Builder() - .setCanonicalGenres("") - .build(); + Program program = new Program.Builder().setCanonicalGenres("").build(); assertNullCanonicalGenres(program); assertHasGenre(program, NOT_FOUND_GENRE, false); assertHasGenre(program, FAMILY_GENRE_ID, false); @@ -65,9 +58,7 @@ public class ProgramTest { @Test public void testFamilyGenre() { - Program program = new Program.Builder() - .setCanonicalGenres(FAMILY_KIDS) - .build(); + Program program = new Program.Builder().setCanonicalGenres(FAMILY_KIDS).build(); assertCanonicalGenres(program, FAMILY_KIDS); assertHasGenre(program, NOT_FOUND_GENRE, false); assertHasGenre(program, FAMILY_GENRE_ID, true); @@ -77,9 +68,8 @@ public class ProgramTest { @Test public void testFamilyComedyGenre() { - Program program = new Program.Builder() - .setCanonicalGenres(FAMILY_KIDS + ", " + COMEDY) - .build(); + Program program = + new Program.Builder().setCanonicalGenres(FAMILY_KIDS + ", " + COMEDY).build(); assertCanonicalGenres(program, FAMILY_KIDS, COMEDY); assertHasGenre(program, NOT_FOUND_GENRE, false); assertHasGenre(program, FAMILY_GENRE_ID, true); @@ -89,9 +79,7 @@ public class ProgramTest { @Test public void testOtherGenre() { - Program program = new Program.Builder() - .setCanonicalGenres("other") - .build(); + Program program = new Program.Builder().setCanonicalGenres("other").build(); assertCanonicalGenres(program); assertHasGenre(program, NOT_FOUND_GENRE, false); assertHasGenre(program, FAMILY_GENRE_ID, false); @@ -107,29 +95,30 @@ public class ProgramTest { TvContentRating[] ratings = new TvContentRating[2]; ratings[0] = TvContentRating.unflattenFromString("1/2/3"); ratings[1] = TvContentRating.unflattenFromString("4/5/6"); - Program p = new Program.Builder() - .setId(1) - .setPackageName("2") - .setChannelId(3) - .setTitle("4") - .setSeriesId("5") - .setEpisodeTitle("6") - .setSeasonNumber("7") - .setSeasonTitle("8") - .setEpisodeNumber("9") - .setStartTimeUtcMillis(10) - .setEndTimeUtcMillis(11) - .setDescription("12") - .setLongDescription("12-long") - .setVideoWidth(13) - .setVideoHeight(14) - .setCriticScores(criticScores) - .setPosterArtUri("15") - .setThumbnailUri("16") - .setCanonicalGenres(Genres.encode(Genres.SPORTS, Genres.SHOPPING)) - .setContentRatings(ratings) - .setRecordingProhibited(true) - .build(); + Program p = + new Program.Builder() + .setId(1) + .setPackageName("2") + .setChannelId(3) + .setTitle("4") + .setSeriesId("5") + .setEpisodeTitle("6") + .setSeasonNumber("7") + .setSeasonTitle("8") + .setEpisodeNumber("9") + .setStartTimeUtcMillis(10) + .setEndTimeUtcMillis(11) + .setDescription("12") + .setLongDescription("12-long") + .setVideoWidth(13) + .setVideoHeight(14) + .setCriticScores(criticScores) + .setPosterArtUri("15") + .setThumbnailUri("16") + .setCanonicalGenres(Genres.encode(Genres.SPORTS, Genres.SHOPPING)) + .setContentRatings(ratings) + .setRecordingProhibited(true) + .build(); Parcel p1 = Parcel.obtain(); Parcel p2 = Parcel.obtain(); try { @@ -147,13 +136,13 @@ public class ProgramTest { @Test public void testParcelableWithCriticScore() { - Program program = new Program.Builder() - .setTitle("MyTitle") - .addCriticScore(new CriticScore( - "default source", - "5/10", - "https://testurl/testimage.jpg")) - .build(); + Program program = + new Program.Builder() + .setTitle("MyTitle") + .addCriticScore( + new CriticScore( + "default source", "5/10", "https://testurl/testimage.jpg")) + .build(); Parcel parcel = Parcel.obtain(); program.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -162,7 +151,8 @@ public class ProgramTest { assertNotNull(programFromParcel.getCriticScores()); assertEquals(programFromParcel.getCriticScores().get(0).source, "default source"); assertEquals(programFromParcel.getCriticScores().get(0).score, "5/10"); - assertEquals(programFromParcel.getCriticScores().get(0).logoUrl, + assertEquals( + programFromParcel.getCriticScores().get(0).logoUrl, "https://testurl/testimage.jpg"); } @@ -172,7 +162,9 @@ public class ProgramTest { } private static void assertCanonicalGenres(Program program, String... expected) { - assertEquals("canonical genres", Arrays.asList(expected), + assertEquals( + "canonical genres", + Arrays.asList(expected), Arrays.asList(program.getCanonicalGenres())); } diff --git a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java index b4682dd9..496d1018 100644 --- a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java +++ b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java @@ -20,24 +20,19 @@ import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; import android.util.Pair; - import com.android.tv.testing.ComparatorTester; 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; import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.util.Comparator; -import java.util.LinkedHashMap; - -/** - * Test for {@link TvInputNewComparator} - */ +/** Test for {@link TvInputNewComparator} */ @SmallTest public class TvInputNewComparatorTest { @Test @@ -51,45 +46,48 @@ public class TvInputNewComparatorTest { inputIdToNewInput.put("3_old_input", new Pair<>(false, true)); SetupUtils setupUtils = Mockito.mock(SetupUtils.class); - Mockito.when(setupUtils.isNewInput(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - String inputId = (String) invocation.getArguments()[0]; - return inputIdToNewInput.get(inputId).first; - } - } - ); - Mockito.when(setupUtils.isSetupDone(Matchers.anyString())).thenAnswer( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - String inputId = (String) invocation.getArguments()[0]; - return inputIdToNewInput.get(inputId).second; - } - } - ); + Mockito.when(setupUtils.isNewInput(Matchers.anyString())) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + return inputIdToNewInput.get(inputId).first; + } + }); + Mockito.when(setupUtils.isSetupDone(Matchers.anyString())) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + return inputIdToNewInput.get(inputId).second; + } + }); TvInputManagerHelper inputManager = Mockito.mock(TvInputManagerHelper.class); - Mockito.when(inputManager.getDefaultTvInputInfoComparator()).thenReturn( - new Comparator() { - @Override - public int compare(TvInputInfo lhs, TvInputInfo rhs) { - return lhs.getId().compareTo(rhs.getId()); - } - } - ); + Mockito.when(inputManager.getDefaultTvInputInfoComparator()) + .thenReturn( + new Comparator() { + @Override + public int compare(TvInputInfo lhs, TvInputInfo rhs) { + return lhs.getId().compareTo(rhs.getId()); + } + }); TvInputNewComparator comparator = new TvInputNewComparator(setupUtils, inputManager); ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); for (String id : inputIdToNewInput.keySet()) { // Put mock resolveInfo to prevent NPE in {@link TvInputInfo#toString} - TvInputInfo info1 = TestUtils.createTvInputInfo( - resolveInfo, id, "test1", TvInputInfo.TYPE_TUNER, false); - TvInputInfo info2 = TestUtils.createTvInputInfo( - resolveInfo, id, "test2", TvInputInfo.TYPE_DISPLAY_PORT, true); - TvInputInfo info3 = TestUtils.createTvInputInfo( - resolveInfo, id, "test", TvInputInfo.TYPE_HDMI, true); + TvInputInfo info1 = + TestUtils.createTvInputInfo( + resolveInfo, id, "test1", TvInputInfo.TYPE_TUNER, false); + TvInputInfo info2 = + TestUtils.createTvInputInfo( + resolveInfo, id, "test2", TvInputInfo.TYPE_DISPLAY_PORT, true); + TvInputInfo info3 = + TestUtils.createTvInputInfo( + resolveInfo, id, "test", TvInputInfo.TYPE_HDMI, true); comparatorTester.addComparableGroup(info1, info2, info3); } comparatorTester.test(); diff --git a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java index 7eea1be7..e65a71fb 100644 --- a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java +++ b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java @@ -23,18 +23,15 @@ import static org.junit.Assert.assertTrue; import android.os.Looper; import android.support.test.filters.MediumTest; - import com.android.tv.data.WatchedHistoryManager.WatchedRecord; - +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; -import java.util.concurrent.TimeUnit; - /** * Test for {@link com.android.tv.data.WatchedHistoryManagerTest} - *

- * This is a medium test because it load files which accessing SharedPreferences. + * + *

This is a medium test because it load files which accessing SharedPreferences. */ @MediumTest public class WatchedHistoryManagerTest { @@ -104,7 +101,8 @@ public class WatchedHistoryManagerTest { } // Since the WatchedHistory is a circular queue, the value for 0 and maxHistorySize // are same. - assertEquals(mWatchedHistoryManager.getRecordFromSharedPreferences(0), + assertEquals( + mWatchedHistoryManager.getRecordFromSharedPreferences(0), mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE)); } @@ -122,8 +120,8 @@ public class WatchedHistoryManagerTest { long time = System.currentTimeMillis(); long duration = TimeUnit.MINUTES.toMillis(10); WatchedRecord record = new WatchedRecord(fakeId, time, duration); - WatchedRecord sameRecord = mWatchedHistoryManager.decode( - mWatchedHistoryManager.encode(record)); + WatchedRecord sameRecord = + mWatchedHistoryManager.decode(mWatchedHistoryManager.encode(record)); assertEquals(record, sameRecord); } @@ -136,6 +134,6 @@ public class WatchedHistoryManagerTest { } @Override - public void onNewRecordAdded(WatchedRecord watchedRecord) { } + public void onNewRecordAdded(WatchedRecord watchedRecord) {} } } diff --git a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java b/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java index 5f0ae15c..c9c76a5a 100644 --- a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java +++ b/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java @@ -23,20 +23,17 @@ import android.support.annotation.NonNull; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; - import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.feature.TestableFeature; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; - +import java.util.List; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.util.List; -import java.util.concurrent.TimeUnit; - /** Tests for {@link BaseDvrDataManager} using {@link DvrDataManagerInMemoryImpl}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) @@ -62,8 +59,9 @@ public class BaseDvrDataManagerTest { @Test public void testGetNonStartedScheduledRecordings() { - ScheduledRecording recording = mDvrDataManager - .addScheduledRecordingInternal(createNewScheduledRecordingStartingNow()); + ScheduledRecording recording = + mDvrDataManager.addScheduledRecordingInternal( + createNewScheduledRecordingStartingNow()); List result = mDvrDataManager.getNonStartedScheduledRecordings(); MoreAsserts.assertContentsInAnyOrder(result, recording); } @@ -78,12 +76,13 @@ public class BaseDvrDataManagerTest { @NonNull private ScheduledRecording createNewScheduledRecordingStartingNow() { - return ScheduledRecording.buildFrom(RecordingTestUtils - .createTestRecordingWithIdAndPeriod( - ScheduledRecording.ID_NOT_SET, - INPUT_ID, CHANNEL_ID, - mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5))) + return ScheduledRecording.buildFrom( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + ScheduledRecording.ID_NOT_SET, + INPUT_ID, + CHANNEL_ID, + mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5))) .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) .build(); } diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java index 9771a2e5..8a5dfabd 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java +++ b/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java @@ -21,14 +21,11 @@ import static org.junit.Assert.assertEquals; import android.os.Build; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; - -import org.junit.Test; - import java.util.ArrayList; import java.util.List; +import org.junit.Test; /** Tests for {@link DvrDataManagerImpl} */ @SmallTest @@ -42,35 +39,43 @@ public class DvrDataManagerImplTest { long id = 1; List scheduledRecordings = new ArrayList<>(); assertNextStartTime(scheduledRecordings, 0L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); assertNextStartTime(scheduledRecordings, 9L, 10L); assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 20L, 30L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 20L, 30L)); assertNextStartTime(scheduledRecordings, 9L, 10L); assertNextStartTime(scheduledRecordings, 10L, 20L); assertNextStartTime(scheduledRecordings, 20L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 30L, 40L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 30L, 40L)); assertNextStartTime(scheduledRecordings, 9L, 10L); assertNextStartTime(scheduledRecordings, 10L, 20L); assertNextStartTime(scheduledRecordings, 20L, 30L); assertNextStartTime(scheduledRecordings, 30L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); scheduledRecordings.clear(); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - scheduledRecordings.add(RecordingTestUtils - .createTestRecordingWithIdAndPeriod(id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); + scheduledRecordings.add( + RecordingTestUtils.createTestRecordingWithIdAndPeriod( + id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); assertNextStartTime(scheduledRecordings, 9L, 10L); assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); } - private void assertNextStartTime(List scheduledRecordings, long startTime, - long expected) { - assertEquals("getNextScheduledStartTimeAfter()", expected, + private void assertNextStartTime( + List scheduledRecordings, long startTime, long expected) { + assertEquals( + "getNextScheduledStartTimeAfter()", + expected, DvrDataManagerImpl.getNextStartTimeAfter(scheduledRecordings, startTime)); } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java index 0a7ab46c..a16f7acc 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java +++ b/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java @@ -24,14 +24,12 @@ import android.support.test.filters.SdkSuppress; import android.text.TextUtils; import android.util.Log; import android.util.Range; - import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.Clock; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -42,7 +40,7 @@ import java.util.concurrent.atomic.AtomicLong; /** A DVR Data manager that stores values in memory suitable for testing. */ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { - private final static String TAG = "DvrDataManagerInMemory"; + private static final String TAG = "DvrDataManagerInMemory"; private final AtomicLong mNextId = new AtomicLong(1); private final Map mScheduledRecordings = new HashMap<>(); private final Map mRecordedPrograms = new HashMap<>(); @@ -99,7 +97,7 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { @Override public long getNextScheduledStartTimeAfter(long startTime) { - List temp = getNonStartedScheduledRecordings(); + List temp = getNonStartedScheduledRecordings(); Collections.sort(temp, ScheduledRecording.START_TIME_COMPARATOR); for (ScheduledRecording r : temp) { if (r.getStartTimeMs() > startTime) { @@ -110,8 +108,8 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } @Override - public List getScheduledRecordings(Range period, - @RecordingState int state) { + public List getScheduledRecordings( + Range period, @RecordingState int state) { List temp = getScheduledRecordingsPrograms(); List result = new ArrayList<>(); for (ScheduledRecording r : temp) { @@ -144,9 +142,7 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { return result; } - /** - * Add a new scheduled recording. - */ + /** Add a new scheduled recording. */ @Override public void addScheduledRecording(ScheduledRecording... scheduledRecordings) { for (ScheduledRecording r : scheduledRecordings) { @@ -154,7 +150,6 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { } } - public void addRecordedProgram(RecordedProgram recordedProgram) { addRecordedProgramInternal(recordedProgram); } @@ -174,26 +169,30 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { notifyRecordedProgramsRemoved(scheduledRecording); } - public ScheduledRecording addScheduledRecordingInternal(ScheduledRecording scheduledRecording) { - SoftPreconditions - .checkState(scheduledRecording.getId() == ScheduledRecording.ID_NOT_SET, TAG, - "expected id of " + ScheduledRecording.ID_NOT_SET + " but was " - + scheduledRecording); - scheduledRecording = ScheduledRecording.buildFrom(scheduledRecording) - .setId(mNextId.incrementAndGet()) - .build(); + SoftPreconditions.checkState( + scheduledRecording.getId() == ScheduledRecording.ID_NOT_SET, + TAG, + "expected id of " + + ScheduledRecording.ID_NOT_SET + + " but was " + + scheduledRecording); + scheduledRecording = + ScheduledRecording.buildFrom(scheduledRecording) + .setId(mNextId.incrementAndGet()) + .build(); mScheduledRecordings.put(scheduledRecording.getId(), scheduledRecording); notifyScheduledRecordingAdded(scheduledRecording); return scheduledRecording; } public RecordedProgram addRecordedProgramInternal(RecordedProgram recordedProgram) { - SoftPreconditions.checkState(recordedProgram.getId() == RecordedProgram.ID_NOT_SET, TAG, + SoftPreconditions.checkState( + recordedProgram.getId() == RecordedProgram.ID_NOT_SET, + TAG, "expected id of " + RecordedProgram.ID_NOT_SET + " but was " + recordedProgram); - recordedProgram = RecordedProgram.buildFrom(recordedProgram) - .setId(mNextId.incrementAndGet()) - .build(); + recordedProgram = + RecordedProgram.buildFrom(recordedProgram).setId(mNextId.incrementAndGet()).build(); mRecordedPrograms.put(recordedProgram.getId(), recordedProgram); notifyRecordedProgramsAdded(recordedProgram); return recordedProgram; @@ -265,7 +264,7 @@ public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { public ScheduledRecording getScheduledRecordingForProgramId(long programId) { for (ScheduledRecording r : mScheduledRecordings.values()) { if (r.getProgramId() == programId) { - return r; + return r; } } return null; diff --git a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java b/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java index 1c77aa0e..ab464b18 100644 --- a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java +++ b/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java @@ -24,17 +24,14 @@ import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import android.util.Range; - import com.android.tv.dvr.DvrScheduleManager.ConflictInfo; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; - -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.junit.Test; /** Tests for {@link DvrScheduleManager} */ @SmallTest @@ -54,24 +51,29 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 0L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 0L, 100L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 100L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 0L, 100L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 100L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); } @@ -82,11 +84,14 @@ public class DvrScheduleManagerTest { List schedules = new ArrayList<>(); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 0)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 0L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L)); assertEquals(schedules, DvrScheduleManager.getConflictingSchedules(schedules, 0)); - schedules.add(0, RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - ++priority, 0L, 100L)); + schedules.add( + 0, + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L)); assertEquals(schedules, DvrScheduleManager.getConflictingSchedules(schedules, 0)); } @@ -96,76 +101,91 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r3); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - ScheduledRecording r4 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r4 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r4); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - ScheduledRecording r5 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r5 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r5); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - ScheduledRecording r6 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 10L, 90L); + ScheduledRecording r6 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 10L, 90L); schedules.add(r6); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - ScheduledRecording r7 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 110L, 190L); + ScheduledRecording r7 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 110L, 190L); schedules.add(r7); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r5, r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - ScheduledRecording r8 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 50L, 150L); + ScheduledRecording r8 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 50L, 150L); schedules.add(r8); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r7, r6, r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 4), + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), + r7, + r6, + r5, + r4, + r3, + r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r5, r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 4), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); } @@ -176,23 +196,26 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 1000L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 1000L); schedules.add(r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r3); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); } @@ -202,76 +225,91 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(0, r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(0, r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(0, r3); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - ScheduledRecording r4 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r4 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(0, r4); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - ScheduledRecording r5 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r5 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(0, r5); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - ScheduledRecording r6 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 10L, 90L); + ScheduledRecording r6 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 10L, 90L); schedules.add(0, r6); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - ScheduledRecording r7 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 110L, 190L); + ScheduledRecording r7 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 110L, 190L); schedules.add(0, r7); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r5, r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - ScheduledRecording r8 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 50L, 150L); + ScheduledRecording r8 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 50L, 150L); schedules.add(0, r8); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r7, r6, r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 2), - r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 3), - r3, r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 4), + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), + r7, + r6, + r5, + r4, + r3, + r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 2), r5, r4, r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 3), r3, r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 4), r1); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); } @@ -281,16 +319,22 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(10L, 20L))), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(110L, 120L))), r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), + r1); } @Test @@ -299,16 +343,22 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(10L, 20L))), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(110L, 120L))), r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), + r1); } @Test @@ -317,29 +367,40 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r2); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r3); - ScheduledRecording r4 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r4 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r4); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(10L, 20L))), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(110L, 120L))), r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - Collections.singletonList(new Range<>(50L, 150L))), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), + r2); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + schedules, 1, Collections.singletonList(new Range<>(50L, 150L))), + r2, + r1); List> ranges = new ArrayList<>(); ranges.add(new Range<>(10L, 20L)); ranges.add(new Range<>(110L, 120L)); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1, - ranges), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1, ranges), r2, r1); } @Test @@ -348,22 +409,33 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 100L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) - .setPriority(++priority).build()), - schedules, 1), r2, r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) - .setPriority(++priority).build()), - schedules, 1), r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + Collections.singletonList( + ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) + .setPriority(++priority) + .build()), + schedules, + 1), + r2, + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + Collections.singletonList( + ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) + .setPriority(++priority) + .build()), + schedules, + 1), + r1); } @Test @@ -372,22 +444,33 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) - .setPriority(++priority).build()), - schedules, 1), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) - .setPriority(++priority).build()), - schedules, 1), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + Collections.singletonList( + ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) + .setPriority(++priority) + .build()), + schedules, + 1), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + Collections.singletonList( + ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) + .setPriority(++priority) + .build()), + schedules, + 1), + r2, + r1); } @Test @@ -396,19 +479,25 @@ public class DvrScheduleManagerTest { long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 400L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 400L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r2); // Returning r1 even though r1 has the higher priority than the new one. That's because r1 // starts at 0 and stops at 100, and the new one will be recorded successfully. - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 200L, 300L) - .setPriority(0).build()), - schedules, 1), r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules( + Collections.singletonList( + ScheduledRecording.builder(INPUT_ID, ++channelId, 200L, 300L) + .setPriority(0) + .build()), + schedules, + 1), + r1); } @Test @@ -416,10 +505,12 @@ public class DvrScheduleManagerTest { long priority = 0; long channelId = 1; List schedules = new ArrayList<>(); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(channelId, - ++priority, 0L, 200L)); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(channelId, - ++priority, 0L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelId, ++priority, 0L, 200L)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelId, ++priority, 0L, 200L)); MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); } @@ -428,18 +519,21 @@ public class DvrScheduleManagerTest { long priority = 0; long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 200L, 300L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 200L, 300L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 400L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 400L); schedules.add(r2); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 200L); schedules.add(r3); // r2 starts recording and fails when r3 starts. r1 is recorded successfully. - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r2); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r2); } @Test @@ -447,18 +541,21 @@ public class DvrScheduleManagerTest { long priority = 0; long channelId = 0; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 200L, 400L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 200L, 400L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 300L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 100L, 300L); schedules.add(r2); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r3); // r2 and r1 are clipped. - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedules(schedules, 1), - r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); } @Test @@ -467,10 +564,12 @@ public class DvrScheduleManagerTest { long priority = 0; long channelId = 1; List schedules = new ArrayList<>(); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(channelId, - ++priority, 0L, 200L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedulesForTune(INPUT_ID, - channelId, 0L, priority + 1, schedules, 1)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelId, ++priority, 0L, 200L)); + MoreAsserts.assertEmpty( + DvrScheduleManager.getConflictingSchedulesForTune( + INPUT_ID, channelId, 0L, priority + 1, schedules, 1)); } @Test @@ -479,10 +578,13 @@ public class DvrScheduleManagerTest { long priority = 0; long channelId = 1; List schedules = new ArrayList<>(); - schedules.add(RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(channelId, - ++priority, 0L, 200L)); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForTune( - INPUT_ID, channelId + 1, 0L, priority + 1, schedules, 1), schedules.get(0)); + schedules.add( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelId, ++priority, 0L, 200L)); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForTune( + INPUT_ID, channelId + 1, 0L, priority + 1, schedules, 1), + schedules.get(0)); } @Test @@ -492,16 +594,21 @@ public class DvrScheduleManagerTest { long channelToWatch = 1; long channelId = 1; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r2); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3)); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), r1); + MoreAsserts.assertEmpty( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3)); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), + r1); } @Test @@ -510,16 +617,21 @@ public class DvrScheduleManagerTest { long channelToWatch = 1; long channelId = 1; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r2); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r2); + MoreAsserts.assertEmpty( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), + r2); } @Test @@ -528,16 +640,21 @@ public class DvrScheduleManagerTest { long channelToWatch = 1; long channelId = 1; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r2); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r1); + MoreAsserts.assertEmpty( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), + r1); } @Test @@ -546,21 +663,31 @@ public class DvrScheduleManagerTest { long channelToWatch = 1; long channelId = 1; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r2); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r3); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), r2); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r2, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), + r2); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), + r2); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), + r2, + r1); } @Test @@ -569,51 +696,62 @@ public class DvrScheduleManagerTest { long channelToWatch = 1; long channelId = 1; List schedules = new ArrayList<>(); - ScheduledRecording r1 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r1 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r1); - ScheduledRecording r2 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); + ScheduledRecording r2 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + channelToWatch, ++priority, 0L, 200L); schedules.add(r2); - ScheduledRecording r3 = RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); + ScheduledRecording r3 = + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, ++priority, 0L, 200L); schedules.add(r3); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), r1); - MoreAsserts.assertContentsInOrder(DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), r3, r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), + r1); + MoreAsserts.assertContentsInOrder( + DvrScheduleManager.getConflictingSchedulesForWatching( + INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), + r3, + r1); } @Test public void testPartiallyConflictingSchedules() { long priority = 100; long channelId = 0; - List schedules = new ArrayList<>(Arrays.asList( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 0L, 400L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 0L, 200L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 200L, 500L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 400L, 600L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 700L, 800L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 600L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 800L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 800L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 750L, 850L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 300L, 450L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod(++channelId, - --priority, 50L, 900L) - )); + List schedules = + new ArrayList<>( + Arrays.asList( + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 0L, 400L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 0L, 200L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 200L, 500L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 400L, 600L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 700L, 800L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 600L, 900L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 800L, 900L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 800L, 900L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 750L, 850L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 300L, 450L), + RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( + ++channelId, --priority, 50L, 900L))); List conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 1); assertNotInList(schedules.get(0), conflicts); @@ -690,4 +828,4 @@ public class DvrScheduleManagerTest { } fail(schedule + " doesn't conflict"); } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java b/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java index b98af603..bf1b4d38 100644 --- a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java +++ b/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java @@ -25,18 +25,15 @@ import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import android.util.Range; - import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.dvr.RecordingTestUtils; - -import org.junit.Test; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import org.junit.Test; /** Tests for {@link ScheduledRecordingTest} */ @SmallTest @@ -47,8 +44,8 @@ public class ScheduledRecordingTest { @Test public void testIsOverLapping() { - ScheduledRecording r = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, - 10L, 20L); + ScheduledRecording r = + createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); assertOverLapping(false, 1L, 9L, r); assertOverLapping(true, 1L, 20L, r); @@ -66,34 +63,37 @@ public class ScheduledRecordingTest { public void testBuildProgram() { Channel c = new Channel.Builder().build(); Program p = new Program.Builder().build(); - ScheduledRecording actual = ScheduledRecording.builder(INPUT_ID, p) - .setChannelId(c.getId()).build(); + ScheduledRecording actual = + ScheduledRecording.builder(INPUT_ID, p).setChannelId(c.getId()).build(); assertEquals("type", ScheduledRecording.TYPE_PROGRAM, actual.getType()); } @Test public void testBuildTime() { - ScheduledRecording actual = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, - 10L, 20L); + ScheduledRecording actual = + createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); assertEquals("type", ScheduledRecording.TYPE_TIMED, actual.getType()); } @Test public void testBuildFrom() { - ScheduledRecording expected = createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, - 10L, 20L); + ScheduledRecording expected = + createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); ScheduledRecording actual = ScheduledRecording.buildFrom(expected).build(); RecordingTestUtils.assertRecordingEquals(expected, actual); } @Test public void testBuild_priority() { - ScheduledRecording a = normalizePriority( - createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L)); - ScheduledRecording b = normalizePriority( - createTestRecordingWithIdAndPeriod(2, INPUT_ID, CHANNEL_ID, 10L, 20L)); - ScheduledRecording c = normalizePriority( - createTestRecordingWithIdAndPeriod(3, INPUT_ID, CHANNEL_ID, 10L, 20L)); + ScheduledRecording a = + normalizePriority( + createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L)); + ScheduledRecording b = + normalizePriority( + createTestRecordingWithIdAndPeriod(2, INPUT_ID, CHANNEL_ID, 10L, 20L)); + ScheduledRecording c = + normalizePriority( + createTestRecordingWithIdAndPeriod(3, INPUT_ID, CHANNEL_ID, 10L, 20L)); // default priority MoreAsserts.assertContentsInOrder(sortByPriority(c, b, a), a, b, c); @@ -103,15 +103,17 @@ public class ScheduledRecordingTest { MoreAsserts.assertContentsInOrder(sortByPriority(a, b, c), b, c, a); } - public Collection sortByPriority(ScheduledRecording a, ScheduledRecording b, - ScheduledRecording c) { + public Collection sortByPriority( + ScheduledRecording a, ScheduledRecording b, ScheduledRecording c) { List list = Arrays.asList(a, b, c); Collections.sort(list, ScheduledRecording.PRIORITY_COMPARATOR); return list; } private void assertOverLapping(boolean expected, long lower, long upper, ScheduledRecording r) { - assertEquals("isOverlapping(Range(" + lower + "," + upper + "), recording " + r, expected, + assertEquals( + "isOverlapping(Range(" + lower + "," + upper + "), recording " + r, + expected, r.isOverLapping(new Range<>(lower, upper))); } } diff --git a/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java b/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java index 790b2ee8..68929e95 100644 --- a/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java +++ b/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java @@ -22,14 +22,10 @@ import android.os.Build; import android.os.Parcel; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.data.Program; - import org.junit.Test; -/** - * Tests for {@link SeriesRecording}. - */ +/** Tests for {@link SeriesRecording}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class SeriesRecordingTest { @@ -39,32 +35,41 @@ public class SeriesRecordingTest { private static final String SERIES_ID = "SERIES_ID"; private static final String OTHER_SERIES_ID = "OTHER_SERIES_ID"; - private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - private final SeriesRecording mSeriesRecordingSeason2 = SeriesRecording - .buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build(); - private final SeriesRecording mSeriesRecordingSeason2Episode5 = SeriesRecording - .buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build(); - private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + private final SeriesRecording mBaseSeriesRecording = + new SeriesRecording.Builder() + .setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID) + .setSeriesId(SERIES_ID) + .build(); + private final SeriesRecording mSeriesRecordingSeason2 = + SeriesRecording.buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build(); + private final SeriesRecording mSeriesRecordingSeason2Episode5 = + SeriesRecording.buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build(); + private final Program mBaseProgram = + new Program.Builder() + .setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID) + .setSeriesId(SERIES_ID) + .build(); @Test public void testParcelable() { - SeriesRecording r1 = new SeriesRecording.Builder() - .setId(1) - .setChannelId(2) - .setPriority(3) - .setTitle("4") - .setDescription("5") - .setLongDescription("5-long") - .setSeriesId("6") - .setStartFromEpisode(7) - .setStartFromSeason(8) - .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) - .setCanonicalGenreIds(new int[] {9, 10}) - .setPosterUri("11") - .setPhotoUri("12") - .build(); + SeriesRecording r1 = + new SeriesRecording.Builder() + .setId(1) + .setChannelId(2) + .setPriority(3) + .setTitle("4") + .setDescription("5") + .setLongDescription("5-long") + .setSeriesId("6") + .setStartFromEpisode(7) + .setStartFromSeason(8) + .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) + .setCanonicalGenreIds(new int[] {9, 10}) + .setPosterUri("11") + .setPhotoUri("12") + .build(); Parcel p1 = Parcel.obtain(); Parcel p2 = Parcel.obtain(); try { @@ -125,9 +130,11 @@ public class SeriesRecordingTest { assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); } - private void assertDoesProgramMatch(Program p, SeriesRecording seriesRecording, - boolean expected) { - assertEquals(seriesRecording + " doesProgramMatch " + p, expected, + private void assertDoesProgramMatch( + Program p, SeriesRecording seriesRecording, boolean expected) { + assertEquals( + seriesRecording + " doesProgramMatch " + p, + expected, seriesRecording.matchProgram(p)); } } diff --git a/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java b/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java index 94f88a51..10a882f6 100644 --- a/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java +++ b/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.when; import android.os.Build; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManagerImpl; @@ -35,15 +34,12 @@ import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.recorder.SeriesRecordingScheduler; - import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Tests for {@link com.android.tv.dvr.DvrScheduleManager} - */ +/** Tests for {@link com.android.tv.dvr.DvrScheduleManager} */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class DvrDbSyncTest { @@ -53,12 +49,20 @@ public class DvrDbSyncTest { private static final long BASE_END_TIME_MS = 1; private static final String BASE_SEASON_NUMBER = "2"; private static final String BASE_EPISODE_NUMBER = "3"; - private static final Program BASE_PROGRAM = new Program.Builder().setId(BASE_PROGRAM_ID) - .setStartTimeUtcMillis(BASE_START_TIME_MS).setEndTimeUtcMillis(BASE_END_TIME_MS) - .build(); - private static final Program BASE_SERIES_PROGRAM = new Program.Builder().setId(BASE_PROGRAM_ID) - .setStartTimeUtcMillis(BASE_START_TIME_MS).setEndTimeUtcMillis(BASE_END_TIME_MS) - .setSeasonNumber(BASE_SEASON_NUMBER).setEpisodeNumber(BASE_EPISODE_NUMBER).build(); + private static final Program BASE_PROGRAM = + new Program.Builder() + .setId(BASE_PROGRAM_ID) + .setStartTimeUtcMillis(BASE_START_TIME_MS) + .setEndTimeUtcMillis(BASE_END_TIME_MS) + .build(); + private static final Program BASE_SERIES_PROGRAM = + new Program.Builder() + .setId(BASE_PROGRAM_ID) + .setStartTimeUtcMillis(BASE_START_TIME_MS) + .setEndTimeUtcMillis(BASE_END_TIME_MS) + .setSeasonNumber(BASE_SEASON_NUMBER) + .setEpisodeNumber(BASE_EPISODE_NUMBER) + .build(); private static final ScheduledRecording BASE_SCHEDULE = ScheduledRecording.builder(INPUT_ID, BASE_PROGRAM).build(); private static final ScheduledRecording BASE_SERIES_SCHEDULE = @@ -76,8 +80,13 @@ public class DvrDbSyncTest { when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); when(mDvrManager.addSeriesRecording(anyObject(), anyObject(), anyInt())) .thenReturn(SeriesRecording.builder(INPUT_ID, BASE_PROGRAM).build()); - mDbSync = new DvrDbSync(getContext(), mDataManager, mChannelDataManager, - mDvrManager, mSeriesRecordingScheduler); + mDbSync = + new DvrDbSync( + getContext(), + mDataManager, + mChannelDataManager, + mDvrManager, + mSeriesRecordingScheduler); } @Test @@ -92,19 +101,25 @@ public class DvrDbSyncTest { addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); long startTimeMs = BASE_START_TIME_MS + 1; long endTimeMs = BASE_END_TIME_MS + 1; - Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) - .setEndTimeUtcMillis(endTimeMs).build(); + Program program = + new Program.Builder(BASE_PROGRAM) + .setStartTimeUtcMillis(startTimeMs) + .setEndTimeUtcMillis(endTimeMs) + .build(); mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); assertUpdateScheduleCalled(program); } @Test public void testHandleUpdateProgram_changeTimeInProgressNotCalled() { - addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS).build()); + addSchedule( + BASE_PROGRAM_ID, + ScheduledRecording.buildFrom(BASE_SCHEDULE) + .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS) + .build()); long startTimeMs = BASE_START_TIME_MS + 1; - Program program = new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs) - .build(); + Program program = + new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs).build(); mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); verify(mDataManager, never()).updateScheduledRecording(anyObject()); } @@ -114,20 +129,29 @@ public class DvrDbSyncTest { addSchedule(BASE_PROGRAM_ID, BASE_SERIES_SCHEDULE); String seasonNumber = BASE_SEASON_NUMBER + "1"; String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = new Program.Builder(BASE_SERIES_PROGRAM).setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber).build(); + Program program = + new Program.Builder(BASE_SERIES_PROGRAM) + .setSeasonNumber(seasonNumber) + .setEpisodeNumber(episodeNumber) + .build(); mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); assertUpdateScheduleCalled(program); } @Test public void testHandleUpdateProgram_finished() { - addSchedule(BASE_PROGRAM_ID, ScheduledRecording.buildFrom(BASE_SERIES_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); + addSchedule( + BASE_PROGRAM_ID, + ScheduledRecording.buildFrom(BASE_SERIES_SCHEDULE) + .setState(ScheduledRecording.STATE_RECORDING_FINISHED) + .build()); String seasonNumber = BASE_SEASON_NUMBER + "1"; String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = new Program.Builder(BASE_SERIES_PROGRAM).setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber).build(); + Program program = + new Program.Builder(BASE_SERIES_PROGRAM) + .setSeasonNumber(seasonNumber) + .setEpisodeNumber(episodeNumber) + .build(); mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); verify(mDataManager, never()).updateScheduledRecording(anyObject()); } @@ -137,7 +161,8 @@ public class DvrDbSyncTest { } private void assertUpdateScheduleCalled(Program program) { - verify(mDataManager).updateScheduledRecording( - eq(ScheduledRecording.builder(INPUT_ID, program).build())); + verify(mDataManager) + .updateScheduledRecording( + eq(ScheduledRecording.builder(INPUT_ID, program).build())); } } diff --git a/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java b/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java index 216d4d5b..a586fd9e 100644 --- a/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java +++ b/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java @@ -22,17 +22,12 @@ import static org.junit.Assert.assertTrue; import android.os.Build; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.dvr.data.SeasonEpisodeNumber; - -import org.junit.Test; - import java.util.ArrayList; import java.util.List; +import org.junit.Test; -/** - * Tests for {@link EpisodicProgramLoadTask} - */ +/** Tests for {@link EpisodicProgramLoadTask} */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class EpisodicProgramLoadTaskTest { @@ -46,38 +41,49 @@ public class EpisodicProgramLoadTaskTest { @Test public void testEpisodeAlreadyScheduled_true() { List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + SeasonEpisodeNumber seasonEpisodeNumber = + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertTrue(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1))); + assertTrue( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1))); } @Test public void testEpisodeAlreadyScheduled_false() { List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + SeasonEpisodeNumber seasonEpisodeNumber = + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1))); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1))); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber( + SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber( + SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2))); } @Test public void testEpisodeAlreadyScheduled_null() { List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); + SeasonEpisodeNumber seasonEpisodeNumber = + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1))); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, null))); - assertFalse(seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, null))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, null))); + assertFalse( + seasonEpisodeNumbers.contains( + new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, null))); } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java b/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java index 8f7dcaf2..3183d92e 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/DvrRecordingServiceTest.java @@ -16,24 +16,16 @@ package com.android.tv.dvr.recorder; -import static org.mockito.Mockito.verify; - import android.content.Intent; import android.os.Build; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.ServiceTestCase; - import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.feature.TestableFeature; - -import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -/** - * Tests for {@link DvrRecordingService}. - */ +/** Tests for {@link DvrRecordingService}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class DvrRecordingServiceTest @@ -180,4 +172,4 @@ public class DvrRecordingServiceTest mIsRecording = false; } } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java index e5c27e2c..f4e6cdf9 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java @@ -34,7 +34,6 @@ import android.os.Looper; import android.os.SystemClock; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.InputSessionManager; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; @@ -46,19 +45,15 @@ import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; import com.android.tv.util.Clock; import com.android.tv.util.TestUtils; - +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link InputTaskScheduler}. - */ +/** Tests for {@link InputTaskScheduler}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class InputTaskSchedulerTest { @@ -88,40 +83,57 @@ public class InputTaskSchedulerTest { MockitoAnnotations.initMocks(this); mFakeClock = FakeClock.createWithCurrentTime(); TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE); - mScheduler = new InputTaskScheduler(getContext(), input, Looper.myLooper(), - mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mFakeClock, - new RecordingTaskFactory() { - @Override - public RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, - Channel channel, DvrManager dvrManager, - InputSessionManager sessionManager, WritableDvrDataManager dataManager, - Clock clock) { - RecordingTask task = mock(RecordingTask.class); - when(task.getPriority()).thenReturn(scheduledRecording.getPriority()); - when(task.getEndTimeMs()).thenReturn(scheduledRecording.getEndTimeMs()); - mRecordingTasks.add(task); - return task; - } - }); + mScheduler = + new InputTaskScheduler( + getContext(), + input, + Looper.myLooper(), + mChannelDataManager, + mDvrManager, + mDataManager, + mSessionManager, + mFakeClock, + new RecordingTaskFactory() { + @Override + public RecordingTask createRecordingTask( + ScheduledRecording scheduledRecording, + Channel channel, + DvrManager dvrManager, + InputSessionManager sessionManager, + WritableDvrDataManager dataManager, + Clock clock) { + RecordingTask task = mock(RecordingTask.class); + when(task.getPriority()) + .thenReturn(scheduledRecording.getPriority()); + when(task.getEndTimeMs()) + .thenReturn(scheduledRecording.getEndTimeMs()); + mRecordingTasks.add(task); + return task; + } + }); } @Test public void testAddSchedule_past() { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, 0L, 1L); + ScheduledRecording r = + RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, 0L, 1L); when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r); mScheduler.handleAddSchedule(r); mScheduler.handleBuildSchedule(); verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1)) - .changeState(any(ScheduledRecording.class), + .changeState( + any(ScheduledRecording.class), eq(ScheduledRecording.STATE_RECORDING_FAILED)); } @Test public void testAddSchedule_start() { - mScheduler.handleAddSchedule(RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); + mScheduler.handleAddSchedule( + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, + CHANNEL_ID, + mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); mScheduler.handleBuildSchedule(); verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); } @@ -132,14 +144,14 @@ public class InputTaskSchedulerTest { long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); long id = 0; mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); startTimeMs = endTimeMs; endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); // The first schedule should not be stopped because the second one should wait for the end @@ -153,17 +165,17 @@ public class InputTaskSchedulerTest { long startTimeMs = mFakeClock.currentTimeMillis(); long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); long id = 0; - when(mDataManager.getScheduledRecording(anyLong())).thenReturn(ScheduledRecording - .builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); + when(mDataManager.getScheduledRecording(anyLong())) + .thenReturn(ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); startTimeMs = endTimeMs; endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); SystemClock.sleep(LISTENER_TIMEOUT_MS); @@ -171,7 +183,8 @@ public class InputTaskSchedulerTest { // The second schedule should not fail because it can starts after the first one finishes. SystemClock.sleep(LISTENER_TIMEOUT_MS); verify(mDataManager, never()) - .changeState(any(ScheduledRecording.class), + .changeState( + any(ScheduledRecording.class), eq(ScheduledRecording.STATE_RECORDING_FAILED)); } @@ -183,14 +196,14 @@ public class InputTaskSchedulerTest { long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); long id = 0; mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - LOW_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); startTimeMs = endTimeMs; endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, - HIGH_PRIORITY, startTimeMs, endTimeMs)); + RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( + ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); mScheduler.handleBuildSchedule(); verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); SystemClock.sleep(LISTENER_TIMEOUT_MS); @@ -202,9 +215,12 @@ public class InputTaskSchedulerTest { @Test public void testUpdateSchedule_noCancel() { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); + ScheduledRecording r = + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, + CHANNEL_ID, + mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); mScheduler.handleAddSchedule(r); mScheduler.handleBuildSchedule(); mScheduler.handleUpdateSchedule(r); @@ -214,14 +230,18 @@ public class InputTaskSchedulerTest { @Test public void testUpdateSchedule_cancel() { - ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); + ScheduledRecording r = + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, + CHANNEL_ID, + mFakeClock.currentTimeMillis(), + mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); mScheduler.handleAddSchedule(r); mScheduler.handleBuildSchedule(); - mScheduler.handleUpdateSchedule(ScheduledRecording.buildFrom(r) - .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) - .build()); + mScheduler.handleUpdateSchedule( + ScheduledRecording.buildFrom(r) + .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) + .build()); verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel(); } diff --git a/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java b/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java index 37561a42..03a4fe50 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java @@ -33,7 +33,6 @@ import android.os.Looper; import android.os.Message; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.RecordingSession; import com.android.tv.common.feature.CommonFeatures; @@ -45,18 +44,14 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.RecordingTask.State; import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; - +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link RecordingTask}. - */ +/** Tests for {@link RecordingTask}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class RecordingTaskTest { @@ -95,20 +90,25 @@ public class RecordingTaskTest { ScheduledRecording r = createRecording(channel); RecordingTask task = createRecordingTask(r, channel); String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); + when(mMockSessionManager.createRecordingSession( + eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong())) + .thenReturn(mMockRecordingSession); when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); assertTrue(task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE))); assertEquals(State.CONNECTION_PENDING, task.getState()); - verify(mMockSessionManager).createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong()); + verify(mMockSessionManager) + .createRecordingSession( + eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong()); verify(mMockRecordingSession).tune(eq(inputId), eq(channel.getUri())); verifyNoMoreInteractions(mMockHandler, mMockRecordingSession, mMockSessionManager); } private static Channel createTestChannel() { - return new Channel.Builder().setInputId(INPUT_ID).setId(CHANNEL_ID) - .setDisplayName("Test Ch " + CHANNEL_ID).build(); + return new Channel.Builder() + .setInputId(INPUT_ID) + .setId(CHANNEL_ID) + .setDisplayName("Test Ch " + CHANNEL_ID) + .build(); } @Test @@ -118,8 +118,9 @@ public class RecordingTaskTest { mDataManager.addScheduledRecording(r); RecordingTask task = createRecordingTask(r, channel); String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession(eq(inputId), anyString(), eq(task), - eq(mMockHandler), anyLong())).thenReturn(mMockRecordingSession); + when(mMockSessionManager.createRecordingSession( + eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong())) + .thenReturn(mMockRecordingSession); when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE)); task.onTuned(channel.getUri()); @@ -129,13 +130,20 @@ public class RecordingTaskTest { private ScheduledRecording createRecording(Channel c) { long startTime = mFakeClock.currentTimeMillis() + START_OFFSET_MS; long endTime = startTime + DURATION; - return RecordingTestUtils.createTestRecordingWithPeriod(c.getInputId(), c.getId(), - startTime, endTime); + return RecordingTestUtils.createTestRecordingWithPeriod( + c.getInputId(), c.getId(), startTime, endTime); } private RecordingTask createRecordingTask(ScheduledRecording r, Channel channel) { - RecordingTask recordingTask = new RecordingTask(getContext(), r, channel, mDvrManager, - mMockSessionManager, mDataManager, mFakeClock); + RecordingTask recordingTask = + new RecordingTask( + getContext(), + r, + channel, + mDvrManager, + mMockSessionManager, + mDataManager, + mFakeClock); recordingTask.setHandler(mMockHandler); return recordingTask; } @@ -146,4 +154,4 @@ public class RecordingTaskTest { msg.what = what; return msg; } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java b/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java index ca72e13f..dc47fc86 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java @@ -23,7 +23,6 @@ import android.os.Build; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; - import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.feature.TestableFeature; import com.android.tv.dvr.DvrDataManagerInMemoryImpl; @@ -31,18 +30,14 @@ import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; - +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link ScheduledProgramReaper}. - */ +/** Tests for {@link ScheduledProgramReaper}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class ScheduledProgramReaperTest { @@ -80,58 +75,62 @@ public class ScheduledProgramReaperTest { @Test public void testRun_oneRecordingsTomorrow() { ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); } @Test public void testRun_oneRecordingsStarted() { ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); mFakeClock.increment(TimeUnit.DAYS); mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); } @Test public void testRun_oneRecordingsFinished() { ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); mFakeClock.increment(TimeUnit.DAYS); mFakeClock.increment(TimeUnit.MINUTES, 2); mReaper.run(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); } @Test public void testRun_oneRecordingsExpired() { ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts - .assertContentsInAnyOrder(mDvrDataManager.getAllScheduledRecordings(), recording); + MoreAsserts.assertContentsInAnyOrder( + mDvrDataManager.getAllScheduledRecordings(), recording); mFakeClock.increment(TimeUnit.DAYS, 1 + ScheduledProgramReaper.DAYS); mFakeClock.increment(TimeUnit.MILLISECONDS, DURATION); // After the cutoff and enough so we can see on the clock mFakeClock.increment(TimeUnit.SECONDS, 1); mReaper.run(); - assertTrue("Recordings after reaper at " + com.android.tv.util.Utils - .toIsoDateTimeString(mFakeClock.currentTimeMillis()), + assertTrue( + "Recordings after reaper at " + + com.android.tv.util.Utils.toIsoDateTimeString( + mFakeClock.currentTimeMillis()), mDvrDataManager.getAllScheduledRecordings().isEmpty()); } private ScheduledRecording addNewScheduledRecordingForTomorrow() { long startTime = mFakeClock.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); - ScheduledRecording recording = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, - CHANNEL_ID, startTime, startTime + DURATION); + ScheduledRecording recording = + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, CHANNEL_ID, startTime, startTime + DURATION); return mDvrDataManager.addScheduledRecordingInternal( ScheduledRecording.buildFrom(recording) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build()); + .setState(ScheduledRecording.STATE_RECORDING_FINISHED) + .build()); } } diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java index a5154729..008e43f1 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java @@ -30,7 +30,6 @@ import android.os.Build; import android.os.Looper; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; - import com.android.tv.InputSessionManager; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.feature.TestableFeature; @@ -41,7 +40,7 @@ import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.testing.FakeClock; import com.android.tv.testing.dvr.RecordingTestUtils; import com.android.tv.util.TvInputManagerHelper; - +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -49,11 +48,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.concurrent.TimeUnit; - -/** - * Tests for {@link RecordingScheduler}. - */ +/** Tests for {@link RecordingScheduler}. */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class SchedulerTest { @@ -77,9 +72,17 @@ public class SchedulerTest { mFakeClock = FakeClock.createWithCurrentTime(); mDataManager = new DvrDataManagerInMemoryImpl(getTargetContext(), mFakeClock); Mockito.when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); - mScheduler = new RecordingScheduler(Looper.myLooper(), mDvrManager, mSessionManager, mDataManager, - mChannelDataManager, mInputManager, getTargetContext(), mFakeClock, - mMockAlarmManager); + mScheduler = + new RecordingScheduler( + Looper.myLooper(), + mDvrManager, + mSessionManager, + mDataManager, + mChannelDataManager, + mInputManager, + getTargetContext(), + mFakeClock, + mMockAlarmManager); } @After @@ -97,29 +100,32 @@ public class SchedulerTest { public void testUpdate_nextIn12Hours() { long now = mFakeClock.currentTimeMillis(); long startTime = now + TimeUnit.HOURS.toMillis(12); - ScheduledRecording r = RecordingTestUtils - .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, - startTime + TimeUnit.HOURS.toMillis(1)); + ScheduledRecording r = + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, CHANNEL_ID, startTime, startTime + TimeUnit.HOURS.toMillis(1)); mDataManager.addScheduledRecording(r); - verify(mMockAlarmManager).setExactAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); + verify(mMockAlarmManager) + .setExactAndAllowWhileIdle( + eq(AlarmManager.RTC_WAKEUP), + eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), + any(PendingIntent.class)); Mockito.reset(mMockAlarmManager); mScheduler.updateAndStartServiceIfNeeded(); - verify(mMockAlarmManager).setExactAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); + verify(mMockAlarmManager) + .setExactAndAllowWhileIdle( + eq(AlarmManager.RTC_WAKEUP), + eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), + any(PendingIntent.class)); } @Test public void testStartsWithin() { long now = mFakeClock.currentTimeMillis(); long startTime = now + 3; - ScheduledRecording r = RecordingTestUtils - .createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, startTime, startTime + 100); + ScheduledRecording r = + RecordingTestUtils.createTestRecordingWithPeriod( + INPUT_ID, CHANNEL_ID, startTime, startTime + 100); assertFalse(mScheduler.startsWithin(r, 2)); assertTrue(mScheduler.startsWithin(r, 3)); } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java index 16fa1baf..c7cbff55 100644 --- a/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java +++ b/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java @@ -23,25 +23,20 @@ import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import android.util.LongSparseArray; - import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.feature.TestableFeature; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManagerInMemoryImpl; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.testing.FakeClock; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; -/** - * Tests for {@link SeriesRecordingScheduler} - */ +/** Tests for {@link SeriesRecordingScheduler} */ @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) public class SeriesRecordingSchedulerTest { @@ -54,10 +49,18 @@ public class SeriesRecordingSchedulerTest { private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; - private final SeriesRecording mBaseSeriesRecording = new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE).setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); - private final Program mBaseProgram = new Program.Builder().setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID).setSeriesId(SERIES_ID).build(); + private final SeriesRecording mBaseSeriesRecording = + new SeriesRecording.Builder() + .setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID) + .setSeriesId(SERIES_ID) + .build(); + private final Program mBaseProgram = + new Program.Builder() + .setTitle(PROGRAM_TITLE) + .setChannelId(CHANNEL_ID) + .setSeriesId(SERIES_ID) + .build(); private final TestableFeature mDvrFeature = CommonFeatures.DVR; private DvrDataManagerInMemoryImpl mDataManager; @@ -76,54 +79,70 @@ public class SeriesRecordingSchedulerTest { @Test public void testPickOneProgramPerEpisode_onePerEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); + SeriesRecording seriesRecording = + SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); mDataManager.addSeriesRecording(seriesRecording); List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1).build(); + Program program1 = + new Program.Builder(mBaseProgram) + .setSeasonNumber(SEASON_NUMBER1) + .setEpisodeNumber(EPISODE_NUMBER1) + .build(); programs.add(program1); - Program program2 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2).build(); + Program program2 = + new Program.Builder(mBaseProgram) + .setSeasonNumber(SEASON_NUMBER2) + .setEpisodeNumber(EPISODE_NUMBER2) + .build(); programs.add(program2); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); + LongSparseArray> result = + SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); } @Test public void testPickOneProgramPerEpisode_manyPerEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); + SeriesRecording seriesRecording = + SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); mDataManager.addSeriesRecording(seriesRecording); List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1).setStartTimeUtcMillis(0).build(); + Program program1 = + new Program.Builder(mBaseProgram) + .setSeasonNumber(SEASON_NUMBER1) + .setEpisodeNumber(EPISODE_NUMBER1) + .setStartTimeUtcMillis(0) + .build(); programs.add(program1); Program program2 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); programs.add(program2); - Program program3 = new Program.Builder(mBaseProgram).setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2).build(); + Program program3 = + new Program.Builder(mBaseProgram) + .setSeasonNumber(SEASON_NUMBER2) + .setEpisodeNumber(EPISODE_NUMBER2) + .build(); programs.add(program3); Program program4 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); programs.add(program4); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); + LongSparseArray> result = + SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program3); } @Test public void testPickOneProgramPerEpisode_nullEpisode() { - SeriesRecording seriesRecording = SeriesRecording.buildFrom(mBaseSeriesRecording) - .setId(SERIES_RECORDING_ID1).build(); + SeriesRecording seriesRecording = + SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); mDataManager.addSeriesRecording(seriesRecording); List programs = new ArrayList<>(); Program program1 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(0).build(); programs.add(program1); Program program2 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(1).build(); programs.add(program2); - LongSparseArray> result = SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); + LongSparseArray> result = + SeriesRecordingScheduler.pickOneProgramPerEpisode( + mDataManager, Collections.singletonList(seriesRecording), programs); MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); } } diff --git a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java b/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java index 5667ee6b..2a1b5667 100644 --- a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java +++ b/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java @@ -19,19 +19,14 @@ package com.android.tv.dvr.ui; import android.support.test.filters.SmallTest; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.ObjectAdapter; - -import junit.framework.TestCase; - -import org.junit.Before; -import org.junit.Test; - import java.util.Arrays; import java.util.Comparator; import java.util.Objects; +import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; -/** - * Tests for {@link SortedArrayAdapter}. - */ +/** Tests for {@link SortedArrayAdapter}. */ @SmallTest public class SortedArrayAdapterTest extends TestCase { public static final TestData P1 = TestData.create(1, "c"); @@ -228,12 +223,13 @@ public class SortedArrayAdapterTest extends TestCase { private static class TestSortedArrayAdapter extends SortedArrayAdapter { - private static final Comparator TEXT_COMPARATOR = new Comparator() { - @Override - public int compare(TestData lhs, TestData rhs) { - return lhs.mText.compareTo(rhs.mText); - } - }; + private static final Comparator TEXT_COMPARATOR = + new Comparator() { + @Override + public int compare(TestData lhs, TestData rhs) { + return lhs.mText.compareTo(rhs.mText); + } + }; TestSortedArrayAdapter(int maxInitialCount, Object extra) { super(new ClassPresenterSelector(), TEXT_COMPARATOR, maxInitialCount); @@ -247,4 +243,4 @@ public class SortedArrayAdapterTest extends TestCase { return item.mId; } } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java b/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java index 3f827ce1..f5fb353c 100644 --- a/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java +++ b/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java @@ -19,22 +19,11 @@ package com.android.tv.experiments; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; - import com.android.tv.common.BuildConfig; - -import junit.framework.Assert; - import org.junit.Before; import org.junit.Test; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; - -/** - * Tests for {@link Experiments}. - */ +/** Tests for {@link Experiments}. */ @SmallTest public class ExperimentsTest { @Before @@ -42,12 +31,11 @@ public class ExperimentsTest { ExperimentFlag.initForTest(); } - @Test public void testEngOnlyDefault() { - assertEquals("ENABLE_DEVELOPER_FEATURES", Boolean.valueOf(BuildConfig.ENG), + assertEquals( + "ENABLE_DEVELOPER_FEATURES", + Boolean.valueOf(BuildConfig.ENG), Experiments.ENABLE_DEVELOPER_FEATURES.get()); } - - } diff --git a/tests/unit/src/com/android/tv/menu/MenuTest.java b/tests/unit/src/com/android/tv/menu/MenuTest.java index e8cfdbef..4d8081bc 100644 --- a/tests/unit/src/com/android/tv/menu/MenuTest.java +++ b/tests/unit/src/com/android/tv/menu/MenuTest.java @@ -20,9 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; - import com.android.tv.menu.Menu.OnMenuVisibilityChangeListener; - import org.junit.Before; import org.junit.Test; import org.mockito.Matchers; @@ -30,9 +28,7 @@ import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -/** - * Tests for {@link Menu}. - */ +/** Tests for {@link Menu}. */ @SmallTest public class MenuTest { private Menu mMenu; @@ -83,8 +79,11 @@ public class MenuTest { Mockito.verify(mVisibilityChangeListener, Mockito.never()) .onMenuVisibilityChange(Matchers.eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView).onShow(Matchers.eq(Menu.REASON_NONE), - Matchers.isNull(String.class), Matchers.isNull(Runnable.class)); + Mockito.verify(mMenuView) + .onShow( + Matchers.eq(Menu.REASON_NONE), + Matchers.isNull(String.class), + Matchers.isNull(Runnable.class)); mMenu.hide(true); setMenuVisible(false); // Listener should be called with "false" argument. @@ -104,8 +103,11 @@ public class MenuTest { Mockito.verify(mVisibilityChangeListener, Mockito.never()) .onMenuVisibilityChange(Matchers.eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView).onShow(Matchers.eq(Menu.REASON_GUIDE), - Matchers.eq(ChannelsRow.ID), Matchers.isNull(Runnable.class)); + Mockito.verify(mMenuView) + .onShow( + Matchers.eq(Menu.REASON_GUIDE), + Matchers.eq(ChannelsRow.ID), + Matchers.isNull(Runnable.class)); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. @@ -125,8 +127,11 @@ public class MenuTest { Mockito.verify(mVisibilityChangeListener, Mockito.never()) .onMenuVisibilityChange(Matchers.eq(false)); // IMenuView.show should be called with the same parameter. - Mockito.verify(mMenuView).onShow(Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), - Matchers.eq(PlayControlsRow.ID), Matchers.isNull(Runnable.class)); + Mockito.verify(mMenuView) + .onShow( + Matchers.eq(Menu.REASON_PLAY_CONTROLS_FAST_FORWARD), + Matchers.eq(PlayControlsRow.ID), + Matchers.isNull(Runnable.class)); mMenu.hide(false); setMenuVisible(false); // Listener should be called with "false" argument. @@ -136,11 +141,13 @@ public class MenuTest { } private void setMenuVisible(final boolean visible) { - Mockito.when(mMenuView.isVisible()).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - return visible; - } - }); + Mockito.when(mMenuView.isVisible()) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return visible; + } + }); } } diff --git a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java index 49ba8514..4faca569 100644 --- a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java +++ b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java @@ -23,22 +23,17 @@ import android.media.tv.TvTrackInfo; import android.os.SystemClock; import android.support.test.filters.MediumTest; import android.text.TextUtils; - import com.android.tv.BaseMainActivityTestCase; import com.android.tv.testing.Constants; import com.android.tv.testing.testinput.ChannelState; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TvTestInputConstants; - -import org.junit.Before; -import org.junit.Test; - import java.util.Collections; import java.util.List; +import org.junit.Before; +import org.junit.Test; -/** - * Tests for {@link TvOptionsRowAdapter}. - */ +/** Tests for {@link TvOptionsRowAdapter}. */ @MediumTest public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { private static final int WAIT_TRACK_EVENT_TIMEOUT_MS = 300; @@ -56,12 +51,14 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { waitUntilAudioTracksHaveSize(1); waitUntilAudioTrackSelected(ChannelState.DEFAULT.getSelectedAudioTrackId()); // update should be called on the main thread to avoid the multi-thread problem. - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mTvOptionsRowAdapter.update(); - } - }); + getInstrumentation() + .runOnMainSync( + new Runnable() { + @Override + public void run() { + mTvOptionsRowAdapter.update(); + } + }); } @Test @@ -74,8 +71,8 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); assertEquals("update Action had change", true, result); - assertEquals("Multi Audio enabled", true, - MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); + assertEquals( + "Multi Audio enabled", true, MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } @Test @@ -91,8 +88,8 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); assertEquals("update Action had change", true, result); - assertEquals("Multi Audio enabled", false, - MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); + assertEquals( + "Multi Audio enabled", false, MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } @Test @@ -109,8 +106,8 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { boolean result = mTvOptionsRowAdapter.updateMultiAudioAction(); assertEquals("update Action had change", true, result); - assertEquals("Multi Audio enabled", false, - MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); + assertEquals( + "Multi Audio enabled", false, MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled()); } private void waitUntilAudioTracksHaveSize(int expected) { @@ -135,8 +132,13 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { } SystemClock.sleep(TRACK_CHECK_INTERVAL_MS); } - fail("Waited for " + WAIT_TRACK_EVENT_TIMEOUT_MS + " milliseconds for track size to be " - + expected + " but was " + size); + fail( + "Waited for " + + WAIT_TRACK_EVENT_TIMEOUT_MS + + " milliseconds for track size to be " + + expected + + " but was " + + size); } private void waitUntilAudioTrackSelected(String trackId) { @@ -158,7 +160,12 @@ public class TvOptionsRowAdapterTest extends BaseMainActivityTestCase { } SystemClock.sleep(TRACK_CHECK_INTERVAL_MS); } - fail("Waited for " + WAIT_TRACK_EVENT_TIMEOUT_MS + " milliseconds for track ID to be " - + trackId + " but was " + selectedTrackId); + fail( + "Waited for " + + WAIT_TRACK_EVENT_TIMEOUT_MS + + " milliseconds for track ID to be " + + trackId + + " but was " + + selectedTrackId); } } diff --git a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java index db765109..d868d3f8 100644 --- a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java +++ b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java @@ -20,18 +20,13 @@ import static android.support.test.InstrumentationRegistry.getContext; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; - import com.android.tv.testing.Utils; - -import org.junit.Before; -import org.junit.Test; - import java.util.Random; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; -/** - * Unit tests for {@link ChannelRecord}. - */ +/** Unit tests for {@link ChannelRecord}. */ @SmallTest public class ChannelRecordTest { private static final int CHANNEL_RECORD_MAX_HISTORY_SIZE = ChannelRecord.MAX_HISTORY_SIZE; @@ -126,8 +121,9 @@ public class ChannelRecordTest { mLatestWatchEndTimeMs += TimeUnit.SECONDS.toMillis(mRandom.nextInt(60) + 1); long durationMs = TimeUnit.SECONDS.toMillis(mRandom.nextInt(60) + 1); - mChannelRecord.logWatchHistory(new WatchedProgram(null, - mLatestWatchEndTimeMs, mLatestWatchEndTimeMs + durationMs)); + mChannelRecord.logWatchHistory( + new WatchedProgram( + null, mLatestWatchEndTimeMs, mLatestWatchEndTimeMs + durationMs)); mLatestWatchEndTimeMs += durationMs; return durationMs; diff --git a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java index 853fb245..af941177 100644 --- a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java +++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java @@ -24,15 +24,11 @@ import com.android.tv.data.Channel; import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.recommendation.Recommender.Evaluator; import com.android.tv.testing.Utils; - -import org.junit.Before; - import java.util.ArrayList; import java.util.List; +import org.junit.Before; -/** - * Base test case for Recommendation Evaluator Unit tests. - */ +/** Base test case for Recommendation Evaluator Unit tests. */ public abstract class EvaluatorTestCase { private static final long INVALID_CHANNEL_ID = -1; @@ -46,8 +42,8 @@ public abstract class EvaluatorTestCase { @Before public void setUp() { mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext()); - mDataManager = RecommendationUtils - .createMockRecommendationDataManager(mChannelRecordSortedMap); + mDataManager = + RecommendationUtils.createMockRecommendationDataManager(mChannelRecordSortedMap); Recommender mRecommender = new FakeRecommender(); mEvaluator = createEvaluator(); mEvaluator.setRecommender(mRecommender); @@ -55,9 +51,7 @@ public abstract class EvaluatorTestCase { mChannelRecordSortedMap.resetRandom(Utils.createTestRandom()); } - /** - * Each evaluator test has to create Evaluator in {@code mEvaluator}. - */ + /** Each evaluator test has to create Evaluator in {@code mEvaluator}. */ public abstract T createEvaluator(); public void addChannels(int numberOfChannels) { @@ -68,15 +62,16 @@ public abstract class EvaluatorTestCase { return mChannelRecordSortedMap.addChannel(); } - public void addRandomWatchLogs(long watchStartTimeMs, long watchEndTimeMs, - long maxWatchDurationMs) { - assertTrue(mChannelRecordSortedMap.addRandomWatchLogs(watchStartTimeMs, watchEndTimeMs, - maxWatchDurationMs)); + public void addRandomWatchLogs( + long watchStartTimeMs, long watchEndTimeMs, long maxWatchDurationMs) { + assertTrue( + mChannelRecordSortedMap.addRandomWatchLogs( + watchStartTimeMs, watchEndTimeMs, maxWatchDurationMs)); } public void addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) { - assertTrue(mChannelRecordSortedMap.addWatchLog(channelId, watchStartTimeMs, - durationTimeMs)); + assertTrue( + mChannelRecordSortedMap.addWatchLog(channelId, watchStartTimeMs, durationTimeMs)); } public List getChannelIdListSorted() { @@ -86,31 +81,29 @@ public abstract class EvaluatorTestCase { public long getLatestWatchEndTimeMs() { long latestWatchEndTimeMs = 0; for (ChannelRecord channelRecord : mChannelRecordSortedMap.values()) { - latestWatchEndTimeMs = Math.max(latestWatchEndTimeMs, - channelRecord.getLastWatchEndTimeMs()); + latestWatchEndTimeMs = + Math.max(latestWatchEndTimeMs, channelRecord.getLastWatchEndTimeMs()); } return latestWatchEndTimeMs; } - /** - * Check whether scores of each channels are valid. - */ + /** Check whether scores of each channels are valid. */ protected void assertChannelScoresValid() { - assertEqualScores(Evaluator.NOT_RECOMMENDED, - mEvaluator.evaluateChannel(INVALID_CHANNEL_ID)); - assertEqualScores(Evaluator.NOT_RECOMMENDED, + assertEqualScores( + Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(INVALID_CHANNEL_ID)); + assertEqualScores( + Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(mChannelRecordSortedMap.size())); for (long channelId : mChannelRecordSortedMap.keySet()) { double score = mEvaluator.evaluateChannel(channelId); - assertTrue("Channel " + channelId + " score of " + score + "is not valid", + assertTrue( + "Channel " + channelId + " score of " + score + "is not valid", score == Evaluator.NOT_RECOMMENDED || (0.0 <= score && score <= 1.0)); } } - /** - * Notify that loading channels and watch logs are finished. - */ + /** Notify that loading channels and watch logs are finished. */ protected void notifyChannelAndWatchLogLoaded() { mEvaluator.onChannelRecordListChanged(new ArrayList<>(mChannelRecordSortedMap.values())); } @@ -125,15 +118,16 @@ public abstract class EvaluatorTestCase { private class FakeRecommender extends Recommender { public FakeRecommender() { - super(new Recommender.Listener() { - @Override - public void onRecommenderReady() { - } - - @Override - public void onRecommendationChanged() { - } - }, true, mDataManager); + super( + new Recommender.Listener() { + @Override + public void onRecommenderReady() {} + + @Override + public void onRecommendationChanged() {} + }, + true, + mDataManager); } @Override diff --git a/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java index ac701af9..76c0a5ed 100644 --- a/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java +++ b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java @@ -19,15 +19,11 @@ package com.android.tv.recommendation; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; - -import org.junit.Test; - import java.util.List; import java.util.concurrent.TimeUnit; +import org.junit.Test; -/** - * Unit tests for {@link FavoriteChannelEvaluator}. - */ +/** Unit tests for {@link FavoriteChannelEvaluator}. */ @SmallTest public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase { private static final int DEFAULT_NUMBER_OF_CHANNELS = 4; @@ -47,14 +43,16 @@ public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase channelIdList = getChannelIdListSorted(); for (long channelId : channelIdList) { - assertEqualScores(Recommender.Evaluator.NOT_RECOMMENDED, - mEvaluator.evaluateChannel(channelId)); + assertEqualScores( + Recommender.Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(channelId)); } } @Test public void testMultiChannelsWithRandomWatchLogs() { addChannels(DEFAULT_NUMBER_OF_CHANNELS); - addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS, + addRandomWatchLogs( + DEFAULT_WATCH_START_TIME_MS, + DEFAULT_WATCH_END_TIME_MS, DEFAULT_MAX_WATCH_DURATION_MS); notifyChannelAndWatchLogLoaded(); @@ -112,30 +112,40 @@ public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase { private static final int DEFAULT_NUMBER_OF_CHANNELS = 4; @@ -49,14 +45,16 @@ public class RecentChannelEvaluatorTest extends EvaluatorTestCase channelIdList = getChannelIdListSorted(); for (long channelId : channelIdList) { - assertEqualScores(Recommender.Evaluator.NOT_RECOMMENDED, - mEvaluator.evaluateChannel(channelId)); + assertEqualScores( + Recommender.Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(channelId)); } } @Test public void testMultiChannelsWithRandomWatchLogs() { addChannels(DEFAULT_NUMBER_OF_CHANNELS); - addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS, + addRandomWatchLogs( + DEFAULT_WATCH_START_TIME_MS, + DEFAULT_WATCH_END_TIME_MS, DEFAULT_MAX_WATCH_DURATION_MS); notifyChannelAndWatchLogLoaded(); @@ -111,7 +111,9 @@ public class RecentChannelEvaluatorTest extends EvaluatorTestCase() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return channelRecordSortedMap.size(); - } - }).when(dataManager).getChannelRecordCount(); - Mockito.doAnswer(new Answer>() { - @Override - public Collection answer(InvocationOnMock invocation) throws Throwable { - return channelRecordSortedMap.values(); - } - }).when(dataManager).getChannelRecords(); - Mockito.doAnswer(new Answer() { - @Override - public ChannelRecord answer(InvocationOnMock invocation) throws Throwable { - long channelId = (long) invocation.getArguments()[0]; - return channelRecordSortedMap.get(channelId); - } - }).when(dataManager).getChannelRecord(Matchers.anyLong()); + Mockito.doAnswer( + new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + return channelRecordSortedMap.size(); + } + }) + .when(dataManager) + .getChannelRecordCount(); + Mockito.doAnswer( + new Answer>() { + @Override + public Collection answer(InvocationOnMock invocation) + throws Throwable { + return channelRecordSortedMap.values(); + } + }) + .when(dataManager) + .getChannelRecords(); + Mockito.doAnswer( + new Answer() { + @Override + public ChannelRecord answer(InvocationOnMock invocation) + throws Throwable { + long channelId = (long) invocation.getArguments()[0]; + return channelRecordSortedMap.get(channelId); + } + }) + .when(dataManager) + .getChannelRecord(Matchers.anyLong()); return dataManager; } @@ -82,9 +88,9 @@ public class RecommendationUtils { } /** - * Add new {@code numberOfChannels} channels by adding channel record to - * {@code channelRecordMap} with no history. - * This action corresponds to loading channels in the RecommendationDataManger. + * Add new {@code numberOfChannels} channels by adding channel record to {@code + * channelRecordMap} with no history. This action corresponds to loading channels in the + * RecommendationDataManger. */ public void addChannels(int numberOfChannels) { for (int i = 0; i < numberOfChannels; ++i) { @@ -107,14 +113,14 @@ public class RecommendationUtils { } /** - * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}. - * Add until latest watch end time becomes bigger than {@code watchEndTimeMs}, - * starting from {@code watchStartTimeMs}. + * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}. Add until + * latest watch end time becomes bigger than {@code watchEndTimeMs}, starting from {@code + * watchStartTimeMs}. * * @return true if adding watch log success, otherwise false. */ - public boolean addRandomWatchLogs(long watchStartTimeMs, long watchEndTimeMs, - long maxWatchDurationMs) { + public boolean addRandomWatchLogs( + long watchStartTimeMs, long watchEndTimeMs, long maxWatchDurationMs) { long latestWatchEndTimeMs = watchStartTimeMs; long previousChannelId = INVALID_CHANNEL_ID; List channelIdList = new ArrayList<>(keySet()); @@ -143,13 +149,13 @@ public class RecommendationUtils { */ public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) { ChannelRecord channelRecord = get(channelId); - if (channelRecord == null || - watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) { + if (channelRecord == null + || watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) { return false; } - channelRecord.logWatchHistory(new WatchedProgram(null, watchStartTimeMs, - watchStartTimeMs + durationTimeMs)); + channelRecord.logWatchHistory( + new WatchedProgram(null, watchStartTimeMs, watchStartTimeMs + durationTimeMs)); if (mRecommender != null) { mRecommender.onNewWatchLog(channelRecord); } diff --git a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java index 85524a82..062633a5 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java @@ -23,14 +23,9 @@ 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.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.testing.Utils; - -import org.junit.Before; -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -39,6 +34,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; @SmallTest public class RecommenderTest { @@ -49,24 +46,27 @@ public class RecommenderTest { System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1); - private final Comparator CHANNEL_SORT_KEY_COMPARATOR = new Comparator() { - @Override - public int compare(Channel lhs, Channel rhs) { - return mRecommender.getChannelSortKey(lhs.getId()) - .compareTo(mRecommender.getChannelSortKey(rhs.getId())); - } - }; - private final Runnable START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS = new Runnable() { - @Override - public void run() { - // Add 4 channels in ChannelRecordMap for testing. Store the added channels to - // mChannels_1 ~ mChannels_4. They are sorted by channel id in increasing order. - mChannel_1 = mChannelRecordSortedMap.addChannel(); - mChannel_2 = mChannelRecordSortedMap.addChannel(); - mChannel_3 = mChannelRecordSortedMap.addChannel(); - mChannel_4 = mChannelRecordSortedMap.addChannel(); - } - }; + private final Comparator CHANNEL_SORT_KEY_COMPARATOR = + new Comparator() { + @Override + public int compare(Channel lhs, Channel rhs) { + return mRecommender + .getChannelSortKey(lhs.getId()) + .compareTo(mRecommender.getChannelSortKey(rhs.getId())); + } + }; + private final Runnable START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS = + new Runnable() { + @Override + public void run() { + // Add 4 channels in ChannelRecordMap for testing. Store the added channels to + // mChannels_1 ~ mChannels_4. They are sorted by channel id in increasing order. + mChannel_1 = mChannelRecordSortedMap.addChannel(); + mChannel_2 = mChannelRecordSortedMap.addChannel(); + mChannel_3 = mChannelRecordSortedMap.addChannel(); + mChannel_4 = mChannelRecordSortedMap.addChannel(); + } + }; private RecommendationDataManager mDataManager; private Recommender mRecommender; @@ -82,8 +82,8 @@ public class RecommenderTest { @Before public void setUp() { mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext()); - mDataManager = RecommendationUtils - .createMockRecommendationDataManager(mChannelRecordSortedMap); + mDataManager = + RecommendationUtils.createMockRecommendationDataManager(mChannelRecordSortedMap); mChannelRecordSortedMap.resetRandom(Utils.createTestRandom()); } @@ -121,16 +121,16 @@ public class RecommenderTest { // recommendChannels must be sorted by score in decreasing order. // (i.e. sorted by channel ID in decreasing order in this case) - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(), mChannel_4, mChannel_3, mChannel_2, mChannel_1); assertEquals(0, mRecommender.recommendChannels(-5).size()); assertEquals(0, mRecommender.recommendChannels(0).size()); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(3), - mChannel_4, mChannel_3, mChannel_2); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(4), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(5), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(3), mChannel_4, mChannel_3, mChannel_2); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(4), mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(5), mChannel_4, mChannel_3, mChannel_2, mChannel_1); } @Test @@ -141,16 +141,16 @@ public class RecommenderTest { // recommendChannels must be sorted by score in decreasing order. // (i.e. sorted by channel ID in decreasing order in this case) - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(), mChannel_4, mChannel_3, mChannel_2, mChannel_1); assertEquals(0, mRecommender.recommendChannels(-5).size()); assertEquals(0, mRecommender.recommendChannels(0).size()); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(3), - mChannel_4, mChannel_3, mChannel_2); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(4), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); - MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(5), - mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(3), mChannel_4, mChannel_3, mChannel_2); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(4), mChannel_4, mChannel_3, mChannel_2, mChannel_1); + MoreAsserts.assertContentsInOrder( + mRecommender.recommendChannels(5), mChannel_4, mChannel_3, mChannel_2, mChannel_1); } @Test @@ -161,16 +161,16 @@ public class RecommenderTest { mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); // Only two channels are recommended because recommender doesn't recommend other channels. - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(), mChannel_1, mChannel_2); assertEquals(0, mRecommender.recommendChannels(-5).size()); assertEquals(0, mRecommender.recommendChannels(0).size()); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(3), - mChannel_1, mChannel_2); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(4), - mChannel_1, mChannel_2); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(5), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(3), mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(4), mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(5), mChannel_1, mChannel_2); } @Test @@ -181,23 +181,23 @@ public class RecommenderTest { mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); assertEquals(4, mRecommender.recommendChannels().size()); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels().subList(0, 2), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels().subList(0, 2), mChannel_1, mChannel_2); assertEquals(0, mRecommender.recommendChannels(-5).size()); assertEquals(0, mRecommender.recommendChannels(0).size()); assertEquals(3, mRecommender.recommendChannels(3).size()); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(3).subList(0, 2), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(3).subList(0, 2), mChannel_1, mChannel_2); assertEquals(4, mRecommender.recommendChannels(4).size()); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(4).subList(0, 2), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(4).subList(0, 2), mChannel_1, mChannel_2); assertEquals(4, mRecommender.recommendChannels(5).size()); - MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(5).subList(0, 2), - mChannel_1, mChannel_2); + MoreAsserts.assertContentsInAnyOrder( + mRecommender.recommendChannels(5).subList(0, 2), mChannel_1, mChannel_2); } @Test @@ -224,7 +224,8 @@ public class RecommenderTest { List expectedChannelList = mRecommender.recommendChannels(3); // A channel which is not recommended by the recommender has to get an invalid sort key. - assertEquals(Recommender.INVALID_CHANNEL_SORT_KEY, + assertEquals( + Recommender.INVALID_CHANNEL_SORT_KEY, mRecommender.getChannelSortKey(mChannel_1.getId())); List channelList = Arrays.asList(mChannel_2, mChannel_3, mChannel_4); @@ -249,8 +250,9 @@ public class RecommenderTest { for (long channelId : mChannelRecordSortedMap.keySet()) { mEvaluator.setChannelScore(channelId, 1.0); // Add a log to recalculate the recommendation score. - assertTrue(mChannelRecordSortedMap.addWatchLog(channelId, latestWatchEndTimeMs, - TimeUnit.MINUTES.toMillis(10))); + assertTrue( + mChannelRecordSortedMap.addWatchLog( + channelId, latestWatchEndTimeMs, TimeUnit.MINUTES.toMillis(10))); latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(10); } @@ -261,14 +263,18 @@ public class RecommenderTest { @Test public void testListener_onRecommenderReady() { - createRecommender(true, new Runnable() { - @Override - public void run() { - mChannelRecordSortedMap.addChannels(DEFAULT_NUMBER_OF_CHANNELS); - mChannelRecordSortedMap.addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, - DEFAULT_WATCH_END_TIME_MS, DEFAULT_MAX_WATCH_DURATION_MS); - } - }); + createRecommender( + true, + new Runnable() { + @Override + public void run() { + mChannelRecordSortedMap.addChannels(DEFAULT_NUMBER_OF_CHANNELS); + mChannelRecordSortedMap.addRandomWatchLogs( + DEFAULT_WATCH_START_TIME_MS, + DEFAULT_WATCH_END_TIME_MS, + DEFAULT_MAX_WATCH_DURATION_MS); + } + }); // After loading channels and watch logs are finished, recommender must be available to use. assertTrue(mOnRecommenderReady); @@ -276,23 +282,29 @@ public class RecommenderTest { private void assertSortKeyNotInvalid(List channelList) { for (Channel channel : channelList) { - MoreAsserts.assertNotEqual(Recommender.INVALID_CHANNEL_SORT_KEY, + MoreAsserts.assertNotEqual( + Recommender.INVALID_CHANNEL_SORT_KEY, mRecommender.getChannelSortKey(channel.getId())); } } - private void createRecommender(boolean includeRecommendedOnly, - Runnable startDataManagerRunnable) { - mRecommender = new Recommender(new Recommender.Listener() { - @Override - public void onRecommenderReady() { - mOnRecommenderReady = true; - } - @Override - public void onRecommendationChanged() { - mOnRecommendationChanged = true; - } - }, includeRecommendedOnly, mDataManager); + private void createRecommender( + boolean includeRecommendedOnly, Runnable startDataManagerRunnable) { + mRecommender = + new Recommender( + new Recommender.Listener() { + @Override + public void onRecommenderReady() { + mOnRecommenderReady = true; + } + + @Override + public void onRecommendationChanged() { + mOnRecommendationChanged = true; + } + }, + includeRecommendedOnly, + mDataManager); mEvaluator = new FakeEvaluator(); mRecommender.registerEvaluator(mEvaluator); diff --git a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java index 7b8e256d..3c972e93 100644 --- a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java @@ -20,17 +20,14 @@ import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; - import com.android.tv.data.Program; import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime; - -import org.junit.Test; - import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.TreeSet; import java.util.concurrent.TimeUnit; +import org.junit.Test; @SmallTest public class RoutineWatchEvaluatorTest extends EvaluatorTestCase { @@ -102,7 +99,6 @@ public class RoutineWatchEvaluatorTest extends EvaluatorTestCase wordList1 = RoutineWatchEvaluator.splitTextToWords(text1); List wordList2 = RoutineWatchEvaluator.splitTextToWords(text2); - assertEquals("MaximumMatchedWordSequenceLength", expectedLength, + assertEquals( + "MaximumMatchedWordSequenceLength", + expectedLength, RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength( wordList1, wordList2)); - assertEquals("MaximumMatchedWordSequenceLength", expectedLength, + assertEquals( + "MaximumMatchedWordSequenceLength", + expectedLength, RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength( wordList2, wordList1)); } - private void assertProgramTime(int expectedWeekDay, int expectedStartTimeOfDayInSec, - int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime) { + private void assertProgramTime( + int expectedWeekDay, + int expectedStartTimeOfDayInSec, + int expectedEndTimeOfDayInSec, + ProgramTime actualProgramTime) { assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay); - assertEquals("StartTimeOfDayInSec", expectedStartTimeOfDayInSec, + assertEquals( + "StartTimeOfDayInSec", + expectedStartTimeOfDayInSec, actualProgramTime.startTimeOfDayInSec); - assertEquals("EndTimeOfDayInSec", expectedEndTimeOfDayInSec, + assertEquals( + "EndTimeOfDayInSec", + expectedEndTimeOfDayInSec, actualProgramTime.endTimeOfDayInSec); } - private void assertOverlappedIntervalScore(int expectedSeconds, boolean overlappedOnSameDay, - ProgramTime t1, ProgramTime t2) { + private void assertOverlappedIntervalScore( + int expectedSeconds, boolean overlappedOnSameDay, ProgramTime t1, ProgramTime t2) { double score = expectedSeconds; if (!overlappedOnSameDay) { score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK; } // Two tests for testing commutative law. - assertEqualScores("OverlappedIntervalScore", score, + assertEqualScores( + "OverlappedIntervalScore", + score, RoutineWatchEvaluator.calculateOverlappedIntervalScore(t1, t2)); - assertEqualScores("OverlappedIntervalScore", score, + assertEqualScores( + "OverlappedIntervalScore", + score, RoutineWatchEvaluator.calculateOverlappedIntervalScore(t2, t1)); } @@ -270,8 +309,10 @@ public class RoutineWatchEvaluatorTest extends EvaluatorTestCase void assertInOrder(T... items) { diff --git a/tests/unit/src/com/android/tv/search/LocalSearchProviderTest.java b/tests/unit/src/com/android/tv/search/LocalSearchProviderTest.java index b0d342c6..9ac81301 100644 --- a/tests/unit/src/com/android/tv/search/LocalSearchProviderTest.java +++ b/tests/unit/src/com/android/tv/search/LocalSearchProviderTest.java @@ -25,12 +25,10 @@ import android.database.Cursor; import android.net.Uri; import android.support.test.filters.SmallTest; import android.test.ProviderTestCase2; - import com.android.tv.ApplicationSingletons; import com.android.tv.TvApplication; import com.android.tv.perf.PerformanceMonitor; import com.android.tv.util.MockApplicationSingletons; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -42,10 +40,16 @@ import org.mockito.MockitoAnnotations; public class LocalSearchProviderTest extends ProviderTestCase2 { private static final String AUTHORITY = "com.android.tv.search"; private static final String KEYWORD = "keyword"; - private static final Uri BASE_SEARCH_URI = Uri.parse("content://" + AUTHORITY + "/" - + SearchManager.SUGGEST_URI_PATH_QUERY + "/" + KEYWORD); - private static final Uri WRONG_SERACH_URI = Uri.parse("content://" + AUTHORITY + "/wrong_path/" - + KEYWORD); + private static final Uri BASE_SEARCH_URI = + Uri.parse( + "content://" + + AUTHORITY + + "/" + + SearchManager.SUGGEST_URI_PATH_QUERY + + "/" + + KEYWORD); + private static final Uri WRONG_SERACH_URI = + Uri.parse("content://" + AUTHORITY + "/wrong_path/" + KEYWORD); private ApplicationSingletons mOldAppSingletons; MockApplicationSingletons mMockAppSingletons; @@ -109,23 +113,26 @@ public class LocalSearchProviderTest extends ProviderTestCase2 SearchInterface.ACTION_TYPE_END) ? - LocalSearchProvider.DEFAULT_SEARCH_ACTION : action; + int expectedLimit = + limit == null || limit <= 0 ? LocalSearchProvider.DEFAULT_SEARCH_LIMIT : limit; + int expectedAction = + (action == null + || action < SearchInterface.ACTION_TYPE_START + || action > SearchInterface.ACTION_TYPE_END) + ? LocalSearchProvider.DEFAULT_SEARCH_ACTION + : action; verify(mMockSearchInterface).search(KEYWORD, expectedLimit, expectedAction); clearInvocations(mMockSearchInterface); } diff --git a/tests/unit/src/com/android/tv/tests/TvActivityTest.java b/tests/unit/src/com/android/tv/tests/TvActivityTest.java index aa33f770..346b9787 100644 --- a/tests/unit/src/com/android/tv/tests/TvActivityTest.java +++ b/tests/unit/src/com/android/tv/tests/TvActivityTest.java @@ -22,10 +22,8 @@ import static org.junit.Assert.assertTrue; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; - import com.android.tv.TvActivity; import com.android.tv.testing.Utils; - import org.junit.Rule; import org.junit.Test; diff --git a/tests/unit/src/com/android/tv/util/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/ImageCacheTest.java index a76194b8..8352f7dc 100644 --- a/tests/unit/src/com/android/tv/util/ImageCacheTest.java +++ b/tests/unit/src/com/android/tv/util/ImageCacheTest.java @@ -21,15 +21,11 @@ import static org.junit.Assert.assertSame; import android.graphics.Bitmap; import android.support.test.filters.MediumTest; - import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; - import org.junit.Before; import org.junit.Test; -/** - * Tests for {@link ImageCache}. - */ +/** Tests for {@link ImageCache}. */ @MediumTest public class ImageCacheTest { private static final Bitmap ORIG = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565); @@ -47,21 +43,21 @@ public class ImageCacheTest { mImageCache = ImageCache.newInstance(0.1f); } - //TODO: Empty the cache in the setup. Try using @VisibleForTesting + // TODO: Empty the cache in the setup. Try using @VisibleForTesting @Test public void testPutIfLarger_smaller() throws Exception { - mImageCache.putIfNeeded( INFO_50); + mImageCache.putIfNeeded(INFO_50); assertSame("before", INFO_50, mImageCache.get(KEY)); - mImageCache.putIfNeeded( INFO_25); + mImageCache.putIfNeeded(INFO_25); assertSame("after", INFO_50, mImageCache.get(KEY)); } @Test public void testPutIfLarger_larger() throws Exception { - mImageCache.putIfNeeded( INFO_50); + mImageCache.putIfNeeded(INFO_50); assertSame("before", INFO_50, mImageCache.get(KEY)); mImageCache.putIfNeeded(INFO_100); @@ -71,10 +67,10 @@ public class ImageCacheTest { @Test public void testPutIfLarger_alreadyMax() throws Exception { - mImageCache.putIfNeeded( INFO_100); + mImageCache.putIfNeeded(INFO_100); assertSame("before", INFO_100, mImageCache.get(KEY)); - mImageCache.putIfNeeded( INFO_200); + mImageCache.putIfNeeded(INFO_200); assertSame("after", INFO_100, mImageCache.get(KEY)); } -} \ No newline at end of file +} diff --git a/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java b/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java index 4cfc7f8a..4eb0c4d7 100644 --- a/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java +++ b/tests/unit/src/com/android/tv/util/MockApplicationSingletons.java @@ -17,7 +17,6 @@ package com.android.tv.util; import android.content.Context; - import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.MainActivityWrapper; @@ -36,9 +35,7 @@ import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.recorder.RecordingScheduler; import com.android.tv.perf.PerformanceMonitor; -/** - * Mock {@link ApplicationSingletons} class. - */ +/** Mock {@link ApplicationSingletons} class. */ public class MockApplicationSingletons implements ApplicationSingletons { private final TvApplication mApp; diff --git a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java index 7335f207..42325c83 100644 --- a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java +++ b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java @@ -21,14 +21,10 @@ import static org.junit.Assert.assertSame; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; - -import org.junit.Test; - import java.util.Collections; +import org.junit.Test; -/** - * Tests for {@link MultiLongSparseArray}. - */ +/** Tests for {@link MultiLongSparseArray}. */ @SmallTest public class MultiLongSparseArrayTest { @Test @@ -52,7 +48,6 @@ public class MultiLongSparseArrayTest { MoreAsserts.assertContentsInAnyOrder(sparseArray.get(0), "foo", "bar"); } - @Test public void testClearEmptyCache() { MultiLongSparseArray sparseArray = new MultiLongSparseArray<>(); @@ -76,8 +71,8 @@ public class MultiLongSparseArrayTest { for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) { sparseArray.remove(i, "foo"); } - assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT, - sparseArray.getEmptyCacheSize()); + assertEquals( + MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT, sparseArray.getEmptyCacheSize()); sparseArray.clearEmptyCache(); assertEquals(0, sparseArray.getEmptyCacheSize()); } @@ -95,13 +90,14 @@ public class MultiLongSparseArrayTest { for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) { sparseArray.remove(i, "foo"); } - assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT, - sparseArray.getEmptyCacheSize()); + assertEquals( + MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT, sparseArray.getEmptyCacheSize()); // now create elements, that use the cached empty sets. for (int i = 0; i < MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT; i++) { sparseArray.put(10 + i, "bar"); - assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT - i - 1, + assertEquals( + MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT - i - 1, sparseArray.getEmptyCacheSize()); } } diff --git a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java index 2714e2e9..87ae5131 100644 --- a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java +++ b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java @@ -4,14 +4,10 @@ import static org.junit.Assert.assertEquals; import android.graphics.Bitmap; import android.support.test.filters.SmallTest; - import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; - import org.junit.Test; -/** - * Tests for {@link ScaledBitmapInfo}. - */ +/** Tests for {@link ScaledBitmapInfo}. */ @SmallTest public class ScaledBitmapInfoTest { private static final Bitmap B80x100 = Bitmap.createBitmap(80, 100, Bitmap.Config.RGB_565); @@ -33,25 +29,28 @@ public class ScaledBitmapInfoTest { assertNeedsToReload(true, actual, 101, 101); } - /** - * Reproduces b/20488453. - */ + /** Reproduces b/20488453. */ @Test public void testBug20488453() { - ScaledBitmapInfo actual = BitmapUtils - .createScaledBitmapInfo("B960x1440", B960x1440, 284, 160); + ScaledBitmapInfo actual = + BitmapUtils.createScaledBitmapInfo("B960x1440", B960x1440, 284, 160); assertScaledBitmapSize(8, 107, 160, actual); assertNeedsToReload(false, actual, 284, 160); } - private static void assertNeedsToReload(boolean expected, ScaledBitmapInfo scaledBitmap, - int reqWidth, int reqHeight) { - assertEquals(scaledBitmap.id + " needToReload(" + reqWidth + "," + reqHeight + ")", - expected, scaledBitmap.needToReload(reqWidth, reqHeight)); + private static void assertNeedsToReload( + boolean expected, ScaledBitmapInfo scaledBitmap, int reqWidth, int reqHeight) { + assertEquals( + scaledBitmap.id + " needToReload(" + reqWidth + "," + reqHeight + ")", + expected, + scaledBitmap.needToReload(reqWidth, reqHeight)); } - private static void assertScaledBitmapSize(int expectedInSampleSize, int expectedWidth, - int expectedHeight, ScaledBitmapInfo actual) { + private static void assertScaledBitmapSize( + int expectedInSampleSize, + int expectedWidth, + int expectedHeight, + ScaledBitmapInfo actual) { assertEquals(actual.id + " inSampleSize", expectedInSampleSize, actual.inSampleSize); assertEquals(actual.id + " width", expectedWidth, actual.bitmap.getWidth()); assertEquals(actual.id + " height", expectedHeight, actual.bitmap.getHeight()); diff --git a/tests/unit/src/com/android/tv/util/TestUtils.java b/tests/unit/src/com/android/tv/util/TestUtils.java index d200733d..92669070 100644 --- a/tests/unit/src/com/android/tv/util/TestUtils.java +++ b/tests/unit/src/com/android/tv/util/TestUtils.java @@ -23,83 +23,159 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.media.tv.TvInputInfo; import android.os.Build; import android.os.Bundle; - import java.lang.reflect.Constructor; -/** - * A class that includes convenience methods for testing. - */ +/** A class that includes convenience methods for testing. */ public class TestUtils { - /** - * Creates a {@link TvInputInfo}. - */ - public static TvInputInfo createTvInputInfo(ResolveInfo service, String id, String parentId, - int type, boolean isHardwareInput) throws Exception { + /** Creates a {@link TvInputInfo}. */ + public static TvInputInfo createTvInputInfo( + ResolveInfo service, String id, String parentId, int type, boolean isHardwareInput) + throws Exception { return createTvInputInfo(service, id, parentId, type, isHardwareInput, false, 0); } /** * Creates a {@link TvInputInfo}. - *

- * If this is called on MNC, {@code canRecord} and {@code tunerCount} are ignored. + * + *

If this is called on MNC, {@code canRecord} and {@code tunerCount} are ignored. */ - public static TvInputInfo createTvInputInfo(ResolveInfo service, String id, String parentId, - int type, boolean isHardwareInput, boolean canRecord, int tunerCount) throws Exception { + public static TvInputInfo createTvInputInfo( + ResolveInfo service, + String id, + String parentId, + int type, + boolean isHardwareInput, + boolean canRecord, + int tunerCount) + throws Exception { // Create a mock TvInputInfo by using private constructor // Note that mockito doesn't support mock/spy on final object. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return createTvInputInfoForO(service, id, parentId, type, isHardwareInput, canRecord, - tunerCount); + return createTvInputInfoForO( + service, id, parentId, type, isHardwareInput, canRecord, tunerCount); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return createTvInputInfoForNyc(service, id, parentId, type, isHardwareInput, canRecord, - tunerCount); + return createTvInputInfoForNyc( + service, id, parentId, type, isHardwareInput, canRecord, tunerCount); } return createTvInputInfoForMnc(service, id, parentId, type, isHardwareInput); } /** * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, - * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, - * String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, - * boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { + * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, + * String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, + * boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { */ - private static TvInputInfo createTvInputInfoForO(ResolveInfo service, String id, - String parentId, int type, boolean isHardwareInput, boolean canRecord, int tunerCount) + private static TvInputInfo createTvInputInfoForO( + ResolveInfo service, + String id, + String parentId, + int type, + boolean isHardwareInput, + boolean canRecord, + int tunerCount) throws Exception { - Constructor constructor = TvInputInfo.class.getDeclaredConstructor( - ResolveInfo.class, String.class, int.class, boolean.class, CharSequence.class, - int.class, Icon.class, Icon.class, Icon.class, String.class, boolean.class, - int.class, HdmiDeviceInfo.class, boolean.class, String.class, Bundle.class); + Constructor constructor = + TvInputInfo.class.getDeclaredConstructor( + ResolveInfo.class, + String.class, + int.class, + boolean.class, + CharSequence.class, + int.class, + Icon.class, + Icon.class, + Icon.class, + String.class, + boolean.class, + int.class, + HdmiDeviceInfo.class, + boolean.class, + String.class, + Bundle.class); constructor.setAccessible(true); - return constructor.newInstance(service, id, type, isHardwareInput, null, 0, null, null, - null, null, canRecord, tunerCount, null, false, parentId, null); + return constructor.newInstance( + service, + id, + type, + isHardwareInput, + null, + 0, + null, + null, + null, + null, + canRecord, + tunerCount, + null, + false, + parentId, + null); } /** * private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, - * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, - * String setupActivity, String settingsActivity, boolean canRecord, int tunerCount, - * HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, - * Bundle extras) { + * CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, + * String setupActivity, String settingsActivity, boolean canRecord, int tunerCount, + * HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, Bundle + * extras) { */ - private static TvInputInfo createTvInputInfoForNyc(ResolveInfo service, String id, - String parentId, int type, boolean isHardwareInput, boolean canRecord, int tunerCount) + private static TvInputInfo createTvInputInfoForNyc( + ResolveInfo service, + String id, + String parentId, + int type, + boolean isHardwareInput, + boolean canRecord, + int tunerCount) throws Exception { - Constructor constructor = TvInputInfo.class.getDeclaredConstructor( - ResolveInfo.class, String.class, int.class, boolean.class, CharSequence.class, - int.class, Icon.class, Icon.class, Icon.class, String.class, String.class, - boolean.class, int.class, HdmiDeviceInfo.class, boolean.class, String.class, - Bundle.class); + Constructor constructor = + TvInputInfo.class.getDeclaredConstructor( + ResolveInfo.class, + String.class, + int.class, + boolean.class, + CharSequence.class, + int.class, + Icon.class, + Icon.class, + Icon.class, + String.class, + String.class, + boolean.class, + int.class, + HdmiDeviceInfo.class, + boolean.class, + String.class, + Bundle.class); constructor.setAccessible(true); - return constructor.newInstance(service, id, type, isHardwareInput, null, 0, null, null, - null, null, null, canRecord, tunerCount, null, false, parentId, null); + return constructor.newInstance( + service, + id, + type, + isHardwareInput, + null, + 0, + null, + null, + null, + null, + null, + canRecord, + tunerCount, + null, + false, + parentId, + null); } - private static TvInputInfo createTvInputInfoForMnc(ResolveInfo service, String id, - String parentId, int type, boolean isHardwareInput) throws Exception { - Constructor constructor = TvInputInfo.class.getDeclaredConstructor( - ResolveInfo.class, String.class, String.class, int.class, boolean.class); + private static TvInputInfo createTvInputInfoForMnc( + ResolveInfo service, String id, String parentId, int type, boolean isHardwareInput) + throws Exception { + Constructor constructor = + TvInputInfo.class.getDeclaredConstructor( + ResolveInfo.class, String.class, String.class, int.class, boolean.class); constructor.setAccessible(true); return constructor.newInstance(service, id, parentId, type, isHardwareInput); } diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java index 404ee5d3..44c4477e 100644 --- a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java +++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java @@ -21,21 +21,16 @@ import static android.support.test.InstrumentationRegistry.getContext; import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; - import com.android.tv.testing.ComparatorTester; - +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * Test for {@link TvInputManagerHelper} - */ +/** Test for {@link TvInputManagerHelper} */ @SmallTest public class TvInputManagerHelperTest { final HashMap TEST_INPUT_MAP = new HashMap<>(); @@ -45,18 +40,51 @@ public class TvInputManagerHelperTest { ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); List inputs = new ArrayList<>(); - inputs.add(createTvInputInfo(resolveInfo, "2_partner_input", null, 0, false, - "2_partner_input", null, true)); - inputs.add(createTvInputInfo(resolveInfo, "3_partner_input", null, 0, false, - "3_partner_input", null, true)); - inputs.add(createTvInputInfo(resolveInfo, "1_3rd_party_input", null, 0, false, - "1_3rd_party_input", null, false)); - inputs.add(createTvInputInfo(resolveInfo, "4_3rd_party_input", null, 0, false, - "4_3rd_party_input", null, false)); + inputs.add( + createTvInputInfo( + resolveInfo, + "2_partner_input", + null, + 0, + false, + "2_partner_input", + null, + true)); + inputs.add( + createTvInputInfo( + resolveInfo, + "3_partner_input", + null, + 0, + false, + "3_partner_input", + null, + true)); + inputs.add( + createTvInputInfo( + resolveInfo, + "1_3rd_party_input", + null, + 0, + false, + "1_3rd_party_input", + null, + false)); + inputs.add( + createTvInputInfo( + resolveInfo, + "4_3rd_party_input", + null, + 0, + false, + "4_3rd_party_input", + null, + false)); TvInputManagerHelper manager = createMockTvInputManager(); - ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + ComparatorTester comparatorTester = + ComparatorTester.withoutEqualsTest( new TvInputManagerHelper.InputComparatorInternal(manager)); for (TvInputInfo input : inputs) { comparatorTester.addComparableGroup(input); @@ -68,20 +96,54 @@ public class TvInputManagerHelperTest { public void testHardwareInputComparatorHdmi() { ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); - TvInputInfo hdmi1 = createTvInputInfo(resolveInfo, "HDMI1", null, TvInputInfo.TYPE_HDMI, - true, "HDMI1", null, false); - TvInputInfo hdmi2 = createTvInputInfo(resolveInfo, "HDMI2", null, TvInputInfo.TYPE_HDMI, - true, "HDMI2", "DVD", false); - TvInputInfo hdmi3 = createTvInputInfo(resolveInfo, "HDMI3", null, TvInputInfo.TYPE_HDMI, - true, "HDMI3", "Cable", false); - TvInputInfo hdmi4 = createTvInputInfo(resolveInfo, "HDMI4", null, TvInputInfo.TYPE_HDMI, - true, "HDMI4", null, false); + TvInputInfo hdmi1 = + createTvInputInfo( + resolveInfo, + "HDMI1", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI1", + null, + false); + TvInputInfo hdmi2 = + createTvInputInfo( + resolveInfo, + "HDMI2", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI2", + "DVD", + false); + TvInputInfo hdmi3 = + createTvInputInfo( + resolveInfo, + "HDMI3", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI3", + "Cable", + false); + TvInputInfo hdmi4 = + createTvInputInfo( + resolveInfo, + "HDMI4", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI4", + null, + false); TvInputManagerHelper manager = createMockTvInputManager(); - ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + ComparatorTester comparatorTester = + ComparatorTester.withoutEqualsTest( new TvInputManagerHelper.HardwareInputComparator(getContext(), manager)); - comparatorTester.addComparableGroup(hdmi3) + comparatorTester + .addComparableGroup(hdmi3) .addComparableGroup(hdmi2) .addComparableGroup(hdmi1) .addComparableGroup(hdmi4) @@ -92,61 +154,111 @@ public class TvInputManagerHelperTest { public void testHardwareInputComparatorCec() { ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test"); - TvInputInfo hdmi1 = createTvInputInfo(resolveInfo, "HDMI1", null, TvInputInfo.TYPE_HDMI, - true, "HDMI1", null, false); - TvInputInfo hdmi2 = createTvInputInfo(resolveInfo, "HDMI2", null, TvInputInfo.TYPE_HDMI, - true, "HDMI2", null, false); + TvInputInfo hdmi1 = + createTvInputInfo( + resolveInfo, + "HDMI1", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI1", + null, + false); + TvInputInfo hdmi2 = + createTvInputInfo( + resolveInfo, + "HDMI2", + null, + TvInputInfo.TYPE_HDMI, + true, + "HDMI2", + null, + false); - TvInputInfo cec1 = createTvInputInfo(resolveInfo, "2_cec", "HDMI1", TvInputInfo.TYPE_HDMI, - true, "2_cec", null, false); - TvInputInfo cec2 = createTvInputInfo(resolveInfo, "1_cec", "HDMI2", TvInputInfo.TYPE_HDMI, - true, "1_cec", null, false); + TvInputInfo cec1 = + createTvInputInfo( + resolveInfo, + "2_cec", + "HDMI1", + TvInputInfo.TYPE_HDMI, + true, + "2_cec", + null, + false); + TvInputInfo cec2 = + createTvInputInfo( + resolveInfo, + "1_cec", + "HDMI2", + TvInputInfo.TYPE_HDMI, + true, + "1_cec", + null, + false); TvInputManagerHelper manager = createMockTvInputManager(); - ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest( + ComparatorTester comparatorTester = + ComparatorTester.withoutEqualsTest( new TvInputManagerHelper.HardwareInputComparator(getContext(), manager)); - comparatorTester.addComparableGroup(cec1) - .addComparableGroup(cec2) - .test(); + comparatorTester.addComparableGroup(cec1).addComparableGroup(cec2).test(); } private TvInputManagerHelper createMockTvInputManager() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); - Mockito.doAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; - return TEST_INPUT_MAP.get(info.getId()).mIsPartnerInput; - } - }).when(manager).isPartnerInput(Mockito.any()); - Mockito.doAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; - return TEST_INPUT_MAP.get(info.getId()).mLabel; - } - }).when(manager).loadLabel(Mockito.any()); - Mockito.doAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; - return TEST_INPUT_MAP.get(info.getId()).mCustomLabel; - } - }).when(manager).loadCustomLabel(Mockito.any()); - Mockito.doAnswer(new Answer() { - @Override - public TvInputInfo answer(InvocationOnMock invocation) throws Throwable { - String inputId = (String) invocation.getArguments()[0]; - TvInputInfoWrapper inputWrapper = TEST_INPUT_MAP.get(inputId); - return inputWrapper == null ? null : inputWrapper.mInput; - } - }).when(manager).getTvInputInfo(Mockito.any()); + Mockito.doAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; + return TEST_INPUT_MAP.get(info.getId()).mIsPartnerInput; + } + }) + .when(manager) + .isPartnerInput(Mockito.any()); + Mockito.doAnswer( + new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; + return TEST_INPUT_MAP.get(info.getId()).mLabel; + } + }) + .when(manager) + .loadLabel(Mockito.any()); + Mockito.doAnswer( + new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + TvInputInfo info = (TvInputInfo) invocation.getArguments()[0]; + return TEST_INPUT_MAP.get(info.getId()).mCustomLabel; + } + }) + .when(manager) + .loadCustomLabel(Mockito.any()); + Mockito.doAnswer( + new Answer() { + @Override + public TvInputInfo answer(InvocationOnMock invocation) + throws Throwable { + String inputId = (String) invocation.getArguments()[0]; + TvInputInfoWrapper inputWrapper = TEST_INPUT_MAP.get(inputId); + return inputWrapper == null ? null : inputWrapper.mInput; + } + }) + .when(manager) + .getTvInputInfo(Mockito.any()); return manager; } - private TvInputInfo createTvInputInfo(ResolveInfo service, String id, - String parentId, int type, boolean isHardwareInput, String label, String customLabel, + private TvInputInfo createTvInputInfo( + ResolveInfo service, + String id, + String parentId, + int type, + boolean isHardwareInput, + String label, + String customLabel, boolean isPartnerInput) { TvInputInfoWrapper inputWrapper = new TvInputInfoWrapper(); try { diff --git a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java index 4512bb7d..e60aae05 100644 --- a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java +++ b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java @@ -20,19 +20,14 @@ import static org.junit.Assert.assertEquals; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; - import com.android.tv.testing.ComparatorTester; - -import org.junit.Test; - import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import org.junit.Test; -/** - * Tests for {@link com.android.tv.util.TvTrackInfoUtils}. - */ +/** Tests for {@link com.android.tv.util.TvTrackInfoUtils}. */ @SmallTest public class TvTrackInfoUtilsTest { private static final String UN_MATCHED_ID = "no matching ID"; @@ -54,10 +49,10 @@ public class TvTrackInfoUtilsTest { .build(); } - private static final List ALL = Arrays.asList(INFO_1_EN_1, INFO_2_EN_5, - INFO_3_FR_8, INFO_4_NULL_2, INFO_5_NULL_6); - private static final List NULL_LANGUAGE_TRACKS = Arrays.asList(INFO_4_NULL_2, - INFO_5_NULL_6); + private static final List ALL = + Arrays.asList(INFO_1_EN_1, INFO_2_EN_5, INFO_3_FR_8, INFO_4_NULL_2, INFO_5_NULL_6); + private static final List NULL_LANGUAGE_TRACKS = + Arrays.asList(INFO_4_NULL_2, INFO_5_NULL_6); @Test public void testGetBestTrackInfo_empty() { @@ -112,15 +107,17 @@ public class TvTrackInfoUtilsTest { Comparator comparator = TvTrackInfoUtils.createComparator("1", "en", 1); ComparatorTester.withoutEqualsTest(comparator) // lang not match - .addComparableGroup(create("1", "kr", 1), create("2", "kr", 2), + .addComparableGroup( + create("1", "kr", 1), + create("2", "kr", 2), create("1", "ja", 1), create("1", "ch", 1)) - // lang match not count match - .addComparableGroup(create("2", "en", 2), create("3", "en", 3), - create("1", "en", 2)) - // lang and count match + // lang match not count match + .addComparableGroup( + create("2", "en", 2), create("3", "en", 3), create("1", "en", 2)) + // lang and count match .addComparableGroup(create("2", "en", 1), create("3", "en", 1)) - // all match + // all match .addComparableGroup(create("1", "en", 1), create("1", "en", 1)) .test(); } diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java index e61802f5..f5eefc64 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java @@ -20,22 +20,20 @@ import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; import android.text.format.DateUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * Tests for {@link com.android.tv.util.Utils#getDurationString}. - *

- * This test uses deprecated flags {@link DateUtils#FORMAT_12HOUR} and - * {@link DateUtils#FORMAT_24HOUR} to run this test independent to system's 12/24h format. - * Note that changing system setting requires permission android.permission.WRITE_SETTINGS - * and it should be defined in TV app, not this test. + * + *

This test uses deprecated flags {@link DateUtils#FORMAT_12HOUR} and {@link + * DateUtils#FORMAT_24HOUR} to run this test independent to system's 12/24h format. Note that + * changing system setting requires permission android.permission.WRITE_SETTINGS and it should be + * defined in TV app, not this test. */ @SmallTest public class UtilsTest_GetDurationString { @@ -59,9 +57,7 @@ public class UtilsTest_GetDurationString { Locale.setDefault(mLocale); } - /** - * Return time in millis assuming that whose year is this year and month is Jan. - */ + /** Return time in millis assuming that whose year is this year and month is Jan. */ private static long getJanOfThisYearInMillis(int date, int hour, int minutes) { return new GregorianCalendar(getThisYear(), Calendar.JANUARY, date, hour, minutes) .getTimeInMillis(); @@ -71,9 +67,7 @@ public class UtilsTest_GetDurationString { return getJanOfThisYearInMillis(date, hour, 0); } - /** - * Return time in millis assuming that whose year is this year and month is Feb. - */ + /** Return time in millis assuming that whose year is this year and month is Feb. */ private static long getFebOfThisYearInMillis(int date, int hour, int minutes) { return new GregorianCalendar(getThisYear(), Calendar.FEBRUARY, date, hour, minutes) .getTimeInMillis(); @@ -89,184 +83,363 @@ public class UtilsTest_GetDurationString { @Test public void testSameDateAndTime() { - assertEquals("3:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(1, 3), false, + assertEquals( + "3:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(1, 3), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("03:00", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(1, 3), false, + assertEquals( + "03:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(1, 3), + false, DateUtils.FORMAT_24HOUR)); } @Test public void testDurationWithinToday() { - assertEquals("12:00 – 3:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), false, + assertEquals( + "12:00 – 3:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("00:00 – 03:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), false, + assertEquals( + "00:00 – 03:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + false, DateUtils.FORMAT_24HOUR)); } @Test public void testDurationFromYesterdayToToday() { - assertEquals("Jan 31, 3:00 AM – Feb 1, 4:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getJanOfThisYearInMillis(31, 3), getFebOfThisYearInMillis(1, 4), false, + assertEquals( + "Jan 31, 3:00 AM – Feb 1, 4:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getJanOfThisYearInMillis(31, 3), + getFebOfThisYearInMillis(1, 4), + false, + DateUtils.FORMAT_12HOUR)); + assertEquals( + "Jan 31, 03:00 – Feb 1, 04:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getJanOfThisYearInMillis(31, 3), + getFebOfThisYearInMillis(1, 4), + false, + DateUtils.FORMAT_24HOUR)); + assertEquals( + "1/31, 11:30 PM – 12:30 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getJanOfThisYearInMillis(31, 23, 30), + getFebOfThisYearInMillis(1, 0, 30), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("Jan 31, 03:00 – Feb 1, 04:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getJanOfThisYearInMillis(31, 3), getFebOfThisYearInMillis(1, 4), false, + assertEquals( + "1/31, 23:30 – 00:30", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getJanOfThisYearInMillis(31, 23, 30), + getFebOfThisYearInMillis(1, 0, 30), + true, DateUtils.FORMAT_24HOUR)); - assertEquals("1/31, 11:30 PM – 12:30 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getJanOfThisYearInMillis(31, 23, 30), getFebOfThisYearInMillis(1, 0, 30), - true, DateUtils.FORMAT_12HOUR)); - assertEquals("1/31, 23:30 – 00:30", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getJanOfThisYearInMillis(31, 23, 30), getFebOfThisYearInMillis(1, 0, 30), - true, DateUtils.FORMAT_24HOUR)); } @Test public void testDurationFromTodayToTomorrow() { - assertEquals("Feb 1, 3:00 AM – Feb 2, 4:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(2, 4), false, + assertEquals( + "Feb 1, 3:00 AM – Feb 2, 4:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(2, 4), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("Feb 1, 03:00 – Feb 2, 04:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(2, 4), false, + assertEquals( + "Feb 1, 03:00 – Feb 2, 04:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(2, 4), + false, DateUtils.FORMAT_24HOUR)); - assertEquals("2/1, 3:00 AM – 2/2, 4:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(2, 4), true, + assertEquals( + "2/1, 3:00 AM – 2/2, 4:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(2, 4), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("2/1, 03:00 – 2/2, 04:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 3), getFebOfThisYearInMillis(2, 4), true, + assertEquals( + "2/1, 03:00 – 2/2, 04:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 3), + getFebOfThisYearInMillis(2, 4), + true, DateUtils.FORMAT_24HOUR)); - assertEquals("Feb 1, 11:30 PM – Feb 2, 12:30 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23, 30), getFebOfThisYearInMillis(2, 0, 30), + assertEquals( + "Feb 1, 11:30 PM – Feb 2, 12:30 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23, 30), + getFebOfThisYearInMillis(2, 0, 30), false, DateUtils.FORMAT_12HOUR)); - assertEquals("Feb 1, 23:30 – Feb 2, 00:30", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23, 30), getFebOfThisYearInMillis(2, 0, 30), + assertEquals( + "Feb 1, 23:30 – Feb 2, 00:30", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23, 30), + getFebOfThisYearInMillis(2, 0, 30), false, DateUtils.FORMAT_24HOUR)); - assertEquals("11:30 PM – 12:30 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23, 30), getFebOfThisYearInMillis(2, 0, 30), + assertEquals( + "11:30 PM – 12:30 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23, 30), + getFebOfThisYearInMillis(2, 0, 30), true, DateUtils.FORMAT_12HOUR)); - assertEquals("23:30 – 00:30", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23, 30), getFebOfThisYearInMillis(2, 0, 30), + assertEquals( + "23:30 – 00:30", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23, 30), + getFebOfThisYearInMillis(2, 0, 30), true, DateUtils.FORMAT_24HOUR)); } @Test public void testDurationWithinTomorrow() { - assertEquals("Feb 2, 2:00 – 4:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 2), getFebOfThisYearInMillis(2, 4), false, + assertEquals( + "Feb 2, 2:00 – 4:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 2), + getFebOfThisYearInMillis(2, 4), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("Feb 2, 02:00 – 04:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 2), getFebOfThisYearInMillis(2, 4), false, + assertEquals( + "Feb 2, 02:00 – 04:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 2), + getFebOfThisYearInMillis(2, 4), + false, DateUtils.FORMAT_24HOUR)); - assertEquals("2/2, 2:00 – 4:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 2), getFebOfThisYearInMillis(2, 4), true, + assertEquals( + "2/2, 2:00 – 4:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 2), + getFebOfThisYearInMillis(2, 4), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("2/2, 02:00 – 04:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 2), getFebOfThisYearInMillis(2, 4), true, + assertEquals( + "2/2, 02:00 – 04:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 2), + getFebOfThisYearInMillis(2, 4), + true, DateUtils.FORMAT_24HOUR)); } @Test public void testStartOfDay() { - assertEquals("12:00 – 1:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 1), false, + assertEquals( + "12:00 – 1:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 1), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("00:00 – 01:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 1), false, + assertEquals( + "00:00 – 01:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 1), + false, DateUtils.FORMAT_24HOUR)); - assertEquals("Feb 2, 12:00 – 1:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(2, 1), false, + assertEquals( + "Feb 2, 12:00 – 1:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(2, 1), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("Feb 2, 00:00 – 01:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(2, 1), false, + assertEquals( + "Feb 2, 00:00 – 01:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(2, 1), + false, DateUtils.FORMAT_24HOUR)); - assertEquals("2/2, 12:00 – 1:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(2, 1), true, + assertEquals( + "2/2, 12:00 – 1:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(2, 1), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("2/2, 00:00 – 01:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(2, 1), true, + assertEquals( + "2/2, 00:00 – 01:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(2, 1), + true, DateUtils.FORMAT_24HOUR)); } @Test public void testEndOfDay() { for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) { - assertEquals("11:00 PM – 12:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23), getFebOfThisYearInMillis(2, 0), + assertEquals( + "11:00 PM – 12:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23), + getFebOfThisYearInMillis(2, 0), useShortFormat, DateUtils.FORMAT_12HOUR)); - assertEquals("23:00 – 00:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(1, 23), getFebOfThisYearInMillis(2, 0), + assertEquals( + "23:00 – 00:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(1, 23), + getFebOfThisYearInMillis(2, 0), useShortFormat, DateUtils.FORMAT_24HOUR)); } - assertEquals("Feb 2, 11:00 PM – 12:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 23), getFebOfThisYearInMillis(3, 0), false, + assertEquals( + "Feb 2, 11:00 PM – 12:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 23), + getFebOfThisYearInMillis(3, 0), + false, DateUtils.FORMAT_12HOUR)); - assertEquals("Feb 2, 23:00 – 00:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 23), getFebOfThisYearInMillis(3, 0), false, + assertEquals( + "Feb 2, 23:00 – 00:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 23), + getFebOfThisYearInMillis(3, 0), + false, DateUtils.FORMAT_24HOUR)); - assertEquals("2/2, 11:00 PM – 12:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 23), getFebOfThisYearInMillis(3, 0), true, + assertEquals( + "2/2, 11:00 PM – 12:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 23), + getFebOfThisYearInMillis(3, 0), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("2/2, 23:00 – 00:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 23), getFebOfThisYearInMillis(3, 0), true, + assertEquals( + "2/2, 23:00 – 00:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 23), + getFebOfThisYearInMillis(3, 0), + true, DateUtils.FORMAT_24HOUR)); - assertEquals("2/2, 12:00 AM – 2/3, 12:00 AM", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(3, 0), true, + assertEquals( + "2/2, 12:00 AM – 2/3, 12:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(3, 0), + true, DateUtils.FORMAT_12HOUR)); - assertEquals("2/2, 00:00 – 2/3, 00:00", - Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - getFebOfThisYearInMillis(2, 0), getFebOfThisYearInMillis(3, 0), true, + assertEquals( + "2/2, 00:00 – 2/3, 00:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + getFebOfThisYearInMillis(2, 0), + getFebOfThisYearInMillis(3, 0), + true, DateUtils.FORMAT_24HOUR)); } @Test public void testMidnight() { for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) { - assertEquals("12:00 AM", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, useShortFormat, + assertEquals( + "12:00 AM", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + useShortFormat, DateUtils.FORMAT_12HOUR)); - assertEquals("00:00", Utils.getDurationString(getContext(), DATE_THIS_YEAR_2_1_MS, - DATE_THIS_YEAR_2_1_MS, DATE_THIS_YEAR_2_1_MS, useShortFormat, + assertEquals( + "00:00", + Utils.getDurationString( + getContext(), + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + DATE_THIS_YEAR_2_1_MS, + useShortFormat, DateUtils.FORMAT_24HOUR)); } } diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java index 1e75342b..79ed14c0 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java @@ -22,12 +22,9 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; - import org.junit.Test; -/** - * Tests for {@link com.android.tv.util.Utils#getMultiAudioString}. - */ +/** Tests for {@link com.android.tv.util.Utils#getMultiAudioString}. */ @SmallTest public class UtilsTest_GetMultiAudioString { private static final String TRACK_ID = "test_track_id"; @@ -36,13 +33,15 @@ public class UtilsTest_GetMultiAudioString { @Test public void testAudioTrackLanguage() { Context context = getTargetContext(); - assertEquals("Korean", - Utils.getMultiAudioString(context, createAudioTrackInfo("kor"), false)); - assertEquals("English", - Utils.getMultiAudioString(context, createAudioTrackInfo("eng"), false)); - assertEquals("Unknown language", + assertEquals( + "Korean", Utils.getMultiAudioString(context, createAudioTrackInfo("kor"), false)); + assertEquals( + "English", Utils.getMultiAudioString(context, createAudioTrackInfo("eng"), false)); + assertEquals( + "Unknown language", Utils.getMultiAudioString(context, createAudioTrackInfo(null), false)); - assertEquals("Unknown language", + assertEquals( + "Unknown language", Utils.getMultiAudioString(context, createAudioTrackInfo(""), false)); assertEquals("abc", Utils.getMultiAudioString(context, createAudioTrackInfo("abc"), false)); } @@ -50,36 +49,48 @@ public class UtilsTest_GetMultiAudioString { @Test public void testAudioTrackCount() { Context context = getTargetContext(); - assertEquals("English", + assertEquals( + "English", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", -1), false)); - assertEquals("English", + assertEquals( + "English", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 0), false)); - assertEquals("English (mono)", + assertEquals( + "English (mono)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 1), false)); - assertEquals("English (stereo)", + assertEquals( + "English (stereo)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 2), false)); - assertEquals("English (3 channels)", + assertEquals( + "English (3 channels)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 3), false)); - assertEquals("English (4 channels)", + assertEquals( + "English (4 channels)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 4), false)); - assertEquals("English (5 channels)", + assertEquals( + "English (5 channels)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 5), false)); - assertEquals("English (5.1 surround)", + assertEquals( + "English (5.1 surround)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 6), false)); - assertEquals("English (7 channels)", + assertEquals( + "English (7 channels)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 7), false)); - assertEquals("English (7.1 surround)", + assertEquals( + "English (7.1 surround)", Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 8), false)); } @Test public void testShowSampleRate() { - assertEquals("Korean (48kHz)", - Utils.getMultiAudioString(getTargetContext(), - createAudioTrackInfo("kor", 0), true)); - assertEquals("Korean (7.1 surround, 48kHz)", - Utils.getMultiAudioString(getTargetContext(), - createAudioTrackInfo("kor", 8), true)); + assertEquals( + "Korean (48kHz)", + Utils.getMultiAudioString( + getTargetContext(), createAudioTrackInfo("kor", 0), true)); + assertEquals( + "Korean (7.1 surround, 48kHz)", + Utils.getMultiAudioString( + getTargetContext(), createAudioTrackInfo("kor", 8), true)); } private static TvTrackInfo createAudioTrackInfo(String language) { @@ -88,7 +99,9 @@ public class UtilsTest_GetMultiAudioString { private static TvTrackInfo createAudioTrackInfo(String language, int channelCount) { return new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, TRACK_ID) - .setLanguage(language).setAudioChannelCount(channelCount) - .setAudioSampleRate(AUDIO_SAMPLE_RATE).build(); + .setLanguage(language) + .setAudioChannelCount(channelCount) + .setAudioSampleRate(AUDIO_SAMPLE_RATE) + .build(); } } diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java index 2b43abc1..cf0212cb 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java @@ -20,30 +20,28 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; - -import org.junit.Test; - import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; +import org.junit.Test; -/** - * Tests for {@link com.android.tv.util.Utils#isInGivenDay}. - */ +/** Tests for {@link com.android.tv.util.Utils#isInGivenDay}. */ @SmallTest public class UtilsTest_IsInGivenDay { @Test public void testIsInGivenDay() { - assertTrue(Utils.isInGivenDay( - new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), - new GregorianCalendar(2015, Calendar.JANUARY, 1, 0, 30).getTimeInMillis())); + assertTrue( + Utils.isInGivenDay( + new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), + new GregorianCalendar(2015, Calendar.JANUARY, 1, 0, 30).getTimeInMillis())); } @Test public void testIsNotInGivenDay() { - assertFalse(Utils.isInGivenDay( - new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), - new GregorianCalendar(2015, Calendar.JANUARY, 2).getTimeInMillis())); + assertFalse( + Utils.isInGivenDay( + new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(), + new GregorianCalendar(2015, Calendar.JANUARY, 2).getTimeInMillis())); } @Test @@ -53,8 +51,7 @@ public class UtilsTest_IsInGivenDay { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); // 2015.01.01 00:00 in KST = 2014.12.31 15:00 in UTC - long date2015StartMs = - new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(); + long date2015StartMs = new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(); // 2015.01.01 10:00 in KST = 2015.01.01 01:00 in UTC long date2015Start10AMMs = -- cgit v1.2.3 From 4a5144ac8c51c4d89d1359e13e37fcd7f928ed9a Mon Sep 17 00:00:00 2001 From: Live Channels Team Date: Thu, 11 Jan 2018 20:42:01 -0800 Subject: Project import generated by Copybara. PiperOrigin-RevId: 181700159 Change-Id: I7bae213f26b690c0d76189c08abd85d7f7b304a3 --- Android.mk | 57 +- AndroidManifest.xml | 64 +- common/BuildConfig.java.in | 1 + common/res/values/strings.xml | 1 + .../com/android/tv/common/AutoCloseableUtils.java | 34 - .../src/com/android/tv/common/BaseApplication.java | 112 + .../src/com/android/tv/common/BaseSingletons.java | 36 + .../src/com/android/tv/common/CollectionUtils.java | 95 - .../src/com/android/tv/common/CommonConstants.java | 61 + .../tv/common/CommonPreferenceProvider.java | 214 ++ .../com/android/tv/common/CommonPreferences.java | 332 +++ .../android/tv/common/SharedPreferencesUtils.java | 82 - .../com/android/tv/common/SoftPreconditions.java | 107 +- .../com/android/tv/common/TvCommonConstants.java | 61 - .../src/com/android/tv/common/TvCommonUtils.java | 72 - .../android/tv/common/annotation/UsedByNative.java | 43 + .../tv/common/concurrent/NamedThreadFactory.java | 45 + .../tv/common/config/DefaultConfigManager.java | 55 + .../tv/common/config/RemoteConfigFeature.java | 43 + .../tv/common/config/RemoteConfigUtils.java | 43 + .../android/tv/common/config/api/RemoteConfig.java | 43 + .../tv/common/customization/CustomAction.java | 68 + .../common/customization/CustomizationManager.java | 271 ++ .../tv/common/experiments/ExperimentFlag.java | 66 + .../tv/common/experiments/ExperimentLoader.java | 28 + .../android/tv/common/experiments/Experiments.java | 63 + .../android/tv/common/feature/CommonFeatures.java | 61 +- .../tv/common/feature/ExperimentFeature.java | 44 + .../android/tv/common/feature/FeatureUtils.java | 15 + .../android/tv/common/feature/PropertyFeature.java | 7 +- common/src/com/android/tv/common/feature/Sdk.java | 8 + .../common/feature/SharedPreferencesFeature.java | 2 +- .../android/tv/common/feature/TestableFeature.java | 12 +- .../recording/RecordingStorageStatusManager.java | 256 ++ .../common/ui/setup/SetupGuidedStepFragment.java | 25 +- .../tv/common/ui/setup/SetupMultiPaneFragment.java | 3 +- .../animation/TranslationAnimationCreator.java | 15 + .../android/tv/common/util/AutoCloseableUtils.java | 34 + common/src/com/android/tv/common/util/Clock.java | 64 + .../android/tv/common/util/CollectionUtils.java | 95 + .../com/android/tv/common/util/CommonUtils.java | 148 ++ common/src/com/android/tv/common/util/Debug.java | 50 + .../com/android/tv/common/util/DurationTimer.java | 80 + .../com/android/tv/common/util/LocationUtils.java | 142 ++ .../android/tv/common/util/NetworkTrafficTags.java | 63 + .../android/tv/common/util/PermissionUtils.java | 68 + .../android/tv/common/util/PostalCodeUtils.java | 137 + .../tv/common/util/SharedPreferencesUtils.java | 83 + .../com/android/tv/common/util/StringUtils.java | 34 + .../android/tv/common/util/SystemProperties.java | 55 + .../tv/common/util/SystemPropertiesProxy.java | 79 + .../classes/core/src/com/ibm/icu/text/SCSU.java | 405 --- .../src/com/ibm/icu/text/UnicodeDecompressor.java | 795 ------ jni/DvbManager.cpp | 10 +- jni/DvbManager.h | 1 - jni/gen_jni.sh | 15 + jni/tunertvinput_jni.cpp | 2 +- libs/exoplayer-2.6.1.aar | Bin 0 -> 3057 bytes libs/exoplayer-core-2.6.1.aar | Bin 0 -> 1062771 bytes libs/exoplayer-r1.5.16.aar | Bin 0 -> 737991 bytes libs/exoplayer.jar | Bin 792705 -> 0 bytes libs/exoplayer_v2.jar | Bin 1077775 -> 0 bytes libs/exoplayer_v2_ext_ffmpeg.jar | Bin 7271 -> 0 bytes proguard.flags | 33 +- proto/channel.proto | 104 - proto/track.proto | 49 - res/drawable-xhdpi/ic_launcher.png | Bin 3325 -> 0 bytes res/drawable-xhdpi/ic_live_channels.png | Bin 0 -> 33740 bytes res/drawable-xhdpi/ic_live_channels_96x96.png | Bin 0 -> 3325 bytes res/layout/details_overview.xml | 89 + res/layout/dvr_recording_card_view.xml | 2 +- res/layout/guided_action_editable.xml | 41 - res/layout/lb_details_overview.xml | 89 - res/values/arrays.xml | 2 +- res/values/strings.xml | 24 +- res/xml/remote_config_defaults.xml | 37 - src/com/android/exoplayer/MediaFormatUtil.java | 94 - .../android/exoplayer/MediaSoftwareCodecUtil.java | 273 -- src/com/android/exoplayer/text/SubtitleView.java | 321 --- .../exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java | 126 - .../exoplayer2/ext/ffmpeg/FfmpegLibrary.java | 75 - src/com/android/tv/ApplicationSingletons.java | 85 - src/com/android/tv/AudioManagerHelper.java | 36 +- src/com/android/tv/Features.java | 193 -- src/com/android/tv/InputSessionManager.java | 2 +- src/com/android/tv/LauncherActivity.java | 27 +- src/com/android/tv/MainActivity.java | 365 +-- src/com/android/tv/MediaSessionWrapper.java | 18 +- src/com/android/tv/SetupPassthroughActivity.java | 50 +- src/com/android/tv/Starter.java | 42 + src/com/android/tv/TimeShiftManager.java | 11 +- src/com/android/tv/TvApplication.java | 243 +- src/com/android/tv/TvFeatures.java | 102 + src/com/android/tv/TvSingletons.java | 105 + .../tv/analytics/SendChannelStatusRunnable.java | 3 +- src/com/android/tv/app/LiveTvApplication.java | 137 + src/com/android/tv/config/ConfigKeys.java | 23 - .../android/tv/config/DefaultConfigManager.java | 54 - src/com/android/tv/config/RemoteConfig.java | 43 - src/com/android/tv/config/RemoteConfigFeature.java | 40 - src/com/android/tv/config/RemoteConfigUtils.java | 42 - src/com/android/tv/customization/CustomAction.java | 68 - .../tv/customization/TvCustomizationManager.java | 271 -- src/com/android/tv/data/Channel.java | 9 +- src/com/android/tv/data/ChannelDataManager.java | 5 +- src/com/android/tv/data/ChannelLogoFetcher.java | 5 +- src/com/android/tv/data/ChannelNumber.java | 19 +- src/com/android/tv/data/InternalDataUtils.java | 1 + src/com/android/tv/data/Lineup.java | 53 +- src/com/android/tv/data/PreviewDataManager.java | 15 +- src/com/android/tv/data/PreviewProgramContent.java | 49 +- src/com/android/tv/data/Program.java | 8 +- src/com/android/tv/data/ProgramDataManager.java | 3 +- src/com/android/tv/data/WatchedHistoryManager.java | 18 +- .../data/epg/AutoValue_EpgReader_EpgChannel.java | 86 + src/com/android/tv/data/epg/EpgFetchHelper.java | 9 +- src/com/android/tv/data/epg/EpgFetchService.java | 70 + src/com/android/tv/data/epg/EpgFetcher.java | 705 +----- src/com/android/tv/data/epg/EpgFetcherImpl.java | 805 ++++++ src/com/android/tv/data/epg/EpgInputWhiteList.java | 92 + src/com/android/tv/data/epg/EpgReader.java | 25 +- src/com/android/tv/data/epg/StubEpgReader.java | 11 +- .../tv/dialog/DvrHistoryDialogFragment.java | 6 +- src/com/android/tv/dialog/PinDialogFragment.java | 8 +- .../tv/dialog/SafeDismissDialogFragment.java | 4 +- src/com/android/tv/dvr/BaseDvrDataManager.java | 2 +- src/com/android/tv/dvr/DvrDataManagerImpl.java | 31 +- src/com/android/tv/dvr/DvrManager.java | 16 +- src/com/android/tv/dvr/DvrScheduleManager.java | 14 +- .../android/tv/dvr/DvrStorageStatusManager.java | 286 +-- .../android/tv/dvr/DvrWatchedPositionManager.java | 2 +- src/com/android/tv/dvr/data/RecordedProgram.java | 4 +- .../android/tv/dvr/data/ScheduledRecording.java | 22 +- src/com/android/tv/dvr/data/SeriesRecording.java | 2 +- .../android/tv/dvr/provider/AsyncDvrDbTask.java | 3 +- .../android/tv/dvr/provider/DvrDatabaseHelper.java | 2 +- src/com/android/tv/dvr/provider/DvrDbSync.java | 6 +- .../tv/dvr/provider/EpisodicProgramLoadTask.java | 6 +- .../android/tv/dvr/recorder/ConflictChecker.java | 34 +- .../tv/dvr/recorder/DvrRecordingService.java | 12 +- .../tv/dvr/recorder/DvrStartRecordingReceiver.java | 7 +- .../tv/dvr/recorder/InputTaskScheduler.java | 3 +- .../tv/dvr/recorder/RecordingScheduler.java | 9 +- src/com/android/tv/dvr/recorder/RecordingTask.java | 13 +- .../tv/dvr/recorder/ScheduledProgramReaper.java | 2 +- .../tv/dvr/recorder/SeriesRecordingScheduler.java | 34 +- .../tv/dvr/ui/DvrAlreadyRecordedFragment.java | 4 +- .../tv/dvr/ui/DvrAlreadyScheduledFragment.java | 4 +- .../ui/DvrChannelRecordDurationOptionFragment.java | 6 +- src/com/android/tv/dvr/ui/DvrConflictFragment.java | 10 +- .../android/tv/dvr/ui/DvrGuidedStepFragment.java | 16 +- .../dvr/ui/DvrInsufficientSpaceErrorFragment.java | 4 +- .../tv/dvr/ui/DvrPrioritySettingsFragment.java | 10 +- src/com/android/tv/dvr/ui/DvrScheduleFragment.java | 14 +- .../tv/dvr/ui/DvrSeriesDeletionActivity.java | 4 +- .../tv/dvr/ui/DvrSeriesDeletionFragment.java | 8 +- .../tv/dvr/ui/DvrSeriesScheduledFragment.java | 8 +- .../tv/dvr/ui/DvrSeriesSettingsActivity.java | 4 +- .../tv/dvr/ui/DvrSeriesSettingsFragment.java | 14 +- .../tv/dvr/ui/DvrStopRecordingFragment.java | 4 +- .../tv/dvr/ui/DvrStopSeriesRecordingFragment.java | 5 +- src/com/android/tv/dvr/ui/DvrUiHelper.java | 23 +- .../tv/dvr/ui/TrackedGuidedStepFragment.java | 4 +- .../ui/browse/CurrentRecordingDetailsFragment.java | 6 +- .../android/tv/dvr/ui/browse/DetailsContent.java | 6 +- .../dvr/ui/browse/DetailsViewBackgroundHelper.java | 1 + .../tv/dvr/ui/browse/DvrBrowseActivity.java | 4 +- .../tv/dvr/ui/browse/DvrBrowseFragment.java | 9 +- .../tv/dvr/ui/browse/DvrDetailsActivity.java | 4 +- .../tv/dvr/ui/browse/DvrDetailsFragment.java | 10 +- .../android/tv/dvr/ui/browse/DvrItemPresenter.java | 5 +- .../dvr/ui/browse/FullSchedulesCardPresenter.java | 4 +- .../ui/browse/RecordedProgramDetailsFragment.java | 8 +- .../tv/dvr/ui/browse/RecordedProgramPresenter.java | 4 +- .../tv/dvr/ui/browse/RecordingCardView.java | 4 +- .../tv/dvr/ui/browse/RecordingDetailsFragment.java | 4 +- .../browse/ScheduledRecordingDetailsFragment.java | 4 +- .../dvr/ui/browse/ScheduledRecordingPresenter.java | 4 +- .../ui/browse/SeriesRecordingDetailsFragment.java | 8 +- .../tv/dvr/ui/browse/SeriesRecordingPresenter.java | 5 +- .../tv/dvr/ui/list/BaseDvrSchedulesFragment.java | 7 +- .../tv/dvr/ui/list/DvrSchedulesActivity.java | 4 +- .../tv/dvr/ui/list/DvrSeriesSchedulesFragment.java | 5 +- .../android/tv/dvr/ui/list/ScheduleRowAdapter.java | 8 +- .../tv/dvr/ui/list/ScheduleRowPresenter.java | 10 +- .../dvr/ui/list/SchedulesHeaderRowPresenter.java | 6 +- .../tv/dvr/ui/list/SeriesScheduleRowAdapter.java | 5 +- .../tv/dvr/ui/playback/DvrPlaybackActivity.java | 4 +- .../ui/playback/DvrPlaybackMediaSessionHelper.java | 6 +- .../ui/playback/DvrPlaybackOverlayFragment.java | 6 +- src/com/android/tv/experiments/ExperimentFlag.java | 62 - src/com/android/tv/experiments/Experiments.java | 42 - src/com/android/tv/guide/ProgramGuide.java | 7 +- src/com/android/tv/guide/ProgramItemView.java | 8 +- src/com/android/tv/guide/ProgramTableAdapter.java | 20 +- src/com/android/tv/license/LicenseUtils.java | 1 + src/com/android/tv/menu/ChannelsRowAdapter.java | 20 +- .../tv/menu/CustomizableOptionsRowAdapter.java | 2 +- src/com/android/tv/menu/Menu.java | 10 +- src/com/android/tv/menu/MenuLayoutManager.java | 4 +- src/com/android/tv/menu/MenuRowFactory.java | 17 +- src/com/android/tv/menu/OptionsRowAdapter.java | 4 +- .../android/tv/menu/PartnerOptionsRowAdapter.java | 2 +- src/com/android/tv/menu/PlayControlsRowView.java | 10 +- src/com/android/tv/menu/TvOptionsRowAdapter.java | 10 +- .../android/tv/onboarding/NewSourcesFragment.java | 11 +- .../android/tv/onboarding/OnboardingActivity.java | 19 +- .../tv/onboarding/SetupSourcesFragment.java | 21 +- src/com/android/tv/onboarding/WelcomeFragment.java | 17 +- .../android/tv/parental/ContentRatingsManager.java | 12 +- .../android/tv/parental/ContentRatingsParser.java | 1 + .../tv/parental/ParentalControlSettings.java | 2 +- src/com/android/tv/perf/EventNames.java | 1 + .../tv/receiver/AudioCapabilitiesReceiver.java | 11 +- .../android/tv/receiver/BootCompletedReceiver.java | 13 +- src/com/android/tv/receiver/GlobalKeyReceiver.java | 6 +- .../tv/receiver/PackageIntentsReceiver.java | 9 +- .../tv/recommendation/ChannelPreviewUpdater.java | 15 +- .../android/tv/recommendation/ChannelRecord.java | 14 +- .../tv/recommendation/NotificationService.java | 18 +- .../recommendation/RecommendationDataManager.java | 7 +- .../RecordedProgramPreviewUpdater.java | 13 +- src/com/android/tv/search/DataManagerSearch.java | 9 +- src/com/android/tv/search/LocalSearchProvider.java | 10 +- .../tv/search/ProgramGuideSearchFragment.java | 7 +- src/com/android/tv/search/TvProviderSearch.java | 3 +- src/com/android/tv/setup/SystemSetupActivity.java | 9 +- .../android/tv/tuner/ChannelScanFileParser.java | 104 - src/com/android/tv/tuner/DvbDeviceAccessor.java | 222 -- src/com/android/tv/tuner/DvbTunerHal.java | 177 -- src/com/android/tv/tuner/TunerHal.java | 358 --- src/com/android/tv/tuner/TunerInputController.java | 428 +++- .../android/tv/tuner/TunerPreferenceProvider.java | 214 -- src/com/android/tv/tuner/TunerPreferences.java | 428 ---- src/com/android/tv/tuner/cc/CaptionLayout.java | 77 - .../android/tv/tuner/cc/CaptionTrackRenderer.java | 340 --- .../android/tv/tuner/cc/CaptionWindowLayout.java | 680 ----- src/com/android/tv/tuner/cc/Cea708Parser.java | 922 ------- src/com/android/tv/tuner/data/Cea708Data.java | 329 --- src/com/android/tv/tuner/data/PsiData.java | 93 - src/com/android/tv/tuner/data/PsipData.java | 871 ------- src/com/android/tv/tuner/data/TunerChannel.java | 517 ---- .../tuner/exoplayer/Cea708TextTrackRenderer.java | 305 --- .../exoplayer/ExoPlayerExtractorsFactory.java | 41 - .../tuner/exoplayer/ExoPlayerSampleExtractor.java | 610 ----- .../tv/tuner/exoplayer/FileSampleExtractor.java | 139 -- .../android/tv/tuner/exoplayer/MpegTsPlayer.java | 672 ----- .../tv/tuner/exoplayer/MpegTsRendererBuilder.java | 77 - .../tv/tuner/exoplayer/MpegTsSampleExtractor.java | 345 --- .../tv/tuner/exoplayer/MpegTsSampleSource.java | 195 -- .../tuner/exoplayer/MpegTsVideoTrackRenderer.java | 111 - .../tv/tuner/exoplayer/SampleExtractor.java | 131 - .../tv/tuner/exoplayer/audio/AudioClock.java | 94 - .../tv/tuner/exoplayer/audio/AudioDecoder.java | 69 - .../tuner/exoplayer/audio/AudioTrackMonitor.java | 140 -- .../tuner/exoplayer/audio/AudioTrackWrapper.java | 174 -- .../exoplayer/audio/MediaCodecAudioDecoder.java | 233 -- .../audio/MpegTsDefaultAudioTrackRenderer.java | 743 ------ .../audio/MpegTsMediaCodecAudioTrackRenderer.java | 94 - .../tv/tuner/exoplayer/buffer/BufferManager.java | 682 ----- .../tuner/exoplayer/buffer/DvrStorageManager.java | 391 --- .../exoplayer/buffer/RecordingSampleBuffer.java | 303 --- .../tv/tuner/exoplayer/buffer/SampleChunk.java | 428 ---- .../exoplayer/buffer/SampleChunkIoHelper.java | 464 ---- .../tv/tuner/exoplayer/buffer/SamplePool.java | 67 - .../tv/tuner/exoplayer/buffer/SampleQueue.java | 72 - .../tuner/exoplayer/buffer/SimpleSampleBuffer.java | 177 -- .../exoplayer/buffer/TrickplayStorageManager.java | 145 -- .../exoplayer/ffmpeg/FfmpegDecoderClient.java | 247 -- .../exoplayer/ffmpeg/FfmpegDecoderService.java | 202 -- .../tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl | 29 - src/com/android/tv/tuner/layout/ScaledLayout.java | 290 --- .../tv/tuner/setup/ConnectionTypeFragment.java | 98 - .../android/tv/tuner/setup/PostalCodeFragment.java | 179 -- src/com/android/tv/tuner/setup/ScanFragment.java | 542 ---- .../android/tv/tuner/setup/ScanResultFragment.java | 133 - .../android/tv/tuner/setup/TunerSetupActivity.java | 548 ---- .../android/tv/tuner/setup/WelcomeFragment.java | 127 - .../android/tv/tuner/source/FileTsStreamer.java | 487 ---- src/com/android/tv/tuner/source/TsDataSource.java | 49 - .../tv/tuner/source/TsDataSourceManager.java | 136 - .../android/tv/tuner/source/TsStreamWriter.java | 238 -- src/com/android/tv/tuner/source/TsStreamer.java | 53 - .../android/tv/tuner/source/TunerTsStreamer.java | 420 ---- .../tv/tuner/source/TunerTsStreamerManager.java | 303 --- src/com/android/tv/tuner/ts/SectionParser.java | 2083 ---------------- src/com/android/tv/tuner/ts/TsParser.java | 543 ---- .../tv/tuner/tvinput/ChannelDataManager.java | 801 ------ .../android/tv/tuner/tvinput/EventDetector.java | 349 --- .../tv/tuner/tvinput/FileSourceEventDetector.java | 259 -- .../tv/tuner/tvinput/PlaybackBufferListener.java | 38 - src/com/android/tv/tuner/tvinput/TunerDebug.java | 147 -- .../tv/tuner/tvinput/TunerRecordingSession.java | 101 - .../tuner/tvinput/TunerRecordingSessionWorker.java | 683 ----- src/com/android/tv/tuner/tvinput/TunerSession.java | 341 --- .../tv/tuner/tvinput/TunerSessionWorker.java | 1852 -------------- .../tuner/tvinput/TunerStorageCleanUpService.java | 177 -- .../tv/tuner/tvinput/TunerTvInputService.java | 123 - src/com/android/tv/tuner/util/ByteArrayBuffer.java | 152 -- src/com/android/tv/tuner/util/ConvertUtils.java | 33 - .../android/tv/tuner/util/GlobalSettingsUtils.java | 34 - src/com/android/tv/tuner/util/Ints.java | 26 - src/com/android/tv/tuner/util/PostalCodeUtils.java | 138 -- src/com/android/tv/tuner/util/StatusTextUtils.java | 137 - .../tv/tuner/util/SystemPropertiesProxy.java | 79 - .../android/tv/tuner/util/TisConfiguration.java | 20 - .../android/tv/tuner/util/TunerInputInfoUtils.java | 111 - src/com/android/tv/ui/AppLayerTvView.java | 6 +- src/com/android/tv/ui/ChannelBannerView.java | 6 +- src/com/android/tv/ui/KeypadChannelSwitchView.java | 6 +- src/com/android/tv/ui/SelectInputView.java | 11 +- src/com/android/tv/ui/TunableTvView.java | 29 +- src/com/android/tv/ui/TvOverlayManager.java | 13 +- src/com/android/tv/ui/TvViewUiManager.java | 4 +- .../ui/sidepanel/CustomizeChannelListFragment.java | 2 +- .../tv/ui/sidepanel/DeveloperOptionFragment.java | 25 +- .../android/tv/ui/sidepanel/SettingsFragment.java | 32 +- src/com/android/tv/ui/sidepanel/SideFragment.java | 8 +- .../parentalcontrols/ChannelsBlockedFragment.java | 11 + .../parentalcontrols/RatingsFragment.java | 2 +- src/com/android/tv/util/AccountHelper.java | 96 - src/com/android/tv/util/AsyncDbTask.java | 4 +- src/com/android/tv/util/BitmapUtils.java | 1 + src/com/android/tv/util/Clock.java | 64 - src/com/android/tv/util/Debug.java | 50 - src/com/android/tv/util/DurationTimer.java | 80 - src/com/android/tv/util/ImageLoader.java | 1 + src/com/android/tv/util/LocationUtils.java | 137 - src/com/android/tv/util/NamedThreadFactory.java | 45 - src/com/android/tv/util/NetworkTrafficTags.java | 63 - src/com/android/tv/util/OnboardingUtils.java | 8 +- src/com/android/tv/util/PermissionUtils.java | 53 - src/com/android/tv/util/RecurringRunner.java | 2 +- src/com/android/tv/util/SetupUtils.java | 53 +- src/com/android/tv/util/SqlParams.java | 74 + src/com/android/tv/util/StringUtils.java | 34 - src/com/android/tv/util/SystemProperties.java | 53 - src/com/android/tv/util/TvInputManagerHelper.java | 104 +- src/com/android/tv/util/Utils.java | 69 +- src/com/android/tv/util/ViewCache.java | 15 + src/com/android/tv/util/account/AccountHelper.java | 38 + .../android/tv/util/account/AccountHelperImpl.java | 101 + tests/common/Android.mk | 12 +- .../src/com/android/tv/testing/ChannelInfo.java | 352 --- .../android/tv/testing/ChannelNumberSubject.java | 68 + .../src/com/android/tv/testing/ChannelUtils.java | 176 -- .../src/com/android/tv/testing/Constants.java | 47 - .../src/com/android/tv/testing/DbTestingUtils.java | 40 + .../src/com/android/tv/testing/EpgTestData.java | 197 ++ .../src/com/android/tv/testing/FakeClock.java | 2 +- .../src/com/android/tv/testing/FakeEpgFetcher.java | 57 + .../src/com/android/tv/testing/FakeEpgReader.java | 163 ++ .../com/android/tv/testing/FakeRemoteConfig.java | 47 + .../com/android/tv/testing/FakeTvInputManager.java | 117 + .../tv/testing/FakeTvInputManagerHelper.java | 32 + .../src/com/android/tv/testing/FakeTvProvider.java | 2605 ++++++++++++++++++++ .../src/com/android/tv/testing/ProgramInfo.java | 333 --- .../src/com/android/tv/testing/ProgramUtils.java | 112 - .../com/android/tv/testing/SingletonProvider.java | 37 + .../com/android/tv/testing/TestSingletonApp.java | 219 ++ .../tv/testing/TvContentRatingConstants.java | 67 - tests/common/src/com/android/tv/testing/Utils.java | 119 - .../android/tv/testing/constants/Constants.java | 47 + .../constants/TvContentRatingConstants.java | 67 + .../com/android/tv/testing/data/ChannelInfo.java | 352 +++ .../com/android/tv/testing/data/ChannelUtils.java | 181 ++ .../com/android/tv/testing/data/ProgramInfo.java | 336 +++ .../com/android/tv/testing/data/ProgramUtils.java | 113 + .../tv/testing/shadows/ShadowMediaSession.java | 89 + .../android/tv/testing/testinput/ChannelState.java | 2 +- .../testinput/TestInputControlConnection.java | 2 +- .../tv/testing/testinput/TvTestInputConstants.java | 2 +- .../uihelper/LiveChannelsUiDeviceHelper.java | 17 +- .../src/com/android/tv/testing/utils/Utils.java | 118 + .../android/tv/tests/ui/LiveChannelsAppTest.java | 8 +- .../android/tv/tests/ui/LiveChannelsTestCase.java | 2 +- .../tv/tests/ui/PlayControlsRowViewTest.java | 3 +- tests/input/func.sh | 24 + .../com/android/tv/testinput/TestInputControl.java | 4 +- .../android/tv/testinput/TestTvInputService.java | 3 +- .../tv/testinput/TestTvInputSetupActivity.java | 10 +- .../instrument/TestSetupInstrumentation.java | 2 +- tests/input/tools/get_test_logos.sh | 57 + tests/input/unit.sh | 24 + tests/tunerscripts/measure-tuning-time.awk | 32 + tests/tunerscripts/usbtuner-test.sh | 159 ++ tests/tunerunit/Android.mk | 26 + tests/tunerunit/AndroidManifest.xml | 32 + .../exoplayer/buffer/VerySlowSampleChunk.java | 51 + .../tv/tuner/exoplayer/tests/AssetDataSource.java | 124 + .../exoplayer/tests/SampleSourceExtractorTest.java | 246 ++ .../com/android/tv/BaseMainActivityTestCase.java | 2 +- .../android/tv/CurrentPositionMediatorTest.java | 34 +- tests/unit/src/com/android/tv/FeaturesTest.java | 7 +- .../unit/src/com/android/tv/MainActivityTest.java | 15 +- .../src/com/android/tv/TimeShiftManagerTest.java | 47 +- .../android/tv/data/ChannelDataManagerTest.java | 131 +- .../src/com/android/tv/data/ChannelNumberTest.java | 89 - .../unit/src/com/android/tv/data/ChannelTest.java | 34 +- .../src/com/android/tv/data/GenreItemTest.java | 31 +- .../android/tv/data/ProgramDataManagerTest.java | 114 +- .../unit/src/com/android/tv/data/ProgramTest.java | 40 +- .../android/tv/data/TvInputNewComparatorTest.java | 3 + .../android/tv/data/WatchedHistoryManagerTest.java | 41 +- .../com/android/tv/dvr/BaseDvrDataManagerTest.java | 89 - .../com/android/tv/dvr/DvrDataManagerImplTest.java | 81 - .../android/tv/dvr/DvrDataManagerInMemoryImpl.java | 310 --- .../com/android/tv/dvr/DvrScheduleManagerTest.java | 831 ------- .../com/android/tv/dvr/ScheduledRecordingTest.java | 119 - .../android/tv/dvr/data/SeriesRecordingTest.java | 140 -- .../com/android/tv/dvr/provider/DvrDbSyncTest.java | 168 -- .../dvr/provider/EpisodicProgramLoadTaskTest.java | 89 - .../tv/dvr/recorder/InputTaskSchedulerTest.java | 251 -- .../android/tv/dvr/recorder/RecordingTaskTest.java | 157 -- .../dvr/recorder/ScheduledProgramReaperTest.java | 136 - .../com/android/tv/dvr/recorder/SchedulerTest.java | 131 - .../dvr/recorder/SeriesRecordingSchedulerTest.java | 148 -- .../android/tv/dvr/ui/SortedArrayAdapterTest.java | 246 -- .../android/tv/experiments/ExperimentsTest.java | 41 - .../android/tv/menu/TvOptionsRowAdapterTest.java | 2 +- .../tv/recommendation/ChannelRecordTest.java | 2 +- .../tv/recommendation/EvaluatorTestCase.java | 2 +- .../tv/recommendation/RecommendationUtils.java | 2 +- .../android/tv/recommendation/RecommenderTest.java | 28 +- .../recommendation/RoutineWatchEvaluatorTest.java | 40 +- .../android/tv/search/LocalSearchProviderTest.java | 23 +- .../src/com/android/tv/tests/TvActivityTest.java | 2 +- .../src/com/android/tv/util/ImageCacheTest.java | 3 + .../android/tv/util/MockApplicationSingletons.java | 151 -- .../src/com/android/tv/util/MockTvSingletons.java | 203 ++ .../android/tv/util/MultiLongSparseArrayTest.java | 3 + .../com/android/tv/util/ScaledBitmapInfoTest.java | 18 + .../android/tv/util/TvInputManagerHelperTest.java | 3 + .../com/android/tv/util/TvTrackInfoUtilsTest.java | 3 + .../tv/util/UtilsTest_GetDurationString.java | 3 + .../tv/util/UtilsTest_GetMultiAudioString.java | 3 + .../android/tv/util/UtilsTest_IsInGivenDay.java | 3 + tuner/Android.mk | 47 + tuner/AndroidManifest.xml | 22 + tuner/BuildConfig.java.in | 8 + tuner/buildconfig.mk | 39 + tuner/proto/channel.proto | 111 + tuner/proto/track.proto | 49 + .../res/drawable-xhdpi/recommendation_antenna.png | Bin 0 -> 1662 bytes tuner/res/drawable-xhdpi/usb_antenna.png | Bin 0 -> 1639 bytes tuner/res/drawable/ut_scan_progress.xml | 35 + tuner/res/layout/guided_action_editable.xml | 41 + tuner/res/layout/ut_channel_list.xml | 32 + tuner/res/layout/ut_channel_scan.xml | 117 + tuner/res/layout/ut_overlay_view.xml | 57 + tuner/res/raw/ut_euro_dvbt_all | 287 +++ tuner/res/raw/ut_kr_all | 264 ++ tuner/res/raw/ut_kr_atsc_center_frequencies_8vsb | 58 + .../ut_kr_cable_standard_center_frequencies_qam256 | 206 ++ .../ut_kr_dev_cj_cable_center_frequencies_qam256 | 28 + tuner/res/raw/ut_us_all | 214 ++ tuner/res/raw/ut_us_atsc_center_frequencies_8vsb | 70 + .../ut_us_cable_standard_center_frequencies_qam256 | 144 ++ tuner/res/values/attrs.xml | 25 + tuner/res/values/colors.xml | 24 + tuner/res/values/dimens.xml | 44 + tuner/res/values/integers.xml | 20 + tuner/res/values/strings.xml | 219 ++ tuner/res/xml/ut_tvinputservice.xml | 39 + .../android/tv/tuner/ChannelScanFileParser.java | 108 + .../com/android/tv/tuner/DvbDeviceAccessor.java | 222 ++ tuner/src/com/android/tv/tuner/DvbTunerHal.java | 177 ++ tuner/src/com/android/tv/tuner/TunerFeatures.java | 102 + tuner/src/com/android/tv/tuner/TunerHal.java | 367 +++ .../src/com/android/tv/tuner/TunerPreferences.java | 98 + .../src/com/android/tv/tuner/cc/CaptionLayout.java | 77 + .../android/tv/tuner/cc/CaptionTrackRenderer.java | 340 +++ .../android/tv/tuner/cc/CaptionWindowLayout.java | 680 +++++ .../src/com/android/tv/tuner/cc/Cea708Parser.java | 922 +++++++ .../src/com/android/tv/tuner/data/Cea708Data.java | 329 +++ tuner/src/com/android/tv/tuner/data/PsiData.java | 93 + tuner/src/com/android/tv/tuner/data/PsipData.java | 871 +++++++ .../com/android/tv/tuner/data/TunerChannel.java | 552 +++++ .../tuner/exoplayer/Cea708TextTrackRenderer.java | 305 +++ .../exoplayer/ExoPlayerExtractorsFactory.java | 41 + .../tuner/exoplayer/ExoPlayerSampleExtractor.java | 613 +++++ .../tv/tuner/exoplayer/FileSampleExtractor.java | 139 ++ .../android/tv/tuner/exoplayer/MpegTsPlayer.java | 672 +++++ .../tv/tuner/exoplayer/MpegTsRendererBuilder.java | 77 + .../tv/tuner/exoplayer/MpegTsSampleExtractor.java | 345 +++ .../tv/tuner/exoplayer/MpegTsSampleSource.java | 195 ++ .../tuner/exoplayer/MpegTsVideoTrackRenderer.java | 126 + .../tv/tuner/exoplayer/SampleExtractor.java | 131 + .../tv/tuner/exoplayer/audio/AudioClock.java | 94 + .../tv/tuner/exoplayer/audio/AudioDecoder.java | 69 + .../tuner/exoplayer/audio/AudioTrackMonitor.java | 140 ++ .../tuner/exoplayer/audio/AudioTrackWrapper.java | 174 ++ .../exoplayer/audio/MediaCodecAudioDecoder.java | 233 ++ .../audio/MpegTsDefaultAudioTrackRenderer.java | 739 ++++++ .../audio/MpegTsMediaCodecAudioTrackRenderer.java | 94 + .../tv/tuner/exoplayer/buffer/BufferManager.java | 682 +++++ .../tuner/exoplayer/buffer/DvrStorageManager.java | 392 +++ .../exoplayer/buffer/RecordingSampleBuffer.java | 303 +++ .../tv/tuner/exoplayer/buffer/SampleChunk.java | 431 ++++ .../exoplayer/buffer/SampleChunkIoHelper.java | 464 ++++ .../tv/tuner/exoplayer/buffer/SamplePool.java | 67 + .../tv/tuner/exoplayer/buffer/SampleQueue.java | 72 + .../tuner/exoplayer/buffer/SimpleSampleBuffer.java | 177 ++ .../exoplayer/buffer/TrickplayStorageManager.java | 145 ++ .../tv/tuner/exoplayer/text/SubtitleView.java | 323 +++ .../com/android/tv/tuner/layout/ScaledLayout.java | 290 +++ .../tv/tuner/setup/BaseTunerSetupActivity.java | 515 ++++ .../tv/tuner/setup/ConnectionTypeFragment.java | 101 + .../tv/tuner/setup/LiveTvTunerSetupActivity.java | 75 + .../android/tv/tuner/setup/PostalCodeFragment.java | 187 ++ .../com/android/tv/tuner/setup/ScanFragment.java | 551 +++++ .../android/tv/tuner/setup/ScanResultFragment.java | 135 + .../android/tv/tuner/setup/WelcomeFragment.java | 129 + .../android/tv/tuner/source/FileTsStreamer.java | 487 ++++ .../com/android/tv/tuner/source/TsDataSource.java | 49 + .../tv/tuner/source/TsDataSourceManager.java | 136 + .../android/tv/tuner/source/TsStreamWriter.java | 238 ++ .../com/android/tv/tuner/source/TsStreamer.java | 53 + .../android/tv/tuner/source/TunerTsStreamer.java | 420 ++++ .../tv/tuner/source/TunerTsStreamerManager.java | 303 +++ .../src/com/android/tv/tuner/ts/SectionParser.java | 2093 ++++++++++++++++ tuner/src/com/android/tv/tuner/ts/TsParser.java | 543 ++++ .../tv/tuner/tvinput/BaseTunerTvInputService.java | 130 + .../tv/tuner/tvinput/ChannelDataManager.java | 796 ++++++ .../android/tv/tuner/tvinput/EventDetector.java | 349 +++ .../tv/tuner/tvinput/FileSourceEventDetector.java | 259 ++ .../tuner/tvinput/LiveTvTunerTvInputService.java | 22 + .../tv/tuner/tvinput/PlaybackBufferListener.java | 38 + .../com/android/tv/tuner/tvinput/TunerDebug.java | 147 ++ .../tv/tuner/tvinput/TunerRecordingSession.java | 101 + .../tuner/tvinput/TunerRecordingSessionWorker.java | 584 +++++ .../com/android/tv/tuner/tvinput/TunerSession.java | 341 +++ .../tv/tuner/tvinput/TunerSessionWorker.java | 1864 ++++++++++++++ .../tuner/tvinput/TunerStorageCleanUpService.java | 177 ++ .../com/android/tv/tuner/util/ByteArrayBuffer.java | 155 ++ .../com/android/tv/tuner/util/ConvertUtils.java | 33 + .../android/tv/tuner/util/GlobalSettingsUtils.java | 34 + tuner/src/com/android/tv/tuner/util/Ints.java | 41 + .../com/android/tv/tuner/util/StatusTextUtils.java | 137 + .../android/tv/tuner/util/TunerInputInfoUtils.java | 112 + .../google/android/exoplayer/MediaFormatUtil.java | 96 + .../android/exoplayer/MediaSoftwareCodecUtil.java | 273 ++ tuner/src/com/mediatek/tunerservice/IMtkTuner.aidl | 27 + .../tests/unittests/javatests/AndroidManifest.xml | 34 + .../unittests/javatests/assets/capture_kqed.ts | Bin 0 -> 31457280 bytes .../unittests/javatests/assets/capture_stream.ts | Bin 0 -> 73699196 bytes .../google/android/tv/tuner/AndroidManifest.xml | 31 + .../com/google/android/tv/tuner/FakeTunerHal.java | 79 + .../com/google/android/tv/tuner/FileTunerHal.java | 274 ++ .../google/android/tv/tuner/ZappingTimeTest.java | 413 ++++ .../tv/tuner/layout/tests/AndroidManifest.xml | 25 + .../tuner/layout/tests/ScaledLayoutActivity.java | 83 + .../tv/tuner/layout/tests/ScaledLayoutTest.java | 147 ++ .../tv/tuner/setup/TunerHalFactoryTest.java | 79 + .../res/layout/activity_scaled_layout_test.xml | 53 + .../tests/unittests/javatests/res/values/attrs.xml | 25 + .../drawable-xhdpi/recommendation_antenna.png | Bin 1662 -> 0 bytes usbtuner-res/drawable-xhdpi/usb_antenna.png | Bin 1639 -> 0 bytes usbtuner-res/drawable/ut_scan_progress.xml | 35 - usbtuner-res/layout/ut_channel_list.xml | 32 - usbtuner-res/layout/ut_channel_scan.xml | 117 - usbtuner-res/layout/ut_overlay_view.xml | 57 - usbtuner-res/raw/ut_euro_dvbt_all | 287 --- usbtuner-res/raw/ut_kr_all | 264 -- .../raw/ut_kr_atsc_center_frequencies_8vsb | 58 - .../ut_kr_cable_standard_center_frequencies_qam256 | 206 -- .../ut_kr_dev_cj_cable_center_frequencies_qam256 | 28 - usbtuner-res/raw/ut_us_all | 214 -- .../raw/ut_us_atsc_center_frequencies_8vsb | 70 - .../ut_us_cable_standard_center_frequencies_qam256 | 144 -- usbtuner-res/values-af/strings.xml | 90 - usbtuner-res/values-am/strings.xml | 90 - usbtuner-res/values-ar/strings.xml | 102 - usbtuner-res/values-az-rAZ/strings.xml | 90 - usbtuner-res/values-bg/strings.xml | 90 - usbtuner-res/values-bn-rBD/strings.xml | 90 - usbtuner-res/values-ca/strings.xml | 90 - usbtuner-res/values-cs/strings.xml | 96 - usbtuner-res/values-da/strings.xml | 90 - usbtuner-res/values-de/strings.xml | 90 - usbtuner-res/values-el/strings.xml | 90 - usbtuner-res/values-en-rAU/strings.xml | 90 - usbtuner-res/values-en-rGB/strings.xml | 90 - usbtuner-res/values-en-rIN/strings.xml | 90 - usbtuner-res/values-es-rUS/strings.xml | 90 - usbtuner-res/values-es/strings.xml | 90 - usbtuner-res/values-et-rEE/strings.xml | 90 - usbtuner-res/values-eu-rES/strings.xml | 90 - usbtuner-res/values-fa/strings.xml | 90 - usbtuner-res/values-fi/strings.xml | 90 - usbtuner-res/values-fr-rCA/strings.xml | 90 - usbtuner-res/values-fr/strings.xml | 90 - usbtuner-res/values-gl-rES/strings.xml | 90 - usbtuner-res/values-hi/strings.xml | 90 - usbtuner-res/values-hr/strings.xml | 93 - usbtuner-res/values-hu/strings.xml | 90 - usbtuner-res/values-hy-rAM/strings.xml | 90 - usbtuner-res/values-in/strings.xml | 90 - usbtuner-res/values-is-rIS/strings.xml | 90 - usbtuner-res/values-it/strings.xml | 90 - usbtuner-res/values-iw/strings.xml | 96 - usbtuner-res/values-ja/strings.xml | 90 - usbtuner-res/values-ka-rGE/strings.xml | 90 - usbtuner-res/values-kk-rKZ/strings.xml | 90 - usbtuner-res/values-km-rKH/strings.xml | 90 - usbtuner-res/values-kn-rIN/strings.xml | 90 - usbtuner-res/values-ko/strings.xml | 90 - usbtuner-res/values-ky-rKG/strings.xml | 90 - usbtuner-res/values-lo-rLA/strings.xml | 90 - usbtuner-res/values-lt/strings.xml | 96 - usbtuner-res/values-lv/strings.xml | 93 - usbtuner-res/values-mk-rMK/strings.xml | 90 - usbtuner-res/values-ml-rIN/strings.xml | 90 - usbtuner-res/values-mn-rMN/strings.xml | 90 - usbtuner-res/values-mr-rIN/strings.xml | 90 - usbtuner-res/values-ms-rMY/strings.xml | 90 - usbtuner-res/values-my-rMM/strings.xml | 90 - usbtuner-res/values-nb/strings.xml | 90 - usbtuner-res/values-ne-rNP/strings.xml | 90 - usbtuner-res/values-nl/strings.xml | 90 - usbtuner-res/values-pl/strings.xml | 96 - usbtuner-res/values-pt-rPT/strings.xml | 90 - usbtuner-res/values-pt/strings.xml | 90 - usbtuner-res/values-ro/strings.xml | 93 - usbtuner-res/values-ru/strings.xml | 92 - usbtuner-res/values-si-rLK/strings.xml | 90 - usbtuner-res/values-sk/strings.xml | 96 - usbtuner-res/values-sl/strings.xml | 96 - usbtuner-res/values-sr/strings.xml | 93 - usbtuner-res/values-sv/strings.xml | 90 - usbtuner-res/values-sw/strings.xml | 90 - usbtuner-res/values-ta-rIN/strings.xml | 90 - usbtuner-res/values-te-rIN/strings.xml | 90 - usbtuner-res/values-th/strings.xml | 90 - usbtuner-res/values-tl/strings.xml | 90 - usbtuner-res/values-tr/strings.xml | 90 - usbtuner-res/values-uk/strings.xml | 96 - usbtuner-res/values-ur-rPK/strings.xml | 90 - usbtuner-res/values-uz-rUZ/strings.xml | 90 - usbtuner-res/values-vi/strings.xml | 90 - usbtuner-res/values-zh-rCN/strings.xml | 90 - usbtuner-res/values-zh-rHK/strings.xml | 90 - usbtuner-res/values-zh-rTW/strings.xml | 90 - usbtuner-res/values-zu/strings.xml | 90 - usbtuner-res/values/attrs.xml | 25 - usbtuner-res/values/colors.xml | 24 - usbtuner-res/values/dimens.xml | 44 - usbtuner-res/values/integers.xml | 20 - usbtuner-res/values/strings.xml | 191 -- usbtuner-res/xml/ut_tvinputservice.xml | 39 - version.mk | 6 +- 651 files changed, 40530 insertions(+), 44408 deletions(-) delete mode 100644 common/src/com/android/tv/common/AutoCloseableUtils.java create mode 100644 common/src/com/android/tv/common/BaseApplication.java create mode 100644 common/src/com/android/tv/common/BaseSingletons.java delete mode 100644 common/src/com/android/tv/common/CollectionUtils.java create mode 100644 common/src/com/android/tv/common/CommonConstants.java create mode 100644 common/src/com/android/tv/common/CommonPreferenceProvider.java create mode 100644 common/src/com/android/tv/common/CommonPreferences.java delete mode 100644 common/src/com/android/tv/common/SharedPreferencesUtils.java delete mode 100644 common/src/com/android/tv/common/TvCommonConstants.java delete mode 100644 common/src/com/android/tv/common/TvCommonUtils.java create mode 100644 common/src/com/android/tv/common/annotation/UsedByNative.java create mode 100644 common/src/com/android/tv/common/concurrent/NamedThreadFactory.java create mode 100644 common/src/com/android/tv/common/config/DefaultConfigManager.java create mode 100644 common/src/com/android/tv/common/config/RemoteConfigFeature.java create mode 100644 common/src/com/android/tv/common/config/RemoteConfigUtils.java create mode 100644 common/src/com/android/tv/common/config/api/RemoteConfig.java create mode 100644 common/src/com/android/tv/common/customization/CustomAction.java create mode 100644 common/src/com/android/tv/common/customization/CustomizationManager.java create mode 100644 common/src/com/android/tv/common/experiments/ExperimentFlag.java create mode 100644 common/src/com/android/tv/common/experiments/ExperimentLoader.java create mode 100644 common/src/com/android/tv/common/experiments/Experiments.java create mode 100644 common/src/com/android/tv/common/feature/ExperimentFeature.java create mode 100644 common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java create mode 100644 common/src/com/android/tv/common/util/AutoCloseableUtils.java create mode 100644 common/src/com/android/tv/common/util/Clock.java create mode 100644 common/src/com/android/tv/common/util/CollectionUtils.java create mode 100644 common/src/com/android/tv/common/util/CommonUtils.java create mode 100644 common/src/com/android/tv/common/util/Debug.java create mode 100644 common/src/com/android/tv/common/util/DurationTimer.java create mode 100644 common/src/com/android/tv/common/util/LocationUtils.java create mode 100644 common/src/com/android/tv/common/util/NetworkTrafficTags.java create mode 100644 common/src/com/android/tv/common/util/PermissionUtils.java create mode 100644 common/src/com/android/tv/common/util/PostalCodeUtils.java create mode 100644 common/src/com/android/tv/common/util/SharedPreferencesUtils.java create mode 100644 common/src/com/android/tv/common/util/StringUtils.java create mode 100644 common/src/com/android/tv/common/util/SystemProperties.java create mode 100644 common/src/com/android/tv/common/util/SystemPropertiesProxy.java delete mode 100644 icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java delete mode 100644 icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java create mode 100644 libs/exoplayer-2.6.1.aar create mode 100644 libs/exoplayer-core-2.6.1.aar create mode 100644 libs/exoplayer-r1.5.16.aar delete mode 100644 libs/exoplayer.jar delete mode 100644 libs/exoplayer_v2.jar delete mode 100644 libs/exoplayer_v2_ext_ffmpeg.jar delete mode 100644 proto/channel.proto delete mode 100644 proto/track.proto delete mode 100644 res/drawable-xhdpi/ic_launcher.png create mode 100644 res/drawable-xhdpi/ic_live_channels.png create mode 100644 res/drawable-xhdpi/ic_live_channels_96x96.png create mode 100644 res/layout/details_overview.xml delete mode 100644 res/layout/guided_action_editable.xml delete mode 100644 res/layout/lb_details_overview.xml delete mode 100644 res/xml/remote_config_defaults.xml delete mode 100644 src/com/android/exoplayer/MediaFormatUtil.java delete mode 100644 src/com/android/exoplayer/MediaSoftwareCodecUtil.java delete mode 100644 src/com/android/exoplayer/text/SubtitleView.java delete mode 100644 src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java delete mode 100644 src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java delete mode 100644 src/com/android/tv/ApplicationSingletons.java delete mode 100644 src/com/android/tv/Features.java create mode 100644 src/com/android/tv/Starter.java create mode 100644 src/com/android/tv/TvFeatures.java create mode 100644 src/com/android/tv/TvSingletons.java create mode 100644 src/com/android/tv/app/LiveTvApplication.java delete mode 100644 src/com/android/tv/config/ConfigKeys.java delete mode 100644 src/com/android/tv/config/DefaultConfigManager.java delete mode 100644 src/com/android/tv/config/RemoteConfig.java delete mode 100644 src/com/android/tv/config/RemoteConfigFeature.java delete mode 100644 src/com/android/tv/config/RemoteConfigUtils.java delete mode 100644 src/com/android/tv/customization/CustomAction.java delete mode 100644 src/com/android/tv/customization/TvCustomizationManager.java create mode 100644 src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java create mode 100644 src/com/android/tv/data/epg/EpgFetchService.java create mode 100644 src/com/android/tv/data/epg/EpgFetcherImpl.java create mode 100644 src/com/android/tv/data/epg/EpgInputWhiteList.java delete mode 100644 src/com/android/tv/experiments/ExperimentFlag.java delete mode 100644 src/com/android/tv/experiments/Experiments.java delete mode 100644 src/com/android/tv/tuner/ChannelScanFileParser.java delete mode 100644 src/com/android/tv/tuner/DvbDeviceAccessor.java delete mode 100644 src/com/android/tv/tuner/DvbTunerHal.java delete mode 100644 src/com/android/tv/tuner/TunerHal.java delete mode 100644 src/com/android/tv/tuner/TunerPreferenceProvider.java delete mode 100644 src/com/android/tv/tuner/TunerPreferences.java delete mode 100644 src/com/android/tv/tuner/cc/CaptionLayout.java delete mode 100644 src/com/android/tv/tuner/cc/CaptionTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/cc/CaptionWindowLayout.java delete mode 100644 src/com/android/tv/tuner/cc/Cea708Parser.java delete mode 100644 src/com/android/tv/tuner/data/Cea708Data.java delete mode 100644 src/com/android/tv/tuner/data/PsiData.java delete mode 100644 src/com/android/tv/tuner/data/PsipData.java delete mode 100644 src/com/android/tv/tuner/data/TunerChannel.java delete mode 100644 src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java delete mode 100644 src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java delete mode 100644 src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/SampleExtractor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioClock.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java delete mode 100644 src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java delete mode 100644 src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl delete mode 100644 src/com/android/tv/tuner/layout/ScaledLayout.java delete mode 100644 src/com/android/tv/tuner/setup/ConnectionTypeFragment.java delete mode 100644 src/com/android/tv/tuner/setup/PostalCodeFragment.java delete mode 100644 src/com/android/tv/tuner/setup/ScanFragment.java delete mode 100644 src/com/android/tv/tuner/setup/ScanResultFragment.java delete mode 100644 src/com/android/tv/tuner/setup/TunerSetupActivity.java delete mode 100644 src/com/android/tv/tuner/setup/WelcomeFragment.java delete mode 100644 src/com/android/tv/tuner/source/FileTsStreamer.java delete mode 100644 src/com/android/tv/tuner/source/TsDataSource.java delete mode 100644 src/com/android/tv/tuner/source/TsDataSourceManager.java delete mode 100644 src/com/android/tv/tuner/source/TsStreamWriter.java delete mode 100644 src/com/android/tv/tuner/source/TsStreamer.java delete mode 100644 src/com/android/tv/tuner/source/TunerTsStreamer.java delete mode 100644 src/com/android/tv/tuner/source/TunerTsStreamerManager.java delete mode 100644 src/com/android/tv/tuner/ts/SectionParser.java delete mode 100644 src/com/android/tv/tuner/ts/TsParser.java delete mode 100644 src/com/android/tv/tuner/tvinput/ChannelDataManager.java delete mode 100644 src/com/android/tv/tuner/tvinput/EventDetector.java delete mode 100644 src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java delete mode 100644 src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerDebug.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerRecordingSession.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerSession.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerSessionWorker.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java delete mode 100644 src/com/android/tv/tuner/tvinput/TunerTvInputService.java delete mode 100644 src/com/android/tv/tuner/util/ByteArrayBuffer.java delete mode 100644 src/com/android/tv/tuner/util/ConvertUtils.java delete mode 100644 src/com/android/tv/tuner/util/GlobalSettingsUtils.java delete mode 100644 src/com/android/tv/tuner/util/Ints.java delete mode 100644 src/com/android/tv/tuner/util/PostalCodeUtils.java delete mode 100644 src/com/android/tv/tuner/util/StatusTextUtils.java delete mode 100644 src/com/android/tv/tuner/util/SystemPropertiesProxy.java delete mode 100644 src/com/android/tv/tuner/util/TisConfiguration.java delete mode 100644 src/com/android/tv/tuner/util/TunerInputInfoUtils.java delete mode 100644 src/com/android/tv/util/AccountHelper.java delete mode 100644 src/com/android/tv/util/Clock.java delete mode 100644 src/com/android/tv/util/Debug.java delete mode 100644 src/com/android/tv/util/DurationTimer.java delete mode 100644 src/com/android/tv/util/LocationUtils.java delete mode 100644 src/com/android/tv/util/NamedThreadFactory.java delete mode 100644 src/com/android/tv/util/NetworkTrafficTags.java delete mode 100644 src/com/android/tv/util/PermissionUtils.java create mode 100644 src/com/android/tv/util/SqlParams.java delete mode 100644 src/com/android/tv/util/StringUtils.java delete mode 100644 src/com/android/tv/util/SystemProperties.java create mode 100644 src/com/android/tv/util/account/AccountHelper.java create mode 100644 src/com/android/tv/util/account/AccountHelperImpl.java delete mode 100644 tests/common/src/com/android/tv/testing/ChannelInfo.java create mode 100644 tests/common/src/com/android/tv/testing/ChannelNumberSubject.java delete mode 100644 tests/common/src/com/android/tv/testing/ChannelUtils.java delete mode 100644 tests/common/src/com/android/tv/testing/Constants.java create mode 100644 tests/common/src/com/android/tv/testing/DbTestingUtils.java create mode 100644 tests/common/src/com/android/tv/testing/EpgTestData.java create mode 100644 tests/common/src/com/android/tv/testing/FakeEpgFetcher.java create mode 100644 tests/common/src/com/android/tv/testing/FakeEpgReader.java create mode 100644 tests/common/src/com/android/tv/testing/FakeRemoteConfig.java create mode 100644 tests/common/src/com/android/tv/testing/FakeTvInputManager.java create mode 100644 tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java create mode 100644 tests/common/src/com/android/tv/testing/FakeTvProvider.java delete mode 100644 tests/common/src/com/android/tv/testing/ProgramInfo.java delete mode 100644 tests/common/src/com/android/tv/testing/ProgramUtils.java create mode 100644 tests/common/src/com/android/tv/testing/SingletonProvider.java create mode 100644 tests/common/src/com/android/tv/testing/TestSingletonApp.java delete mode 100644 tests/common/src/com/android/tv/testing/TvContentRatingConstants.java delete mode 100644 tests/common/src/com/android/tv/testing/Utils.java create mode 100644 tests/common/src/com/android/tv/testing/constants/Constants.java create mode 100644 tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java create mode 100644 tests/common/src/com/android/tv/testing/data/ChannelInfo.java create mode 100644 tests/common/src/com/android/tv/testing/data/ChannelUtils.java create mode 100644 tests/common/src/com/android/tv/testing/data/ProgramInfo.java create mode 100644 tests/common/src/com/android/tv/testing/data/ProgramUtils.java create mode 100644 tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java create mode 100644 tests/common/src/com/android/tv/testing/utils/Utils.java create mode 100644 tests/input/func.sh create mode 100755 tests/input/tools/get_test_logos.sh create mode 100644 tests/input/unit.sh create mode 100644 tests/tunerscripts/measure-tuning-time.awk create mode 100755 tests/tunerscripts/usbtuner-test.sh create mode 100644 tests/tunerunit/Android.mk create mode 100644 tests/tunerunit/AndroidManifest.xml create mode 100644 tests/tunerunit/src/com/android/tv/tuner/exoplayer/buffer/VerySlowSampleChunk.java create mode 100644 tests/tunerunit/src/com/android/tv/tuner/exoplayer/tests/AssetDataSource.java create mode 100644 tests/tunerunit/src/com/android/tv/tuner/exoplayer/tests/SampleSourceExtractorTest.java delete mode 100644 tests/unit/src/com/android/tv/data/ChannelNumberTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java delete mode 100644 tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java delete mode 100644 tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java delete mode 100644 tests/unit/src/com/android/tv/experiments/ExperimentsTest.java delete mode 100644 tests/unit/src/com/android/tv/util/MockApplicationSingletons.java create mode 100644 tests/unit/src/com/android/tv/util/MockTvSingletons.java create mode 100644 tuner/Android.mk create mode 100644 tuner/AndroidManifest.xml create mode 100644 tuner/BuildConfig.java.in create mode 100644 tuner/buildconfig.mk create mode 100644 tuner/proto/channel.proto create mode 100644 tuner/proto/track.proto create mode 100644 tuner/res/drawable-xhdpi/recommendation_antenna.png create mode 100644 tuner/res/drawable-xhdpi/usb_antenna.png create mode 100644 tuner/res/drawable/ut_scan_progress.xml create mode 100644 tuner/res/layout/guided_action_editable.xml create mode 100644 tuner/res/layout/ut_channel_list.xml create mode 100644 tuner/res/layout/ut_channel_scan.xml create mode 100644 tuner/res/layout/ut_overlay_view.xml create mode 100644 tuner/res/raw/ut_euro_dvbt_all create mode 100644 tuner/res/raw/ut_kr_all create mode 100644 tuner/res/raw/ut_kr_atsc_center_frequencies_8vsb create mode 100644 tuner/res/raw/ut_kr_cable_standard_center_frequencies_qam256 create mode 100644 tuner/res/raw/ut_kr_dev_cj_cable_center_frequencies_qam256 create mode 100644 tuner/res/raw/ut_us_all create mode 100644 tuner/res/raw/ut_us_atsc_center_frequencies_8vsb create mode 100644 tuner/res/raw/ut_us_cable_standard_center_frequencies_qam256 create mode 100644 tuner/res/values/attrs.xml create mode 100644 tuner/res/values/colors.xml create mode 100644 tuner/res/values/dimens.xml create mode 100644 tuner/res/values/integers.xml create mode 100644 tuner/res/values/strings.xml create mode 100644 tuner/res/xml/ut_tvinputservice.xml create mode 100644 tuner/src/com/android/tv/tuner/ChannelScanFileParser.java create mode 100644 tuner/src/com/android/tv/tuner/DvbDeviceAccessor.java create mode 100644 tuner/src/com/android/tv/tuner/DvbTunerHal.java create mode 100644 tuner/src/com/android/tv/tuner/TunerFeatures.java create mode 100644 tuner/src/com/android/tv/tuner/TunerHal.java create mode 100644 tuner/src/com/android/tv/tuner/TunerPreferences.java create mode 100644 tuner/src/com/android/tv/tuner/cc/CaptionLayout.java create mode 100644 tuner/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java create mode 100644 tuner/src/com/android/tv/tuner/cc/CaptionWindowLayout.java create mode 100644 tuner/src/com/android/tv/tuner/cc/Cea708Parser.java create mode 100644 tuner/src/com/android/tv/tuner/data/Cea708Data.java create mode 100644 tuner/src/com/android/tv/tuner/data/PsiData.java create mode 100644 tuner/src/com/android/tv/tuner/data/PsipData.java create mode 100644 tuner/src/com/android/tv/tuner/data/TunerChannel.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/SampleExtractor.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java create mode 100644 tuner/src/com/android/tv/tuner/exoplayer/text/SubtitleView.java create mode 100644 tuner/src/com/android/tv/tuner/layout/ScaledLayout.java create mode 100644 tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java create mode 100644 tuner/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java create mode 100644 tuner/src/com/android/tv/tuner/setup/LiveTvTunerSetupActivity.java create mode 100644 tuner/src/com/android/tv/tuner/setup/PostalCodeFragment.java create mode 100644 tuner/src/com/android/tv/tuner/setup/ScanFragment.java create mode 100644 tuner/src/com/android/tv/tuner/setup/ScanResultFragment.java create mode 100644 tuner/src/com/android/tv/tuner/setup/WelcomeFragment.java create mode 100644 tuner/src/com/android/tv/tuner/source/FileTsStreamer.java create mode 100644 tuner/src/com/android/tv/tuner/source/TsDataSource.java create mode 100644 tuner/src/com/android/tv/tuner/source/TsDataSourceManager.java create mode 100644 tuner/src/com/android/tv/tuner/source/TsStreamWriter.java create mode 100644 tuner/src/com/android/tv/tuner/source/TsStreamer.java create mode 100644 tuner/src/com/android/tv/tuner/source/TunerTsStreamer.java create mode 100644 tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java create mode 100644 tuner/src/com/android/tv/tuner/ts/SectionParser.java create mode 100644 tuner/src/com/android/tv/tuner/ts/TsParser.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/ChannelDataManager.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/EventDetector.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/LiveTvTunerTvInputService.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerDebug.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerSession.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java create mode 100644 tuner/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java create mode 100644 tuner/src/com/android/tv/tuner/util/ByteArrayBuffer.java create mode 100644 tuner/src/com/android/tv/tuner/util/ConvertUtils.java create mode 100644 tuner/src/com/android/tv/tuner/util/GlobalSettingsUtils.java create mode 100644 tuner/src/com/android/tv/tuner/util/Ints.java create mode 100644 tuner/src/com/android/tv/tuner/util/StatusTextUtils.java create mode 100644 tuner/src/com/android/tv/tuner/util/TunerInputInfoUtils.java create mode 100644 tuner/src/com/google/android/exoplayer/MediaFormatUtil.java create mode 100644 tuner/src/com/google/android/exoplayer/MediaSoftwareCodecUtil.java create mode 100644 tuner/src/com/mediatek/tunerservice/IMtkTuner.aidl create mode 100644 tuner/tests/unittests/javatests/AndroidManifest.xml create mode 100644 tuner/tests/unittests/javatests/assets/capture_kqed.ts create mode 100644 tuner/tests/unittests/javatests/assets/capture_stream.ts create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/AndroidManifest.xml create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/FakeTunerHal.java create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/FileTunerHal.java create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/ZappingTimeTest.java create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/layout/tests/AndroidManifest.xml create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/layout/tests/ScaledLayoutActivity.java create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/layout/tests/ScaledLayoutTest.java create mode 100644 tuner/tests/unittests/javatests/com/google/android/tv/tuner/setup/TunerHalFactoryTest.java create mode 100644 tuner/tests/unittests/javatests/res/layout/activity_scaled_layout_test.xml create mode 100644 tuner/tests/unittests/javatests/res/values/attrs.xml delete mode 100644 usbtuner-res/drawable-xhdpi/recommendation_antenna.png delete mode 100644 usbtuner-res/drawable-xhdpi/usb_antenna.png delete mode 100644 usbtuner-res/drawable/ut_scan_progress.xml delete mode 100644 usbtuner-res/layout/ut_channel_list.xml delete mode 100644 usbtuner-res/layout/ut_channel_scan.xml delete mode 100644 usbtuner-res/layout/ut_overlay_view.xml delete mode 100644 usbtuner-res/raw/ut_euro_dvbt_all delete mode 100644 usbtuner-res/raw/ut_kr_all delete mode 100644 usbtuner-res/raw/ut_kr_atsc_center_frequencies_8vsb delete mode 100644 usbtuner-res/raw/ut_kr_cable_standard_center_frequencies_qam256 delete mode 100644 usbtuner-res/raw/ut_kr_dev_cj_cable_center_frequencies_qam256 delete mode 100644 usbtuner-res/raw/ut_us_all delete mode 100644 usbtuner-res/raw/ut_us_atsc_center_frequencies_8vsb delete mode 100644 usbtuner-res/raw/ut_us_cable_standard_center_frequencies_qam256 delete mode 100644 usbtuner-res/values-af/strings.xml delete mode 100644 usbtuner-res/values-am/strings.xml delete mode 100644 usbtuner-res/values-ar/strings.xml delete mode 100644 usbtuner-res/values-az-rAZ/strings.xml delete mode 100644 usbtuner-res/values-bg/strings.xml delete mode 100644 usbtuner-res/values-bn-rBD/strings.xml delete mode 100644 usbtuner-res/values-ca/strings.xml delete mode 100644 usbtuner-res/values-cs/strings.xml delete mode 100644 usbtuner-res/values-da/strings.xml delete mode 100644 usbtuner-res/values-de/strings.xml delete mode 100644 usbtuner-res/values-el/strings.xml delete mode 100644 usbtuner-res/values-en-rAU/strings.xml delete mode 100644 usbtuner-res/values-en-rGB/strings.xml delete mode 100644 usbtuner-res/values-en-rIN/strings.xml delete mode 100644 usbtuner-res/values-es-rUS/strings.xml delete mode 100644 usbtuner-res/values-es/strings.xml delete mode 100644 usbtuner-res/values-et-rEE/strings.xml delete mode 100644 usbtuner-res/values-eu-rES/strings.xml delete mode 100644 usbtuner-res/values-fa/strings.xml delete mode 100644 usbtuner-res/values-fi/strings.xml delete mode 100644 usbtuner-res/values-fr-rCA/strings.xml delete mode 100644 usbtuner-res/values-fr/strings.xml delete mode 100644 usbtuner-res/values-gl-rES/strings.xml delete mode 100644 usbtuner-res/values-hi/strings.xml delete mode 100644 usbtuner-res/values-hr/strings.xml delete mode 100644 usbtuner-res/values-hu/strings.xml delete mode 100644 usbtuner-res/values-hy-rAM/strings.xml delete mode 100644 usbtuner-res/values-in/strings.xml delete mode 100644 usbtuner-res/values-is-rIS/strings.xml delete mode 100644 usbtuner-res/values-it/strings.xml delete mode 100644 usbtuner-res/values-iw/strings.xml delete mode 100644 usbtuner-res/values-ja/strings.xml delete mode 100644 usbtuner-res/values-ka-rGE/strings.xml delete mode 100644 usbtuner-res/values-kk-rKZ/strings.xml delete mode 100644 usbtuner-res/values-km-rKH/strings.xml delete mode 100644 usbtuner-res/values-kn-rIN/strings.xml delete mode 100644 usbtuner-res/values-ko/strings.xml delete mode 100644 usbtuner-res/values-ky-rKG/strings.xml delete mode 100644 usbtuner-res/values-lo-rLA/strings.xml delete mode 100644 usbtuner-res/values-lt/strings.xml delete mode 100644 usbtuner-res/values-lv/strings.xml delete mode 100644 usbtuner-res/values-mk-rMK/strings.xml delete mode 100644 usbtuner-res/values-ml-rIN/strings.xml delete mode 100644 usbtuner-res/values-mn-rMN/strings.xml delete mode 100644 usbtuner-res/values-mr-rIN/strings.xml delete mode 100644 usbtuner-res/values-ms-rMY/strings.xml delete mode 100644 usbtuner-res/values-my-rMM/strings.xml delete mode 100644 usbtuner-res/values-nb/strings.xml delete mode 100644 usbtuner-res/values-ne-rNP/strings.xml delete mode 100644 usbtuner-res/values-nl/strings.xml delete mode 100644 usbtuner-res/values-pl/strings.xml delete mode 100644 usbtuner-res/values-pt-rPT/strings.xml delete mode 100644 usbtuner-res/values-pt/strings.xml delete mode 100644 usbtuner-res/values-ro/strings.xml delete mode 100644 usbtuner-res/values-ru/strings.xml delete mode 100644 usbtuner-res/values-si-rLK/strings.xml delete mode 100644 usbtuner-res/values-sk/strings.xml delete mode 100644 usbtuner-res/values-sl/strings.xml delete mode 100644 usbtuner-res/values-sr/strings.xml delete mode 100644 usbtuner-res/values-sv/strings.xml delete mode 100644 usbtuner-res/values-sw/strings.xml delete mode 100644 usbtuner-res/values-ta-rIN/strings.xml delete mode 100644 usbtuner-res/values-te-rIN/strings.xml delete mode 100644 usbtuner-res/values-th/strings.xml delete mode 100644 usbtuner-res/values-tl/strings.xml delete mode 100644 usbtuner-res/values-tr/strings.xml delete mode 100644 usbtuner-res/values-uk/strings.xml delete mode 100644 usbtuner-res/values-ur-rPK/strings.xml delete mode 100644 usbtuner-res/values-uz-rUZ/strings.xml delete mode 100644 usbtuner-res/values-vi/strings.xml delete mode 100644 usbtuner-res/values-zh-rCN/strings.xml delete mode 100644 usbtuner-res/values-zh-rHK/strings.xml delete mode 100644 usbtuner-res/values-zh-rTW/strings.xml delete mode 100644 usbtuner-res/values-zu/strings.xml delete mode 100644 usbtuner-res/values/attrs.xml delete mode 100644 usbtuner-res/values/colors.xml delete mode 100644 usbtuner-res/values/dimens.xml delete mode 100644 usbtuner-res/values/integers.xml delete mode 100644 usbtuner-res/values/strings.xml delete mode 100644 usbtuner-res/xml/ut_tvinputservice.xml diff --git a/Android.mk b/Android.mk index 07f17e68..152ea0f1 100644 --- a/Android.mk +++ b/Android.mk @@ -23,9 +23,7 @@ LOCAL_MODULE_TAGS := optional include $(LOCAL_PATH)/version.mk -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) \ - $(call all-proto-files-under, proto) +LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := LiveTv @@ -40,18 +38,14 @@ LOCAL_MIN_SDK_VERSION := 23 # M LOCAL_USE_AAPT2 := true LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/common/res \ + $(LOCAL_PATH)/tuner/res \ $(LOCAL_PATH)/res \ - $(LOCAL_PATH)/usbtuner-res - -LOCAL_SRC_FILES += \ - src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl LOCAL_STATIC_JAVA_LIBRARIES := \ - icu4j-usbtuner \ - lib-exoplayer \ - lib-exoplayer-v2 \ - lib-exoplayer-v2-ext-ffmpeg \ - android-support-annotations + android-support-annotations \ + javax-annotations-jar \ + jsr330-jar \ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-compat \ @@ -60,7 +54,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-palette \ android-support-v7-recyclerview \ android-support-v17-leanback \ - tv-common + live-tv-tuner \ + tv-common \ LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked @@ -68,33 +63,14 @@ LOCAL_AAPT_FLAGS += \ --version-name "$(version_name_package)" \ --version-code $(version_code_package) \ -LOCAL_PROGUARD_FLAG_FILES := proguard.flags +#LOCAL_PROGUARD_FLAG_FILES := proguard.flags LOCAL_JNI_SHARED_LIBRARIES := libtunertvinput_jni LOCAL_AAPT_FLAGS += --extra-packages com.android.tv.tuner -LOCAL_PROTOC_OPTIMIZE_TYPE := nano -LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/ - include $(BUILD_PACKAGE) -# -------------------------------------------------------------- -# Build a tiny icu4j library out of the classes necessary for the project. - -include $(CLEAR_VARS) - -LOCAL_MODULE := icu4j-usbtuner -LOCAL_MODULE_TAGS := optional -icu4j_path := icu/icu4j -LOCAL_SRC_FILES := \ - $(icu4j_path)/main/classes/core/src/com/ibm/icu/text/SCSU.java \ - $(icu4j_path)/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java -LOCAL_SDK_VERSION := system_current - -include $(BUILD_STATIC_JAVA_LIBRARY) - - ############################################################# # Pre-built dependency jars ############################################################# @@ -103,13 +79,18 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \ - lib-exoplayer:libs/exoplayer.jar \ - lib-exoplayer-v2:libs/exoplayer_v2.jar \ - lib-exoplayer-v2-ext-ffmpeg:libs/exoplayer_v2_ext_ffmpeg.jar \ + lib-exoplayer:libs/exoplayer-r1.5.16.aar \ + lib-exoplayer-v2:libs/exoplayer-2.6.1.aar \ + lib-exoplayer-v2-core:libs/exoplayer-core-2.6.1.aar \ +# TODO use external/jsr330 +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += auto-value-jar:../../../prebuilts/tools/common/m2/repository/com/google/auto/value/auto-value/1.5.2/auto-value-1.5.2.jar +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += guava-22-0-jar:../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0.jar +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += javax-annotations-jar:../../../prebuilts/tools/common/m2/repository/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2.jar +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += jsr330-jar:../../../prebuilts/tools/common/m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar +LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += truth-0-36-prebuilt-jar:../../../prebuilts/tools/common/m2/repository/com/google/truth/truth/0.36/truth-0.36.jar -include $(BUILD_MULTI_PREBUILT) +include $(BUILD_MULTI_PREBUILT) include $(call all-makefiles-under,$(LOCAL_PATH)) - diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8770301e..88c86e31 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -22,6 +22,7 @@ + @@ -42,7 +43,6 @@ - @@ -100,11 +100,11 @@ android:resource="@xml/searchable" /> - - @@ -113,17 +113,17 @@ - - - @@ -138,7 +138,7 @@ - @@ -149,35 +149,36 @@ - - - - - + android:enabled="true" tools:ignore="ExportedContentProvider" + > - @@ -212,7 +213,7 @@ - - - - - - - - - - @@ -270,17 +265,16 @@ - - + + - - - diff --git a/common/BuildConfig.java.in b/common/BuildConfig.java.in index f4631340..10816db1 100644 --- a/common/BuildConfig.java.in +++ b/common/BuildConfig.java.in @@ -4,5 +4,6 @@ package com.android.tv.common; public final class BuildConfig { public static final boolean DEBUG = %DEBUG%; public static final boolean ENG = %ENG%; + public static final boolean NO_JNI_TEST = false; private BuildConfig() {} } \ No newline at end of file diff --git a/common/res/values/strings.xml b/common/res/values/strings.xml index ec029136..aa7adac4 100644 --- a/common/res/values/strings.xml +++ b/common/res/values/strings.xml @@ -18,6 +18,7 @@ Done Skip + Retry diff --git a/common/src/com/android/tv/common/AutoCloseableUtils.java b/common/src/com/android/tv/common/AutoCloseableUtils.java deleted file mode 100644 index 03d7bd9a..00000000 --- a/common/src/com/android/tv/common/AutoCloseableUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.common; - -import android.util.Log; - -/** Static utilities for AutoCloseable. */ -public class AutoCloseableUtils { - private static final String TAG = "AutoCloseableUtils"; - - private AutoCloseableUtils() {} - - public static void closeQuietly(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception ex) { - Log.e(TAG, "Error closing " + closeable, ex); - } - } -} diff --git a/common/src/com/android/tv/common/BaseApplication.java b/common/src/com/android/tv/common/BaseApplication.java new file mode 100644 index 00000000..75e689ad --- /dev/null +++ b/common/src/com/android/tv/common/BaseApplication.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; +import android.os.StrictMode; +import android.support.annotation.VisibleForTesting; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.util.Clock; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.Debug; +import com.android.tv.common.util.PermissionUtils; +import com.android.tv.common.util.SystemProperties; + +/** The base application class for Live TV applications. */ +public abstract class BaseApplication extends Application implements BaseSingletons { + private RecordingStorageStatusManager mRecordingStorageStatusManager; + + /** + * An instance of {@link BaseSingletons}. Note that this can be set directly only for the test + * purpose. + */ + @VisibleForTesting public static BaseSingletons sSingletons; + + /** Returns the {@link BaseSingletons} using the application context. */ + public static BaseSingletons getSingletons(Context context) { + // STOP-SHIP: changing the method to protected once the Tuner application is created. + // No need to be "synchronized" because this doesn't create any instance. + if (sSingletons == null) { + sSingletons = (BaseSingletons) context.getApplicationContext(); + } + return sSingletons; + } + + @Override + public void onCreate() { + super.onCreate(); + if (!PermissionUtils.hasInternet(this)) { + // When an isolated process starts, just skip all the initialization. + return; + } + Debug.getTimer(Debug.TAG_START_UP_TIMER).start(); + Debug.getTimer(Debug.TAG_START_UP_TIMER) + .log("Start " + this.getClass().getSimpleName() + ".onCreate"); + CommonPreferences.initialize(this); + + // Only set StrictMode for ENG builds because the build server only produces userdebug + // builds. + if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) { + StrictMode.ThreadPolicy.Builder threadPolicyBuilder = + new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog(); + // TODO(b/69565157): Turn penaltyDeath on for VMPolicy when tests are fixed. + StrictMode.VmPolicy.Builder vmPolicyBuilder = + new StrictMode.VmPolicy.Builder().detectAll().penaltyLog(); + + if (!CommonUtils.isRunningInTest()) { + threadPolicyBuilder.penaltyDialog(); + } + StrictMode.setThreadPolicy(threadPolicyBuilder.build()); + StrictMode.setVmPolicy(vmPolicyBuilder.build()); + } + if (CommonFeatures.DVR.isEnabled(this)) { + getRecordingStorageStatusManager(); + } + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // Fetch remote config + getRemoteConfig().fetch(null); + return null; + } + }.execute(); + } + + @Override + public Clock getClock() { + return Clock.SYSTEM; + } + + /** Returns {@link RecordingStorageStatusManager}. */ + @Override + @TargetApi(Build.VERSION_CODES.N) + public RecordingStorageStatusManager getRecordingStorageStatusManager() { + if (mRecordingStorageStatusManager == null) { + mRecordingStorageStatusManager = new RecordingStorageStatusManager(this); + } + return mRecordingStorageStatusManager; + } + + @Override + public abstract Intent getTunerSetupIntent(Context context); +} diff --git a/common/src/com/android/tv/common/BaseSingletons.java b/common/src/com/android/tv/common/BaseSingletons.java new file mode 100644 index 00000000..3a4650fc --- /dev/null +++ b/common/src/com/android/tv/common/BaseSingletons.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common; + +import android.content.Context; +import android.content.Intent; +import com.android.tv.common.config.api.RemoteConfig; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.util.Clock; + +public interface BaseSingletons { + + Clock getClock(); + + RecordingStorageStatusManager getRecordingStorageStatusManager(); + + RemoteConfig getRemoteConfig(); + + Intent getTunerSetupIntent(Context context); + + String getEmbeddedTunerInputId(); +} diff --git a/common/src/com/android/tv/common/CollectionUtils.java b/common/src/com/android/tv/common/CollectionUtils.java deleted file mode 100644 index 79f6a9e0..00000000 --- a/common/src/com/android/tv/common/CollectionUtils.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.common; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** Static utilities for collections */ -public class CollectionUtils { - - /** - * Returns an array with the arrays concatenated together. - * - * @see Stackoverflow answer by Joachim Sauer - */ - public static T[] concatAll(T[] first, T[]... rest) { - int totalLength = first.length; - for (T[] array : rest) { - totalLength += array.length; - } - T[] result = Arrays.copyOf(first, totalLength); - int offset = first.length; - for (T[] array : rest) { - System.arraycopy(array, 0, result, offset, array.length); - offset += array.length; - } - return result; - } - - /** - * Unions the two collections and returns the unified list. - * - *

The elements is not compared with hashcode() or equals(). Comparator is used for the - * equality check. - */ - public static List union( - Collection originals, Collection toAdds, Comparator comparator) { - List result = new ArrayList<>(originals); - Collections.sort(result, comparator); - List resultToAdd = new ArrayList<>(); - for (T toAdd : toAdds) { - if (Collections.binarySearch(result, toAdd, comparator) < 0) { - resultToAdd.add(toAdd); - } - } - result.addAll(resultToAdd); - return result; - } - - /** Subtracts the elements from the original collection. */ - public static List subtract( - Collection originals, T[] toSubtracts, Comparator comparator) { - List result = new ArrayList<>(originals); - Collections.sort(result, comparator); - for (T toSubtract : toSubtracts) { - int index = Collections.binarySearch(result, toSubtract, comparator); - if (index >= 0) { - result.remove(index); - } - } - return result; - } - - /** Returns {@code true} if the two specified collections have common elements. */ - public static boolean containsAny( - Collection c1, Collection c2, Comparator comparator) { - List contains = new ArrayList<>(c1); - Collections.sort(contains, comparator); - for (T iterate : c2) { - if (Collections.binarySearch(contains, iterate, comparator) >= 0) { - return true; - } - } - return false; - } -} diff --git a/common/src/com/android/tv/common/CommonConstants.java b/common/src/com/android/tv/common/CommonConstants.java new file mode 100644 index 00000000..26d68b4d --- /dev/null +++ b/common/src/com/android/tv/common/CommonConstants.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common; + +import android.media.tv.TvInputInfo; + +/** Constants for common use in apps and tests. */ +public final class CommonConstants { + /** 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}. + * + *

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}. + * + *

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}. + * + *

Value type: Intent (Parcelable) + */ + public static final String EXTRA_ACTIVITY_AFTER_COMPLETION = + "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION"; + + private CommonConstants() {} +} diff --git a/common/src/com/android/tv/common/CommonPreferenceProvider.java b/common/src/com/android/tv/common/CommonPreferenceProvider.java new file mode 100644 index 00000000..05eb2e7e --- /dev/null +++ b/common/src/com/android/tv/common/CommonPreferenceProvider.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; + +/** + * A content provider for storing common preferences. It's used across TV app and tuner TV inputs. + */ +public class CommonPreferenceProvider extends ContentProvider { + /** The authority of the provider */ + public static final String AUTHORITY = "com.android.tv.common.preferences"; + + private static final String PATH_PREFERENCES = "preferences"; + + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "usbtuner_preferences.db"; + private static final String PREFERENCES_TABLE = "preferences"; + + private static final int MATCH_PREFERENCE = 1; + private static final int MATCH_PREFERENCE_KEY = 2; + + private static final UriMatcher sUriMatcher; + + private DatabaseOpenHelper mDatabaseOpenHelper; + + static { + sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + sUriMatcher.addURI(AUTHORITY, "preferences", MATCH_PREFERENCE); + sUriMatcher.addURI(AUTHORITY, "preferences/*", MATCH_PREFERENCE_KEY); + } + + /** + * Builds a Uri that points to a specific preference. + * + * @param key a key of the preference to point to + */ + public static Uri buildPreferenceUri(String key) { + return Preferences.CONTENT_URI.buildUpon().appendPath(key).build(); + } + + /** Columns definitions for the preferences table. */ + public interface Preferences { + + /** The content:// style for the preferences table. */ + Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES); + + /** The MIME type of a directory of preferences. */ + String CONTENT_TYPE = "vnd.android.cursor.dir/preferences"; + + /** The MIME type of a single preference. */ + String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences"; + + /** + * The ID of this preference. + * + *

This is auto-incremented. + * + *

Type: INTEGER + */ + String _ID = "_id"; + + /** + * The key of this preference. + * + *

Should be unique. + * + *

Type: TEXT + */ + String COLUMN_KEY = "key"; + + /** + * The value of this preference. + * + *

Type: TEXT + */ + String COLUMN_VALUE = "value"; + } + + private static class DatabaseOpenHelper extends SQLiteOpenHelper { + public DatabaseOpenHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL( + "CREATE TABLE " + + PREFERENCES_TABLE + + " (" + + Preferences._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Preferences.COLUMN_KEY + + " TEXT NOT NULL," + + Preferences.COLUMN_VALUE + + " TEXT);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // No-op + } + } + + @Override + public boolean onCreate() { + mDatabaseOpenHelper = new DatabaseOpenHelper(getContext()); + return true; + } + + @Override + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + int match = sUriMatcher.match(uri); + if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) { + throw new UnsupportedOperationException(); + } + SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase(); + Cursor cursor = + db.query( + PREFERENCES_TABLE, + projection, + selection, + selectionArgs, + null, + null, + sortOrder); + cursor.setNotificationUri(getContext().getContentResolver(), uri); + return cursor; + } + + @Override + public String getType(Uri uri) { + switch (sUriMatcher.match(uri)) { + case MATCH_PREFERENCE: + return Preferences.CONTENT_TYPE; + case MATCH_PREFERENCE_KEY: + return Preferences.CONTENT_ITEM_TYPE; + } + throw new IllegalArgumentException("Unknown URI " + uri); + } + + /** + * Inserts a preference row into the preference table. + * + *

If a key is already exists in the table, it removes the old row and inserts a new row. + * + * @param uri the URL of the table to insert into + * @param values the initial values for the newly inserted row + * @return the URL of the newly created row + */ + @Override + public Uri insert(Uri uri, ContentValues values) { + if (sUriMatcher.match(uri) != MATCH_PREFERENCE) { + throw new UnsupportedOperationException(); + } + return insertRow(uri, values); + } + + private Uri insertRow(Uri uri, ContentValues values) { + SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase(); + + // Remove the old row. + db.delete( + PREFERENCES_TABLE, + Preferences.COLUMN_KEY + " like ?", + new String[] {values.getAsString(Preferences.COLUMN_KEY)}); + + long rowId = db.insert(PREFERENCES_TABLE, null, values); + if (rowId > 0) { + Uri rowUri = buildPreferenceUri(values.getAsString(Preferences.COLUMN_KEY)); + getContext().getContentResolver().notifyChange(rowUri, null); + return rowUri; + } + + throw new SQLiteException("Failed to insert row into " + uri); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } +} diff --git a/common/src/com/android/tv/common/CommonPreferences.java b/common/src/com/android/tv/common/CommonPreferences.java new file mode 100644 index 00000000..8023d739 --- /dev/null +++ b/common/src/com/android/tv/common/CommonPreferences.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.common; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.GuardedBy; +import android.support.annotation.IntDef; +import android.support.annotation.MainThread; +import android.util.Log; +import com.android.tv.common.CommonPreferenceProvider.Preferences; +import com.android.tv.common.util.CommonUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; + +/** A helper class for setting/getting common preferences across applications. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed +public class CommonPreferences { + private static final String TAG = "CommonPreferences"; + + private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup"; + private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream"; + private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting"; + private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code"; + + private static final Map sPref2TypeMapping = new HashMap<>(); + + static { + sPref2TypeMapping.put(PREFS_KEY_TRICKPLAY_SETTING, int.class); + sPref2TypeMapping.put(PREFS_KEY_STORE_TS_STREAM, boolean.class); + sPref2TypeMapping.put(PREFS_KEY_LAUNCH_SETUP, boolean.class); + sPref2TypeMapping.put(PREFS_KEY_LAST_POSTAL_CODE, String.class); + } + + private static final String SHARED_PREFS_NAME = "com.android.tv.common.preferences"; + + @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED}) + @Retention(RetentionPolicy.SOURCE) + public @interface TrickplaySetting {} + + /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */ + public static final int TRICKPLAY_SETTING_NOT_SET = -1; + + /** Trickplay setting is disabled. */ + public static final int TRICKPLAY_SETTING_DISABLED = 0; + + /** Trickplay setting is enabled. */ + public static final int TRICKPLAY_SETTING_ENABLED = 1; + + @GuardedBy("CommonPreferences.class") + private static final Bundle sPreferenceValues = new Bundle(); + + private static LoadPreferencesTask sLoadPreferencesTask; + private static ContentObserver sContentObserver; + private static CommonPreferencesChangedListener sPreferencesChangedListener = null; + + protected static boolean sInitialized; + + /** Listeners for CommonPreferences change. */ + public interface CommonPreferencesChangedListener { + void onCommonPreferencesChanged(); + } + + /** Initializes the common preferences. */ + @MainThread + public static void initialize(final Context context) { + if (sInitialized) { + return; + } + sInitialized = true; + if (useContentProvider(context)) { + loadPreferences(context); + sContentObserver = + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + loadPreferences(context); + } + }; + context.getContentResolver() + .registerContentObserver(Preferences.CONTENT_URI, true, sContentObserver); + } else { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + getSharedPreferences(context); + return null; + } + }.execute(); + } + } + + /** Releases the resources. */ + public static synchronized void release(Context context) { + if (useContentProvider(context) && sContentObserver != null) { + context.getContentResolver().unregisterContentObserver(sContentObserver); + } + setCommonPreferencesChangedListener(null); + } + + /** Sets the listener for CommonPreferences change. */ + public static void setCommonPreferencesChangedListener( + CommonPreferencesChangedListener listener) { + sPreferencesChangedListener = listener; + } + + /** + * Loads the preferences from database. + * + *

This preferences is used across processes, so the preferences should be loaded again when + * the databases changes. + */ + @MainThread + public static void loadPreferences(Context context) { + if (sLoadPreferencesTask != null + && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) { + sLoadPreferencesTask.cancel(true); + } + sLoadPreferencesTask = new LoadPreferencesTask(context); + sLoadPreferencesTask.execute(); + } + + private static boolean useContentProvider(Context context) { + // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access. + return CommonUtils.isPackagedWithLiveChannels(context); + } + + public static synchronized boolean shouldShowSetupActivity(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP); + } else { + return getSharedPreferences(context).getBoolean(PREFS_KEY_LAUNCH_SETUP, false); + } + } + + public static synchronized void setShouldShowSetupActivity(Context context, boolean need) { + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); + } else { + getSharedPreferences(context).edit().putBoolean(PREFS_KEY_LAUNCH_SETUP, need).apply(); + } + } + + public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); + } else { + return getSharedPreferences(context) + .getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); + } + } + + public static synchronized void setTrickplaySetting( + Context context, @TrickplaySetting int trickplaySetting) { + SoftPreconditions.checkState(sInitialized); + SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); + } else { + getSharedPreferences(context) + .edit() + .putInt(PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) + .apply(); + } + } + + public static synchronized boolean getStoreTsStream(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); + } else { + return getSharedPreferences(context).getBoolean(PREFS_KEY_STORE_TS_STREAM, false); + } + } + + public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); + } else { + getSharedPreferences(context) + .edit() + .putBoolean(PREFS_KEY_STORE_TS_STREAM, shouldStore) + .apply(); + } + } + + public static synchronized String getLastPostalCode(Context context) { + SoftPreconditions.checkState(sInitialized); + if (useContentProvider(context)) { + return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE); + } else { + return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null); + } + } + + public static synchronized void setLastPostalCode(Context context, String postalCode) { + if (useContentProvider(context)) { + setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); + } else { + getSharedPreferences(context) + .edit() + .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode) + .apply(); + } + } + + protected static SharedPreferences getSharedPreferences(Context context) { + return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); + } + + private static synchronized void setPreference(Context context, String key, String value) { + sPreferenceValues.putString(key, value); + savePreference(context, key, value); + } + + private static synchronized void setPreference(Context context, String key, int value) { + sPreferenceValues.putInt(key, value); + savePreference(context, key, Integer.toString(value)); + } + + private static synchronized void setPreference(Context context, String key, long value) { + sPreferenceValues.putLong(key, value); + savePreference(context, key, Long.toString(value)); + } + + private static synchronized void setPreference(Context context, String key, boolean value) { + sPreferenceValues.putBoolean(key, value); + savePreference(context, key, Boolean.toString(value)); + } + + private static void savePreference( + final Context context, final String key, final String value) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + ContentResolver resolver = context.getContentResolver(); + ContentValues values = new ContentValues(); + values.put(Preferences.COLUMN_KEY, key); + values.put(Preferences.COLUMN_VALUE, value); + try { + resolver.insert(Preferences.CONTENT_URI, values); + } catch (Exception e) { + SoftPreconditions.warn( + TAG, "setPreference", e, "Error writing preference values"); + } + return null; + } + }.execute(); + } + + private static class LoadPreferencesTask extends AsyncTask { + private final Context mContext; + + private LoadPreferencesTask(Context context) { + mContext = context; + } + + @Override + protected Bundle doInBackground(Void... params) { + Bundle bundle = new Bundle(); + ContentResolver resolver = mContext.getContentResolver(); + String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE}; + try (Cursor cursor = + resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) { + if (cursor != null) { + while (!isCancelled() && cursor.moveToNext()) { + String key = cursor.getString(0); + String value = cursor.getString(1); + Class prefClass = sPref2TypeMapping.get(key); + if (prefClass == int.class) { + try { + bundle.putInt(key, Integer.parseInt(value)); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid format, key=" + key + ", value=" + value); + } + } else if (prefClass == long.class) { + try { + bundle.putLong(key, Long.parseLong(value)); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid format, key=" + key + ", value=" + value); + } + } else if (prefClass == boolean.class) { + bundle.putBoolean(key, Boolean.parseBoolean(value)); + } else { + bundle.putString(key, value); + } + } + } + } catch (Exception e) { + SoftPreconditions.warn(TAG, "getPreference", e, "Error querying preference values"); + return null; + } + return bundle; + } + + @Override + protected void onPostExecute(Bundle bundle) { + synchronized (CommonPreferences.class) { + if (bundle != null) { + sPreferenceValues.putAll(bundle); + } + } + if (sPreferencesChangedListener != null) { + sPreferencesChangedListener.onCommonPreferencesChanged(); + } + } + } +} diff --git a/common/src/com/android/tv/common/SharedPreferencesUtils.java b/common/src/com/android/tv/common/SharedPreferencesUtils.java deleted file mode 100644 index 18f5d7bb..00000000 --- a/common/src/com/android/tv/common/SharedPreferencesUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.common; - -import android.content.Context; -import android.os.AsyncTask; -import android.preference.PreferenceManager; - -/** Static utilities for {@link android.content.SharedPreferences} */ -public final class SharedPreferencesUtils { - // Note that changing the preference name will reset the preference values. - public static final String SHARED_PREF_FEATURES = "sharePreferencesFeatures"; - public static final String SHARED_PREF_BROWSABLE = "browsable_shared_preference"; - public static final String SHARED_PREF_WATCHED_HISTORY = "watched_history_shared_preference"; - public static final String SHARED_PREF_DVR_WATCHED_POSITION = - "dvr_watched_position_shared_preference"; - public static final String SHARED_PREF_AUDIO_CAPABILITIES = "com.android.tv.audio_capabilities"; - public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner"; - public static final String SHARED_PREF_EPG = "epg_preferences"; - public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings"; - /** No need to pre-initialize. It's used only on the worker thread. */ - public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris"; - /** Stores the UI related settings */ - public static final String SHARED_PREF_UI_SETTINGS = "ui_settings"; - - public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms"; - - private static boolean sInitializeCalled; - - /** - * {@link android.content.SharedPreferences} loads the preference file when {@link - * Context#getSharedPreferences(String, int)} is called for the first time. Call {@link - * Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR due to the - * file loading. - */ - public static synchronized void initialize(final Context context, final Runnable postTask) { - if (!sInitializeCalled) { - sInitializeCalled = true; - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - PreferenceManager.getDefaultSharedPreferences(context); - context.getSharedPreferences(SHARED_PREF_FEATURES, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); - context.getSharedPreferences( - SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE); - context.getSharedPreferences( - SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE); - context.getSharedPreferences( - SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE); - context.getSharedPreferences( - SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); - context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); - return null; - } - - @Override - protected void onPostExecute(Void result) { - postTask.run(); - } - }.execute(); - } - } - - private SharedPreferencesUtils() {} -} diff --git a/common/src/com/android/tv/common/SoftPreconditions.java b/common/src/com/android/tv/common/SoftPreconditions.java index 9fe0137b..3b0510d8 100644 --- a/common/src/com/android/tv/common/SoftPreconditions.java +++ b/common/src/com/android/tv/common/SoftPreconditions.java @@ -17,9 +17,11 @@ package com.android.tv.common; import android.content.Context; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; import com.android.tv.common.feature.Feature; +import com.android.tv.common.util.CommonUtils; /** * Simple static methods to be called at the start of your own methods to verify correct arguments @@ -39,13 +41,24 @@ public final class SoftPreconditions { * @param expression a boolean expression * @param tag Used to identify the source of a log message. It usually identifies the class or * activity where the log call occurs. - * @param msg The message you would like logged. + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message + * in square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. * @return the evaluation result of the boolean expression * @throws IllegalArgumentException if {@code expression} is true */ - public static boolean checkArgument(final boolean expression, String tag, String msg) { + public static boolean checkArgument( + final boolean expression, + String tag, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { if (!expression) { - warn(tag, "Illegal argument", msg, new IllegalArgumentException(msg)); + String msg = format(errorMessageTemplate, errorMessageArgs); + warn(tag, "Illegal argument", new IllegalArgumentException(msg), msg); } return expression; } @@ -68,13 +81,24 @@ public final class SoftPreconditions { * @param reference an object reference * @param tag Used to identify the source of a log message. It usually identifies the class or * activity where the log call occurs. - * @param msg The message you would like logged. + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message + * in square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. * @return true if the object is null * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(final T reference, String tag, String msg) { + public static T checkNotNull( + final T reference, + String tag, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { if (reference == null) { - warn(tag, "Null Pointer", msg, new NullPointerException(msg)); + String msg = format(errorMessageTemplate, errorMessageArgs); + warn(tag, "Null Pointer", new NullPointerException(msg), msg); } return reference; } @@ -97,13 +121,24 @@ public final class SoftPreconditions { * @param expression a boolean expression * @param tag Used to identify the source of a log message. It usually identifies the class or * activity where the log call occurs. - * @param msg The message you would like logged. + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message + * in square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. * @return the evaluation result of the boolean expression * @throws IllegalStateException if {@code expression} is true */ - public static boolean checkState(final boolean expression, String tag, String msg) { + public static boolean checkState( + final boolean expression, + String tag, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { if (!expression) { - warn(tag, "Illegal State", msg, new IllegalStateException(msg)); + String msg = format(errorMessageTemplate, errorMessageArgs); + warn(tag, "Illegal State", new IllegalStateException(msg), msg); } return expression; } @@ -135,14 +170,15 @@ public final class SoftPreconditions { } /** - * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning. + * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true and not running in a + * test, else log a warning. * * @param tag Used to identify the source of a log message. It usually identifies the class or * activity where the log call occurs. - * @param msg The message you would like logged * @param e The exception to wrap with a RuntimeException when thrown. + * @param msg The message to be logged */ - public static void warn(String tag, String prefix, String msg, Exception e) + public static void warn(String tag, String prefix, Exception e, String msg) throws RuntimeException { if (TextUtils.isEmpty(tag)) { tag = TAG; @@ -156,12 +192,57 @@ public final class SoftPreconditions { logMessage = prefix + ": " + msg; } - if (BuildConfig.ENG) { + if (BuildConfig.ENG && !CommonUtils.isRunningInTest()) { throw new RuntimeException(msg, e); } else { Log.w(tag, logMessage, e); } } + /** + * Substitutes each {@code %s} in {@code template} with an argument. These are matched by + * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than + * placeholders, the unmatched arguments will be appended to the end of the formatted message in + * square braces. + * + * @param template a string containing 0 or more {@code %s} placeholders. null is treated as + * "null". + * @param args the arguments to be substituted into the message template. Arguments are + * converted to strings using {@link String#valueOf(Object)}. Arguments can be null. + */ + static String format(@Nullable String template, @Nullable Object... args) { + template = String.valueOf(template); // null -> "null" + + args = args == null ? new Object[] {"(Object[])null"} : args; + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template, templateStart, template.length()); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } + private SoftPreconditions() {} } diff --git a/common/src/com/android/tv/common/TvCommonConstants.java b/common/src/com/android/tv/common/TvCommonConstants.java deleted file mode 100644 index 0c64a346..00000000 --- a/common/src/com/android/tv/common/TvCommonConstants.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.common; - -import android.media.tv.TvInputInfo; - -/** Constants for common use in TV app and tests. */ -public final class TvCommonConstants { - /** A constant for the key of the extra data for the app linking intent. */ - public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri"; - - /** - * An intent action to launch setup activity of a TV input. The intent should include TV input - * ID in the value of {@link EXTRA_INPUT_ID}. Optionally, given the value of {@link - * EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched after the setup activity - * successfully finishes. - */ - public static final String INTENT_ACTION_INPUT_SETUP = - "com.android.tv.action.LAUNCH_INPUT_SETUP"; - - /** - * A constant of the key to indicate a TV input ID for the intent action {@link - * INTENT_ACTION_INPUT_SETUP}. - * - *

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}. - * - *

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}. - * - *

Value type: Intent (Parcelable) - */ - public static final String EXTRA_ACTIVITY_AFTER_COMPLETION = - "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION"; - - private TvCommonConstants() {} -} diff --git a/common/src/com/android/tv/common/TvCommonUtils.java b/common/src/com/android/tv/common/TvCommonUtils.java deleted file mode 100644 index 2172e1c4..00000000 --- a/common/src/com/android/tv/common/TvCommonUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.common; - -import android.content.Intent; -import android.media.tv.TvInputInfo; - -/** Util class for common use in TV app and inputs. */ -public final class TvCommonUtils { - private static Boolean sRunningInTest; - - private TvCommonUtils() {} - - /** - * Returns an intent to start the setup activity for the TV input using {@link - * TvCommonConstants#INTENT_ACTION_INPUT_SETUP}. - */ - public static Intent createSetupIntent(Intent originalSetupIntent, String inputId) { - if (originalSetupIntent == null) { - return null; - } - Intent setupIntent = new Intent(originalSetupIntent); - if (!TvCommonConstants.INTENT_ACTION_INPUT_SETUP.equals(originalSetupIntent.getAction())) { - Intent intentContainer = new Intent(TvCommonConstants.INTENT_ACTION_INPUT_SETUP); - intentContainer.putExtra(TvCommonConstants.EXTRA_SETUP_INTENT, originalSetupIntent); - intentContainer.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId); - setupIntent = intentContainer; - } - return setupIntent; - } - - /** - * Returns an intent to start the setup activity for this TV input using {@link - * TvCommonConstants#INTENT_ACTION_INPUT_SETUP}. - */ - public static Intent createSetupIntent(TvInputInfo input) { - return createSetupIntent(input.createSetupIntent(), input.getId()); - } - - /** - * Checks if this application is running in tests. - * - *

{@link android.app.ActivityManager#isRunningInTestHarness} doesn't return {@code true} for - * the usual devices even the application is running in tests. We need to figure it out by - * checking whether the class in tv-tests-common module can be loaded or not. - */ - public static synchronized boolean isRunningInTest() { - if (sRunningInTest == null) { - try { - Class.forName("com.android.tv.testing.Utils"); - sRunningInTest = true; - } catch (ClassNotFoundException e) { - sRunningInTest = false; - } - } - return sRunningInTest; - } -} diff --git a/common/src/com/android/tv/common/annotation/UsedByNative.java b/common/src/com/android/tv/common/annotation/UsedByNative.java new file mode 100644 index 00000000..523c9798 --- /dev/null +++ b/common/src/com/android/tv/common/annotation/UsedByNative.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation used for marking methods and fields that are called from native + * code. Useful for keeping components that would otherwise be removed by + * Proguard. Use the value parameter to mention a file that calls this method. + * + * Note that adding this annotation to a method is not enough to guarantee that + * it is kept - either its class must be referenced elsewhere in the program, or + * the class must be annotated with this as well. + * + * Usage example:
+ *

{@code
+ *  @UsedByNative("NativeCrashHandler.cpp")
+ * public static void reportCrash(int signal, int code, int address) {
+ * ...
+ * }
+ * 
+ * + */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.CONSTRUCTOR}) +public @interface UsedByNative { + String value(); +} diff --git a/common/src/com/android/tv/common/concurrent/NamedThreadFactory.java b/common/src/com/android/tv/common/concurrent/NamedThreadFactory.java new file mode 100644 index 00000000..4c8371f3 --- /dev/null +++ b/common/src/com/android/tv/common/concurrent/NamedThreadFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.concurrent; + +import android.support.annotation.NonNull; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** A thread factory that creates threads with named prefix-##. */ +public class NamedThreadFactory implements ThreadFactory { + private final AtomicInteger mCount = new AtomicInteger(0); + private final ThreadFactory mDefaultThreadFactory; + private final String mPrefix; + + public NamedThreadFactory(final String prefix) { + mDefaultThreadFactory = Executors.defaultThreadFactory(); + mPrefix = prefix + "-"; + } + + @Override + public Thread newThread(@NonNull final Runnable runnable) { + final Thread thread = mDefaultThreadFactory.newThread(runnable); + thread.setName(mPrefix + mCount.getAndIncrement()); + return thread; + } + + public boolean namedWithPrefix(Thread thread) { + return thread.getName().startsWith(mPrefix); + } +} diff --git a/common/src/com/android/tv/common/config/DefaultConfigManager.java b/common/src/com/android/tv/common/config/DefaultConfigManager.java new file mode 100644 index 00000000..f8e706d1 --- /dev/null +++ b/common/src/com/android/tv/common/config/DefaultConfigManager.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.config; + +import android.content.Context; +import com.android.tv.common.config.api.RemoteConfig; + +/** Stub Remote Config. */ +public class DefaultConfigManager { + public static final long DEFAULT_LONG_VALUE = 0; + + public static DefaultConfigManager createInstance(Context context) { + return new DefaultConfigManager(); + } + + private StubRemoteConfig mRemoteConfig = new StubRemoteConfig(); + + public RemoteConfig getRemoteConfig() { + return mRemoteConfig; + } + + private static class StubRemoteConfig implements RemoteConfig { + @Override + public void fetch(OnRemoteConfigUpdatedListener listener) {} + + @Override + public String getString(String key) { + return null; + } + + @Override + public boolean getBoolean(String key) { + return false; + } + + @Override + public long getLong(String key) { + return DEFAULT_LONG_VALUE; + } + } +} diff --git a/common/src/com/android/tv/common/config/RemoteConfigFeature.java b/common/src/com/android/tv/common/config/RemoteConfigFeature.java new file mode 100644 index 00000000..66acb0cf --- /dev/null +++ b/common/src/com/android/tv/common/config/RemoteConfigFeature.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.config; + +import android.content.Context; +import com.android.tv.common.BaseApplication; +import com.android.tv.common.feature.Feature; + +/** + * A {@link Feature} controlled by a {@link com.android.tv.common.config.api.RemoteConfig} + * boolean. + */ +public class RemoteConfigFeature implements Feature { + private final String mKey; + + /** Creates a {@link RemoteConfigFeature for the {@code key}. */ + public static RemoteConfigFeature fromKey(String key) { + return new RemoteConfigFeature(key); + } + + private RemoteConfigFeature(String key) { + mKey = key; + } + + @Override + public boolean isEnabled(Context context) { + return BaseApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey); + } +} diff --git a/common/src/com/android/tv/common/config/RemoteConfigUtils.java b/common/src/com/android/tv/common/config/RemoteConfigUtils.java new file mode 100644 index 00000000..9d0ddf47 --- /dev/null +++ b/common/src/com/android/tv/common/config/RemoteConfigUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.config; + +import android.content.Context; +import android.util.Log; +import com.android.tv.common.BaseApplication; +import com.android.tv.common.config.api.RemoteConfig; + +/** A utility class to get the remote config. */ +public final class RemoteConfigUtils { + private static final String TAG = "RemoteConfigUtils"; + private static final boolean DEBUG = false; + + private RemoteConfigUtils() {} + + public static long getRemoteConfig(Context context, String key, long defaultValue) { + RemoteConfig remoteConfig = BaseApplication.getSingletons(context).getRemoteConfig(); + try { + long remoteValue = remoteConfig.getLong(key); + if (DEBUG) Log.d(TAG, "Got " + key + " from remote: " + remoteValue); + return remoteValue; + } catch (Exception e) { + Log.w(TAG, "Cannot get " + key + " from RemoteConfig", e); + } + if (DEBUG) Log.d(TAG, "Use default value " + defaultValue); + return defaultValue; + } +} diff --git a/common/src/com/android/tv/common/config/api/RemoteConfig.java b/common/src/com/android/tv/common/config/api/RemoteConfig.java new file mode 100644 index 00000000..d2a189b0 --- /dev/null +++ b/common/src/com/android/tv/common/config/api/RemoteConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.config.api; + +/** + * Manages Live TV Configuration, allowing remote updates. + * + *

This is a thin wrapper around Firebase Remote Config + */ +public interface RemoteConfig { + + /** Notified on successful completion of a {@link #fetch)} */ + interface OnRemoteConfigUpdatedListener { + void onRemoteConfigUpdated(); + } + + /** Starts a fetch and notifies {@code listener} on successful completion. */ + void fetch(OnRemoteConfigUpdatedListener listener); + + /** Gets value as a string corresponding to the specified key. */ + String getString(String key); + + /** Gets value as a boolean corresponding to the specified key. */ + boolean getBoolean(String key); + + /** Gets value as a long corresponding to the specified key. */ + long getLong(String key); +} diff --git a/common/src/com/android/tv/common/customization/CustomAction.java b/common/src/com/android/tv/common/customization/CustomAction.java new file mode 100644 index 00000000..ee18cb3c --- /dev/null +++ b/common/src/com/android/tv/common/customization/CustomAction.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.customization; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; + +/** Describes a custom option defined in customization package. This will be added to main menu. */ +public class CustomAction implements Comparable { + private static final int POSITION_THRESHOLD = 100; + + private final int mPositionPriority; + private final String mTitle; + private final Drawable mIconDrawable; + private final Intent mIntent; + + public CustomAction(int positionPriority, String title, Drawable iconDrawable, Intent intent) { + mPositionPriority = positionPriority; + mTitle = title; + mIconDrawable = iconDrawable; + mIntent = intent; + } + + /** + * Returns if this option comes before the existing items. Note that custom options can only be + * placed at the front or back. (i.e. cannot be added in the middle of existing options.) + * + * @return {@code true} if it goes to the beginning. {@code false} if it goes to the end. + */ + public boolean isFront() { + return mPositionPriority < POSITION_THRESHOLD; + } + + @Override + public int compareTo(@NonNull CustomAction another) { + return mPositionPriority - another.mPositionPriority; + } + + /** Returns title. */ + public String getTitle() { + return mTitle; + } + + /** Returns icon drawable. */ + public Drawable getIconDrawable() { + return mIconDrawable; + } + + /** Returns intent to launch when this option is clicked. */ + public Intent getIntent() { + return mIntent; + } +} diff --git a/common/src/com/android/tv/common/customization/CustomizationManager.java b/common/src/com/android/tv/common/customization/CustomizationManager.java new file mode 100644 index 00000000..5076f5d0 --- /dev/null +++ b/common/src/com/android/tv/common/customization/CustomizationManager.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.customization; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.annotation.IntDef; +import android.text.TextUtils; +import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CustomizationManager { + private static final String TAG = "CustomizationManager"; + private static final boolean DEBUG = false; + + private static final String[] CUSTOMIZE_PERMISSIONS = { + "com.android.tv.permission.CUSTOMIZE_TV_APP" + }; + + private static final String CATEGORY_TV_CUSTOMIZATION = "com.android.tv.category"; + + /** Row IDs to share customized actions. Only rows listed below can have customized action. */ + public static final String ID_OPTIONS_ROW = "options_row"; + + public static final String ID_PARTNER_ROW = "partner_row"; + + @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface TRICKPLAY_MODE {} + + public static final int TRICKPLAY_MODE_ENABLED = 0; + public static final int TRICKPLAY_MODE_DISABLED = 1; + public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2; + + private static final String[] TRICKPLAY_MODE_STRINGS = { + "enabled", "disabled", "use_external_storage_only" + }; + + private static final HashMap INTENT_CATEGORY_TO_ROW_ID; + + static { + INTENT_CATEGORY_TO_ROW_ID = new HashMap<>(); + INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW); + INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".PARTNER_ROW", ID_PARTNER_ROW); + } + + private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title"; + private static final String RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER = + "has_linux_dvb_built_in_tuner"; + private static final String RES_ID_TRICKPLAY_MODE = "trickplay_mode"; + + private static final String RES_TYPE_STRING = "string"; + private static final String RES_TYPE_BOOLEAN = "bool"; + + private static String sCustomizationPackage; + private static Boolean sHasLinuxDvbBuiltInTuner; + private static @TRICKPLAY_MODE Integer sTrickplayMode; + + private final Context mContext; + private boolean mInitialized; + + private String mPartnerRowTitle; + private final Map> mRowIdToCustomActionsMap = new HashMap<>(); + + public CustomizationManager(Context context) { + mContext = context; + mInitialized = false; + } + + /** + * Returns {@code true} if there's a customization package installed and it specifies built-in + * tuner devices are available. The built-in tuner should support DVB API to be recognized by + * Live TV. + */ + public static boolean hasLinuxDvbBuiltInTuner(Context context) { + if (sHasLinuxDvbBuiltInTuner == null) { + if (TextUtils.isEmpty(getCustomizationPackageName(context))) { + sHasLinuxDvbBuiltInTuner = false; + } else { + try { + Resources res = + context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = + res.getIdentifier( + RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, + RES_TYPE_BOOLEAN, + sCustomizationPackage); + sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId); + } catch (NameNotFoundException e) { + sHasLinuxDvbBuiltInTuner = false; + } + } + } + return sHasLinuxDvbBuiltInTuner; + } + + public static @TRICKPLAY_MODE int getTrickplayMode(Context context) { + if (sTrickplayMode == null) { + if (TextUtils.isEmpty(getCustomizationPackageName(context))) { + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + } else { + try { + String customization = null; + Resources res = + context.getPackageManager() + .getResourcesForApplication(sCustomizationPackage); + int resId = + res.getIdentifier( + RES_ID_TRICKPLAY_MODE, RES_TYPE_STRING, sCustomizationPackage); + customization = resId == 0 ? null : res.getString(resId); + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + if (customization != null) { + for (int i = 0; i < TRICKPLAY_MODE_STRINGS.length; ++i) { + if (TRICKPLAY_MODE_STRINGS[i].equalsIgnoreCase(customization)) { + sTrickplayMode = i; + break; + } + } + } + } catch (NameNotFoundException e) { + sTrickplayMode = TRICKPLAY_MODE_ENABLED; + } + } + } + return sTrickplayMode; + } + + private static String getCustomizationPackageName(Context context) { + if (sCustomizationPackage == null) { + List packageInfos = + context.getPackageManager() + .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); + sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName; + } + return sCustomizationPackage; + } + + /** Initialize TV customization options. Run this API only on the main thread. */ + public void initialize() { + if (mInitialized) { + return; + } + mInitialized = true; + if (!TextUtils.isEmpty(getCustomizationPackageName(mContext))) { + buildCustomActions(); + buildPartnerRow(); + } + } + + private void buildCustomActions() { + mRowIdToCustomActionsMap.clear(); + PackageManager pm = mContext.getPackageManager(); + for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) { + Intent customOptionIntent = new Intent(Intent.ACTION_MAIN); + customOptionIntent.addCategory(intentCategory); + + List activities = + pm.queryIntentActivities( + customOptionIntent, + PackageManager.GET_RECEIVERS + | PackageManager.GET_RESOLVED_FILTER + | PackageManager.GET_META_DATA); + for (ResolveInfo info : activities) { + String packageName = info.activityInfo.packageName; + if (!TextUtils.equals(packageName, sCustomizationPackage)) { + Log.w( + TAG, + "A customization package " + + sCustomizationPackage + + " already exist. Ignoring " + + packageName); + continue; + } + + int position = info.filter.getPriority(); + String title = info.loadLabel(pm).toString(); + Drawable drawable = info.loadIcon(pm); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(intentCategory); + intent.setClassName(sCustomizationPackage, info.activityInfo.name); + + String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory); + List actions = mRowIdToCustomActionsMap.get(rowId); + if (actions == null) { + actions = new ArrayList<>(); + mRowIdToCustomActionsMap.put(rowId, actions); + } + actions.add(new CustomAction(position, title, drawable, intent)); + } + } + // Sort items by position + for (List actions : mRowIdToCustomActionsMap.values()) { + Collections.sort(actions); + } + + if (DEBUG) { + Log.d(TAG, "Dumping custom actions"); + for (String id : mRowIdToCustomActionsMap.keySet()) { + for (CustomAction action : mRowIdToCustomActionsMap.get(id)) { + Log.d( + TAG, + "Custom row rowId=" + + id + + " title=" + + action.getTitle() + + " class=" + + action.getIntent()); + } + } + Log.d(TAG, "Dumping custom actions - end of dump"); + } + } + + /** + * Returns custom actions for given row id. + * + *

Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW. + */ + public List getCustomActions(String rowId) { + return mRowIdToCustomActionsMap.get(rowId); + } + + private void buildPartnerRow() { + mPartnerRowTitle = null; + Resources res; + try { + res = mContext.getPackageManager().getResourcesForApplication(sCustomizationPackage); + } catch (NameNotFoundException e) { + Log.w(TAG, "Could not get resources for package " + sCustomizationPackage); + return; + } + int resId = + res.getIdentifier(RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); + if (resId != 0) { + mPartnerRowTitle = res.getString(resId); + } + if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]"); + } + + /** Returns partner row title. */ + public String getPartnerRowTitle() { + return mPartnerRowTitle; + } +} diff --git a/common/src/com/android/tv/common/experiments/ExperimentFlag.java b/common/src/com/android/tv/common/experiments/ExperimentFlag.java new file mode 100644 index 00000000..42f7afca --- /dev/null +++ b/common/src/com/android/tv/common/experiments/ExperimentFlag.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.experiments; + +import android.support.annotation.VisibleForTesting; + + +/** Experiments return values based on user, device and other criteria. */ +public final class ExperimentFlag { + + private static boolean sAllowOverrides = false; + + @VisibleForTesting + public static void initForTest() { + sAllowOverrides = true; + } + + /** Returns a boolean experiment */ + public static ExperimentFlag createFlag( + boolean defaultValue) { + return new ExperimentFlag<>( + defaultValue); + } + + private final T mDefaultValue; + + private T mOverrideValue = null; + private boolean mOverridden = false; + + private ExperimentFlag( + T defaultValue) { + mDefaultValue = defaultValue; + } + + /** Returns value for this experiment */ + public T get() { + return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue; + } + + @VisibleForTesting + public void override(T t) { + if (sAllowOverrides) { + mOverridden = true; + mOverrideValue = t; + } + } + + @VisibleForTesting + public void resetOverride() { + mOverridden = false; + } +} diff --git a/common/src/com/android/tv/common/experiments/ExperimentLoader.java b/common/src/com/android/tv/common/experiments/ExperimentLoader.java new file mode 100644 index 00000000..5f012e11 --- /dev/null +++ b/common/src/com/android/tv/common/experiments/ExperimentLoader.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.experiments; + +import android.content.Context; + +/** Used to sync {@link ExperimentFlag}s. */ +public class ExperimentLoader { + + /** Starts a background task to update {@link ExperimentFlag}s */ + public void asyncRefreshExperiments(Context context) { + // Override for your experiment system + } +} diff --git a/common/src/com/android/tv/common/experiments/Experiments.java b/common/src/com/android/tv/common/experiments/Experiments.java new file mode 100644 index 00000000..96b15e53 --- /dev/null +++ b/common/src/com/android/tv/common/experiments/Experiments.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.experiments; + +import static com.android.tv.common.experiments.ExperimentFlag.createFlag; + +import com.android.tv.common.BuildConfig; + +/** + * Set of experiments visible in AOSP. + * + *

This file is maintained by hand. + */ +public final class Experiments { + public static final ExperimentFlag CLOUD_EPG = + ExperimentFlag.createFlag( + true); + + public static final ExperimentFlag ENABLE_UNRATED_CONTENT_SETTINGS = + ExperimentFlag.createFlag( + false); + + /** Turn analytics on or off based on the System Checkbox for logging. */ + public static final ExperimentFlag ENABLE_ANALYTICS_VIA_CHECKBOX = + createFlag( + false); + + /** + * Allow developer features such as the dev menu and other aids. + * + *

These features are available to select users(aka fishfooders) on production builds. + */ + public static final ExperimentFlag ENABLE_DEVELOPER_FEATURES = + ExperimentFlag.createFlag( + BuildConfig.ENG); + + /** + * Allow QA features. + * + *

These features must be carefully limited, keeping QA differences to a minimum. + * + *

These features are available to select users(aka QA) on production builds. + */ + public static final ExperimentFlag ENABLE_QA_FEATURES = + ExperimentFlag.createFlag( + false); + + private Experiments() {} +} diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java index 792e59c6..6016ecbe 100644 --- a/common/src/com/android/tv/common/feature/CommonFeatures.java +++ b/common/src/com/android/tv/common/feature/CommonFeatures.java @@ -19,12 +19,24 @@ package com.android.tv.common.feature; import static com.android.tv.common.feature.FeatureUtils.AND; import static com.android.tv.common.feature.TestableFeature.createTestableFeature; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.common.BaseApplication; +import com.android.tv.common.experiments.Experiments; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.LocationUtils; + /** * List of {@link Feature} that affect more than just the Live TV app. * *

Remove the {@code Feature} once it is launched. */ public class CommonFeatures { + private static final String TAG = "CommonFeatures"; + private static final boolean DEBUG = false; + /** * DVR * @@ -41,13 +53,46 @@ public class CommonFeatures { *

Enables dvr recording regardless of storage status. */ public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE = - new PropertyFeature("force_recording_until_no_space", false); + PropertyFeature.create("force_recording_until_no_space", false); - /** - * USE_SW_CODEC_FOR_SD - * - *

Prefer software based codec for SD channels. - */ - public static final Feature USE_SW_CODEC_FOR_SD = - new PropertyFeature("use_sw_codec_for_sd", false); + public static final Feature TUNER = + new Feature() { + @Override + public boolean isEnabled(Context context) { + + if (CommonUtils.isDeveloper()) { + // we enable tuner for developers to test tuner in any platform. + return true; + } + + // This is special handling just for USB Tuner. + // It does not require any N API's but relies on a improvements in N for AC3 + // support + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + }; + + /** Show postal code fragment before channel scan. */ + public static final Feature ENABLE_CLOUD_EPG_REGION = + new Feature() { + private final String[] SUPPORTED_REGIONS = { + }; + + + @Override + public boolean isEnabled(Context context) { + if (!Experiments.CLOUD_EPG.get()) { + if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false"); + return false; + } + String country = LocationUtils.getCurrentCountry(context); + for (int i = 0; i < SUPPORTED_REGIONS.length; i++) { + if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) { + return true; + } + } + if (DEBUG) Log.d(TAG, "EPG flag false after country check"); + return false; + } + }; } diff --git a/common/src/com/android/tv/common/feature/ExperimentFeature.java b/common/src/com/android/tv/common/feature/ExperimentFeature.java new file mode 100644 index 00000000..820eda49 --- /dev/null +++ b/common/src/com/android/tv/common/feature/ExperimentFeature.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.feature; + +import android.content.Context; +import com.android.tv.common.experiments.ExperimentFlag; + +/** A {@link Feature} base on an {@link ExperimentFlag}. */ +public final class ExperimentFeature implements Feature { + + public static Feature from(ExperimentFlag flag) { + return new ExperimentFeature(flag); + } + + private final ExperimentFlag mFlag; + + private ExperimentFeature(ExperimentFlag flag) { + mFlag = flag; + } + + @Override + public boolean isEnabled(Context context) { + return mFlag.get(); + } + + @Override + public String toString() { + return "ExperimentFeature for " + mFlag; + } +} diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java index bbaa2107..8650d151 100644 --- a/common/src/com/android/tv/common/feature/FeatureUtils.java +++ b/common/src/com/android/tv/common/feature/FeatureUtils.java @@ -17,6 +17,7 @@ package com.android.tv.common.feature; import android.content.Context; +import com.android.tv.common.util.CommonUtils; import java.util.Arrays; /** Static utilities for features. */ @@ -98,5 +99,19 @@ public class FeatureUtils { } }; + /** True if running in robolectric. */ + public static final Feature ROBOLECTRIC = + new Feature() { + @Override + public boolean isEnabled(Context context) { + return CommonUtils.isRoboTest(); + } + + @Override + public String toString() { + return "isRobolecteric"; + } + }; + private FeatureUtils() {} } diff --git a/common/src/com/android/tv/common/feature/PropertyFeature.java b/common/src/com/android/tv/common/feature/PropertyFeature.java index 33227bdf..0cf36317 100644 --- a/common/src/com/android/tv/common/feature/PropertyFeature.java +++ b/common/src/com/android/tv/common/feature/PropertyFeature.java @@ -25,6 +25,11 @@ import com.android.tv.common.BooleanSystemProperty; *

See {@link BooleanSystemProperty} for instructions on how to set using adb. */ public final class PropertyFeature implements Feature { + + public static PropertyFeature create(String key, boolean defaultValue) { + return new PropertyFeature(key, defaultValue); + } + private final BooleanSystemProperty mProperty; /** @@ -33,7 +38,7 @@ public final class PropertyFeature implements Feature { * @param key the system property key. Length must be <= 31 characters. * @param defaultValue the value to return if the property is undefined or empty. */ - public PropertyFeature(String key, boolean defaultValue) { + private PropertyFeature(String key, boolean defaultValue) { if (key.length() > 31) { // Since Features are initialized at startup and the keys are static go ahead and kill // the application. diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java index b6ab04a9..5588939f 100644 --- a/common/src/com/android/tv/common/feature/Sdk.java +++ b/common/src/com/android/tv/common/feature/Sdk.java @@ -29,5 +29,13 @@ public class Sdk { } }; + public static final Feature AT_LEAST_O = + new Feature() { + @Override + public boolean isEnabled(Context context) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + }; + private Sdk() {} } diff --git a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java index d4ec81ae..ef2260d6 100644 --- a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java +++ b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java @@ -19,7 +19,7 @@ package com.android.tv.common.feature; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.util.SharedPreferencesUtils; /** Feature controlled by shared preferences. */ public final class SharedPreferencesFeature implements Feature { diff --git a/common/src/com/android/tv/common/feature/TestableFeature.java b/common/src/com/android/tv/common/feature/TestableFeature.java index c37da766..1f18639d 100644 --- a/common/src/com/android/tv/common/feature/TestableFeature.java +++ b/common/src/com/android/tv/common/feature/TestableFeature.java @@ -19,7 +19,7 @@ package com.android.tv.common.feature; import android.content.Context; import android.support.annotation.VisibleForTesting; import android.util.Log; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.common.util.CommonUtils; /** * When run in a test harness this feature can be turned on or off, overriding the normal value. @@ -56,7 +56,7 @@ public class TestableFeature implements Feature { @VisibleForTesting public void enableForTest() { - if (!TvCommonUtils.isRunningInTest()) { + if (!CommonUtils.isRunningInTest()) { Log.e(TAG, "Not enabling for test:" + this, new IllegalStateException(DETAIL_MESSAGE)); } else { mTestValue = true; @@ -65,7 +65,7 @@ public class TestableFeature implements Feature { @VisibleForTesting public void disableForTests() { - if (!TvCommonUtils.isRunningInTest()) { + if (!CommonUtils.isRunningInTest()) { Log.e( TAG, "Not disabling for test: " + this, @@ -77,7 +77,7 @@ public class TestableFeature implements Feature { @VisibleForTesting public void resetForTests() { - if (!TvCommonUtils.isRunningInTest()) { + if (!CommonUtils.isRunningInTest()) { Log.e(TAG, "Not resetting feature: " + this, new IllegalStateException(DETAIL_MESSAGE)); } else { mTestValue = null; @@ -86,7 +86,7 @@ public class TestableFeature implements Feature { @Override public boolean isEnabled(Context context) { - if (TvCommonUtils.isRunningInTest() && mTestValue != null) { + if (CommonUtils.isRunningInTest() && mTestValue != null) { return mTestValue; } return mDelegate.isEnabled(context); @@ -95,7 +95,7 @@ public class TestableFeature implements Feature { @Override public String toString() { String msg = mDelegate.toString(); - if (TvCommonUtils.isRunningInTest()) { + if (CommonUtils.isRunningInTest()) { if (mTestValue == null) { msg = "Testable Feature is unchanged: " + msg; } else { diff --git a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java new file mode 100644 index 00000000..8b45a730 --- /dev/null +++ b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.recording; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Environment; +import android.os.Looper; +import android.os.StatFs; +import android.support.annotation.AnyThread; +import android.support.annotation.IntDef; +import android.support.annotation.WorkerThread; +import android.util.Log; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.feature.CommonFeatures; +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** Signals DVR storage status change such as plugging/unplugging. */ +public class RecordingStorageStatusManager { + private static final String TAG = "RecordingStorageStatusManager"; + private static final boolean DEBUG = false; + + /** Minimum storage size to support DVR */ + public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB + + private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES = + 10 * 1024 * 1024 * 1024L; // 10GB + private static final String RECORDING_DATA_SUB_PATH = "/recording"; + + /** Storage status constants. */ + @IntDef({ + STORAGE_STATUS_OK, + STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL, + STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, + STORAGE_STATUS_MISSING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StorageStatus {} + + /** Current storage is OK to record a program. */ + public static final int STORAGE_STATUS_OK = 0; + + /** Current storage's total capacity is smaller than DVR requirement. */ + public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1; + + /** Current storage's free space is insufficient to record programs. */ + public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2; + + /** Current storage is missing. */ + public static final int STORAGE_STATUS_MISSING = 3; + + private final Context mContext; + private final Set mOnStorageMountChangedListeners = + new CopyOnWriteArraySet<>(); + private MountedStorageStatus mMountedStorageStatus; + private boolean mStorageValid; + + private class MountedStorageStatus { + private final boolean mStorageMounted; + private final File mStorageMountedDir; + private final long mStorageMountedCapacity; + + private MountedStorageStatus(boolean mounted, File mountedDir, long capacity) { + mStorageMounted = mounted; + mStorageMountedDir = mountedDir; + mStorageMountedCapacity = capacity; + } + + private boolean isValidForDvr() { + return mStorageMounted && mStorageMountedCapacity >= MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof MountedStorageStatus)) { + return false; + } + MountedStorageStatus status = (MountedStorageStatus) other; + return mStorageMounted == status.mStorageMounted + && Objects.equals(mStorageMountedDir, status.mStorageMountedDir) + && mStorageMountedCapacity == status.mStorageMountedCapacity; + } + } + + public interface OnStorageMountChangedListener { + + /** + * Listener for DVR storage status change. + * + * @param storageMounted {@code true} when DVR possible storage is mounted, {@code false} + * otherwise. + */ + void onStorageMountChanged(boolean storageMounted); + } + + private final class StorageStatusBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + MountedStorageStatus result = getStorageStatusInternal(); + if (mMountedStorageStatus.equals(result)) { + return; + } + mMountedStorageStatus = result; + if (result.mStorageMounted) { + cleanUpDbIfNeeded(); + } + boolean valid = result.isValidForDvr(); + if (valid == mStorageValid) { + return; + } + mStorageValid = valid; + for (OnStorageMountChangedListener l : mOnStorageMountChangedListeners) { + l.onStorageMountChanged(valid); + } + } + } + + /** + * Creates RecordingStorageStatusManager. + * + * @param context {@link Context} + */ + public RecordingStorageStatusManager(final Context context) { + mContext = context; + mMountedStorageStatus = getStorageStatusInternal(); + mStorageValid = mMountedStorageStatus.isValidForDvr(); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MEDIA_MOUNTED); + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); + filter.addAction(Intent.ACTION_MEDIA_EJECT); + filter.addAction(Intent.ACTION_MEDIA_REMOVED); + filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); + filter.addDataScheme(ContentResolver.SCHEME_FILE); + mContext.registerReceiver(new StorageStatusBroadcastReceiver(), filter); + } + + /** + * Adds the listener for receiving storage status change. + * + * @param listener + */ + public void addListener(OnStorageMountChangedListener listener) { + mOnStorageMountChangedListeners.add(listener); + } + + /** Removes the current listener. */ + public void removeListener(OnStorageMountChangedListener listener) { + mOnStorageMountChangedListeners.remove(listener); + } + + /** Returns true if a storage is mounted. */ + public boolean isStorageMounted() { + return mMountedStorageStatus.mStorageMounted; + } + + /** Returns the path to DVR recording data directory. This can take for a while sometimes. */ + @WorkerThread + public File getRecordingRootDataDirectory() { + SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper()); + if (mMountedStorageStatus.mStorageMountedDir == null) { + return null; + } + File root = mContext.getExternalFilesDir(null); + String rootPath; + try { + rootPath = root != null ? root.getCanonicalPath() : null; + } catch (IOException | SecurityException e) { + return null; + } + return rootPath == null ? null : new File(rootPath + RECORDING_DATA_SUB_PATH); + } + + /** + * Returns the current storage status for DVR recordings. + * + * @return {@link StorageStatus} + */ + @AnyThread + public @StorageStatus int getDvrStorageStatus() { + MountedStorageStatus status = mMountedStorageStatus; + if (status.mStorageMountedDir == null) { + return STORAGE_STATUS_MISSING; + } + if (CommonFeatures.FORCE_RECORDING_UNTIL_NO_SPACE.isEnabled(mContext)) { + return STORAGE_STATUS_OK; + } + if (status.mStorageMountedCapacity < MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES) { + return STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL; + } + try { + StatFs statFs = new StatFs(status.mStorageMountedDir.toString()); + if (statFs.getAvailableBytes() < MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES) { + return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; + } + } catch (IllegalArgumentException e) { + // In rare cases, storage status change was not notified yet. + SoftPreconditions.checkState(false); + return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; + } + return STORAGE_STATUS_OK; + } + + /** + * Returns whether the storage has sufficient storage. + * + * @return {@code true} when there is sufficient storage, {@code false} otherwise + */ + public boolean isStorageSufficient() { + return getDvrStorageStatus() == STORAGE_STATUS_OK; + } + + /** APPs that want to clean up DB for recordings should override this method to do the job. */ + protected void cleanUpDbIfNeeded() {} + + private MountedStorageStatus getStorageStatusInternal() { + boolean storageMounted = + Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + File storageMountedDir = storageMounted ? Environment.getExternalStorageDirectory() : null; + storageMounted = storageMounted && storageMountedDir != null; + long storageMountedCapacity = 0L; + if (storageMounted) { + try { + StatFs statFs = new StatFs(storageMountedDir.toString()); + storageMountedCapacity = statFs.getTotalBytes(); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Storage mount status was changed."); + storageMounted = false; + storageMountedDir = null; + } + } + return new MountedStorageStatus(storageMounted, storageMountedDir, storageMountedCapacity); + } +} diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java index 7a649285..59ac5b02 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java @@ -16,6 +16,8 @@ package com.android.tv.common.ui.setup; +import static android.content.Context.ACCESSIBILITY_SERVICE; + import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist; @@ -25,6 +27,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; +import android.view.accessibility.AccessibilityManager; import android.widget.LinearLayout; import com.android.tv.common.R; @@ -37,6 +40,8 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { */ public static final String KEY_THREE_PANE = "key_three_pane"; + private View mContentFragment; + @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -44,10 +49,9 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { Bundle arguments = getArguments(); view.findViewById(android.support.v17.leanback.R.id.action_fragment_root) .setPadding(0, 0, 0, 0); + mContentFragment = view.findViewById(android.support.v17.leanback.R.id.content_fragment); LinearLayout.LayoutParams guidanceLayoutParams = - (LinearLayout.LayoutParams) - view.findViewById(android.support.v17.leanback.R.id.content_fragment) - .getLayoutParams(); + (LinearLayout.LayoutParams) mContentFragment.getLayoutParams(); guidanceLayoutParams.weight = 0; if (arguments != null && arguments.getBoolean(KEY_THREE_PANE, false)) { // Content fragment. @@ -94,6 +98,17 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { return view; } + @Override + public void onResume() { + super.onResume(); + AccessibilityManager am = + (AccessibilityManager) getActivity().getSystemService(ACCESSIBILITY_SERVICE); + if (am.isEnabled() && am.isTouchExplorationEnabled()) { + mContentFragment.setFocusable(true); + mContentFragment.requestFocus(); + } + } + @Override public GuidanceStylist onCreateGuidanceStylist() { return new GuidanceStylist() { @@ -112,6 +127,10 @@ public abstract class SetupGuidedStepFragment extends GuidedStepFragment { protected abstract String getActionCategory(); + protected View getDoneButton() { + return getActivity().findViewById(R.id.button_done); + } + @Override public void onGuidedActionClicked(GuidedAction action) { SetupActionHelper.onActionClick(this, getActionCategory(), (int) action.getId()); diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java index e05a40c8..c02d3f56 100644 --- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java +++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java @@ -32,8 +32,9 @@ public abstract class SetupMultiPaneFragment extends SetupFragment { public static final int ACTION_DONE = Integer.MAX_VALUE; public static final int ACTION_SKIP = ACTION_DONE - 1; + public static final int MAX_SUBCLASSES_ID = ACTION_SKIP - 1; - private static final String CONTENT_FRAGMENT_TAG = "content_fragment"; + public static final String CONTENT_FRAGMENT_TAG = "content_fragment"; @Override public View onCreateView( diff --git a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java index 38fccbbe..13b89ea1 100644 --- a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java +++ b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv.common.ui.setup.animation; import android.animation.Animator; diff --git a/common/src/com/android/tv/common/util/AutoCloseableUtils.java b/common/src/com/android/tv/common/util/AutoCloseableUtils.java new file mode 100644 index 00000000..605715ef --- /dev/null +++ b/common/src/com/android/tv/common/util/AutoCloseableUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.util; + +import android.util.Log; + +/** Static utilities for AutoCloseable. */ +public class AutoCloseableUtils { + private static final String TAG = "AutoCloseableUtils"; + + private AutoCloseableUtils() {} + + public static void closeQuietly(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ex) { + Log.e(TAG, "Error closing " + closeable, ex); + } + } +} diff --git a/common/src/com/android/tv/common/util/Clock.java b/common/src/com/android/tv/common/util/Clock.java new file mode 100644 index 00000000..a8c4366c --- /dev/null +++ b/common/src/com/android/tv/common/util/Clock.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.common.util; + +import android.os.SystemClock; + +/** + * An interface through which system clocks can be read. The {@link #SYSTEM} implementation must be + * used for all non-test cases. + */ +public interface Clock { + /** + * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. See {@link + * System#currentTimeMillis()}. + */ + long currentTimeMillis(); + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @see SystemClock#elapsedRealtime() + */ + long elapsedRealtime(); + + /** + * Waits a given number of milliseconds (of uptimeMillis) before returning. + * + * @param ms to sleep before returning, in milliseconds of uptime. + * @see SystemClock#sleep(long) + */ + void sleep(long ms); + + /** The default implementation of Clock. */ + Clock SYSTEM = + new Clock() { + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + @Override + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + + @Override + public void sleep(long ms) { + SystemClock.sleep(ms); + } + }; +} diff --git a/common/src/com/android/tv/common/util/CollectionUtils.java b/common/src/com/android/tv/common/util/CollectionUtils.java new file mode 100644 index 00000000..8ca7e3cc --- /dev/null +++ b/common/src/com/android/tv/common/util/CollectionUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** Static utilities for collections */ +public class CollectionUtils { + + /** + * Returns an array with the arrays concatenated together. + * + * @see Stackoverflow answer by Joachim Sauer + */ + public static T[] concatAll(T[] first, T[]... rest) { + int totalLength = first.length; + for (T[] array : rest) { + totalLength += array.length; + } + T[] result = Arrays.copyOf(first, totalLength); + int offset = first.length; + for (T[] array : rest) { + System.arraycopy(array, 0, result, offset, array.length); + offset += array.length; + } + return result; + } + + /** + * Unions the two collections and returns the unified list. + * + *

The elements is not compared with hashcode() or equals(). Comparator is used for the + * equality check. + */ + public static List union( + Collection originals, Collection toAdds, Comparator comparator) { + List result = new ArrayList<>(originals); + Collections.sort(result, comparator); + List resultToAdd = new ArrayList<>(); + for (T toAdd : toAdds) { + if (Collections.binarySearch(result, toAdd, comparator) < 0) { + resultToAdd.add(toAdd); + } + } + result.addAll(resultToAdd); + return result; + } + + /** Subtracts the elements from the original collection. */ + public static List subtract( + Collection originals, T[] toSubtracts, Comparator comparator) { + List result = new ArrayList<>(originals); + Collections.sort(result, comparator); + for (T toSubtract : toSubtracts) { + int index = Collections.binarySearch(result, toSubtract, comparator); + if (index >= 0) { + result.remove(index); + } + } + return result; + } + + /** Returns {@code true} if the two specified collections have common elements. */ + public static boolean containsAny( + Collection c1, Collection c2, Comparator comparator) { + List contains = new ArrayList<>(c1); + Collections.sort(contains, comparator); + for (T iterate : c2) { + if (Collections.binarySearch(contains, iterate, comparator) >= 0) { + return true; + } + } + return false; + } +} diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java new file mode 100644 index 00000000..865d4c43 --- /dev/null +++ b/common/src/com/android/tv/common/util/CommonUtils.java @@ -0,0 +1,148 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import android.content.Context; +import android.content.Intent; +import android.media.tv.TvInputInfo; +import android.os.Build; +import android.util.ArraySet; +import com.android.tv.common.BuildConfig; +import com.android.tv.common.CommonConstants; +import com.android.tv.common.experiments.Experiments; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Set; + +/** Util class for common use in TV app and inputs. */ +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated +public final class CommonUtils { + private static final String LC_PACKAGE_NAME = "com.android.tv"; + private static final ThreadLocal ISO_8601 = + new ThreadLocal() { + private final SimpleDateFormat value = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + + @Override + protected SimpleDateFormat initialValue() { + return value; + } + }; + // Hardcoded list for known bundled inputs not written by OEM/SOCs. + // Bundled (system) inputs not in the list will get the high priority + // so they and their channels come first in the UI. + private static final Set BUNDLED_PACKAGE_SET = new ArraySet<>(); + + static { + BUNDLED_PACKAGE_SET.add("com.android.tv"); + } + + private static Boolean sRunningInTest; + + private CommonUtils() {} + + /** + * Returns an intent to start the setup activity for the TV input using {@link + * CommonConstants#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); + setupIntent = intentContainer; + } + return setupIntent; + } + + /** + * Returns an intent to start the setup activity for this TV input using {@link + * CommonConstants#INTENT_ACTION_INPUT_SETUP}. + */ + public static Intent createSetupIntent(TvInputInfo input) { + return createSetupIntent(input.createSetupIntent(), input.getId()); + } + + /** + * Checks if this application is running in tests. + * + *

{@link android.app.ActivityManager#isRunningInTestHarness} doesn't return {@code true} for + * the usual devices even the application is running in tests. We need to figure it out by + * checking whether the class in tv-tests-common module can be loaded or not. + */ + public static synchronized boolean isRunningInTest() { + if (sRunningInTest == null) { + try { + Class.forName("com.android.tv.testing.utils.Utils"); + sRunningInTest = true; + } catch (ClassNotFoundException e) { + sRunningInTest = false; + } + } + return sRunningInTest; + } + + /** Checks whether a given package is in our bundled package set. */ + public static boolean isInBundledPackageSet(String packageName) { + return BUNDLED_PACKAGE_SET.contains(packageName); + } + + /** Checks whether a given input is a bundled input. */ + public static boolean isBundledInput(String inputId) { + for (String prefix : BUNDLED_PACKAGE_SET) { + if (inputId.startsWith(prefix + "/")) { + return true; + } + } + return false; + } + + /** Returns true if the application is packaged with Live TV. */ + public static boolean isPackagedWithLiveChannels(Context context) { + return (LC_PACKAGE_NAME.equals(context.getPackageName())); + } + + /** Returns true if the current user is a developer. */ + public static boolean isDeveloper() { + return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get(); + } + + /** Converts time in milliseconds to a ISO 8061 string. */ + public static String toIsoDateTimeString(long timeMillis) { + return ISO_8601.get().format(new Date(timeMillis)); + } + + /** Deletes a file or a directory. */ + public static void deleteDirOrFile(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + for (File child : fileOrDirectory.listFiles()) { + deleteDirOrFile(child); + } + } + fileOrDirectory.delete(); + } + + public static boolean isRoboTest() { + return "robolectric".equals(Build.FINGERPRINT); + } +} diff --git a/common/src/com/android/tv/common/util/Debug.java b/common/src/com/android/tv/common/util/Debug.java new file mode 100644 index 00000000..ab908741 --- /dev/null +++ b/common/src/com/android/tv/common/util/Debug.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** A class only for help developers. */ +public class Debug { + /** + * A threshold of start up time, when the start up time of Live TV is more than it, a + * warning will show to the developer. + */ + public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6); + /** Tag for measuring start up time of Live TV. */ + public static final String TAG_START_UP_TIMER = "start_up_timer"; + + /** A global map for duration timers. */ + private static final Map sTimerMap = new HashMap<>(); + + /** Returns the global duration timer by tag. */ + public static DurationTimer getTimer(String tag) { + if (sTimerMap.get(tag) != null) { + return sTimerMap.get(tag); + } + DurationTimer timer = new DurationTimer(tag, true); + sTimerMap.put(tag, timer); + return timer; + } + + /** Removes the global duration timer by tag. */ + public static DurationTimer removeTimer(String tag) { + return sTimerMap.remove(tag); + } +} diff --git a/common/src/com/android/tv/common/util/DurationTimer.java b/common/src/com/android/tv/common/util/DurationTimer.java new file mode 100644 index 00000000..91581ad5 --- /dev/null +++ b/common/src/com/android/tv/common/util/DurationTimer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import android.os.SystemClock; +import android.util.Log; +import com.android.tv.common.BuildConfig; + +/** Times a duration. */ +public final class DurationTimer { + private static final String TAG = "DurationTimer"; + public static final long TIME_NOT_SET = -1; + + private long mStartTimeMs = TIME_NOT_SET; + private String mTag = TAG; + private boolean mLogEngOnly; + + public DurationTimer() {} + + public DurationTimer(String tag, boolean logEngOnly) { + mTag = tag; + mLogEngOnly = logEngOnly; + } + + /** Returns true if the timer is running. */ + public boolean isRunning() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** Start the timer. */ + public void start() { + mStartTimeMs = SystemClock.elapsedRealtime(); + } + + /** Returns true if timer is started. */ + public boolean isStarted() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** + * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long getDuration() { + return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET; + } + + /** + * Stops the timer and resets its value to {@link #TIME_NOT_SET}. + * + * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long reset() { + long duration = getDuration(); + mStartTimeMs = TIME_NOT_SET; + return duration; + } + + /** Adds information and duration time to the log. */ + public void log(String message) { + if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) { + Log.i(mTag, message + " : " + getDuration() + "ms"); + } + } +} diff --git a/common/src/com/android/tv/common/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java new file mode 100644 index 00000000..e11e17a9 --- /dev/null +++ b/common/src/com/android/tv/common/util/LocationUtils.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Address; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; + + + + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +/** A utility class to get the current location. */ +public class LocationUtils { + private static final String TAG = "LocationUtils"; + private static final boolean DEBUG = false; + + private static Context sApplicationContext; + private static Address sAddress; + private static String sCountry; + private static IOException sError; + + /** Checks the current location. */ + public static synchronized Address getCurrentAddress(Context context) + throws IOException, SecurityException { + if (sAddress != null) { + return sAddress; + } + if (sError != null) { + throw sError; + } + if (sApplicationContext == null) { + sApplicationContext = context.getApplicationContext(); + } + LocationUtilsHelper.startLocationUpdates(); + return null; + } + + /** Returns the current country. */ + @NonNull + public static synchronized String getCurrentCountry(Context context) { + if (sCountry != null) { + return sCountry; + } + if (TextUtils.isEmpty(sCountry)) { + sCountry = context.getResources().getConfiguration().locale.getCountry(); + } + return sCountry; + } + + private static void updateAddress(Location location) { + if (DEBUG) Log.d(TAG, "Updating address with " + location); + if (location == null) { + return; + } + Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault()); + try { + List

addresses = + geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1); + if (addresses != null && !addresses.isEmpty()) { + sAddress = addresses.get(0); + if (DEBUG) Log.d(TAG, "Got " + sAddress); + try { + PostalCodeUtils.updatePostalCode(sApplicationContext); + } catch (Exception e) { + // Do nothing + } + } else { + if (DEBUG) Log.d(TAG, "No address returned"); + } + sError = null; + } catch (IOException e) { + Log.w(TAG, "Error in updating address", e); + sError = e; + } + } + + private LocationUtils() {} + + private static class LocationUtilsHelper { + private static final LocationListener LOCATION_LISTENER = + new LocationListener() { + @Override + public void onLocationChanged(Location location) { + updateAddress(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} + + @Override + public void onProviderEnabled(String provider) {} + + @Override + public void onProviderDisabled(String provider) {} + }; + + private static LocationManager sLocationManager; + + public static void startLocationUpdates() { + if (sLocationManager == null) { + sLocationManager = + (LocationManager) + sApplicationContext.getSystemService(Context.LOCATION_SERVICE); + try { + sLocationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null); + } catch (SecurityException e) { + // Enables requesting the location updates again. + sLocationManager = null; + throw e; + } + } + } + } +} diff --git a/common/src/com/android/tv/common/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java new file mode 100644 index 00000000..91f2bcd1 --- /dev/null +++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.util; + +import android.net.TrafficStats; +import android.support.annotation.NonNull; +import java.util.concurrent.Executor; + +/** Constants for tagging network traffic in the Live channels app. */ +public final class NetworkTrafficTags { + + public static final int DEFAULT_LIVE_CHANNELS = 1; + public static final int LOGO_FETCHER = 2; + public static final int HDHOMERUN = 3; + public static final int EPG_FETCH = 4; + + /** + * An executor which simply wraps a provided delegate executor, but calls {@link + * TrafficStats#setThreadStatsTag(int)} before executing any task. + */ + public static class TrafficStatsTaggingExecutor implements Executor { + private final Executor delegateExecutor; + private final int tag; + + public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) { + this.delegateExecutor = delegateExecutor; + this.tag = tag; + } + + @Override + public void execute(final @NonNull Runnable command) { + // TODO(b/62038127): robolectric does not support lamdas in unbundled apps + delegateExecutor.execute( + new Runnable() { + @Override + public void run() { + TrafficStats.setThreadStatsTag(tag); + try { + command.run(); + } finally { + TrafficStats.clearThreadStatsTag(); + } + } + }); + } + } + + private NetworkTrafficTags() {} +} diff --git a/common/src/com/android/tv/common/util/PermissionUtils.java b/common/src/com/android/tv/common/util/PermissionUtils.java new file mode 100644 index 00000000..8d409e50 --- /dev/null +++ b/common/src/com/android/tv/common/util/PermissionUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.common.util; + +import android.content.Context; +import android.content.pm.PackageManager; + +/** Util class to handle permissions. */ +public class PermissionUtils { + /** Permission to read the TV listings. */ + public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; + + private static Boolean sHasAccessAllEpgPermission; + private static Boolean sHasAccessWatchedHistoryPermission; + private static Boolean sHasModifyParentalControlsPermission; + + public static boolean hasAccessAllEpg(Context context) { + if (sHasAccessAllEpgPermission == null) { + sHasAccessAllEpgPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA") + == PackageManager.PERMISSION_GRANTED; + } + return sHasAccessAllEpgPermission; + } + + public static boolean hasAccessWatchedHistory(Context context) { + if (sHasAccessWatchedHistoryPermission == null) { + sHasAccessWatchedHistoryPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") + == PackageManager.PERMISSION_GRANTED; + } + return sHasAccessWatchedHistoryPermission; + } + + public static boolean hasModifyParentalControls(Context context) { + if (sHasModifyParentalControlsPermission == null) { + sHasModifyParentalControlsPermission = + context.checkSelfPermission("android.permission.MODIFY_PARENTAL_CONTROLS") + == PackageManager.PERMISSION_GRANTED; + } + return sHasModifyParentalControlsPermission; + } + + public static boolean hasReadTvListings(Context context) { + return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS) + == PackageManager.PERMISSION_GRANTED; + } + + public static boolean hasInternet(Context context) { + return context.checkSelfPermission("android.permission.INTERNET") + == PackageManager.PERMISSION_GRANTED; + } +} diff --git a/common/src/com/android/tv/common/util/PostalCodeUtils.java b/common/src/com/android/tv/common/util/PostalCodeUtils.java new file mode 100644 index 00000000..c0917af2 --- /dev/null +++ b/common/src/com/android/tv/common/util/PostalCodeUtils.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import android.content.Context; +import android.location.Address; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.common.CommonPreferences; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +/** A utility class to update, get, and set the last known postal or zip code. */ +public class PostalCodeUtils { + private static final String TAG = "PostalCodeUtils"; + + // Postcode formats, where A signifies a letter and 9 a digit: + // US zip code format: 99999 + private static final String POSTCODE_REGEX_US = "^(\\d{5})"; + // UK postcode district formats: A9, A99, AA9, AA99 + // Full UK postcode format: Postcode District + space + 9AA + // Should be able to handle both postcode district and full postcode + private static final String POSTCODE_REGEX_GB = + "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$"; + private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode + + private static final Map REGION_PATTERN = new HashMap<>(); + private static final Map REGION_MAX_LENGTH = new HashMap<>(); + + static { + REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US)); + REGION_PATTERN.put( + Locale.UK.getCountry(), + Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR)); + REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5); + REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8); + } + + // The longest postcode number is 10-character-long. + // Use a larger number to accommodate future changes. + private static final int DEFAULT_MAX_LENGTH = 16; + + /** Returns {@code true} if postal code has been changed */ + public static boolean updatePostalCode(Context context) + throws IOException, SecurityException, NoPostalCodeException { + String postalCode = getPostalCode(context); + String lastPostalCode = getLastPostalCode(context); + if (TextUtils.isEmpty(postalCode)) { + if (TextUtils.isEmpty(lastPostalCode)) { + throw new NoPostalCodeException(); + } + } else if (!TextUtils.equals(postalCode, lastPostalCode)) { + setLastPostalCode(context, postalCode); + return true; + } + return false; + } + + /** + * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or + * input by users. + */ + public static String getLastPostalCode(Context context) { + return CommonPreferences.getLastPostalCode(context); + } + + /** + * Sets the last stored postal or zip code. This method will overwrite the value written by + * calling {@link #updatePostalCode(Context)}. + */ + public static void setLastPostalCode(Context context, String postalCode) { + Log.i(TAG, "Set Postal Code:" + postalCode); + CommonPreferences.setLastPostalCode(context, postalCode); + } + + @Nullable + private static String getPostalCode(Context context) throws IOException, SecurityException { + Address address = LocationUtils.getCurrentAddress(context); + if (address != null) { + Log.i( + TAG, + "Current country and postal code is " + + address.getCountryName() + + ", " + + address.getPostalCode()); + return address.getPostalCode(); + } + return null; + } + + /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ + public static class NoPostalCodeException extends Exception { + public NoPostalCodeException() {} + } + + /** + * Checks whether a postcode matches the format of the specific region. + * + * @return {@code false} if the region is supported and the postcode doesn't match; {@code true} + * otherwise + */ + public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) { + Pattern pattern = REGION_PATTERN.get(region.toUpperCase()); + return pattern == null || pattern.matcher(postcode).matches(); + } + + /** + * Gets the largest possible postcode length in the region. + * + * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH} + * otherwise + */ + public static int getRegionMaxLength(Context context) { + Integer maxLength = + REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); + return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; + } +} diff --git a/common/src/com/android/tv/common/util/SharedPreferencesUtils.java b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java new file mode 100644 index 00000000..f93951dd --- /dev/null +++ b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.common.util; + +import android.content.Context; +import android.os.AsyncTask; +import android.preference.PreferenceManager; + +/** Static utilities for {@link android.content.SharedPreferences} */ +public final class SharedPreferencesUtils { + // Note that changing the preference name will reset the preference values. + public static final String SHARED_PREF_FEATURES = "sharePreferencesFeatures"; + public static final String SHARED_PREF_BROWSABLE = "browsable_shared_preference"; + public static final String SHARED_PREF_WATCHED_HISTORY = "watched_history_shared_preference"; + public static final String SHARED_PREF_DVR_WATCHED_POSITION = + "dvr_watched_position_shared_preference"; + public static final String SHARED_PREF_AUDIO_CAPABILITIES = + "com.android.tv.audio_capabilities"; + public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner"; + public static final String SHARED_PREF_EPG = "epg_preferences"; + public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings"; + /** No need to pre-initialize. It's used only on the worker thread. */ + public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris"; + /** Stores the UI related settings */ + public static final String SHARED_PREF_UI_SETTINGS = "ui_settings"; + + public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms"; + + private static boolean sInitializeCalled; + + /** + * {@link android.content.SharedPreferences} loads the preference file when {@link + * Context#getSharedPreferences(String, int)} is called for the first time. Call {@link + * Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR due to the + * file loading. + */ + public static synchronized void initialize(final Context context, final Runnable postTask) { + if (!sInitializeCalled) { + sInitializeCalled = true; + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + PreferenceManager.getDefaultSharedPreferences(context); + context.getSharedPreferences(SHARED_PREF_FEATURES, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + return null; + } + + @Override + protected void onPostExecute(Void result) { + postTask.run(); + } + }.execute(); + } + } + + private SharedPreferencesUtils() {} +} diff --git a/common/src/com/android/tv/common/util/StringUtils.java b/common/src/com/android/tv/common/util/StringUtils.java new file mode 100644 index 00000000..b9461426 --- /dev/null +++ b/common/src/com/android/tv/common/util/StringUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +/** Utility class for handling {@link String}. */ +public final class StringUtils { + + private StringUtils() {} + + /** Returns compares two strings lexicographically and handles null values quietly. */ + public static int compare(String a, String b) { + if (a == null) { + return b == null ? 0 : -1; + } + if (b == null) { + return 1; + } + return a.compareTo(b); + } +} diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java new file mode 100644 index 00000000..02bf5791 --- /dev/null +++ b/common/src/com/android/tv/common/util/SystemProperties.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import com.android.tv.common.BooleanSystemProperty; + +/** A convenience class for getting TV related system properties. */ +public final class SystemProperties { + + /** Allow Google Analytics for eng builds. */ + public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = + new BooleanSystemProperty("tv_allow_analytics_in_eng", false); + + /** Allow Strict mode for debug builds. */ + public static final BooleanSystemProperty ALLOW_STRICT_MODE = + new BooleanSystemProperty("tv_allow_strict_mode", true); + + /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */ + public static final BooleanSystemProperty LOG_KEYEVENT = + new BooleanSystemProperty("tv_log_keyevent", false); + /** When true debug keys are used. Defaults to false. */ + public static final BooleanSystemProperty USE_DEBUG_KEYS = + new BooleanSystemProperty("tv_use_debug_keys", false); + + /** + * Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. + */ + public static final BooleanSystemProperty USE_TRACKER = + new BooleanSystemProperty("tv_use_tracker", true); + + static { + updateSystemProperties(); + } + + private SystemProperties() {} + + /** Update the TV related system properties. */ + public static void updateSystemProperties() { + BooleanSystemProperty.resetAll(); + } +} diff --git a/common/src/com/android/tv/common/util/SystemPropertiesProxy.java b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java new file mode 100644 index 00000000..a3ffd0fa --- /dev/null +++ b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.util; + +import android.util.Log; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Proxy class that gives an access to a hidden API {@link android.os.SystemProperties#getBoolean}. + */ +public class SystemPropertiesProxy { + private static final String TAG = "SystemPropertiesProxy"; + + private SystemPropertiesProxy() {} + + public static boolean getBoolean(String key, boolean def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getBooleanMethod = + SystemPropertiesClass.getDeclaredMethod( + "getBoolean", String.class, boolean.class); + getBooleanMethod.setAccessible(true); + return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e); + } + return def; + } + + public static int getInt(String key, int def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getIntMethod = + SystemPropertiesClass.getDeclaredMethod("getInt", String.class, int.class); + getIntMethod.setAccessible(true); + return (int) getIntMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e); + } + return def; + } + + public static String getString(String key, String def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getIntMethod = + SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class); + getIntMethod.setAccessible(true); + return (String) getIntMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.get()", e); + } + return def; + } +} diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java deleted file mode 100644 index a257c1c6..00000000 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java +++ /dev/null @@ -1,405 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -/* - ******************************************************************************* - * Copyright (C) 1996-2007, International Business Machines Corporation and * - * others. All Rights Reserved. * - ******************************************************************************* - */ - -package com.ibm.icu.text; - -/** - * An interface defining constants for the Standard Compression Scheme for Unicode (SCSU) as - * outlined in Unicode Technical Report #6. - * - * @author Stephen F. Booth - * @version 1.1 05 Aug 99 - * @version 1.0 26 Jul 99 - */ -interface SCSU { - // ========================== - // Generic window shift - // ========================== - static final int COMPRESSIONOFFSET = 0x80; - - // ========================== - // Number of windows - // ========================== - static final int NUMWINDOWS = 8; - static final int NUMSTATICWINDOWS = 8; - - // ========================== - // Indicates a window index is invalid - // ========================== - static final int INVALIDWINDOW = -1; - - // ========================== - // Indicates a character doesn't exist in input (past end of buffer) - // ========================== - static final int INVALIDCHAR = -1; - - // ========================== - // Compression modes - // ========================== - static final int SINGLEBYTEMODE = 0; - static final int UNICODEMODE = 1; - - // ========================== - // Maximum value for a window's index - // ========================== - static final int MAXINDEX = 0xFF; - - // ========================== - // Reserved index value (characters belongs to first block) - // ========================== - static final int RESERVEDINDEX = 0x00; - - // ========================== - // Indices for scripts which cross a half-block boundary - // ========================== - static final int LATININDEX = 0xF9; - static final int IPAEXTENSIONINDEX = 0xFA; - static final int GREEKINDEX = 0xFB; - static final int ARMENIANINDEX = 0xFC; - static final int HIRAGANAINDEX = 0xFD; - static final int KATAKANAINDEX = 0xFE; - static final int HALFWIDTHKATAKANAINDEX = 0xFF; - - // ========================== - // Single-byte mode tags - // ========================== - static final int SDEFINEX = 0x0B; - static final int SRESERVED = 0x0C; // reserved value - static final int SQUOTEU = 0x0E; - static final int SCHANGEU = 0x0F; - - static final int SQUOTE0 = 0x01; - static final int SQUOTE1 = 0x02; - static final int SQUOTE2 = 0x03; - static final int SQUOTE3 = 0x04; - static final int SQUOTE4 = 0x05; - static final int SQUOTE5 = 0x06; - static final int SQUOTE6 = 0x07; - static final int SQUOTE7 = 0x08; - - static final int SCHANGE0 = 0x10; - static final int SCHANGE1 = 0x11; - static final int SCHANGE2 = 0x12; - static final int SCHANGE3 = 0x13; - static final int SCHANGE4 = 0x14; - static final int SCHANGE5 = 0x15; - static final int SCHANGE6 = 0x16; - static final int SCHANGE7 = 0x17; - - static final int SDEFINE0 = 0x18; - static final int SDEFINE1 = 0x19; - static final int SDEFINE2 = 0x1A; - static final int SDEFINE3 = 0x1B; - static final int SDEFINE4 = 0x1C; - static final int SDEFINE5 = 0x1D; - static final int SDEFINE6 = 0x1E; - static final int SDEFINE7 = 0x1F; - - // ========================== - // Unicode mode tags - // ========================== - static final int UCHANGE0 = 0xE0; - static final int UCHANGE1 = 0xE1; - static final int UCHANGE2 = 0xE2; - static final int UCHANGE3 = 0xE3; - static final int UCHANGE4 = 0xE4; - static final int UCHANGE5 = 0xE5; - static final int UCHANGE6 = 0xE6; - static final int UCHANGE7 = 0xE7; - - static final int UDEFINE0 = 0xE8; - static final int UDEFINE1 = 0xE9; - static final int UDEFINE2 = 0xEA; - static final int UDEFINE3 = 0xEB; - static final int UDEFINE4 = 0xEC; - static final int UDEFINE5 = 0xED; - static final int UDEFINE6 = 0xEE; - static final int UDEFINE7 = 0xEF; - - static final int UQUOTEU = 0xF0; - static final int UDEFINEX = 0xF1; - static final int URESERVED = 0xF2; // reserved value - - // ========================== - // Class variables - // ========================== - - /** For window offset mapping */ - static final int[] sOffsetTable = { - // table generated by CompressionTableGenerator - 0x0, - 0x80, - 0x100, - 0x180, - 0x200, - 0x280, - 0x300, - 0x380, - 0x400, - 0x480, - 0x500, - 0x580, - 0x600, - 0x680, - 0x700, - 0x780, - 0x800, - 0x880, - 0x900, - 0x980, - 0xa00, - 0xa80, - 0xb00, - 0xb80, - 0xc00, - 0xc80, - 0xd00, - 0xd80, - 0xe00, - 0xe80, - 0xf00, - 0xf80, - 0x1000, - 0x1080, - 0x1100, - 0x1180, - 0x1200, - 0x1280, - 0x1300, - 0x1380, - 0x1400, - 0x1480, - 0x1500, - 0x1580, - 0x1600, - 0x1680, - 0x1700, - 0x1780, - 0x1800, - 0x1880, - 0x1900, - 0x1980, - 0x1a00, - 0x1a80, - 0x1b00, - 0x1b80, - 0x1c00, - 0x1c80, - 0x1d00, - 0x1d80, - 0x1e00, - 0x1e80, - 0x1f00, - 0x1f80, - 0x2000, - 0x2080, - 0x2100, - 0x2180, - 0x2200, - 0x2280, - 0x2300, - 0x2380, - 0x2400, - 0x2480, - 0x2500, - 0x2580, - 0x2600, - 0x2680, - 0x2700, - 0x2780, - 0x2800, - 0x2880, - 0x2900, - 0x2980, - 0x2a00, - 0x2a80, - 0x2b00, - 0x2b80, - 0x2c00, - 0x2c80, - 0x2d00, - 0x2d80, - 0x2e00, - 0x2e80, - 0x2f00, - 0x2f80, - 0x3000, - 0x3080, - 0x3100, - 0x3180, - 0x3200, - 0x3280, - 0x3300, - 0x3380, - 0xe000, - 0xe080, - 0xe100, - 0xe180, - 0xe200, - 0xe280, - 0xe300, - 0xe380, - 0xe400, - 0xe480, - 0xe500, - 0xe580, - 0xe600, - 0xe680, - 0xe700, - 0xe780, - 0xe800, - 0xe880, - 0xe900, - 0xe980, - 0xea00, - 0xea80, - 0xeb00, - 0xeb80, - 0xec00, - 0xec80, - 0xed00, - 0xed80, - 0xee00, - 0xee80, - 0xef00, - 0xef80, - 0xf000, - 0xf080, - 0xf100, - 0xf180, - 0xf200, - 0xf280, - 0xf300, - 0xf380, - 0xf400, - 0xf480, - 0xf500, - 0xf580, - 0xf600, - 0xf680, - 0xf700, - 0xf780, - 0xf800, - 0xf880, - 0xf900, - 0xf980, - 0xfa00, - 0xfa80, - 0xfb00, - 0xfb80, - 0xfc00, - 0xfc80, - 0xfd00, - 0xfd80, - 0xfe00, - 0xfe80, - 0xff00, - 0xff80, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0x0, - 0xc0, - 0x250, - 0x370, - 0x530, - 0x3040, - 0x30a0, - 0xff60 - }; - - /** Static compression window offsets */ - static final int[] sOffsets = { - 0x0000, // for quoting single-byte mode tags - 0x0080, // Latin-1 Supplement - 0x0100, // Latin Extended-A - 0x0300, // Combining Diacritical Marks - 0x2000, // General Punctuation - 0x2080, // Curency Symbols - 0x2100, // Letterlike Symbols and Number Forms - 0x3000 // CJK Symbols and Punctuation - }; -} diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java deleted file mode 100644 index 6e2a2d71..00000000 --- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java +++ /dev/null @@ -1,795 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -/* - ******************************************************************************* - * Copyright (C) 1996-2016, International Business Machines Corporation and * - * others. All Rights Reserved. * - ******************************************************************************* - */ - -package com.ibm.icu.text; - -/** - * A decompression engine implementing the Standard Compression Scheme for Unicode (SCSU) as - * outlined in Unicode Technical Report #6. - * - *

USAGE - * - *

The static methods on UnicodeDecompressor may be used in a straightforward manner to - * decompress simple strings: - * - *

- *  byte [] compressed = ... ; // get compressed bytes from somewhere
- *  String result = UnicodeDecompressor.decompress(compressed);
- * 
- * - *

The static methods have a fairly large memory footprint. For finer-grained control over memory - * usage, UnicodeDecompressor offers more powerful APIs allowing iterative decompression: - * - *

- *  // Decompress an array "bytes" of length "len" using a buffer of 512 chars
- *  // to the Writer "out"
- *
- *  UnicodeDecompressor myDecompressor         = new UnicodeDecompressor();
- *  final static int    BUFSIZE                = 512;
- *  char []             charBuffer             = new char [ BUFSIZE ];
- *  int                 charsWritten           = 0;
- *  int []              bytesRead              = new int [1];
- *  int                 totalBytesDecompressed = 0;
- *  int                 totalCharsWritten      = 0;
- *
- *  do {
- *    // do the decompression
- *    charsWritten = myDecompressor.decompress(bytes, totalBytesDecompressed,
- *                                             len, bytesRead,
- *                                             charBuffer, 0, BUFSIZE);
- *
- *    // do something with the current set of chars
- *    out.write(charBuffer, 0, charsWritten);
- *
- *    // update the no. of bytes decompressed
- *    totalBytesDecompressed += bytesRead[0];
- *
- *    // update the no. of chars written
- *    totalCharsWritten += charsWritten;
- *
- *  } while(totalBytesDecompressed < len);
- *
- *  myDecompressor.reset(); // reuse decompressor
- * 
- * - *

Decompression is performed according to the standard set forth in Unicode Technical Report #6 - * - * @see UnicodeCompressor - * @author Stephen F. Booth - * @stable ICU 2.4 - */ -public final class UnicodeDecompressor implements SCSU { - // ========================== - // Instance variables - // ========================== - - /** Alias to current dynamic window */ - private int fCurrentWindow = 0; - - /** Dynamic compression window offsets */ - private int[] fOffsets = new int[NUMWINDOWS]; - - /** Current compression mode */ - private int fMode = SINGLEBYTEMODE; - - /** Size of our internal buffer */ - private static final int BUFSIZE = 3; - - /** Internal buffer for saving state */ - private byte[] fBuffer = new byte[BUFSIZE]; - - /** Number of characters in our internal buffer */ - private int fBufferLength = 0; - - /** - * Create a UnicodeDecompressor. Sets all windows to their default values. - * - * @see #reset - * @stable ICU 2.4 - */ - public UnicodeDecompressor() { - reset(); // initialize to defaults - } - - /** - * Decompress a byte array into a String. - * - * @param buffer The byte array to decompress. - * @return A String containing the decompressed characters. - * @see #decompress(byte [], int, int) - * @stable ICU 2.4 - */ - public static String decompress(byte[] buffer) { - char[] buf = decompress(buffer, 0, buffer.length); - return new String(buf); - } - - /** - * Decompress a byte array into a Unicode character array. - * - * @param buffer The byte array to decompress. - * @param start The start of the byte run to decompress. - * @param limit The limit of the byte run to decompress. - * @return A character array containing the decompressed bytes. - * @see #decompress(byte []) - * @stable ICU 2.4 - */ - public static char[] decompress(byte[] buffer, int start, int limit) { - UnicodeDecompressor comp = new UnicodeDecompressor(); - - // use a buffer we know will never overflow - // in the worst case, each byte will decompress - // to a surrogate pair (buffer must be at least 2 chars) - int len = Math.max(2, 2 * (limit - start)); - char[] temp = new char[len]; - - int charCount = comp.decompress(buffer, start, limit, null, temp, 0, len); - - char[] result = new char[charCount]; - System.arraycopy(temp, 0, result, 0, charCount); - return result; - } - - /** - * Decompress a byte array into a Unicode character array. - * - *

This function will either completely fill the output buffer, or consume the entire input. - * - * @param byteBuffer The byte buffer to decompress. - * @param byteBufferStart The start of the byte run to decompress. - * @param byteBufferLimit The limit of the byte run to decompress. - * @param bytesRead A one-element array. If not null, on return the number of bytes read from - * byteBuffer. - * @param charBuffer A buffer to receive the decompressed data. This buffer must be at minimum - * two characters in size. - * @param charBufferStart The starting offset to which to write decompressed data. - * @param charBufferLimit The limiting offset for writing decompressed data. - * @return The number of Unicode characters written to charBuffer. - * @stable ICU 2.4 - */ - public int decompress( - byte[] byteBuffer, - int byteBufferStart, - int byteBufferLimit, - int[] bytesRead, - char[] charBuffer, - int charBufferStart, - int charBufferLimit) { - // the current position in the source byte buffer - int bytePos = byteBufferStart; - - // the current position in the target char buffer - int ucPos = charBufferStart; - - // the current byte from the source buffer - int aByte = 0x00; - - // charBuffer must be at least 2 chars in size - if (charBuffer.length < 2 || (charBufferLimit - charBufferStart) < 2) - throw new IllegalArgumentException("charBuffer.length < 2"); - - // if our internal buffer isn't empty, flush its contents - // to the output buffer before doing any more decompression - if (fBufferLength > 0) { - - int newBytes = 0; - - // fill the buffer completely, to guarantee one full character - if (fBufferLength != BUFSIZE) { - newBytes = fBuffer.length - fBufferLength; - - // verify there are newBytes bytes in byteBuffer - if (byteBufferLimit - byteBufferStart < newBytes) - newBytes = byteBufferLimit - byteBufferStart; - - System.arraycopy(byteBuffer, byteBufferStart, fBuffer, fBufferLength, newBytes); - } - - // reset buffer length to 0 before recursive call - fBufferLength = 0; - - // call self recursively to decompress the buffer - int count = - decompress( - fBuffer, - 0, - fBuffer.length, - null, - charBuffer, - charBufferStart, - charBufferLimit); - - // update the positions into the arrays - ucPos += count; - bytePos += newBytes; - } - - // the main decompression loop - mainLoop: - while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { - switch (fMode) { - case SINGLEBYTEMODE: - // single-byte mode decompression loop - singleByteModeLoop: - while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { - aByte = byteBuffer[bytePos++] & 0xFF; - switch (aByte) { - // All bytes from 0x80 through 0xFF are remapped - // to chars or surrogate pairs according to the - // currently active window - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x98: - case 0x99: - case 0x9A: - case 0x9B: - case 0x9C: - case 0x9D: - case 0x9E: - case 0x9F: - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: - case 0xC0: - case 0xC1: - case 0xC2: - case 0xC3: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD5: - case 0xD6: - case 0xD7: - case 0xD8: - case 0xD9: - case 0xDA: - case 0xDB: - case 0xDC: - case 0xDD: - case 0xDE: - case 0xDF: - case 0xE0: - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xED: - case 0xEE: - case 0xEF: - case 0xF0: - case 0xF1: - case 0xF2: - case 0xF3: - case 0xF4: - case 0xF5: - case 0xF6: - case 0xF7: - case 0xF8: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: - // For offsets <= 0xFFFF, convert to a single char - // by adding the window's offset and subtracting - // the generic compression offset - if (fOffsets[fCurrentWindow] <= 0xFFFF) { - charBuffer[ucPos++] = - (char) - (aByte - + fOffsets[fCurrentWindow] - - COMPRESSIONOFFSET); - } - // For offsets > 0x10000, convert to a surrogate pair by - // normBase = window's offset - 0x10000 - // high surr. = 0xD800 + (normBase >> 10) - // low surr. = 0xDC00 + (normBase & 0x3FF) + (byte & 0x7F) - else { - // make sure there is enough room to write - // both characters - // if not, save state and break out - if ((ucPos + 1) >= charBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - int normalizedBase = fOffsets[fCurrentWindow] - 0x10000; - charBuffer[ucPos++] = (char) (0xD800 + (normalizedBase >> 10)); - charBuffer[ucPos++] = - (char) - (0xDC00 - + (normalizedBase & 0x3FF) - + (aByte & 0x7F)); - } - break; - - // bytes from 0x20 through 0x7F are treated as ASCII and - // are remapped to chars by padding the high byte - // (this is the same as quoting from static window 0) - // NUL (0x00), HT (0x09), CR (0x0A), LF (0x0D) - // are treated as ASCII as well - case 0x00: - case 0x09: - case 0x0A: - case 0x0D: - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - charBuffer[ucPos++] = (char) aByte; - break; - - // quote unicode - case SQUOTEU: - // verify we have two bytes following tag - // if not, save state and break out - if ((bytePos + 1) >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++]; - charBuffer[ucPos++] = - (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; - - // switch to Unicode mode - case SCHANGEU: - fMode = UNICODEMODE; - break singleByteModeLoop; - // break; - - // handle all quote tags - case SQUOTE0: - case SQUOTE1: - case SQUOTE2: - case SQUOTE3: - case SQUOTE4: - case SQUOTE5: - case SQUOTE6: - case SQUOTE7: - // verify there is a byte following the tag - // if not, save state and break out - if (bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - // if the byte is in the range 0x00 - 0x7F, use - // static window n otherwise, use dynamic window n - int dByte = byteBuffer[bytePos++] & 0xFF; - charBuffer[ucPos++] = - (char) - (dByte - + (dByte >= 0x00 && dByte < 0x80 - ? sOffsets[aByte - SQUOTE0] - : (fOffsets[aByte - SQUOTE0] - - COMPRESSIONOFFSET))); - break; - - // handle all change tags - case SCHANGE0: - case SCHANGE1: - case SCHANGE2: - case SCHANGE3: - case SCHANGE4: - case SCHANGE5: - case SCHANGE6: - case SCHANGE7: - fCurrentWindow = aByte - SCHANGE0; - break; - - // handle all define tags - case SDEFINE0: - case SDEFINE1: - case SDEFINE2: - case SDEFINE3: - case SDEFINE4: - case SDEFINE5: - case SDEFINE6: - case SDEFINE7: - // verify there is a byte following the tag - // if not, save state and break out - if (bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - fCurrentWindow = aByte - SDEFINE0; - fOffsets[fCurrentWindow] = - sOffsetTable[byteBuffer[bytePos++] & 0xFF]; - break; - - // handle define extended tag - case SDEFINEX: - // verify we have two bytes following tag - // if not, save state and break out - if ((bytePos + 1) >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++] & 0xFF; - fCurrentWindow = (aByte & 0xE0) >> 5; - fOffsets[fCurrentWindow] = - 0x10000 - + (0x80 - * (((aByte & 0x1F) << 8) - | (byteBuffer[bytePos++] & 0xFF))); - break; - - // reserved, shouldn't happen - case SRESERVED: - break; - } // end switch - } // end while - break; - - case UNICODEMODE: - // unicode mode decompression loop - unicodeModeLoop: - while (bytePos < byteBufferLimit && ucPos < charBufferLimit) { - aByte = byteBuffer[bytePos++] & 0xFF; - switch (aByte) { - // handle all define tags - case UDEFINE0: - case UDEFINE1: - case UDEFINE2: - case UDEFINE3: - case UDEFINE4: - case UDEFINE5: - case UDEFINE6: - case UDEFINE7: - // verify there is a byte following tag - // if not, save state and break out - if (bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - fCurrentWindow = aByte - UDEFINE0; - fOffsets[fCurrentWindow] = - sOffsetTable[byteBuffer[bytePos++] & 0xFF]; - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - // break; - - // handle define extended tag - case UDEFINEX: - // verify we have two bytes following tag - // if not, save state and break out - if ((bytePos + 1) >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++] & 0xFF; - fCurrentWindow = (aByte & 0xE0) >> 5; - fOffsets[fCurrentWindow] = - 0x10000 - + (0x80 - * (((aByte & 0x1F) << 8) - | (byteBuffer[bytePos++] & 0xFF))); - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - // break; - - // handle all change tags - case UCHANGE0: - case UCHANGE1: - case UCHANGE2: - case UCHANGE3: - case UCHANGE4: - case UCHANGE5: - case UCHANGE6: - case UCHANGE7: - fCurrentWindow = aByte - UCHANGE0; - fMode = SINGLEBYTEMODE; - break unicodeModeLoop; - // break; - - // quote unicode - case UQUOTEU: - // verify we have two bytes following tag - // if not, save state and break out - if (bytePos >= byteBufferLimit - 1) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - aByte = byteBuffer[bytePos++]; - charBuffer[ucPos++] = - (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; - - default: - // verify there is a byte following tag - // if not, save state and break out - if (bytePos >= byteBufferLimit) { - --bytePos; - System.arraycopy( - byteBuffer, - bytePos, - fBuffer, - 0, - byteBufferLimit - bytePos); - fBufferLength = byteBufferLimit - bytePos; - bytePos += fBufferLength; - break mainLoop; - } - - charBuffer[ucPos++] = - (char) (aByte << 8 | (byteBuffer[bytePos++] & 0xFF)); - break; - } // end switch - } // end while - break; - } // end switch( fMode ) - } // end while - - // fill in output parameter - if (bytesRead != null) bytesRead[0] = (bytePos - byteBufferStart); - - // return # of chars written - return (ucPos - charBufferStart); - } - - /** - * Reset the decompressor to its initial state. - * - * @stable ICU 2.4 - */ - public void reset() { - // reset dynamic windows - fOffsets[0] = 0x0080; // Latin-1 - fOffsets[1] = 0x00C0; // Latin-1 Supplement + Latin Extended-A - fOffsets[2] = 0x0400; // Cyrillic - fOffsets[3] = 0x0600; // Arabic - fOffsets[4] = 0x0900; // Devanagari - fOffsets[5] = 0x3040; // Hiragana - fOffsets[6] = 0x30A0; // Katakana - fOffsets[7] = 0xFF00; // Fullwidth ASCII - - fCurrentWindow = 0; // Make current window Latin-1 - fMode = SINGLEBYTEMODE; // Always start in single-byte mode - fBufferLength = 0; // Empty buffer - } -} diff --git a/jni/DvbManager.cpp b/jni/DvbManager.cpp index b344f803..e55d07c5 100644 --- a/jni/DvbManager.cpp +++ b/jni/DvbManager.cpp @@ -39,14 +39,12 @@ static double currentTimeMillis() { DvbManager::DvbManager(JNIEnv *env, jobject) : mFeFd(-1), - mDemuxFd(-1), mDvrFd(-1), mPatFilterFd(-1), mDvbApiVersion(DVB_API_VERSION_UNDEFINED), mDeliverySystemType(-1), mFeHasLock(false), mHasPendingTune(false) { - (void) mDemuxFd; // suppress unused warning jclass clazz = env->FindClass( "com/android/tv/tuner/TunerHal"); mOpenDvbFrontEndMethodID = env->GetMethodID( @@ -118,11 +116,13 @@ int DvbManager::tune(JNIEnv *env, jobject thiz, if (mDvbApiVersion == DVB_API_VERSION5) { struct dtv_property deliverySystemProperty = { - .cmd = DTV_DELIVERY_SYSTEM, .u.data = SYS_ATSC + .cmd = DTV_DELIVERY_SYSTEM }; + deliverySystemProperty.u.data = SYS_ATSC; struct dtv_property frequencyProperty = { - .cmd = DTV_FREQUENCY, .u.data = static_cast<__u32>(frequency) + .cmd = DTV_FREQUENCY }; + frequencyProperty.u.data = static_cast<__u32>(frequency); struct dtv_property modulationProperty = { .cmd = DTV_MODULATION }; if (strncmp(modulationStr, "QAM", 3) == 0) { modulationProperty.u.data = QAM_AUTO; @@ -503,4 +503,4 @@ int DvbManager::getDeliverySystemType(JNIEnv *env, jobject thiz) { } } return mDeliverySystemType; -} +} \ No newline at end of file diff --git a/jni/DvbManager.h b/jni/DvbManager.h index 2252332c..6289d645 100644 --- a/jni/DvbManager.h +++ b/jni/DvbManager.h @@ -61,7 +61,6 @@ class DvbManager { int mFeFd; - int mDemuxFd; int mDvrFd; int mPatFilterFd; int mDvbApiVersion; diff --git a/jni/gen_jni.sh b/jni/gen_jni.sh index aa52b248..2c246189 100755 --- a/jni/gen_jni.sh +++ b/jni/gen_jni.sh @@ -1,3 +1,18 @@ #!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + javah -jni -classpath ../../bin/classes:../../../../../../prebuilts/sdk/current/android.jar -o tunertvinput_jni.h com.android.tv.tuner.TunerHal diff --git a/jni/tunertvinput_jni.cpp b/jni/tunertvinput_jni.cpp index 9ad15141..40091779 100644 --- a/jni/tunertvinput_jni.cpp +++ b/jni/tunertvinput_jni.cpp @@ -172,4 +172,4 @@ Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType sDvbManagers.insert(std::pair(deviceId, dvbManager)); return dvbManager->getDeliverySystemType(env, thiz); } -} +} \ No newline at end of file diff --git a/libs/exoplayer-2.6.1.aar b/libs/exoplayer-2.6.1.aar new file mode 100644 index 00000000..4b8bc0dd Binary files /dev/null and b/libs/exoplayer-2.6.1.aar differ diff --git a/libs/exoplayer-core-2.6.1.aar b/libs/exoplayer-core-2.6.1.aar new file mode 100644 index 00000000..17b7df6b Binary files /dev/null and b/libs/exoplayer-core-2.6.1.aar differ diff --git a/libs/exoplayer-r1.5.16.aar b/libs/exoplayer-r1.5.16.aar new file mode 100644 index 00000000..364104fc Binary files /dev/null and b/libs/exoplayer-r1.5.16.aar differ diff --git a/libs/exoplayer.jar b/libs/exoplayer.jar deleted file mode 100644 index 43aea971..00000000 Binary files a/libs/exoplayer.jar and /dev/null differ diff --git a/libs/exoplayer_v2.jar b/libs/exoplayer_v2.jar deleted file mode 100644 index 6cfcde9c..00000000 Binary files a/libs/exoplayer_v2.jar and /dev/null differ diff --git a/libs/exoplayer_v2_ext_ffmpeg.jar b/libs/exoplayer_v2_ext_ffmpeg.jar deleted file mode 100644 index 65117267..00000000 Binary files a/libs/exoplayer_v2_ext_ffmpeg.jar and /dev/null differ diff --git a/proguard.flags b/proguard.flags index 0edd14f3..69b17861 100644 --- a/proguard.flags +++ b/proguard.flags @@ -27,12 +27,6 @@ -dontwarn com.google.android.volley.** -dontwarn com.google.android.common.** -# Keep the methods called from native code. --keepclasseswithmembers class com.android.tv.tuner.TunerHal { - int openDvbFrontEndFd(); - int openDvbDemuxFd(); - int openDvbDvrFd(); -} -keepclasseswithmembers class com.android.tv.tuner.*DataSource { int readAt(long, byte[], int, int); long getSize(); @@ -42,10 +36,28 @@ native ; } -# Keep method which is used for reflection. --keep @com.android.tv.common.annotation.UsedByReflection class * {*;} --keepclasseswithmembers class * { - @com.android.tv.common.annotation.UsedByReflection ; +# Configuration of proguard via annotations. Apply them to +# the elements of your program not only to ensure correct proguard +# functionality, but to document non-obvious entry points to your code to make +# it survive refactorings. + +# Annotations are implemented as attributes, so we have to explicitly keep them. +# Catch all which encompasses attributes like RuntimeVisibleParameterAnnotations +# and RuntimeVisibleTypeAnnotations +-keepattributes RuntimeVisible*Annotation* + +# JNI is an entry point that's hard to keep track of, so there's +# an annotation to mark fields and methods used by native code. + +# Keep the annotations that proguard needs to process. +-keep class com.android.tv.common.annotation.UsedBy* + +# Just because native code accesses members of a class, does not mean that the +# class itself needs to be annotated - only annotate classes that are +# referenced themselves in native code. +-keep @com.android.tv.common.annotation.UsedBy* class * +-keepclassmembers class * { + @com.android.tv.common.annotation.UsedBy* *; } # For tests @@ -56,4 +68,3 @@ # Grpc used by epg via reflection -keep class io.grpc.internal.DnsNameResolverProvider - diff --git a/proto/channel.proto b/proto/channel.proto deleted file mode 100644 index b4e67e07..00000000 --- a/proto/channel.proto +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = 'proto2'; - -package com.android.tv.tuner.data; - -option java_package = "com.android.tv.tuner.data"; -option java_outer_classname = "Channel"; - -import "track.proto"; - -// Holds information about a channel used in the tuners. -message TunerChannelProto { - optional TunerType type = 1; - optional string short_name = 2; - optional string long_name = 3; - optional int32 frequency = 4; - optional string modulation = 5; - optional string filepath = 6; - optional int32 program_number = 7; - optional int32 virtual_major = 8; - optional int32 virtual_minor = 9; - optional int64 channel_id = 10; - optional string description = 11; - optional int32 tsid = 12; - optional int32 video_pid = 13; - optional VideoStreamType video_stream_type = 14; - optional int32 pcr_pid = 15; - repeated AtscAudioTrack audio_tracks = 16; - repeated int32 audio_pids = 17; - repeated AudioStreamType audio_stream_types = 18; - optional int32 audio_track_index = 19; - repeated AtscCaptionTrack caption_tracks = 20; - optional bool has_caption_track = 21; - optional AtscServiceType service_type = 22 [default = SERVICE_TYPE_ATSC_DIGITAL_TELEVISION]; - optional bool recording_prohibited = 23; - optional string video_format = 24; -} - -// Enum describing the types of tuner. -enum TunerType { - TYPE_TUNER = 0; - TYPE_FILE = 1; - TYPE_NETWORK = 2; -} - -// Enum describing the types of video stream. -enum VideoStreamType { - // ISO/IEC 11172 Video (MPEG-1) - MPEG1 = 0x01; - // ISO/IEC 13818-2 (MPEG-2) Video - MPEG2 = 0x02; - // ISO/IEC 14496-2 (MPEG-4 H.263 based) - H263 = 0x10; - // ISO/IE 14496-10 (H.264 video) - H264 = 0x01b; - // ISO/IE 23008-2 (H.265 video) - H265 = 0x024; -} - -// Enum describing the types of audio stream. -enum AudioStreamType { - // ISO/IEC 11172 Audio (MPEG-1) - MPEG1AUDIO = 0x03; - // ISO/IEC 13818-3 Audio (MPEG-2) - MPEG2AUDIO = 0x04; - // ISO/IEC 13818-7 Audio with ADTS transport syntax - MPEG2AACAUDIO = 0x0f; - // ISO/IEC 14496-3 (MPEG-4 LOAS multi-format framed audio) - MPEG4LATMAACAUDIO = 0x11; - // Dolby Digital Audio (ATSC) - A52AC3AUDIO = 0x81; - // Dolby Digital Plus Audio (ATSC)ISO/IEC 14496-2Video (MPEG-1) - EAC3AUDIO = 0x87; -} - -// Enum describing ATSC service types -// See ATSC Code Points Registry. -enum AtscServiceType { - SERVICE_TYPE_ATSC_RESERVED = 0x0; - SERVICE_TYPE_ANALOG_TELEVISION_CHANNELS = 0x1; - SERVICE_TYPE_ATSC_DIGITAL_TELEVISION = 0x2; - SERVICE_TYPE_ATSC_AUDIO = 0x3; - SERVICE_TYPE_ATSC_DATA_ONLY_SERVICE = 0x4; - SERVICE_TYPE_SOFTWARE_DOWNLOAD = 0x5; - SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE = 0x6; - SERVICE_TYPE_PARAMETERIZED_SERVICE = 0x7; - SERVICE_TYPE_ATSC_NRT_SERVICE = 0x8; - SERVICE_TYPE_EXTENDED_PARAMERTERIZED_SERVICE = 0x9; -} diff --git a/proto/track.proto b/proto/track.proto deleted file mode 100644 index fe60fed5..00000000 --- a/proto/track.proto +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto2"; - -package com.android.tv.tuner.data; - -option java_package = "com.android.tv.tuner.data"; -option java_outer_classname = "Track"; - -// Represents a AC3 audio track. -message AtscAudioTrack { - optional string language = 1; - optional AudioType audio_type = 2; - optional int32 index = 3; - optional int32 channel_count = 4; - optional int32 sample_rate = 5; - - // Enum describing the types of a audio track. - // See ISO/IEC 138181-1:2000(e) Table 2-53. - enum AudioType { - AUDIOTYPE_UNDEFINED = 0; - AUDIOTYPE_CLEAN_EFFECTS = 1; - AUDIOTYPE_HEARING_IMPAIRED = 2; - AUDIOTYPE_VISUAL_IMPAIRED = 3; - } -} - -// Represents a CEA-708 caption track. -message AtscCaptionTrack { - optional string language = 1; - optional int32 service_number = 2; - optional bool easy_reader = 3; - optional bool wide_aspect_ratio = 4; -} - diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 7f4eb10d..00000000 Binary files a/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_live_channels.png b/res/drawable-xhdpi/ic_live_channels.png new file mode 100644 index 00000000..bb1c2d9d Binary files /dev/null and b/res/drawable-xhdpi/ic_live_channels.png differ diff --git a/res/drawable-xhdpi/ic_live_channels_96x96.png b/res/drawable-xhdpi/ic_live_channels_96x96.png new file mode 100644 index 00000000..7f4eb10d Binary files /dev/null and b/res/drawable-xhdpi/ic_live_channels_96x96.png differ diff --git a/res/layout/details_overview.xml b/res/layout/details_overview.xml new file mode 100644 index 00000000..dbcf2055 --- /dev/null +++ b/res/layout/details_overview.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/dvr_recording_card_view.xml b/res/layout/dvr_recording_card_view.xml index 53a7cf3d..3e953510 100644 --- a/res/layout/dvr_recording_card_view.xml +++ b/res/layout/dvr_recording_card_view.xml @@ -15,7 +15,7 @@ --> diff --git a/res/layout/guided_action_editable.xml b/res/layout/guided_action_editable.xml deleted file mode 100644 index 84f56f8d..00000000 --- a/res/layout/guided_action_editable.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/lb_details_overview.xml b/res/layout/lb_details_overview.xml deleted file mode 100644 index dbcf2055..00000000 --- a/res/layout/lb_details_overview.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index a6c8735b..e57b0546 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -65,7 +65,7 @@ Browse content from your apps with a familiar guide and friendly interface, \njust like channels on TV. Add more channels by installing apps that offer live channels. -\nFind compatible apps in Google Play Store by using the link within the TV menu. +\nFind compatible apps in the online store by using the link within the TV menu. Set up your newly installed channel sources to customize your channel list. diff --git a/res/values/strings.xml b/res/values/strings.xml index 57049aba..bdc5811e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -129,7 +129,7 @@ Unknown language - Closed captions %1$d + Closed captions %1$d @@ -162,7 +162,7 @@ 7.1 surround - %d channels + %d channels @@ -330,15 +330,6 @@ Try again, PIN doesn\'t match - - Enter your ZIP Code. - - Live TV app will use the ZIP Code to provide a complete program guide for the TV channels. - - Enter your ZIP Code - - Invalid ZIP Code - @@ -522,6 +513,13 @@ Live TV are updating recording schedules. + + Install %s + + Tuner Package + + Install tuner package to watch tuner channels. + No result @@ -533,7 +531,7 @@ Set up your sources Live channels combines the experience of traditional TV channels with streaming channels provided by apps. -\n\nGet started by setting up the channel sources already installed. Or browse Google Play Store for more apps that offer live channels. +\n\nGet started by setting up the channel sources already installed. Or browse the online store for more apps that offer live channels. Recordings & schedules @@ -754,7 +752,7 @@ DVR needs more storage - You will be able to record programs with DVR. However there is not enough storage on your device now for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage. + You will be able to record programs with DVR. However there is not enough storage on your device now for DVR to work. Please connect an external drive that is %1$dGB or larger and follow the steps to format it as device storage. Not enough storage diff --git a/res/xml/remote_config_defaults.xml b/res/xml/remote_config_defaults.xml deleted file mode 100644 index 6ecad191..00000000 --- a/res/xml/remote_config_defaults.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - live_channels_ac3_software_decoder - false - - - - live_channels_epg_host_and_port - datamixer-pa.googleapis.com:443 - - - - live_channels_epg_fetcher_interval_hour - 4 - - - - live_channels_epg_reader_max_channels_per_program_fetch - 20 - - \ No newline at end of file diff --git a/src/com/android/exoplayer/MediaFormatUtil.java b/src/com/android/exoplayer/MediaFormatUtil.java deleted file mode 100644 index 151c6dd5..00000000 --- a/src/com/android/exoplayer/MediaFormatUtil.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer; - -import android.support.annotation.Nullable; -import com.google.android.exoplayer.util.MimeTypes; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/** {@link MediaFormat} creation helper util */ -public class MediaFormatUtil { - - /** - * Creates {@link MediaFormat} from {@link android.media.MediaFormat}. Since {@link - * com.google.android.exoplayer.TrackRenderer} uses {@link MediaFormat}, {@link - * android.media.MediaFormat} should be converted to be used with ExoPlayer. - */ - public static MediaFormat createMediaFormat(android.media.MediaFormat format) { - String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); - String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE); - int maxInputSize = - getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE); - int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH); - int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); - int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees"); - int channelCount = - getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); - int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); - int encoderDelay = getOptionalIntegerV16(format, "encoder-delay"); - int encoderPadding = getOptionalIntegerV16(format, "encoder-padding"); - ArrayList initializationData = new ArrayList<>(); - for (int i = 0; format.containsKey("csd-" + i); i++) { - ByteBuffer buffer = format.getByteBuffer("csd-" + i); - byte[] data = new byte[buffer.limit()]; - buffer.get(data); - initializationData.add(data); - buffer.flip(); - } - long durationUs = - format.containsKey(android.media.MediaFormat.KEY_DURATION) - ? format.getLong(android.media.MediaFormat.KEY_DURATION) - : C.UNKNOWN_TIME_US; - int pcmEncoding = - MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : MediaFormat.NO_VALUE; - MediaFormat mediaFormat = - new MediaFormat( - null, - mimeType, - MediaFormat.NO_VALUE, - maxInputSize, - durationUs, - width, - height, - rotationDegrees, - MediaFormat.NO_VALUE, - channelCount, - sampleRate, - language, - MediaFormat.OFFSET_SAMPLE_RELATIVE, - initializationData, - false, - MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, - pcmEncoding, - encoderDelay, - encoderPadding, - null, - MediaFormat.NO_VALUE); - mediaFormat.setFrameworkFormatV16(format); - return mediaFormat; - } - - @Nullable - private static String getOptionalStringV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getString(key) : null; - } - - private static int getOptionalIntegerV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE; - } -} diff --git a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java b/src/com/android/exoplayer/MediaSoftwareCodecUtil.java deleted file mode 100644 index cf74f106..00000000 --- a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer; - -import android.annotation.TargetApi; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import com.google.android.exoplayer.util.MimeTypes; -import java.util.HashMap; - -/** - * Mostly copied from {@link com.google.android.exoplayer.MediaCodecUtil} in order to choose - * software codec over hardware codec. - */ -public class MediaSoftwareCodecUtil { - private static final String TAG = "MediaSoftwareCodecUtil"; - - /** - * Thrown when an error occurs querying the device for its underlying media capabilities. - * - *

Such failures are not expected in normal operation and are normally temporary (e.g. if the - * mediaserver process has crashed and is yet to restart). - */ - public static class DecoderQueryException extends Exception { - - private DecoderQueryException(Throwable cause) { - super("Failed to query underlying media codecs", cause); - } - } - - private static final HashMap> - sSwCodecs = new HashMap<>(); - - /** Gets information about the software decoder that will be used for a given mime type. */ - public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure) - throws DecoderQueryException { - // TODO: Add a test for this method. - Pair info = - getMediaSoftwareCodecInfo(mimeType, secure); - if (info == null) { - return null; - } - return new DecoderInfo(info.first, info.second); - } - - /** Returns the name of the software decoder and its capabilities for the given mimeType. */ - private static synchronized Pair - getMediaSoftwareCodecInfo(String mimeType, boolean secure) - throws DecoderQueryException { - CodecKey key = new CodecKey(mimeType, secure); - if (sSwCodecs.containsKey(key)) { - return sSwCodecs.get(key); - } - MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure); - Pair codecInfo = - getMediaSoftwareCodecInfo(key, mediaCodecList); - if (secure && codecInfo == null) { - // Some devices don't list secure decoders on API level 21. Try the legacy path. - mediaCodecList = new MediaCodecListCompatV16(); - codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); - if (codecInfo != null) { - Log.w( - TAG, - "MediaCodecList API didn't list secure decoder for: " - + mimeType - + ". Assuming: " - + codecInfo.first); - } - } - return codecInfo; - } - - private static Pair getMediaSoftwareCodecInfo( - CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { - try { - return getMediaSoftwareCodecInfoInternal(key, mediaCodecList); - } catch (Exception e) { - // If the underlying mediaserver is in a bad state, we may catch an - // IllegalStateException or an IllegalArgumentException here. - throw new DecoderQueryException(e); - } - } - - private static Pair getMediaSoftwareCodecInfoInternal( - CodecKey key, MediaCodecListCompat mediaCodecList) { - String mimeType = key.mimeType; - int numberOfCodecs = mediaCodecList.getCodecCount(); - boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); - // Note: MediaCodecList is sorted by the framework such that the best decoders come first. - for (int i = 0; i < numberOfCodecs; i++) { - MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); - String codecName = info.getName(); - if (!info.isEncoder() - && codecName.startsWith("OMX.google.") - && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { - String[] supportedTypes = info.getSupportedTypes(); - for (String supportedType : supportedTypes) { - if (supportedType.equalsIgnoreCase(mimeType)) { - MediaCodecInfo.CodecCapabilities capabilities = - info.getCapabilitiesForType(supportedType); - boolean secure = - mediaCodecList.isSecurePlaybackSupported( - key.mimeType, capabilities); - if (!secureDecodersExplicit) { - // Cache variants for both insecure and (if we think it's supported) - // secure playback. - sSwCodecs.put( - key.secure ? new CodecKey(mimeType, false) : key, - Pair.create(codecName, capabilities)); - if (secure) { - sSwCodecs.put( - key.secure ? key : new CodecKey(mimeType, true), - Pair.create(codecName + ".secure", capabilities)); - } - } else { - // Only cache this variant. If both insecure and secure decoders are - // available, they should both be listed separately. - sSwCodecs.put( - key.secure == secure ? key : new CodecKey(mimeType, secure), - Pair.create(codecName, capabilities)); - } - if (sSwCodecs.containsKey(key)) { - return sSwCodecs.get(key); - } - } - } - } - } - sSwCodecs.put(key, null); - return null; - } - - private interface MediaCodecListCompat { - - /** Returns the number of codecs in the list. */ - int getCodecCount(); - - /** - * Returns the info at the specified index in the list. - * - * @param index The index. - */ - MediaCodecInfo getCodecInfoAt(int index); - - /** Returns whether secure decoders are explicitly listed, if present. */ - boolean secureDecodersExplicit(); - - /** - * Returns true if secure playback is supported for the given {@link - * android.media.MediaCodecInfo.CodecCapabilities}, which should have been obtained from a - * {@link MediaCodecInfo} obtained from this list. - */ - boolean isSecurePlaybackSupported( - String mimeType, MediaCodecInfo.CodecCapabilities capabilities); - } - - @TargetApi(21) - private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { - - private final int codecKind; - - private MediaCodecInfo[] mediaCodecInfos; - - public MediaCodecListCompatV21(boolean includeSecure) { - codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; - } - - @Override - public int getCodecCount() { - ensureMediaCodecInfosInitialized(); - return mediaCodecInfos.length; - } - - @Override - public MediaCodecInfo getCodecInfoAt(int index) { - ensureMediaCodecInfosInitialized(); - return mediaCodecInfos[index]; - } - - @Override - public boolean secureDecodersExplicit() { - return true; - } - - @Override - public boolean isSecurePlaybackSupported( - String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { - return capabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); - } - - private void ensureMediaCodecInfosInitialized() { - if (mediaCodecInfos == null) { - mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); - } - } - } - - @SuppressWarnings("deprecation") - private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { - - @Override - public int getCodecCount() { - return MediaCodecList.getCodecCount(); - } - - @Override - public MediaCodecInfo getCodecInfoAt(int index) { - return MediaCodecList.getCodecInfoAt(index); - } - - @Override - public boolean secureDecodersExplicit() { - return false; - } - - @Override - public boolean isSecurePlaybackSupported( - String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { - // Secure decoders weren't explicitly listed prior to API level 21. We assume that - // a secure H264 decoder exists. - return MimeTypes.VIDEO_H264.equals(mimeType); - } - } - - private static final class CodecKey { - - public final String mimeType; - public final boolean secure; - - public CodecKey(String mimeType, boolean secure) { - this.mimeType = mimeType; - this.secure = secure; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); - result = 2 * result + (secure ? 0 : 1); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof CodecKey)) { - return false; - } - CodecKey other = (CodecKey) obj; - return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; - } - } -} diff --git a/src/com/android/exoplayer/text/SubtitleView.java b/src/com/android/exoplayer/text/SubtitleView.java deleted file mode 100644 index e930ef2d..00000000 --- a/src/com/android/exoplayer/text/SubtitleView.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Join; -import android.graphics.Paint.Style; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.text.Layout.Alignment; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.View; -import com.google.android.exoplayer.util.Util; -import java.util.ArrayList; - -/** - * Since this class does not exist in recent version of ExoPlayer and used by {@link - * com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from older version of - * ExoPlayer. A view for rendering a single caption. - */ -@Deprecated -public class SubtitleView extends View { - /** Ratio of inner padding to font size. */ - private static final float INNER_PADDING_RATIO = 0.125f; - - /** Temporary rectangle used for computing line bounds. */ - private final RectF mLineBounds = new RectF(); - - // Styled dimensions. - private final float mCornerRadius; - private final float mOutlineWidth; - private final float mShadowRadius; - private final float mShadowOffset; - - private final TextPaint mTextPaint; - private final Paint mPaint; - - private CharSequence mText; - - private int mForegroundColor; - private int mBackgroundColor; - private int mEdgeColor; - private int mEdgeType; - - private boolean mHasMeasurements; - private int mLastMeasuredWidth; - private StaticLayout mLayout; - - private Alignment mAlignment; - private final float mSpacingMult; - private final float mSpacingAdd; - private int mInnerPaddingX; - private float mWhiteSpaceWidth; - private ArrayList mPrefixSpaces = new ArrayList<>(); - - public SubtitleView(Context context) { - this(context, null); - } - - public SubtitleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - int[] viewAttr = { - android.R.attr.text, - android.R.attr.textSize, - android.R.attr.lineSpacingExtra, - android.R.attr.lineSpacingMultiplier - }; - TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0); - CharSequence text = a.getText(0); - int textSize = a.getDimensionPixelSize(1, 15); - mSpacingAdd = a.getDimensionPixelSize(2, 0); - mSpacingMult = a.getFloat(3, 1); - a.recycle(); - - Resources resources = getContext().getResources(); - DisplayMetrics displayMetrics = resources.getDisplayMetrics(); - int twoDpInPx = - Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); - mCornerRadius = twoDpInPx; - mOutlineWidth = twoDpInPx; - mShadowRadius = twoDpInPx; - mShadowOffset = twoDpInPx; - - mTextPaint = new TextPaint(); - mTextPaint.setAntiAlias(true); - mTextPaint.setSubpixelText(true); - - mAlignment = Alignment.ALIGN_CENTER; - - mPaint = new Paint(); - mPaint.setAntiAlias(true); - - mInnerPaddingX = 0; - setText(text); - setTextSize(textSize); - setStyle(CaptionStyleCompat.DEFAULT); - } - - @Override - public void setBackgroundColor(int color) { - mBackgroundColor = color; - forceUpdate(false); - } - - /** - * Sets the text to be displayed by the view. - * - * @param text The text to display. - */ - public void setText(CharSequence text) { - this.mText = text; - forceUpdate(true); - } - - /** - * Sets the text size in pixels. - * - * @param size The text size in pixels. - */ - public void setTextSize(float size) { - if (mTextPaint.getTextSize() != size) { - mTextPaint.setTextSize(size); - mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); - mWhiteSpaceWidth -= mInnerPaddingX * 2; - forceUpdate(true); - } - } - - /** - * Sets the text alignment. - * - * @param textAlignment The text alignment. - */ - public void setTextAlignment(Alignment textAlignment) { - mAlignment = textAlignment; - } - - /** - * Configures the view according to the given style. - * - * @param style A style for the view. - */ - public void setStyle(CaptionStyleCompat style) { - mForegroundColor = style.foregroundColor; - mBackgroundColor = style.backgroundColor; - mEdgeType = style.edgeType; - mEdgeColor = style.edgeColor; - setTypeface(style.typeface); - super.setBackgroundColor(style.windowColor); - forceUpdate(true); - } - - public void setPrefixSpaces(ArrayList prefixSpaces) { - mPrefixSpaces = prefixSpaces; - } - - public void setWhiteSpaceWidth(float whiteSpaceWidth) { - mWhiteSpaceWidth = whiteSpaceWidth; - } - - private void setTypeface(Typeface typeface) { - if (mTextPaint.getTypeface() != typeface) { - mTextPaint.setTypeface(typeface); - forceUpdate(true); - } - } - - private void forceUpdate(boolean needsLayout) { - if (needsLayout) { - mHasMeasurements = false; - requestLayout(); - } - invalidate(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int widthSpec = MeasureSpec.getSize(widthMeasureSpec); - - if (computeMeasurements(widthSpec)) { - final StaticLayout layout = this.mLayout; - final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; - final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom(); - int width = 0; - int lineCount = layout.getLineCount(); - for (int i = 0; i < lineCount; i++) { - width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width); - } - width += paddingX; - setMeasuredDimension(width, height); - } else if (Util.SDK_INT >= 11) { - setTooSmallMeasureDimensionV11(); - } else { - setMeasuredDimension(0, 0); - } - } - - @TargetApi(11) - private void setTooSmallMeasureDimensionV11() { - setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); - } - - @Override - public void onLayout(boolean changed, int l, int t, int r, int b) { - final int width = r - l; - computeMeasurements(width); - } - - private boolean computeMeasurements(int maxWidth) { - if (mHasMeasurements && maxWidth == mLastMeasuredWidth) { - return true; - } - - // Account for padding. - final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; - maxWidth -= paddingX; - if (maxWidth <= 0) { - return false; - } - - mHasMeasurements = true; - mLastMeasuredWidth = maxWidth; - mLayout = - new StaticLayout( - mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true); - return true; - } - - @Override - protected void onDraw(Canvas c) { - final StaticLayout layout = this.mLayout; - if (layout == null) { - return; - } - - final int saveCount = c.save(); - final int innerPaddingX = this.mInnerPaddingX; - c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop()); - - final int lineCount = layout.getLineCount(); - final Paint textPaint = this.mTextPaint; - final Paint paint = this.mPaint; - final RectF bounds = mLineBounds; - - if (Color.alpha(mBackgroundColor) > 0) { - final float cornerRadius = this.mCornerRadius; - float previousBottom = layout.getLineTop(0); - - paint.setColor(mBackgroundColor); - paint.setStyle(Style.FILL); - - for (int i = 0; i < lineCount; i++) { - float spacesPadding = 0.0f; - if (i < mPrefixSpaces.size()) { - spacesPadding += mPrefixSpaces.get(i) * mWhiteSpaceWidth; - } - bounds.left = layout.getLineLeft(i) - innerPaddingX + spacesPadding; - bounds.right = layout.getLineRight(i) + innerPaddingX; - bounds.top = previousBottom; - bounds.bottom = layout.getLineBottom(i); - previousBottom = bounds.bottom; - - c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); - } - } - - if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { - textPaint.setStrokeJoin(Join.ROUND); - textPaint.setStrokeWidth(mOutlineWidth); - textPaint.setColor(mEdgeColor); - textPaint.setStyle(Style.FILL_AND_STROKE); - layout.draw(c); - } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { - textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor); - } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED - || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { - boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; - int colorUp = raised ? Color.WHITE : mEdgeColor; - int colorDown = raised ? mEdgeColor : Color.WHITE; - float offset = mShadowRadius / 2f; - textPaint.setColor(mForegroundColor); - textPaint.setStyle(Style.FILL); - textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); - layout.draw(c); - textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); - } - - textPaint.setColor(mForegroundColor); - textPaint.setStyle(Style.FILL); - layout.draw(c); - textPaint.setShadowLayer(0, 0, 0, 0); - c.restoreToCount(saveCount); - } -} diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java deleted file mode 100644 index 321e19da..00000000 --- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ext.ffmpeg; - -import android.content.Context; -import android.content.pm.PackageManager; -import com.android.tv.common.SoftPreconditions; -import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; -import com.google.android.exoplayer2.util.MimeTypes; -import java.nio.ByteBuffer; - -/** - * Audio decoder which uses ffmpeg extension of ExoPlayer2. Since {@link FfmpegDecoder} is package - * private, expose the decoder via this class. Supported formats are AC3 and MP2. - */ -public class FfmpegAudioDecoder { - private static final int NUM_DECODER_BUFFERS = 1; - - // The largest AC3 sample size. This is bigger than the largest MP2 sample size (1729). - private static final int INITIAL_INPUT_BUFFER_SIZE = 2560; - private static boolean AVAILABLE; - - static { - AVAILABLE = - FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_AC3) - && FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_MPEG_L2); - } - - private FfmpegDecoder mDecoder; - private DecoderInputBuffer mInputBuffer; - private SimpleOutputBuffer mOutputBuffer; - private boolean mStarted; - - /** Return whether Ffmpeg based software audio decoder is available. */ - public static boolean isAvailable() { - return AVAILABLE; - } - - /** Creates an Ffmpeg based software audio decoder. */ - public FfmpegAudioDecoder(Context context) { - if (context.checkSelfPermission("android.permission.INTERNET") - == PackageManager.PERMISSION_GRANTED) { - throw new IllegalStateException("This code should run in an isolated process"); - } - } - - /** - * Decodes an audio sample. - * - * @param timeUs presentation timestamp of the sample - * @param sample data - */ - public void decode(long timeUs, byte[] sample) { - SoftPreconditions.checkState(AVAILABLE); - mInputBuffer.data.clear(); - mInputBuffer.data.put(sample); - mInputBuffer.data.flip(); - mInputBuffer.timeUs = timeUs; - mDecoder.decode(mInputBuffer, mOutputBuffer, !mStarted); - if (!mStarted) { - mStarted = true; - } - } - - /** Returns a decoded sample from decoder. */ - public ByteBuffer getDecodedSample() { - return mOutputBuffer.data; - } - - /** Returns the presentation time for the decoded sample. */ - public long getDecodedTimeUs() { - return mOutputBuffer.timeUs; - } - - /** - * Clear previous decode state if any. Prepares to decode samples of the specified encoding. - * This method should be called before using decode. - * - * @param mime audio encoding - */ - public void resetDecoderState(String mime) { - SoftPreconditions.checkState(AVAILABLE); - release(); - try { - mDecoder = - new FfmpegDecoder( - NUM_DECODER_BUFFERS, - NUM_DECODER_BUFFERS, - INITIAL_INPUT_BUFFER_SIZE, - mime, - null); - mStarted = false; - mInputBuffer = mDecoder.createInputBuffer(); - // Since native JNI requires direct buffer, we should allocate it by #allocateDirect. - mInputBuffer.data = ByteBuffer.allocateDirect(INITIAL_INPUT_BUFFER_SIZE); - mOutputBuffer = mDecoder.createOutputBuffer(); - } catch (FfmpegDecoderException e) { - // if AVAILABLE is {@code true}, this will not happen. - } - } - - /** Releases all the resource. */ - public void release() { - SoftPreconditions.checkState(AVAILABLE); - if (mDecoder != null) { - mDecoder.release(); - mInputBuffer = null; - mOutputBuffer = null; - mDecoder = null; - } - } -} diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java deleted file mode 100644 index a33d4020..00000000 --- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ext.ffmpeg; - -import com.google.android.exoplayer2.util.LibraryLoader; -import com.google.android.exoplayer2.util.MimeTypes; - -/** - * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2 in - * order to support mp2 decoder. Configures and queries the underlying native library. - */ -public final class FfmpegLibrary { - - private static final LibraryLoader LOADER = - new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg"); - - private FfmpegLibrary() {} - - /** - * Overrides the names of the FFmpeg native libraries. If an application wishes to call this - * method, it must do so before calling any other method defined by this class, and before - * instantiating a {@link FfmpegAudioRenderer} instance. - */ - public static void setLibraries(String... libraries) { - LOADER.setLibraries(libraries); - } - - /** Returns whether the underlying library is available, loading it if necessary. */ - public static boolean isAvailable() { - return LOADER.isAvailable(); - } - - /** Returns the version of the underlying library if available, or null otherwise. */ - public static String getVersion() { - return isAvailable() ? ffmpegGetVersion() : null; - } - - /** Returns whether the underlying library supports the specified MIME type. */ - public static boolean supportsFormat(String mimeType) { - if (!isAvailable()) { - return false; - } - String codecName = getCodecName(mimeType); - return codecName != null && ffmpegHasDecoder(codecName); - } - - /** Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. */ - /* package */ static String getCodecName(String mimeType) { - switch (mimeType) { - case MimeTypes.AUDIO_MPEG_L2: - return "mp2"; - case MimeTypes.AUDIO_AC3: - return "ac3"; - default: - return null; - } - } - - private static native String ffmpegGetVersion(); - - private static native boolean ffmpegHasDecoder(String codecName); -} diff --git a/src/com/android/tv/ApplicationSingletons.java b/src/com/android/tv/ApplicationSingletons.java deleted file mode 100644 index f9eaf58e..00000000 --- a/src/com/android/tv/ApplicationSingletons.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv; - -import com.android.tv.analytics.Analytics; -import com.android.tv.analytics.Tracker; -import com.android.tv.config.RemoteConfig; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.PreviewDataManager; -import com.android.tv.data.ProgramDataManager; -import com.android.tv.dvr.DvrDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrScheduleManager; -import com.android.tv.dvr.DvrStorageStatusManager; -import com.android.tv.dvr.DvrWatchedPositionManager; -import com.android.tv.dvr.recorder.RecordingScheduler; -import com.android.tv.perf.PerformanceMonitor; -import com.android.tv.util.AccountHelper; -import com.android.tv.util.TvInputManagerHelper; - -/** Interface with getters for application scoped singletons. */ -public interface ApplicationSingletons { - - Analytics getAnalytics(); - - ChannelDataManager getChannelDataManager(); - - /** - * Checks if the {@link ChannelDataManager} instance has been created and all the channels has - * been loaded. - */ - boolean isChannelDataManagerLoadFinished(); - - ProgramDataManager getProgramDataManager(); - - /** - * Checks if the {@link ProgramDataManager} instance has been created and the current programs - * for all the channels has been loaded. - */ - boolean isProgramDataManagerCurrentProgramsLoadFinished(); - - PreviewDataManager getPreviewDataManager(); - - DvrDataManager getDvrDataManager(); - - DvrStorageStatusManager getDvrStorageStatusManager(); - - DvrScheduleManager getDvrScheduleManager(); - - DvrManager getDvrManager(); - - RecordingScheduler getRecordingScheduler(); - - DvrWatchedPositionManager getDvrWatchedPositionManager(); - - InputSessionManager getInputSessionManager(); - - Tracker getTracker(); - - TvInputManagerHelper getTvInputManagerHelper(); - - MainActivityWrapper getMainActivityWrapper(); - - AccountHelper getAccountHelper(); - - RemoteConfig getRemoteConfig(); - - boolean isRunningInMainProcess(); - - PerformanceMonitor getPerformanceMonitor(); -} diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java index f4bfdb9a..b0187617 100644 --- a/src/com/android/tv/AudioManagerHelper.java +++ b/src/com/android/tv/AudioManagerHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv; import android.app.Activity; @@ -46,20 +61,33 @@ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { if (mTvView.isPlaying()) { switch (mAudioFocusStatus) { case AudioManager.AUDIOFOCUS_GAIN: - mTvView.setStreamVolume(AUDIO_MAX_VOLUME); + if (mTvView.isTimeShiftAvailable()) { + mTvView.timeshiftPlay(); + } else { + mTvView.setStreamVolume(AUDIO_MAX_VOLUME); + } break; case AudioManager.AUDIOFOCUS_LOSS: - if (Features.PICTURE_IN_PICTURE.isEnabled(mActivity) + if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mActivity.isInPictureInPictureMode()) { mActivity.finish(); break; } + // fall through case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - mTvView.setStreamVolume(AUDIO_MIN_VOLUME); + if (mTvView.isTimeShiftAvailable()) { + mTvView.timeshiftPause(); + } else { + mTvView.setStreamVolume(AUDIO_MIN_VOLUME); + } break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME); + if (mTvView.isTimeShiftAvailable()) { + mTvView.timeshiftPause(); + } else { + mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME); + } break; } } diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java deleted file mode 100644 index 3f8e56a8..00000000 --- a/src/com/android/tv/Features.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv; - -import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; -import static com.android.tv.common.feature.FeatureUtils.AND; -import static com.android.tv.common.feature.FeatureUtils.OFF; -import static com.android.tv.common.feature.FeatureUtils.ON; -import static com.android.tv.common.feature.FeatureUtils.OR; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.support.annotation.VisibleForTesting; -import android.util.Log; -import com.android.tv.common.feature.Feature; -import com.android.tv.common.feature.GServiceFeature; -import com.android.tv.common.feature.PropertyFeature; -import com.android.tv.experiments.Experiments; -import com.android.tv.util.LocationUtils; -import com.android.tv.util.PermissionUtils; -import com.android.tv.util.Utils; -import java.util.Locale; - -/** - * List of {@link Feature} for the Live TV App. - * - *

Remove the {@code Feature} once it is launched. - */ -public final class Features { - private static final String TAG = "Features"; - private static final boolean DEBUG = false; - - /** - * UI for opting in to analytics. - * - *

Do not turn this on until the splash screen asking existing users to opt-in is launched. - * See b/20228119 - */ - public static final Feature ANALYTICS_OPT_IN = ENG_ONLY_FEATURE; - - /** - * Analytics that include sensitive information such as channel or program identifiers. - * - *

See b/22062676 - */ - public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN); - - public static final Feature EPG_SEARCH = - new PropertyFeature("feature_tv_use_epg_search", false); - - public static final Feature TUNER = - new Feature() { - @Override - public boolean isEnabled(Context context) { - - if (Utils.isDeveloper()) { - // we enable tuner for developers to test tuner in any platform. - return true; - } - - // This is special handling just for USB Tuner. - // It does not require any N API's but relies on a improvements in N for AC3 - // support - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; - } - }; - - /** Use network tuner if it is available and there is no other tuner types. */ - public static final Feature NETWORK_TUNER = - new Feature() { - @Override - public boolean isEnabled(Context context) { - if (!TUNER.isEnabled(context)) { - return false; - } - if (Utils.isDeveloper()) { - // Network tuner will be enabled for developers. - return true; - } - return Locale.US - .getCountry() - .equalsIgnoreCase(LocationUtils.getCurrentCountry(context)); - } - }; - - private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide"; - /** A flag which indicates that LC app is unhidden even when there is no input. */ - public static final Feature UNHIDE = - OR( - new GServiceFeature(GSERVICE_KEY_UNHIDE, false), - new Feature() { - @Override - public boolean isEnabled(Context context) { - // If LC app runs as non-system app, we unhide the app. - return !PermissionUtils.hasAccessAllEpg(context); - } - }); - - public static final Feature PICTURE_IN_PICTURE = - new Feature() { - private Boolean mEnabled; - - @Override - public boolean isEnabled(Context context) { - if (mEnabled == null) { - mEnabled = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - && context.getPackageManager() - .hasSystemFeature( - PackageManager.FEATURE_PICTURE_IN_PICTURE); - } - return mEnabled; - } - }; - - /** Use AC3 software decode. */ - public static final Feature AC3_SOFTWARE_DECODE = - new Feature() { - private final String[] SUPPORTED_REGIONS = {}; - - private Boolean mEnabled; - - @Override - public boolean isEnabled(Context context) { - if (mEnabled == null) { - if (mEnabled == null) { - // We will not cache the result of fallback solution. - String country = LocationUtils.getCurrentCountry(context); - for (int i = 0; i < SUPPORTED_REGIONS.length; ++i) { - if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) { - return true; - } - } - if (DEBUG) Log.d(TAG, "AC3 flag false after country check"); - return false; - } - } - if (DEBUG) Log.d(TAG, "AC3 flag " + mEnabled); - return mEnabled; - } - }; - - /** Show postal code fragment before channel scan. */ - public static final Feature ENABLE_CLOUD_EPG_REGION = - new Feature() { - private final String[] SUPPORTED_REGIONS = {}; - - @Override - public boolean isEnabled(Context context) { - if (!Experiments.CLOUD_EPG.get()) { - if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false"); - return false; - } - String country = LocationUtils.getCurrentCountry(context); - for (int i = 0; i < SUPPORTED_REGIONS.length; i++) { - if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) { - return true; - } - } - if (DEBUG) Log.d(TAG, "EPG flag false after country check"); - return false; - } - }; - - /** Enable a conflict dialog between currently watched channel and upcoming recording. */ - public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF; - - /** Use input blacklist to disable partner's tuner input. */ - public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON; - - /** Enable Dvb parsers and listeners. */ - public static final Feature ENABLE_FILE_DVB = OFF; - - @VisibleForTesting - public static final Feature TEST_FEATURE = new PropertyFeature("test_feature", false); - - private Features() {} -} diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java index 709ed4a4..416dbb68 100644 --- a/src/com/android/tv/InputSessionManager.java +++ b/src/com/android/tv/InputSessionManager.java @@ -76,7 +76,7 @@ public class InputSessionManager { public InputSessionManager(Context context) { mContext = context.getApplicationContext(); - mInputManager = TvApplication.getSingletons(context).getTvInputManagerHelper(); + mInputManager = TvSingletons.getSingletons(context).getTvInputManagerHelper(); } /** diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java index 545d49b1..3aca35a4 100644 --- a/src/com/android/tv/LauncherActivity.java +++ b/src/com/android/tv/LauncherActivity.java @@ -27,15 +27,16 @@ import android.util.Log; * An activity to launch a new activity. * *

In the case when {@link MainActivity} starts a new activity using {@link - * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is terminated if - * the new activity crashes. That's because the {@link android.app.ActivityManager} terminates the - * activity which is just below the crashed activity in the activity stack. To avoid this, we need - * to locate an additional activity between these activities in the activity stack. + * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is + * terminated if the new activity crashes. That's because the {@link android.app.ActivityManager} + * terminates the activity which is just below the crashed activity in the activity stack. To avoid + * this, we need to locate an additional activity between these activities in the activity stack. */ public class LauncherActivity extends Activity { private static final String TAG = "LauncherActivity"; - public static final String ERROR_MESSAGE = "com.android.tv.LauncherActivity.ErrorMessage"; + public static final String ERROR_MESSAGE = + "com.android.tv.LauncherActivity.ErrorMessage"; private static final int REQUEST_CODE_DEFAULT = 0; private static final int REQUEST_START_ACTIVITY = 100; @@ -52,22 +53,6 @@ public class LauncherActivity extends Activity { createIntent(baseActivity, intentToLaunch, false), REQUEST_CODE_DEFAULT); } - /** - * Starts an activity by calling {@link Activity#startActivityForResult}. - * - *

Note: {@code requestCode} should not be 0. The value is reserved for internal use. - */ - public static void startActivityForResultSafe( - Activity baseActivity, Intent intentToLaunch, int requestCode) { - if (requestCode == REQUEST_CODE_DEFAULT) { - throw new IllegalArgumentException("requestCode should not be 0."); - } - // To avoid the app termination when the new activity crashes, LauncherActivity should be - // started by calling startActivityForResult(). - baseActivity.startActivityForResult( - createIntent(baseActivity, intentToLaunch, true), requestCode); - } - private static Intent createIntent( Context context, Intent intentToLaunch, boolean requestResult) { Intent intent = new Intent(context, LauncherActivity.class); diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index 427d562a..17adeb7d 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -17,6 +17,7 @@ package com.android.tv; import android.app.Activity; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -66,13 +67,18 @@ import com.android.tv.analytics.SendChannelStatusRunnable; import com.android.tv.analytics.SendConfigInfoRunnable; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; +import com.android.tv.common.CommonPreferences; import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonUtils; import com.android.tv.common.TvContentRatingCache; import com.android.tv.common.WeakHandler; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.ui.setup.OnActionClickListener; +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.common.util.SystemProperties; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.OnCurrentProgramUpdatedListener; @@ -80,7 +86,6 @@ import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; -import com.android.tv.data.epg.EpgFetcher; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; @@ -96,15 +101,10 @@ import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.perf.EventNames; import com.android.tv.perf.PerformanceMonitor; -import com.android.tv.perf.StubPerformanceMonitor; import com.android.tv.perf.TimerEvent; import com.android.tv.recommendation.ChannelPreviewUpdater; import com.android.tv.recommendation.NotificationService; import com.android.tv.search.ProgramGuideSearchFragment; -import com.android.tv.tuner.TunerInputController; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.setup.TunerSetupActivity; -import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.ui.ChannelBannerView; import com.android.tv.ui.InputBannerView; import com.android.tv.ui.KeypadChannelSwitchView; @@ -124,19 +124,16 @@ import com.android.tv.ui.sidepanel.SettingsFragment; import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment; import com.android.tv.util.CaptionSettings; -import com.android.tv.util.Debug; -import com.android.tv.util.DurationTimer; import com.android.tv.util.ImageCache; import com.android.tv.util.OnboardingUtils; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.RecurringRunner; import com.android.tv.util.SetupUtils; -import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.TvSettings; import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; import com.android.tv.util.ViewCache; +import com.android.tv.util.account.AccountHelper; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -148,6 +145,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** The main activity for the Live TV app. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener { private static final String TAG = "MainActivity"; private static final boolean DEBUG = false; @@ -176,6 +174,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // Tracker screen names. public static final String SCREEN_NAME = "Main"; + private static final String SCREEN_PIP = "PIP"; private static final String SCREEN_BEHIND_NAME = "Behind"; private static final float REFRESH_RATE_EPSILON = 0.01f; @@ -205,6 +204,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1; + private static final int REQUEST_CODE_NOW_PLAYING = 2; private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id"; @@ -239,6 +239,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private final DurationTimer mTuneDurationTimer = new DurationTimer(); private DvrManager mDvrManager; private ConflictChecker mDvrConflictChecker; + private SetupUtils mSetupUtils; private View mContentView; private TunableTvView mTvView; @@ -271,6 +272,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private boolean mOtherActivityLaunched; private PerformanceMonitor mPerformanceMonitor; + private boolean mIsInPIPMode; private boolean mIsFilmModeSet; private float mDefaultRefreshRate; @@ -307,10 +309,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP case Intent.ACTION_SCREEN_OFF: if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF"); // We need to stop TvView, when the screen is turned off. If not and TIS - // uses - // MediaPlayer, a device may not go to the sleep mode and audio can be - // heard, - // because MediaPlayer keeps playing media by its wake lock. + // uses MediaPlayer, a device may not go to the sleep mode and audio + // can be heard, because MediaPlayer keeps playing media by its wake + // lock. mScreenOffIntentReceived = true; markCurrentChannelDuringScreenOff(); stopAll(true); @@ -319,10 +320,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON"); if (!mActivityResumed && mVisibleBehind) { // ACTION_SCREEN_ON is usually called after onResume. But, if media - // is - // played under launcher with requestVisibleBehind(true), onResume - // will - // not be called. In this case, we need to resume TvView explicitly. + // is played under launcher with requestVisibleBehind(true), + // onResume will not be called. In this case, we need to resume + // TvView explicitly. resumeTvIfNeeded(); } break; @@ -339,6 +339,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP tune(true); } break; + default: // fall out } } }; @@ -367,7 +368,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP public void onLoadFinished() { Debug.getTimer(Debug.TAG_START_UP_TIMER) .log("MainActivity.mChannelTunerListener.onLoadFinished"); - SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable(); + mSetupUtils.markNewChannelsBrowsable(); if (mActivityResumed) { resumeTvIfNeeded(); } @@ -405,13 +406,16 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP new TvInputCallback() { @Override public void onInputAdded(String inputId) { - if (Features.TUNER.isEnabled(MainActivity.this) + if (TvFeatures.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId) - && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) { - Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this); + && CommonPreferences.shouldShowSetupActivity(MainActivity.this)) { + Intent intent = + TvSingletons + .getSingletons(MainActivity.this) + .getTunerSetupIntent(MainActivity.this); startActivity(intent); - TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false); - SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId); + CommonPreferences.setShouldShowSetupActivity(MainActivity.this, false); + mSetupUtils.markAsKnownInput(mTunerInputId); } } }; @@ -427,7 +431,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override protected void onCreate(Bundle savedInstanceState) { - TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer(); + TvSingletons tvSingletons = TvSingletons.getSingletons(this); + mPerformanceMonitor = tvSingletons.getPerformanceMonitor(); + TimerEvent timer = mPerformanceMonitor.startTimer(); DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER); if (!startUpDebugTimer.isStarted() || startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) { @@ -436,16 +442,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP startUpDebugTimer.start(); } startUpDebugTimer.log("MainActivity.onCreate"); - if (DEBUG) Log.d(TAG, "onCreate()"); - TvApplication.setCurrentRunningProcess(this, true); + if (DEBUG) { + Log.d(TAG, "onCreate()"); + } + Starter.start(this); super.onCreate(savedInstanceState); - ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this); - if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) { + if (!tvSingletons.getTvInputManagerHelper().hasTvInputManager()) { Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); finishAndRemoveTask(); return; } - mPerformanceMonitor = applicationSingletons.getPerformanceMonitor(); + mPerformanceMonitor = tvSingletons.getPerformanceMonitor(); + mSetupUtils = tvSingletons.getSetupUtils(); TvApplication tvApplication = (TvApplication) getApplication(); mChannelDataManager = tvApplication.getChannelDataManager(); @@ -460,7 +468,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if ((OnboardingUtils.isFirstRunWithCurrentVersion(this) || channelLoadedAndNoChannelAvailable) && !tuneToPassthroughInput - && !TvCommonUtils.isRunningInTest()) { + && !CommonUtils.isRunningInTest()) { startOnboardingActivity(); return; } @@ -495,7 +503,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP }); long channelId = Utils.getLastWatchedChannelId(this); String inputId = Utils.getLastWatchedTunerInputId(this); - if (!isPassthroughInput && inputId != null && channelId != Channel.INVALID_ID) { + if (!isPassthroughInput + && inputId != null + && channelId != Channel.INVALID_ID) { mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId)); } @@ -504,10 +514,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show(); } mTracker = tvApplication.getTracker(); - if (Features.TUNER.isEnabled(this)) { + if (TvFeatures.TUNER.isEnabled(this)) { mTvInputManagerHelper.addCallback(mTvInputCallback); } - mTunerInputId = TunerTvInputService.getInputId(this); + mTunerInputId = tvSingletons.getEmbeddedTunerInputId(); mProgramDataManager.addOnCurrentProgramUpdatedListener( Channel.INVALID_ID, mOnCurrentProgramUpdatedListener); mProgramDataManager.setPrefetchEnabled(true); @@ -635,7 +645,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mSearchFragment); mAudioManagerHelper = new AudioManagerHelper(this, mTvView); - mMediaSessionWrapper = new MediaSessionWrapper(this); + Intent nowPlayingIntent = new Intent(this, MainActivity.class); + PendingIntent pendingIntent = + PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING, nowPlayingIntent, 0); + mMediaSessionWrapper = new MediaSessionWrapper(this, pendingIntent); mTvViewUiManager.restoreDisplayMode(false); if (!handleIntent(getIntent())) { @@ -659,7 +672,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // To avoid not updating Rating systems when changing language. mTvInputManagerHelper.getContentRatingsManager().update(); if (CommonFeatures.DVR.isEnabled(this) - && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) { + && TvFeatures.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) { mDvrConflictChecker = new ConflictChecker(this); } initForTest(); @@ -738,7 +751,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override protected void onNewIntent(Intent intent) { - if (DEBUG) Log.d(TAG, "onNewIntent(): " + intent); + if (DEBUG) { + Log.d(TAG, "onNewIntent(): " + intent); + } if (mOverlayManager == null) { // It's called before onCreate. The intent will be handled at onCreate. b/30725058 return; @@ -754,7 +769,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override protected void onStart() { TimerEvent timer = mPerformanceMonitor.startTimer(); - if (DEBUG) Log.d(TAG, "onStart()"); + if (DEBUG) { + Log.d(TAG, "onStart()"); + } super.onStart(); mScreenOffIntentReceived = false; mActivityStarted = true; @@ -769,9 +786,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION); startService(notificationIntent); } - TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this); - - EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded(); + TvSingletons singletons = TvSingletons.getSingletons(this); + singletons.getTunerInputController().executeNetworkTunerDiscoveryAsyncTask(this); + singletons.getEpgFetcher().fetchImmediatelyIfNeeded(); mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART); } @@ -781,6 +798,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start"); if (DEBUG) Log.d(TAG, "onResume()"); super.onResume(); + mIsInPIPMode = false; if (!PermissionUtils.hasAccessAllEpg(this) && checkSelfPermission(PERMISSION_READ_TV_LISTINGS) != PackageManager.PERMISSION_GRANTED) { @@ -818,7 +836,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } if (mChannelTuner.areAllChannelsLoaded()) { - SetupUtils.getInstance(this).markNewChannelsBrowsable(); + mSetupUtils.markNewChannelsBrowsable(); resumeTvIfNeeded(); } mOverlayManager.showMenuWithTimeShiftPauseIfNeeded(); @@ -830,13 +848,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mInputToSetUp = null; } else if (mShowProgramGuide) { mShowProgramGuide = false; + // This will delay the start of the animation until after the Live Channel app is + // shown. Without this the animation is completed before it is actually visible on + // the screen. mHandler.post( new Runnable() { - // This will delay the start of the animation until after the Live Channel - // app is - // shown. Without this the animation is completed before it is actually - // visible on - // the screen. @Override public void run() { mOverlayManager.showProgramGuide(); @@ -844,16 +860,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP }); } else if (mShowSelectInputView) { mShowSelectInputView = false; + // mShowSelectInputView is true when the activity is started/resumed because the + // TV_INPUT button was pressed in a different app. This will delay the start of + // the animation until after the Live Channel app is shown. Without this the + // animation is completed before it is actually visible on the screen. mHandler.post( new Runnable() { - // mShowSelectInputView is true when the activity is started/resumed because - // the - // TV_INPUT button was pressed in a different app. - // This will delay the start of the animation until after the Live Channel - // app is - // shown. Without this the animation is completed before it is actually - // visible on - // the screen. @Override public void run() { mOverlayManager.showSelectInputView(); @@ -881,12 +893,17 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mShowLockedChannelsTemporarily = false; mShouldTuneToTunerChannel = false; if (!mVisibleBehind) { - mAudioManagerHelper.abandonAudioFocus(); - mMediaSessionWrapper.setPlaybackState(false); - mTracker.sendScreenView(""); + if (mIsInPIPMode) { + mTracker.sendScreenView(SCREEN_PIP); + } else { + mTracker.sendScreenView(""); + mAudioManagerHelper.abandonAudioFocus(); + mMediaSessionWrapper.setPlaybackState(false); + } } else { mTracker.sendScreenView(SCREEN_BEHIND_NAME); } + TvSingletons.getSingletons(this).getExperimentLoader().asyncRefreshExperiments(this); super.onPause(); } @@ -924,10 +941,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mOverlayManager .getSideFragmentManager() .show(new ParentalControlsFragment(), false); - // Pass through. + // fall through. case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN: mOverlayManager.getSideFragmentManager().showSidePanel(true); break; + default: // fall out } } else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) { mOverlayManager.getSideFragmentManager().hideAll(false); @@ -1075,7 +1093,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment. */ public void startSetupActivity(TvInputInfo input, boolean calledByPopup) { - Intent intent = TvCommonUtils.createSetupIntent(input); + Intent intent = CommonUtils.createSetupIntent(input); if (intent == null) { Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show(); return; @@ -1219,15 +1237,6 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP LauncherActivity.startActivitySafe(this, intent); } - /** - * Call {@link Activity#startActivityForResult} in a safe way. - * - * @see LauncherActivity - */ - private void startActivityForResultSafe(Intent intent, int requestCode) { - LauncherActivity.startActivityForResultSafe(this, intent, requestCode); - } - /** Show settings fragment. */ public void showSettingsFragment() { if (!mChannelTuner.areAllChannelsLoaded()) { @@ -1320,31 +1329,39 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) { - if (resultCode == RESULT_OK) { - int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); - String text; - if (count > 0) { - text = - getResources() - .getQuantityString(R.plurals.msg_channel_added, count, count); + switch (requestCode) { + case REQUEST_CODE_START_SETUP_ACTIVITY: + if (resultCode == RESULT_OK) { + int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup); + String text; + if (count > 0) { + text = + getResources() + .getQuantityString( + R.plurals.msg_channel_added, count, count); + } else { + text = getString(R.string.msg_no_channel_added); + } + Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); + mInputIdUnderSetup = null; + if (mChannelTuner.getCurrentChannel() == null) { + mChannelTuner.moveToAdjacentBrowsableChannel(true); + } + if (mTunePending) { + tune(true); + } } else { - text = getString(R.string.msg_no_channel_added); + mInputIdUnderSetup = null; } - Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); - mInputIdUnderSetup = null; - if (mChannelTuner.getCurrentChannel() == null) { - mChannelTuner.moveToAdjacentBrowsableChannel(true); + if (!mIsSetupActivityCalledByPopup) { + mOverlayManager.getSideFragmentManager().showSidePanel(false); } - if (mTunePending) { - tune(true); - } - } else { - mInputIdUnderSetup = null; - } - if (!mIsSetupActivityCalledByPopup) { - mOverlayManager.getSideFragmentManager().showSidePanel(false); - } + break; + case REQUEST_CODE_NOW_PLAYING: + // nothing needs to be done. onResume will restore everything. + break; + default: + // do nothing } if (data != null) { String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE); @@ -1536,7 +1553,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mAudioManagerHelper.abandonAudioFocus(); mMediaSessionWrapper.setPlaybackState(false); } - TvApplication.getSingletons(this) + TvSingletons.getSingletons(this) .getMainActivityWrapper() .notifyCurrentChannelChange(this, null); mChannelTuner.resetCurrentChannel(); @@ -1583,8 +1600,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP finish(); return; } - SetupUtils setupUtils = SetupUtils.getInstance(this); - if (setupUtils.isFirstTune()) { + + if (mSetupUtils.isFirstTune()) { if (!mChannelTuner.areAllChannelsLoaded()) { // tune() will be called, once all channels are loaded. stopTv("tune()", false); @@ -1613,9 +1630,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } return; } - if (!TvCommonUtils.isRunningInTest() + if (!CommonUtils.isRunningInTest() && mShowNewSourcesFragment - && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { + && mSetupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) { // Show new channel sources fragment. runAfterAttachedToWindow( new Runnable() { @@ -1631,7 +1648,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } }); } - setupUtils.onTuned(); + mSetupUtils.onTuned(); if (mTuneParams != null) { Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID); if (initChannelId == channel.getId()) { @@ -1671,7 +1688,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP addToRecentChannels(channel.getId()); } Utils.setLastWatchedChannel(this, channel); - TvApplication.getSingletons(this) + TvSingletons.getSingletons(this) .getMainActivityWrapper() .notifyCurrentChannelChange(this, channel); } @@ -1971,7 +1988,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } if (mTvInputManagerHelper != null) { mTvInputManagerHelper.clearTvInputLabels(); - if (Features.TUNER.isEnabled(this)) { + if (TvFeatures.TUNER.isEnabled(this)) { mTvInputManagerHelper.removeCallback(mTvInputCallback); } } @@ -1992,7 +2009,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return false; case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH: default: - // pass through + // fall through } if (mSearchFragment.isVisible()) { return super.onKeyDown(keyCode, event); @@ -2030,6 +2047,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mTracker.sendChannelDown(); } return true; + default: // fall out } } return super.onKeyDown(keyCode, event); @@ -2070,7 +2088,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP return false; case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH: default: - // pass through + // fall through } if (mSearchFragment.isVisible()) { if (keyCode == KeyEvent.KEYCODE_BACK) { @@ -2100,6 +2118,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP case KeyEvent.KEYCODE_MENU: showSettingsFragment(); return true; + default: // fall out } } else { if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) { @@ -2167,82 +2186,74 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { break; } - // Pass through. + // fall through. case KeyEvent.KEYCODE_CAPTIONS: - { - mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); - return true; - } + mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment()); + return true; case KeyEvent.KEYCODE_A: if (!SystemProperties.USE_DEBUG_KEYS.getValue()) { break; } - // Pass through. + // fall through. case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: - { - mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); - return true; - } + mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment()); + return true; case KeyEvent.KEYCODE_INFO: - { - mOverlayManager.showBanner(); - return true; - } + mOverlayManager.showBanner(); + return true; case KeyEvent.KEYCODE_MEDIA_RECORD: case KeyEvent.KEYCODE_V: - { - Channel currentChannel = getCurrentChannel(); - if (currentChannel != null && mDvrManager != null) { - boolean isRecording = - mDvrManager.getCurrentRecording(currentChannel.getId()) != null; - if (!isRecording) { - if (!mDvrManager.isChannelRecordable(currentChannel)) { - Toast.makeText( - this, - R.string.dvr_msg_cannot_record_program, - Toast.LENGTH_SHORT) - .show(); - } else { - Program program = - mProgramDataManager.getCurrentProgram( - currentChannel.getId()); - DvrUiHelper.checkStorageStatusAndShowErrorMessage( - this, - currentChannel.getInputId(), - new Runnable() { - @Override - public void run() { - DvrUiHelper.requestRecordingCurrentProgram( - MainActivity.this, - currentChannel, - program, - false); - } - }); - } + Channel currentChannel = getCurrentChannel(); + if (currentChannel != null && mDvrManager != null) { + boolean isRecording = + mDvrManager.getCurrentRecording(currentChannel.getId()) != null; + if (!isRecording) { + if (!mDvrManager.isChannelRecordable(currentChannel)) { + Toast.makeText( + this, + R.string.dvr_msg_cannot_record_program, + Toast.LENGTH_SHORT) + .show(); } else { - DvrUiHelper.showStopRecordingDialog( + Program program = + mProgramDataManager.getCurrentProgram( + currentChannel.getId()); + DvrUiHelper.checkStorageStatusAndShowErrorMessage( this, - currentChannel.getId(), - DvrStopRecordingFragment.REASON_USER_STOP, - new HalfSizedDialogFragment.OnActionClickListener() { + currentChannel.getInputId(), + new Runnable() { @Override - public void onActionClick(long actionId) { - if (actionId - == DvrStopRecordingFragment.ACTION_STOP) { - ScheduledRecording currentRecording = - mDvrManager.getCurrentRecording( - currentChannel.getId()); - if (currentRecording != null) { - mDvrManager.stopRecording(currentRecording); - } - } + public void run() { + DvrUiHelper.requestRecordingCurrentProgram( + MainActivity.this, + currentChannel, + program, + false); } }); } + } else { + DvrUiHelper.showStopRecordingDialog( + this, + currentChannel.getId(), + DvrStopRecordingFragment.REASON_USER_STOP, + new HalfSizedDialogFragment.OnActionClickListener() { + @Override + public void onActionClick(long actionId) { + if (actionId == DvrStopRecordingFragment.ACTION_STOP) { + ScheduledRecording currentRecording = + mDvrManager.getCurrentRecording( + currentChannel.getId()); + if (currentRecording != null) { + mDvrManager.stopRecording(currentRecording); + } + } + } + }); } - return true; } + return true; + default: // fall out } } if (keyCode == KeyEvent.KEYCODE_WINDOW) { @@ -2280,6 +2291,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP case KeyEvent.KEYCODE_D: mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment()); return true; + default: // fall out } } return super.onKeyUp(keyCode, event); @@ -2314,14 +2326,19 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // We need to hide overlay first, before moving the activity to PIP. If not, UI will // be shown during PIP stack resizing, because UI and its animation is stuck during // PIP resizing. - mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); - mHandler.post( - new Runnable() { - @Override - public void run() { - MainActivity.super.enterPictureInPictureMode(); - } - }); + mIsInPIPMode = true; + if (mOverlayManager.isOverlayOpened()) { + mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION); + mHandler.post( + new Runnable() { + @Override + public void run() { + MainActivity.super.enterPictureInPictureMode(); + } + }); + } else { + MainActivity.super.enterPictureInPictureMode(); + } } @Override @@ -2403,7 +2420,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } else if (channel.equals(mTvView.getCurrentChannel())) { mOverlayManager.updateChannelBannerAndShowIfNeeded( TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE); - } else if (channel == mChannelTuner.getCurrentChannel()) { + } else if (channel.equals(mChannelTuner.getCurrentChannel())) { // Channel banner is already updated in moveToAdjacentChannel tune(false); } else if (mChannelTuner.moveToChannel(channel)) { @@ -2533,7 +2550,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private void updateAvailabilityToast() { if (mTvView.isVideoAvailable() - || mTvView.getCurrentChannel() != mChannelTuner.getCurrentChannel()) { + || !Objects.equals( + mTvView.getCurrentChannel(), mChannelTuner.getCurrentChannel())) { return; } @@ -2597,7 +2615,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // Initialize TV app for test. The setup process should be finished before the Live TV app is // started. We only enable all the channels here. private void initForTest() { - if (!TvCommonUtils.isRunningInTest()) { + if (!CommonUtils.isRunningInTest()) { return; } @@ -2669,6 +2687,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP sendMessageDelayed(Message.obtain(msg), getDelay(startTime)); mainActivity.moveToAdjacentChannel(true, true); break; + default: // fall out } } @@ -2715,7 +2734,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) { mTracker.sendChannelTuneTime(info.getCurrentChannel(), mTuneDurationTimer.reset()); } - if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) { + if (info.isVideoOrAudioAvailable() && mChannel.equals(getCurrentChannel())) { mOverlayManager.updateChannelBannerAndShowIfNeeded( TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO); } diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java index 2cc898c3..b3472ba5 100644 --- a/src/com/android/tv/MediaSessionWrapper.java +++ b/src/com/android/tv/MediaSessionWrapper.java @@ -16,6 +16,7 @@ package com.android.tv; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -28,6 +29,7 @@ import android.media.tv.TvInputInfo; import android.os.AsyncTask; 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; @@ -40,14 +42,16 @@ import com.android.tv.util.Utils; */ class MediaSessionWrapper { private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession"; - private static PlaybackState MEDIA_SESSION_STATE_PLAYING = + + private static final PlaybackState MEDIA_SESSION_STATE_PLAYING = new PlaybackState.Builder() .setState( PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f) .build(); - private static PlaybackState MEDIA_SESSION_STATE_STOPPED = + + private static final PlaybackState MEDIA_SESSION_STATE_STOPPED = new PlaybackState.Builder() .setState( PlaybackState.STATE_STOPPED, @@ -60,7 +64,7 @@ class MediaSessionWrapper { private int mNowPlayingCardWidth; private int mNowPlayingCardHeight; - MediaSessionWrapper(Context context) { + MediaSessionWrapper(Context context, PendingIntent pendingIntent) { mContext = context; mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG); mMediaSession.setCallback( @@ -74,6 +78,7 @@ class MediaSessionWrapper { mMediaSession.setFlags( MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mMediaSession.setSessionActivity(pendingIntent); mNowPlayingCardWidth = mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width); mNowPlayingCardHeight = @@ -151,7 +156,7 @@ class MediaSessionWrapper { private String getChannelName(Channel channel) { if (channel.isPassthrough()) { TvInputInfo input = - TvApplication.getSingletons(mContext) + TvSingletons.getSingletons(mContext) .getTvInputManagerHelper() .getTvInputInfo(channel.getInputId()); return Utils.loadLabel(mContext, input); @@ -213,6 +218,11 @@ class MediaSessionWrapper { }.execute(); } + @VisibleForTesting + MediaSession getMediaSession() { + return mMediaSession; + } + private static class ProgramPosterArtCallback extends ImageLoader.ImageLoaderCallback { private final Channel mChannel; diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java index d1158682..3ca5be77 100644 --- a/src/com/android/tv/SetupPassthroughActivity.java +++ b/src/com/android/tv/SetupPassthroughActivity.java @@ -26,12 +26,13 @@ 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.TvCommonConstants; +import com.android.tv.common.experiments.Experiments; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ChannelDataManager.Listener; import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.experiments.Experiments; +import com.android.tv.data.epg.EpgInputWhiteList; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -53,35 +54,47 @@ public class SetupPassthroughActivity extends Activity { private TvInputInfo mTvInputInfo; private Intent mActivityAfterCompletion; private boolean mEpgFetcherDuringScan; + private EpgInputWhiteList mEpgInputWhiteList; @Override public void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); - ApplicationSingletons appSingletons = TvApplication.getSingletons(this); - TvInputManagerHelper inputManager = appSingletons.getTvInputManagerHelper(); + TvSingletons tvSingletons = TvSingletons.getSingletons(this); + TvInputManagerHelper inputManager = tvSingletons.getTvInputManagerHelper(); Intent intent = getIntent(); - String inputId = intent.getStringExtra(TvCommonConstants.EXTRA_INPUT_ID); + String inputId = intent.getStringExtra(CommonConstants.EXTRA_INPUT_ID); mTvInputInfo = inputManager.getTvInputInfo(inputId); + mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig()); mActivityAfterCompletion = - intent.getParcelableExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); + intent.getParcelableExtra(CommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); boolean needToFetchEpg = - Utils.isInternalTvInput(this, mTvInputInfo.getId()) && Experiments.CLOUD_EPG.get(); + mTvInputInfo != null + && Utils.isInternalTvInput(this, mTvInputInfo.getId()) + && Experiments.CLOUD_EPG.get(); if (needToFetchEpg) { // In case when the activity is restored, this flag should be restored as well. mEpgFetcherDuringScan = true; } if (savedInstanceState == null) { - SoftPreconditions.checkState( - intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP)); + SoftPreconditions.checkArgument( + CommonConstants.INTENT_ACTION_INPUT_SETUP.equals(intent.getAction()), + TAG, + "Unsupported action %s", + intent.getAction()); if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo); if (mTvInputInfo == null) { Log.w(TAG, "There is no input with the ID " + inputId + "."); finish(); return; } + if (intent.getExtras() == null) { + Log.w(TAG, "There is no extra info in the intent"); + finish(); + return; + } Intent setupIntent = - intent.getExtras().getParcelable(TvCommonConstants.EXTRA_SETUP_INTENT); + intent.getExtras().getParcelable(CommonConstants.EXTRA_SETUP_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."); @@ -93,7 +106,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(TvCommonConstants.EXTRA_SETUP_INTENT); + extras.remove(CommonConstants.EXTRA_SETUP_INTENT); setupIntent.putExtras(extras); try { startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY); @@ -107,14 +120,15 @@ public class SetupPassthroughActivity extends Activity { sScanTimeoutMonitor = new ScanTimeoutMonitor(this); } sScanTimeoutMonitor.startMonitoring(); - EpgFetcher.getInstance(this).onChannelScanStarted(); + TvSingletons.getSingletons(this).getEpgFetcher().onChannelScanStarted(); } } } @Override public void onActivityResult(int requestCode, final int resultCode, final Intent data) { - if (DEBUG) Log.d(TAG, "onActivityResult"); + if (DEBUG) + Log.d(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")"); if (sScanTimeoutMonitor != null) { sScanTimeoutMonitor.stopMonitoring(); } @@ -122,15 +136,17 @@ public class SetupPassthroughActivity extends Activity { boolean setupComplete = requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK; // Tells EpgFetcher that channel source setup is finished. + EpgFetcher epgFetcher = TvSingletons.getSingletons(this).getEpgFetcher(); if (mEpgFetcherDuringScan) { - EpgFetcher.getInstance(this).onChannelScanFinished(); + epgFetcher.onChannelScanFinished(); } if (!setupComplete) { setResult(resultCode, data); finish(); return; } - SetupUtils.getInstance(this) + TvSingletons.getSingletons(this) + .getSetupUtils() .onTvInputSetupFinished( mTvInputInfo.getId(), new Runnable() { @@ -192,7 +208,7 @@ public class SetupPassthroughActivity extends Activity { private ScanTimeoutMonitor(Context context) { mContext = context.getApplicationContext(); - mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager(); + mChannelDataManager = TvSingletons.getSingletons(context).getChannelDataManager(); } private void startMonitoring() { @@ -220,7 +236,7 @@ public class SetupPassthroughActivity extends Activity { private void onScanTimedOut() { stopMonitoring(); - EpgFetcher.getInstance(mContext).onChannelScanFinished(); + TvSingletons.getSingletons(mContext).getEpgFetcher().onChannelScanFinished(); } } } diff --git a/src/com/android/tv/Starter.java b/src/com/android/tv/Starter.java new file mode 100644 index 00000000..22fda0bd --- /dev/null +++ b/src/com/android/tv/Starter.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv; + +import android.content.Context; +import android.util.Log; + +/** Initializes TvApplication. */ +public interface Starter { + + /** + * Initializes TvApplication. + * + *

Note: it should be called at the beginning of any Service.onCreate, Activity.onCreate, or + * BroadcastReceiver.onCreate. + */ + static void start(Context context) { + // TODO(b/63064354) TvApplication should not have to know if it is "the main process" + if (context.getApplicationContext() instanceof Starter) { + Starter starter = (Starter) context.getApplicationContext(); + starter.start(); + } else { + // Application context can be MockTvApplication. + Log.w("Start", "It is not a context of TvApplication"); + } + } + + void start(); +} diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java index 513fe3cd..e37f190c 100644 --- a/src/com/android/tv/TimeShiftManager.java +++ b/src/com/android/tv/TimeShiftManager.java @@ -146,8 +146,8 @@ public class TimeShiftManager { DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL; /** * The current position sent from TIS can not be exactly the same as the current system time due - * to the elapsed time to pass the message from TIS to Live TV. So the boundary threshold is - * necessary. The same goes for the recording start time. It's the same {@link + * to the elapsed time to pass the message from TIS to Live TV. So the boundary threshold + * is necessary. The same goes for the recording start time. It's the same {@link * #REQUEST_CURRENT_POSITION_INTERVAL}. */ private static final long RECORDING_BOUNDARY_THRESHOLD = REQUEST_CURRENT_POSITION_INTERVAL; @@ -1095,10 +1095,9 @@ public class TimeShiftManager { SoftPreconditions.checkArgument( endTimeMs - startTimeMs <= TWO_WEEKS_MS, TAG, - "createDummyProgram: long duration of dummy programs are requested (" - + Utils.toTimeString(startTimeMs) - + ", " - + Utils.toTimeString(endTimeMs)); + "createDummyProgram: long duration of dummy programs are requested ( %s , %s)", + Utils.toTimeString(startTimeMs), + Utils.toTimeString(endTimeMs)); if (startTimeMs >= endTimeMs) { return Collections.emptyList(); } diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java index 549ab6d8..01fe5b07 100644 --- a/src/com/android/tv/TvApplication.java +++ b/src/com/android/tv/TvApplication.java @@ -18,7 +18,6 @@ package com.android.tv; import android.annotation.TargetApi; import android.app.Activity; -import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,30 +28,24 @@ import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.StrictMode; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; -import com.android.tv.analytics.Analytics; -import com.android.tv.analytics.StubAnalytics; -import com.android.tv.analytics.Tracker; -import com.android.tv.common.BuildConfig; -import com.android.tv.common.SharedPreferencesUtils; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.common.BaseApplication; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.recording.RecordingStorageStatusManager; import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; -import com.android.tv.config.DefaultConfigManager; -import com.android.tv.config.RemoteConfig; +import com.android.tv.common.util.Clock; +import com.android.tv.common.util.Debug; +import com.android.tv.common.util.SharedPreferencesUtils; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.ProgramDataManager; import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.data.epg.EpgFetcherImpl; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManagerImpl; import com.android.tv.dvr.DvrManager; @@ -60,80 +53,65 @@ import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.recorder.RecordingScheduler; -import com.android.tv.perf.EventNames; import com.android.tv.perf.PerformanceMonitor; -import com.android.tv.perf.StubPerformanceMonitor; -import com.android.tv.perf.TimerEvent; import com.android.tv.recommendation.ChannelPreviewUpdater; import com.android.tv.recommendation.RecordedProgramPreviewUpdater; import com.android.tv.tuner.TunerInputController; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.tvinput.TunerTvInputService; import com.android.tv.tuner.util.TunerInputInfoUtils; -import com.android.tv.util.AccountHelper; -import com.android.tv.util.Clock; -import com.android.tv.util.Debug; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.SetupUtils; -import com.android.tv.util.SystemProperties; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.util.List; -public class TvApplication extends Application implements ApplicationSingletons { +/** + * Live TV application. + * + *

This includes all the Google specific hooks. + */ +public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter { private static final String TAG = "TvApplication"; private static final boolean DEBUG = false; - private static final TimerEvent sAppStartTimer = StubPerformanceMonitor.startBootstrapTimer(); - /** - * An instance of {@link ApplicationSingletons}. Note that this can be set directly only for the - * test purpose. - */ - @VisibleForTesting public static ApplicationSingletons sAppSingletons; + /** Namespace for LiveChannels configs. LiveChannels configs are kept in piper. */ + public static final String CONFIGNS_P4 = "configns:p4"; /** * Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link - * com.android.tv.tuner.TunerInputController} will recevice this intent to check the existence - * of tuner input when the new version is first launched. + * com.android.tv.tuner.TunerInputController} will recevice this intent to check the + * existence of tuner input when the new version is first launched. */ public static final String ACTION_APPLICATION_FIRST_LAUNCHED = "com.android.tv.action.APPLICATION_FIRST_LAUNCHED"; private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch"; - private RemoteConfig mRemoteConfig; private String mVersionName = ""; private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper(); private SelectInputActivity mSelectInputActivity; - private Analytics mAnalytics; - private Tracker mTracker; - private TvInputManagerHelper mTvInputManagerHelper; private ChannelDataManager mChannelDataManager; private volatile ProgramDataManager mProgramDataManager; private PreviewDataManager mPreviewDataManager; private DvrManager mDvrManager; private DvrScheduleManager mDvrScheduleManager; private DvrDataManager mDvrDataManager; - private DvrStorageStatusManager mDvrStorageStatusManager; private DvrWatchedPositionManager mDvrWatchedPositionManager; private RecordingScheduler mRecordingScheduler; + private RecordingStorageStatusManager mDvrStorageStatusManager; @Nullable private InputSessionManager mInputSessionManager; - private AccountHelper mAccountHelper; + // STOP-SHIP: Remove this variable when Tuner Process is split to another application. // When this variable is null, we don't know in which process TvApplication runs. private Boolean mRunningInMainProcess; private PerformanceMonitor mPerformanceMonitor; + private TvInputManagerHelper mTvInputManagerHelper; + private boolean mStarted; + private EpgFetcher mEpgFetcher; + private TunerInputController mTunerInputController; @Override public void onCreate() { super.onCreate(); - if (!PermissionUtils.hasInternet(this)) { - // When an isolated process starts, just skip all the initialization. - return; - } - Debug.getTimer(Debug.TAG_START_UP_TIMER).start(); - Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start TvApplication.onCreate"); SharedPreferencesUtils.initialize( this, new Runnable() { @@ -144,9 +122,6 @@ public class TvApplication extends Application implements ApplicationSingletons } } }); - // TunerPreferences is used to enable/disable the tuner input even when TUNER feature is - // disabled. - TunerPreferences.initialize(this); try { PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); mVersionName = pInfo.versionName; @@ -156,66 +131,35 @@ public class TvApplication extends Application implements ApplicationSingletons } Log.i(TAG, "Starting Live TV " + getVersionName()); - // Only set StrictMode for ENG builds because the build server only produces userdebug - // builds. - if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) { - StrictMode.ThreadPolicy.Builder threadPolicyBuilder = - new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog(); - StrictMode.VmPolicy.Builder vmPolicyBuilder = - new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath(); - if (!TvCommonUtils.isRunningInTest()) { - threadPolicyBuilder.penaltyDialog(); - } - StrictMode.setThreadPolicy(threadPolicyBuilder.build()); - StrictMode.setVmPolicy(vmPolicyBuilder.build()); - } - if (BuildConfig.ENG && !SystemProperties.ALLOW_ANALYTICS_IN_ENG.getValue()) { - mAnalytics = StubAnalytics.getInstance(this); - } else { - mAnalytics = StubAnalytics.getInstance(this); - } - mTracker = mAnalytics.getDefaultTracker(); - getTvInputManagerHelper(); // In SetupFragment, transitions are set in the constructor. Because the fragment can be // created in Activity.onCreate() by the framework, SetupAnimationHelper should be // initialized here before Activity.onCreate() is called. + mEpgFetcher = EpgFetcherImpl.create(this); SetupAnimationHelper.initialize(this); + getTvInputManagerHelper(); Log.i(TAG, "Started Live TV " + mVersionName); Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate"); - getPerformanceMonitor().stopTimer(sAppStartTimer, EventNames.APPLICATION_ONCREATE); } - private void setCurrentRunningProcess(boolean isMainProcess) { - if (mRunningInMainProcess != null) { - SoftPreconditions.checkState(isMainProcess == mRunningInMainProcess); + /** Initializes application. It is a noop if called twice. */ + @Override + public void start() { + if (mStarted) { return; } - Debug.getTimer(Debug.TAG_START_UP_TIMER) - .log("start TvApplication.setCurrentRunningProcess"); - mRunningInMainProcess = isMainProcess; - if (CommonFeatures.DVR.isEnabled(this)) { - mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess); - } - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - // Fetch remote config - getRemoteConfig().fetch(null); - return null; - } - }.execute(); + mStarted = true; + mRunningInMainProcess = true; + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("start TvApplication.start"); if (mRunningInMainProcess) { getTvInputManagerHelper() .addCallback( new TvInputCallback() { @Override public void onInputAdded(String inputId) { - if (Features.TUNER.isEnabled(TvApplication.this) + if (TvFeatures.TUNER.isEnabled(TvApplication.this) && TextUtils.equals( - inputId, - TunerTvInputService.getInputId( - TvApplication.this))) { + inputId, getEmbeddedTunerInputId())) { TunerInputInfoUtils.updateTunerInputInfo( TvApplication.this); } @@ -227,7 +171,7 @@ public class TvApplication extends Application implements ApplicationSingletons handleInputCountChanged(); } }); - if (Features.TUNER.isEnabled(this)) { + if (TvFeatures.TUNER.isEnabled(this)) { // If the tuner input service is added before the app is started, we need to // handle it here. TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this); @@ -237,15 +181,14 @@ public class TvApplication extends Application implements ApplicationSingletons mDvrManager = new DvrManager(this); mRecordingScheduler = RecordingScheduler.createScheduler(this); } - EpgFetcher.getInstance(this).startRoutineService(); + mEpgFetcher.startRoutineService(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ChannelPreviewUpdater.getInstance(this).startRoutineService(); RecordedProgramPreviewUpdater.getInstance(this) .updatePreviewDataForRecordedPrograms(); } } - Debug.getTimer(Debug.TAG_START_UP_TIMER) - .log("finish TvApplication.setCurrentRunningProcess"); + Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start"); } private void checkTunerServiceOnFirstLaunch() { @@ -255,13 +198,24 @@ public class TvApplication extends Application implements ApplicationSingletons boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true); if (isFirstLaunch) { if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!"); - TunerInputController.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED); + getTunerInputController() + .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false); editor.apply(); } } + @Override + public EpgFetcher getEpgFetcher() { + return mEpgFetcher; + } + + @Override + public synchronized SetupUtils getSetupUtils() { + return SetupUtils.createForTvSingletons(this); + } + /** Returns the {@link DvrManager}. */ @Override public DvrManager getDvrManager() { @@ -299,18 +253,6 @@ public class TvApplication extends Application implements ApplicationSingletons return mInputSessionManager; } - /** Returns the {@link Analytics}. */ - @Override - public Analytics getAnalytics() { - return mAnalytics; - } - - /** Returns the default tracker. */ - @Override - public Tracker getTracker() { - return mTracker; - } - /** Returns {@link ChannelDataManager}. */ @Override public ChannelDataManager getChannelDataManager() { @@ -373,21 +315,13 @@ public class TvApplication extends Application implements ApplicationSingletons return mDvrDataManager; } - /** Returns {@link DvrStorageStatusManager}. */ - @TargetApi(Build.VERSION_CODES.N) - @Override - public DvrStorageStatusManager getDvrStorageStatusManager() { - return mDvrStorageStatusManager; - } - - /** Returns {@link TvInputManagerHelper}. */ @Override - public TvInputManagerHelper getTvInputManagerHelper() { - if (mTvInputManagerHelper == null) { - mTvInputManagerHelper = new TvInputManagerHelper(this); - mTvInputManagerHelper.start(); + @TargetApi(Build.VERSION_CODES.N) + public RecordingStorageStatusManager getRecordingStorageStatusManager() { + if (mDvrStorageStatusManager == null) { + mDvrStorageStatusManager = new DvrStorageStatusManager(this); } - return mTvInputManagerHelper; + return mDvrStorageStatusManager; } /** Returns the main activity information. */ @@ -396,22 +330,24 @@ public class TvApplication extends Application implements ApplicationSingletons return mMainActivityWrapper; } - /** Returns the {@link AccountHelper}. */ + /** Returns {@link TvInputManagerHelper}. */ @Override - public AccountHelper getAccountHelper() { - if (mAccountHelper == null) { - mAccountHelper = new AccountHelper(getApplicationContext()); + public TvInputManagerHelper getTvInputManagerHelper() { + if (mTvInputManagerHelper == null) { + mTvInputManagerHelper = new TvInputManagerHelper(this); + mTvInputManagerHelper.start(); } - return mAccountHelper; + return mTvInputManagerHelper; } @Override - public RemoteConfig getRemoteConfig() { - if (mRemoteConfig == null) { - // No need to synchronize this, it does not hurt to create two and throw one away. - mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig(); + public synchronized TunerInputController getTunerInputController() { + if (mTunerInputController == null) { + mTunerInputController = + new TunerInputController( + ComponentName.unflattenFromString(getEmbeddedTunerInputId())); } - return mRemoteConfig; + return mTunerInputController; } @Override @@ -419,14 +355,6 @@ public class TvApplication extends Application implements ApplicationSingletons return mRunningInMainProcess != null && mRunningInMainProcess; } - @Override - public PerformanceMonitor getPerformanceMonitor() { - if (mPerformanceMonitor == null) { - mPerformanceMonitor = StubPerformanceMonitor.initialize(this); - } - return mPerformanceMonitor; - } - /** * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link * SelectInputActivity#onDestroy}. @@ -513,9 +441,10 @@ public class TvApplication extends Application implements ApplicationSingletons } /** - * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link + * Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link * SetupUtils}. */ + @Override public void handleInputCountChanged() { handleInputCountChanged(false, false, false); } @@ -524,8 +453,8 @@ public class TvApplication extends Application implements ApplicationSingletons * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link * SetupUtils}. * - * @param calledByTunerServiceChanged true if it is called when TunerTvInputService is enabled - * or disabled. + * @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is + * enabled or disabled. * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true. * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by * default. But, if dontKillApp is true, the app won't restart. @@ -535,7 +464,7 @@ public class TvApplication extends Application implements ApplicationSingletons TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); boolean enable = (calledByTunerServiceChanged && tunerServiceEnabled) - || Features.UNHIDE.isEnabled(TvApplication.this); + || TvFeatures.UNHIDE.isEnabled(TvApplication.this); if (!enable) { List inputs = inputManager.getTvInputList(); boolean skipTunerInputCheck = false; @@ -544,7 +473,7 @@ public class TvApplication extends Application implements ApplicationSingletons for (TvInputInfo input : inputs) { if (calledByTunerServiceChanged && !tunerServiceEnabled - && TunerTvInputService.getInputId(this).equals(input.getId())) { + && getEmbeddedTunerInputId().equals(input.getId())) { continue; } if (input.getType() == TvInputInfo.TYPE_TUNER) { @@ -566,34 +495,6 @@ public class TvApplication extends Application implements ApplicationSingletons name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0); Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV."); } - SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager); - } - - /** Returns the @{@link ApplicationSingletons} using the application context. */ - public static ApplicationSingletons getSingletons(Context context) { - // No need to be "synchronized" because this doesn't create any instance. - if (sAppSingletons == null) { - sAppSingletons = (ApplicationSingletons) context.getApplicationContext(); - } - return sAppSingletons; - } - - /** - * Sets true, if TvApplication is running on the main process. If TvApplication runs on tuner - * process or other process, it sets false. - * - *

Note: it should be called at the beginning of Service.onCreate Activity.onCreate, or - * BroadcastReceiver.onCreate. When it is firstly called after launch, it runs process specific - * initializations. - */ - public static void setCurrentRunningProcess(Context context, boolean isMainProcess) { - // TODO(b/63064354) TvApplication should not have to know if it is "the main process" - if (context.getApplicationContext() instanceof TvApplication) { - TvApplication tvApplication = (TvApplication) context.getApplicationContext(); - tvApplication.setCurrentRunningProcess(isMainProcess); - } else { - // Application context can be MockTvApplication. - Log.w(TAG, "It is not a context of TvApplication"); - } + getSetupUtils().onInputListUpdated(inputManager); } } diff --git a/src/com/android/tv/TvFeatures.java b/src/com/android/tv/TvFeatures.java new file mode 100644 index 00000000..81cc576b --- /dev/null +++ b/src/com/android/tv/TvFeatures.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv; + +import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE; +import static com.android.tv.common.feature.FeatureUtils.AND; +import static com.android.tv.common.feature.FeatureUtils.OFF; +import static com.android.tv.common.feature.FeatureUtils.ON; +import static com.android.tv.common.feature.FeatureUtils.OR; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.annotation.VisibleForTesting; +import com.android.tv.common.experiments.Experiments; +import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.feature.ExperimentFeature; +import com.android.tv.common.feature.Feature; +import com.android.tv.common.feature.FeatureUtils; +import com.android.tv.common.feature.GServiceFeature; +import com.android.tv.common.feature.PropertyFeature; +import com.android.tv.common.feature.Sdk; +import com.android.tv.common.feature.TestableFeature; +import com.android.tv.common.util.PermissionUtils; + + +/** + * List of {@link Feature} for the Live TV App. + * + *

Remove the {@code Feature} once it is launched. + */ +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); + + /** + * Analytics that include sensitive information such as channel or program identifiers. + * + *

See b/22062676 + */ + public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN); + + public static final Feature EPG_SEARCH = + PropertyFeature.create("feature_tv_use_epg_search", false); + + private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide"; + /** A flag which indicates that LC app is unhidden even when there is no input. */ + public static final Feature UNHIDE = + OR( + new GServiceFeature(GSERVICE_KEY_UNHIDE, false), + new Feature() { + @Override + public boolean isEnabled(Context context) { + // If LC app runs as non-system app, we unhide the app. + return !PermissionUtils.hasAccessAllEpg(context); + } + }); + + public static final Feature PICTURE_IN_PICTURE = + new Feature() { + private Boolean mEnabled; + + @Override + public boolean isEnabled(Context context) { + if (mEnabled == null) { + mEnabled = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && context.getPackageManager() + .hasSystemFeature( + PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + return mEnabled; + } + }; + + /** Enable a conflict dialog between currently watched channel and upcoming recording. */ + public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF; + + /** Use input blacklist to disable partner's tuner input. */ + public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON; + + @VisibleForTesting + public static final Feature TEST_FEATURE = PropertyFeature.create("test_feature", false); + + private TvFeatures() {} +} diff --git a/src/com/android/tv/TvSingletons.java b/src/com/android/tv/TvSingletons.java new file mode 100644 index 00000000..80c74576 --- /dev/null +++ b/src/com/android/tv/TvSingletons.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv; + +import android.content.Context; +import com.android.tv.analytics.Analytics; +import com.android.tv.analytics.Tracker; +import com.android.tv.common.BaseApplication; +import com.android.tv.common.BaseSingletons; +import com.android.tv.common.experiments.ExperimentLoader; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.PreviewDataManager; +import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.data.epg.EpgReader; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.tuner.TunerInputController; +import com.android.tv.util.SetupUtils; +import com.android.tv.util.TvInputManagerHelper; +import com.android.tv.util.account.AccountHelper; +import javax.inject.Provider; + +/** Interface with getters for application scoped singletons. */ +public interface TvSingletons extends BaseSingletons { + + /** Returns the @{@link TvSingletons} using the application context. */ + static TvSingletons getSingletons(Context context) { + return (TvSingletons) BaseApplication.getSingletons(context); + } + + Analytics getAnalytics(); + + void handleInputCountChanged(); + + ChannelDataManager getChannelDataManager(); + + /** + * Checks if the {@link ChannelDataManager} instance has been created and all the channels has + * been loaded. + */ + boolean isChannelDataManagerLoadFinished(); + + ProgramDataManager getProgramDataManager(); + + /** + * Checks if the {@link ProgramDataManager} instance has been created and the current programs + * for all the channels has been loaded. + */ + boolean isProgramDataManagerCurrentProgramsLoadFinished(); + + PreviewDataManager getPreviewDataManager(); + + DvrDataManager getDvrDataManager(); + + DvrScheduleManager getDvrScheduleManager(); + + DvrManager getDvrManager(); + + RecordingScheduler getRecordingScheduler(); + + DvrWatchedPositionManager getDvrWatchedPositionManager(); + + InputSessionManager getInputSessionManager(); + + Tracker getTracker(); + + MainActivityWrapper getMainActivityWrapper(); + + AccountHelper getAccountHelper(); + + boolean isRunningInMainProcess(); + + PerformanceMonitor getPerformanceMonitor(); + + TvInputManagerHelper getTvInputManagerHelper(); + + Provider providesEpgReader(); + + EpgFetcher getEpgFetcher(); + + SetupUtils getSetupUtils(); + + TunerInputController getTunerInputController(); + + ExperimentLoader getExperimentLoader(); +} diff --git a/src/com/android/tv/analytics/SendChannelStatusRunnable.java b/src/com/android/tv/analytics/SendChannelStatusRunnable.java index 601e82f7..2f208828 100644 --- a/src/com/android/tv/analytics/SendChannelStatusRunnable.java +++ b/src/com/android/tv/analytics/SendChannelStatusRunnable.java @@ -31,7 +31,8 @@ import java.util.concurrent.TimeUnit; * *

* - *

This should only be started from a user activity like {@link com.android.tv.MainActivity}. + *

This should only be started from a user activity like {@link + * com.android.tv.MainActivity}. */ @MainThread public class SendChannelStatusRunnable implements Runnable { diff --git a/src/com/android/tv/app/LiveTvApplication.java b/src/com/android/tv/app/LiveTvApplication.java new file mode 100644 index 00000000..1c4f1522 --- /dev/null +++ b/src/com/android/tv/app/LiveTvApplication.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.media.tv.TvContract; +import com.android.tv.tuner.tvinput.LiveTvTunerTvInputService; +import com.android.tv.TvApplication; +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.config.DefaultConfigManager; +import com.android.tv.common.config.api.RemoteConfig; +import com.android.tv.common.experiments.ExperimentLoader; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.data.epg.EpgReader; +import com.android.tv.data.epg.StubEpgReader; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.StubPerformanceMonitor; +import com.android.tv.tuner.setup.LiveTvTunerSetupActivity; +import com.android.tv.util.account.AccountHelper; +import com.android.tv.util.account.AccountHelperImpl; +import javax.inject.Provider; + +/** The top level application for Live TV. */ +public class LiveTvApplication extends TvApplication { + protected static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity"; + + private final StubPerformanceMonitor performanceMonitor = new StubPerformanceMonitor(); + private final Provider mEpgReaderProvider = + new Provider() { + + @Override + public EpgReader get() { + return new StubEpgReader(LiveTvApplication.this); + } + }; + + private AccountHelper mAccountHelper; + private Analytics mAnalytics; + private Tracker mTracker; + private String mEmbeddedInputId; + private RemoteConfig mRemoteConfig; + private ExperimentLoader mExperimentLoader; + + /** Returns the {@link AccountHelperImpl}. */ + @Override + public AccountHelper getAccountHelper() { + if (mAccountHelper == null) { + mAccountHelper = new AccountHelperImpl(getApplicationContext()); + } + return mAccountHelper; + } + + @Override + public synchronized PerformanceMonitor getPerformanceMonitor() { + return performanceMonitor; + } + + @Override + public Provider providesEpgReader() { + return mEpgReaderProvider; + } + + @Override + public ExperimentLoader getExperimentLoader() { + mExperimentLoader = new ExperimentLoader(); + return mExperimentLoader; + } + + /** Returns the {@link Analytics}. */ + @Override + public synchronized Analytics getAnalytics() { + if (mAnalytics == null) { + mAnalytics = StubAnalytics.getInstance(this); + } + return mAnalytics; + } + + /** Returns the default tracker. */ + @Override + public synchronized Tracker getTracker() { + if (mTracker == null) { + mTracker = getAnalytics().getDefaultTracker(); + } + return mTracker; + } + + @Override + public Intent getTunerSetupIntent(Context context) { + // Make an intent to launch the setup activity of TV tuner input. + Intent intent = + CommonUtils.createSetupIntent( + new Intent(context, LiveTvTunerSetupActivity.class), mEmbeddedInputId); + intent.putExtra(CommonConstants.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); + return intent; + } + + @Override + public synchronized String getEmbeddedTunerInputId() { + if (mEmbeddedInputId == null) { + mEmbeddedInputId = + TvContract.buildInputId( + new ComponentName(this, LiveTvTunerTvInputService.class)); + } + return mEmbeddedInputId; + } + + @Override + public RemoteConfig getRemoteConfig() { + if (mRemoteConfig == null) { + // No need to synchronize this, it does not hurt to create two and throw one away. + mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig(); + } + return mRemoteConfig; + } +} diff --git a/src/com/android/tv/config/ConfigKeys.java b/src/com/android/tv/config/ConfigKeys.java deleted file mode 100644 index 135017ae..00000000 --- a/src/com/android/tv/config/ConfigKeys.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.config; - -/** Static list of config keys. */ -public final class ConfigKeys { - - private ConfigKeys() {} -} diff --git a/src/com/android/tv/config/DefaultConfigManager.java b/src/com/android/tv/config/DefaultConfigManager.java deleted file mode 100644 index 4d754d1f..00000000 --- a/src/com/android/tv/config/DefaultConfigManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.config; - -import android.content.Context; - -/** Stub Remote Config. */ -public class DefaultConfigManager { - public static final long DEFAULT_LONG_VALUE = 0; - - public static DefaultConfigManager createInstance(Context context) { - return new DefaultConfigManager(); - } - - private StubRemoteConfig mRemoteConfig = new StubRemoteConfig(); - - public RemoteConfig getRemoteConfig() { - return mRemoteConfig; - } - - private static class StubRemoteConfig implements RemoteConfig { - @Override - public void fetch(OnRemoteConfigUpdatedListener listener) {} - - @Override - public String getString(String key) { - return null; - } - - @Override - public boolean getBoolean(String key) { - return false; - } - - @Override - public long getLong(String key) { - return DEFAULT_LONG_VALUE; - } - } -} diff --git a/src/com/android/tv/config/RemoteConfig.java b/src/com/android/tv/config/RemoteConfig.java deleted file mode 100644 index d72a1f3f..00000000 --- a/src/com/android/tv/config/RemoteConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.config; - -/** - * Manages Live TV Configuration, allowing remote updates. - * - *

This is a thin wrapper around Firebase Remote Config - */ -public interface RemoteConfig { - - /** Notified on successful completion of a {@link #fetch)} */ - interface OnRemoteConfigUpdatedListener { - void onRemoteConfigUpdated(); - } - - /** Starts a fetch and notifies {@code listener} on successful completion. */ - void fetch(OnRemoteConfigUpdatedListener listener); - - /** Gets value as a string corresponding to the specified key. */ - String getString(String key); - - /** Gets value as a boolean corresponding to the specified key. */ - boolean getBoolean(String key); - - /** Gets value as a long corresponding to the specified key. */ - long getLong(String key); -} diff --git a/src/com/android/tv/config/RemoteConfigFeature.java b/src/com/android/tv/config/RemoteConfigFeature.java deleted file mode 100644 index c22446be..00000000 --- a/src/com/android/tv/config/RemoteConfigFeature.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.config; - -import android.content.Context; -import com.android.tv.TvApplication; -import com.android.tv.common.feature.Feature; - -/** A {@link Feature} controlled by a {@link RemoteConfig} boolean. */ -public class RemoteConfigFeature implements Feature { - private final String mKey; - - /** Creates a {@link RemoteConfigFeature for the {@code key}. */ - public static RemoteConfigFeature fromKey(String key) { - return new RemoteConfigFeature(key); - } - - private RemoteConfigFeature(String key) { - mKey = key; - } - - @Override - public boolean isEnabled(Context context) { - return TvApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey); - } -} diff --git a/src/com/android/tv/config/RemoteConfigUtils.java b/src/com/android/tv/config/RemoteConfigUtils.java deleted file mode 100644 index 09d85239..00000000 --- a/src/com/android/tv/config/RemoteConfigUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.config; - -import android.content.Context; -import android.util.Log; -import com.android.tv.TvApplication; - -/** A utility class to get the remote config. */ -public class RemoteConfigUtils { - private static final String TAG = "RemoteConfigUtils"; - private static final boolean DEBUG = false; - - private RemoteConfigUtils() {} - - public static long getRemoteConfig(Context context, String key, long defaultValue) { - RemoteConfig remoteConfig = TvApplication.getSingletons(context).getRemoteConfig(); - try { - long remoteValue = remoteConfig.getLong(key); - if (DEBUG) Log.d(TAG, "Got " + key + " from remote: " + remoteValue); - return remoteValue; - } catch (Exception e) { - Log.w(TAG, "Cannot get " + key + " from RemoteConfig", e); - } - if (DEBUG) Log.d(TAG, "Use default value " + defaultValue); - return defaultValue; - } -} diff --git a/src/com/android/tv/customization/CustomAction.java b/src/com/android/tv/customization/CustomAction.java deleted file mode 100644 index 77a5ae5e..00000000 --- a/src/com/android/tv/customization/CustomAction.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.customization; - -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; - -/** Describes a custom option defined in customization package. This will be added to main menu. */ -public class CustomAction implements Comparable { - private static final int POSITION_THRESHOLD = 100; - - private final int mPositionPriority; - private final String mTitle; - private final Drawable mIconDrawable; - private final Intent mIntent; - - public CustomAction(int positionPriority, String title, Drawable iconDrawable, Intent intent) { - mPositionPriority = positionPriority; - mTitle = title; - mIconDrawable = iconDrawable; - mIntent = intent; - } - - /** - * Returns if this option comes before the existing items. Note that custom options can only be - * placed at the front or back. (i.e. cannot be added in the middle of existing options.) - * - * @return {@code true} if it goes to the beginning. {@code false} if it goes to the end. - */ - public boolean isFront() { - return mPositionPriority < POSITION_THRESHOLD; - } - - @Override - public int compareTo(@NonNull CustomAction another) { - return mPositionPriority - another.mPositionPriority; - } - - /** Returns title. */ - public String getTitle() { - return mTitle; - } - - /** Returns icon drawable. */ - public Drawable getIconDrawable() { - return mIconDrawable; - } - - /** Returns intent to launch when this option is clicked. */ - public Intent getIntent() { - return mIntent; - } -} diff --git a/src/com/android/tv/customization/TvCustomizationManager.java b/src/com/android/tv/customization/TvCustomizationManager.java deleted file mode 100644 index 7d21c6d2..00000000 --- a/src/com/android/tv/customization/TvCustomizationManager.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.customization; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.support.annotation.IntDef; -import android.text.TextUtils; -import android.util.Log; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TvCustomizationManager { - private static final String TAG = "TvCustomizationManager"; - private static final boolean DEBUG = false; - - private static final String[] CUSTOMIZE_PERMISSIONS = { - "com.android.tv.permission.CUSTOMIZE_TV_APP" - }; - - private static final String CATEGORY_TV_CUSTOMIZATION = "com.android.tv.category"; - - /** Row IDs to share customized actions. Only rows listed below can have customized action. */ - public static final String ID_OPTIONS_ROW = "options_row"; - - public static final String ID_PARTNER_ROW = "partner_row"; - - @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE}) - @Retention(RetentionPolicy.SOURCE) - public @interface TRICKPLAY_MODE {} - - public static final int TRICKPLAY_MODE_ENABLED = 0; - public static final int TRICKPLAY_MODE_DISABLED = 1; - public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2; - - private static final String[] TRICKPLAY_MODE_STRINGS = { - "enabled", "disabled", "use_external_storage_only" - }; - - private static final HashMap INTENT_CATEGORY_TO_ROW_ID; - - static { - INTENT_CATEGORY_TO_ROW_ID = new HashMap<>(); - INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW); - INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".PARTNER_ROW", ID_PARTNER_ROW); - } - - private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title"; - private static final String RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER = - "has_linux_dvb_built_in_tuner"; - private static final String RES_ID_TRICKPLAY_MODE = "trickplay_mode"; - - private static final String RES_TYPE_STRING = "string"; - private static final String RES_TYPE_BOOLEAN = "bool"; - - private static String sCustomizationPackage; - private static Boolean sHasLinuxDvbBuiltInTuner; - private static @TRICKPLAY_MODE Integer sTrickplayMode; - - private final Context mContext; - private boolean mInitialized; - - private String mPartnerRowTitle; - private final Map> mRowIdToCustomActionsMap = new HashMap<>(); - - public TvCustomizationManager(Context context) { - mContext = context; - mInitialized = false; - } - - /** - * Returns {@code true} if there's a customization package installed and it specifies built-in - * tuner devices are available. The built-in tuner should support DVB API to be recognized by - * Live TV. - */ - public static boolean hasLinuxDvbBuiltInTuner(Context context) { - if (sHasLinuxDvbBuiltInTuner == null) { - if (TextUtils.isEmpty(getCustomizationPackageName(context))) { - sHasLinuxDvbBuiltInTuner = false; - } else { - try { - Resources res = - context.getPackageManager() - .getResourcesForApplication(sCustomizationPackage); - int resId = - res.getIdentifier( - RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, - RES_TYPE_BOOLEAN, - sCustomizationPackage); - sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId); - } catch (NameNotFoundException e) { - sHasLinuxDvbBuiltInTuner = false; - } - } - } - return sHasLinuxDvbBuiltInTuner; - } - - public static @TRICKPLAY_MODE int getTrickplayMode(Context context) { - if (sTrickplayMode == null) { - if (TextUtils.isEmpty(getCustomizationPackageName(context))) { - sTrickplayMode = TRICKPLAY_MODE_ENABLED; - } else { - try { - String customization = null; - Resources res = - context.getPackageManager() - .getResourcesForApplication(sCustomizationPackage); - int resId = - res.getIdentifier( - RES_ID_TRICKPLAY_MODE, RES_TYPE_STRING, sCustomizationPackage); - customization = resId == 0 ? null : res.getString(resId); - sTrickplayMode = TRICKPLAY_MODE_ENABLED; - if (customization != null) { - for (int i = 0; i < TRICKPLAY_MODE_STRINGS.length; ++i) { - if (TRICKPLAY_MODE_STRINGS[i].equalsIgnoreCase(customization)) { - sTrickplayMode = i; - break; - } - } - } - } catch (NameNotFoundException e) { - sTrickplayMode = TRICKPLAY_MODE_ENABLED; - } - } - } - return sTrickplayMode; - } - - private static String getCustomizationPackageName(Context context) { - if (sCustomizationPackage == null) { - List packageInfos = - context.getPackageManager() - .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); - sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName; - } - return sCustomizationPackage; - } - - /** Initialize TV customization options. Run this API only on the main thread. */ - public void initialize() { - if (mInitialized) { - return; - } - mInitialized = true; - if (!TextUtils.isEmpty(getCustomizationPackageName(mContext))) { - buildCustomActions(); - buildPartnerRow(); - } - } - - private void buildCustomActions() { - mRowIdToCustomActionsMap.clear(); - PackageManager pm = mContext.getPackageManager(); - for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) { - Intent customOptionIntent = new Intent(Intent.ACTION_MAIN); - customOptionIntent.addCategory(intentCategory); - - List activities = - pm.queryIntentActivities( - customOptionIntent, - PackageManager.GET_RECEIVERS - | PackageManager.GET_RESOLVED_FILTER - | PackageManager.GET_META_DATA); - for (ResolveInfo info : activities) { - String packageName = info.activityInfo.packageName; - if (!TextUtils.equals(packageName, sCustomizationPackage)) { - Log.w( - TAG, - "A customization package " - + sCustomizationPackage - + " already exist. Ignoring " - + packageName); - continue; - } - - int position = info.filter.getPriority(); - String title = info.loadLabel(pm).toString(); - Drawable drawable = info.loadIcon(pm); - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(intentCategory); - intent.setClassName(sCustomizationPackage, info.activityInfo.name); - - String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory); - List actions = mRowIdToCustomActionsMap.get(rowId); - if (actions == null) { - actions = new ArrayList<>(); - mRowIdToCustomActionsMap.put(rowId, actions); - } - actions.add(new CustomAction(position, title, drawable, intent)); - } - } - // Sort items by position - for (List actions : mRowIdToCustomActionsMap.values()) { - Collections.sort(actions); - } - - if (DEBUG) { - Log.d(TAG, "Dumping custom actions"); - for (String id : mRowIdToCustomActionsMap.keySet()) { - for (CustomAction action : mRowIdToCustomActionsMap.get(id)) { - Log.d( - TAG, - "Custom row rowId=" - + id - + " title=" - + action.getTitle() - + " class=" - + action.getIntent()); - } - } - Log.d(TAG, "Dumping custom actions - end of dump"); - } - } - - /** - * Returns custom actions for given row id. - * - *

Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW. - */ - public List getCustomActions(String rowId) { - return mRowIdToCustomActionsMap.get(rowId); - } - - private void buildPartnerRow() { - mPartnerRowTitle = null; - Resources res; - try { - res = mContext.getPackageManager().getResourcesForApplication(sCustomizationPackage); - } catch (NameNotFoundException e) { - Log.w(TAG, "Could not get resources for package " + sCustomizationPackage); - return; - } - int resId = - res.getIdentifier(RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); - if (resId != 0) { - mPartnerRowTitle = res.getString(resId); - } - if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]"); - } - - /** Returns partner row title. */ - public String getPartnerRowTitle() { - return mPartnerRowTitle; - } -} diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java index eda188e4..1204a49f 100644 --- a/src/com/android/tv/data/Channel.java +++ b/src/com/android/tv/data/Channel.java @@ -28,7 +28,8 @@ import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; -import com.android.tv.common.TvCommonConstants; +import com.android.tv.common.CommonConstants; +import com.android.tv.common.util.CommonUtils; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -124,7 +125,7 @@ public final class Channel { channel.mAppLinkIconUri = cursor.getString(index++); channel.mAppLinkPosterArtUri = cursor.getString(index++); channel.mAppLinkIntentUri = cursor.getString(index++); - if (Utils.isBundledInput(channel.mInputId)) { + if (CommonUtils.isBundledInput(channel.mInputId)) { channel.mRecordingProhibited = cursor.getInt(index++) != 0; } return channel; @@ -625,7 +626,7 @@ public final class Channel { if (intent.resolveActivityInfo(pm, 0) != null) { mAppLinkIntent = intent; mAppLinkIntent.putExtra( - TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); + CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); mAppLinkType = APP_LINK_TYPE_CHANNEL; return; } else { @@ -642,7 +643,7 @@ public final class Channel { mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName); if (mAppLinkIntent != null) { mAppLinkIntent.putExtra( - TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); + CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString()); mAppLinkType = APP_LINK_TYPE_APP; } } diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java index e4d1cd85..68fbdb6a 100644 --- a/src/com/android/tv/data/ChannelDataManager.java +++ b/src/com/android/tv/data/ChannelDataManager.java @@ -38,11 +38,11 @@ import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Log; import android.util.MutableInt; -import com.android.tv.common.SharedPreferencesUtils; 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.util.AsyncDbTask; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.io.IOException; @@ -62,6 +62,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * methods are called in only the main thread. */ @AnyThread +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class ChannelDataManager { private static final String TAG = "ChannelDataManager"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java index 2dc43102..8aaaf73a 100644 --- a/src/com/android/tv/data/ChannelLogoFetcher.java +++ b/src/com/android/tv/data/ChannelLogoFetcher.java @@ -28,10 +28,10 @@ import android.os.RemoteException; import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; -import com.android.tv.common.SharedPreferencesUtils; +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.util.PermissionUtils; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -42,6 +42,7 @@ import java.util.Map; * Fetches channel logos from the cloud into the database. It's for the channels which have no logos * or need update logos. This class is thread safe. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class ChannelLogoFetcher { private static final String TAG = "ChannelLogoFetcher"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java index 63f8a972..1623b33d 100644 --- a/src/com/android/tv/data/ChannelNumber.java +++ b/src/com/android/tv/data/ChannelNumber.java @@ -19,7 +19,7 @@ package com.android.tv.data; import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.KeyEvent; -import com.android.tv.util.StringUtils; +import com.android.tv.common.util.StringUtils; import java.util.Objects; /** A convenience class to handle channel number. */ @@ -43,6 +43,23 @@ public final class ChannelNumber implements Comparable { reset(); } + /** + * {@code lhs} and {@code rhs} are equivalent if {@link ChannelNumber#compare(String, String)} + * is 0 or if only one has a delimiter and both {@link ChannelNumber#majorNumber} equals. + */ + public static boolean equivalent(String lhs, String rhs) { + if (compare(lhs, rhs) == 0) { + return true; + } + // Match if only one has delimiter + ChannelNumber lhsNumber = parseChannelNumber(lhs); + ChannelNumber rhsNumber = parseChannelNumber(rhs); + return lhsNumber != null + && rhsNumber != null + && lhsNumber.hasDelimiter != rhsNumber.hasDelimiter + && lhsNumber.majorNumber.equals(rhsNumber.majorNumber); + } + public void reset() { setChannelNumber("", false, ""); } diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java index 4c30d395..99a3d4e8 100644 --- a/src/com/android/tv/data/InternalDataUtils.java +++ b/src/com/android/tv/data/InternalDataUtils.java @@ -33,6 +33,7 @@ import java.util.List; * android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the {@link * android.media.tv.TvContract.Programs}. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public final class InternalDataUtils { private static final boolean DEBUG = false; private static final String TAG = "InternalDataUtils"; diff --git a/src/com/android/tv/data/Lineup.java b/src/com/android/tv/data/Lineup.java index 0f11c1cc..4393cd3d 100644 --- a/src/com/android/tv/data/Lineup.java +++ b/src/com/android/tv/data/Lineup.java @@ -19,23 +19,45 @@ package com.android.tv.data; import android.support.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; /** A class that represents a lineup. */ public class Lineup { /** The ID of this lineup. */ - public final String id; + public String getId() { + return id; + } /** The type associated with this lineup. */ - public final int type; + public int getType() { + return type; + } /** The human readable name associated with this lineup. */ - public final String name; + public String getName() { + return name; + } - /** - * Location this lineup can be found. This is a human readable description of a geographic - * location. - */ - public final String location; + /** The human readable name associated with this lineup. */ + public String getLocation() { + return location; + } + + /** An unmodifiable list of channel numbers that this lineup has. */ + public List getChannels() { + return channels; + } + + private final String id; + + private final int type; + + private final String name; + + private final String location; + + private final List channels; @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -44,7 +66,9 @@ public class Lineup { LINEUP_BROADCAST_DIGITAL, LINEUP_BROADCAST_ANALOG, LINEUP_IPTV, - LINEUP_MVPD + LINEUP_MVPD, + LINEUP_INTERNET, + LINEUP_OTHER }) public @interface LineupType {} @@ -64,16 +88,23 @@ public class Lineup { public static final int LINEUP_IPTV = 4; /** - * Indicates the lineup is either satelite, cable or IPTV but we are not sure which specific + * Indicates the lineup is either satellite, cable or IPTV but we are not sure which specific * type. */ public static final int LINEUP_MVPD = 5; + /** Lineup type for Internet. */ + public static final int LINEUP_INTERNET = 6; + + /** Lineup type for other. */ + public static final int LINEUP_OTHER = 7; + /** Creates a lineup. */ - public Lineup(String id, int type, String name, String location) { + public Lineup(String id, int type, String name, String location, List channels) { this.id = id; this.type = type; this.name = name; this.location = location; + this.channels = Collections.unmodifiableList(channels); } } diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java index b103a5d7..ac78147b 100644 --- a/src/com/android/tv/data/PreviewDataManager.java +++ b/src/com/android/tv/data/PreviewDataManager.java @@ -36,7 +36,7 @@ import android.support.media.tv.PreviewProgram; import android.util.Log; import android.util.Pair; import com.android.tv.R; -import com.android.tv.util.PermissionUtils; +import com.android.tv.common.util.PermissionUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; @@ -47,22 +47,22 @@ import java.util.concurrent.CopyOnWriteArraySet; /** Class to manage the preview data. */ @TargetApi(Build.VERSION_CODES.O) @MainThread +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class PreviewDataManager { private static final String TAG = "PreviewDataManager"; - // STOPSHIP: set it to false. - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; /** Invalid preview channel ID. */ public static final long INVALID_PREVIEW_CHANNEL_ID = -1; - @IntDef({(int) TYPE_DEFAULT_PREVIEW_CHANNEL, (int) TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) + @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL}) @Retention(RetentionPolicy.SOURCE) public @interface PreviewChannelType {} /** Type of default preview channel */ - public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1; + public static final int TYPE_DEFAULT_PREVIEW_CHANNEL = 1; /** Type of recorded program channel */ - public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2; + public static final int TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2; private final Context mContext; private final ContentResolver mContentResolver; @@ -604,7 +604,8 @@ public class PreviewDataManager { .setPosterArtUri(program.getPosterArtUri()) .setIntentUri(program.getIntentUri()) .setPreviewVideoUri(program.getPreviewVideoUri()) - .setInternalProviderId(Long.toString(program.getId())); + .setInternalProviderId(Long.toString(program.getId())) + .setContentId(program.getIntentUri().toString()); return builder.build(); } diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java index 845ca9d4..252092b0 100644 --- a/src/com/android/tv/data/PreviewProgramContent.java +++ b/src/com/android/tv/data/PreviewProgramContent.java @@ -17,17 +17,18 @@ package com.android.tv.data; import android.content.Context; -import android.media.tv.TvContract; import android.net.Uri; +import android.support.annotation.VisibleForTesting; +import android.support.media.tv.TvContractCompat; import android.text.TextUtils; import android.util.Pair; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.data.RecordedProgram; import java.util.Objects; /** A class to store the content of preview programs. */ public class PreviewProgramContent { - private static final String PARAM_INPUT = "input"; + @VisibleForTesting static final String PARAM_INPUT = "input"; private long mId; private long mPreviewChannelId; @@ -43,17 +44,30 @@ public class PreviewProgramContent { public static PreviewProgramContent createFromProgram( Context context, long previewChannelId, Program program) { Channel channel = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getChannelDataManager() .getChannel(program.getChannelId()); - if (channel == null) { - return null; - } + return channel == null ? null : createFromProgram(previewChannelId, program, channel); + } + + /** Create preview program content from {@link RecordedProgram} */ + public static PreviewProgramContent createFromRecordedProgram( + Context context, long previewChannelId, RecordedProgram recordedProgram) { + Channel channel = + TvSingletons.getSingletons(context) + .getChannelDataManager() + .getChannel(recordedProgram.getChannelId()); + return createFromRecordedProgram(previewChannelId, recordedProgram, channel); + } + + @VisibleForTesting + static PreviewProgramContent createFromProgram( + long previewChannelId, Program program, Channel channel) { String channelDisplayName = channel.getDisplayName(); return new PreviewProgramContent.Builder() .setId(program.getId()) .setPreviewChannelId(previewChannelId) - .setType(TvContract.PreviewPrograms.TYPE_CHANNEL) + .setType(TvContractCompat.PreviewPrograms.TYPE_CHANNEL) .setLive(true) .setTitle(program.getTitle()) .setDescription( @@ -68,22 +82,15 @@ public class PreviewProgramContent { .build(); } - /** Create preview program content from {@link RecordedProgram} */ - public static PreviewProgramContent createFromRecordedProgram( - Context context, long previewChannelId, RecordedProgram recordedProgram) { - Channel channel = - TvApplication.getSingletons(context) - .getChannelDataManager() - .getChannel(recordedProgram.getChannelId()); - String channelDisplayName = null; - if (channel != null) { - channelDisplayName = channel.getDisplayName(); - } - Uri recordedProgramUri = TvContract.buildRecordedProgramUri(recordedProgram.getId()); + @VisibleForTesting + static PreviewProgramContent createFromRecordedProgram( + long previewChannelId, RecordedProgram recordedProgram, Channel channel) { + String channelDisplayName = channel == null ? null : channel.getDisplayName(); + Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(recordedProgram.getId()); return new PreviewProgramContent.Builder() .setId(recordedProgram.getId()) .setPreviewChannelId(previewChannelId) - .setType(TvContract.PreviewPrograms.TYPE_CLIP) + .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP) .setTitle(recordedProgram.getTitle()) .setDescription(channelDisplayName != null ? channelDisplayName : "") .setPosterArtUri(Uri.parse(recordedProgram.getPosterArtUri())) diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java index f47a3a06..30a3033e 100644 --- a/src/com/android/tv/data/Program.java +++ b/src/com/android/tv/data/Program.java @@ -33,8 +33,9 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import com.android.tv.common.BuildConfig; -import com.android.tv.common.CollectionUtils; 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.util.Utils; import java.io.Serializable; @@ -128,7 +129,7 @@ public final class Program extends BaseProgram implements Comparable, P builder.setEndTimeUtcMillis(cursor.getLong(index++)); builder.setVideoWidth((int) cursor.getLong(index++)); builder.setVideoHeight((int) cursor.getLong(index++)); - if (Utils.isInBundledPackageSet(packageName)) { + if (CommonUtils.isInBundledPackageSet(packageName)) { InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); } index++; @@ -475,6 +476,9 @@ public final class Program extends BaseProgram implements Comparable, P public static ContentValues toContentValues(Program program) { ContentValues values = new ContentValues(); values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId()); + if (!TextUtils.isEmpty(program.getPackageName())) { + values.put(Programs.COLUMN_PACKAGE_NAME, program.getPackageName()); + } putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle()); putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java index 639ac99a..3a7693a4 100644 --- a/src/com/android/tv/data/ProgramDataManager.java +++ b/src/com/android/tv/data/ProgramDataManager.java @@ -35,8 +35,8 @@ import android.util.LongSparseArray; import android.util.LruCache; import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.Clock; import com.android.tv.util.AsyncDbTask; -import com.android.tv.util.Clock; import com.android.tv.util.MultiLongSparseArray; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @MainThread +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class ProgramDataManager implements MemoryManageable { private static final String TAG = "ProgramDataManager"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java index 8c9756b0..25ba7716 100644 --- a/src/com/android/tv/data/WatchedHistoryManager.java +++ b/src/com/android/tv/data/WatchedHistoryManager.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv.data; import android.content.Context; @@ -12,7 +27,7 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.util.SharedPreferencesUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -28,6 +43,7 @@ import java.util.concurrent.TimeUnit; * *

Note that this class is not thread safe. Please use this on one thread. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class WatchedHistoryManager { private static final String TAG = "WatchedHistoryManager"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java new file mode 100644 index 00000000..90d109d7 --- /dev/null +++ b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java @@ -0,0 +1,86 @@ +/* + * 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.epg; + +import com.android.tv.data.Channel; + +/** + * Hand copy of generated Autovalue class. + * + * TODO get autovalue working + */ +final class AutoValue_EpgReader_EpgChannel extends EpgReader.EpgChannel { + + private final Channel channel; + private final String epgChannelId; + + AutoValue_EpgReader_EpgChannel( + Channel channel, + String epgChannelId) { + if (channel == null) { + throw new NullPointerException("Null channel"); + } + this.channel = channel; + if (epgChannelId == null) { + throw new NullPointerException("Null epgChannelId"); + } + this.epgChannelId = epgChannelId; + } + + @Override + public Channel getChannel() { + return channel; + } + + @Override + public String getEpgChannelId() { + return epgChannelId; + } + + @Override + public String toString() { + return "EpgChannel{" + + "channel=" + channel + ", " + + "epgChannelId=" + epgChannelId + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof EpgReader.EpgChannel) { + EpgReader.EpgChannel that = (EpgReader.EpgChannel) o; + return (this.channel.equals(that.getChannel())) + && (this.epgChannelId.equals(that.getEpgChannelId())); + } + return false; + } + + @Override + public int hashCode() { + int h = 1; + h *= 1000003; + h ^= this.channel.hashCode(); + h *= 1000003; + h ^= this.epgChannelId.hashCode(); + return h; + } + +} + diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java index 89d5f494..30123ee5 100644 --- a/src/com/android/tv/data/epg/EpgFetchHelper.java +++ b/src/com/android/tv/data/epg/EpgFetchHelper.java @@ -27,13 +27,15 @@ import android.preference.PreferenceManager; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; +import com.android.tv.common.util.Clock; import com.android.tv.data.Program; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -/** The helper class for {@link com.android.tv.data.epg.EpgFetcher} */ +/** The helper class for {@link EpgFetcher} */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed class EpgFetchHelper { private static final String TAG = "EpgFetchHelper"; private static final boolean DEBUG = false; @@ -64,13 +66,14 @@ class EpgFetchHelper { * @param fetchedPrograms the newly fetched program data. * @return {@code true} if new program data are successfully updated. Otherwise {@code false}. */ - static boolean updateEpgData(Context context, long channelId, List fetchedPrograms) { + static boolean updateEpgData( + Context context, Clock clock, long channelId, List fetchedPrograms) { final int fetchedProgramsCount = fetchedPrograms.size(); if (fetchedProgramsCount == 0) { return false; } boolean updated = false; - long startTimeMs = System.currentTimeMillis(); + long startTimeMs = clock.currentTimeMillis(); long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION_MS; List oldPrograms = queryPrograms(context, channelId, startTimeMs, endTimeMs); int oldProgramsIndex = 0; diff --git a/src/com/android/tv/data/epg/EpgFetchService.java b/src/com/android/tv/data/epg/EpgFetchService.java new file mode 100644 index 00000000..aa4f3588 --- /dev/null +++ b/src/com/android/tv/data/epg/EpgFetchService.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.data.epg; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; +import com.android.tv.data.ChannelDataManager; + +/** JobService to Fetch EPG data. */ +public class EpgFetchService extends JobService { + private EpgFetcher mEpgFetcher; + private ChannelDataManager mChannelDataManager; + + @Override + public void onCreate() { + super.onCreate(); + Starter.start(this); + TvSingletons tvSingletons = TvSingletons.getSingletons(getApplicationContext()); + mEpgFetcher = tvSingletons.getEpgFetcher(); + mChannelDataManager = tvSingletons.getChannelDataManager(); + } + + @Override + public boolean onStartJob(JobParameters params) { + if (!mChannelDataManager.isDbLoadFinished()) { + mChannelDataManager.addListener( + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + if (!mEpgFetcher.executeFetchTaskIfPossible( + EpgFetchService.this, params)) { + jobFinished(params, false); + } + } + + @Override + public void onChannelListUpdated() {} + + @Override + public void onChannelBrowsableChanged() {} + }); + return true; + } else { + return mEpgFetcher.executeFetchTaskIfPossible(this, params); + } + } + + @Override + public boolean onStopJob(JobParameters params) { + mEpgFetcher.stopFetchingJob(); + return false; + } +} diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java index b10bdc1b..9c24613d 100644 --- a/src/com/android/tv/data/epg/EpgFetcher.java +++ b/src/com/android/tv/data/epg/EpgFetcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,138 +16,13 @@ package com.android.tv.data.epg; -import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; -import android.content.ComponentName; -import android.content.Context; -import android.media.tv.TvInputInfo; -import android.net.TrafficStats; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.AnyThread; import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; -import android.util.Log; -import com.android.tv.ApplicationSingletons; -import com.android.tv.Features; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonUtils; -import com.android.tv.config.RemoteConfigUtils; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.ChannelLogoFetcher; -import com.android.tv.data.Lineup; -import com.android.tv.data.Program; -import com.android.tv.perf.EventNames; -import com.android.tv.perf.PerformanceMonitor; -import com.android.tv.perf.TimerEvent; -import com.android.tv.tuner.util.PostalCodeUtils; -import com.android.tv.util.LocationUtils; -import com.android.tv.util.NetworkTrafficTags; -import com.android.tv.util.Utils; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -/** - * The service class to fetch EPG routinely or on-demand during channel scanning - * - *

Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one - * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on - * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}. - */ -public class EpgFetcher { - private static final String TAG = "EpgFetcher"; - private static final boolean DEBUG = false; - - private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101; - - private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10); - - private static final int REASON_EPG_READER_NOT_READY = 1; - private static final int REASON_LOCATION_INFO_UNAVAILABLE = 2; - private static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3; - private static final int REASON_NO_EPG_DATA_RETURNED = 4; - private static final int REASON_NO_NEW_EPG = 5; - - private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10); - - private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3); - private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2); - - private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4; - private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour"; - - private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1; - private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2; - private static final int MSG_FINISH_FETCH_DURING_SCAN = 3; - private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4; - - private static final int QUERY_CHANNEL_COUNT = 50; - private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3; - - private static EpgFetcher sInstance; - - private final Context mContext; - private final ChannelDataManager mChannelDataManager; - private final EpgReader mEpgReader; - private final PerformanceMonitor mPerformanceMonitor; - private FetchAsyncTask mFetchTask; - private FetchDuringScanHandler mFetchDuringScanHandler; - private long mEpgTimeStamp; - private List mPossibleLineups; - private final Object mPossibleLineupsLock = new Object(); - private final Object mFetchDuringScanHandlerLock = new Object(); - // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished. - private boolean mScanStarted; - - private final long mRoutineIntervalMs; - private final long mEpgDataExpiredTimeLimitMs; - private final long mFastFetchDurationSec; - - public static EpgFetcher getInstance(Context context) { - if (sInstance == null) { - sInstance = new EpgFetcher(context); - } - return sInstance; - } - - /** Creates and returns {@link EpgReader}. */ - public static EpgReader createEpgReader(Context context, String region) { - return new StubEpgReader(context); - } - - private EpgFetcher(Context context) { - mContext = context.getApplicationContext(); - ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); - mChannelDataManager = applicationSingletons.getChannelDataManager(); - mPerformanceMonitor = applicationSingletons.getPerformanceMonitor(); - mEpgReader = createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext)); - - int remoteInteval = - (int) - RemoteConfigUtils.getRemoteConfig( - context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR); - mRoutineIntervalMs = - remoteInteval < 0 - ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR) - : TimeUnit.HOURS.toMillis(remoteInteval); - mEpgDataExpiredTimeLimitMs = mRoutineIntervalMs * 2; - mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + mRoutineIntervalMs / 1000; - } +/** Fetch EPG routinely or on-demand during channel scanning */ +public interface EpgFetcher { /** * Starts the routine service of EPG fetching. It use {@link JobScheduler} to schedule the EPG @@ -155,590 +30,30 @@ public class EpgFetcher { * channel scanning of tuner input is started. */ @MainThread - public void startRoutineService() { - JobScheduler jobScheduler = - (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); - for (JobInfo job : jobScheduler.getAllPendingJobs()) { - if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) { - return; - } - } - JobInfo job = - new JobInfo.Builder( - EPG_ROUTINELY_FETCHING_JOB_ID, - new ComponentName(mContext, EpgFetchService.class)) - .setPeriodic(mRoutineIntervalMs) - .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL) - .setPersisted(true) - .build(); - jobScheduler.schedule(job); - Log.i(TAG, "EPG fetching routine service started."); - } + void startRoutineService(); /** * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated by * routine fetching service due to various reasons. */ @MainThread - public void fetchImmediatelyIfNeeded() { - if (TvCommonUtils.isRunningInTest()) { - // Do not run EpgFetcher in test. - return; - } - new AsyncTask() { - @Override - protected Long doInBackground(Void... args) { - return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext); - } - - @Override - protected void onPostExecute(Long result) { - if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) - > mEpgDataExpiredTimeLimitMs) { - Log.i(TAG, "EPG data expired. Start fetching immediately."); - fetchImmediately(); - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } + void fetchImmediatelyIfNeeded(); /** Fetches EPG immediately. */ @MainThread - public void fetchImmediately() { - if (!mChannelDataManager.isDbLoadFinished()) { - mChannelDataManager.addListener( - new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - mChannelDataManager.removeListener(this); - executeFetchTaskIfPossible(null, null); - } - - @Override - public void onChannelListUpdated() {} - - @Override - public void onChannelBrowsableChanged() {} - }); - } else { - executeFetchTaskIfPossible(null, null); - } - } + void fetchImmediately(); /** Notifies EPG fetch service that channel scanning is started. */ @MainThread - public void onChannelScanStarted() { - if (mScanStarted || !Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { - return; - } - mScanStarted = true; - stopFetchingJob(); - synchronized (mFetchDuringScanHandlerLock) { - if (mFetchDuringScanHandler == null) { - HandlerThread thread = new HandlerThread("EpgFetchDuringScan"); - thread.start(); - mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper()); - } - mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN); - } - Log.i(TAG, "EPG fetching on channel scanning started."); - } + void onChannelScanStarted(); /** Notifies EPG fetch service that channel scanning is finished. */ @MainThread - public void onChannelScanFinished() { - if (!mScanStarted) { - return; - } - mScanStarted = false; - mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); - } + void onChannelScanFinished(); @MainThread - private void stopFetchingJob() { - if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job..."); - if (mFetchTask != null) { - mFetchTask.cancel(true); - mFetchTask = null; - Log.i(TAG, "EPG routinely fetching job stopped."); - } - } + boolean executeFetchTaskIfPossible(JobService jobService, JobParameters params); @MainThread - private boolean executeFetchTaskIfPossible(JobService service, JobParameters params) { - SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished()); - if (!TvCommonUtils.isRunningInTest() && checkFetchPrerequisite()) { - mFetchTask = new FetchAsyncTask(service, params); - mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - return true; - } - return false; - } - - @MainThread - private boolean checkFetchPrerequisite() { - if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job."); - if (!Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { - Log.i( - TAG, - "Cannot start routine service: country not supported: " - + LocationUtils.getCurrentCountry(mContext)); - return false; - } - if (mFetchTask != null) { - // Fetching job is already running or ready to run, no need to start again. - return false; - } - if (mFetchDuringScanHandler != null) { - if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels."); - return false; - } - if (getTunerChannelCount() == 0) { - if (DEBUG) Log.d(TAG, "Cannot start routine service: no internal tuner channels."); - return false; - } - if (!TextUtils.isEmpty(EpgFetchHelper.getLastLineupId(mContext))) { - return true; - } - if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { - return true; - } - return true; - } - - @MainThread - private int getTunerChannelCount() { - for (TvInputInfo input : - TvApplication.getSingletons(mContext) - .getTvInputManagerHelper() - .getTvInputInfos(true, true)) { - String inputId = input.getId(); - if (Utils.isInternalTvInput(mContext, inputId)) { - return mChannelDataManager.getChannelCountForInput(inputId); - } - } - return 0; - } - - @AnyThread - private void clearUnusedLineups(@Nullable String lineupId) { - synchronized (mPossibleLineupsLock) { - if (mPossibleLineups == null) { - return; - } - for (Lineup lineup : mPossibleLineups) { - if (!TextUtils.equals(lineupId, lineup.id)) { - mEpgReader.clearCachedChannels(lineup.id); - } - } - mPossibleLineups = null; - } - } - - @WorkerThread - private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) { - if (!mEpgReader.isAvailable()) { - Log.i(TAG, "EPG reader is temporarily unavailable."); - return REASON_EPG_READER_NOT_READY; - } - // Checks the EPG Timestamp. - mEpgTimeStamp = mEpgReader.getEpgTimestamp(); - if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) { - if (DEBUG) Log.d(TAG, "No new EPG."); - return REASON_NO_NEW_EPG; - } - // Updates postal code. - boolean postalCodeChanged = false; - try { - postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext); - } catch (IOException e) { - if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e); - if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { - return REASON_LOCATION_INFO_UNAVAILABLE; - } - } catch (SecurityException e) { - Log.w(TAG, "No permission to get the current location."); - if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { - return REASON_LOCATION_PERMISSION_NOT_GRANTED; - } - } catch (PostalCodeUtils.NoPostalCodeException e) { - Log.i(TAG, "Cannot get address or postal code."); - return REASON_LOCATION_INFO_UNAVAILABLE; - } - // Updates possible lineups if necessary. - SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset."); - if (postalCodeChanged - || forceUpdatePossibleLineups - || EpgFetchHelper.getLastLineupId(mContext) == null) { - // To prevent main thread being blocked, though theoretically it should not happen. - List possibleLineups = - mEpgReader.getLineups(PostalCodeUtils.getLastPostalCode(mContext)); - if (possibleLineups.isEmpty()) { - return REASON_NO_EPG_DATA_RETURNED; - } - for (Lineup lineup : possibleLineups) { - mEpgReader.preloadChannels(lineup.id); - } - synchronized (mPossibleLineupsLock) { - mPossibleLineups = possibleLineups; - } - EpgFetchHelper.setLastLineupId(mContext, null); - } - return null; - } - - @WorkerThread - private void batchFetchEpg(List channels, long durationSec) { - Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + channels.size()); - if (channels.size() == 0) { - return; - } - List queryChannelIds = new ArrayList<>(QUERY_CHANNEL_COUNT); - for (Channel channel : channels) { - queryChannelIds.add(channel.getId()); - if (queryChannelIds.size() >= QUERY_CHANNEL_COUNT) { - batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec)); - queryChannelIds.clear(); - } - } - if (!queryChannelIds.isEmpty()) { - batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec)); - } - } - - @WorkerThread - private void batchUpdateEpg(Map> allPrograms) { - for (Map.Entry> entry : allPrograms.entrySet()) { - List programs = entry.getValue(); - if (programs == null) { - continue; - } - Collections.sort(programs); - Log.i( - TAG, - "Batch fetched " + programs.size() + " programs for channel " + entry.getKey()); - EpgFetchHelper.updateEpgData(mContext, entry.getKey(), programs); - } - } - - @Nullable - @WorkerThread - private String pickBestLineupId(List currentChannelList) { - String maxLineupId = null; - synchronized (mPossibleLineupsLock) { - if (mPossibleLineups == null) { - return null; - } - int maxCount = 0; - for (Lineup lineup : mPossibleLineups) { - int count = getMatchedChannelCount(lineup.id, currentChannelList); - Log.i(TAG, lineup.name + " (" + lineup.id + ") - " + count + " matches"); - if (count > maxCount) { - maxCount = count; - maxLineupId = lineup.id; - } - } - } - return maxLineupId; - } - - @WorkerThread - private int getMatchedChannelCount(String lineupId, List currentChannelList) { - // Construct a list of display numbers for existing channels. - if (currentChannelList.isEmpty()) { - if (DEBUG) Log.d(TAG, "No existing channel to compare"); - return 0; - } - List numbers = new ArrayList<>(currentChannelList.size()); - for (Channel channel : currentChannelList) { - // We only support channels from internal tuner inputs. - if (Utils.isInternalTvInput(mContext, channel.getInputId())) { - numbers.add(channel.getDisplayNumber()); - } - } - numbers.retainAll(mEpgReader.getChannelNumbers(lineupId)); - return numbers.size(); - } - - public static class EpgFetchService extends JobService { - private EpgFetcher mEpgFetcher; - - @Override - public void onCreate() { - super.onCreate(); - TvApplication.setCurrentRunningProcess(this, true); - mEpgFetcher = EpgFetcher.getInstance(this); - } - - @Override - public boolean onStartJob(JobParameters params) { - if (!mEpgFetcher.mChannelDataManager.isDbLoadFinished()) { - mEpgFetcher.mChannelDataManager.addListener( - new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - mEpgFetcher.mChannelDataManager.removeListener(this); - if (!mEpgFetcher.executeFetchTaskIfPossible( - EpgFetchService.this, params)) { - jobFinished(params, false); - } - } - - @Override - public void onChannelListUpdated() {} - - @Override - public void onChannelBrowsableChanged() {} - }); - return true; - } else { - return mEpgFetcher.executeFetchTaskIfPossible(this, params); - } - } - - @Override - public boolean onStopJob(JobParameters params) { - mEpgFetcher.stopFetchingJob(); - return false; - } - } - - private class FetchAsyncTask extends AsyncTask { - private final JobService mService; - private final JobParameters mParams; - private List mCurrentChannelList; - private TimerEvent mTimerEvent; - - private FetchAsyncTask(JobService service, JobParameters params) { - mService = service; - mParams = params; - } - - @Override - protected void onPreExecute() { - mTimerEvent = mPerformanceMonitor.startTimer(); - mCurrentChannelList = mChannelDataManager.getChannelList(); - } - - @Override - protected Integer doInBackground(Void... args) { - final int oldTag = TrafficStats.getThreadStatsTag(); - TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH); - try { - if (DEBUG) Log.d(TAG, "Start EPG routinely fetching."); - Integer failureReason = prepareFetchEpg(false); - // InterruptedException might be caught by RPC, we should check it here. - if (failureReason != null || this.isCancelled()) { - return failureReason; - } - String lineupId = EpgFetchHelper.getLastLineupId(mContext); - lineupId = lineupId == null ? pickBestLineupId(mCurrentChannelList) : lineupId; - if (lineupId != null) { - Log.i(TAG, "Selecting the lineup " + lineupId); - // During normal fetching process, the lineup ID should be confirmed since all - // channels are known, clear up possible lineups to save resources. - EpgFetchHelper.setLastLineupId(mContext, lineupId); - clearUnusedLineups(lineupId); - } else { - Log.i(TAG, "Failed to get lineup id"); - return REASON_NO_EPG_DATA_RETURNED; - } - final List channels = mEpgReader.getChannels(lineupId); - // InterruptedException might be caught by RPC, we should check it here. - if (this.isCancelled()) { - return null; - } - if (channels.isEmpty()) { - Log.i(TAG, "Failed to get EPG channels."); - return REASON_NO_EPG_DATA_RETURNED; - } - if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) - > mEpgDataExpiredTimeLimitMs) { - batchFetchEpg(channels, mFastFetchDurationSec); - } - new Handler(mContext.getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - ChannelLogoFetcher.startFetchingChannelLogos( - mContext, channels); - } - }); - for (Channel channel : channels) { - if (this.isCancelled()) { - return null; - } - long channelId = channel.getId(); - List programs = new ArrayList<>(mEpgReader.getPrograms(channelId)); - // InterruptedException might be caught by RPC, we should check it here. - Collections.sort(programs); - Log.i(TAG, "Fetched " + programs.size() + " programs for channel " + channelId); - EpgFetchHelper.updateEpgData(mContext, channelId, programs); - } - EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp); - if (DEBUG) Log.d(TAG, "Fetching EPG is finished."); - return null; - } finally { - TrafficStats.setThreadStatsTag(oldTag); - } - } - - @Override - protected void onPostExecute(Integer failureReason) { - mFetchTask = null; - if (failureReason == null - || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED - || failureReason == REASON_NO_NEW_EPG) { - jobFinished(false); - } else { - // Applies back-off policy - jobFinished(true); - } - mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK); - mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK); - } - - @Override - protected void onCancelled(Integer failureReason) { - clearUnusedLineups(null); - jobFinished(false); - } - - private void jobFinished(boolean reschedule) { - if (mService != null && mParams != null) { - // Task is executed from JobService, need to report jobFinished. - mService.jobFinished(mParams, reschedule); - } - } - } - - @WorkerThread - private class FetchDuringScanHandler extends Handler { - private final Set mFetchedChannelIdsDuringScan = new HashSet<>(); - private String mPossibleLineupId; - - private final ChannelDataManager.Listener mDuringScanChannelListener = - new ChannelDataManager.Listener() { - @Override - public void onLoadFinished() { - if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()"); - if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP - && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - Message.obtain( - FetchDuringScanHandler.this, - MSG_CHANNEL_UPDATED_DURING_SCAN, - new ArrayList<>(mChannelDataManager.getChannelList())) - .sendToTarget(); - } - } - - @Override - public void onChannelListUpdated() { - if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()"); - if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP - && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - Message.obtain( - FetchDuringScanHandler.this, - MSG_CHANNEL_UPDATED_DURING_SCAN, - mChannelDataManager.getChannelList()) - .sendToTarget(); - } - } - - @Override - public void onChannelBrowsableChanged() { - // Do nothing - } - }; - - @AnyThread - private FetchDuringScanHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_PREPARE_FETCH_DURING_SCAN: - case MSG_RETRY_PREPARE_FETCH_DURING_SCAN: - onPrepareFetchDuringScan(); - break; - case MSG_CHANNEL_UPDATED_DURING_SCAN: - if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - onChannelUpdatedDuringScan((List) msg.obj); - } - break; - case MSG_FINISH_FETCH_DURING_SCAN: - removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN); - if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { - sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); - } else { - onFinishFetchDuringScan(); - } - break; - } - } - - private void onPrepareFetchDuringScan() { - Integer failureReason = prepareFetchEpg(true); - if (failureReason != null) { - sendEmptyMessageDelayed( - MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS); - return; - } - mChannelDataManager.addListener(mDuringScanChannelListener); - } - - private void onChannelUpdatedDuringScan(List currentChannelList) { - String lineupId = pickBestLineupId(currentChannelList); - Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId); - if (TextUtils.isEmpty(lineupId)) { - if (TextUtils.isEmpty(mPossibleLineupId)) { - return; - } - } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) { - mFetchedChannelIdsDuringScan.clear(); - mPossibleLineupId = lineupId; - } - List currentChannelIds = new ArrayList<>(); - for (Channel channel : currentChannelList) { - currentChannelIds.add(channel.getId()); - } - mFetchedChannelIdsDuringScan.retainAll(currentChannelIds); - List newChannels = new ArrayList<>(); - for (Channel channel : mEpgReader.getChannels(mPossibleLineupId)) { - if (!mFetchedChannelIdsDuringScan.contains(channel.getId())) { - newChannels.add(channel); - mFetchedChannelIdsDuringScan.add(channel.getId()); - } - } - batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC); - } - - private void onFinishFetchDuringScan() { - mChannelDataManager.removeListener(mDuringScanChannelListener); - EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId); - clearUnusedLineups(null); - mFetchedChannelIdsDuringScan.clear(); - synchronized (mFetchDuringScanHandlerLock) { - if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) { - removeCallbacksAndMessages(null); - getLooper().quit(); - mFetchDuringScanHandler = null; - } - } - // Clear timestamp to make routine service start right away. - EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0); - Log.i(TAG, "EPG Fetching during channel scanning finished."); - new Handler(Looper.getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - fetchImmediately(); - } - }); - } - } + void stopFetchingJob(); } diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java new file mode 100644 index 00000000..59839593 --- /dev/null +++ b/src/com/android/tv/data/epg/EpgFetcherImpl.java @@ -0,0 +1,805 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.data.epg; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.media.tv.TvInputInfo; +import android.net.TrafficStats; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.AnyThread; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.TvFeatures; +import com.android.tv.TvSingletons; +import com.android.tv.common.BuildConfig; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.config.RemoteConfigUtils; +import com.android.tv.common.util.Clock; +import com.android.tv.common.util.CommonUtils; +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.ChannelLogoFetcher; +import com.android.tv.data.Lineup; +import com.android.tv.data.Program; +import com.android.tv.perf.EventNames; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.perf.TimerEvent; +import com.android.tv.util.Utils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * The service class to fetch EPG routinely or on-demand during channel scanning + * + *

Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one + * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on + * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}. + */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed +public class EpgFetcherImpl implements EpgFetcher { + private static final String TAG = "EpgFetcherImpl"; + private static final boolean DEBUG = false; + + private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101; + + private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10); + + @VisibleForTesting static final int REASON_EPG_READER_NOT_READY = 1; + @VisibleForTesting static final int REASON_LOCATION_INFO_UNAVAILABLE = 2; + @VisibleForTesting static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3; + @VisibleForTesting static final int REASON_NO_EPG_DATA_RETURNED = 4; + @VisibleForTesting static final int REASON_NO_NEW_EPG = 5; + @VisibleForTesting static final int REASON_ERROR = 6; + @VisibleForTesting static final int REASON_CLOUD_EPG_FAILURE = 7; + @VisibleForTesting static final int REASON_NO_BUILT_IN_CHANNELS = 8; + + private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10); + + private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3); + private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2); + + private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4; + private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour"; + + private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1; + private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2; + private static final int MSG_FINISH_FETCH_DURING_SCAN = 3; + private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4; + + private static final int QUERY_CHANNEL_COUNT = 50; + private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3; + + private final Context mContext; + private final ChannelDataManager mChannelDataManager; + private final EpgReader mEpgReader; + private final PerformanceMonitor mPerformanceMonitor; + private final EpgInputWhiteList mEpgInputWhiteList; + private FetchAsyncTask mFetchTask; + private FetchDuringScanHandler mFetchDuringScanHandler; + private long mEpgTimeStamp; + private List mPossibleLineups; + private final Object mPossibleLineupsLock = new Object(); + private final Object mFetchDuringScanHandlerLock = new Object(); + // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished. + private boolean mScanStarted; + + private final long mRoutineIntervalMs; + private final long mEpgDataExpiredTimeLimitMs; + private final long mFastFetchDurationSec; + private Clock mClock; + + public static EpgFetcher create(Context context) { + context = context.getApplicationContext(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + ChannelDataManager channelDataManager = tvSingletons.getChannelDataManager(); + PerformanceMonitor performanceMonitor = tvSingletons.getPerformanceMonitor(); + EpgReader epgReader = tvSingletons.providesEpgReader().get(); + Clock clock = tvSingletons.getClock(); + EpgInputWhiteList epgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig()); + int routineIntervalMs = + (int) + RemoteConfigUtils.getRemoteConfig( + context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR); + + return new EpgFetcherImpl( + context, + channelDataManager, + epgReader, + performanceMonitor, + clock, + routineIntervalMs, + epgInputWhiteList); + } + + @VisibleForTesting + EpgFetcherImpl( + Context context, + ChannelDataManager channelDataManager, + EpgReader epgReader, + PerformanceMonitor performanceMonitor, + Clock clock, + long routineIntervalMs, + EpgInputWhiteList epgInputWhiteList) { + mContext = context; + mChannelDataManager = channelDataManager; + mEpgReader = epgReader; + mPerformanceMonitor = performanceMonitor; + mClock = clock; + mRoutineIntervalMs = + routineIntervalMs <= 0 + ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR) + : TimeUnit.HOURS.toMillis(routineIntervalMs); + mEpgInputWhiteList = epgInputWhiteList; + mEpgDataExpiredTimeLimitMs = routineIntervalMs * 2; + mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + routineIntervalMs / 1000; + } + + private static Set getExistingChannelsForMyPackage(Context context) { + HashSet channels = new HashSet<>(); + String selection = null; + String[] selectionArgs = null; + String myPackageName = context.getPackageName(); + if (PermissionUtils.hasAccessAllEpg(context)) { + selection = "package_name=?"; + selectionArgs = new String[] {myPackageName}; + } + try (Cursor c = + context.getContentResolver() + .query( + TvContract.Channels.CONTENT_URI, + Channel.PROJECTION, + selection, + selectionArgs, + null)) { + if (c != null) { + while (c.moveToNext()) { + Channel channel = Channel.fromCursor(c); + if (DEBUG) Log.d(TAG, "Found " + channel); + if (myPackageName.equals(channel.getPackageName())) { + channels.add(channel); + } + } + } + } + if (DEBUG) + Log.d(TAG, "Found " + channels.size() + " channels for package " + myPackageName); + return channels; + } + + @Override + @MainThread + public void startRoutineService() { + JobScheduler jobScheduler = + (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE); + for (JobInfo job : jobScheduler.getAllPendingJobs()) { + if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) { + return; + } + } + JobInfo job = + new JobInfo.Builder( + EPG_ROUTINELY_FETCHING_JOB_ID, + new ComponentName(mContext, EpgFetchService.class)) + .setPeriodic(mRoutineIntervalMs) + .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL) + .setPersisted(true) + .build(); + jobScheduler.schedule(job); + Log.i(TAG, "EPG fetching routine service started."); + } + + @Override + @MainThread + public void fetchImmediatelyIfNeeded() { + if (CommonUtils.isRunningInTest()) { + // Do not run EpgFetcher in test. + return; + } + new AsyncTask() { + @Override + protected Long doInBackground(Void... args) { + return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext); + } + + @Override + protected void onPostExecute(Long result) { + if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) + > mEpgDataExpiredTimeLimitMs) { + Log.i(TAG, "EPG data expired. Start fetching immediately."); + fetchImmediately(); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + @MainThread + public void fetchImmediately() { + if (DEBUG) Log.d(TAG, "fetchImmediately"); + if (!mChannelDataManager.isDbLoadFinished()) { + mChannelDataManager.addListener( + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + mChannelDataManager.removeListener(this); + executeFetchTaskIfPossible(null, null); + } + + @Override + public void onChannelListUpdated() {} + + @Override + public void onChannelBrowsableChanged() {} + }); + } else { + executeFetchTaskIfPossible(null, null); + } + } + + @Override + @MainThread + public void onChannelScanStarted() { + if (mScanStarted || !TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { + return; + } + mScanStarted = true; + stopFetchingJob(); + synchronized (mFetchDuringScanHandlerLock) { + if (mFetchDuringScanHandler == null) { + HandlerThread thread = new HandlerThread("EpgFetchDuringScan"); + thread.start(); + mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper()); + } + mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN); + } + Log.i(TAG, "EPG fetching on channel scanning started."); + } + + @Override + @MainThread + public void onChannelScanFinished() { + if (!mScanStarted) { + return; + } + mScanStarted = false; + mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); + } + + @MainThread + public void stopFetchingJob() { + if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job..."); + if (mFetchTask != null) { + mFetchTask.cancel(true); + mFetchTask = null; + Log.i(TAG, "EPG routinely fetching job stopped."); + } + } + + @MainThread + @Override + public boolean executeFetchTaskIfPossible(JobService service, JobParameters params) { + if (DEBUG) Log.d(TAG, "executeFetchTaskIfPossible"); + SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished()); + if (!CommonUtils.isRunningInTest() && checkFetchPrerequisite()) { + mFetchTask = createFetchTask(service, params); + mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return true; + } + return false; + } + + @VisibleForTesting + FetchAsyncTask createFetchTask(JobService service, JobParameters params) { + return new FetchAsyncTask(service, params); + } + + @MainThread + private boolean checkFetchPrerequisite() { + if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job."); + if (!TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) { + Log.i( + TAG, + "Cannot start routine service: country not supported: " + + LocationUtils.getCurrentCountry(mContext)); + return false; + } + if (mFetchTask != null) { + // Fetching job is already running or ready to run, no need to start again. + return false; + } + if (mFetchDuringScanHandler != null) { + if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels."); + return false; + } + + return true; + } + + @MainThread + private int getTunerChannelCount() { + for (TvInputInfo input : + TvSingletons.getSingletons(mContext) + .getTvInputManagerHelper() + .getTvInputInfos(true, true)) { + String inputId = input.getId(); + if (Utils.isInternalTvInput(mContext, inputId)) { + return mChannelDataManager.getChannelCountForInput(inputId); + } + } + return 0; + } + + @AnyThread + private void clearUnusedLineups(@Nullable String lineupId) { + synchronized (mPossibleLineupsLock) { + if (mPossibleLineups == null) { + return; + } + for (Lineup lineup : mPossibleLineups) { + if (!TextUtils.equals(lineupId, lineup.getId())) { + mEpgReader.clearCachedChannels(lineup.getId()); + } + } + mPossibleLineups = null; + } + } + + @WorkerThread + private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) { + if (!mEpgReader.isAvailable()) { + Log.i(TAG, "EPG reader is temporarily unavailable."); + return REASON_EPG_READER_NOT_READY; + } + // Checks the EPG Timestamp. + mEpgTimeStamp = mEpgReader.getEpgTimestamp(); + if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) { + if (DEBUG) Log.d(TAG, "No new EPG."); + return REASON_NO_NEW_EPG; + } + // Updates postal code. + boolean postalCodeChanged = false; + try { + postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext); + } catch (IOException e) { + if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e); + if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { + return REASON_LOCATION_INFO_UNAVAILABLE; + } + } catch (SecurityException e) { + Log.w(TAG, "No permission to get the current location."); + if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) { + return REASON_LOCATION_PERMISSION_NOT_GRANTED; + } + } catch (PostalCodeUtils.NoPostalCodeException e) { + Log.i(TAG, "Cannot get address or postal code."); + return REASON_LOCATION_INFO_UNAVAILABLE; + } + // Updates possible lineups if necessary. + SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset."); + if (postalCodeChanged + || forceUpdatePossibleLineups + || EpgFetchHelper.getLastLineupId(mContext) == null) { + // To prevent main thread being blocked, though theoretically it should not happen. + String lastPostalCode = PostalCodeUtils.getLastPostalCode(mContext); + List possibleLineups = mEpgReader.getLineups(lastPostalCode); + if (possibleLineups.isEmpty()) { + Log.i(TAG, "No lineups found for " + lastPostalCode); + return REASON_NO_EPG_DATA_RETURNED; + } + for (Lineup lineup : possibleLineups) { + mEpgReader.preloadChannels(lineup.getId()); + } + synchronized (mPossibleLineupsLock) { + mPossibleLineups = possibleLineups; + } + EpgFetchHelper.setLastLineupId(mContext, null); + } + return null; + } + + @WorkerThread + private void batchFetchEpg(Set epgChannels, long durationSec) { + Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + epgChannels.size()); + if (epgChannels.size() == 0) { + return; + } + Set batch = new HashSet<>(QUERY_CHANNEL_COUNT); + for (EpgReader.EpgChannel epgChannel : epgChannels) { + batch.add(epgChannel); + if (batch.size() >= QUERY_CHANNEL_COUNT) { + batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec)); + batch.clear(); + } + } + if (!batch.isEmpty()) { + batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec)); + } + } + + @WorkerThread + private void batchUpdateEpg(Map> allPrograms) { + for (Map.Entry> entry : allPrograms.entrySet()) { + List programs = new ArrayList(entry.getValue()); + if (programs == null) { + continue; + } + Collections.sort(programs); + Log.i( + TAG, + "Batch fetched " + programs.size() + " programs for channel " + entry.getKey()); + EpgFetchHelper.updateEpgData( + mContext, mClock, entry.getKey().getChannel().getId(), programs); + } + } + + @Nullable + @WorkerThread + private String pickBestLineupId(Set currentChannels) { + String maxLineupId = null; + synchronized (mPossibleLineupsLock) { + if (mPossibleLineups == null) { + return null; + } + int maxCount = 0; + for (Lineup lineup : mPossibleLineups) { + int count = getMatchedChannelCount(lineup.getId(), currentChannels); + Log.i(TAG, lineup.getName() + " (" + lineup.getId() + ") - " + count + " matches"); + if (count > maxCount) { + maxCount = count; + maxLineupId = lineup.getId(); + } + } + } + return maxLineupId; + } + + @WorkerThread + private int getMatchedChannelCount(String lineupId, Set currentChannels) { + // Construct a list of display numbers for existing channels. + if (currentChannels.isEmpty()) { + if (DEBUG) Log.d(TAG, "No existing channel to compare"); + return 0; + } + List numbers = new ArrayList<>(currentChannels.size()); + for (Channel channel : currentChannels) { + // We only support channels from internal tuner inputs. + if (Utils.isInternalTvInput(mContext, channel.getInputId())) { + numbers.add(channel.getDisplayNumber()); + } + } + numbers.retainAll(mEpgReader.getChannelNumbers(lineupId)); + return numbers.size(); + } + + + @VisibleForTesting + class FetchAsyncTask extends AsyncTask { + private final JobService mService; + private final JobParameters mParams; + private Set mCurrentChannels; + private TimerEvent mTimerEvent; + + private FetchAsyncTask(JobService service, JobParameters params) { + mService = service; + mParams = params; + } + + @Override + protected void onPreExecute() { + mTimerEvent = mPerformanceMonitor.startTimer(); + mCurrentChannels = new HashSet<>(mChannelDataManager.getChannelList()); + } + + @Override + protected Integer doInBackground(Void... args) { + final int oldTag = TrafficStats.getThreadStatsTag(); + TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH); + try { + if (DEBUG) Log.d(TAG, "Start EPG routinely fetching."); + Integer builtInResult = fetchEpgForBuiltInTuner(); + boolean anyCloudEpgFailure = false; + boolean anyCloudEpgSuccess = false; + if (builtInResult == null || builtInResult == REASON_NO_BUILT_IN_CHANNELS) { + return anyCloudEpgFailure + ? ((Integer) REASON_CLOUD_EPG_FAILURE) + : anyCloudEpgSuccess ? null : builtInResult; + } else { + return builtInResult; + } + } finally { + TrafficStats.setThreadStatsTag(oldTag); + } + } + + private Integer fetchEpgForBuiltInTuner() { + try { + Integer failureReason = prepareFetchEpg(false); + // InterruptedException might be caught by RPC, we should check it here. + if (failureReason != null || this.isCancelled()) { + return failureReason; + } + String lineupId = EpgFetchHelper.getLastLineupId(mContext); + lineupId = lineupId == null ? pickBestLineupId(mCurrentChannels) : lineupId; + if (lineupId != null) { + Log.i(TAG, "Selecting the lineup " + lineupId); + // During normal fetching process, the lineup ID should be confirmed since all + // channels are known, clear up possible lineups to save resources. + EpgFetchHelper.setLastLineupId(mContext, lineupId); + clearUnusedLineups(lineupId); + } else { + Log.i(TAG, "Failed to get lineup id"); + return REASON_NO_EPG_DATA_RETURNED; + } + Set existingChannelsForMyPackage = + getExistingChannelsForMyPackage(mContext); + if (existingChannelsForMyPackage.isEmpty()) { + return REASON_NO_BUILT_IN_CHANNELS; + } + return fetchEpgFor(lineupId, existingChannelsForMyPackage); + } catch (Exception e) { + Log.w(TAG, "Failed to update EPG for builtin tuner", e); + return REASON_ERROR; + } + } + + @Nullable + private Integer fetchEpgFor(String lineupId, Set existingChannels) { + if (DEBUG) { + Log.d( + TAG, + "Starting Fetching EPG is for " + + lineupId + + " with channelCount " + + existingChannels.size()); + } + final Set channels = + mEpgReader.getChannels(existingChannels, lineupId); + // InterruptedException might be caught by RPC, we should check it here. + if (this.isCancelled()) { + return null; + } + if (channels.isEmpty()) { + Log.i(TAG, "Failed to get EPG channels for " + lineupId); + return REASON_NO_EPG_DATA_RETURNED; + } + if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext) + > mEpgDataExpiredTimeLimitMs) { + batchFetchEpg(channels, mFastFetchDurationSec); + } + new Handler(mContext.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + ChannelLogoFetcher.startFetchingChannelLogos( + mContext, asChannelList(channels)); + } + }); + for (EpgReader.EpgChannel epgChannel : channels) { + if (this.isCancelled()) { + return null; + } + List programs = new ArrayList<>(mEpgReader.getPrograms(epgChannel)); + // InterruptedException might be caught by RPC, we should check it here. + Collections.sort(programs); + Log.i( + TAG, + "Fetched " + + programs.size() + + " programs for channel " + + epgChannel.getChannel()); + EpgFetchHelper.updateEpgData( + mContext, mClock, epgChannel.getChannel().getId(), programs); + } + EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp); + if (DEBUG) Log.d(TAG, "Fetching EPG is for " + lineupId); + return null; + } + + @Override + protected void onPostExecute(Integer failureReason) { + mFetchTask = null; + if (failureReason == null + || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED + || failureReason == REASON_NO_NEW_EPG) { + jobFinished(false); + } else { + // Applies back-off policy + jobFinished(true); + } + mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK); + mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK); + } + + @Override + protected void onCancelled(Integer failureReason) { + clearUnusedLineups(null); + jobFinished(false); + } + + private void jobFinished(boolean reschedule) { + if (mService != null && mParams != null) { + // Task is executed from JobService, need to report jobFinished. + mService.jobFinished(mParams, reschedule); + } + } + } + + private List asChannelList(Set epgChannels) { + List result = new ArrayList<>(epgChannels.size()); + for (EpgReader.EpgChannel epgChannel : epgChannels) { + result.add(epgChannel.getChannel()); + } + return result; + } + + @WorkerThread + private class FetchDuringScanHandler extends Handler { + private final Set mFetchedChannelIdsDuringScan = new HashSet<>(); + private String mPossibleLineupId; + + private final ChannelDataManager.Listener mDuringScanChannelListener = + new ChannelDataManager.Listener() { + @Override + public void onLoadFinished() { + if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()"); + if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP + && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + Message.obtain( + FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, + getExistingChannelsForMyPackage(mContext)) + .sendToTarget(); + } + } + + @Override + public void onChannelListUpdated() { + if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()"); + if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP + && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + Message.obtain( + FetchDuringScanHandler.this, + MSG_CHANNEL_UPDATED_DURING_SCAN, + getExistingChannelsForMyPackage(mContext)) + .sendToTarget(); + } + } + + @Override + public void onChannelBrowsableChanged() { + // Do nothing + } + }; + + @AnyThread + private FetchDuringScanHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PREPARE_FETCH_DURING_SCAN: + case MSG_RETRY_PREPARE_FETCH_DURING_SCAN: + onPrepareFetchDuringScan(); + break; + case MSG_CHANNEL_UPDATED_DURING_SCAN: + if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + onChannelUpdatedDuringScan((Set) msg.obj); + } + break; + case MSG_FINISH_FETCH_DURING_SCAN: + removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN); + if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) { + sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN); + } else { + onFinishFetchDuringScan(); + } + break; + } + } + + private void onPrepareFetchDuringScan() { + Integer failureReason = prepareFetchEpg(true); + if (failureReason != null) { + sendEmptyMessageDelayed( + MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS); + return; + } + mChannelDataManager.addListener(mDuringScanChannelListener); + } + + private void onChannelUpdatedDuringScan(Set currentChannels) { + String lineupId = pickBestLineupId(currentChannels); + Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId); + if (TextUtils.isEmpty(lineupId)) { + if (TextUtils.isEmpty(mPossibleLineupId)) { + return; + } + } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) { + mFetchedChannelIdsDuringScan.clear(); + mPossibleLineupId = lineupId; + } + List currentChannelIds = new ArrayList<>(); + for (Channel channel : currentChannels) { + currentChannelIds.add(channel.getId()); + } + mFetchedChannelIdsDuringScan.retainAll(currentChannelIds); + Set newChannels = new HashSet<>(); + for (EpgReader.EpgChannel epgChannel : + mEpgReader.getChannels(currentChannels, mPossibleLineupId)) { + if (!mFetchedChannelIdsDuringScan.contains(epgChannel.getChannel().getId())) { + newChannels.add(epgChannel); + mFetchedChannelIdsDuringScan.add(epgChannel.getChannel().getId()); + } + } + batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC); + } + + private void onFinishFetchDuringScan() { + mChannelDataManager.removeListener(mDuringScanChannelListener); + EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId); + clearUnusedLineups(null); + mFetchedChannelIdsDuringScan.clear(); + synchronized (mFetchDuringScanHandlerLock) { + if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) { + removeCallbacksAndMessages(null); + getLooper().quit(); + mFetchDuringScanHandler = null; + } + } + // Clear timestamp to make routine service start right away. + EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0); + Log.i(TAG, "EPG Fetching during channel scanning finished."); + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + fetchImmediately(); + } + }); + } + } +} diff --git a/src/com/android/tv/data/epg/EpgInputWhiteList.java b/src/com/android/tv/data/epg/EpgInputWhiteList.java new file mode 100644 index 00000000..de0478fc --- /dev/null +++ b/src/com/android/tv/data/epg/EpgInputWhiteList.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.data.epg; + +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.common.BuildConfig; +import com.android.tv.common.config.api.RemoteConfig; +import com.android.tv.common.experiments.Experiments; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** Checks if a package or a input is white listed. */ +public final class EpgInputWhiteList { + private static final boolean DEBUG = false; + private static final String TAG = "EpgInputWhiteList"; + @VisibleForTesting public static final String KEY = "live_channels_3rd_party_epg_inputs"; + private static final String QA_DEV_INPUTS = + "com.example.partnersupportsampletvinput/.SampleTvInputService"; + + /** Returns the package portion of a inputId */ + @Nullable + public static String getPackageFromInput(@Nullable String inputId) { + return inputId == null ? null : inputId.substring(0, inputId.indexOf("/")); + } + + private final RemoteConfig remoteConfig; + + public EpgInputWhiteList(RemoteConfig remoteConfig) { + this.remoteConfig = remoteConfig; + } + + public boolean isInputWhiteListed(String inputId) { + return getWhiteListedInputs().contains(inputId); + } + + public boolean isPackageWhiteListed(String packageName) { + if (DEBUG) Log.d(TAG, "isPackageWhiteListed " + packageName); + Set whiteList = getWhiteListedInputs(); + for (String good : whiteList) { + try { + String goodPackage = getPackageFromInput(good); + if (goodPackage.equals(packageName)) { + return true; + } + } catch (Exception e) { + if (DEBUG) Log.d(TAG, "Error parsing package name of " + good, e); + continue; + } + } + return false; + } + + private Set getWhiteListedInputs() { + Set result = toInputSet(remoteConfig.getString(KEY)); + if (BuildConfig.ENG || Experiments.ENABLE_QA_FEATURES.get()) { + HashSet moreInputs = new HashSet<>(toInputSet(QA_DEV_INPUTS)); + if (result.isEmpty()) { + result = moreInputs; + } else { + result.addAll(moreInputs); + } + } + if (DEBUG) Log.d(TAG, "getWhiteListedInputs " + result); + return result; + } + + private Set toInputSet(String value) { + if (TextUtils.isEmpty(value)) { + return Collections.EMPTY_SET; + } + return new HashSet(Arrays.asList(value.split(","))); + } +} diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java index d10a852c..4e4177c4 100644 --- a/src/com/android/tv/data/epg/EpgReader.java +++ b/src/com/android/tv/data/epg/EpgReader.java @@ -23,12 +23,28 @@ import com.android.tv.data.Channel; import com.android.tv.data.Lineup; import com.android.tv.data.Program; import com.android.tv.dvr.data.SeriesInfo; +//import com.google.auto.value.AutoValue; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; /** An interface used to retrieve the EPG data. This class should be used in worker thread. */ @WorkerThread public interface EpgReader { + + /** Value class that holds a EpgChannelId and its corresponding Channel */ + //@AutoValue + abstract class EpgChannel { + public static EpgChannel createEpgChannel(Channel channel, String epgChannelId) { + return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId); + } + + public abstract Channel getChannel(); + + public abstract String getEpgChannelId(); + } + /** Checks if the reader is available. */ boolean isAvailable(); @@ -55,7 +71,7 @@ public interface EpgReader { * Returns the list of channels for the given lineup. The returned channels should map into the * existing channels on the device. This method is usually called after selecting the lineup. */ - List getChannels(@NonNull String lineupId); + Set getChannels(Set inputChannels, @NonNull String lineupId); /** Pre-loads and caches channels for a given lineup. */ void preloadChannels(@NonNull String lineupId); @@ -65,18 +81,19 @@ public interface EpgReader { void clearCachedChannels(@NonNull String lineupId); /** - * Returns the programs for the given channel. Must call {@link #getChannels(String)} + * Returns the programs for the given channel. Must call {@link #getChannels(Set, String)} * beforehand. Note that the {@code Program} doesn't have valid program ID because it's not * retrieved from TvProvider. */ - List getPrograms(long channelId); + List getPrograms(EpgChannel epgChannel); /** * Returns the programs for the given channels. Note that the {@code Program} doesn't have valid * program ID because it's not retrieved from TvProvider. This method is only used to get * programs for a short duration typically. */ - Map> getPrograms(@NonNull List channelIds, long duration); + Map> getPrograms( + @NonNull Set epgChannels, long duration); /** Returns the series information for the given series ID. */ SeriesInfo getSeriesInfo(@NonNull String seriesId); diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java index 49409a1d..9a87619d 100644 --- a/src/com/android/tv/data/epg/StubEpgReader.java +++ b/src/com/android/tv/data/epg/StubEpgReader.java @@ -22,9 +22,11 @@ import com.android.tv.data.Channel; import com.android.tv.data.Lineup; import com.android.tv.data.Program; import com.android.tv.dvr.data.SeriesInfo; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; /** A stub class to read EPG. */ public class StubEpgReader implements EpgReader { @@ -56,8 +58,8 @@ public class StubEpgReader implements EpgReader { } @Override - public List getChannels(@NonNull String lineupId) { - return Collections.emptyList(); + public Set getChannels(Set inputChannels, @NonNull String lineupId) { + return Collections.emptySet(); } @Override @@ -71,12 +73,13 @@ public class StubEpgReader implements EpgReader { } @Override - public List getPrograms(long channelId) { + public List getPrograms(EpgChannel epgChannel) { return Collections.emptyList(); } @Override - public Map> getPrograms(@NonNull List channelIds, long duration) { + public Map> getPrograms( + @NonNull Set channels, long duration) { return Collections.emptyMap(); } diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java index 442a663d..173a2891 100644 --- a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java +++ b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java @@ -30,9 +30,8 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.dvr.DvrDataManager; @@ -45,6 +44,7 @@ import java.util.List; /** Displays the DVR history. */ @TargetApi(VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class DvrHistoryDialogFragment extends SafeDismissDialogFragment { public static final String DIALOG_TAG = DvrHistoryDialogFragment.class.getSimpleName(); @@ -53,7 +53,7 @@ public class DvrHistoryDialogFragment extends SafeDismissDialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); DvrDataManager dataManager = singletons.getDvrDataManager(); ChannelDataManager channelDataManager = singletons.getChannelDataManager(); for (ScheduledRecording schedule : dataManager.getAllScheduledRecordings()) { diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java index ccc3a983..71f45fbe 100644 --- a/src/com/android/tv/dialog/PinDialogFragment.java +++ b/src/com/android/tv/dialog/PinDialogFragment.java @@ -45,13 +45,13 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.util.TvSettings; public class PinDialogFragment extends SafeDismissDialogFragment { private static final String TAG = "PinDialogFragment"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; /** PIN code dialog for unlock channel */ public static final int PIN_DIALOG_TYPE_UNLOCK_CHANNEL = 0; @@ -68,7 +68,7 @@ public class PinDialogFragment extends SafeDismissDialogFragment { /** PIN code dialog for set new PIN */ public static final int PIN_DIALOG_TYPE_NEW_PIN = 3; - // PIN code dialog for checking old PIN. This is internal only. + // PIN code dialog for checking old PIN. Only used in this class. private static final int PIN_DIALOG_TYPE_OLD_PIN = 4; /** PIN code dialog for unlocking DVR playback */ @@ -192,7 +192,7 @@ public class PinDialogFragment extends SafeDismissDialogFragment { mTitleView.setText( getString( R.string.pin_enter_unlock_dvr, - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getTvInputManagerHelper() .getContentRatingsManager() .getDisplayNameForRating(tvContentRating))); diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java index 18460cb6..6eb67dfd 100644 --- a/src/com/android/tv/dialog/SafeDismissDialogFragment.java +++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java @@ -19,7 +19,7 @@ package com.android.tv.dialog; import android.app.Activity; import android.app.DialogFragment; import com.android.tv.MainActivity; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.HasTrackerLabel; import com.android.tv.analytics.Tracker; @@ -37,7 +37,7 @@ public abstract class SafeDismissDialogFragment extends DialogFragment implement if (activity instanceof MainActivity) { mActivity = (MainActivity) activity; } - mTracker = TvApplication.getSingletons(activity).getTracker(); + mTracker = TvSingletons.getSingletons(activity).getTracker(); if (mDismissPending) { mDismissPending = false; dismiss(); diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index 342e4b21..0befba9c 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -25,11 +25,11 @@ import android.util.ArraySet; import android.util.Log; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.util.Clock; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; import com.android.tv.dvr.data.SeriesRecording; -import com.android.tv.util.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index 17ea63a0..28006b08 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -38,9 +38,12 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Range; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.recording.RecordingStorageStatusManager.OnStorageMountChangedListener; +import com.android.tv.common.util.Clock; +import com.android.tv.common.util.CommonUtils; import com.android.tv.dvr.data.IdGenerator; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; @@ -58,11 +61,9 @@ import com.android.tv.dvr.provider.DvrDbSync; import com.android.tv.dvr.recorder.SeriesRecordingScheduler; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.AsyncDbTask.AsyncRecordedProgramQueryTask; -import com.android.tv.util.Clock; import com.android.tv.util.Filter; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.TvUriMatcher; -import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -114,7 +115,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private boolean mRecordedProgramLoadFinished; private final Set mPendingTasks = new ArraySet<>(); private DvrDbSync mDbSync; - private DvrStorageStatusManager mStorageStatusManager; + private RecordingStorageStatusManager mStorageStatusManager; private final TvInputCallback mInputCallback = new TvInputCallback() { @@ -140,7 +141,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { @Override public void onStorageMountChanged(boolean storageMounted) { for (TvInputInfo input : mInputManager.getTvInputInfos(true, true)) { - if (Utils.isBundledInput(input.getId())) { + if (CommonUtils.isBundledInput(input.getId())) { if (storageMounted) { unhideInput(input.getId()); } else { @@ -169,8 +170,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { public DvrDataManagerImpl(Context context, Clock clock) { super(context, clock); mContext = context; - mInputManager = TvApplication.getSingletons(context).getTvInputManagerHelper(); - mStorageStatusManager = TvApplication.getSingletons(context).getDvrStorageStatusManager(); + mInputManager = TvSingletons.getSingletons(context).getTvInputManagerHelper(); + mStorageStatusManager = + TvSingletons.getSingletons(context).getRecordingStorageStatusManager(); } public void start() { @@ -608,9 +610,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { SoftPreconditions.checkArgument( previousSeries == null, TAG, - "Attempt to add series" - + " recording with the duplicate series ID: " - + r.getSeriesId()); + "Attempt to add series" + " recording with the duplicate series ID: %s", + r.getSeriesId()); } if (mDvrLoadFinished) { notifySeriesRecordingAdded(seriesRecordings); @@ -779,13 +780,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { if (!SoftPreconditions.checkArgument( mSeriesRecordings.containsKey(r.getId()), TAG, - "Non Existing Series ID: " + r)) { + "Non Existing Series ID: %s", + r)) { continue; } SeriesRecording old1 = mSeriesRecordings.put(r.getId(), r); SeriesRecording old2 = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r); SoftPreconditions.checkArgument( - old1.equals(old2), TAG, "Series ID cannot be" + " updated: " + r); + old1.equals(old2), TAG, "Series ID cannot be updated: %s", r); } if (mDvrLoadFinished) { notifySeriesRecordingChanged(seriesRecordings); @@ -795,7 +797,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { private boolean isInputAvailable(String inputId) { return mInputManager.hasTvInputInfo(inputId) - && (!Utils.isBundledInput(inputId) || mStorageStatusManager.isStorageMounted()); + && (!CommonUtils.isBundledInput(inputId) + || mStorageStatusManager.isStorageMounted()); } private void removeDeletedSchedules(ScheduledRecording... addedSchedules) { diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java index 50751d95..247e1bc5 100644 --- a/src/com/android/tv/dvr/DvrManager.java +++ b/src/com/android/tv/dvr/DvrManager.java @@ -36,10 +36,10 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +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.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; @@ -78,9 +78,9 @@ public class DvrManager { public DvrManager(Context context) { SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); mAppContext = context.getApplicationContext(); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); - mScheduleManager = appSingletons.getDvrScheduleManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mDataManager = (WritableDvrDataManager) tvSingletons.getDvrDataManager(); + mScheduleManager = tvSingletons.getDvrScheduleManager(); if (mDataManager.isInitialized() && mScheduleManager.isInitialized()) { createSeriesRecordingsForRecordedProgramsIfNeeded(mDataManager.getRecordedPrograms()); } else { @@ -666,7 +666,7 @@ public class DvrManager { return false; } Program program = - TvApplication.getSingletons(mAppContext) + TvSingletons.getSingletons(mAppContext) .getProgramDataManager() .getCurrentProgram(channel.getId()); return program == null || !program.isRecordingProhibited(); @@ -683,7 +683,7 @@ public class DvrManager { return false; } Channel channel = - TvApplication.getSingletons(mAppContext) + TvSingletons.getSingletons(mAppContext) .getChannelDataManager() .getChannel(program.getChannelId()); if (channel == null || channel.isRecordingProhibited()) { @@ -833,7 +833,7 @@ public class DvrManager { if (!recordedProgramPath.exists()) { if (DEBUG) Log.d(TAG, "File to delete not exist: " + recordedProgramPath); } else { - Utils.deleteDirOrFile(recordedProgramPath); + CommonUtils.deleteDirOrFile(recordedProgramPath); if (DEBUG) { Log.d(TAG, "Sucessfully deleted files of the recorded program: " + dataUri); } diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java index 62f93c8b..cbb89290 100644 --- a/src/com/android/tv/dvr/DvrScheduleManager.java +++ b/src/com/android/tv/dvr/DvrScheduleManager.java @@ -25,8 +25,7 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Range; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; @@ -51,6 +50,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /** A class to manage the schedules. */ @TargetApi(Build.VERSION_CODES.N) @MainThread +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class DvrScheduleManager { private static final String TAG = "DvrScheduleManager"; @@ -94,9 +94,9 @@ public class DvrScheduleManager { public DvrScheduleManager(Context context) { mContext = context; - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDataManager = (DvrDataManagerImpl) appSingletons.getDvrDataManager(); - mChannelDataManager = appSingletons.getChannelDataManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mDataManager = (DvrDataManagerImpl) tvSingletons.getDvrDataManager(); + mChannelDataManager = tvSingletons.getChannelDataManager(); if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) { buildData(); } else { @@ -126,7 +126,7 @@ public class DvrScheduleManager { TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); if (!SoftPreconditions.checkArgument( - input != null, TAG, "Input was removed for : " + schedule)) { + input != null, TAG, "Input was removed for : %s", schedule)) { // Input removed. mInputScheduleMap.remove(schedule.getInputId()); mInputConflictInfoMap.remove(schedule.getInputId()); @@ -190,7 +190,7 @@ public class DvrScheduleManager { TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); if (!SoftPreconditions.checkArgument( - input != null, TAG, "Input was removed for : " + schedule)) { + input != null, TAG, "Input was removed for : %s", schedule)) { // Input removed. mInputScheduleMap.remove(schedule.getInputId()); mInputConflictInfoMap.remove(schedule.getInputId()); diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java index a2f4bda8..fe5a47b8 100644 --- a/src/com/android/tv/dvr/DvrStorageStatusManager.java +++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,272 +11,56 @@ * 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 + * limitations under the License. */ - package com.android.tv.dvr; -import android.content.BroadcastReceiver; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.OperationApplicationException; import android.database.Cursor; -import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.net.Uri; import android.os.AsyncTask; -import android.os.Environment; -import android.os.Looper; import android.os.RemoteException; -import android.os.StatFs; -import android.support.annotation.AnyThread; -import android.support.annotation.IntDef; -import android.support.annotation.WorkerThread; +import android.support.media.tv.TvContractCompat; import android.util.Log; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.TvSingletons; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.util.CommonUtils; import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; import java.io.File; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -/** Signals DVR storage status change such as plugging/unplugging. */ -public class DvrStorageStatusManager { +/** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed +public class DvrStorageStatusManager extends RecordingStorageStatusManager { private static final String TAG = "DvrStorageStatusManager"; - private static final boolean DEBUG = false; - - /** Minimum storage size to support DVR */ - public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB - private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES = - 10 * 1024 * 1024 * 1024L; // 10GB - private static final String RECORDING_DATA_SUB_PATH = "/recording"; + private final Context mContext; + private CleanUpDbTask mCleanUpDbTask; private static final String[] PROJECTION = { - TvContract.RecordedPrograms._ID, - TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI + TvContractCompat.RecordedPrograms._ID, + TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME, + TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI }; private static final int BATCH_OPERATION_COUNT = 100; - @IntDef({ - STORAGE_STATUS_OK, - STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL, - STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, - STORAGE_STATUS_MISSING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface StorageStatus {} - - /** Current storage is OK to record a program. */ - public static final int STORAGE_STATUS_OK = 0; - - /** Current storage's total capacity is smaller than DVR requirement. */ - public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1; - - /** Current storage's free space is insufficient to record programs. */ - public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2; - - /** Current storage is missing. */ - public static final int STORAGE_STATUS_MISSING = 3; - - private final Context mContext; - private final Set mOnStorageMountChangedListeners = - new CopyOnWriteArraySet<>(); - private final boolean mRunningInMainProcess; - private MountedStorageStatus mMountedStorageStatus; - private boolean mStorageValid; - private CleanUpDbTask mCleanUpDbTask; - - private class MountedStorageStatus { - private final boolean mStorageMounted; - private final File mStorageMountedDir; - private final long mStorageMountedCapacity; - - private MountedStorageStatus(boolean mounted, File mountedDir, long capacity) { - mStorageMounted = mounted; - mStorageMountedDir = mountedDir; - mStorageMountedCapacity = capacity; - } - - private boolean isValidForDvr() { - return mStorageMounted && mStorageMountedCapacity >= MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof MountedStorageStatus)) { - return false; - } - MountedStorageStatus status = (MountedStorageStatus) other; - return mStorageMounted == status.mStorageMounted - && Objects.equals(mStorageMountedDir, status.mStorageMountedDir) - && mStorageMountedCapacity == status.mStorageMountedCapacity; - } - } - - public interface OnStorageMountChangedListener { - - /** - * Listener for DVR storage status change. - * - * @param storageMounted {@code true} when DVR possible storage is mounted, {@code false} - * otherwise. - */ - void onStorageMountChanged(boolean storageMounted); - } - - private final class StorageStatusBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - MountedStorageStatus result = getStorageStatusInternal(); - if (mMountedStorageStatus.equals(result)) { - return; - } - mMountedStorageStatus = result; - if (result.mStorageMounted && mRunningInMainProcess) { - // Cleans up DB in LC process. - // Tuner process is not always on. - if (mCleanUpDbTask != null) { - mCleanUpDbTask.cancel(true); - } - mCleanUpDbTask = new CleanUpDbTask(); - mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - boolean valid = result.isValidForDvr(); - if (valid == mStorageValid) { - return; - } - mStorageValid = valid; - for (OnStorageMountChangedListener l : mOnStorageMountChangedListeners) { - l.onStorageMountChanged(valid); - } - } - } - - /** - * Creates DvrStorageStatusManager. - * - * @param context {@link Context} - */ - public DvrStorageStatusManager(final Context context, boolean runningInMainProcess) { + public DvrStorageStatusManager(Context context) { + super(context); mContext = context; - mRunningInMainProcess = runningInMainProcess; - mMountedStorageStatus = getStorageStatusInternal(); - mStorageValid = mMountedStorageStatus.isValidForDvr(); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MEDIA_MOUNTED); - filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - filter.addAction(Intent.ACTION_MEDIA_EJECT); - filter.addAction(Intent.ACTION_MEDIA_REMOVED); - filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); - filter.addDataScheme(ContentResolver.SCHEME_FILE); - mContext.registerReceiver(new StorageStatusBroadcastReceiver(), filter); - } - - /** - * Adds the listener for receiving storage status change. - * - * @param listener - */ - public void addListener(OnStorageMountChangedListener listener) { - mOnStorageMountChangedListeners.add(listener); - } - - /** Removes the current listener. */ - public void removeListener(OnStorageMountChangedListener listener) { - mOnStorageMountChangedListeners.remove(listener); - } - - /** Returns true if a storage is mounted. */ - public boolean isStorageMounted() { - return mMountedStorageStatus.mStorageMounted; - } - - /** Returns the path to DVR recording data directory. This can take for a while sometimes. */ - @WorkerThread - public File getRecordingRootDataDirectory() { - SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper()); - if (mMountedStorageStatus.mStorageMountedDir == null) { - return null; - } - File root = mContext.getExternalFilesDir(null); - String rootPath; - try { - rootPath = root != null ? root.getCanonicalPath() : null; - } catch (IOException | SecurityException e) { - return null; - } - return rootPath == null ? null : new File(rootPath + RECORDING_DATA_SUB_PATH); - } - - /** - * Returns the current storage status for DVR recordings. - * - * @return {@link StorageStatus} - */ - @AnyThread - public @StorageStatus int getDvrStorageStatus() { - MountedStorageStatus status = mMountedStorageStatus; - if (status.mStorageMountedDir == null) { - return STORAGE_STATUS_MISSING; - } - if (CommonFeatures.FORCE_RECORDING_UNTIL_NO_SPACE.isEnabled(mContext)) { - return STORAGE_STATUS_OK; - } - if (status.mStorageMountedCapacity < MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES) { - return STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL; - } - try { - StatFs statFs = new StatFs(status.mStorageMountedDir.toString()); - if (statFs.getAvailableBytes() < MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES) { - return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; - } - } catch (IllegalArgumentException e) { - // In rare cases, storage status change was not notified yet. - SoftPreconditions.checkState(false); - return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; - } - return STORAGE_STATUS_OK; } - /** - * Returns whether the storage has sufficient storage. - * - * @return {@code true} when there is sufficient storage, {@code false} otherwise - */ - public boolean isStorageSufficient() { - return getDvrStorageStatus() == STORAGE_STATUS_OK; - } - - private MountedStorageStatus getStorageStatusInternal() { - boolean storageMounted = - Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); - File storageMountedDir = storageMounted ? Environment.getExternalStorageDirectory() : null; - storageMounted = storageMounted && storageMountedDir != null; - long storageMountedCapacity = 0L; - if (storageMounted) { - try { - StatFs statFs = new StatFs(storageMountedDir.toString()); - storageMountedCapacity = statFs.getTotalBytes(); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Storage mount status was changed."); - storageMounted = false; - storageMountedDir = null; - } + @Override + protected void cleanUpDbIfNeeded() { + if (mCleanUpDbTask != null) { + mCleanUpDbTask.cancel(true); } - return new MountedStorageStatus(storageMounted, storageMountedDir, storageMountedCapacity); + mCleanUpDbTask = new CleanUpDbTask(); + mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private class CleanUpDbTask extends AsyncTask { @@ -288,11 +72,11 @@ public class DvrStorageStatusManager { @Override protected Boolean doInBackground(Void... params) { - @DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus(); - if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { + @StorageStatus int storageStatus = getDvrStorageStatus(); + if (storageStatus == STORAGE_STATUS_MISSING) { return null; } - if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { + if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { return true; } List ops = getDeleteOps(); @@ -310,7 +94,7 @@ public class DvrStorageStatusManager { ArrayList batchOps = new ArrayList<>(ops.subList(i, toIndex)); try { - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, batchOps); + mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps); } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, "Failed to clean up RecordedPrograms.", e); } @@ -321,16 +105,16 @@ public class DvrStorageStatusManager { @Override protected void onPostExecute(Boolean forgetStorage) { if (forgetStorage != null && forgetStorage == true) { - DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager(); TvInputManagerHelper tvInputManagerHelper = - TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + TvSingletons.getSingletons(mContext).getTvInputManagerHelper(); List tvInputInfoList = tvInputManagerHelper.getTvInputInfos(true, false); if (tvInputInfoList == null || tvInputInfoList.isEmpty()) { return; } for (TvInputInfo info : tvInputInfoList) { - if (Utils.isBundledInput(info.getId())) { + if (CommonUtils.isBundledInput(info.getId())) { dvrManager.forgetStorage(info.getId()); } } @@ -345,7 +129,7 @@ public class DvrStorageStatusManager { try (Cursor c = mContentResolver.query( - TvContract.RecordedPrograms.CONTENT_URI, + TvContractCompat.RecordedPrograms.CONTENT_URI, PROJECTION, null, null, @@ -354,10 +138,8 @@ public class DvrStorageStatusManager { return null; } while (c.moveToNext()) { - @DvrStorageStatusManager.StorageStatus - int storageStatus = getDvrStorageStatus(); - if (isCancelled() - || storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { + @StorageStatus int storageStatus = getDvrStorageStatus(); + if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) { ops.clear(); break; } @@ -368,7 +150,7 @@ public class DvrStorageStatusManager { continue; } Uri dataUri = Uri.parse(dataUriString); - if (!Utils.isInBundledPackageSet(packageName) + if (!CommonUtils.isInBundledPackageSet(packageName) || dataUri == null || dataUri.getPath() == null || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { @@ -378,7 +160,7 @@ public class DvrStorageStatusManager { if (!recordedProgramDir.exists()) { ops.add( ContentProviderOperation.newDelete( - TvContract.buildRecordedProgramUri( + TvContractCompat.buildRecordedProgramUri( Long.parseLong(id))) .build()); } diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java index 7da2bfc9..8616962f 100644 --- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java +++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java @@ -20,7 +20,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.media.tv.TvInputManager; import android.support.annotation.IntDef; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.util.SharedPreferencesUtils; import com.android.tv.dvr.data.RecordedProgram; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java index 18841ae5..e1fbca8c 100644 --- a/src/com/android/tv/dvr/data/RecordedProgram.java +++ b/src/com/android/tv/dvr/data/RecordedProgram.java @@ -30,10 +30,10 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.common.R; import com.android.tv.common.TvContentRatingCache; +import com.android.tv.common.util.CommonUtils; import com.android.tv.data.BaseProgram; import com.android.tv.data.GenreItems; import com.android.tv.data.InternalDataUtils; -import com.android.tv.util.Utils; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; @@ -118,7 +118,7 @@ public class RecordedProgram extends BaseProgram { .setInternalProviderFlag3(cursor.getInt(index++)) .setInternalProviderFlag4(cursor.getInt(index++)) .setVersionNumber(cursor.getInt(index++)); - if (Utils.isInBundledPackageSet(builder.mPackageName)) { + if (CommonUtils.isInBundledPackageSet(builder.mPackageName)) { InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); } return builder.build(); diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java index 7de37ebc..aa1dfc72 100644 --- a/src/com/android/tv/dvr/data/ScheduledRecording.java +++ b/src/com/android/tv/dvr/data/ScheduledRecording.java @@ -16,23 +16,25 @@ package com.android.tv.dvr.data; +import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.IntDef; import android.text.TextUtils; import android.util.Range; import com.android.tv.R; -import com.android.tv.TvApplication; +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.dvr.DvrScheduleManager; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.util.CompositeComparator; -import com.android.tv.util.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; @@ -40,6 +42,8 @@ import java.util.Comparator; import java.util.Objects; /** A data class for one recording contents. */ +@TargetApi(Build.VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public final class ScheduledRecording implements Parcelable { private static final String TAG = "ScheduledRecording"; @@ -655,7 +659,7 @@ public final class ScheduledRecording implements Parcelable { return mProgramTitle; } Channel channel = - TvApplication.getSingletons(context).getChannelDataManager().getChannel(mChannelId); + TvSingletons.getSingletons(context).getChannelDataManager().getChannel(mChannelId); return channel != null ? channel.getDisplayName() : context.getString(R.string.no_program_information); @@ -669,7 +673,7 @@ public final class ScheduledRecording implements Parcelable { case Schedules.TYPE_PROGRAM: return TYPE_PROGRAM; default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); + SoftPreconditions.checkArgument(false, TAG, "Unknown recording type %s", type); return TYPE_TIMED; } } @@ -682,7 +686,7 @@ public final class ScheduledRecording implements Parcelable { case TYPE_PROGRAM: return Schedules.TYPE_PROGRAM; default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type); + SoftPreconditions.checkArgument(false, TAG, "Unknown recording type %s", type); return Schedules.TYPE_TIMED; } } @@ -708,7 +712,7 @@ public final class ScheduledRecording implements Parcelable { case Schedules.STATE_RECORDING_CANCELED: return STATE_RECORDING_CANCELED; default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); + SoftPreconditions.checkArgument(false, TAG, "Unknown recording state %s", state); return STATE_RECORDING_NOT_STARTED; } } @@ -734,7 +738,7 @@ public final class ScheduledRecording implements Parcelable { case STATE_RECORDING_CANCELED: return Schedules.STATE_RECORDING_CANCELED; default: - SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state); + SoftPreconditions.checkArgument(false, TAG, "Unknown recording state %s", state); return Schedules.STATE_RECORDING_NOT_STARTED; } } @@ -765,12 +769,12 @@ public final class ScheduledRecording implements Parcelable { + ",type=" + mType + ",startTime=" - + Utils.toIsoDateTimeString(mStartTimeMs) + + CommonUtils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")" + ",endTime=" - + Utils.toIsoDateTimeString(mEndTimeMs) + + CommonUtils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")" diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java index 1fd1cea3..96b3425a 100644 --- a/src/com/android/tv/dvr/data/SeriesRecording.java +++ b/src/com/android/tv/dvr/data/SeriesRecording.java @@ -568,7 +568,7 @@ public class SeriesRecording implements Parcelable { mLongDescription, mSeriesId, mChannelOption, - mCanonicalGenreIds, + Arrays.hashCode(mCanonicalGenreIds), mPosterUri, mPhotoUri, mState); diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java index ad00bec8..db18e609 100644 --- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java +++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java @@ -20,17 +20,18 @@ import android.content.Context; import android.database.Cursor; import android.os.AsyncTask; import android.support.annotation.Nullable; +import com.android.tv.common.concurrent.NamedThreadFactory; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; -import com.android.tv.util.NamedThreadFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public abstract class AsyncDvrDbTask extends AsyncTask { private static final NamedThreadFactory THREAD_FACTORY = diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java index fb793a0e..0fb96d1b 100644 --- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java +++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java @@ -34,7 +34,7 @@ import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; /** A data class for one recorded contents. */ public class DvrDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "DvrDatabaseHelper"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int DATABASE_VERSION = 17; private static final String DB_NAME = "dvr.db"; diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java index 1cdeef24..8bd16221 100644 --- a/src/com/android/tv/dvr/provider/DvrDbSync.java +++ b/src/com/android/tv/dvr/provider/DvrDbSync.java @@ -29,7 +29,7 @@ import android.os.Looper; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; import android.util.Log; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; @@ -140,8 +140,8 @@ public class DvrDbSync { this( context, dataManager, - TvApplication.getSingletons(context).getChannelDataManager(), - TvApplication.getSingletons(context).getDvrManager(), + TvSingletons.getSingletons(context).getChannelDataManager(), + TvSingletons.getSingletons(context).getDvrManager(), SeriesRecordingScheduler.getInstance(context)); } diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java index e9ca11e5..7cdc7b73 100644 --- a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java +++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java @@ -25,8 +25,9 @@ import android.net.Uri; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -34,7 +35,6 @@ import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask; import com.android.tv.util.AsyncDbTask.CursorFilter; -import com.android.tv.util.PermissionUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -91,7 +91,7 @@ public abstract class EpisodicProgramLoadTask { */ public EpisodicProgramLoadTask(Context context, Collection seriesRecordings) { mContext = context.getApplicationContext(); - mDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); mSeriesRecordings.addAll(seriesRecordings); } diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java index 732815cd..f5bc7b9f 100644 --- a/src/com/android/tv/dvr/recorder/ConflictChecker.java +++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java @@ -27,11 +27,10 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.ArraySet; import android.util.Log; -import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener; import com.android.tv.MainActivity; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.WeakHandler; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; @@ -40,6 +39,7 @@ import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -88,21 +88,35 @@ public class ConflictChecker { new ScheduledRecordingListener() { @Override public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings); + if (DEBUG) { + Log.d( + TAG, + "onScheduledRecordingAdded: " + + Arrays.toString(scheduledRecordings)); + } mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); } @Override public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings); + if (DEBUG) { + Log.d( + TAG, + "onScheduledRecordingRemoved: " + + Arrays.toString(scheduledRecordings)); + } mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); } @Override public void onScheduledRecordingStatusChanged( ScheduledRecording... scheduledRecordings) { - if (DEBUG) - Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings); + if (DEBUG) { + Log.d( + TAG, + "onScheduledRecordingStatusChanged: " + + Arrays.toString(scheduledRecordings)); + } mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT); } }; @@ -119,10 +133,10 @@ public class ConflictChecker { public ConflictChecker(MainActivity mainActivity) { mMainActivity = mainActivity; - ApplicationSingletons appSingletons = TvApplication.getSingletons(mainActivity); - mChannelDataManager = appSingletons.getChannelDataManager(); - mScheduleManager = appSingletons.getDvrScheduleManager(); - mSessionManager = appSingletons.getInputSessionManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(mainActivity); + mChannelDataManager = tvSingletons.getChannelDataManager(); + mScheduleManager = tvSingletons.getDvrScheduleManager(); + mSessionManager = tvSingletons.getInputSessionManager(); } /** Starts checking the conflict. */ diff --git a/src/com/android/tv/dvr/recorder/DvrRecordingService.java b/src/com/android/tv/dvr/recorder/DvrRecordingService.java index 3b21bab2..9fdbf062 100644 --- a/src/com/android/tv/dvr/recorder/DvrRecordingService.java +++ b/src/com/android/tv/dvr/recorder/DvrRecordingService.java @@ -29,15 +29,15 @@ import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.annotation.VisibleForTesting; import android.util.Log; -import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.util.Clock; import com.android.tv.dvr.WritableDvrDataManager; -import com.android.tv.util.Clock; import com.android.tv.util.RecurringRunner; /** @@ -114,12 +114,12 @@ public class DvrRecordingService extends Service { @Override public void onCreate() { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(); SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG); sInstance = this; - ApplicationSingletons singletons = TvApplication.getSingletons(this); + TvSingletons singletons = TvSingletons.getSingletons(this); WritableDvrDataManager dataManager = (WritableDvrDataManager) singletons.getDvrDataManager(); mSessionManager = singletons.getInputSessionManager(); @@ -183,7 +183,6 @@ public class DvrRecordingService extends Service { @VisibleForTesting protected void startForegroundInternal(boolean hasUpcomingRecording) { - // STOPSHIP: Replace the content title with real UX strings Notification.Builder builder = new Notification.Builder(this) .setContentTitle(mContentTitle) @@ -204,7 +203,6 @@ public class DvrRecordingService extends Service { private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // STOPSHIP: Replace the channel name with real UX strings mNotificationChannel = new NotificationChannel( DVR_NOTIFICATION_CHANNEL_ID, diff --git a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java index f7521d6a..bb5ea99d 100644 --- a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java +++ b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java @@ -21,15 +21,16 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.support.annotation.RequiresApi; -import com.android.tv.TvApplication; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; /** Signals the DVR to start recording shows soon. */ @RequiresApi(Build.VERSION_CODES.N) public class DvrStartRecordingReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - TvApplication.setCurrentRunningProcess(context, true); - RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler(); + Starter.start(context); + RecordingScheduler scheduler = TvSingletons.getSingletons(context).getRecordingScheduler(); if (scheduler != null) { scheduler.updateAndStartServiceIfNeeded(); } diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java index ff46c7c3..722e75fc 100644 --- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java +++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java @@ -26,13 +26,13 @@ import android.util.ArrayMap; 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.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.util.Clock; import com.android.tv.util.CompositeComparator; import java.util.ArrayList; import java.util.Collections; @@ -443,6 +443,7 @@ public class InputTaskScheduler { break; case MSG_UPDATE_SCHEDULED_RECORDING: handleUpdateSchedule((ScheduledRecording) msg.obj); + break; case MSG_BUILD_SCHEDULE: handleBuildSchedule(); break; diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java index ea54f8c3..d631d84f 100644 --- a/src/com/android/tv/dvr/recorder/RecordingScheduler.java +++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java @@ -31,10 +31,10 @@ import android.support.annotation.VisibleForTesting; import android.util.ArrayMap; import android.util.Log; import android.util.Range; -import com.android.tv.ApplicationSingletons; import com.android.tv.InputSessionManager; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.Clock; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ChannelDataManager.Listener; import com.android.tv.dvr.DvrDataManager; @@ -43,7 +43,6 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.util.Clock; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.util.Arrays; @@ -120,10 +119,10 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco */ public static RecordingScheduler createScheduler(Context context) { SoftPreconditions.checkState( - TvApplication.getSingletons(context).getRecordingScheduler() == null); + TvSingletons.getSingletons(context).getRecordingScheduler() == null); HandlerThread handlerThread = new HandlerThread(HANDLER_THREAD_NAME); handlerThread.start(); - ApplicationSingletons singletons = TvApplication.getSingletons(context); + TvSingletons singletons = TvSingletons.getSingletons(context); return new RecordingScheduler( handlerThread.getLooper(), singletons.getDvrManager(), diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java index 85c6a0d5..4bd73e8a 100644 --- a/src/com/android/tv/dvr/recorder/RecordingTask.java +++ b/src/com/android/tv/dvr/recorder/RecordingTask.java @@ -33,14 +33,15 @@ import android.widget.Toast; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.RecordingSession; import com.android.tv.R; -import com.android.tv.TvApplication; +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.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper; -import com.android.tv.util.Clock; import com.android.tv.util.Utils; import java.util.Comparator; import java.util.concurrent.TimeUnit; @@ -178,7 +179,7 @@ public class RecordingTask extends RecordingCallback release(); return false; default: - SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg); + SoftPreconditions.checkArgument(false, TAG, "unexpected message type %s", msg); break; } return true; @@ -253,7 +254,7 @@ public class RecordingTask extends RecordingCallback new Runnable() { @Override public void run() { - if (TvApplication.getSingletons(mContext) + if (TvSingletons.getSingletons(mContext) .getMainActivityWrapper() .isResumed()) { ScheduledRecording scheduledRecording = @@ -281,7 +282,7 @@ public class RecordingTask extends RecordingCallback } } }); - // Pass through + // fall through default: failAndQuit(); break; @@ -426,7 +427,7 @@ public class RecordingTask extends RecordingCallback + " with a delay of " + delay / 1000 + " seconds to arrive at " - + Utils.toIsoDateTimeString(when)); + + CommonUtils.toIsoDateTimeString(when)); } return mHandler.sendEmptyMessageDelayed(what, delay); } diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java index c59d4a93..f30308f3 100644 --- a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java +++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java @@ -18,10 +18,10 @@ 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; import com.android.tv.dvr.data.SeriesRecording; -import com.android.tv.util.Clock; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java index 05f876ad..4f7a789b 100644 --- a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java +++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java @@ -27,13 +27,13 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.LongSparseArray; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; -import com.android.tv.common.CollectionUtils; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.experiments.Experiments; +import com.android.tv.common.util.CollectionUtils; +import com.android.tv.common.util.SharedPreferencesUtils; import com.android.tv.data.Program; -import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.data.epg.EpgReader; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; @@ -44,8 +44,6 @@ import com.android.tv.dvr.data.SeasonEpisodeNumber; import com.android.tv.dvr.data.SeriesInfo; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.EpisodicProgramLoadTask; -import com.android.tv.experiments.Experiments; -import com.android.tv.util.LocationUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -58,6 +56,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import javax.inject.Provider; /** * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for the {@link @@ -208,9 +207,9 @@ public class SeriesRecordingScheduler { private SeriesRecordingScheduler(Context context) { mContext = context.getApplicationContext(); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDvrManager = appSingletons.getDvrManager(); - mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mDvrManager = tvSingletons.getDvrManager(); + mDataManager = (WritableDvrDataManager) tvSingletons.getDvrDataManager(); mSharedPreferences = context.getSharedPreferences( SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); @@ -263,7 +262,10 @@ public class SeriesRecordingScheduler { private void executeFetchSeriesInfoTask(SeriesRecording seriesRecording) { if (Experiments.CLOUD_EPG.get()) { - FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording); + FetchSeriesInfoTask task = + new FetchSeriesInfoTask( + seriesRecording, + TvSingletons.getSingletons(mContext).providesEpgReader()); task.execute(); mFetchSeriesInfoTasks.put(seriesRecording.getId(), task); } @@ -534,16 +536,18 @@ public class SeriesRecordingScheduler { } private class FetchSeriesInfoTask extends AsyncTask { - private SeriesRecording mSeriesRecording; + private final SeriesRecording mSeriesRecording; + private final Provider mEpgReaderProvider; - FetchSeriesInfoTask(SeriesRecording seriesRecording) { + FetchSeriesInfoTask( + SeriesRecording seriesRecording, Provider epgReaderProvider) { mSeriesRecording = seriesRecording; + mEpgReaderProvider = epgReaderProvider; } @Override protected SeriesInfo doInBackground(Void... voids) { - return EpgFetcher.createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext)) - .getSeriesInfo(mSeriesRecording.getSeriesId()); + return mEpgReaderProvider.get().getSeriesInfo(mSeriesRecording.getSeriesId()); } @Override diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java index f4077e44..fce94230 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java @@ -25,7 +25,7 @@ import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.RecordedProgram; @@ -49,7 +49,7 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment { public void onAttach(Context context) { super.onAttach(context); mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); - DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(context).getDvrManager(); mDuplicate = dvrManager.getRecordedProgram( mProgram.getTitle(), diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java index f27ec5c5..456ad830 100644 --- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java @@ -26,7 +26,7 @@ import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.text.format.DateUtils; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -50,7 +50,7 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment { public void onAttach(Context context) { super.onAttach(context); mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); - DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(context).getDvrManager(); mDuplicate = dvrManager.getScheduledRecording( mProgram.getTitle(), diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java index e247b82b..24a6fcd3 100644 --- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java @@ -22,7 +22,7 @@ import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.dvr.DvrManager; @@ -42,7 +42,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen if (args != null) { long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); mChannel = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getChannelDataManager() .getChannel(channelId); } @@ -90,7 +90,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen @Override public void onTrackedGuidedActionClicked(GuidedAction action) { - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); long duration = mDurations.get((int) action.getId()); long startTimeMs = System.currentTimeMillis(); long endTimeMs = System.currentTimeMillis() + duration; diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java index 80011acd..641f86c1 100644 --- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java +++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java @@ -29,7 +29,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.Program; @@ -149,7 +149,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { private String getScheduleTitle(ScheduledRecording schedule) { if (schedule.getType() == ScheduledRecording.TYPE_TIMED) { Channel channel = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getChannelDataManager() .getChannel(schedule.getChannelId()); if (channel != null) { @@ -179,7 +179,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { List conflicts = null; if (input != null) { conflicts = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager() .getConflictingSchedules(mProgram); } @@ -227,7 +227,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { Bundle args = getArguments(); long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID); mChannel = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getChannelDataManager() .getChannel(channelId); SoftPreconditions.checkArgument(mChannel != null); @@ -238,7 +238,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment { mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS); mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS); conflicts = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager() .getConflictingSchedules( mChannel.getId(), mStartTimeMs, mEndTimeMs); diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java index 8524e1ea..793bd01b 100644 --- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java +++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java @@ -26,14 +26,13 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; +import com.android.tv.common.recording.RecordingStorageStatusManager; import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener; import com.android.tv.dialog.SafeDismissDialogFragment; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrStorageStatusManager; import java.util.List; public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { @@ -56,7 +55,7 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { @Override public void onAttach(Context context) { super.onAttach(context); - ApplicationSingletons singletons = TvApplication.getSingletons(context); + TvSingletons singletons = TvSingletons.getSingletons(context); mDvrManager = singletons.getDvrManager(); } @@ -115,8 +114,8 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { } /** - * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment - * .DvrNoFreeSpaceErrorDialogFragment}. + * The inner guided step fragment for {@link + * com.android.tv.dvr.ui.DvrHalfSizedDialogFragment .DvrNoFreeSpaceErrorDialogFragment}. */ public static class DvrNoFreeSpaceErrorFragment extends DvrGuidedStepFragment { @Override @@ -155,7 +154,8 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { } /** - * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment + * The inner guided step fragment for {@link + * com.android.tv.dvr.ui.DvrHalfSizedDialogFragment * .DvrSmallSizedStorageErrorDialogFragment}. */ public static class DvrSmallSizedStorageErrorFragment extends DvrGuidedStepFragment { @@ -166,7 +166,7 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment { getResources() .getString( R.string.dvr_error_small_sized_storage_description, - DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES + RecordingStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024 / 1024 / 1024); diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java index ad26a5c2..6fba4d98 100644 --- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java +++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java @@ -23,7 +23,7 @@ import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.ui.browse.DvrBrowseActivity; import java.util.ArrayList; @@ -102,7 +102,7 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment { Activity activity = getActivity(); actions.add( new GuidedAction.Builder(activity).clickAction(GuidedAction.ACTION_ID_OK).build()); - if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) { + if (TvSingletons.getSingletons(getContext()).getDvrManager().hasValidItems()) { actions.add( new GuidedAction.Builder(activity) .id(ACTION_VIEW_RECENT_RECORDINGS) diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java index 03124260..5bb97e90 100644 --- a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java +++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java @@ -16,9 +16,11 @@ package com.android.tv.dvr.ui; +import android.annotation.TargetApi; import android.app.FragmentManager; import android.content.Context; import android.graphics.Typeface; +import android.os.Build; import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; @@ -27,7 +29,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; @@ -36,6 +38,8 @@ import java.util.ArrayList; import java.util.List; /** Fragment for DVR series recording settings. */ +@TargetApi(Build.VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { /** Name of series recording id starting the fragment. Type: Long */ public static final String COME_FROM_SERIES_RECORDING_ID = "series_recording_id"; @@ -62,7 +66,7 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { .setPriority(Long.MAX_VALUE) .setId(ONE_TIME_RECORDING_ID) .build()); - DvrDataManager dvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + DvrDataManager dvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); long comeFromSeriesRecordingId = getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1); for (SeriesRecording series : dvrDataManager.getSeriesRecordings()) { if (series.getState() == SeriesRecording.STATE_SERIES_NORMAL @@ -127,7 +131,7 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment { public void onTrackedGuidedActionClicked(GuidedAction action) { long actionId = action.getId(); if (actionId == ACTION_ID_SAVE) { - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); int size = mSeriesRecordings.size(); for (int i = 1; i < size; ++i) { long priority = DvrScheduleManager.suggestSeriesPriority(size - i); diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java index 854fea56..5251e140 100644 --- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java +++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java @@ -27,7 +27,7 @@ import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import android.text.format.DateUtils; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.dvr.DvrManager; @@ -63,16 +63,18 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM); mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false); } - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); SoftPreconditions.checkArgument( mProgram != null && mProgram.isEpisodic(), TAG, - "The program should be episodic: " + mProgram); + "The program should be episodic: %s ", + mProgram); SeriesRecording seriesRecording = dvrManager.getSeriesRecording(mProgram); SoftPreconditions.checkArgument( seriesRecording == null || seriesRecording.isStopped(), TAG, - "The series recording should be stopped or null: " + seriesRecording); + "The series recording should be stopped or null: %s", + seriesRecording); super.onCreate(savedInstanceState); } @@ -144,7 +146,7 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { } } else if (action.getId() == ACTION_RECORD_SERIES) { SeriesRecording seriesRecording = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrDataManager() .getSeriesRecording(mProgram.getSeriesId()); if (seriesRecording == null) { @@ -159,7 +161,7 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment { seriesRecording = SeriesRecording.buildFrom(seriesRecording) .setPriority( - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrScheduleManager() .suggestNewSeriesPriority()) .build(); diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java index 8b05cf1c..a2ae1f97 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java @@ -20,7 +20,7 @@ import android.app.Activity; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; /** Activity to show details view in DVR. */ public class DvrSeriesDeletionActivity extends Activity { @@ -29,7 +29,7 @@ public class DvrSeriesDeletionActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_dvr_series_settings); // Check savedInstanceState to prevent that activity is being showed with animation. diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java index 5f2c3582..685f0a58 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java @@ -27,7 +27,7 @@ import android.text.TextUtils; import android.view.ViewGroup.LayoutParams; import android.widget.Toast; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; @@ -67,9 +67,9 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { mSeriesRecordingId = getArguments().getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1); SoftPreconditions.checkArgument(mSeriesRecordingId != -1); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); mDvrWatchedPositionManager = - TvApplication.getSingletons(context).getDvrWatchedPositionManager(); + TvSingletons.getSingletons(context).getDvrWatchedPositionManager(); mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId); mOneLineActionHeight = getResources() @@ -166,7 +166,7 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment { } } if (!idsToDelete.isEmpty()) { - DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getActivity()).getDvrManager(); dvrManager.removeRecordedPrograms(idsToDelete); } Toast.makeText( diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java index d600b54d..edb62c96 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java @@ -23,7 +23,7 @@ import android.os.Bundle; import android.support.v17.leanback.widget.GuidanceStylist; import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Program; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -68,7 +68,7 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { getArguments() .getBoolean(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION); mSeriesRecording = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getDvrDataManager() .getSeriesRecording(seriesRecordingId); if (mSeriesRecording == null) { @@ -78,12 +78,12 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment { mPrograms = (List) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS); BigArguments.reset(); mSchedulesAddedCount = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager() .getAvailableScheduledRecording(mSeriesRecording.getId()) .size(); DvrScheduleManager dvrScheduleManager = - TvApplication.getSingletons(context).getDvrScheduleManager(); + TvSingletons.getSingletons(context).getDvrScheduleManager(); List conflictingRecordings = dvrScheduleManager.getConflictingSchedules(mSeriesRecording); mHasConflict = !conflictingRecordings.isEmpty(); diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java index 117f72d8..1a51cf46 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java @@ -21,7 +21,7 @@ import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; import com.android.tv.common.SoftPreconditions; /** Activity to show details view in DVR. */ @@ -60,7 +60,7 @@ public class DvrSeriesSettingsActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_dvr_series_settings); long seriesRecordingId = getIntent().getLongExtra(SERIES_RECORDING_ID, -1); diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java index c44e44a3..9383058a 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java @@ -16,8 +16,10 @@ package com.android.tv.dvr.ui; +import android.annotation.TargetApi; import android.app.FragmentManager; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; @@ -25,7 +27,7 @@ import android.support.v17.leanback.widget.GuidedAction; import android.support.v17.leanback.widget.GuidedActionsStylist; import android.util.LongSparseArray; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; @@ -43,6 +45,8 @@ import java.util.List; import java.util.Set; /** Fragment for DVR series recording settings. */ +@TargetApi(Build.VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class DvrSeriesSettingsFragment extends GuidedStepFragment implements DvrDataManager.SeriesRecordingListener { private static final String TAG = "SeriesSettingsFragment"; @@ -81,7 +85,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment public void onAttach(Context context) { super.onAttach(context); mBackStackCount = getFragmentManager().getBackStackEntryCount(); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID); mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); if (mSeriesRecording == null) { @@ -102,7 +106,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment } Set channelIds = new HashSet<>(); ChannelDataManager channelDataManager = - TvApplication.getSingletons(context).getChannelDataManager(); + TvSingletons.getSingletons(context).getChannelDataManager(); for (Program program : mPrograms) { long channelId = program.getChannelId(); if (channelIds.add(channelId)) { @@ -208,7 +212,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment if (mSelectedChannelId != Channel.INVALID_ID) { builder.setChannelId(mSelectedChannelId); } - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); dvrManager.updateSeriesRecording(builder.build()); if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL @@ -328,7 +332,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment recordingCandidates) .get(mSeriesRecordingId); if (!programsToSchedule.isEmpty()) { - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager() .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule); } diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java index 6f34e8a0..e93387ab 100644 --- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java @@ -26,7 +26,7 @@ import android.support.annotation.NonNull; import android.support.v17.leanback.widget.GuidanceStylist.Guidance; import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.data.ScheduledRecording; @@ -100,7 +100,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment { dismissDialog(); return; } - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); mDvrDataManager.addScheduledRecordingListener(mScheduledRecordingListener); mStopReason = args.getInt(KEY_REASON); } diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java index 3d84f48f..99211fdb 100644 --- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java +++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java @@ -25,9 +25,8 @@ import android.support.v17.leanback.widget.GuidedAction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -77,7 +76,7 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment { @Override public void onTrackedGuidedActionClicked(GuidedAction action) { if (action.getId() == ACTION_STOP_SERIES_RECORDING) { - ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); DvrManager dvrManager = singletons.getDvrManager(); DvrDataManager dataManager = singletons.getDvrDataManager(); List toDelete = new ArrayList<>(); diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java index ae60f4a4..6373b30f 100644 --- a/src/com/android/tv/dvr/ui/DvrUiHelper.java +++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java @@ -39,14 +39,15 @@ import android.widget.ImageView; import android.widget.Toast; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; 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.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrStorageStatusManager; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; @@ -91,17 +92,17 @@ public class DvrUiHelper { */ public static void checkStorageStatusAndShowErrorMessage( Activity activity, String inputId, Runnable recordingRequestRunnable) { - if (Utils.isBundledInput(inputId)) { - switch (TvApplication.getSingletons(activity) - .getDvrStorageStatusManager() + if (CommonUtils.isBundledInput(inputId)) { + switch (TvSingletons.getSingletons(activity) + .getRecordingStorageStatusManager() .getDvrStorageStatus()) { - case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL: + case RecordingStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL: showDvrSmallSizedStorageErrorDialog(activity); return; - case DvrStorageStatusManager.STORAGE_STATUS_MISSING: + case RecordingStorageStatusManager.STORAGE_STATUS_MISSING: showDvrMissingStorageErrorDialog(activity); return; - case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT: + case RecordingStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT: showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable); return; } @@ -281,7 +282,7 @@ public class DvrUiHelper { if (program == null) { return false; } - DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(activity).getDvrManager(); if (!program.isEpisodic()) { // One time recording. dvrManager.addSchedule(program); @@ -392,7 +393,7 @@ public class DvrUiHelper { return; } List conflicts = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getDvrManager() .getConflictingSchedulesForTune(channel.getId()); startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); @@ -443,7 +444,7 @@ public class DvrUiHelper { boolean showViewScheduleOptionInDialog, Program currentProgram) { SeriesRecording series = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getDvrDataManager() .getSeriesRecording(seriesRecordingId); if (series == null) { diff --git a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java index 0a24187a..0172f76f 100644 --- a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java +++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java @@ -19,7 +19,7 @@ package com.android.tv.dvr.ui; import android.content.Context; import android.support.v17.leanback.app.GuidedStepFragment; import android.support.v17.leanback.widget.GuidedAction; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; /** A {@link GuidedStepFragment} with {@link Tracker} for analytics. */ @@ -29,7 +29,7 @@ public abstract class TrackedGuidedStepFragment extends GuidedStepFragment { @Override public void onAttach(Context context) { super.onAttach(context); - mTracker = TvApplication.getSingletons(context).getAnalytics().getDefaultTracker(); + mTracker = TvSingletons.getSingletons(context).getAnalytics().getDefaultTracker(); } @Override diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java index 22246e5a..7e7e1f75 100644 --- a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java @@ -22,7 +22,7 @@ import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; @@ -66,7 +66,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { @Override public void onAttach(Context context) { super.onAttach(context); - mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrDataManger = TvSingletons.getSingletons(context).getDvrDataManager(); mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener); } @@ -100,7 +100,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment { public void onActionClick(long actionId) { if (actionId == DvrStopRecordingFragment.ACTION_STOP) { DvrManager dvrManager = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager(); dvrManager.stopRecording(getRecording()); getActivity().finish(); diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java index 9f588aa3..70903373 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java @@ -21,7 +21,7 @@ import android.media.tv.TvContract; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; @@ -76,7 +76,7 @@ class DetailsContent { static DetailsContent createFromScheduledRecording( Context context, ScheduledRecording scheduledRecording) { Channel channel = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getChannelDataManager() .getChannel(scheduledRecording.getChannelId()); String description = @@ -278,7 +278,7 @@ class DetailsContent { /** Builds details content. */ public DetailsContent build(Context context) { Channel channel = - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getChannelDataManager() .getChannel(mChannelId); if (mDetailsContent.mTitle == null) { diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java index 5a058454..849360b8 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java @@ -57,6 +57,7 @@ class DetailsViewBackgroundHelper { public DetailsViewBackgroundHelper(Activity activity) { mBackgroundManager = BackgroundManager.getInstance(activity); mBackgroundManager.attach(activity.getWindow()); + mBackgroundManager.setAutoReleaseOnStop(false); } /** Sets the given image to background. */ diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java index f208b5e8..6cc1c7a1 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java @@ -21,7 +21,7 @@ import android.content.Intent; import android.media.tv.TvInputManager; import android.os.Bundle; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; /** {@link android.app.Activity} for DVR UI. */ public class DvrBrowseActivity extends Activity { @@ -29,7 +29,7 @@ public class DvrBrowseActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(savedInstanceState); setContentView(R.layout.dvr_main); mFragment = (DvrBrowseFragment) getFragmentManager().findFragmentById(R.id.dvr_frame); diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java index f8a54ef0..90326a8b 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java @@ -16,7 +16,9 @@ package com.android.tv.dvr.ui.browse; +import android.annotation.TargetApi; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v17.leanback.app.BrowseFragment; @@ -29,9 +31,8 @@ import android.support.v17.leanback.widget.TitleViewAdapter; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.GenreItems; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; @@ -51,6 +52,8 @@ import java.util.HashMap; import java.util.List; /** {@link BrowseFragment} for DVR functions. */ +@TargetApi(Build.VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class DvrBrowseFragment extends BrowseFragment implements RecordedProgramListener, ScheduledRecordingListener, @@ -168,7 +171,7 @@ public class DvrBrowseFragment extends BrowseFragment if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); Context context = getContext(); - ApplicationSingletons singletons = TvApplication.getSingletons(context); + TvSingletons singletons = TvSingletons.getSingletons(context); mDvrDataManager = singletons.getDvrDataManager(); mDvrScheudleManager = singletons.getDvrScheduleManager(); mPresenterSelector = diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java index a953f1d2..2659c3f3 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java @@ -23,7 +23,7 @@ import android.transition.Transition; import android.transition.Transition.TransitionListener; import android.view.View; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; import com.android.tv.dialog.PinDialogFragment; /** Activity to show details view in DVR. */ @@ -59,7 +59,7 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On @Override public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_dvr_details); long recordId = getIntent().getLongExtra(RECORDING_ID, -1); diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java index f03f3f58..209fc6e1 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java @@ -37,8 +37,9 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.text.TextUtils; import android.widget.Toast; import com.android.tv.R; -import com.android.tv.TvApplication; +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.dialog.PinDialogFragment; @@ -48,7 +49,6 @@ 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.Utils; import java.io.File; abstract class DvrDetailsFragment extends DetailsFragment { @@ -195,7 +195,7 @@ abstract class DvrDetailsFragment extends DetailsFragment { } protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) { - if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) + if (CommonUtils.isInBundledPackageSet(recordedProgram.getPackageName()) && !isDataUriAccessible(recordedProgram.getDataUri())) { // Since cleaning RecordedProgram from forgotten storage will take some time, // ignore playback until cleaning is finished. @@ -207,7 +207,7 @@ abstract class DvrDetailsFragment extends DetailsFragment { } long programId = recordedProgram.getId(); ParentalControlSettings parental = - TvApplication.getSingletons(getActivity()) + TvSingletons.getSingletons(getActivity()) .getTvInputManagerHelper() .getParentalControlSettings(); if (!parental.isParentalControlsEnabled()) { @@ -215,7 +215,7 @@ abstract class DvrDetailsFragment extends DetailsFragment { return; } ChannelDataManager channelDataManager = - TvApplication.getSingletons(getActivity()).getChannelDataManager(); + TvSingletons.getSingletons(getActivity()).getChannelDataManager(); Channel channel = channelDataManager.getChannel(recordedProgram.getChannelId()); if (channel != null && channel.isLocked()) { checkPinToPlay(recordedProgram, seekTimeMs); diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java index 4298d86a..1e5f6935 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java @@ -31,8 +31,9 @@ import java.util.Set; /** * An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in * {@link DvrBrowseFragment}. DVR items might include: {@link - * com.android.tv.dvr.data.ScheduledRecording}, {@link com.android.tv.dvr.data.RecordedProgram}, and - * {@link com.android.tv.dvr.data.SeriesRecording}. + * com.android.tv.dvr.data.ScheduledRecording}, {@link + * com.android.tv.dvr.data.RecordedProgram}, and {@link + * com.android.tv.dvr.data.SeriesRecording}. */ public abstract class DvrItemPresenter extends Presenter { protected final Context mContext; diff --git a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java index 88133331..af0f24c0 100644 --- a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java @@ -20,7 +20,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.view.View; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.util.Utils; @@ -50,7 +50,7 @@ class FullSchedulesCardPresenter extends DvrItemPresenter { cardView.setTitle(mCardTitleText); cardView.setImage(mIconDrawable); List scheduledRecordings = - TvApplication.getSingletons(mContext) + TvSingletons.getSingletons(mContext) .getDvrDataManager() .getAvailableScheduledRecordings(); int fullDays = 0; diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java index 3b3401b2..47b1a198 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java @@ -23,7 +23,7 @@ import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrWatchedPositionManager; @@ -44,7 +44,7 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment @Override public void onCreate(Bundle savedInstanceState) { - mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(getContext()).getDvrDataManager(); mDvrDataManager.addRecordedProgramListener(this); super.onCreate(savedInstanceState); } @@ -52,7 +52,7 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment @Override public void onCreateInternal() { mDvrWatchedPositionManager = - TvApplication.getSingletons(getActivity()).getDvrWatchedPositionManager(); + TvSingletons.getSingletons(getActivity()).getDvrWatchedPositionManager(); setDetailsOverviewRow( DetailsContent.createFromRecordedProgram(getContext(), mRecordedProgram)); } @@ -139,7 +139,7 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment mRecordedProgram.getId())); } else if (action.getId() == ACTION_DELETE_RECORDING) { DvrManager dvrManager = - TvApplication.getSingletons(getActivity()).getDvrManager(); + TvSingletons.getSingletons(getActivity()).getDvrManager(); dvrManager.removeRecordedProgram(mRecordedProgram); getActivity().finish(); } diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java index aad1cc6a..e2db3ac4 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java @@ -19,7 +19,7 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.media.tv.TvInputManager; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener; import com.android.tv.dvr.data.RecordedProgram; @@ -95,7 +95,7 @@ public class RecordedProgramPresenter extends DvrItemPresenter mTodayString = mContext.getString(R.string.dvr_date_today); mYesterdayString = mContext.getString(R.string.dvr_date_yesterday); mDvrWatchedPositionManager = - TvApplication.getSingletons(mContext).getDvrWatchedPositionManager(); + TvSingletons.getSingletons(mContext).getDvrWatchedPositionManager(); mProgressBarColor = mContext.getResources().getColor(R.color.play_controls_progress_bar_watched); mShowEpisodeTitle = showEpisodeTitle; diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java index edee5d3a..0a204c14 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java +++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java @@ -37,8 +37,8 @@ import com.android.tv.ui.ViewUtils; import com.android.tv.util.ImageLoader; /** - * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} or - * {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. + * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} + * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. */ public class RecordingCardView extends BaseCardView { // This value should be the same with diff --git a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java index c8f1c785..e4d95630 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java @@ -18,7 +18,7 @@ package com.android.tv.dvr.ui.browse; import android.os.Bundle; import android.support.v17.leanback.app.DetailsFragment; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.data.ScheduledRecording; /** {@link DetailsFragment} for recordings in DVR. */ @@ -35,7 +35,7 @@ abstract class RecordingDetailsFragment extends DvrDetailsFragment { protected boolean onLoadRecordingDetails(Bundle args) { long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID); mRecording = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrDataManager() .getScheduledRecording(scheduledRecordingId); return mRecording != null; diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java index b3e6ebb3..0765117d 100644 --- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java @@ -22,7 +22,7 @@ import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.ui.DvrUiHelper; @@ -37,7 +37,7 @@ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment @Override public void onCreate(Bundle savedInstance) { - mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); mHideViewSchedule = getArguments().getBoolean(DvrDetailsActivity.HIDE_VIEW_SCHEDULE); super.onCreate(savedInstance); } diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java index fa948447..f1ed52c8 100644 --- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java @@ -19,7 +19,7 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.os.Handler; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.util.Utils; @@ -100,7 +100,7 @@ class ScheduledRecordingPresenter extends DvrItemPresenter { public ScheduledRecordingPresenter(Context context) { super(context); - mDvrManager = TvApplication.getSingletons(mContext).getDvrManager(); + mDvrManager = TvSingletons.getSingletons(mContext).getDvrManager(); mProgressBarColor = mContext.getResources() .getColor(R.color.play_controls_recording_icon_color_on_focus); diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java index 48bc9cbd..2cd191a7 100644 --- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java @@ -33,7 +33,7 @@ import android.support.v17.leanback.widget.PresenterSelector; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import android.text.TextUtils; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.BaseProgram; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrWatchedPositionManager; @@ -73,7 +73,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment @Override public void onCreate(Bundle savedInstanceState) { - mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(getActivity()).getDvrDataManager(); mWatchLabel = getString(R.string.dvr_detail_watch); mResumeLabel = getString(R.string.dvr_detail_series_resume); mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null); @@ -84,7 +84,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment @Override protected void onCreateInternal() { mDvrWatchedPositionManager = - TvApplication.getSingletons(getActivity()).getDvrWatchedPositionManager(); + TvSingletons.getSingletons(getActivity()).getDvrWatchedPositionManager(); setDetailsOverviewRow(DetailsContent.createFromSeriesRecording(getContext(), mSeries)); setupRecordedProgramsRow(); mDvrDataManager.addSeriesRecordingListener(this); @@ -137,7 +137,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment protected boolean onLoadRecordingDetails(Bundle args) { long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID); mSeries = - TvApplication.getSingletons(getActivity()) + TvSingletons.getSingletons(getActivity()) .getDvrDataManager() .getSeriesRecording(recordId); if (mSeries == null) { diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java index 02ce24ef..14f9dceb 100644 --- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java @@ -19,9 +19,8 @@ package com.android.tv.dvr.ui.browse; import android.content.Context; import android.media.tv.TvInputManager; import android.text.TextUtils; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; @@ -186,7 +185,7 @@ class SeriesRecordingPresenter extends DvrItemPresenter { public SeriesRecordingPresenter(Context context) { super(context); - ApplicationSingletons singletons = TvApplication.getSingletons(context); + TvSingletons singletons = TvSingletons.getSingletons(context); mDvrDataManager = singletons.getDvrDataManager(); mDvrManager = singletons.getDvrManager(); mWatchedPositionManager = singletons.getDvrWatchedPositionManager(); diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java index 42c7086a..84298bdf 100644 --- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java @@ -23,9 +23,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -50,7 +49,7 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment mRowsAdapter = onCreateRowsAdapter(presenterSelector); setAdapter(mRowsAdapter); mRowsAdapter.start(); - ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); singletons.getDvrDataManager().addScheduledRecordingListener(this); singletons.getDvrScheduleManager().addOnConflictStateChangeListener(this); mEmptyInfoScreenView = (TextView) getActivity().findViewById(R.id.empty_info_screen); @@ -96,7 +95,7 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment @Override public void onDestroy() { - ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); singletons.getDvrScheduleManager().removeOnConflictStateChangeListener(this); singletons.getDvrDataManager().removeScheduledRecordingListener(this); mRowsAdapter.stop(); diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java index 11df780c..82b85630 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java @@ -21,7 +21,7 @@ import android.app.ProgressDialog; import android.os.Bundle; import android.support.annotation.IntDef; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; import com.android.tv.data.Program; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.provider.EpisodicProgramLoadTask; @@ -53,7 +53,7 @@ public class DvrSchedulesActivity extends Activity { @Override public void onCreate(final Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); // Pass null to prevent automatically re-creating fragments super.onCreate(null); setContentView(R.layout.activity_dvr_schedules); diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java index 6ec2e152..d376e358 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java @@ -30,9 +30,8 @@ import android.transition.Fade; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager; @@ -141,7 +140,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ApplicationSingletons singletons = TvApplication.getSingletons(getContext()); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); mChannelDataManager = singletons.getChannelDataManager(); mChannelDataManager.addListener(mChannelListener); mDvrDataManager = singletons.getDvrDataManager(); diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java index 8dd6c322..1215c19a 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java @@ -26,7 +26,7 @@ import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -79,11 +79,11 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { public void start() { clear(); List recordingList = - TvApplication.getSingletons(mContext) + TvSingletons.getSingletons(mContext) .getDvrDataManager() .getNonStartedScheduledRecordings(); recordingList.addAll( - TvApplication.getSingletons(mContext).getDvrDataManager().getStartedRecordings()); + TvSingletons.getSingletons(mContext).getDvrDataManager().getStartedRecordings()); Collections.sort( recordingList, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); long deadLine = Utils.getLastMillisecondOfDay(System.currentTimeMillis()); @@ -136,7 +136,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { /** Stops schedules row adapter. */ public void stop() { mHandler.removeCallbacksAndMessages(null); - DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); for (int i = 0; i < size(); i++) { if (get(i) instanceof ScheduleRow) { ScheduleRow row = (ScheduleRow) get(i); diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java index 67096e3b..5cab607a 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java @@ -38,7 +38,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.dialog.HalfSizedDialogFragment; @@ -344,8 +344,8 @@ class ScheduleRowPresenter extends RowPresenter { setHeaderPresenter(null); setSelectEffectEnabled(false); mContext = context; - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); - mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager(); + mDvrManager = TvSingletons.getSingletons(context).getDvrManager(); + mDvrScheduleManager = TvSingletons.getSingletons(context).getDvrScheduleManager(); mTunerConflictWillNotBeRecordedInfo = mContext.getString(R.string.dvr_schedules_tuner_conflict_will_not_be_recorded_info); mTunerConflictWillBePartiallyRecordedInfo = @@ -426,7 +426,7 @@ class ScheduleRowPresenter extends RowPresenter { switch (actions.length) { case 2: viewHolder.mSecondActionView.setImageResource(getImageForAction(actions[1])); - // pass through + // fall through case 1: viewHolder.mFirstActionView.setImageResource(getImageForAction(actions[0])); break; @@ -486,7 +486,7 @@ class ScheduleRowPresenter extends RowPresenter { private String getChannelNameText(ScheduleRow row) { Channel channel = - TvApplication.getSingletons(mContext) + TvSingletons.getSingletons(mContext) .getChannelDataManager() .getChannel(row.getChannelId()); return channel == null diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java index 03cc0a79..eb01aba2 100644 --- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java @@ -28,7 +28,7 @@ import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.dvr.data.SeriesRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow; @@ -158,11 +158,11 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter { SeriesRecording seriesRecording = SeriesRecording.buildFrom(header.getSeriesRecording()) .setPriority( - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrScheduleManager() .suggestNewSeriesPriority()) .build(); - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getDvrManager() .updateSeriesRecording(seriesRecording); DvrUiHelper.startSeriesSettingsActivity( diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java index 692c0f99..9a9c94ea 100644 --- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java @@ -23,9 +23,8 @@ import android.os.Build; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.util.ArrayMap; import android.util.Log; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Program; import com.android.tv.dvr.DvrDataManager; @@ -65,7 +64,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter { } else { mInputId = null; } - ApplicationSingletons singletons = TvApplication.getSingletons(context); + TvSingletons singletons = TvSingletons.getSingletons(context); mDvrManager = singletons.getDvrManager(); mDataManager = singletons.getDvrDataManager(); setHasStableIds(true); diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java index 29f2734d..b8b19adc 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java @@ -24,7 +24,7 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.util.Utils; @@ -39,7 +39,7 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene @Override public void onCreate(Bundle savedInstanceState) { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); if (DEBUG) Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); setIntent(createProgramIntent(getIntent())); diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java index 3ff90aa4..dd17b22d 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java @@ -29,7 +29,7 @@ import android.os.AsyncTask; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.dvr.DvrWatchedPositionManager; @@ -61,8 +61,8 @@ class DvrPlaybackMediaSessionHelper { mActivity = activity; mDvrPlayer = dvrPlayer; mDvrWatchedPositionManager = - TvApplication.getSingletons(activity).getDvrWatchedPositionManager(); - mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager(); + TvSingletons.getSingletons(activity).getDvrWatchedPositionManager(); + mChannelDataManager = TvSingletons.getSingletons(activity).getChannelDataManager(); mDvrPlayer.setCallback( new DvrPlayer.DvrPlayerCallback() { @Override diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java index c5fccda2..d3374cfa 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java @@ -43,7 +43,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.BaseProgram; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dvr.DvrDataManager; @@ -116,9 +116,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment { .getResources() .getDimensionPixelOffset( R.dimen.dvr_playback_overlay_padding_top_no_secondary_row); - mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); + mDvrDataManager = TvSingletons.getSingletons(getActivity()).getDvrDataManager(); mContentRatingsManager = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getTvInputManagerHelper() .getContentRatingsManager(); if (!mDvrDataManager.isRecordedProgramLoadFinished()) { diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/src/com/android/tv/experiments/ExperimentFlag.java deleted file mode 100644 index 2963482e..00000000 --- a/src/com/android/tv/experiments/ExperimentFlag.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.experiments; - -import android.support.annotation.VisibleForTesting; - -/** Experiments return values based on user, device and other criteria. */ -public final class ExperimentFlag { - - private static boolean sAllowOverrides = false; - - @VisibleForTesting - public static void initForTest() { - sAllowOverrides = true; - } - - /** Returns a boolean experiment */ - public static ExperimentFlag createFlag(boolean defaultValue) { - return new ExperimentFlag<>(defaultValue); - } - - private final T mDefaultValue; - - private T mOverrideValue = null; - private boolean mOverridden = false; - - private ExperimentFlag(T defaultValue) { - mDefaultValue = defaultValue; - } - - /** Returns value for this experiment */ - public T get() { - return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue; - } - - @VisibleForTesting - public void override(T t) { - if (sAllowOverrides) { - mOverridden = true; - mOverrideValue = t; - } - } - - @VisibleForTesting - public void resetOverride() { - mOverridden = false; - } -} diff --git a/src/com/android/tv/experiments/Experiments.java b/src/com/android/tv/experiments/Experiments.java deleted file mode 100644 index 0bff384e..00000000 --- a/src/com/android/tv/experiments/Experiments.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.experiments; - -import static com.android.tv.experiments.ExperimentFlag.createFlag; - -import com.android.tv.common.BuildConfig; - -/** - * Set of experiments visible in AOSP. - * - *

This file is maintained by hand. - */ -public final class Experiments { - public static final ExperimentFlag CLOUD_EPG = createFlag(true); - - public static final ExperimentFlag ENABLE_UNRATED_CONTENT_SETTINGS = createFlag(false); - - /** - * Allow developer features such as the dev menu and other aids. - * - *

These features are available to select users(aka fishfooders) on production builds. - */ - public static final ExperimentFlag ENABLE_DEVELOPER_FEATURES = - createFlag(BuildConfig.ENG); - - private Experiments() {} -} diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index 20c3ccdf..33ab9ad7 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -44,11 +44,12 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; import com.android.tv.ChannelTuner; -import com.android.tv.Features; import com.android.tv.MainActivity; import com.android.tv.R; +import com.android.tv.TvFeatures; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; +import com.android.tv.common.util.DurationTimer; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.GenreItems; import com.android.tv.data.ProgramDataManager; @@ -56,7 +57,6 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; import com.android.tv.ui.ViewUtils; -import com.android.tv.util.DurationTimer; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.util.ArrayList; @@ -165,6 +165,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } }; + @SuppressWarnings("RestrictTo") public ProgramGuide( MainActivity activity, ChannelTuner channelTuner, @@ -236,7 +237,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mSidePanelGridView.setWindowAlignmentOffsetPercent( VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); - if (Features.EPG_SEARCH.isEnabled(mActivity)) { + if (TvFeatures.EPG_SEARCH.isEnabled(mActivity)) { mSearchOrb = (SearchOrbView) mContainer.findViewById(R.id.program_guide_side_panel_search_orb); diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index c8c7ff2d..9daa9f2f 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -35,10 +35,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; -import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; @@ -91,8 +90,7 @@ public class ProgramItemView extends TextView { // do nothing return; } - ApplicationSingletons singletons = - TvApplication.getSingletons(view.getContext()); + TvSingletons singletons = TvSingletons.getSingletons(view.getContext()); Tracker tracker = singletons.getTracker(); tracker.sendEpgItemClicked(); final MainActivity tvActivity = (MainActivity) view.getContext(); @@ -208,7 +206,7 @@ public class ProgramItemView extends TextView { super(context, attrs, defStyle); setOnClickListener(ON_CLICKED); setOnFocusChangeListener(ON_FOCUS_CHANGED); - mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); } private void initIfNeeded() { diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 8dd14ca3..1a1ba36c 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -16,8 +16,6 @@ package com.android.tv.guide; -import static com.android.tv.util.ImageLoader.ImageLoaderCallback; - import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -42,6 +40,7 @@ import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; @@ -49,9 +48,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.TvCommonUtils; +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; @@ -63,6 +62,7 @@ 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; @@ -113,10 +113,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter customActions = - mTvCustomizationManager.getCustomActions(TvCustomizationManager.ID_PARTNER_ROW); - String title = mTvCustomizationManager.getPartnerRowTitle(); + mCustomizationManager.getCustomActions(CustomizationManager.ID_PARTNER_ROW); + String title = mCustomizationManager.getPartnerRowTitle(); if (customActions != null && !TextUtils.isEmpty(title)) { return new PartnerRow(mMainActivity, menu, title, customActions); } @@ -60,8 +60,7 @@ public class MenuRowFactory { return new TvOptionsRow( mMainActivity, menu, - mTvCustomizationManager.getCustomActions( - TvCustomizationManager.ID_OPTIONS_ROW)); + mCustomizationManager.getCustomActions(CustomizationManager.ID_OPTIONS_ROW)); } return null; } diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java index 593bede7..ceffe861 100644 --- a/src/com/android/tv/menu/OptionsRowAdapter.java +++ b/src/com/android/tv/menu/OptionsRowAdapter.java @@ -19,7 +19,7 @@ package com.android.tv.menu; import android.content.Context; import android.view.View; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import java.util.List; @@ -54,7 +54,7 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< public OptionsRowAdapter(Context context) { super(context); - mTracker = TvApplication.getSingletons(context).getTracker(); + mTracker = TvSingletons.getSingletons(context).getTracker(); } /** Update action list and its content. */ diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java index 1a962640..9676fe4d 100644 --- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java +++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java @@ -17,7 +17,7 @@ package com.android.tv.menu; import android.content.Context; -import com.android.tv.customization.CustomAction; +import com.android.tv.common.customization.CustomAction; import java.util.Collections; import java.util.List; diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index d9879e18..35f1d503 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -28,7 +28,7 @@ import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TimeShiftManager; import com.android.tv.TimeShiftManager.TimeShiftActionId; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; @@ -127,8 +127,8 @@ public class PlayControlsRowView extends MenuRowView { mCompactButtonMargin = res.getDimensionPixelSize(R.dimen.play_controls_button_compact_margin); if (CommonFeatures.DVR.isEnabled(context)) { - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); + mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); + mDvrManager = TvSingletons.getSingletons(context).getDvrManager(); } else { mDvrDataManager = null; mDvrManager = null; @@ -275,7 +275,7 @@ public class PlayControlsRowView extends MenuRowView { private void onRecordButtonClicked() { boolean isRecording = isCurrentChannelRecording(); Channel currentChannel = mMainActivity.getCurrentChannel(); - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getTracker() .sendMenuClicked( isRecording @@ -290,7 +290,7 @@ public class PlayControlsRowView extends MenuRowView { .show(); } else { Program program = - TvApplication.getSingletons(mMainActivity) + TvSingletons.getSingletons(mMainActivity) .getProgramDataManager() .getCurrentProgram(currentChannel.getId()); DvrUiHelper.checkStorageStatusAndShowErrorMessage( diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java index d340d309..55affb59 100644 --- a/src/com/android/tv/menu/TvOptionsRowAdapter.java +++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java @@ -19,16 +19,16 @@ package com.android.tv.menu; import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.annotation.VisibleForTesting; -import com.android.tv.Features; +import com.android.tv.TvFeatures; import com.android.tv.TvOptionsManager; -import com.android.tv.customization.CustomAction; +import com.android.tv.common.customization.CustomAction; +import com.android.tv.common.util.CommonUtils; import com.android.tv.data.DisplayMode; import com.android.tv.ui.TvViewUiManager; import com.android.tv.ui.sidepanel.ClosedCaptionFragment; import com.android.tv.ui.sidepanel.DeveloperOptionFragment; import com.android.tv.ui.sidepanel.DisplayModeFragment; import com.android.tv.ui.sidepanel.MultiAudioFragment; -import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -45,12 +45,12 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { List actionList = new ArrayList<>(); actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION); actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION); - if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { + if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { actionList.add(MenuAction.SYSTEMWIDE_PIP_ACTION); } actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); actionList.add(MenuAction.MORE_CHANNELS_ACTION); - if (Utils.isDeveloper()) { + if (CommonUtils.isDeveloper()) { actionList.add(MenuAction.DEV_ACTION); } actionList.add(MenuAction.SETTINGS_ACTION); diff --git a/src/com/android/tv/onboarding/NewSourcesFragment.java b/src/com/android/tv/onboarding/NewSourcesFragment.java index eaf06990..e6b247a0 100644 --- a/src/com/android/tv/onboarding/NewSourcesFragment.java +++ b/src/com/android/tv/onboarding/NewSourcesFragment.java @@ -24,14 +24,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.ui.setup.SetupActionHelper; -import com.android.tv.util.SetupUtils; /** A fragment for new channel source info/setup. */ public class NewSourcesFragment extends Fragment { /** The action category. */ - public static final String ACTION_CATEOGRY = "com.android.tv.onboarding.NewSourcesFragment"; + public static final String ACTION_CATEOGRY = + "com.android.tv.onboarding.NewSourcesFragment"; /** An action to show the setup screen. */ public static final int ACTION_SETUP = 1; /** An action to close this fragment. */ @@ -52,9 +52,8 @@ public class NewSourcesFragment extends Fragment { View view = inflater.inflate(R.layout.fragment_new_sources, container, false); initializeButton(view.findViewById(R.id.setup), ACTION_SETUP); initializeButton(view.findViewById(R.id.skip), ACTION_SKIP); - SetupUtils.getInstance(getActivity()) - .markAllInputsRecognized( - TvApplication.getSingletons(getActivity()).getTvInputManagerHelper()); + TvSingletons singletons = TvSingletons.getSingletons(getActivity()); + singletons.getSetupUtils().markAllInputsRecognized(singletons.getTvInputManagerHelper()); view.requestFocus(); return view; } diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java index 8d429a1d..a1cf9de1 100644 --- a/src/com/android/tv/onboarding/OnboardingActivity.java +++ b/src/com/android/tv/onboarding/OnboardingActivity.java @@ -26,16 +26,15 @@ import android.media.tv.TvInputInfo; import android.os.Bundle; import android.support.annotation.NonNull; import android.widget.Toast; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.SetupPassthroughActivity; -import com.android.tv.TvApplication; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.TvSingletons; import com.android.tv.common.ui.setup.SetupActivity; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.data.ChannelDataManager; import com.android.tv.util.OnboardingUtils; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; @@ -50,12 +49,13 @@ public class OnboardingActivity extends SetupActivity { private ChannelDataManager mChannelDataManager; private TvInputManagerHelper mInputManager; + private SetupUtils mSetupUtils; private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() { @Override public void onLoadFinished() { mChannelDataManager.removeListener(this); - SetupUtils.getInstance(OnboardingActivity.this).markNewChannelsBrowsable(); + mSetupUtils.markNewChannelsBrowsable(); } @Override @@ -81,14 +81,15 @@ public class OnboardingActivity extends SetupActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ApplicationSingletons singletons = TvApplication.getSingletons(this); + TvSingletons singletons = TvSingletons.getSingletons(this); mInputManager = singletons.getTvInputManagerHelper(); + mSetupUtils = singletons.getSetupUtils(); if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) { mChannelDataManager = singletons.getChannelDataManager(); // Make the channels of the new inputs which have been setup outside Live TV // browsable. if (mChannelDataManager.isDbLoadFinished()) { - SetupUtils.getInstance(this).markNewChannelsBrowsable(); + mSetupUtils.markNewChannelsBrowsable(); } else { mChannelDataManager.addListener(mChannelListener); } @@ -179,7 +180,7 @@ public class OnboardingActivity extends SetupActivity { params.getString( SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); TvInputInfo input = mInputManager.getTvInputInfo(inputId); - Intent intent = TvCommonUtils.createSetupIntent(input); + Intent intent = CommonUtils.createSetupIntent(input); if (intent == null) { Toast.makeText( this, @@ -213,7 +214,7 @@ public class OnboardingActivity extends SetupActivity { case SetupMultiPaneFragment.ACTION_DONE: { ChannelDataManager manager = - TvApplication.getSingletons(OnboardingActivity.this) + TvSingletons.getSingletons(OnboardingActivity.this) .getChannelDataManager(); if (manager.getChannelCount() == 0) { finish(); diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java index 1ac6b27e..3025538b 100644 --- a/src/com/android/tv/onboarding/SetupSourcesFragment.java +++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java @@ -30,14 +30,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.ui.setup.SetupGuidedStepFragment; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.TvInputNewComparator; -import com.android.tv.tuner.TunerInputController; import com.android.tv.ui.GuidedActionsStylistWithDivider; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; @@ -48,7 +46,8 @@ import java.util.List; /** A fragment for channel source info/setup. */ public class SetupSourcesFragment extends SetupMultiPaneFragment { /** The action category for the actions which is fired from this fragment. */ - public static final String ACTION_CATEGORY = "com.android.tv.onboarding.SetupSourcesFragment"; + public static final String ACTION_CATEGORY = + "com.android.tv.onboarding.SetupSourcesFragment"; /** An action to open the merchant collection. */ public static final int ACTION_ONLINE_STORE = 1; /** @@ -71,7 +70,7 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); - TvApplication.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL); + TvSingletons.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL); return view; } @@ -190,16 +189,18 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment { @Override public void onCreate(Bundle savedInstanceState) { Context context = getActivity(); - ApplicationSingletons app = TvApplication.getSingletons(context); - mInputManager = app.getTvInputManagerHelper(); - mChannelDataManager = app.getChannelDataManager(); - mSetupUtils = SetupUtils.getInstance(context); + TvSingletons singletons = TvSingletons.getSingletons(context); + mInputManager = singletons.getTvInputManagerHelper(); + mChannelDataManager = singletons.getChannelDataManager(); + mSetupUtils = singletons.getSetupUtils(); buildInputs(); mInputManager.addCallback(mInputCallback); mChannelDataManager.addListener(mChannelDataManagerListener); super.onCreate(savedInstanceState); mParentFragment = (SetupSourcesFragment) getParentFragment(); - TunerInputController.executeNetworkTunerDiscoveryAsyncTask(getContext()); + singletons + .getTunerInputController() + .executeNetworkTunerDiscoveryAsyncTask(getContext()); } @Override diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java index d139314e..150e0997 100644 --- a/src/com/android/tv/onboarding/WelcomeFragment.java +++ b/src/com/android/tv/onboarding/WelcomeFragment.java @@ -578,6 +578,8 @@ public class WelcomeFragment extends OnboardingFragment { private Animator mAnimator; + private boolean mLogoAnimationFinished; + public WelcomeFragment() { setExitTransition( new SetupAnimationHelper.TransitionBuilder() @@ -605,7 +607,13 @@ public class WelcomeFragment extends OnboardingFragment { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); setLogoResourceId(R.drawable.splash_logo); - if (savedInstanceState != null) { + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (savedInstanceState != null && mLogoAnimationFinished) { switch (getCurrentPageIndex()) { case 0: mTvContentView.setImageResource( @@ -627,7 +635,6 @@ public class WelcomeFragment extends OnboardingFragment { break; } } - return view; } @Override @@ -635,6 +642,12 @@ public class WelcomeFragment extends OnboardingFragment { return R.style.Theme_Leanback_Onboarding; } + @Override + protected void onLogoAnimationFinished() { + super.onLogoAnimationFinished(); + mLogoAnimationFinished = true; + } + @Override protected Animator onCreateEnterAnimation() { List animators = new ArrayList<>(); diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java index a9c947c6..32a1325b 100644 --- a/src/com/android/tv/parental/ContentRatingsManager.java +++ b/src/com/android/tv/parental/ContentRatingsManager.java @@ -19,12 +19,12 @@ package com.android.tv.parental; import android.content.Context; import android.media.tv.TvContentRating; import android.media.tv.TvContentRatingSystemInfo; -import android.media.tv.TvInputManager; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.R; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; +import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; import java.util.List; @@ -32,19 +32,19 @@ public class ContentRatingsManager { private final List mContentRatingSystems = new ArrayList<>(); private final Context mContext; + private final TvInputManagerHelper.TvInputManagerInterface mTvInputManager; - public ContentRatingsManager(Context context) { + public ContentRatingsManager( + Context context, TvInputManagerHelper.TvInputManagerInterface tvInputManager) { mContext = context; + this.mTvInputManager = tvInputManager; } public void update() { mContentRatingSystems.clear(); - - TvInputManager manager = - (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE); ContentRatingsParser parser = new ContentRatingsParser(mContext); - List infos = manager.getTvContentRatingSystemList(); + List infos = mTvInputManager.getTvContentRatingSystemList(); for (TvContentRatingSystemInfo info : infos) { List list = parser.parse(info); if (list != null) { diff --git a/src/com/android/tv/parental/ContentRatingsParser.java b/src/com/android/tv/parental/ContentRatingsParser.java index 3e645ce9..294e9463 100644 --- a/src/com/android/tv/parental/ContentRatingsParser.java +++ b/src/com/android/tv/parental/ContentRatingsParser.java @@ -33,6 +33,7 @@ import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class ContentRatingsParser { private static final String TAG = "ContentRatingsParser"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java index b9cf45b2..db1f0a4d 100644 --- a/src/com/android/tv/parental/ParentalControlSettings.java +++ b/src/com/android/tv/parental/ParentalControlSettings.java @@ -19,7 +19,7 @@ package com.android.tv.parental; import android.content.Context; import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; -import com.android.tv.experiments.Experiments; +import com.android.tv.common.experiments.Experiments; import com.android.tv.parental.ContentRatingSystem.Rating; import com.android.tv.parental.ContentRatingSystem.SubRating; import com.android.tv.util.TvSettings; diff --git a/src/com/android/tv/perf/EventNames.java b/src/com/android/tv/perf/EventNames.java index 12488ddb..54745f3b 100644 --- a/src/com/android/tv/perf/EventNames.java +++ b/src/com/android/tv/perf/EventNames.java @@ -25,6 +25,7 @@ import java.lang.annotation.Retention; * Constants for performance event names. * *

Only constants are used to insure no PII is sent. + * */ public final class EventNames { diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java index 90e087f0..3fb66245 100644 --- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java +++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java @@ -24,11 +24,10 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Analytics; import com.android.tv.analytics.Tracker; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.util.SharedPreferencesUtils; /** * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google @@ -61,9 +60,9 @@ public final class AudioCapabilitiesReceiver { public AudioCapabilitiesReceiver( @NonNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener) { mContext = context; - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mAnalytics = appSingletons.getAnalytics(); - mTracker = appSingletons.getTracker(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mAnalytics = tvSingletons.getAnalytics(); + mTracker = tvSingletons.getTracker(); mListener = listener; } diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java index b3ecb8e6..d8528bb5 100644 --- a/src/com/android/tv/receiver/BootCompletedReceiver.java +++ b/src/com/android/tv/receiver/BootCompletedReceiver.java @@ -23,9 +23,10 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; -import com.android.tv.Features; +import com.android.tv.Starter; import com.android.tv.TvActivity; -import com.android.tv.TvApplication; +import com.android.tv.TvFeatures; +import com.android.tv.TvSingletons; import com.android.tv.dvr.recorder.DvrRecordingService; import com.android.tv.dvr.recorder.RecordingScheduler; import com.android.tv.recommendation.ChannelPreviewUpdater; @@ -51,12 +52,12 @@ public class BootCompletedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); return; } if (DEBUG) Log.d(TAG, "boot completed " + intent); - TvApplication.setCurrentRunningProcess(context, true); + Starter.start(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ChannelPreviewUpdater.getInstance(context).updatePreviewDataForChannelsImmediately(); @@ -69,7 +70,7 @@ public class BootCompletedReceiver extends BroadcastReceiver { // Grant permission to already set up packages after the system has finished booting. SetupUtils.grantEpgPermissionToSetUpPackages(context); - if (Features.UNHIDE.isEnabled(context)) { + if (TvFeatures.UNHIDE.isEnabled(context)) { if (OnboardingUtils.isFirstBoot(context)) { // Enable the application if this is the first "unhide" feature is enabled just in // case when the app has been disabled before. @@ -84,7 +85,7 @@ public class BootCompletedReceiver extends BroadcastReceiver { } } - RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler(); + RecordingScheduler scheduler = TvSingletons.getSingletons(context).getRecordingScheduler(); if (scheduler != null) { scheduler.updateAndStartServiceIfNeeded(); } diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java index 7c4117d4..0133d8ee 100644 --- a/src/com/android/tv/receiver/GlobalKeyReceiver.java +++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java @@ -23,7 +23,9 @@ import android.os.AsyncTask; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; +import com.android.tv.Starter; import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; /** Handles global keys. */ public class GlobalKeyReceiver extends BroadcastReceiver { @@ -39,11 +41,11 @@ public class GlobalKeyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); return; } - TvApplication.setCurrentRunningProcess(context, true); + Starter.start(context); Context appContext = context.getApplicationContext(); if (DEBUG) Log.d(TAG, "onReceive: " + intent); if (sUserSetupComplete) { diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java index bd26c7b3..7ff67b50 100644 --- a/src/com/android/tv/receiver/PackageIntentsReceiver.java +++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java @@ -21,7 +21,8 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.util.Log; -import com.android.tv.TvApplication; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; import com.android.tv.util.Partner; /** A class for handling the broadcast intents from PackageManager. */ @@ -30,12 +31,12 @@ public class PackageIntentsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { + if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) { Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); return; } - TvApplication.setCurrentRunningProcess(context, true); - ((TvApplication) context.getApplicationContext()).handleInputCountChanged(); + Starter.start(context); + ((TvSingletons) context.getApplicationContext()).handleInputCountChanged(); Uri uri = intent.getData(); final String packageName = (uri != null ? uri.getSchemeSpecificPart() : null); diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java index d332e18a..5da802c4 100644 --- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java +++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java @@ -28,8 +28,8 @@ import android.support.annotation.RequiresApi; import android.support.media.tv.TvContractCompat; import android.text.TextUtils; import android.util.Log; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +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; @@ -46,8 +46,7 @@ import java.util.concurrent.TimeUnit; @RequiresApi(Build.VERSION_CODES.O) public class ChannelPreviewUpdater { private static final String TAG = "ChannelPreviewUpdater"; - // STOPSHIP: set it to false. - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int UPATE_PREVIEW_PROGRAMS_JOB_ID = 1000001; private static final long ROUTINE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10); @@ -100,10 +99,10 @@ public class ChannelPreviewUpdater { mRecommender.registerEvaluator(new RandomEvaluator(), 0.1, 0.1); mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5); mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0); - ApplicationSingletons appSingleton = TvApplication.getSingletons(context); - mPreviewDataManager = appSingleton.getPreviewDataManager(); + TvSingletons tvSingleton = TvSingletons.getSingletons(context); + mPreviewDataManager = tvSingleton.getPreviewDataManager(); mParentalControlSettings = - appSingleton.getTvInputManagerHelper().getParentalControlSettings(); + tvSingleton.getTvInputManagerHelper().getParentalControlSettings(); } /** Starts the routine service for updating the preview programs. */ @@ -294,7 +293,7 @@ public class ChannelPreviewUpdater { @Override public void onCreate() { - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); if (DEBUG) Log.d(TAG, "ChannelPreviewUpdateService.onCreate"); mChannelPreviewUpdater = ChannelPreviewUpdater.getInstance(this); } diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java index 812b9d3f..34679452 100644 --- a/src/com/android/tv/recommendation/ChannelRecord.java +++ b/src/com/android/tv/recommendation/ChannelRecord.java @@ -17,8 +17,9 @@ package com.android.tv.recommendation; import android.content.Context; +import android.support.annotation.GuardedBy; import android.support.annotation.VisibleForTesting; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; @@ -29,7 +30,10 @@ public class ChannelRecord { // TODO: decide the value for max history size. @VisibleForTesting static final int MAX_HISTORY_SIZE = 100; private final Context mContext; + + @GuardedBy("this") private final Deque mWatchHistory; + private Program mCurrentProgram; private Channel mChannel; private long mTotalWatchDurationMs; @@ -59,7 +63,7 @@ public class ChannelRecord { mInputRemoved = removed; } - public long getLastWatchEndTimeMs() { + public synchronized long getLastWatchEndTimeMs() { WatchedProgram p = mWatchHistory.peekLast(); return (p == null) ? 0 : p.getWatchEndTimeMs(); } @@ -68,7 +72,7 @@ public class ChannelRecord { long time = System.currentTimeMillis(); if (mCurrentProgram == null || mCurrentProgram.getEndTimeUtcMillis() < time) { ProgramDataManager manager = - TvApplication.getSingletons(mContext).getProgramDataManager(); + TvSingletons.getSingletons(mContext).getProgramDataManager(); mCurrentProgram = manager.getCurrentProgram(mChannel.getId()); } return mCurrentProgram; @@ -78,11 +82,11 @@ public class ChannelRecord { return mTotalWatchDurationMs; } - public final WatchedProgram[] getWatchHistory() { + public final synchronized WatchedProgram[] getWatchHistory() { return mWatchHistory.toArray(new WatchedProgram[mWatchHistory.size()]); } - public void logWatchHistory(WatchedProgram p) { + public synchronized void logWatchHistory(WatchedProgram p) { mWatchHistory.offer(p); mTotalWatchDurationMs += p.getWatchedDurationMs(); if (mWatchHistory.size() > MAX_HISTORY_SIZE) { diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index c4b321e1..201bb103 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -40,10 +40,10 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseLongArray; import android.view.View; -import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.Starter; +import com.android.tv.TvSingletons; import com.android.tv.common.WeakHandler; import com.android.tv.data.Channel; import com.android.tv.data.Program; @@ -122,7 +122,7 @@ public class NotificationService extends Service @Override public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate"); - TvApplication.setCurrentRunningProcess(this, true); + Starter.start(this); super.onCreate(); mCurrentNotificationCount = 0; mNotificationChannels = new long[NOTIFICATION_COUNT]; @@ -146,17 +146,17 @@ public class NotificationService extends Service getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - ApplicationSingletons appSingletons = TvApplication.getSingletons(this); - mTvInputManagerHelper = appSingletons.getTvInputManagerHelper(); + TvSingletons tvSingletons = TvSingletons.getSingletons(this); + mTvInputManagerHelper = tvSingletons.getTvInputManagerHelper(); mHandlerThread = new HandlerThread("tv notification"); mHandlerThread.start(); mHandler = new NotificationHandler(mHandlerThread.getLooper(), this); mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER); // Just called for early initialization. - appSingletons.getChannelDataManager(); - appSingletons.getProgramDataManager(); - appSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this); + tvSingletons.getChannelDataManager(); + tvSingletons.getProgramDataManager(); + tvSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this); } @UiThread @@ -209,7 +209,7 @@ public class NotificationService extends Service @Override public void onDestroy() { - TvApplication.getSingletons(this) + TvSingletons.getSingletons(this) .getMainActivityWrapper() .removeOnCurrentChannelChangeListener(this); if (mRecommender != null) { diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index 794ca7e2..8e8455fa 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -33,13 +33,13 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import com.android.tv.TvApplication; +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.util.PermissionUtils; import com.android.tv.util.TvUriMatcher; import java.util.ArrayList; import java.util.Collection; @@ -50,6 +50,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class RecommendationDataManager implements WatchedHistoryManager.Listener { private static final int MSG_START = 1000; private static final int MSG_STOP = 1001; @@ -185,7 +186,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener mHandler = new RecommendationHandler(mHandlerThread.getLooper(), this); mMainHandler = new RecommendationMainHandler(Looper.getMainLooper(), this); mContentObserver = new RecommendationContentObserver(mHandler); - mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager(); + mChannelDataManager = TvSingletons.getSingletons(mContext).getChannelDataManager(); runOnMainThread( new Runnable() { @Override diff --git a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java index 2b17c510..edc23c53 100644 --- a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java +++ b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java @@ -21,8 +21,7 @@ import android.os.Build; import android.support.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.PreviewProgramContent; import com.android.tv.dvr.DvrDataManager; @@ -34,10 +33,10 @@ import java.util.Set; /** Class to update the preview data for {@link RecordedProgram} */ @RequiresApi(Build.VERSION_CODES.O) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated public class RecordedProgramPreviewUpdater { private static final String TAG = "RecordedProgramPreviewUpdater"; - // STOPSHIP: set it to false. - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int RECOMMENDATION_COUNT = 6; @@ -58,9 +57,9 @@ public class RecordedProgramPreviewUpdater { private RecordedProgramPreviewUpdater(Context context) { mContext = context.getApplicationContext(); - ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext); - mPreviewDataManager = applicationSingletons.getPreviewDataManager(); - mDvrDataManager = applicationSingletons.getDvrDataManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(mContext); + mPreviewDataManager = tvSingletons.getPreviewDataManager(); + mDvrDataManager = tvSingletons.getDvrDataManager(); mDvrDataManager.addRecordedProgramListener( new DvrDataManager.RecordedProgramListener() { @Override diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java index a60355f4..0d4a9dbc 100644 --- a/src/com/android/tv/search/DataManagerSearch.java +++ b/src/com/android/tv/search/DataManagerSearch.java @@ -26,8 +26,7 @@ import android.os.SystemClock; import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; @@ -60,9 +59,9 @@ public class DataManagerSearch implements SearchInterface { DataManagerSearch(Context context) { mContext = context; mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mChannelDataManager = appSingletons.getChannelDataManager(); - mProgramDataManager = appSingletons.getProgramDataManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mChannelDataManager = tvSingletons.getChannelDataManager(); + mProgramDataManager = tvSingletons.getProgramDataManager(); } @Override diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java index dfd585c3..579811e7 100644 --- a/src/com/android/tv/search/LocalSearchProvider.java +++ b/src/com/android/tv/search/LocalSearchProvider.java @@ -27,13 +27,13 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.perf.EventNames; import com.android.tv.perf.PerformanceMonitor; import com.android.tv.perf.TimerEvent; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvUriMatcher; import java.util.ArrayList; import java.util.Arrays; @@ -84,13 +84,13 @@ public class LocalSearchProvider extends ContentProvider { @Override public boolean onCreate() { - mPerformanceMonitor = TvApplication.getSingletons(getContext()).getPerformanceMonitor(); + mPerformanceMonitor = TvSingletons.getSingletons(getContext()).getPerformanceMonitor(); return true; } @VisibleForTesting void setSearchInterface(SearchInterface searchInterface) { - SoftPreconditions.checkState(TvCommonUtils.isRunningInTest()); + SoftPreconditions.checkState(CommonUtils.isRunningInTest()); mSearchInterface = searchInterface; } diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java index 1ca86d22..d3fe1554 100644 --- a/src/com/android/tv/search/ProgramGuideSearchFragment.java +++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java @@ -40,8 +40,8 @@ import android.view.View; 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.PermissionUtils; import java.util.List; public class ProgramGuideSearchFragment extends SearchFragment { @@ -83,7 +83,8 @@ public class ProgramGuideSearchFragment extends SearchFragment { mMainCardHeight, createImageLoaderCallback(cardView)); } else { - cardView.setMainImage(mMainActivity.getDrawable(R.drawable.ic_launcher)); + cardView.setMainImage( + mMainActivity.getDrawable(R.drawable.ic_live_channels_96x96)); } } @@ -168,7 +169,7 @@ public class ProgramGuideSearchFragment extends SearchFragment { View v = super.onCreateView(inflater, container, savedInstanceState); v.setBackgroundResource(R.color.program_guide_scrim); - setBadgeDrawable(mMainActivity.getDrawable(R.drawable.ic_launcher)); + setBadgeDrawable(mMainActivity.getDrawable(R.drawable.ic_live_channels_96x96)); setSearchResultProvider(mSearchResultProvider); setOnItemViewClickedListener(mItemClickedListener); return v; diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java index d6cf9cc9..a8640538 100644 --- a/src/com/android/tv/search/TvProviderSearch.java +++ b/src/com/android/tv/search/TvProviderSearch.java @@ -33,8 +33,8 @@ import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.util.Log; import com.android.tv.common.TvContentRatingCache; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.search.LocalSearchProvider.SearchResult; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Collections; @@ -48,6 +48,7 @@ import java.util.Objects; import java.util.Set; /** An implementation of {@link SearchInterface} to search query from TvProvider directly. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class TvProviderSearch implements SearchInterface { private static final String TAG = "TvProviderSearch"; private static final boolean DEBUG = false; diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java index df25afa6..3fefc0c8 100644 --- a/src/com/android/tv/setup/SystemSetupActivity.java +++ b/src/com/android/tv/setup/SystemSetupActivity.java @@ -24,13 +24,12 @@ import android.content.Intent; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.widget.Toast; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; import com.android.tv.SetupPassthroughActivity; -import com.android.tv.TvApplication; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.TvSingletons; import com.android.tv.common.ui.setup.SetupActivity; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; +import com.android.tv.common.util.CommonUtils; import com.android.tv.onboarding.SetupSourcesFragment; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.SetupUtils; @@ -52,7 +51,7 @@ public class SystemSetupActivity extends SetupActivity { finish(); return; } - ApplicationSingletons singletons = TvApplication.getSingletons(this); + TvSingletons singletons = TvSingletons.getSingletons(this); mInputManager = singletons.getTvInputManagerHelper(); } @@ -86,7 +85,7 @@ public class SystemSetupActivity extends SetupActivity { params.getString( SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID); TvInputInfo input = mInputManager.getTvInputInfo(inputId); - Intent intent = TvCommonUtils.createSetupIntent(input); + Intent intent = CommonUtils.createSetupIntent(input); if (intent == null) { Toast.makeText( this, diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java deleted file mode 100644 index 50153f89..00000000 --- a/src/com/android/tv/tuner/ChannelScanFileParser.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.util.Log; -import com.android.tv.tuner.data.nano.Channel; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -/** Parses plain text formatted scan files, which contain the list of channels. */ -public class ChannelScanFileParser { - private static final String TAG = "ChannelScanFileParser"; - - public static final class ScanChannel { - public final int type; - public final int frequency; - public final String modulation; - public final String filename; - /** - * Radio frequency (channel) number specified at - * https://en.wikipedia.org/wiki/North_American_television_frequencies This can be {@code - * null} for cases like cable signal. - */ - public final Integer radioFrequencyNumber; - - public static ScanChannel forTuner( - int frequency, String modulation, Integer radioFrequencyNumber) { - return new ScanChannel( - Channel.TYPE_TUNER, frequency, modulation, null, radioFrequencyNumber); - } - - public static ScanChannel forFile(int frequency, String filename) { - return new ScanChannel(Channel.TYPE_FILE, frequency, "file:", filename, null); - } - - private ScanChannel( - int type, - int frequency, - String modulation, - String filename, - Integer radioFrequencyNumber) { - this.type = type; - this.frequency = frequency; - this.modulation = modulation; - this.filename = filename; - this.radioFrequencyNumber = radioFrequencyNumber; - } - } - - /** - * Parses a given scan file and returns the list of {@link ScanChannel} objects. - * - * @param is {@link InputStream} of a scan file. Each line matches one channel. The line format - * of the scan file is as follows:
- * "A <frequency> <modulation>". - * @return a list of {@link ScanChannel} objects parsed - */ - public static List parseScanFile(InputStream is) { - BufferedReader in = new BufferedReader(new InputStreamReader(is)); - String line; - List scanChannelList = new ArrayList<>(); - try { - while ((line = in.readLine()) != null) { - if (line.isEmpty()) { - continue; - } - if (line.charAt(0) == '#') { - // Skip comment line - continue; - } - String[] tokens = line.split("\\s+"); - if (tokens.length != 3 && tokens.length != 4) { - continue; - } - scanChannelList.add( - ScanChannel.forTuner( - Integer.parseInt(tokens[1]), - tokens[2], - tokens.length == 4 ? Integer.parseInt(tokens[3]) : null)); - } - } catch (IOException e) { - Log.e(TAG, "error on parseScanFile()", e); - } - return scanChannelList; - } -} diff --git a/src/com/android/tv/tuner/DvbDeviceAccessor.java b/src/com/android/tv/tuner/DvbDeviceAccessor.java deleted file mode 100644 index 217433d2..00000000 --- a/src/com/android/tv/tuner/DvbDeviceAccessor.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.Context; -import android.media.tv.TvInputManager; -import android.os.ParcelFileDescriptor; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.util.Log; -import com.android.tv.common.recording.RecordingCapability; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** Provides with the file descriptors to access DVB device. */ -public class DvbDeviceAccessor { - private static final String TAG = "DvbDeviceAccessor"; - - @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND}) - @Retention(RetentionPolicy.SOURCE) - public @interface DvbDevice {} - - public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX; - public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR; - public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND; - - private static Method sGetDvbDeviceListMethod; - private static Method sOpenDvbDeviceMethod; - - private final TvInputManager mTvInputManager; - - static { - try { - Class tvInputManagerClass = Class.forName("android.media.tv.TvInputManager"); - Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo"); - sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList"); - sGetDvbDeviceListMethod.setAccessible(true); - sOpenDvbDeviceMethod = - tvInputManagerClass.getDeclaredMethod( - "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE); - sOpenDvbDeviceMethod.setAccessible(true); - } catch (ClassNotFoundException e) { - Log.e(TAG, "Couldn't find class", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Couldn't find method", e); - } - } - - public DvbDeviceAccessor(Context context) { - mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - } - - public List getDvbDeviceList() { - try { - List wrapperList = new ArrayList<>(); - List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager); - for (Object dvbDeviceInfo : dvbDeviceInfoList) { - wrapperList.add(new DvbDeviceInfoWrapper(dvbDeviceInfo)); - } - Collections.sort(wrapperList); - return wrapperList; - } catch (IllegalAccessException e) { - Log.e(TAG, "Couldn't access", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Couldn't invoke", e); - } - return null; - } - - /** Returns the number of currently connected DVB devices. */ - public int getNumOfDvbDevices() { - List dvbDeviceList = getDvbDeviceList(); - return dvbDeviceList == null ? 0 : dvbDeviceList.size(); - } - - public boolean isDvbDeviceAvailable() { - try { - List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager); - return (!dvbDeviceInfoList.isEmpty()); - } catch (IllegalAccessException e) { - Log.e(TAG, "Couldn't access", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Couldn't invoke", e); - } - return false; - } - - public ParcelFileDescriptor openDvbDevice( - DvbDeviceInfoWrapper deviceInfo, @DvbDevice int device) { - try { - return (ParcelFileDescriptor) - sOpenDvbDeviceMethod.invoke( - mTvInputManager, deviceInfo.getDvbDeviceInfo(), device); - } catch (IllegalAccessException e) { - Log.e(TAG, "Couldn't access", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Couldn't invoke", e); - } - return null; - } - - /** - * Returns the current recording capability for USB tuner. - * - * @param inputId the input id to use. - */ - public RecordingCapability getRecordingCapability(String inputId) { - List deviceList = getDvbDeviceList(); - // TODO(DVR) implement accurate capabilities and updating values when needed. - return RecordingCapability.builder() - .setInputId(inputId) - .setMaxConcurrentPlayingSessions(1) - .setMaxConcurrentTunedSessions(deviceList.size()) - .setMaxConcurrentSessionsOfAllTypes(deviceList.size() + 1) - .build(); - } - - public static class DvbDeviceInfoWrapper implements Comparable { - private static Method sGetAdapterIdMethod; - private static Method sGetDeviceIdMethod; - private final Object mDvbDeviceInfo; - private final int mAdapterId; - private final int mDeviceId; - private final long mId; - - static { - try { - Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo"); - sGetAdapterIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getAdapterId"); - sGetAdapterIdMethod.setAccessible(true); - sGetDeviceIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getDeviceId"); - sGetDeviceIdMethod.setAccessible(true); - } catch (ClassNotFoundException e) { - Log.e(TAG, "Couldn't find class", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Couldn't find method", e); - } - } - - public DvbDeviceInfoWrapper(Object dvbDeviceInfo) { - mDvbDeviceInfo = dvbDeviceInfo; - mAdapterId = initAdapterId(); - mDeviceId = initDeviceId(); - mId = (((long) getAdapterId()) << 32) | (getDeviceId() & 0xffffffffL); - } - - public long getId() { - return mId; - } - - public int getAdapterId() { - return mAdapterId; - } - - private int initAdapterId() { - try { - return (int) sGetAdapterIdMethod.invoke(mDvbDeviceInfo); - } catch (InvocationTargetException e) { - Log.e(TAG, "Couldn't invoke", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Couldn't access", e); - } - return -1; - } - - public int getDeviceId() { - return mDeviceId; - } - - private int initDeviceId() { - try { - return (int) sGetDeviceIdMethod.invoke(mDvbDeviceInfo); - } catch (InvocationTargetException e) { - Log.e(TAG, "Couldn't invoke", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Couldn't access", e); - } - return -1; - } - - public Object getDvbDeviceInfo() { - return mDvbDeviceInfo; - } - - @Override - public int compareTo(@NonNull DvbDeviceInfoWrapper another) { - if (getAdapterId() != another.getAdapterId()) { - return getAdapterId() - another.getAdapterId(); - } - return getDeviceId() - another.getDeviceId(); - } - - @Override - public String toString() { - return String.format( - Locale.US, - "DvbDeviceInfo {adapterId: %d, deviceId: %d}", - getAdapterId(), - getDeviceId()); - } - } -} diff --git a/src/com/android/tv/tuner/DvbTunerHal.java b/src/com/android/tv/tuner/DvbTunerHal.java deleted file mode 100644 index 8397400f..00000000 --- a/src/com/android/tv/tuner/DvbTunerHal.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.Context; -import android.os.ParcelFileDescriptor; -import android.util.Log; -import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; - -/** A class to handle a hardware Linux DVB API supported tuner device. */ -public class DvbTunerHal extends TunerHal { - - private static final Object sLock = new Object(); - // @GuardedBy("sLock") - private static final SortedSet sUsedDvbDevices = new TreeSet<>(); - - private final DvbDeviceAccessor mDvbDeviceAccessor; - private DvbDeviceInfoWrapper mDvbDeviceInfo; - - protected DvbTunerHal(Context context) { - super(context); - mDvbDeviceAccessor = new DvbDeviceAccessor(context); - } - - @Override - protected boolean openFirstAvailable() { - List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); - if (deviceInfoList == null || deviceInfoList.isEmpty()) { - Log.e(TAG, "There's no dvb device attached"); - return false; - } - synchronized (sLock) { - for (DvbDeviceInfoWrapper deviceInfo : deviceInfoList) { - if (!sUsedDvbDevices.contains(deviceInfo)) { - if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); - mDvbDeviceInfo = deviceInfo; - sUsedDvbDevices.add(deviceInfo); - getDeliverySystemTypeFromDevice(); - return true; - } - } - } - Log.e(TAG, "There's no available dvb devices"); - return false; - } - - /** - * Acquires the tuner device. The requested device will be locked to the current instance if - * it's not acquired by others. - * - * @param deviceInfo a tuner device to open - * @return {@code true} if the operation was successful, {@code false} otherwise - */ - protected boolean open(DvbDeviceInfoWrapper deviceInfo) { - if (deviceInfo == null) { - Log.e(TAG, "Device info should not be null"); - return false; - } - if (mDvbDeviceInfo != null) { - Log.e(TAG, "Already acquired"); - return false; - } - List deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList(); - if (deviceInfoList == null || deviceInfoList.isEmpty()) { - Log.e(TAG, "There's no dvb device attached"); - return false; - } - for (DvbDeviceInfoWrapper deviceInfoWrapper : deviceInfoList) { - if (deviceInfoWrapper.compareTo(deviceInfo) == 0) { - synchronized (sLock) { - if (sUsedDvbDevices.contains(deviceInfo)) { - Log.e(TAG, deviceInfo + " is already taken"); - return false; - } - sUsedDvbDevices.add(deviceInfo); - } - if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo); - mDvbDeviceInfo = deviceInfo; - return true; - } - } - Log.e(TAG, "There's no such dvb device attached"); - return false; - } - - @Override - public void close() { - if (mDvbDeviceInfo != null) { - if (isStreaming()) { - stopTune(); - } - nativeFinalize(mDvbDeviceInfo.getId()); - synchronized (sLock) { - sUsedDvbDevices.remove(mDvbDeviceInfo); - } - mDvbDeviceInfo = null; - } - } - - @Override - protected boolean isDeviceOpen() { - return (mDvbDeviceInfo != null); - } - - @Override - protected long getDeviceId() { - if (mDvbDeviceInfo != null) { - return mDvbDeviceInfo.getId(); - } - return -1; - } - - @Override - protected int openDvbFrontEndFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = - mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - @Override - protected int openDvbDemuxFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = - mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - @Override - protected int openDvbDvrFd() { - if (mDvbDeviceInfo != null) { - ParcelFileDescriptor descriptor = - mDvbDeviceAccessor.openDvbDevice( - mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR); - if (descriptor != null) { - return descriptor.detachFd(); - } - } - return -1; - } - - /** Gets the number of USB tuner devices currently present. */ - public static int getNumberOfDevices(Context context) { - try { - return (new DvbDeviceAccessor(context)).getNumOfDvbDevices(); - } catch (Exception e) { - return 0; - } - } -} diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java deleted file mode 100644 index c8db73c3..00000000 --- a/src/com/android/tv/tuner/TunerHal.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.Context; -import android.support.annotation.IntDef; -import android.support.annotation.StringDef; -import android.support.annotation.WorkerThread; -import android.util.Log; -import android.util.Pair; -import com.android.tv.customization.TvCustomizationManager; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** A base class to handle a hardware tuner device. */ -public abstract class TunerHal implements AutoCloseable { - protected static final String TAG = "TunerHal"; - protected static final boolean DEBUG = false; - - @IntDef({FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR}) - @Retention(RetentionPolicy.SOURCE) - public @interface FilterType {} - - public static final int FILTER_TYPE_OTHER = 0; - public static final int FILTER_TYPE_AUDIO = 1; - public static final int FILTER_TYPE_VIDEO = 2; - public static final int FILTER_TYPE_PCR = 3; - - @StringDef({MODULATION_8VSB, MODULATION_QAM256}) - @Retention(RetentionPolicy.SOURCE) - public @interface ModulationType {} - - public static final String MODULATION_8VSB = "8VSB"; - public static final String MODULATION_QAM256 = "QAM256"; - - @IntDef({ - DELIVERY_SYSTEM_UNDEFINED, - DELIVERY_SYSTEM_ATSC, - DELIVERY_SYSTEM_DVBC, - DELIVERY_SYSTEM_DVBS, - DELIVERY_SYSTEM_DVBS2, - DELIVERY_SYSTEM_DVBT, - DELIVERY_SYSTEM_DVBT2 - }) - @Retention(RetentionPolicy.SOURCE) - public @interface DeliverySystemType {} - - public static final int DELIVERY_SYSTEM_UNDEFINED = 0; - public static final int DELIVERY_SYSTEM_ATSC = 1; - public static final int DELIVERY_SYSTEM_DVBC = 2; - public static final int DELIVERY_SYSTEM_DVBS = 3; - public static final int DELIVERY_SYSTEM_DVBS2 = 4; - public static final int DELIVERY_SYSTEM_DVBT = 5; - public static final int DELIVERY_SYSTEM_DVBT2 = 6; - - @IntDef({TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK}) - @Retention(RetentionPolicy.SOURCE) - public @interface TunerType {} - - public static final int TUNER_TYPE_BUILT_IN = 1; - public static final int TUNER_TYPE_USB = 2; - public static final int TUNER_TYPE_NETWORK = 3; - - protected static final int PID_PAT = 0; - protected static final int PID_ATSC_SI_BASE = 0x1ffb; - protected static final int PID_DVB_SDT = 0x0011; - protected static final int PID_DVB_EIT = 0x0012; - protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000; - protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for - // QAM256 tuning. - @IntDef({BUILT_IN_TUNER_TYPE_LINUX_DVB}) - @Retention(RetentionPolicy.SOURCE) - private @interface BuiltInTunerType {} - - private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1; - - private static Integer sBuiltInTunerType; - - protected @DeliverySystemType int mDeliverySystemType; - private boolean mIsStreaming; - private int mFrequency; - private String mModulation; - - static { - System.loadLibrary("tunertvinput_jni"); - } - - /** - * Creates a TunerHal instance. - * - * @param context context for creating the TunerHal instance - * @return the TunerHal instance - */ - @WorkerThread - public static synchronized TunerHal createInstance(Context context) { - TunerHal tunerHal = null; - if (DvbTunerHal.getNumberOfDevices(context) > 0) { - if (DEBUG) Log.d(TAG, "Use DvbTunerHal"); - tunerHal = new DvbTunerHal(context); - } - return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null; - } - - /** Gets the number of tuner devices currently present. */ - @WorkerThread - public static Pair getTunerTypeAndCount(Context context) { - if (useBuiltInTuner(context)) { - if (getBuiltInTunerType(context) == BUILT_IN_TUNER_TYPE_LINUX_DVB) { - return new Pair<>(TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context)); - } - } else { - int usbTunerCount = DvbTunerHal.getNumberOfDevices(context); - if (usbTunerCount > 0) { - return new Pair<>(TUNER_TYPE_USB, usbTunerCount); - } - } - return new Pair<>(null, 0); - } - - /** Check a delivery system is for DVB or not. */ - public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) { - return deliverySystemType == DELIVERY_SYSTEM_DVBC - || deliverySystemType == DELIVERY_SYSTEM_DVBS - || deliverySystemType == DELIVERY_SYSTEM_DVBS2 - || deliverySystemType == DELIVERY_SYSTEM_DVBT - || deliverySystemType == DELIVERY_SYSTEM_DVBT2; - } - - /** - * Returns if tuner input service would use built-in tuners instead of USB tuners or network - * tuners. - */ - static boolean useBuiltInTuner(Context context) { - return getBuiltInTunerType(context) != 0; - } - - private static @BuiltInTunerType int getBuiltInTunerType(Context context) { - if (sBuiltInTunerType == null) { - sBuiltInTunerType = 0; - if (TvCustomizationManager.hasLinuxDvbBuiltInTuner(context) - && DvbTunerHal.getNumberOfDevices(context) > 0) { - sBuiltInTunerType = BUILT_IN_TUNER_TYPE_LINUX_DVB; - } - } - return sBuiltInTunerType; - } - - protected TunerHal(Context context) { - mIsStreaming = false; - mFrequency = -1; - mModulation = null; - } - - protected boolean isStreaming() { - return mIsStreaming; - } - - protected void getDeliverySystemTypeFromDevice() { - if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) { - mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId()); - } - } - - /** - * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels of - * the same frequency. - */ - public boolean isReusable() { - return true; - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - close(); - } - - protected native void nativeFinalize(long deviceId); - - /** - * Acquires the first available tuner device. If there is a tuner device that is available, the - * tuner device will be locked to the current instance. - * - * @return {@code true} if the operation was successful, {@code false} otherwise - */ - protected abstract boolean openFirstAvailable(); - - protected abstract boolean isDeviceOpen(); - - protected abstract long getDeviceId(); - - /** - * Sets the tuner channel. This should be called after acquiring a tuner device. - * - * @param frequency a frequency of the channel to tune to - * @param modulation a modulation method of the channel to tune to - * @param channelNumber channel number when channel number is already known. Some tuner HAL may - * use channelNumber instead of frequency for tune. - * @return {@code true} if the operation was successful, {@code false} otherwise - */ - public synchronized boolean tune( - int frequency, @ModulationType String modulation, String channelNumber) { - if (!isDeviceOpen()) { - Log.e(TAG, "There's no available device"); - return false; - } - if (mIsStreaming) { - nativeCloseAllPidFilters(getDeviceId()); - mIsStreaming = false; - } - - // When tuning to a new channel in the same frequency, there's no need to stop current tuner - // device completely and the only thing necessary for tuning is reopening pid filters. - if (mFrequency == frequency && Objects.equals(mModulation, modulation)) { - addPidFilter(PID_PAT, FILTER_TYPE_OTHER); - addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER); - if (isDvbDeliverySystem(mDeliverySystemType)) { - addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER); - addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER); - } - mIsStreaming = true; - return true; - } - int timeout_ms = - modulation.equals(MODULATION_8VSB) - ? DEFAULT_VSB_TUNE_TIMEOUT_MS - : DEFAULT_QAM_TUNE_TIMEOUT_MS; - if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) { - addPidFilter(PID_PAT, FILTER_TYPE_OTHER); - addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER); - if (isDvbDeliverySystem(mDeliverySystemType)) { - addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER); - addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER); - } - mFrequency = frequency; - mModulation = modulation; - mIsStreaming = true; - return true; - } - return false; - } - - protected native boolean nativeTune( - long deviceId, int frequency, @ModulationType String modulation, int timeout_ms); - - /** - * Sets a pid filter. This should be set after setting a channel. - * - * @param pid a pid number to be added to filter list - * @param filterType a type of pid. Must be one of (FILTER_TYPE_XXX) - * @return {@code true} if the operation was successful, {@code false} otherwise - */ - public synchronized boolean addPidFilter(int pid, @FilterType int filterType) { - if (!isDeviceOpen()) { - Log.e(TAG, "There's no available device"); - return false; - } - if (pid >= 0 && pid <= 0x1fff) { - nativeAddPidFilter(getDeviceId(), pid, filterType); - return true; - } - return false; - } - - protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType); - - protected native void nativeCloseAllPidFilters(long deviceId); - - protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune); - - protected native int nativeGetDeliverySystemType(long deviceId); - - /** - * Stops current tuning. The tuner device and pid filters will be reset by this call and make - * the tuner ready to accept another tune request. - */ - public synchronized void stopTune() { - if (isDeviceOpen()) { - if (mIsStreaming) { - nativeCloseAllPidFilters(getDeviceId()); - } - nativeStopTune(getDeviceId()); - } - mIsStreaming = false; - mFrequency = -1; - mModulation = null; - } - - public void setHasPendingTune(boolean hasPendingTune) { - nativeSetHasPendingTune(getDeviceId(), hasPendingTune); - } - - public int getDeliverySystemType() { - return mDeliverySystemType; - } - - protected native void nativeStopTune(long deviceId); - - /** - * This method must be called after {@link TunerHal#tune} and before {@link TunerHal#stopTune}. - * Writes at most maxSize TS frames in a buffer provided by the user. The frames employ MPEG - * encoding. - * - * @param javaBuffer a buffer to write the video data in - * @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number - * should be equal to the length of the buffer. - * @return the amount of bytes written in the buffer. Note that this value could be 0 if no new - * frames have been obtained since the last call. - */ - public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) { - if (isDeviceOpen()) { - return nativeWriteInBuffer(getDeviceId(), javaBuffer, javaBufferSize); - } else { - return 0; - } - } - - protected native int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize); - - /** - * Opens Linux DVB frontend device. This method is called from native JNI and used only for - * DvbTunerHal. - */ - protected int openDvbFrontEndFd() { - return -1; - } - - /** - * Opens Linux DVB demux device. This method is called from native JNI and used only for - * DvbTunerHal. - */ - protected int openDvbDemuxFd() { - return -1; - } - - /** - * Opens Linux DVB dvr device. This method is called from native JNI and used only for - * DvbTunerHal. - */ - protected int openDvbDvrFd() { - return -1; - } -} diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java index 829bec1c..f395949d 100644 --- a/src/com/android/tv/tuner/TunerInputController.java +++ b/src/com/android/tv/tuner/TunerInputController.java @@ -17,6 +17,9 @@ package com.android.tv.tuner; import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -24,10 +27,14 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; @@ -38,72 +45,92 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; -import com.android.tv.Features; import com.android.tv.R; +import com.android.tv.Starter; import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.setup.TunerSetupActivity; -import com.android.tv.tuner.tvinput.TunerTvInputService; -import com.android.tv.tuner.util.SystemPropertiesProxy; +import com.android.tv.TvSingletons; +import com.android.tv.common.BuildConfig; +import com.android.tv.common.util.SystemPropertiesProxy; +import com.android.tv.tuner.setup.BaseTunerSetupActivity; import com.android.tv.tuner.util.TunerInputInfoUtils; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** - * Controls the package visibility of {@link TunerTvInputService}. + * Controls the package visibility of {@link BaseTunerTvInputService}. * *

Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, {@code * UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} to * update the connection status of the supported USB TV tuners. */ public class TunerInputController { - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final String TAG = "TunerInputController"; private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner"; private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch"; private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd"; + private static final String PLAY_STORE_LINK_TEMPLATE = "market://details?id=%s"; /** Action of {@link Intent} to check network connection repeatedly when it is necessary. */ - private static final String CHECKING_NETWORK_CONNECTION = - "com.android.tv.action.CHECKING_NETWORK_CONNECTION"; + private static final String CHECKING_NETWORK_TUNER_STATUS = + "com.android.tv.action.CHECKING_NETWORK_TUNER_STATUS"; private static final String EXTRA_CHECKING_DURATION = "com.android.tv.action.extra.CHECKING_DURATION"; + private static final String EXTRA_DEVICE_IP = "com.android.tv.action.extra.DEVICE_IP"; private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10); + private static final String NOTIFICATION_CHANNEL_ID = "tuner_discovery_notification"; + // TODO: Load settings from XML file private static final TunerDevice[] TUNER_DEVICES = { new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q // WinTV-dualHD (bulk) will be supported after 2017 April security patch. new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk) - // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete. new TunerDevice(0x2040, 0x0264, null), }; private static final int MSG_ENABLE_INPUT_SERVICE = 1000; private static final long DVB_DRIVER_CHECK_DELAY_MS = 300; + private final ComponentName usbTunerComponent; + private final ComponentName networkTunerComponent; + private final ComponentName builtInTunerComponent; + private final Map mTunerServiceMapping = new HashMap<>(); + + private final Map mTunerApplicationNames = new HashMap<>(); + private final Map mNotificationMessages = new HashMap<>(); + private final Map mNotificationLargeIcons = new HashMap<>(); + + private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(this); + + public TunerInputController(ComponentName embeddedTuner) { + usbTunerComponent = embeddedTuner; + networkTunerComponent = usbTunerComponent; + builtInTunerComponent = usbTunerComponent; + for (TunerDevice device : TUNER_DEVICES) { + mTunerServiceMapping.put(device, usbTunerComponent); + } + } + /** Checks status of USB devices to see if there are available USB tuners connected. */ - public static void onCheckingUsbTunerStatus(Context context, String action) { - onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler()); + public void onCheckingUsbTunerStatus(Context context, String action) { + onCheckingUsbTunerStatus(context, action, mHandler); } - private static void onCheckingUsbTunerStatus( + private void onCheckingUsbTunerStatus( Context context, String action, @NonNull CheckDvbDeviceHandler handler) { - SharedPreferences sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(context); - if (TunerHal.useBuiltInTuner(context)) { - enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN); - return; - } - // Falls back to the below to check USB tuner devices. - boolean enabled = isUsbTunerConnected(context); + Set connectedUsbTuners = getConnectedUsbTuners(context); handler.removeMessages(MSG_ENABLE_INPUT_SERVICE); - if (enabled) { + if (!connectedUsbTuners.isEmpty()) { // Need to check if DVB driver is accessible. Since the driver creation // could be happen after the USB event, delay the checking by // DVB_DRIVER_CHECK_DELAY_MS. @@ -111,45 +138,37 @@ public class TunerInputController { handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), DVB_DRIVER_CHECK_DELAY_MS); } else { - if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) { - // Since network tuner is attached, do not disable TunerTvInput, - // just updates the TvInputInfo. - TunerInputInfoUtils.updateTunerInputInfo(context); - return; - } - enableTunerTvInputService( + handleTunerStatusChanged( context, false, - false, + connectedUsbTuners, TextUtils.equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ? TunerHal.TUNER_TYPE_USB : null); } } - private static void onNetworkTunerChanged(Context context, boolean enabled) { + private void onNetworkTunerChanged(Context context, boolean enabled) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + if (sharedPreferences.contains(PREFERENCE_IS_NETWORK_TUNER_ATTACHED) + && sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false) + == enabled) { + // the status is not changed + return; + } if (enabled) { - // Network tuner detection is initiated by UI. So the app should not - // be killed. sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply(); - enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK); } else { sharedPreferences .edit() .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false) .apply(); - if (!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) { - // Network tuner detection is initiated by UI. So the app should not - // be killed. - enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK); - } else { - // Since USB tuner is attached, do not disable TunerTvInput, - // just updates the TvInputInfo. - TunerInputInfoUtils.updateTunerInputInfo(context); - } } + // Network tuner detection is initiated by UI. So the app should not + // be killed. + handleTunerStatusChanged( + context, true, getConnectedUsbTuners(context), TunerHal.TUNER_TYPE_NETWORK); } /** @@ -158,75 +177,131 @@ public class TunerInputController { * @param context {@link Context} instance * @return {@code true} if any tuner device we support is plugged in */ - private static boolean isUsbTunerConnected(Context context) { + private Set getConnectedUsbTuners(Context context) { UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); Map deviceList = manager.getDeviceList(); String currentSecurityLevel = SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null); + Set devices = new HashSet<>(); for (UsbDevice device : deviceList.values()) { if (DEBUG) { Log.d(TAG, "Device: " + device); } for (TunerDevice tuner : TUNER_DEVICES) { - if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) { + if (tuner.equalsTo(device) && tuner.isSupported(currentSecurityLevel)) { Log.i(TAG, "Tuner found"); - return true; + devices.add(tuner); } } } - return false; + return devices; + } + + private void handleTunerStatusChanged( + Context context, + boolean forceDontKillApp, + Set connectedUsbTuners, + Integer triggerType) { + Map serviceToEnable = new HashMap<>(); + Set serviceToDisable = new HashSet<>(); + serviceToDisable.add(builtInTunerComponent); + serviceToDisable.add(networkTunerComponent); + if (TunerFeatures.TUNER.isEnabled(context)) { + // TODO: support both built-in tuner and other tuners at the same time? + if (TunerHal.useBuiltInTuner(context)) { + enableTunerTvInputService( + context, true, false, TunerHal.TUNER_TYPE_BUILT_IN, builtInTunerComponent); + return; + } + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); + if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) { + serviceToEnable.put(networkTunerComponent, TunerHal.TUNER_TYPE_NETWORK); + } + } + for (TunerDevice device : TUNER_DEVICES) { + if (TunerFeatures.TUNER.isEnabled(context) && connectedUsbTuners.contains(device)) { + serviceToEnable.put(mTunerServiceMapping.get(device), TunerHal.TUNER_TYPE_USB); + } else { + serviceToDisable.add(mTunerServiceMapping.get(device)); + } + } + serviceToDisable.removeAll(serviceToEnable.keySet()); + for (ComponentName serviceComponent : serviceToEnable.keySet()) { + if (isTunerPackageInstalled(context, serviceComponent)) { + enableTunerTvInputService( + context, + true, + forceDontKillApp, + serviceToEnable.get(serviceComponent), + serviceComponent); + } else { + sendNotificationToInstallPackage(context, serviceComponent); + } + } + for (ComponentName serviceComponent : serviceToDisable) { + if (isTunerPackageInstalled(context, serviceComponent)) { + enableTunerTvInputService( + context, false, forceDontKillApp, triggerType, serviceComponent); + } else { + cancelNotificationToInstallPackage(context, serviceComponent); + } + } } /** - * Enable/disable the component {@link TunerTvInputService}. + * Enable/disable the component {@link BaseTunerTvInputService}. * * @param context {@link Context} instance * @param enabled {@code true} to enable the service; otherwise {@code false} */ private static void enableTunerTvInputService( - Context context, boolean enabled, boolean forceDontKillApp, Integer tunerType) { + Context context, + boolean enabled, + boolean forceDontKillApp, + Integer tunerType, + ComponentName serviceComponent) { if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled); PackageManager pm = context.getPackageManager(); - ComponentName componentName = new ComponentName(context, TunerTvInputService.class); - - // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling - // TvActivity, the following pm.setComponentEnabledSetting doesn't work. - ((TvApplication) context.getApplicationContext()) - .handleInputCountChanged(true, enabled, true); - // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds - // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only - // when the LiveChannels app is active since we don't want to kill the running app. - int flags = - forceDontKillApp - || TvApplication.getSingletons(context) - .getMainActivityWrapper() - .isCreated() - ? PackageManager.DONT_KILL_APP - : 0; int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - if (newState != pm.getComponentEnabledSetting(componentName)) { - // Send/cancel the USB tuner TV input setup notification. - TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType); - // Enable/disable the USB tuner TV input. - pm.setComponentEnabledSetting(componentName, newState, flags); - if (!enabled && tunerType != null) { - if (tunerType == TunerHal.TUNER_TYPE_USB) { - Toast.makeText(context, R.string.msg_usb_tuner_disconnected, Toast.LENGTH_SHORT) - .show(); - } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) { - Toast.makeText( - context, - R.string.msg_network_tuner_disconnected, - Toast.LENGTH_SHORT) - .show(); + if (newState != pm.getComponentEnabledSetting(serviceComponent)) { + int flags = forceDontKillApp ? PackageManager.DONT_KILL_APP : 0; + if (serviceComponent.getPackageName().equals(context.getPackageName())) { + // Don't kill APP when handling input count changing. Or the following + // setComponentEnabledSetting() call won't work. + ((TvApplication) context.getApplicationContext()) + .handleInputCountChanged(true, enabled, true); + // Bundled input. Don't kill app if LiveChannels app is active since we don't want + // to kill the running app. + if (TvSingletons.getSingletons(context).getMainActivityWrapper().isCreated()) { + flags |= PackageManager.DONT_KILL_APP; + } + // Send/cancel the USB tuner TV input setup notification. + BaseTunerSetupActivity.onTvInputEnabled(context, enabled, tunerType); + if (!enabled && tunerType != null) { + if (tunerType == TunerHal.TUNER_TYPE_USB) { + Toast.makeText( + context, + R.string.msg_usb_tuner_disconnected, + Toast.LENGTH_SHORT) + .show(); + } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) { + Toast.makeText( + context, + R.string.msg_network_tuner_disconnected, + Toast.LENGTH_SHORT) + .show(); + } } } + // Enable/disable the USB tuner TV input. + pm.setComponentEnabledSetting(serviceComponent, newState, flags); if (DEBUG) Log.d(TAG, "Status updated:" + enabled); - } else if (enabled) { + } else if (enabled && serviceComponent.getPackageName().equals(context.getPackageName())) { // When # of tuners is changed or the tuner input service is switching from/to using // network tuners or the device just boots. TunerInputInfoUtils.updateTunerInputInfo(context); @@ -236,62 +311,50 @@ public class TunerInputController { /** * Discovers a network tuner. If the network connection is down, it won't repeatedly checking. */ - public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) { - boolean runningInMainProcess = - TvApplication.getSingletons(context).isRunningInMainProcess(); - SoftPreconditions.checkState(runningInMainProcess); - if (!runningInMainProcess) { - return; - } - executeNetworkTunerDiscoveryAsyncTask(context, 0); + public void executeNetworkTunerDiscoveryAsyncTask(final Context context) { + executeNetworkTunerDiscoveryAsyncTask(context, 0, 0); } /** * Discovers a network tuner. * * @param context {@link Context} - * @param repeatedDurationMs the time length to wait to repeatedly check network status to start + * @param repeatedDurationMs The time length to wait to repeatedly check network status to start * finding network tuner when the network connection is not available. {@code 0} to disable * repeatedly checking. + * @param deviceIp The previous discovered device IP, 0 if none. */ - private static void executeNetworkTunerDiscoveryAsyncTask( - final Context context, final long repeatedDurationMs) { - if (!Features.NETWORK_TUNER.isEnabled(context)) { + private void executeNetworkTunerDiscoveryAsyncTask( + final Context context, final long repeatedDurationMs, final int deviceIp) { + if (!TunerFeatures.NETWORK_TUNER.isEnabled(context)) { return; } - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - if (isNetworkConnected(context)) { + final Intent networkCheckingIntent = new Intent(context, IntentReceiver.class); + networkCheckingIntent.setAction(CHECKING_NETWORK_TUNER_STATUS); + if (!isNetworkConnected(context) && repeatedDurationMs > 0) { + sendCheckingAlarm(context, networkCheckingIntent, repeatedDurationMs); + } else { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + Boolean result = null; // Implement and execute network tuner discovery AsyncTask here. - } else if (repeatedDurationMs > 0) { - AlarmManager alarmManager = - (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent networkCheckingIntent = new Intent(context, IntentReceiver.class); - networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION); - networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs); - PendingIntent alarmIntent = - PendingIntent.getBroadcast( - context, - 0, - networkCheckingIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - alarmManager.set( - AlarmManager.ELAPSED_REALTIME, - SystemClock.elapsedRealtime() + repeatedDurationMs, - alarmIntent); + return result; } - return null; - } - @Override - protected void onPostExecute(Boolean result) { - if (result == null) { - return; + @Override + protected void onPostExecute(Boolean foundNetworkTuner) { + if (foundNetworkTuner == null) { + return; + } + sendCheckingAlarm( + context, + networkCheckingIntent, + foundNetworkTuner ? INITIAL_CHECKING_DURATION_MS : repeatedDurationMs); + onNetworkTunerChanged(context, foundNetworkTuner); } - onNetworkTunerChanged(context, result); - } - }.execute(); + }.execute(); + } } private static boolean isNetworkConnected(Context context) { @@ -301,33 +364,119 @@ public class TunerInputController { return networkInfo != null && networkInfo.isConnected(); } + private static void sendCheckingAlarm(Context context, Intent intent, long delayMs) { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + intent.putExtra(EXTRA_CHECKING_DURATION, delayMs); + PendingIntent alarmIntent = + PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + delayMs, + alarmIntent); + } + + private static boolean isTunerPackageInstalled( + Context context, ComponentName serviceComponent) { + try { + context.getPackageManager().getPackageInfo(serviceComponent.getPackageName(), 0); + return true; + } catch (NameNotFoundException e) { + return false; + } + } + + private void sendNotificationToInstallPackage(Context context, ComponentName serviceComponent) { + if (!BuildConfig.ENG) { + return; + } + String applicationName = mTunerApplicationNames.get(serviceComponent); + if (applicationName == null) { + applicationName = context.getString(R.string.tuner_install_default_application_name); + } + String contentTitle = + context.getString( + R.string.tuner_install_notification_content_title, applicationName); + String contentText = mNotificationMessages.get(serviceComponent); + if (contentText == null) { + contentText = context.getString(R.string.tuner_install_notification_content_text); + } + Bitmap largeIcon = mNotificationLargeIcons.get(serviceComponent); + if (largeIcon == null) { + // TODO: Make a better default image. + largeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_store); + } + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) { + createNotificationChannel(context, notificationManager); + } + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData( + Uri.parse( + String.format( + PLAY_STORE_LINK_TEMPLATE, serviceComponent.getPackageName()))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID); + builder.setAutoCancel(true) + .setSmallIcon(R.drawable.ic_launcher_s) + .setLargeIcon(largeIcon) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setCategory(Notification.CATEGORY_RECOMMENDATION) + .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)); + notificationManager.notify(serviceComponent.getPackageName(), 0, builder.build()); + } + + private static void cancelNotificationToInstallPackage( + Context context, ComponentName serviceComponent) { + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(serviceComponent.getPackageName(), 0); + } + + private static void createNotificationChannel( + Context context, NotificationManager notificationManager) { + notificationManager.createNotificationChannel( + new NotificationChannel( + NOTIFICATION_CHANNEL_ID, + context.getResources() + .getString(R.string.ut_setup_notification_channel_name), + NotificationManager.IMPORTANCE_HIGH)); + } + public static class IntentReceiver extends BroadcastReceiver { - private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(); @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); - TvApplication.setCurrentRunningProcess(context, true); - if (!Features.TUNER.isEnabled(context)) { - enableTunerTvInputService(context, false, false, null); + Starter.start(context); + TunerInputController tunerInputController = + TvSingletons.getSingletons(context).getTunerInputController(); + if (!TunerFeatures.TUNER.isEnabled(context)) { + tunerInputController.handleTunerStatusChanged( + context, false, Collections.emptySet(), null); return; } switch (intent.getAction()) { case Intent.ACTION_BOOT_COMPLETED: - executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS); + tunerInputController.executeNetworkTunerDiscoveryAsyncTask( + context, INITIAL_CHECKING_DURATION_MS, 0); + // fall through case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED: case UsbManager.ACTION_USB_DEVICE_ATTACHED: case UsbManager.ACTION_USB_DEVICE_DETACHED: - onCheckingUsbTunerStatus(context, intent.getAction(), mHandler); + tunerInputController.onCheckingUsbTunerStatus(context, intent.getAction()); break; - case CHECKING_NETWORK_CONNECTION: + case CHECKING_NETWORK_TUNER_STATUS: long repeatedDurationMs = intent.getLongExtra( EXTRA_CHECKING_DURATION, INITIAL_CHECKING_DURATION_MS); - executeNetworkTunerDiscoveryAsyncTask( + tunerInputController.executeNetworkTunerDiscoveryAsyncTask( context, - Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS)); + Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS), + intent.getIntExtra(EXTRA_DEVICE_IP, 0)); break; + default: // fall out } } } @@ -349,7 +498,7 @@ public class TunerInputController { this.minSecurityLevel = minSecurityLevel; } - private boolean equals(UsbDevice device) { + private boolean equalsTo(UsbDevice device) { return device.getVendorId() == vendorId && device.getProductId() == productId; } @@ -372,10 +521,13 @@ public class TunerInputController { } private static class CheckDvbDeviceHandler extends Handler { + + private final TunerInputController mTunerInputController; private DvbDeviceAccessor mDvbDeviceAccessor; - CheckDvbDeviceHandler() { + CheckDvbDeviceHandler(TunerInputController tunerInputController) { super(Looper.getMainLooper()); + this.mTunerInputController = tunerInputController; } @Override @@ -387,9 +539,15 @@ public class TunerInputController { mDvbDeviceAccessor = new DvbDeviceAccessor(context); } boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable(); - enableTunerTvInputService( - context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null); + mTunerInputController.handleTunerStatusChanged( + context, + false, + enabled + ? mTunerInputController.getConnectedUsbTuners(context) + : Collections.emptySet(), + TunerHal.TUNER_TYPE_USB); break; + default: // fall out } } } diff --git a/src/com/android/tv/tuner/TunerPreferenceProvider.java b/src/com/android/tv/tuner/TunerPreferenceProvider.java deleted file mode 100644 index 425c30ac..00000000 --- a/src/com/android/tv/tuner/TunerPreferenceProvider.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; - -/** - * A content provider for storing the preferences. It's used across TV app and USB tuner TV input. - */ -public class TunerPreferenceProvider extends ContentProvider { - /** The authority of the provider */ - public static final String AUTHORITY = "com.android.tv.tuner.preferences"; - - private static final String PATH_PREFERENCES = "preferences"; - - private static final int DATABASE_VERSION = 1; - private static final String DATABASE_NAME = "usbtuner_preferences.db"; - private static final String PREFERENCES_TABLE = "preferences"; - - private static final int MATCH_PREFERENCE = 1; - private static final int MATCH_PREFERENCE_KEY = 2; - - private static final UriMatcher sUriMatcher; - - private DatabaseOpenHelper mDatabaseOpenHelper; - - static { - sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - sUriMatcher.addURI(AUTHORITY, "preferences", MATCH_PREFERENCE); - sUriMatcher.addURI(AUTHORITY, "preferences/*", MATCH_PREFERENCE_KEY); - } - - /** - * Builds a Uri that points to a specific preference. - * - * @param key a key of the preference to point to - */ - public static Uri buildPreferenceUri(String key) { - return Preferences.CONTENT_URI.buildUpon().appendPath(key).build(); - } - - /** Columns definitions for the preferences table. */ - public interface Preferences { - - /** The content:// style for the preferences table. */ - Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES); - - /** The MIME type of a directory of preferences. */ - String CONTENT_TYPE = "vnd.android.cursor.dir/preferences"; - - /** The MIME type of a single preference. */ - String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences"; - - /** - * The ID of this preference. - * - *

This is auto-incremented. - * - *

Type: INTEGER - */ - String _ID = "_id"; - - /** - * The key of this preference. - * - *

Should be unique. - * - *

Type: TEXT - */ - String COLUMN_KEY = "key"; - - /** - * The value of this preference. - * - *

Type: TEXT - */ - String COLUMN_VALUE = "value"; - } - - private static class DatabaseOpenHelper extends SQLiteOpenHelper { - public DatabaseOpenHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL( - "CREATE TABLE " - + PREFERENCES_TABLE - + " (" - + Preferences._ID - + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Preferences.COLUMN_KEY - + " TEXT NOT NULL," - + Preferences.COLUMN_VALUE - + " TEXT);"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // No-op - } - } - - @Override - public boolean onCreate() { - mDatabaseOpenHelper = new DatabaseOpenHelper(getContext()); - return true; - } - - @Override - public Cursor query( - Uri uri, - String[] projection, - String selection, - String[] selectionArgs, - String sortOrder) { - int match = sUriMatcher.match(uri); - if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) { - throw new UnsupportedOperationException(); - } - SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase(); - Cursor cursor = - db.query( - PREFERENCES_TABLE, - projection, - selection, - selectionArgs, - null, - null, - sortOrder); - cursor.setNotificationUri(getContext().getContentResolver(), uri); - return cursor; - } - - @Override - public String getType(Uri uri) { - switch (sUriMatcher.match(uri)) { - case MATCH_PREFERENCE: - return Preferences.CONTENT_TYPE; - case MATCH_PREFERENCE_KEY: - return Preferences.CONTENT_ITEM_TYPE; - } - throw new IllegalArgumentException("Unknown URI " + uri); - } - - /** - * Inserts a preference row into the preference table. - * - *

If a key is already exists in the table, it removes the old row and inserts a new row. - * - * @param uri the URL of the table to insert into - * @param values the initial values for the newly inserted row - * @return the URL of the newly created row - */ - @Override - public Uri insert(Uri uri, ContentValues values) { - if (sUriMatcher.match(uri) != MATCH_PREFERENCE) { - throw new UnsupportedOperationException(); - } - return insertRow(uri, values); - } - - private Uri insertRow(Uri uri, ContentValues values) { - SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase(); - - // Remove the old row. - db.delete( - PREFERENCES_TABLE, - Preferences.COLUMN_KEY + " like ?", - new String[] {values.getAsString(Preferences.COLUMN_KEY)}); - - long rowId = db.insert(PREFERENCES_TABLE, null, values); - if (rowId > 0) { - Uri rowUri = buildPreferenceUri(values.getAsString(Preferences.COLUMN_KEY)); - getContext().getContentResolver().notifyChange(rowUri, null); - return rowUri; - } - - throw new SQLiteException("Failed to insert row into " + uri); - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException(); - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException(); - } -} diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java deleted file mode 100644 index 62a4ce99..00000000 --- a/src/com/android/tv/tuner/TunerPreferences.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.database.Cursor; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.GuardedBy; -import android.support.annotation.IntDef; -import android.support.annotation.MainThread; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.TunerPreferenceProvider.Preferences; -import com.android.tv.tuner.util.TisConfiguration; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** A helper class for the USB tuner preferences. */ -public class TunerPreferences { - private static final String TAG = "TunerPreferences"; - - private static final String PREFS_KEY_CHANNEL_DATA_VERSION = "channel_data_version"; - private static final String PREFS_KEY_SCANNED_CHANNEL_COUNT = "scanned_channel_count"; - private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code"; - private static final String PREFS_KEY_SCAN_DONE = "scan_done"; - private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup"; - private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream"; - private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting"; - private static final String PREFS_KEY_TRICKPLAY_EXPIRED_MS = "trickplay_expired_ms"; - - private static final String SHARED_PREFS_NAME = "com.android.tv.tuner.preferences"; - - public static final int CHANNEL_DATA_VERSION_NOT_SET = -1; - - @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED}) - @Retention(RetentionPolicy.SOURCE) - public @interface TrickplaySetting {} - - /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */ - public static final int TRICKPLAY_SETTING_NOT_SET = -1; - - /** Trickplay setting is disabled. */ - public static final int TRICKPLAY_SETTING_DISABLED = 0; - - /** Trickplay setting is enabled. */ - public static final int TRICKPLAY_SETTING_ENABLED = 1; - - @GuardedBy("TunerPreferences.class") - private static final Bundle sPreferenceValues = new Bundle(); - - private static LoadPreferencesTask sLoadPreferencesTask; - private static ContentObserver sContentObserver; - private static TunerPreferencesChangedListener sPreferencesChangedListener = null; - - private static boolean sInitialized; - - /** Listeners for TunerPreferences change. */ - public interface TunerPreferencesChangedListener { - void onTunerPreferencesChanged(); - } - - /** Initializes the USB tuner preferences. */ - @MainThread - public static void initialize(final Context context) { - if (sInitialized) { - return; - } - sInitialized = true; - if (useContentProvider(context)) { - loadPreferences(context); - sContentObserver = - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - loadPreferences(context); - } - }; - context.getContentResolver() - .registerContentObserver( - TunerPreferenceProvider.Preferences.CONTENT_URI, - true, - sContentObserver); - } else { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - getSharedPreferences(context); - return null; - } - }.execute(); - } - } - - /** Releases the resources. */ - public static synchronized void release(Context context) { - if (useContentProvider(context) && sContentObserver != null) { - context.getContentResolver().unregisterContentObserver(sContentObserver); - } - setTunerPreferencesChangedListener(null); - } - - /** Sets the listener for TunerPreferences change. */ - public static void setTunerPreferencesChangedListener( - TunerPreferencesChangedListener listener) { - sPreferencesChangedListener = listener; - } - - /** - * Loads the preferences from database. - * - *

This preferences is used across processes, so the preferences should be loaded again when - * the databases changes. - */ - @MainThread - public static void loadPreferences(Context context) { - if (sLoadPreferencesTask != null - && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) { - sLoadPreferencesTask.cancel(true); - } - sLoadPreferencesTask = new LoadPreferencesTask(context); - sLoadPreferencesTask.execute(); - } - - private static boolean useContentProvider(Context context) { - // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access. - return TisConfiguration.isPackagedWithLiveChannels(context); - } - - public static synchronized int getChannelDataVersion(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getInt( - PREFS_KEY_CHANNEL_DATA_VERSION, CHANNEL_DATA_VERSION_NOT_SET); - } else { - return getSharedPreferences(context) - .getInt( - TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, - CHANNEL_DATA_VERSION_NOT_SET); - } - } - - public static synchronized void setChannelDataVersion(Context context, int version) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version); - } else { - getSharedPreferences(context) - .edit() - .putInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, version) - .apply(); - } - } - - public static synchronized int getScannedChannelCount(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT); - } else { - return getSharedPreferences(context) - .getInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, 0); - } - } - - public static synchronized void setScannedChannelCount(Context context, int channelCount) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount); - } else { - getSharedPreferences(context) - .edit() - .putInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount) - .apply(); - } - } - - public static synchronized String getLastPostalCode(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE); - } else { - return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null); - } - } - - public static synchronized void setLastPostalCode(Context context, String postalCode) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode); - } else { - getSharedPreferences(context) - .edit() - .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode) - .apply(); - } - } - - public static synchronized boolean isScanDone(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE); - } else { - return getSharedPreferences(context) - .getBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, false); - } - } - - public static synchronized void setScanDone(Context context) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_SCAN_DONE, true); - } else { - getSharedPreferences(context) - .edit() - .putBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, true) - .apply(); - } - } - - public static synchronized boolean shouldShowSetupActivity(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP); - } else { - return getSharedPreferences(context) - .getBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, false); - } - } - - public static synchronized void setShouldShowSetupActivity(Context context, boolean need) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); - } else { - getSharedPreferences(context) - .edit() - .putBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, need) - .apply(); - } - } - - public static synchronized long getTrickplayExpiredMs(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getLong(PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); - } else { - return getSharedPreferences(context) - .getLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0); - } - } - - public static synchronized void setTrickplayExpiredMs(Context context, long timeMs) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs); - } else { - getSharedPreferences(context) - .edit() - .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs) - .apply(); - } - } - - public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET); - } else { - return getSharedPreferences(context) - .getInt( - TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, - TRICKPLAY_SETTING_NOT_SET); - } - } - - public static synchronized void setTrickplaySetting( - Context context, @TrickplaySetting int trickplaySetting) { - SoftPreconditions.checkState(sInitialized); - SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET); - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting); - } else { - getSharedPreferences(context) - .edit() - .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting) - .apply(); - } - } - - public static synchronized boolean getStoreTsStream(Context context) { - SoftPreconditions.checkState(sInitialized); - if (useContentProvider(context)) { - return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); - } else { - return getSharedPreferences(context) - .getBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, false); - } - } - - public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { - if (useContentProvider(context)) { - setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); - } else { - getSharedPreferences(context) - .edit() - .putBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, shouldStore) - .apply(); - } - } - - private static SharedPreferences getSharedPreferences(Context context) { - return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); - } - - private static synchronized void setPreference(Context context, String key, String value) { - sPreferenceValues.putString(key, value); - savePreference(context, key, value); - } - - private static synchronized void setPreference(Context context, String key, int value) { - sPreferenceValues.putInt(key, value); - savePreference(context, key, Integer.toString(value)); - } - - private static synchronized void setPreference(Context context, String key, long value) { - sPreferenceValues.putLong(key, value); - savePreference(context, key, Long.toString(value)); - } - - private static synchronized void setPreference(Context context, String key, boolean value) { - sPreferenceValues.putBoolean(key, value); - savePreference(context, key, Boolean.toString(value)); - } - - private static void savePreference( - final Context context, final String key, final String value) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - ContentResolver resolver = context.getContentResolver(); - ContentValues values = new ContentValues(); - values.put(Preferences.COLUMN_KEY, key); - values.put(Preferences.COLUMN_VALUE, value); - try { - resolver.insert(Preferences.CONTENT_URI, values); - } catch (Exception e) { - SoftPreconditions.warn( - TAG, "setPreference", "Error writing preference values", e); - } - return null; - } - }.execute(); - } - - private static class LoadPreferencesTask extends AsyncTask { - private final Context mContext; - - private LoadPreferencesTask(Context context) { - mContext = context; - } - - @Override - protected Bundle doInBackground(Void... params) { - Bundle bundle = new Bundle(); - ContentResolver resolver = mContext.getContentResolver(); - String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE}; - try (Cursor cursor = - resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) { - if (cursor != null) { - while (!isCancelled() && cursor.moveToNext()) { - String key = cursor.getString(0); - String value = cursor.getString(1); - switch (key) { - case PREFS_KEY_TRICKPLAY_EXPIRED_MS: - bundle.putLong(key, Long.parseLong(value)); - break; - case PREFS_KEY_CHANNEL_DATA_VERSION: - case PREFS_KEY_SCANNED_CHANNEL_COUNT: - case PREFS_KEY_TRICKPLAY_SETTING: - try { - bundle.putInt(key, Integer.parseInt(value)); - } catch (NumberFormatException e) { - // Does nothing. - } - break; - case PREFS_KEY_SCAN_DONE: - case PREFS_KEY_LAUNCH_SETUP: - case PREFS_KEY_STORE_TS_STREAM: - bundle.putBoolean(key, Boolean.parseBoolean(value)); - break; - case PREFS_KEY_LAST_POSTAL_CODE: - bundle.putString(key, value); - break; - } - } - } - } catch (Exception e) { - SoftPreconditions.warn(TAG, "getPreference", "Error querying preference values", e); - return null; - } - return bundle; - } - - @Override - protected void onPostExecute(Bundle bundle) { - synchronized (TunerPreferences.class) { - if (bundle != null) { - sPreferenceValues.putAll(bundle); - } - } - if (sPreferencesChangedListener != null) { - sPreferencesChangedListener.onTunerPreferencesChanged(); - } - } - } -} diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java deleted file mode 100644 index eb9ad463..00000000 --- a/src/com/android/tv/tuner/cc/CaptionLayout.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.cc; - -import android.content.Context; -import android.util.AttributeSet; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.layout.ScaledLayout; - -/** - * Layout containing the safe title area that helps the closed captions look more prominent. This is - * required by CEA-708B. - */ -public class CaptionLayout extends ScaledLayout { - // The safe title area has 10% margins of the screen. - private static final float SAFE_TITLE_AREA_SCALE_START_X = 0.1f; - private static final float SAFE_TITLE_AREA_SCALE_END_X = 0.9f; - private static final float SAFE_TITLE_AREA_SCALE_START_Y = 0.1f; - private static final float SAFE_TITLE_AREA_SCALE_END_Y = 0.9f; - - private final ScaledLayout mSafeTitleAreaLayout; - private AtscCaptionTrack mCaptionTrack; - - public CaptionLayout(Context context) { - this(context, null); - } - - public CaptionLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CaptionLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mSafeTitleAreaLayout = new ScaledLayout(context); - addView( - mSafeTitleAreaLayout, - new ScaledLayoutParams( - SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X, - SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y)); - } - - public void addOrUpdateViewToSafeTitleArea( - CaptionWindowLayout captionWindowLayout, ScaledLayoutParams scaledLayoutParams) { - int index = mSafeTitleAreaLayout.indexOfChild(captionWindowLayout); - if (index < 0) { - mSafeTitleAreaLayout.addView(captionWindowLayout, scaledLayoutParams); - return; - } - mSafeTitleAreaLayout.updateViewLayout(captionWindowLayout, scaledLayoutParams); - } - - public void removeViewFromSafeTitleArea(CaptionWindowLayout captionWindowLayout) { - mSafeTitleAreaLayout.removeView(captionWindowLayout); - } - - public void setCaptionTrack(AtscCaptionTrack captionTrack) { - mCaptionTrack = captionTrack; - } - - public AtscCaptionTrack getCaptionTrack() { - return mCaptionTrack; - } -} diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java deleted file mode 100644 index bcb8e1c0..00000000 --- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.cc; - -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.view.View; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; -import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; -import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation; -import com.android.tv.tuner.data.Cea708Data.CaptionWindow; -import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -/** Decodes and renders CEA-708. */ -public class CaptionTrackRenderer implements Handler.Callback { - // TODO: Remaining works - // CaptionTrackRenderer does not support the full spec of CEA-708. The remaining works are - // described in the follows. - // C0 Table: Backspace, FF, and HCR are not supported. The rule for P16 is not standardized but - // it is handled as EUC-KR charset for korea broadcasting. - // C1 Table: All styles of windows and pens except underline, italic, pen size, and pen offset - // specified in CEA-708 are ignored and this follows system wide cc preferences for - // look and feel. SetPenLocation is not implemented. - // G2 Table: TSP, NBTSP and BLK are not supported. - // Text/commands: Word wrapping, fonts, row and column locking are not supported. - - private static final String TAG = "CaptionTrackRenderer"; - private static final boolean DEBUG = false; - - private static final long DELAY_IN_MILLIS = TimeUnit.MILLISECONDS.toMillis(100); - - // According to CEA-708B, there can exist up to 8 caption windows. - private static final int CAPTION_WINDOWS_MAX = 8; - private static final int CAPTION_ALL_WINDOWS_BITMAP = 255; - - private static final int MSG_DELAY_CANCEL = 1; - private static final int MSG_CAPTION_CLEAR = 2; - - private static final long CAPTION_CLEAR_INTERVAL_MS = 60000; - - private final CaptionLayout mCaptionLayout; - private boolean mIsDelayed = false; - private CaptionWindowLayout mCurrentWindowLayout; - private final CaptionWindowLayout[] mCaptionWindowLayouts = - new CaptionWindowLayout[CAPTION_WINDOWS_MAX]; - private final ArrayList mPendingCaptionEvents = new ArrayList<>(); - private final Handler mHandler; - - public CaptionTrackRenderer(CaptionLayout captionLayout) { - mCaptionLayout = captionLayout; - mHandler = new Handler(this); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_DELAY_CANCEL: - delayCancel(); - return true; - case MSG_CAPTION_CLEAR: - clearWindows(CAPTION_ALL_WINDOWS_BITMAP); - return true; - } - return false; - } - - public void start(AtscCaptionTrack captionTrack) { - if (captionTrack == null) { - stop(); - return; - } - if (DEBUG) { - Log.d(TAG, "Start captionTrack " + captionTrack.language); - } - reset(); - mCaptionLayout.setCaptionTrack(captionTrack); - mCaptionLayout.setVisibility(View.VISIBLE); - } - - public void stop() { - if (DEBUG) { - Log.d(TAG, "Stop captionTrack"); - } - mCaptionLayout.setVisibility(View.INVISIBLE); - mHandler.removeMessages(MSG_CAPTION_CLEAR); - } - - public void processCaptionEvent(CaptionEvent event) { - if (mIsDelayed) { - mPendingCaptionEvents.add(event); - return; - } - switch (event.type) { - case Cea708Parser.CAPTION_EMIT_TYPE_BUFFER: - sendBufferToCurrentWindow((String) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_CONTROL: - sendControlToCurrentWindow((char) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CWX: - setCurrentWindowLayout((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CLW: - clearWindows((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DSW: - displayWindows((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_HDW: - hideWindows((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_TGW: - toggleWindows((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLW: - deleteWindows((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLY: - delay((int) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLC: - delayCancel(); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_RST: - reset(); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPA: - setPenAttr((CaptionPenAttr) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPC: - setPenColor((CaptionPenColor) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPL: - setPenLocation((CaptionPenLocation) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SWA: - setWindowAttr((CaptionWindowAttr) event.obj); - break; - case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DFX: - defineWindow((CaptionWindow) event.obj); - break; - } - } - - // The window related caption commands - private void setCurrentWindowLayout(int windowId) { - if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) { - return; - } - CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId]; - if (windowLayout == null) { - return; - } - if (DEBUG) { - Log.d(TAG, "setCurrentWindowLayout to " + windowId); - } - mCurrentWindowLayout = windowLayout; - } - - // Each bit of windowBitmap indicates a window. - // If a bit is set, the window id is the same as the number of the trailing zeros of the bit. - private ArrayList getWindowsFromBitmap(int windowBitmap) { - ArrayList windows = new ArrayList<>(); - for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) { - if ((windowBitmap & (1 << i)) != 0) { - CaptionWindowLayout windowLayout = mCaptionWindowLayouts[i]; - if (windowLayout != null) { - windows.add(windowLayout); - } - } - } - return windows; - } - - private void clearWindows(int windowBitmap) { - if (windowBitmap == 0) { - return; - } - for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { - windowLayout.clear(); - } - } - - private void displayWindows(int windowBitmap) { - if (windowBitmap == 0) { - return; - } - for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { - windowLayout.show(); - } - } - - private void hideWindows(int windowBitmap) { - if (windowBitmap == 0) { - return; - } - for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { - windowLayout.hide(); - } - } - - private void toggleWindows(int windowBitmap) { - if (windowBitmap == 0) { - return; - } - for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { - if (windowLayout.isShown()) { - windowLayout.hide(); - } else { - windowLayout.show(); - } - } - } - - private void deleteWindows(int windowBitmap) { - if (windowBitmap == 0) { - return; - } - for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) { - windowLayout.removeFromCaptionView(); - mCaptionWindowLayouts[windowLayout.getCaptionWindowId()] = null; - } - } - - public void clear() { - mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR); - } - - public void reset() { - mCurrentWindowLayout = null; - mIsDelayed = false; - mPendingCaptionEvents.clear(); - for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) { - if (mCaptionWindowLayouts[i] != null) { - mCaptionWindowLayouts[i].removeFromCaptionView(); - } - mCaptionWindowLayouts[i] = null; - } - mCaptionLayout.setVisibility(View.INVISIBLE); - mHandler.removeMessages(MSG_CAPTION_CLEAR); - } - - private void setWindowAttr(CaptionWindowAttr windowAttr) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.setWindowAttr(windowAttr); - } - } - - private void defineWindow(CaptionWindow window) { - if (window == null) { - return; - } - int windowId = window.id; - if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) { - return; - } - CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId]; - if (windowLayout == null) { - windowLayout = new CaptionWindowLayout(mCaptionLayout.getContext()); - } - windowLayout.initWindow(mCaptionLayout, window); - mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout; - } - - // The job related caption commands - private void delay(int tenthsOfSeconds) { - if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) { - return; - } - mIsDelayed = true; - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_DELAY_CANCEL), tenthsOfSeconds * DELAY_IN_MILLIS); - } - - private void delayCancel() { - mIsDelayed = false; - processPendingBuffer(); - } - - private void processPendingBuffer() { - for (CaptionEvent event : mPendingCaptionEvents) { - processCaptionEvent(event); - } - mPendingCaptionEvents.clear(); - } - - // The implicit write caption commands - private void sendControlToCurrentWindow(char control) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.sendControl(control); - } - } - - private void sendBufferToCurrentWindow(String buffer) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.sendBuffer(buffer); - mHandler.removeMessages(MSG_CAPTION_CLEAR); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_CAPTION_CLEAR), CAPTION_CLEAR_INTERVAL_MS); - } - } - - // The pen related caption commands - private void setPenAttr(CaptionPenAttr attr) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.setPenAttr(attr); - } - } - - private void setPenColor(CaptionPenColor color) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.setPenColor(color); - } - } - - private void setPenLocation(CaptionPenLocation location) { - if (mCurrentWindowLayout != null) { - mCurrentWindowLayout.setPenLocation(location.row, location.column); - } - } -} diff --git a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java b/src/com/android/tv/tuner/cc/CaptionWindowLayout.java deleted file mode 100644 index e9371f94..00000000 --- a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java +++ /dev/null @@ -1,680 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.cc; - -import android.content.Context; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.text.Layout.Alignment; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; -import android.text.style.SubscriptSpan; -import android.text.style.SuperscriptSpan; -import android.text.style.UnderlineSpan; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.CaptioningManager; -import android.view.accessibility.CaptioningManager.CaptionStyle; -import android.view.accessibility.CaptioningManager.CaptioningChangeListener; -import android.widget.RelativeLayout; -import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; -import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; -import com.android.tv.tuner.data.Cea708Data.CaptionWindow; -import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; -import com.android.tv.tuner.layout.ScaledLayout; -import com.google.android.exoplayer.text.CaptionStyleCompat; -import com.google.android.exoplayer.text.SubtitleView; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Layout which renders a caption window of CEA-708B. It contains a {@link SubtitleView} that takes - * care of displaying the actual cc text. - */ -public class CaptionWindowLayout extends RelativeLayout implements View.OnLayoutChangeListener { - private static final String TAG = "CaptionWindowLayout"; - private static final boolean DEBUG = false; - - private static final float PROPORTION_PEN_SIZE_SMALL = .75f; - private static final float PROPORTION_PEN_SIZE_LARGE = 1.25f; - - // The following values indicates the maximum cell number of a window. - private static final int ANCHOR_RELATIVE_POSITIONING_MAX = 99; - private static final int ANCHOR_VERTICAL_MAX = 74; - private static final int ANCHOR_HORIZONTAL_4_3_MAX = 159; - private static final int ANCHOR_HORIZONTAL_16_9_MAX = 209; - - // The following values indicates a gravity of a window. - private static final int ANCHOR_MODE_DIVIDER = 3; - private static final int ANCHOR_HORIZONTAL_MODE_LEFT = 0; - private static final int ANCHOR_HORIZONTAL_MODE_CENTER = 1; - private static final int ANCHOR_HORIZONTAL_MODE_RIGHT = 2; - private static final int ANCHOR_VERTICAL_MODE_TOP = 0; - private static final int ANCHOR_VERTICAL_MODE_CENTER = 1; - private static final int ANCHOR_VERTICAL_MODE_BOTTOM = 2; - - private static final int US_MAX_COLUMN_COUNT_16_9 = 42; - private static final int US_MAX_COLUMN_COUNT_4_3 = 32; - private static final int KR_MAX_COLUMN_COUNT_16_9 = 52; - private static final int KR_MAX_COLUMN_COUNT_4_3 = 40; - private static final int MAX_ROW_COUNT = 15; - - private static final String KOR_ALPHABET = - new String("\uAC00".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - private static final float WIDE_SCREEN_ASPECT_RATIO_THRESHOLD = 1.6f; - - private CaptionLayout mCaptionLayout; - private CaptionStyleCompat mCaptionStyleCompat; - - // TODO: Replace SubtitleView to {@link com.google.android.exoplayer.text.SubtitleLayout}. - private final SubtitleView mSubtitleView; - private int mRowLimit = 0; - private final SpannableStringBuilder mBuilder = new SpannableStringBuilder(); - private final List mCharacterStyles = new ArrayList<>(); - private int mCaptionWindowId; - private int mCurrentTextRow = -1; - private float mFontScale; - private float mTextSize; - private String mWidestChar; - private int mLastCaptionLayoutWidth; - private int mLastCaptionLayoutHeight; - private int mWindowJustify; - private int mPrintDirection; - - private class SystemWideCaptioningChangeListener extends CaptioningChangeListener { - @Override - public void onUserStyleChanged(CaptionStyle userStyle) { - mCaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(userStyle); - mSubtitleView.setStyle(mCaptionStyleCompat); - updateWidestChar(); - } - - @Override - public void onFontScaleChanged(float fontScale) { - mFontScale = fontScale; - updateTextSize(); - } - } - - public CaptionWindowLayout(Context context) { - this(context, null); - } - - public CaptionWindowLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CaptionWindowLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - // Add a subtitle view to the layout. - mSubtitleView = new SubtitleView(context); - LayoutParams params = - new RelativeLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - addView(mSubtitleView, params); - - // Set the system wide cc preferences to the subtitle view. - CaptioningManager captioningManager = - (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); - mFontScale = captioningManager.getFontScale(); - mCaptionStyleCompat = - CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); - mSubtitleView.setStyle(mCaptionStyleCompat); - mSubtitleView.setText(""); - captioningManager.addCaptioningChangeListener(new SystemWideCaptioningChangeListener()); - updateWidestChar(); - } - - public int getCaptionWindowId() { - return mCaptionWindowId; - } - - public void setCaptionWindowId(int captionWindowId) { - mCaptionWindowId = captionWindowId; - } - - public void clear() { - clearText(); - hide(); - } - - public void show() { - setVisibility(View.VISIBLE); - requestLayout(); - } - - public void hide() { - setVisibility(View.INVISIBLE); - requestLayout(); - } - - public void setPenAttr(CaptionPenAttr penAttr) { - mCharacterStyles.clear(); - if (penAttr.italic) { - mCharacterStyles.add(new StyleSpan(Typeface.ITALIC)); - } - if (penAttr.underline) { - mCharacterStyles.add(new UnderlineSpan()); - } - switch (penAttr.penSize) { - case CaptionPenAttr.PEN_SIZE_SMALL: - mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_SMALL)); - break; - case CaptionPenAttr.PEN_SIZE_LARGE: - mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_LARGE)); - break; - } - switch (penAttr.penOffset) { - case CaptionPenAttr.OFFSET_SUBSCRIPT: - mCharacterStyles.add(new SubscriptSpan()); - break; - case CaptionPenAttr.OFFSET_SUPERSCRIPT: - mCharacterStyles.add(new SuperscriptSpan()); - break; - } - } - - public void setPenColor(CaptionPenColor penColor) { - // TODO: apply pen colors or skip this and use the style of system wide cc style as is. - } - - public void setPenLocation(int row, int column) { - // TODO: change the location of pen when window's justify isn't left. - // According to the CEA708B spec 8.7, setPenLocation means set the pen cursor within - // window's text buffer. When row > mCurrentTextRow, we add "\n" to make the cursor locate - // at row. Adding white space to make cursor locate at column. - if (mWindowJustify == CaptionWindowAttr.JUSTIFY_LEFT) { - if (mCurrentTextRow >= 0) { - for (int r = mCurrentTextRow; r < row; ++r) { - appendText("\n"); - } - if (mCurrentTextRow <= row) { - for (int i = 0; i < column; ++i) { - appendText(" "); - } - } - } - } - mCurrentTextRow = row; - } - - public void setWindowAttr(CaptionWindowAttr windowAttr) { - // TODO: apply window attrs or skip this and use the style of system wide cc style as is. - mWindowJustify = windowAttr.justify; - mPrintDirection = windowAttr.printDirection; - } - - public void sendBuffer(String buffer) { - appendText(buffer); - } - - public void sendControl(char control) { - // TODO: there are a bunch of ASCII-style control codes. - } - - /** - * This method places the window on a given CaptionLayout along with the anchor of the window. - * - *

According to CEA-708B, the anchor id indicates the gravity of the window as the follows. - * For example, A value 7 of a anchor id says that a window is align with its parent bottom and - * is located at the center horizontally of its parent. - * - *

Anchor id and the gravity of a window

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
GRAVITYLEFTCENTER_HORIZONTALRIGHT
TOP012
CENTER_VERTICAL345
BOTTOM678
- * - *

In order to handle the gravity of a window, there are two steps. First, set the size of - * the window. Since the window will be positioned at {@link ScaledLayout}, the size factors are - * determined in a ratio. Second, set the gravity of the window. {@link CaptionWindowLayout} is - * inherited from {@link RelativeLayout}. Hence, we could set the gravity of its child view, - * {@link SubtitleView}. - * - *

The gravity of the window is also related to its size. When it should be pushed to a one - * of the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point should be a - * boundary of the window. When it should be pushed in the horizontal/vertical center of its - * container, the horizontal/vertical center point of the window should be the same as the - * anchor point. - * - * @param captionLayout a given {@link CaptionLayout}, which contains a safe title area - * @param captionWindow a given {@link CaptionWindow}, which stores the construction info of the - * window - */ - public void initWindow(CaptionLayout captionLayout, CaptionWindow captionWindow) { - if (DEBUG) { - Log.d( - TAG, - "initWindow with " - + (captionLayout != null ? captionLayout.getCaptionTrack() : null)); - } - if (mCaptionLayout != captionLayout) { - if (mCaptionLayout != null) { - mCaptionLayout.removeOnLayoutChangeListener(this); - } - mCaptionLayout = captionLayout; - mCaptionLayout.addOnLayoutChangeListener(this); - updateWidestChar(); - } - - // Both anchor vertical and horizontal indicates the position cell number of the window. - float scaleRow = - (float) captionWindow.anchorVertical - / (captionWindow.relativePositioning - ? ANCHOR_RELATIVE_POSITIONING_MAX - : ANCHOR_VERTICAL_MAX); - float scaleCol = - (float) captionWindow.anchorHorizontal - / (captionWindow.relativePositioning - ? ANCHOR_RELATIVE_POSITIONING_MAX - : (isWideAspectRatio() - ? ANCHOR_HORIZONTAL_16_9_MAX - : ANCHOR_HORIZONTAL_4_3_MAX)); - - // The range of scaleRow/Col need to be verified to be in [0, 1]. - // Otherwise a {@link RuntimeException} will be raised in {@link ScaledLayout}. - if (scaleRow < 0 || scaleRow > 1) { - Log.i( - TAG, - "The vertical position of the anchor point should be at the range of 0 and 1" - + " but " - + scaleRow); - scaleRow = Math.max(0, Math.min(scaleRow, 1)); - } - if (scaleCol < 0 || scaleCol > 1) { - Log.i( - TAG, - "The horizontal position of the anchor point should be at the range of 0 and" - + " 1 but " - + scaleCol); - scaleCol = Math.max(0, Math.min(scaleCol, 1)); - } - int gravity = Gravity.CENTER; - int horizontalMode = captionWindow.anchorId % ANCHOR_MODE_DIVIDER; - int verticalMode = captionWindow.anchorId / ANCHOR_MODE_DIVIDER; - float scaleStartRow = 0; - float scaleEndRow = 1; - float scaleStartCol = 0; - float scaleEndCol = 1; - switch (horizontalMode) { - case ANCHOR_HORIZONTAL_MODE_LEFT: - gravity = Gravity.LEFT; - mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL); - scaleStartCol = scaleCol; - break; - case ANCHOR_HORIZONTAL_MODE_CENTER: - float gap = Math.min(1 - scaleCol, scaleCol); - - // Since all TV sets use left text alignment instead of center text alignment - // for this case, we follow the industry convention if possible. - int columnCount = captionWindow.columnCount + 1; - if (isKoreanLanguageTrack()) { - columnCount /= 2; - } - columnCount = Math.min(getScreenColumnCount(), columnCount); - StringBuilder widestTextBuilder = new StringBuilder(); - for (int i = 0; i < columnCount; ++i) { - widestTextBuilder.append(mWidestChar); - } - Paint paint = new Paint(); - paint.setTypeface(mCaptionStyleCompat.typeface); - paint.setTextSize(mTextSize); - float maxWindowWidth = paint.measureText(widestTextBuilder.toString()); - float halfMaxWidthScale = - mCaptionLayout.getWidth() > 0 - ? maxWindowWidth / 2.0f / (mCaptionLayout.getWidth() * 0.8f) - : 0.0f; - if (halfMaxWidthScale > 0f && halfMaxWidthScale < scaleCol) { - // Calculate the expected max window size based on the column count of the - // caption window multiplied by average alphabets char width, then align the - // left side of the window with the left side of the expected max window. - gravity = Gravity.LEFT; - mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL); - scaleStartCol = scaleCol - halfMaxWidthScale; - scaleEndCol = 1.0f; - } else { - // The gap will be the minimum distance value of the distances from both - // horizontal end points to the anchor point. - // If scaleCol <= 0.5, the range of scaleCol is [0, the anchor point * 2]. - // If scaleCol > 0.5, the range of scaleCol is [(1 - the anchor point) * 2, 1]. - // The anchor point is located at the horizontal center of the window in both - // cases. - gravity = Gravity.CENTER_HORIZONTAL; - mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER); - scaleStartCol = scaleCol - gap; - scaleEndCol = scaleCol + gap; - } - break; - case ANCHOR_HORIZONTAL_MODE_RIGHT: - gravity = Gravity.RIGHT; - mSubtitleView.setTextAlignment(Alignment.ALIGN_OPPOSITE); - scaleEndCol = scaleCol; - break; - } - switch (verticalMode) { - case ANCHOR_VERTICAL_MODE_TOP: - gravity |= Gravity.TOP; - scaleStartRow = scaleRow; - break; - case ANCHOR_VERTICAL_MODE_CENTER: - gravity |= Gravity.CENTER_VERTICAL; - - // See the above comment. - float gap = Math.min(1 - scaleRow, scaleRow); - scaleStartRow = scaleRow - gap; - scaleEndRow = scaleRow + gap; - break; - case ANCHOR_VERTICAL_MODE_BOTTOM: - gravity |= Gravity.BOTTOM; - scaleEndRow = scaleRow; - break; - } - mCaptionLayout.addOrUpdateViewToSafeTitleArea( - this, - new ScaledLayout.ScaledLayoutParams( - scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); - setCaptionWindowId(captionWindow.id); - setRowLimit(captionWindow.rowCount); - setGravity(gravity); - setWindowStyle(captionWindow.windowStyle); - if (mWindowJustify == CaptionWindowAttr.JUSTIFY_CENTER) { - mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER); - } - if (captionWindow.visible) { - show(); - } else { - hide(); - } - } - - @Override - public void onLayoutChange( - View v, - int left, - int top, - int right, - int bottom, - int oldLeft, - int oldTop, - int oldRight, - int oldBottom) { - int width = right - left; - int height = bottom - top; - if (width != mLastCaptionLayoutWidth || height != mLastCaptionLayoutHeight) { - mLastCaptionLayoutWidth = width; - mLastCaptionLayoutHeight = height; - updateTextSize(); - } - } - - private boolean isKoreanLanguageTrack() { - return mCaptionLayout != null - && mCaptionLayout.getCaptionTrack() != null - && mCaptionLayout.getCaptionTrack().language != null - && "KOR".compareToIgnoreCase(mCaptionLayout.getCaptionTrack().language) == 0; - } - - private boolean isWideAspectRatio() { - return mCaptionLayout != null - && mCaptionLayout.getCaptionTrack() != null - && mCaptionLayout.getCaptionTrack().wideAspectRatio; - } - - private void updateWidestChar() { - if (isKoreanLanguageTrack()) { - mWidestChar = KOR_ALPHABET; - } else { - Paint paint = new Paint(); - paint.setTypeface(mCaptionStyleCompat.typeface); - Charset latin1 = Charset.forName("ISO-8859-1"); - float widestCharWidth = 0f; - for (int i = 0; i < 256; ++i) { - String ch = new String(new byte[] {(byte) i}, latin1); - float charWidth = paint.measureText(ch); - if (widestCharWidth < charWidth) { - widestCharWidth = charWidth; - mWidestChar = ch; - } - } - } - updateTextSize(); - } - - private void updateTextSize() { - if (mCaptionLayout == null) return; - - // Calculate text size based on the max window size. - StringBuilder widestTextBuilder = new StringBuilder(); - int screenColumnCount = getScreenColumnCount(); - for (int i = 0; i < screenColumnCount; ++i) { - widestTextBuilder.append(mWidestChar); - } - String widestText = widestTextBuilder.toString(); - Paint paint = new Paint(); - paint.setTypeface(mCaptionStyleCompat.typeface); - float startFontSize = 0f; - float endFontSize = 255f; - Rect boundRect = new Rect(); - while (startFontSize < endFontSize) { - float testTextSize = (startFontSize + endFontSize) / 2f; - paint.setTextSize(testTextSize); - float width = paint.measureText(widestText); - paint.getTextBounds(widestText, 0, widestText.length(), boundRect); - float height = boundRect.height() + width - boundRect.width(); - // According to CEA-708B Section 9.13, the height of standard font size shouldn't taller - // than 1/15 of the height of the safe-title area, and the width shouldn't wider than - // 1/{@code getScreenColumnCount()} of the width of the safe-title area. - if (mCaptionLayout.getWidth() * 0.8f > width - && mCaptionLayout.getHeight() * 0.8f / MAX_ROW_COUNT > height) { - startFontSize = testTextSize + 0.01f; - } else { - endFontSize = testTextSize - 0.01f; - } - } - mTextSize = endFontSize * mFontScale; - paint.setTextSize(mTextSize); - float whiteSpaceWidth = paint.measureText(" "); - mSubtitleView.setWhiteSpaceWidth(whiteSpaceWidth); - mSubtitleView.setTextSize(mTextSize); - } - - private int getScreenColumnCount() { - float screenAspectRatio = (float) mCaptionLayout.getWidth() / mCaptionLayout.getHeight(); - boolean isWideAspectRationScreen = screenAspectRatio > WIDE_SCREEN_ASPECT_RATIO_THRESHOLD; - if (isKoreanLanguageTrack()) { - // Each korean character consumes two slots. - if (isWideAspectRationScreen || isWideAspectRatio()) { - return KR_MAX_COLUMN_COUNT_16_9 / 2; - } else { - return KR_MAX_COLUMN_COUNT_4_3 / 2; - } - } else { - if (isWideAspectRationScreen || isWideAspectRatio()) { - return US_MAX_COLUMN_COUNT_16_9; - } else { - return US_MAX_COLUMN_COUNT_4_3; - } - } - } - - public void removeFromCaptionView() { - if (mCaptionLayout != null) { - mCaptionLayout.removeViewFromSafeTitleArea(this); - mCaptionLayout.removeOnLayoutChangeListener(this); - mCaptionLayout = null; - } - } - - public void setText(String text) { - updateText(text, false); - } - - public void appendText(String text) { - updateText(text, true); - } - - public void clearText() { - mBuilder.clear(); - mSubtitleView.setText(""); - } - - private void updateText(String text, boolean appended) { - if (!appended) { - mBuilder.clear(); - } - if (text != null && text.length() > 0) { - int length = mBuilder.length(); - mBuilder.append(text); - for (CharacterStyle characterStyle : mCharacterStyles) { - mBuilder.setSpan( - characterStyle, - length, - mBuilder.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - String[] lines = TextUtils.split(mBuilder.toString(), "\n"); - - // Truncate text not to exceed the row limit. - // Plus one here since the range of the rows is [0, mRowLimit]. - int startRow = Math.max(0, lines.length - (mRowLimit + 1)); - String truncatedText = - TextUtils.join("\n", Arrays.copyOfRange(lines, startRow, lines.length)); - mBuilder.delete(0, mBuilder.length() - truncatedText.length()); - mCurrentTextRow = lines.length - startRow - 1; - - // Trim the buffer first then set text to {@link SubtitleView}. - int start = 0, last = mBuilder.length() - 1; - int end = last; - while ((start <= end) && (mBuilder.charAt(start) <= ' ')) { - ++start; - } - while (start - 1 >= 0 && start <= end && mBuilder.charAt(start - 1) != '\n') { - --start; - } - while ((end >= start) && (mBuilder.charAt(end) <= ' ')) { - --end; - } - if (start == 0 && end == last) { - mSubtitleView.setPrefixSpaces(getPrefixSpaces(mBuilder)); - mSubtitleView.setText(mBuilder); - } else { - SpannableStringBuilder trim = new SpannableStringBuilder(); - trim.append(mBuilder); - if (end < last) { - trim.delete(end + 1, last + 1); - } - if (start > 0) { - trim.delete(0, start); - } - mSubtitleView.setPrefixSpaces(getPrefixSpaces(trim)); - mSubtitleView.setText(trim); - } - } - - private static ArrayList getPrefixSpaces(SpannableStringBuilder builder) { - ArrayList prefixSpaces = new ArrayList<>(); - String[] lines = TextUtils.split(builder.toString(), "\n"); - for (String line : lines) { - int start = 0; - while (start < line.length() && line.charAt(start) <= ' ') { - start++; - } - prefixSpaces.add(start); - } - return prefixSpaces; - } - - public void setRowLimit(int rowLimit) { - if (rowLimit < 0) { - throw new IllegalArgumentException("A rowLimit should have a positive number"); - } - mRowLimit = rowLimit; - } - - private void setWindowStyle(int windowStyle) { - // TODO: Set other attributes of window style. Like fill opacity and fill color. - switch (windowStyle) { - case 2: - mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - case 3: - mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - case 4: - mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - case 5: - mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - case 6: - mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - case 7: - mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT; - mPrintDirection = CaptionWindowAttr.PRINT_TOP_TO_BOTTOM; - break; - default: - if (windowStyle != 0 && windowStyle != 1) { - Log.e(TAG, "Error predefined window style:" + windowStyle); - } - mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT; - mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT; - break; - } - } -} diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java deleted file mode 100644 index 4e080276..00000000 --- a/src/com/android/tv/tuner/cc/Cea708Parser.java +++ /dev/null @@ -1,922 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.cc; - -import android.os.SystemClock; -import android.support.annotation.IntDef; -import android.util.Log; -import android.util.SparseIntArray; -import com.android.tv.tuner.data.Cea708Data; -import com.android.tv.tuner.data.Cea708Data.CaptionColor; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr; -import com.android.tv.tuner.data.Cea708Data.CaptionPenColor; -import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation; -import com.android.tv.tuner.data.Cea708Data.CaptionWindow; -import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr; -import com.android.tv.tuner.data.Cea708Data.CcPacket; -import com.android.tv.tuner.util.ByteArrayBuffer; -import java.io.UnsupportedEncodingException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.TreeSet; - -/** - * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV. - * - *

ATSC DTV closed caption data are carried on picture user data of video streams. This class - * starts to parse from picture user data payload, so extraction process of user_data from video - * streams is up to outside of this code. - * - *

There are 4 steps to decode user_data to provide closed caption services. - * - *

Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method)

- * - *

First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a - * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data - * packets must be reassembled in the frame display order, CcPackets are reordered. - * - *

Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method)

- * - *

Each cc_data packet has a one byte for declaring a type of itself and data validity, and the - * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet. - * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet - * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has - * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled. - * - *

Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method)

- * - *

A DTVCC packet consists of multiple service blocks. Each service block represents a caption - * track and has a service number, which ranges from 1 to 63, that denotes caption track identity. - * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}. Otherwise, - * just skip the other service blocks. - * - *

Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX}, and - * {@link #parseExt1} methods)

- * - *

Service block data is actual caption stream. it looks similar to telnet. It uses most parts of - * ASCII table and consists of specially defined commands and some ASCII control codes which work in - * a behavior slightly different from their original purpose. ASCII control codes and caption - * commands are explicit instructions that control the state of a closed caption service and the - * other ASCII and text codes are implicit instructions that send their characters to buffer. - * - *

There are 4 main code groups and 4 extended code groups. Both the range of code groups are the - * same as the range of a byte. - * - *

4 main code groups: C0, C1, G0, G1
- * 4 extended code groups: C2, C3, G2, G3 - * - *

Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group - * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while - * {@link #parseExt1} method maps on the extended code groups. - * - *

The main code groups: - * - *

    - *
  • C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA - * standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc, - * even for the alphanumeric characters instead of ASCII characters. - *
  • C1 - contains the caption commands. There are 3 categories of a caption command. - *
      - *
    • Window commands: The window commands control a caption window which is addressable - * area being with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX) - *
    • Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL) - *
    • Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, - * RST) - *
    - *
  • G0 - same as printable ASCII character set except music note character. - *
  • G1 - same as ISO 8859-1 Latin 1 character set. - *
- * - *

Most of the extended code groups are being skipped. - */ -public class Cea708Parser { - private static final String TAG = "Cea708Parser"; - private static final boolean DEBUG = false; - - // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps. - private static final int MAX_ALLOCATED_SIZE = 9600 / 8; - private static final String MUSIC_NOTE_CHAR = - new String("\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - - // The following values are denoting the type of closed caption data. - // See CEA-708B section 4.4.1. - private static final int CC_TYPE_DTVCC_PACKET_START = 3; - private static final int CC_TYPE_DTVCC_PACKET_DATA = 2; - - // The following values are defined in CEA-708B Figure 4 and 6. - private static final int DTVCC_MAX_PACKET_SIZE = 64; - private static final int DTVCC_PACKET_SIZE_SCALE_FACTOR = 2; - private static final int DTVCC_EXTENDED_SERVICE_NUMBER_POINT = 7; - - // The following values are for seeking closed caption tracks. - private static final int DISCOVERY_PERIOD_MS = 10000; // 10 sec - private static final int DISCOVERY_NUM_BYTES_THRESHOLD = 10; // 10 bytes - private static final int DISCOVERY_CC_SERVICE_NUMBER_START = 1; // CC1 - private static final int DISCOVERY_CC_SERVICE_NUMBER_END = 4; // CC4 - - private final ByteArrayBuffer mDtvCcPacket = new ByteArrayBuffer(MAX_ALLOCATED_SIZE); - private final TreeSet mCcPackets = new TreeSet<>(); - private final StringBuffer mBuffer = new StringBuffer(); - private final SparseIntArray mDiscoveredNumBytes = new SparseIntArray(); // per service number - private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime(); - private int mCommand = 0; - private int mListenServiceNumber = 0; - private boolean mDtvCcPacking = false; - private boolean mFirstServiceNumberDiscovered; - - // Assign a dummy listener in order to avoid null checks. - private OnCea708ParserListener mListener = - new OnCea708ParserListener() { - @Override - public void emitEvent(CaptionEvent event) { - // do nothing - } - - @Override - public void discoverServiceNumber(int serviceNumber) { - // do nothing - } - }; - - /** - * {@link Cea708Parser} emits caption event of three different types. {@link - * OnCea708ParserListener#emitEvent} is invoked with the parameter {@link CaptionEvent} to pass - * all the results to an observer of the decoding process. - * - *

{@link CaptionEvent#type} determines the type of the result and {@link CaptionEvent#obj} - * contains the output value of a caption event. The observer must do the casting to the - * corresponding type. - * - *

    - *
  • {@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer. {@code - * obj} must be of {@link String}. - *
  • {@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a - * observer. {@code obj} must be of {@link Character}. - *
  • {@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer. {@code - * obj} must be {@code NULL}. - *
- */ - @IntDef({ - CAPTION_EMIT_TYPE_BUFFER, - CAPTION_EMIT_TYPE_CONTROL, - CAPTION_EMIT_TYPE_COMMAND_CWX, - CAPTION_EMIT_TYPE_COMMAND_CLW, - CAPTION_EMIT_TYPE_COMMAND_DSW, - CAPTION_EMIT_TYPE_COMMAND_HDW, - CAPTION_EMIT_TYPE_COMMAND_TGW, - CAPTION_EMIT_TYPE_COMMAND_DLW, - CAPTION_EMIT_TYPE_COMMAND_DLY, - CAPTION_EMIT_TYPE_COMMAND_DLC, - CAPTION_EMIT_TYPE_COMMAND_RST, - CAPTION_EMIT_TYPE_COMMAND_SPA, - CAPTION_EMIT_TYPE_COMMAND_SPC, - CAPTION_EMIT_TYPE_COMMAND_SPL, - CAPTION_EMIT_TYPE_COMMAND_SWA, - CAPTION_EMIT_TYPE_COMMAND_DFX - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CaptionEmitType {} - - public static final int CAPTION_EMIT_TYPE_BUFFER = 1; - public static final int CAPTION_EMIT_TYPE_CONTROL = 2; - public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3; - public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4; - public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5; - public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6; - public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7; - public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8; - public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9; - public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10; - public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11; - public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12; - public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13; - public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14; - public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15; - public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16; - - public interface OnCea708ParserListener { - void emitEvent(CaptionEvent event); - - void discoverServiceNumber(int serviceNumber); - } - - public void setListener(OnCea708ParserListener listener) { - if (listener != null) { - mListener = listener; - } - } - - public void clear() { - mDtvCcPacket.clear(); - mCcPackets.clear(); - mBuffer.setLength(0); - mDiscoveredNumBytes.clear(); - mCommand = 0; - mDtvCcPacking = false; - } - - public void setListenServiceNumber(int serviceNumber) { - mListenServiceNumber = serviceNumber; - } - - private void emitCaptionEvent(CaptionEvent captionEvent) { - // Emit the existing string buffer before a new event is arrived. - emitCaptionBuffer(); - mListener.emitEvent(captionEvent); - } - - private void emitCaptionBuffer() { - if (mBuffer.length() > 0) { - mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuffer.toString())); - mBuffer.setLength(0); - } - } - - // Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method) - public void parseClosedCaption(ByteBuffer data, long framePtsUs) { - int ccCount = data.limit() / 3; - byte[] ccBytes = new byte[3 * ccCount]; - for (int i = 0; i < 3 * ccCount; i++) { - ccBytes[i] = data.get(i); - } - CcPacket ccPacket = new CcPacket(ccBytes, ccCount, framePtsUs); - mCcPackets.add(ccPacket); - } - - public boolean processClosedCaptions(long framePtsUs) { - // To get the sorted cc packets that have lower frame pts than current frame pts, - // the following offset divides off the lower side of the packets. - CcPacket offsetPacket = new CcPacket(new byte[0], 0, framePtsUs); - offsetPacket = mCcPackets.lower(offsetPacket); - boolean processed = false; - if (offsetPacket != null) { - while (!mCcPackets.isEmpty() && offsetPacket.compareTo(mCcPackets.first()) >= 0) { - CcPacket packet = mCcPackets.pollFirst(); - parseCcPacket(packet); - processed = true; - } - } - return processed; - } - - // Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method) - private void parseCcPacket(CcPacket ccPacket) { - // For the details of cc packet, see ATSC TSG-676 - Table A8. - byte[] bytes = ccPacket.bytes; - int pos = 0; - for (int i = 0; i < ccPacket.ccCount; ++i) { - boolean ccValid = (bytes[pos] & 0x04) != 0; - int ccType = bytes[pos] & 0x03; - - // The dtvcc should be considered complete: - // - if either ccValid is set and ccType is 3 - // - or ccValid is clear and ccType is 2 or 3. - if (ccValid) { - if (ccType == CC_TYPE_DTVCC_PACKET_START) { - if (mDtvCcPacking) { - parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length()); - mDtvCcPacket.clear(); - } - mDtvCcPacking = true; - mDtvCcPacket.append(bytes[pos + 1]); - mDtvCcPacket.append(bytes[pos + 2]); - } else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) { - mDtvCcPacket.append(bytes[pos + 1]); - mDtvCcPacket.append(bytes[pos + 2]); - } - } else { - if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA) - && mDtvCcPacking) { - mDtvCcPacking = false; - parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length()); - mDtvCcPacket.clear(); - } - } - pos += 3; - } - } - - // Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method) - private void parseDtvCcPacket(byte[] data, int limit) { - // For the details of DTVCC packet, see CEA-708B Figure 4. - int pos = 0; - int packetSize = data[pos] & 0x3f; - if (packetSize == 0) { - packetSize = DTVCC_MAX_PACKET_SIZE; - } - int calculatedPacketSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR; - if (limit != calculatedPacketSize) { - return; - } - ++pos; - int len = pos + calculatedPacketSize; - while (pos < len) { - // For the details of Service Block, see CEA-708B Figure 5 and 6. - int serviceNumber = (data[pos] & 0xe0) >> 5; - int blockSize = data[pos] & 0x1f; - ++pos; - if (serviceNumber == DTVCC_EXTENDED_SERVICE_NUMBER_POINT) { - serviceNumber = (data[pos] & 0x3f); - ++pos; - - // Return if invalid service number - if (serviceNumber < DTVCC_EXTENDED_SERVICE_NUMBER_POINT) { - return; - } - } - if (pos + blockSize > limit) { - return; - } - - // Send parsed service number in order to find unveiled closed caption tracks which - // are not specified in any ATSC PSIP sections. Since some broadcasts send empty closed - // caption tracks, it detects the proper closed caption tracks by counting the number of - // bytes sent with the same service number during a discovery period. - // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different - // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported. - if (blockSize > 0 - && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START - && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) { - mDiscoveredNumBytes.put( - serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0)); - } - if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime() - || !mFirstServiceNumberDiscovered) { - for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) { - int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i); - if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) { - int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i); - mListener.discoverServiceNumber(discoveredServiceNumber); - mFirstServiceNumberDiscovered = true; - } - } - mDiscoveredNumBytes.clear(); - mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime(); - } - - // Skip current service block if either there is no block data or the service number - // is not same as listening service number. - if (blockSize == 0 || serviceNumber != mListenServiceNumber) { - pos += blockSize; - continue; - } - - // From this point, starts to read DTVCC coding layer. - // First, identify code groups, which is defined in CEA-708B Section 7.1. - int blockLimit = pos + blockSize; - while (pos < blockLimit) { - pos = parseServiceBlockData(data, pos); - } - - // Emit the buffer after reading codes. - emitCaptionBuffer(); - pos = blockLimit; - } - } - - // Step 4. Main code groups - private int parseServiceBlockData(byte[] data, int pos) { - // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6. - mCommand = data[pos] & 0xff; - ++pos; - if (mCommand == Cea708Data.CODE_C0_EXT1) { - pos = parseExt1(data, pos); - } else if (mCommand >= Cea708Data.CODE_C0_RANGE_START - && mCommand <= Cea708Data.CODE_C0_RANGE_END) { - pos = parseC0(data, pos); - } else if (mCommand >= Cea708Data.CODE_C1_RANGE_START - && mCommand <= Cea708Data.CODE_C1_RANGE_END) { - pos = parseC1(data, pos); - } else if (mCommand >= Cea708Data.CODE_G0_RANGE_START - && mCommand <= Cea708Data.CODE_G0_RANGE_END) { - pos = parseG0(data, pos); - } else if (mCommand >= Cea708Data.CODE_G1_RANGE_START - && mCommand <= Cea708Data.CODE_G1_RANGE_END) { - pos = parseG1(data, pos); - } - return pos; - } - - private int parseC0(byte[] data, int pos) { - // For the details of C0 code group, see CEA-708B Section 7.4.1. - // CL Group: C0 Subset of ASCII Control codes - if (mCommand >= Cea708Data.CODE_C0_SKIP2_RANGE_START - && mCommand <= Cea708Data.CODE_C0_SKIP2_RANGE_END) { - if (mCommand == Cea708Data.CODE_C0_P16) { - // TODO : P16 escapes next two bytes for the large character maps.(no standard rule) - // TODO : For korea broadcasting, express whole letters by using this. - try { - if (data[pos] == 0) { - mBuffer.append((char) data[pos + 1]); - } else { - String value = new String(Arrays.copyOfRange(data, pos, pos + 2), "EUC-KR"); - mBuffer.append(value); - } - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "P16 Code - Could not find supported encoding", e); - } - } - pos += 2; - } else if (mCommand >= Cea708Data.CODE_C0_SKIP1_RANGE_START - && mCommand <= Cea708Data.CODE_C0_SKIP1_RANGE_END) { - ++pos; - } else { - // NUL, BS, FF, CR interpreted as they are in ASCII control codes. - // HCR moves the pen location to th beginning of the current line and deletes contents. - // FF clears the screen and moves the pen location to (0,0). - // ETX is the NULL command which is used to flush text to the current window when no - // other command is pending. - switch (mCommand) { - case Cea708Data.CODE_C0_NUL: - break; - case Cea708Data.CODE_C0_ETX: - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand)); - break; - case Cea708Data.CODE_C0_BS: - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand)); - break; - case Cea708Data.CODE_C0_FF: - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand)); - break; - case Cea708Data.CODE_C0_CR: - mBuffer.append('\n'); - break; - case Cea708Data.CODE_C0_HCR: - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand)); - break; - default: - break; - } - } - return pos; - } - - private int parseC1(byte[] data, int pos) { - // For the details of C1 code group, see CEA-708B Section 8.10. - // CR Group: C1 Caption Control Codes - switch (mCommand) { - case Cea708Data.CODE_C1_CW0: - case Cea708Data.CODE_C1_CW1: - case Cea708Data.CODE_C1_CW2: - case Cea708Data.CODE_C1_CW3: - case Cea708Data.CODE_C1_CW4: - case Cea708Data.CODE_C1_CW5: - case Cea708Data.CODE_C1_CW6: - case Cea708Data.CODE_C1_CW7: - { - // SetCurrentWindow0-7 - int windowId = mCommand - Cea708Data.CODE_C1_CW0; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId)); - if (DEBUG) { - Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId)); - } - break; - } - - case Cea708Data.CODE_C1_CLW: - { - // ClearWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap)); - if (DEBUG) { - Log.d( - TAG, - String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap)); - } - break; - } - - case Cea708Data.CODE_C1_DSW: - { - // DisplayWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap)); - if (DEBUG) { - Log.d( - TAG, - String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap)); - } - break; - } - - case Cea708Data.CODE_C1_HDW: - { - // HideWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap)); - if (DEBUG) { - Log.d( - TAG, - String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap)); - } - break; - } - - case Cea708Data.CODE_C1_TGW: - { - // ToggleWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap)); - if (DEBUG) { - Log.d( - TAG, - String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap)); - } - break; - } - - case Cea708Data.CODE_C1_DLW: - { - // DeleteWindows - int windowBitmap = data[pos] & 0xff; - ++pos; - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap)); - if (DEBUG) { - Log.d( - TAG, - String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap)); - } - break; - } - - case Cea708Data.CODE_C1_DLY: - { - // Delay - int tenthsOfSeconds = data[pos] & 0xff; - ++pos; - emitCaptionEvent( - new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds)); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand DLY %d tenths of seconds", - tenthsOfSeconds)); - } - break; - } - case Cea708Data.CODE_C1_DLC: - { - // DelayCancel - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null)); - if (DEBUG) { - Log.d(TAG, "CaptionCommand DLC"); - } - break; - } - - case Cea708Data.CODE_C1_RST: - { - // Reset - emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null)); - if (DEBUG) { - Log.d(TAG, "CaptionCommand RST"); - } - break; - } - - case Cea708Data.CODE_C1_SPA: - { - // SetPenAttributes - int textTag = (data[pos] & 0xf0) >> 4; - int penSize = data[pos] & 0x03; - int penOffset = (data[pos] & 0x0c) >> 2; - boolean italic = (data[pos + 1] & 0x80) != 0; - boolean underline = (data[pos + 1] & 0x40) != 0; - int edgeType = (data[pos + 1] & 0x38) >> 3; - int fontTag = data[pos + 1] & 0x7; - pos += 2; - emitCaptionEvent( - new CaptionEvent( - CAPTION_EMIT_TYPE_COMMAND_SPA, - new CaptionPenAttr( - penSize, penOffset, textTag, fontTag, edgeType, - underline, italic))); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, " - + "fontTag: %d, edgeType: %d, underline: %s, italic: %s", - penSize, penOffset, textTag, fontTag, edgeType, underline, - italic)); - } - break; - } - - case Cea708Data.CODE_C1_SPC: - { - // SetPenColor - int opacity = (data[pos] & 0xc0) >> 6; - int red = (data[pos] & 0x30) >> 4; - int green = (data[pos] & 0x0c) >> 2; - int blue = data[pos] & 0x03; - CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue); - ++pos; - opacity = (data[pos] & 0xc0) >> 6; - red = (data[pos] & 0x30) >> 4; - green = (data[pos] & 0x0c) >> 2; - blue = data[pos] & 0x03; - CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue); - ++pos; - red = (data[pos] & 0x30) >> 4; - green = (data[pos] & 0x0c) >> 2; - blue = data[pos] & 0x03; - CaptionColor edgeColor = - new CaptionColor(CaptionColor.OPACITY_SOLID, red, green, blue); - ++pos; - emitCaptionEvent( - new CaptionEvent( - CAPTION_EMIT_TYPE_COMMAND_SPC, - new CaptionPenColor( - foregroundColor, backgroundColor, edgeColor))); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s", - foregroundColor, backgroundColor, edgeColor)); - } - break; - } - - case Cea708Data.CODE_C1_SPL: - { - // SetPenLocation - // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats - int row = data[pos] & 0x0f; - int column = data[pos + 1] & 0x3f; - pos += 2; - emitCaptionEvent( - new CaptionEvent( - CAPTION_EMIT_TYPE_COMMAND_SPL, - new CaptionPenLocation(row, column))); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand SPL row: %d, column: %d", row, column)); - } - break; - } - - case Cea708Data.CODE_C1_SWA: - { - // SetWindowAttributes - int opacity = (data[pos] & 0xc0) >> 6; - int red = (data[pos] & 0x30) >> 4; - int green = (data[pos] & 0x0c) >> 2; - int blue = data[pos] & 0x03; - CaptionColor fillColor = new CaptionColor(opacity, red, green, blue); - int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5; - red = (data[pos + 1] & 0x30) >> 4; - green = (data[pos + 1] & 0x0c) >> 2; - blue = data[pos + 1] & 0x03; - CaptionColor borderColor = - new CaptionColor(CaptionColor.OPACITY_SOLID, red, green, blue); - boolean wordWrap = (data[pos + 2] & 0x40) != 0; - int printDirection = (data[pos + 2] & 0x30) >> 4; - int scrollDirection = (data[pos + 2] & 0x0c) >> 2; - int justify = (data[pos + 2] & 0x03); - int effectSpeed = (data[pos + 3] & 0xf0) >> 4; - int effectDirection = (data[pos + 3] & 0x0c) >> 2; - int displayEffect = data[pos + 3] & 0x3; - pos += 4; - emitCaptionEvent( - new CaptionEvent( - CAPTION_EMIT_TYPE_COMMAND_SWA, - new CaptionWindowAttr( - fillColor, - borderColor, - borderType, - wordWrap, - printDirection, - scrollDirection, - justify, - effectDirection, - effectSpeed, - displayEffect))); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d" - + "wordWrap: %s, printDirection: %d, scrollDirection: %d, " - + "justify: %s, effectDirection: %d, effectSpeed: %d, " - + "displayEffect: %d", - fillColor, - borderColor, - borderType, - wordWrap, - printDirection, - scrollDirection, - justify, - effectDirection, - effectSpeed, - displayEffect)); - } - break; - } - - case Cea708Data.CODE_C1_DF0: - case Cea708Data.CODE_C1_DF1: - case Cea708Data.CODE_C1_DF2: - case Cea708Data.CODE_C1_DF3: - case Cea708Data.CODE_C1_DF4: - case Cea708Data.CODE_C1_DF5: - case Cea708Data.CODE_C1_DF6: - case Cea708Data.CODE_C1_DF7: - { - // DefineWindow0-7 - int windowId = mCommand - Cea708Data.CODE_C1_DF0; - boolean visible = (data[pos] & 0x20) != 0; - boolean rowLock = (data[pos] & 0x10) != 0; - boolean columnLock = (data[pos] & 0x08) != 0; - int priority = data[pos] & 0x07; - boolean relativePositioning = (data[pos + 1] & 0x80) != 0; - int anchorVertical = data[pos + 1] & 0x7f; - int anchorHorizontal = data[pos + 2] & 0xff; - int anchorId = (data[pos + 3] & 0xf0) >> 4; - int rowCount = data[pos + 3] & 0x0f; - int columnCount = data[pos + 4] & 0x3f; - int windowStyle = (data[pos + 5] & 0x38) >> 3; - int penStyle = data[pos + 5] & 0x07; - pos += 6; - emitCaptionEvent( - new CaptionEvent( - CAPTION_EMIT_TYPE_COMMAND_DFX, - new CaptionWindow( - windowId, - visible, - rowLock, - columnLock, - priority, - relativePositioning, - anchorVertical, - anchorHorizontal, - anchorId, - rowCount, - columnCount, - penStyle, - windowStyle))); - if (DEBUG) { - Log.d( - TAG, - String.format( - "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, " - + "rowLock: %s, visible: %s, anchorVertical: %d, " - + "relativePositioning: %s, anchorHorizontal: %d, " - + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, " - + "windowStyle: %d", - windowId, - priority, - columnLock, - rowLock, - visible, - anchorVertical, - relativePositioning, - anchorHorizontal, - rowCount, - anchorId, - columnCount, - penStyle, - windowStyle)); - } - break; - } - - default: - break; - } - return pos; - } - - private int parseG0(byte[] data, int pos) { - // For the details of G0 code group, see CEA-708B Section 7.4.3. - // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII) - if (mCommand == Cea708Data.CODE_G0_MUSICNOTE) { - // Music note. - mBuffer.append(MUSIC_NOTE_CHAR); - } else { - // Put ASCII code into buffer. - mBuffer.append((char) mCommand); - } - return pos; - } - - private int parseG1(byte[] data, int pos) { - // For the details of G0 code group, see CEA-708B Section 7.4.4. - // GR Group: G1 ISO 8859-1 Latin 1 Characters - // Put ASCII Extended character set into buffer. - mBuffer.append((char) mCommand); - return pos; - } - - // Step 4. Extended code groups - private int parseExt1(byte[] data, int pos) { - // For the details of EXT1 code group, see CEA-708B Section 7.2. - mCommand = data[pos] & 0xff; - ++pos; - if (mCommand >= Cea708Data.CODE_C2_RANGE_START - && mCommand <= Cea708Data.CODE_C2_RANGE_END) { - pos = parseC2(data, pos); - } else if (mCommand >= Cea708Data.CODE_C3_RANGE_START - && mCommand <= Cea708Data.CODE_C3_RANGE_END) { - pos = parseC3(data, pos); - } else if (mCommand >= Cea708Data.CODE_G2_RANGE_START - && mCommand <= Cea708Data.CODE_G2_RANGE_END) { - pos = parseG2(data, pos); - } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START - && mCommand <= Cea708Data.CODE_G3_RANGE_END) { - pos = parseG3(data, pos); - } - return pos; - } - - private int parseC2(byte[] data, int pos) { - // For the details of C2 code group, see CEA-708B Section 7.4.7. - // Extended Miscellaneous Control Codes - // C2 Table : No commands as of CEA-708B. A decoder must skip. - if (mCommand >= Cea708Data.CODE_C2_SKIP0_RANGE_START - && mCommand <= Cea708Data.CODE_C2_SKIP0_RANGE_END) { - // Do nothing. - } else if (mCommand >= Cea708Data.CODE_C2_SKIP1_RANGE_START - && mCommand <= Cea708Data.CODE_C2_SKIP1_RANGE_END) { - ++pos; - } else if (mCommand >= Cea708Data.CODE_C2_SKIP2_RANGE_START - && mCommand <= Cea708Data.CODE_C2_SKIP2_RANGE_END) { - pos += 2; - } else if (mCommand >= Cea708Data.CODE_C2_SKIP3_RANGE_START - && mCommand <= Cea708Data.CODE_C2_SKIP3_RANGE_END) { - pos += 3; - } - return pos; - } - - private int parseC3(byte[] data, int pos) { - // For the details of C3 code group, see CEA-708B Section 7.4.8. - // Extended Control Code Set 2 - // C3 Table : No commands as of CEA-708B. A decoder must skip. - if (mCommand >= Cea708Data.CODE_C3_SKIP4_RANGE_START - && mCommand <= Cea708Data.CODE_C3_SKIP4_RANGE_END) { - pos += 4; - } else if (mCommand >= Cea708Data.CODE_C3_SKIP5_RANGE_START - && mCommand <= Cea708Data.CODE_C3_SKIP5_RANGE_END) { - pos += 5; - } - return pos; - } - - private int parseG2(byte[] data, int pos) { - // For the details of C3 code group, see CEA-708B Section 7.4.5. - // Extended Control Code Set 1(G2 Table) - switch (mCommand) { - case Cea708Data.CODE_G2_TSP: - // TODO : TSP is the Transparent space - break; - case Cea708Data.CODE_G2_NBTSP: - // TODO : NBTSP is Non-Breaking Transparent Space. - break; - case Cea708Data.CODE_G2_BLK: - // TODO : BLK indicates a solid block which fills the entire character block - // TODO : with a solid foreground color. - break; - default: - break; - } - return pos; - } - - private int parseG3(byte[] data, int pos) { - // For the details of C3 code group, see CEA-708B Section 7.4.6. - // Future characters and icons(G3 Table) - if (mCommand == Cea708Data.CODE_G3_CC) { - // TODO : [CC] icon with square corners - } - - // Do nothing - return pos; - } -} diff --git a/src/com/android/tv/tuner/data/Cea708Data.java b/src/com/android/tv/tuner/data/Cea708Data.java deleted file mode 100644 index 73a90181..00000000 --- a/src/com/android/tv/tuner/data/Cea708Data.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.data; - -import android.graphics.Color; -import android.support.annotation.NonNull; -import com.android.tv.tuner.cc.Cea708Parser; - -/** Collection of CEA-708 structures. */ -public class Cea708Data { - - private Cea708Data() {} - - // According to CEA-708B, the range of valid service number is between 1 and 63. - public static final int EMPTY_SERVICE_NUMBER = 0; - - // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6. - public static final int CODE_C0_RANGE_START = 0x00; - public static final int CODE_C0_RANGE_END = 0x1f; - public static final int CODE_C1_RANGE_START = 0x80; - public static final int CODE_C1_RANGE_END = 0x9f; - public static final int CODE_G0_RANGE_START = 0x20; - public static final int CODE_G0_RANGE_END = 0x7f; - public static final int CODE_G1_RANGE_START = 0xa0; - public static final int CODE_G1_RANGE_END = 0xff; - public static final int CODE_C2_RANGE_START = 0x00; - public static final int CODE_C2_RANGE_END = 0x1f; - public static final int CODE_C3_RANGE_START = 0x80; - public static final int CODE_C3_RANGE_END = 0x9f; - public static final int CODE_G2_RANGE_START = 0x20; - public static final int CODE_G2_RANGE_END = 0x7f; - public static final int CODE_G3_RANGE_START = 0xa0; - public static final int CODE_G3_RANGE_END = 0xff; - - // The following ranges are defined in CEA-708B Section 7.4.1. - public static final int CODE_C0_SKIP2_RANGE_START = 0x18; - public static final int CODE_C0_SKIP2_RANGE_END = 0x1f; - public static final int CODE_C0_SKIP1_RANGE_START = 0x10; - public static final int CODE_C0_SKIP1_RANGE_END = 0x17; - - // The following ranges are defined in CEA-708B Section 7.4.7. - public static final int CODE_C2_SKIP0_RANGE_START = 0x00; - public static final int CODE_C2_SKIP0_RANGE_END = 0x07; - public static final int CODE_C2_SKIP1_RANGE_START = 0x08; - public static final int CODE_C2_SKIP1_RANGE_END = 0x0f; - public static final int CODE_C2_SKIP2_RANGE_START = 0x10; - public static final int CODE_C2_SKIP2_RANGE_END = 0x17; - public static final int CODE_C2_SKIP3_RANGE_START = 0x18; - public static final int CODE_C2_SKIP3_RANGE_END = 0x1f; - - // The following ranges are defined in CEA-708B Section 7.4.8. - public static final int CODE_C3_SKIP4_RANGE_START = 0x80; - public static final int CODE_C3_SKIP4_RANGE_END = 0x87; - public static final int CODE_C3_SKIP5_RANGE_START = 0x88; - public static final int CODE_C3_SKIP5_RANGE_END = 0x8f; - - // The following values are the special characters of CEA-708 spec. - public static final int CODE_C0_NUL = 0x00; - public static final int CODE_C0_ETX = 0x03; - public static final int CODE_C0_BS = 0x08; - public static final int CODE_C0_FF = 0x0c; - public static final int CODE_C0_CR = 0x0d; - public static final int CODE_C0_HCR = 0x0e; - public static final int CODE_C0_EXT1 = 0x10; - public static final int CODE_C0_P16 = 0x18; - public static final int CODE_G0_MUSICNOTE = 0x7f; - public static final int CODE_G2_TSP = 0x20; - public static final int CODE_G2_NBTSP = 0x21; - public static final int CODE_G2_BLK = 0x30; - public static final int CODE_G3_CC = 0xa0; - - // The following values are the command bits of CEA-708 spec. - public static final int CODE_C1_CW0 = 0x80; - public static final int CODE_C1_CW1 = 0x81; - public static final int CODE_C1_CW2 = 0x82; - public static final int CODE_C1_CW3 = 0x83; - public static final int CODE_C1_CW4 = 0x84; - public static final int CODE_C1_CW5 = 0x85; - public static final int CODE_C1_CW6 = 0x86; - public static final int CODE_C1_CW7 = 0x87; - public static final int CODE_C1_CLW = 0x88; - public static final int CODE_C1_DSW = 0x89; - public static final int CODE_C1_HDW = 0x8a; - public static final int CODE_C1_TGW = 0x8b; - public static final int CODE_C1_DLW = 0x8c; - public static final int CODE_C1_DLY = 0x8d; - public static final int CODE_C1_DLC = 0x8e; - public static final int CODE_C1_RST = 0x8f; - public static final int CODE_C1_SPA = 0x90; - public static final int CODE_C1_SPC = 0x91; - public static final int CODE_C1_SPL = 0x92; - public static final int CODE_C1_SWA = 0x97; - public static final int CODE_C1_DF0 = 0x98; - public static final int CODE_C1_DF1 = 0x99; - public static final int CODE_C1_DF2 = 0x9a; - public static final int CODE_C1_DF3 = 0x9b; - public static final int CODE_C1_DF4 = 0x9c; - public static final int CODE_C1_DF5 = 0x9d; - public static final int CODE_C1_DF6 = 0x9e; - public static final int CODE_C1_DF7 = 0x9f; - - public static class CcPacket implements Comparable { - public final byte[] bytes; - public final int ccCount; - public final long pts; - - public CcPacket(byte[] bytes, int ccCount, long pts) { - this.bytes = bytes; - this.ccCount = ccCount; - this.pts = pts; - } - - @Override - public int compareTo(@NonNull CcPacket another) { - return Long.compare(pts, another.pts); - } - } - - /** CEA-708B-specific color. */ - public static class CaptionColor { - public static final int OPACITY_SOLID = 0; - public static final int OPACITY_FLASH = 1; - public static final int OPACITY_TRANSLUCENT = 2; - public static final int OPACITY_TRANSPARENT = 3; - - private static final int[] COLOR_MAP = new int[] {0x00, 0x0f, 0xf0, 0xff}; - private static final int[] OPACITY_MAP = new int[] {0xff, 0xfe, 0x80, 0x00}; - - public final int opacity; - public final int red; - public final int green; - public final int blue; - - public CaptionColor(int opacity, int red, int green, int blue) { - this.opacity = opacity; - this.red = red; - this.green = green; - this.blue = blue; - } - - public int getArgbValue() { - return Color.argb( - OPACITY_MAP[opacity], COLOR_MAP[red], COLOR_MAP[green], COLOR_MAP[blue]); - } - } - - /** Caption event generated by {@link Cea708Parser}. */ - public static class CaptionEvent { - @Cea708Parser.CaptionEmitType public final int type; - public final Object obj; - - public CaptionEvent(int type, Object obj) { - this.type = type; - this.obj = obj; - } - } - - /** Pen style information. */ - public static class CaptionPenAttr { - // Pen sizes - public static final int PEN_SIZE_SMALL = 0; - public static final int PEN_SIZE_STANDARD = 1; - public static final int PEN_SIZE_LARGE = 2; - - // Offsets - public static final int OFFSET_SUBSCRIPT = 0; - public static final int OFFSET_NORMAL = 1; - public static final int OFFSET_SUPERSCRIPT = 2; - - public final int penSize; - public final int penOffset; - public final int textTag; - public final int fontTag; - public final int edgeType; - public final boolean underline; - public final boolean italic; - - public CaptionPenAttr( - int penSize, - int penOffset, - int textTag, - int fontTag, - int edgeType, - boolean underline, - boolean italic) { - this.penSize = penSize; - this.penOffset = penOffset; - this.textTag = textTag; - this.fontTag = fontTag; - this.edgeType = edgeType; - this.underline = underline; - this.italic = italic; - } - } - - /** - * {@link CaptionColor} objects that indicate the foreground, background, and edge color of a - * pen. - */ - public static class CaptionPenColor { - public final CaptionColor foregroundColor; - public final CaptionColor backgroundColor; - public final CaptionColor edgeColor; - - public CaptionPenColor( - CaptionColor foregroundColor, - CaptionColor backgroundColor, - CaptionColor edgeColor) { - this.foregroundColor = foregroundColor; - this.backgroundColor = backgroundColor; - this.edgeColor = edgeColor; - } - } - - /** Location information of a pen. */ - public static class CaptionPenLocation { - public final int row; - public final int column; - - public CaptionPenLocation(int row, int column) { - this.row = row; - this.column = column; - } - } - - /** Attributes of a caption window, which is defined in CEA-708B. */ - public static class CaptionWindowAttr { - public static final int JUSTIFY_LEFT = 0; - public static final int JUSTIFY_CENTER = 2; - public static final int PRINT_LEFT_TO_RIGHT = 0; - public static final int PRINT_RIGHT_TO_LEFT = 1; - public static final int PRINT_TOP_TO_BOTTOM = 2; - public static final int PRINT_BOTTOM_TO_TOP = 3; - - public final CaptionColor fillColor; - public final CaptionColor borderColor; - public final int borderType; - public final boolean wordWrap; - public final int printDirection; - public final int scrollDirection; - public final int justify; - public final int effectDirection; - public final int effectSpeed; - public final int displayEffect; - - public CaptionWindowAttr( - CaptionColor fillColor, - CaptionColor borderColor, - int borderType, - boolean wordWrap, - int printDirection, - int scrollDirection, - int justify, - int effectDirection, - int effectSpeed, - int displayEffect) { - this.fillColor = fillColor; - this.borderColor = borderColor; - this.borderType = borderType; - this.wordWrap = wordWrap; - this.printDirection = printDirection; - this.scrollDirection = scrollDirection; - this.justify = justify; - this.effectDirection = effectDirection; - this.effectSpeed = effectSpeed; - this.displayEffect = displayEffect; - } - } - - /** Construction information of the caption window of CEA-708B. */ - public static class CaptionWindow { - public final int id; - public final boolean visible; - public final boolean rowLock; - public final boolean columnLock; - public final int priority; - public final boolean relativePositioning; - public final int anchorVertical; - public final int anchorHorizontal; - public final int anchorId; - public final int rowCount; - public final int columnCount; - public final int penStyle; - public final int windowStyle; - - public CaptionWindow( - int id, - boolean visible, - boolean rowLock, - boolean columnLock, - int priority, - boolean relativePositioning, - int anchorVertical, - int anchorHorizontal, - int anchorId, - int rowCount, - int columnCount, - int penStyle, - int windowStyle) { - this.id = id; - this.visible = visible; - this.rowLock = rowLock; - this.columnLock = columnLock; - this.priority = priority; - this.relativePositioning = relativePositioning; - this.anchorVertical = anchorVertical; - this.anchorHorizontal = anchorHorizontal; - this.anchorId = anchorId; - this.rowCount = rowCount; - this.columnCount = columnCount; - this.penStyle = penStyle; - this.windowStyle = windowStyle; - } - } -} diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java deleted file mode 100644 index 9b7c2e2c..00000000 --- a/src/com/android/tv/tuner/data/PsiData.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.data; - -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import java.util.List; - -/** Collection of MPEG PSI table items. */ -public class PsiData { - - private PsiData() {} - - public static class PatItem { - private final int mProgramNo; - private final int mPmtPid; - - public PatItem(int programNo, int pmtPid) { - mProgramNo = programNo; - mPmtPid = pmtPid; - } - - public int getProgramNo() { - return mProgramNo; - } - - public int getPmtPid() { - return mPmtPid; - } - - @Override - public String toString() { - return String.format("Program No: %x PMT Pid: %x", mProgramNo, mPmtPid); - } - } - - public static class PmtItem { - public static final int ES_PID_PCR = 0x100; - - private final int mStreamType; - private final int mEsPid; - private final List mAudioTracks; - private final List mCaptionTracks; - - public PmtItem( - int streamType, - int esPid, - List audioTracks, - List captionTracks) { - mStreamType = streamType; - mEsPid = esPid; - mAudioTracks = audioTracks; - mCaptionTracks = captionTracks; - } - - public int getStreamType() { - return mStreamType; - } - - public int getEsPid() { - return mEsPid; - } - - public List getAudioTracks() { - return mAudioTracks; - } - - public List getCaptionTracks() { - return mCaptionTracks; - } - - @Override - public String toString() { - return String.format( - "Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s", - mStreamType, mEsPid, mAudioTracks, mCaptionTracks); - } - } -} diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java deleted file mode 100644 index 6459004c..00000000 --- a/src/com/android/tv/tuner/data/PsipData.java +++ /dev/null @@ -1,871 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.data; - -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.text.format.DateUtils; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.ts.SectionParser; -import com.android.tv.tuner.util.ConvertUtils; -import com.android.tv.util.StringUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; - -/** Collection of ATSC PSIP table items. */ -public class PsipData { - - private PsipData() {} - - public static class PsipSection { - private final int mTableId; - private final int mTableIdExtension; - private final int mSectionNumber; - private final boolean mCurrentNextIndicator; - - public static PsipSection create(byte[] data) { - if (data.length < 9) { - return null; - } - int tableId = data[0] & 0xff; - int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff); - int sectionNumber = data[6] & 0xff; - boolean currentNextIndicator = (data[5] & 0x01) != 0; - return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator); - } - - private PsipSection( - int tableId, - int tableIdExtension, - int sectionNumber, - boolean currentNextIndicator) { - mTableId = tableId; - mTableIdExtension = tableIdExtension; - mSectionNumber = sectionNumber; - mCurrentNextIndicator = currentNextIndicator; - } - - public int getTableId() { - return mTableId; - } - - public int getTableIdExtension() { - return mTableIdExtension; - } - - public int getSectionNumber() { - return mSectionNumber; - } - - // This is for indicating that the section sent is applicable. - // We only consider a situation where currentNextIndicator is expected to have a true value. - // So, we are not going to compare this variable in hashCode() and equals() methods. - public boolean getCurrentNextIndicator() { - return mCurrentNextIndicator; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + mTableId; - result = 31 * result + mTableIdExtension; - result = 31 * result + mSectionNumber; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof PsipSection) { - PsipSection another = (PsipSection) obj; - return mTableId == another.getTableId() - && mTableIdExtension == another.getTableIdExtension() - && mSectionNumber == another.getSectionNumber(); - } - return false; - } - } - - /** {@link TvTracksInterface} for serving the audio and caption tracks. */ - public interface TvTracksInterface { - /** Set the flag that tells the caption tracks have been found in this section container. */ - void setHasCaptionTrack(); - - /** - * Returns whether or not the caption tracks have been found in this section container. If - * true, zero caption track will be interpreted as a clearance of the caption tracks. - */ - boolean hasCaptionTrack(); - - /** Returns the audio tracks received. */ - List getAudioTracks(); - - /** Returns the caption tracks received. */ - List getCaptionTracks(); - } - - public static class MgtItem { - public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100; - public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f; - public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004; - public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200; - public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f; - - private final int mTableType; - private final int mTableTypePid; - - public MgtItem(int tableType, int tableTypePid) { - mTableType = tableType; - mTableTypePid = tableTypePid; - } - - public int getTableType() { - return mTableType; - } - - public int getTableTypePid() { - return mTableTypePid; - } - } - - public static class VctItem { - private final String mShortName; - private final String mLongName; - private final int mServiceType; - private final int mChannelTsid; - private final int mProgramNumber; - private final int mMajorChannelNumber; - private final int mMinorChannelNumber; - private final int mSourceId; - private String mDescription; - - public VctItem( - String shortName, - String longName, - int serviceType, - int channelTsid, - int programNumber, - int majorChannelNumber, - int minorChannelNumber, - int sourceId) { - mShortName = shortName; - mLongName = longName; - mServiceType = serviceType; - mChannelTsid = channelTsid; - mProgramNumber = programNumber; - mMajorChannelNumber = majorChannelNumber; - mMinorChannelNumber = minorChannelNumber; - mSourceId = sourceId; - } - - public String getShortName() { - return mShortName; - } - - public String getLongName() { - return mLongName; - } - - public int getServiceType() { - return mServiceType; - } - - public int getChannelTsid() { - return mChannelTsid; - } - - public int getProgramNumber() { - return mProgramNumber; - } - - public int getMajorChannelNumber() { - return mMajorChannelNumber; - } - - public int getMinorChannelNumber() { - return mMinorChannelNumber; - } - - public int getSourceId() { - return mSourceId; - } - - @Override - public String toString() { - return String.format( - Locale.US, - "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x " - + "ProgramNumber:%d %d-%d SourceId: %x", - mShortName, - mLongName, - mServiceType, - mChannelTsid, - mProgramNumber, - mMajorChannelNumber, - mMinorChannelNumber, - mSourceId); - } - - public void setDescription(String description) { - mDescription = description; - } - - public String getDescription() { - return mDescription; - } - } - - public static class SdtItem { - private final String mServiceName; - private final String mServiceProviderName; - private final int mServiceType; - private final int mServiceId; - private final int mOriginalNetWorkId; - - public SdtItem( - String serviceName, - String serviceProviderName, - int serviceType, - int serviceId, - int originalNetWorkId) { - mServiceName = serviceName; - mServiceProviderName = serviceProviderName; - mServiceType = serviceType; - mServiceId = serviceId; - mOriginalNetWorkId = originalNetWorkId; - } - - public String getServiceName() { - return mServiceName; - } - - public String getServiceProviderName() { - return mServiceProviderName; - } - - public int getServiceType() { - return mServiceType; - } - - public int getServiceId() { - return mServiceId; - } - - public int getOriginalNetworkId() { - return mOriginalNetWorkId; - } - - @Override - public String toString() { - return String.format( - "ServiceName: %s ServiceProviderName:%s ServiceType:%d " - + "OriginalNetworkId:%d", - mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId); - } - } - - /** A base class for descriptors of Ts packets. */ - public abstract static class TsDescriptor { - public abstract int getTag(); - } - - public static class ContentAdvisoryDescriptor extends TsDescriptor { - private final List mRatingRegions; - - public ContentAdvisoryDescriptor(List ratingRegions) { - mRatingRegions = ratingRegions; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY; - } - - public List getRatingRegions() { - return mRatingRegions; - } - } - - public static class CaptionServiceDescriptor extends TsDescriptor { - private final List mCaptionTracks; - - public CaptionServiceDescriptor(List captionTracks) { - mCaptionTracks = captionTracks; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE; - } - - public List getCaptionTracks() { - return mCaptionTracks; - } - } - - public static class ExtendedChannelNameDescriptor extends TsDescriptor { - private final String mLongChannelName; - - public ExtendedChannelNameDescriptor(String longChannelName) { - mLongChannelName = longChannelName; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME; - } - - public String getLongChannelName() { - return mLongChannelName; - } - } - - public static class GenreDescriptor extends TsDescriptor { - private final String[] mBroadcastGenres; - private final String[] mCanonicalGenres; - - public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) { - mBroadcastGenres = broadcastGenres; - mCanonicalGenres = canonicalGenres; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_GENRE; - } - - public String[] getBroadcastGenres() { - return mBroadcastGenres; - } - - public String[] getCanonicalGenres() { - return mCanonicalGenres; - } - } - - public static class Ac3AudioDescriptor extends TsDescriptor { - // See A/52 Annex A. Table A4.2 - private static final byte SAMPLE_RATE_CODE_48000HZ = 0; - private static final byte SAMPLE_RATE_CODE_44100HZ = 1; - private static final byte SAMPLE_RATE_CODE_32000HZ = 2; - - private final byte mSampleRateCode; - private final byte mBsid; - private final byte mBitRateCode; - private final byte mSurroundMode; - private final byte mBsmod; - private final int mNumChannels; - private final boolean mFullSvc; - private final byte mLangCod; - private final byte mLangCod2; - private final byte mMainId; - private final byte mPriority; - private final byte mAsvcflags; - private final String mText; - private final String mLanguage; - private final String mLanguage2; - - public Ac3AudioDescriptor( - byte sampleRateCode, - byte bsid, - byte bitRateCode, - byte surroundMode, - byte bsmod, - int numChannels, - boolean fullSvc, - byte langCod, - byte langCod2, - byte mainId, - byte priority, - byte asvcflags, - String text, - String language, - String language2) { - mSampleRateCode = sampleRateCode; - mBsid = bsid; - mBitRateCode = bitRateCode; - mSurroundMode = surroundMode; - mBsmod = bsmod; - mNumChannels = numChannels; - mFullSvc = fullSvc; - mLangCod = langCod; - mLangCod2 = langCod2; - mMainId = mainId; - mPriority = priority; - mAsvcflags = asvcflags; - mText = text; - mLanguage = language; - mLanguage2 = language2; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM; - } - - public byte getSampleRateCode() { - return mSampleRateCode; - } - - public int getSampleRate() { - switch (mSampleRateCode) { - case SAMPLE_RATE_CODE_48000HZ: - return 48000; - case SAMPLE_RATE_CODE_44100HZ: - return 44100; - case SAMPLE_RATE_CODE_32000HZ: - return 32000; - default: - return 0; - } - } - - public byte getBsid() { - return mBsid; - } - - public byte getBitRateCode() { - return mBitRateCode; - } - - public byte getSurroundMode() { - return mSurroundMode; - } - - public byte getBsmod() { - return mBsmod; - } - - public int getNumChannels() { - return mNumChannels; - } - - public boolean isFullSvc() { - return mFullSvc; - } - - public byte getLangCod() { - return mLangCod; - } - - public byte getLangCod2() { - return mLangCod2; - } - - public byte getMainId() { - return mMainId; - } - - public byte getPriority() { - return mPriority; - } - - public byte getAsvcflags() { - return mAsvcflags; - } - - public String getText() { - return mText; - } - - public String getLanguage() { - return mLanguage; - } - - public String getLanguage2() { - return mLanguage2; - } - - @Override - public String toString() { - return String.format( - Locale.US, - "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, " - + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, " - + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s" - + ", language2: %s", - mSampleRateCode, - mBsid, - mBitRateCode, - mSurroundMode, - mBsmod, - mNumChannels, - mFullSvc, - mLangCod, - mLangCod2, - mMainId, - mPriority, - mAsvcflags, - mText, - mLanguage, - mLanguage2); - } - } - - public static class Iso639LanguageDescriptor extends TsDescriptor { - private final List mAudioTracks; - - public Iso639LanguageDescriptor(List audioTracks) { - mAudioTracks = audioTracks; - } - - @Override - public int getTag() { - return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE; - } - - public List getAudioTracks() { - return mAudioTracks; - } - - @Override - public String toString() { - return String.format("%s %s", getClass().getName(), mAudioTracks); - } - } - - public static class ServiceDescriptor extends TsDescriptor { - private final int mServiceType; - private final String mServiceProviderName; - private final String mServiceName; - - public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) { - mServiceType = serviceType; - mServiceProviderName = serviceProviderName; - mServiceName = serviceName; - } - - @Override - public int getTag() { - return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE; - } - - public int getServiceType() { - return mServiceType; - } - - public String getServiceProviderName() { - return mServiceProviderName; - } - - public String getServiceName() { - return mServiceName; - } - - @Override - public String toString() { - return String.format( - "Service descriptor, service type: %d, " - + "service provider name: %s, " - + "service name: %s", - mServiceType, mServiceProviderName, mServiceName); - } - } - - public static class ShortEventDescriptor extends TsDescriptor { - private final String mLanguage; - private final String mEventName; - private final String mText; - - public ShortEventDescriptor(String language, String eventName, String text) { - mLanguage = language; - mEventName = eventName; - mText = text; - } - - public String getEventName() { - return mEventName; - } - - @Override - public int getTag() { - return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT; - } - - @Override - public String toString() { - return String.format( - "ShortEvent Descriptor, language:%s, event name: %s, " + "text:%s", - mLanguage, mEventName, mText); - } - } - - public static class ParentalRatingDescriptor extends TsDescriptor { - private final HashMap mRatings; - - public ParentalRatingDescriptor(HashMap ratings) { - mRatings = ratings; - } - - @Override - public int getTag() { - return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING; - } - - public HashMap getRatings() { - return mRatings; - } - - @Override - public String toString() { - return String.format("Parental rating descriptor, ratings:" + mRatings); - } - } - - public static class RatingRegion { - private final int mName; - private final String mDescription; - private final List mRegionalRatings; - - public RatingRegion(int name, String description, List regionalRatings) { - mName = name; - mDescription = description; - mRegionalRatings = regionalRatings; - } - - public int getName() { - return mName; - } - - public String getDescription() { - return mDescription; - } - - public List getRegionalRatings() { - return mRegionalRatings; - } - } - - public static class RegionalRating { - private final int mDimension; - private final int mRating; - - public RegionalRating(int dimension, int rating) { - mDimension = dimension; - mRating = rating; - } - - public int getDimension() { - return mDimension; - } - - public int getRating() { - return mRating; - } - } - - public static class EitItem implements Comparable, TvTracksInterface { - public static final long INVALID_PROGRAM_ID = -1; - - // A program id is a primary key of TvContract.Programs table. So it must be positive. - private final long mProgramId; - private final int mEventId; - private final String mTitleText; - private String mDescription; - private final long mStartTime; - private final int mLengthInSecond; - private final String mContentRating; - private final List mAudioTracks; - private final List mCaptionTracks; - private boolean mHasCaptionTrack; - private final String mBroadcastGenre; - private final String mCanonicalGenre; - - public EitItem( - long programId, - int eventId, - String titleText, - long startTime, - int lengthInSecond, - String contentRating, - List audioTracks, - List captionTracks, - String broadcastGenre, - String canonicalGenre, - String description) { - mProgramId = programId; - mEventId = eventId; - mTitleText = titleText; - mStartTime = startTime; - mLengthInSecond = lengthInSecond; - mContentRating = contentRating; - mAudioTracks = audioTracks; - mCaptionTracks = captionTracks; - mBroadcastGenre = broadcastGenre; - mCanonicalGenre = canonicalGenre; - mDescription = description; - } - - public long getProgramId() { - return mProgramId; - } - - public int getEventId() { - return mEventId; - } - - public String getTitleText() { - return mTitleText; - } - - public void setDescription(String description) { - mDescription = description; - } - - public String getDescription() { - return mDescription; - } - - public long getStartTime() { - return mStartTime; - } - - public int getLengthInSecond() { - return mLengthInSecond; - } - - public long getStartTimeUtcMillis() { - return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS; - } - - public long getEndTimeUtcMillis() { - return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime + mLengthInSecond) - * DateUtils.SECOND_IN_MILLIS; - } - - public String getContentRating() { - return mContentRating; - } - - @Override - public List getAudioTracks() { - return mAudioTracks; - } - - @Override - public List getCaptionTracks() { - return mCaptionTracks; - } - - public String getBroadcastGenre() { - return mBroadcastGenre; - } - - public String getCanonicalGenre() { - return mCanonicalGenre; - } - - @Override - public void setHasCaptionTrack() { - mHasCaptionTrack = true; - } - - @Override - public boolean hasCaptionTrack() { - return mHasCaptionTrack; - } - - @Override - public int compareTo(@NonNull EitItem item) { - // The list of caption tracks and the program ids are not compared in here because the - // channels in TIF have the concept of the caption and audio tracks while the programs - // do not and the programs in TIF only have a program id since they are the rows of - // Content Provider. - int ret = mEventId - item.getEventId(); - if (ret != 0) { - return ret; - } - ret = StringUtils.compare(mTitleText, item.getTitleText()); - if (ret != 0) { - return ret; - } - if (mStartTime > item.getStartTime()) { - return 1; - } else if (mStartTime < item.getStartTime()) { - return -1; - } - if (mLengthInSecond > item.getLengthInSecond()) { - return 1; - } else if (mLengthInSecond < item.getLengthInSecond()) { - return -1; - } - - // Compares content ratings - ret = StringUtils.compare(mContentRating, item.getContentRating()); - if (ret != 0) { - return ret; - } - - // Compares broadcast genres - ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre()); - if (ret != 0) { - return ret; - } - // Compares canonical genres - ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre()); - if (ret != 0) { - return ret; - } - - // Compares descriptions - return StringUtils.compare(mDescription, item.getDescription()); - } - - public String getAudioLanguage() { - if (mAudioTracks == null) { - return ""; - } - ArrayList languages = new ArrayList<>(); - for (AtscAudioTrack audioTrack : mAudioTracks) { - languages.add(audioTrack.language); - } - return TextUtils.join(",", languages); - } - - @Override - public String toString() { - return String.format( - Locale.US, - "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, " - + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, " - + "genres (broadcast: %s, canonical: %s), description: %s", - mProgramId, - mEventId, - mTitleText, - mStartTime, - mLengthInSecond, - mContentRating, - mAudioTracks != null ? mAudioTracks.size() : 0, - mCaptionTracks != null ? mCaptionTracks.size() : 0, - mBroadcastGenre, - mCanonicalGenre, - mDescription); - } - } - - public static class EttItem { - public final int eventId; - public final String text; - - public EttItem(int eventId, String text) { - this.eventId = eventId; - this.text = text; - } - } -} diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java deleted file mode 100644 index 52356c2e..00000000 --- a/src/com/android/tv/tuner/data/TunerChannel.java +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.data; - -import android.support.annotation.NonNull; -import android.util.Log; -import com.android.tv.tuner.data.nano.Channel; -import com.android.tv.tuner.data.nano.Channel.TunerChannelProto; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.util.Ints; -import com.android.tv.util.StringUtils; -import com.google.protobuf.nano.MessageNano; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** A class that represents a single channel accessible through a tuner. */ -public class TunerChannel implements Comparable, PsipData.TvTracksInterface { - private static final String TAG = "TunerChannel"; - - /** Channel number separator between major number and minor number. */ - public static final char CHANNEL_NUMBER_SEPARATOR = '-'; - - // See ATSC Code Points Registry. - private static final String[] ATSC_SERVICE_TYPE_NAMES = - new String[] { - "ATSC Reserved", - "Analog television channels", - "ATSC_digital_television", - "ATSC_audio", - "ATSC_data_only_service", - "Software Download", - "Unassociated/Small Screen Service", - "Parameterized Service", - "ATSC NRT Service", - "Extended Parameterized Service" - }; - private static final String ATSC_SERVICE_TYPE_NAME_RESERVED = - ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED]; - - public static final int INVALID_FREQUENCY = -1; - - // According to RFC4259, The number of available PIDs ranges from 0 to 8191. - public static final int INVALID_PID = -1; - - // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff. - public static final int INVALID_STREAMTYPE = -1; - - // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766 - private final TunerChannelProto mProto; - - private TunerChannel( - PsipData.VctItem channel, int programNumber, List pmtItems, int type) { - mProto = new TunerChannelProto(); - if (channel == null) { - mProto.shortName = ""; - mProto.tsid = 0; - mProto.programNumber = programNumber; - mProto.virtualMajor = 0; - mProto.virtualMinor = 0; - } else { - mProto.shortName = channel.getShortName(); - if (channel.getLongName() != null) { - mProto.longName = channel.getLongName(); - } - mProto.tsid = channel.getChannelTsid(); - mProto.programNumber = channel.getProgramNumber(); - mProto.virtualMajor = channel.getMajorChannelNumber(); - mProto.virtualMinor = channel.getMinorChannelNumber(); - if (channel.getDescription() != null) { - mProto.description = channel.getDescription(); - } - mProto.serviceType = channel.getServiceType(); - } - initProto(pmtItems, type); - } - - private void initProto(List pmtItems, int type) { - mProto.type = type; - mProto.channelId = -1L; - mProto.frequency = INVALID_FREQUENCY; - mProto.videoPid = INVALID_PID; - mProto.videoStreamType = INVALID_STREAMTYPE; - List audioPids = new ArrayList<>(); - List audioStreamTypes = new ArrayList<>(); - for (PsiData.PmtItem pmt : pmtItems) { - switch (pmt.getStreamType()) { - // MPEG ES stream video types - case Channel.MPEG1: - case Channel.MPEG2: - case Channel.H263: - case Channel.H264: - case Channel.H265: - mProto.videoPid = pmt.getEsPid(); - mProto.videoStreamType = pmt.getStreamType(); - break; - - // MPEG ES stream audio types - case Channel.MPEG1AUDIO: - case Channel.MPEG2AUDIO: - case Channel.MPEG2AACAUDIO: - case Channel.MPEG4LATMAACAUDIO: - case Channel.A52AC3AUDIO: - case Channel.EAC3AUDIO: - audioPids.add(pmt.getEsPid()); - audioStreamTypes.add(pmt.getStreamType()); - break; - - // Non MPEG ES stream types - case 0x100: // PmtItem.ES_PID_PCR: - mProto.pcrPid = pmt.getEsPid(); - break; - } - } - mProto.audioPids = Ints.toArray(audioPids); - mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); - mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1; - } - - private TunerChannel( - int programNumber, int type, PsipData.SdtItem channel, List pmtItems) { - mProto = new TunerChannelProto(); - mProto.tsid = 0; - mProto.virtualMajor = 0; - mProto.virtualMinor = 0; - if (channel == null) { - mProto.shortName = ""; - mProto.programNumber = programNumber; - } else { - mProto.shortName = channel.getServiceName(); - mProto.programNumber = channel.getServiceId(); - mProto.serviceType = channel.getServiceType(); - } - initProto(pmtItems, type); - } - - /** Initialize tuner channel with VCT items and PMT items. */ - public TunerChannel(PsipData.VctItem channel, List pmtItems) { - this(channel, 0, pmtItems, Channel.TYPE_TUNER); - } - - /** Initialize tuner channel with program number and PMT items. */ - public TunerChannel(int programNumber, List pmtItems) { - this(null, programNumber, pmtItems, Channel.TYPE_TUNER); - } - - /** Initialize tuner channel with SDT items and PMT items. */ - public TunerChannel(PsipData.SdtItem channel, List pmtItems) { - this(0, Channel.TYPE_TUNER, channel, pmtItems); - } - - private TunerChannel(TunerChannelProto tunerChannelProto) { - mProto = tunerChannelProto; - } - - public static TunerChannel forFile(PsipData.VctItem channel, List pmtItems) { - return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE); - } - - public static TunerChannel forDvbFile( - PsipData.SdtItem channel, List pmtItems) { - return new TunerChannel(0, Channel.TYPE_FILE, channel, pmtItems); - } - - /** - * Create a TunerChannel object suitable for network tuners - * - * @param major Channel number major - * @param minor Channel number minor - * @param programNumber Program number - * @param shortName Short name - * @param recordingProhibited Recording prohibition info - * @param videoFormat Video format. Should be {@code null} or one of the followings: {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, {@link - * android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P} - * @return a TunerChannel object - */ - public static TunerChannel forNetwork( - int major, - int minor, - int programNumber, - String shortName, - boolean recordingProhibited, - String videoFormat) { - TunerChannel tunerChannel = - new TunerChannel(null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK); - tunerChannel.setVirtualMajor(major); - tunerChannel.setVirtualMinor(minor); - tunerChannel.setShortName(shortName); - // Set audio and video pids in order to work around the audio-only channel check. - tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0))); - tunerChannel.selectAudioTrack(0); - tunerChannel.setVideoPid(0); - tunerChannel.setRecordingProhibited(recordingProhibited); - if (videoFormat != null) { - tunerChannel.setVideoFormat(videoFormat); - } - return tunerChannel; - } - - public String getName() { - return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName; - } - - public String getShortName() { - return mProto.shortName; - } - - public int getProgramNumber() { - return mProto.programNumber; - } - - public int getServiceType() { - return mProto.serviceType; - } - - public String getServiceTypeName() { - int serviceType = mProto.serviceType; - if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) { - return ATSC_SERVICE_TYPE_NAMES[serviceType]; - } - return ATSC_SERVICE_TYPE_NAME_RESERVED; - } - - public int getVirtualMajor() { - return mProto.virtualMajor; - } - - public int getVirtualMinor() { - return mProto.virtualMinor; - } - - public int getFrequency() { - return mProto.frequency; - } - - public String getModulation() { - return mProto.modulation; - } - - public int getTsid() { - return mProto.tsid; - } - - public int getVideoPid() { - return mProto.videoPid; - } - - public synchronized void setVideoPid(int videoPid) { - mProto.videoPid = videoPid; - } - - public int getVideoStreamType() { - return mProto.videoStreamType; - } - - public int getAudioPid() { - if (mProto.audioTrackIndex == -1) { - return INVALID_PID; - } - return mProto.audioPids[mProto.audioTrackIndex]; - } - - public int getAudioStreamType() { - if (mProto.audioTrackIndex == -1) { - return INVALID_STREAMTYPE; - } - return mProto.audioStreamTypes[mProto.audioTrackIndex]; - } - - public List getAudioPids() { - return Ints.asList(mProto.audioPids); - } - - public synchronized void setAudioPids(List audioPids) { - mProto.audioPids = Ints.toArray(audioPids); - } - - public List getAudioStreamTypes() { - return Ints.asList(mProto.audioStreamTypes); - } - - public synchronized void setAudioStreamTypes(List audioStreamTypes) { - mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); - } - - public int getPcrPid() { - return mProto.pcrPid; - } - - public int getType() { - return mProto.type; - } - - public synchronized void setFilepath(String filepath) { - mProto.filepath = filepath == null ? "" : filepath; - } - - public String getFilepath() { - return mProto.filepath; - } - - public synchronized void setVirtualMajor(int virtualMajor) { - mProto.virtualMajor = virtualMajor; - } - - public synchronized void setVirtualMinor(int virtualMinor) { - mProto.virtualMinor = virtualMinor; - } - - public synchronized void setShortName(String shortName) { - mProto.shortName = shortName == null ? "" : shortName; - } - - public synchronized void setFrequency(int frequency) { - mProto.frequency = frequency; - } - - public synchronized void setModulation(String modulation) { - mProto.modulation = modulation == null ? "" : modulation; - } - - public boolean hasVideo() { - return mProto.videoPid != INVALID_PID; - } - - public boolean hasAudio() { - return getAudioPid() != INVALID_PID; - } - - public long getChannelId() { - return mProto.channelId; - } - - public synchronized void setChannelId(long channelId) { - mProto.channelId = channelId; - } - - public String getDisplayNumber() { - return getDisplayNumber(true); - } - - public String getDisplayNumber(boolean ignoreZeroMinorNumber) { - if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) { - return String.format( - "%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR, mProto.virtualMinor); - } else if (mProto.virtualMajor != 0) { - return Integer.toString(mProto.virtualMajor); - } else { - return Integer.toString(mProto.programNumber); - } - } - - public String getDescription() { - return mProto.description; - } - - @Override - public synchronized void setHasCaptionTrack() { - mProto.hasCaptionTrack = true; - } - - @Override - public boolean hasCaptionTrack() { - return mProto.hasCaptionTrack; - } - - @Override - public List getAudioTracks() { - return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks)); - } - - public synchronized void setAudioTracks(List audioTracks) { - mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]); - } - - @Override - public List getCaptionTracks() { - return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks)); - } - - public synchronized void setCaptionTracks(List captionTracks) { - mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]); - } - - public synchronized void selectAudioTrack(int index) { - if (0 <= index && index < mProto.audioPids.length) { - mProto.audioTrackIndex = index; - } else { - mProto.audioTrackIndex = -1; - } - } - - public synchronized void setRecordingProhibited(boolean recordingProhibited) { - mProto.recordingProhibited = recordingProhibited; - } - - public boolean isRecordingProhibited() { - return mProto.recordingProhibited; - } - - public synchronized void setVideoFormat(String videoFormat) { - mProto.videoFormat = videoFormat == null ? "" : videoFormat; - } - - public String getVideoFormat() { - return mProto.videoFormat; - } - - @Override - public String toString() { - switch (mProto.type) { - case Channel.TYPE_FILE: - return String.format( - "{%d-%d %s} Filepath: %s, ProgramNumber %d", - mProto.virtualMajor, - mProto.virtualMinor, - mProto.shortName, - mProto.filepath, - mProto.programNumber); - // case Channel.TYPE_TUNER: - default: - return String.format( - "{%d-%d %s} Frequency: %d, ProgramNumber %d", - mProto.virtualMajor, - mProto.virtualMinor, - mProto.shortName, - mProto.frequency, - mProto.programNumber); - } - } - - @Override - public int compareTo(@NonNull TunerChannel channel) { - // In the same frequency, the program number acts as the sub-channel number. - int ret = getFrequency() - channel.getFrequency(); - if (ret != 0) { - return ret; - } - ret = getProgramNumber() - channel.getProgramNumber(); - if (ret != 0) { - return ret; - } - ret = StringUtils.compare(getName(), channel.getName()); - if (ret != 0) { - return ret; - } - // For FileTsStreamer, file paths should be compared. - return StringUtils.compare(getFilepath(), channel.getFilepath()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof TunerChannel)) { - return false; - } - return compareTo((TunerChannel) o) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath()); - } - - // Serialization - public synchronized byte[] toByteArray() { - try { - return MessageNano.toByteArray(mProto); - } catch (Exception e) { - // Retry toByteArray. b/34197766 - Log.w( - TAG, - "TunerChannel or its variables are modified in multiple thread without lock", - e); - return MessageNano.toByteArray(mProto); - } - } - - public static TunerChannel parseFrom(byte[] data) { - if (data == null) { - return null; - } - try { - return new TunerChannel(TunerChannelProto.parseFrom(data)); - } catch (IOException e) { - Log.e(TAG, "Could not parse from byte array", e); - return null; - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java deleted file mode 100644 index 1f48c45b..00000000 --- a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.util.Log; -import com.android.tv.tuner.cc.Cea708Parser; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaClock; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.util.Assertions; -import java.io.IOException; - -/** A {@link TrackRenderer} for CEA-708 textual subtitles. */ -public class Cea708TextTrackRenderer extends TrackRenderer - implements Cea708Parser.OnCea708ParserListener { - private static final String TAG = "Cea708TextTrackRenderer"; - private static final boolean DEBUG = false; - - public static final int MSG_SERVICE_NUMBER = 1; - public static final int MSG_ENABLE_CLOSED_CAPTION = 2; - - // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps. - private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8; - - private final SampleSource.SampleSourceReader mSource; - private final SampleHolder mSampleHolder; - private final MediaFormatHolder mFormatHolder; - private int mServiceNumber; - private boolean mInputStreamEnded; - private long mCurrentPositionUs; - private long mPresentationTimeUs; - private int mTrackIndex; - private boolean mRenderingDisabled; - private Cea708Parser mCea708Parser; - private CcListener mCcListener; - - public interface CcListener { - void emitEvent(CaptionEvent captionEvent); - - void clearCaption(); - - void discoverServiceNumber(int serviceNumber); - } - - public Cea708TextTrackRenderer(SampleSource source) { - mSource = source.register(); - mTrackIndex = -1; - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); - mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); - mFormatHolder = new MediaFormatHolder(); - } - - @Override - protected MediaClock getMediaClock() { - return null; - } - - private boolean handlesMimeType(String mimeType) { - return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708); - } - - @Override - protected boolean doPrepare(long positionUs) throws ExoPlaybackException { - boolean sourcePrepared = mSource.prepare(positionUs); - if (!sourcePrepared) { - return false; - } - int trackCount = mSource.getTrackCount(); - for (int i = 0; i < trackCount; ++i) { - MediaFormat trackFormat = mSource.getFormat(i); - if (handlesMimeType(trackFormat.mimeType)) { - mTrackIndex = i; - clearDecodeState(); - return true; - } - } - // TODO: Check this case. (Source do not have the proper mime type.) - return true; - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) { - Assertions.checkArgument(mTrackIndex != -1 && track == 0); - mSource.enable(mTrackIndex, positionUs); - mInputStreamEnded = false; - mPresentationTimeUs = positionUs; - mCurrentPositionUs = Long.MIN_VALUE; - } - - @Override - protected void onDisabled() { - mSource.disable(mTrackIndex); - } - - @Override - protected void onReleased() { - mSource.release(); - mCea708Parser = null; - } - - @Override - protected boolean isEnded() { - return mInputStreamEnded; - } - - @Override - protected boolean isReady() { - // Since this track will be fed by {@link VideoTrackRenderer}, - // it is not required to control transition between ready state and buffering state. - return true; - } - - @Override - protected int getTrackCount() { - return mTrackIndex < 0 ? 0 : 1; - } - - @Override - protected MediaFormat getFormat(int track) { - Assertions.checkArgument(mTrackIndex != -1 && track == 0); - return mSource.getFormat(mTrackIndex); - } - - @Override - protected void maybeThrowError() throws ExoPlaybackException { - try { - mSource.maybeThrowError(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - try { - mPresentationTimeUs = positionUs; - if (!mInputStreamEnded) { - processOutput(); - feedInputBuffer(); - } - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - private boolean processOutput() { - return !mInputStreamEnded - && mCea708Parser != null - && mCea708Parser.processClosedCaptions(mPresentationTimeUs); - } - - private boolean feedInputBuffer() throws IOException, ExoPlaybackException { - if (mInputStreamEnded) { - return false; - } - long discontinuity = mSource.readDiscontinuity(mTrackIndex); - if (discontinuity != SampleSource.NO_DISCONTINUITY) { - if (DEBUG) { - Log.d(TAG, "Read discontinuity happened"); - } - - // TODO: handle input discontinuity for trickplay. - clearDecodeState(); - mPresentationTimeUs = discontinuity; - return false; - } - mSampleHolder.data.clear(); - mSampleHolder.size = 0; - int result = - mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder); - switch (result) { - case SampleSource.NOTHING_READ: - { - return false; - } - case SampleSource.FORMAT_READ: - { - if (DEBUG) { - Log.i(TAG, "Format was read again"); - } - return true; - } - case SampleSource.END_OF_STREAM: - { - if (DEBUG) { - Log.i(TAG, "End of stream from SampleSource"); - } - mInputStreamEnded = true; - return false; - } - case SampleSource.SAMPLE_READ: - { - mSampleHolder.data.flip(); - if (mCea708Parser != null && !mRenderingDisabled) { - mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs); - } - return true; - } - } - return false; - } - - private void clearDecodeState() { - mCea708Parser = new Cea708Parser(); - mCea708Parser.setListener(this); - mCea708Parser.setListenServiceNumber(mServiceNumber); - } - - @Override - protected long getDurationUs() { - return mSource.getFormat(mTrackIndex).durationUs; - } - - @Override - protected long getBufferedPositionUs() { - return mSource.getBufferedPositionUs(); - } - - @Override - protected void seekTo(long currentPositionUs) throws ExoPlaybackException { - mSource.seekToUs(currentPositionUs); - mInputStreamEnded = false; - mPresentationTimeUs = currentPositionUs; - mCurrentPositionUs = Long.MIN_VALUE; - } - - @Override - protected void onStarted() { - // do nothing. - } - - @Override - protected void onStopped() { - // do nothing. - } - - private void setServiceNumber(int serviceNumber) { - mServiceNumber = serviceNumber; - if (mCea708Parser != null) { - mCea708Parser.setListenServiceNumber(serviceNumber); - } - } - - @Override - public void emitEvent(CaptionEvent event) { - if (mCcListener != null) { - mCcListener.emitEvent(event); - } - } - - @Override - public void discoverServiceNumber(int serviceNumber) { - if (mCcListener != null) { - mCcListener.discoverServiceNumber(serviceNumber); - } - } - - public void setCcListener(CcListener ccListener) { - mCcListener = ccListener; - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - switch (messageType) { - case MSG_SERVICE_NUMBER: - setServiceNumber((int) message); - break; - case MSG_ENABLE_CLOSED_CAPTION: - boolean renderingDisabled = (Boolean) message == false; - if (mRenderingDisabled != renderingDisabled) { - mRenderingDisabled = renderingDisabled; - if (mRenderingDisabled) { - if (mCea708Parser != null) { - mCea708Parser.clear(); - } - if (mCcListener != null) { - mCcListener.clearCaption(); - } - } - } - break; - default: - super.handleMessage(messageType, message); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java deleted file mode 100644 index b5369d69..00000000 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.tuner.exoplayer; - -import com.google.android.exoplayer2.extractor.Extractor; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.extractor.TimestampAdjuster; -import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; -import com.google.android.exoplayer2.extractor.ts.TsExtractor; - -/** - * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for - * H.264 stream - */ -public final class ExoPlayerExtractorsFactory implements ExtractorsFactory { - @Override - public Extractor[] createExtractors() { - // Only create TsExtractor since we only target MPEG2TS stream. - Extractor[] extractors = { - new TsExtractor( - new TimestampAdjuster(0), - new DefaultTsPayloadReaderFactory( - DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), - false) - }; - return extractors; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java deleted file mode 100644 index df520900..00000000 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.net.Uri; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; -import android.util.Pair; -import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; -import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.FormatHolder; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.source.ExtractorMediaSource; -import com.google.android.exoplayer2.source.ExtractorMediaSource.EventListener; -import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.SampleStream; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.FixedTrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DefaultAllocator; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A class that extracts samples from a live broadcast stream while storing the sample on the disk. - * For demux, this class relies on {@link com.google.android.exoplayer.extractor.ts.TsExtractor}. - */ -public class ExoPlayerSampleExtractor implements SampleExtractor { - private static final String TAG = "ExoPlayerSampleExtracto"; - - private static final int INVALID_TRACK_INDEX = -1; - private final HandlerThread mSourceReaderThread; - private final long mId; - - private final Handler.Callback mSourceReaderWorker; - - private BufferManager.SampleBuffer mSampleBuffer; - private Handler mSourceReaderHandler; - private volatile boolean mPrepared; - private AtomicBoolean mOnCompletionCalled = new AtomicBoolean(); - private IOException mExceptionOnPrepare; - private List mTrackFormats; - private int mVideoTrackIndex = INVALID_TRACK_INDEX; - private boolean mVideoTrackMet; - private long mBaseSamplePts = Long.MIN_VALUE; - private HashMap mLastExtractedPositionUsMap = new HashMap<>(); - private final List> mPendingSamples = new LinkedList<>(); - private OnCompletionListener mOnCompletionListener; - private Handler mOnCompletionListenerHandler; - private IOException mError; - - public ExoPlayerSampleExtractor( - Uri uri, - final DataSource source, - BufferManager bufferManager, - PlaybackBufferListener bufferListener, - boolean isRecording) { - // It'll be used as a timeshift file chunk name's prefix. - mId = System.currentTimeMillis(); - - EventListener eventListener = - new EventListener() { - @Override - public void onLoadError(IOException error) { - mError = error; - } - }; - - mSourceReaderThread = new HandlerThread("SourceReaderThread"); - mSourceReaderWorker = - new SourceReaderWorker( - new ExtractorMediaSource( - uri, - new com.google.android.exoplayer2.upstream.DataSource.Factory() { - @Override - public com.google.android.exoplayer2.upstream.DataSource - createDataSource() { - // Returns an adapter implementation for ExoPlayer V2 - // DataSource interface. - return new com.google.android.exoplayer2.upstream - .DataSource() { - @Override - public long open(DataSpec dataSpec) throws IOException { - return source.open( - new com.google.android.exoplayer.upstream - .DataSpec( - dataSpec.uri, - dataSpec.postBody, - dataSpec.absoluteStreamPosition, - dataSpec.position, - dataSpec.length, - dataSpec.key, - dataSpec.flags)); - } - - @Override - public int read( - byte[] buffer, int offset, int readLength) - throws IOException { - return source.read(buffer, offset, readLength); - } - - @Override - public Uri getUri() { - return null; - } - - @Override - public void close() throws IOException { - source.close(); - } - }; - } - }, - new ExoPlayerExtractorsFactory(), - // Do not create a handler if we not on a looper. e.g. test. - Looper.myLooper() != null ? new Handler() : null, - eventListener)); - if (isRecording) { - mSampleBuffer = - new RecordingSampleBuffer( - bufferManager, - bufferListener, - false, - RecordingSampleBuffer.BUFFER_REASON_RECORDING); - } else { - if (bufferManager == null) { - mSampleBuffer = new SimpleSampleBuffer(bufferListener); - } else { - mSampleBuffer = - new RecordingSampleBuffer( - bufferManager, - bufferListener, - true, - RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK); - } - } - } - - @Override - public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { - mOnCompletionListener = listener; - mOnCompletionListenerHandler = handler; - } - - private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback { - public static final int MSG_PREPARE = 1; - public static final int MSG_FETCH_SAMPLES = 2; - public static final int MSG_RELEASE = 3; - private static final int RETRY_INTERVAL_MS = 50; - - private final MediaSource mSampleSource; - private MediaPeriod mMediaPeriod; - private SampleStream[] mStreams; - private boolean[] mTrackMetEos; - private boolean mMetEos = false; - private long mCurrentPosition; - private DecoderInputBuffer mDecoderInputBuffer; - private SampleHolder mSampleHolder; - private boolean mPrepareRequested; - - public SourceReaderWorker(MediaSource sampleSource) { - mSampleSource = sampleSource; - mSampleSource.prepareSource( - null, - false, - new MediaSource.Listener() { - @Override - public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { - // Dynamic stream change is not supported yet. b/28169263 - // For now, this will cause EOS and playback reset. - } - }); - mDecoderInputBuffer = - new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - } - - MediaFormat convertFormat(Format format) { - if (format.sampleMimeType.startsWith("audio/")) { - return MediaFormat.createAudioFormat( - format.id, - format.sampleMimeType, - format.bitrate, - format.maxInputSize, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, - format.channelCount, - format.sampleRate, - format.initializationData, - format.language, - format.pcmEncoding); - } else if (format.sampleMimeType.startsWith("video/")) { - return MediaFormat.createVideoFormat( - format.id, - format.sampleMimeType, - format.bitrate, - format.maxInputSize, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, - format.width, - format.height, - format.initializationData, - format.rotationDegrees, - format.pixelWidthHeightRatio, - format.projectionData, - format.stereoMode); - } else if (format.sampleMimeType.endsWith("/cea-608") - || format.sampleMimeType.startsWith("text/")) { - return MediaFormat.createTextFormat( - format.id, - format.sampleMimeType, - format.bitrate, - com.google.android.exoplayer.C.UNKNOWN_TIME_US, - format.language); - } else { - return MediaFormat.createFormatForMimeType( - format.id, - format.sampleMimeType, - format.bitrate, - com.google.android.exoplayer.C.UNKNOWN_TIME_US); - } - } - - @Override - public void onPrepared(MediaPeriod mediaPeriod) { - if (mMediaPeriod == null) { - // This instance is already released while the extractor is preparing. - return; - } - TrackSelection.Factory selectionFactory = new FixedTrackSelection.Factory(); - TrackGroupArray trackGroupArray = mMediaPeriod.getTrackGroups(); - TrackSelection[] selections = new TrackSelection[trackGroupArray.length]; - for (int i = 0; i < selections.length; ++i) { - selections[i] = selectionFactory.createTrackSelection(trackGroupArray.get(i), 0); - } - boolean retain[] = new boolean[trackGroupArray.length]; - boolean reset[] = new boolean[trackGroupArray.length]; - mStreams = new SampleStream[trackGroupArray.length]; - mMediaPeriod.selectTracks(selections, retain, mStreams, reset, 0); - if (mTrackFormats == null) { - int trackCount = trackGroupArray.length; - mTrackMetEos = new boolean[trackCount]; - List trackFormats = new ArrayList<>(); - int videoTrackCount = 0; - for (int i = 0; i < trackCount; i++) { - Format format = trackGroupArray.get(i).getFormat(0); - if (format.sampleMimeType.startsWith("video/")) { - videoTrackCount++; - mVideoTrackIndex = i; - } - trackFormats.add(convertFormat(format)); - } - if (videoTrackCount > 1) { - // Disable dropping samples when there are multiple video tracks. - mVideoTrackIndex = INVALID_TRACK_INDEX; - } - mTrackFormats = trackFormats; - List ids = new ArrayList<>(); - for (int i = 0; i < mTrackFormats.size(); i++) { - ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i)); - } - try { - mSampleBuffer.init(ids, mTrackFormats); - } catch (IOException e) { - // In this case, we will not schedule any further operation. - // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will - // call release() eventually. - mExceptionOnPrepare = e; - return; - } - mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); - mPrepared = true; - } - } - - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - source.continueLoading(mCurrentPosition); - } - - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_PREPARE: - if (!mPrepareRequested) { - mPrepareRequested = true; - mMediaPeriod = - mSampleSource.createPeriod( - 0, - new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), - 0); - mMediaPeriod.prepare(this); - try { - mMediaPeriod.maybeThrowPrepareError(); - } catch (IOException e) { - mError = e; - } - } - return true; - case MSG_FETCH_SAMPLES: - boolean didSomething = false; - ConditionVariable conditionVariable = new ConditionVariable(); - int trackCount = mStreams.length; - for (int i = 0; i < trackCount; ++i) { - if (!mTrackMetEos[i] - && C.RESULT_NOTHING_READ - != fetchSample(i, mSampleHolder, conditionVariable)) { - if (mMetEos) { - // If mMetEos was on during fetchSample() due to an error, - // fetching from other tracks is not necessary. - break; - } - didSomething = true; - } - } - mMediaPeriod.continueLoading(mCurrentPosition); - if (!mMetEos) { - if (didSomething) { - mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); - } else { - mSourceReaderHandler.sendEmptyMessageDelayed( - MSG_FETCH_SAMPLES, RETRY_INTERVAL_MS); - } - } else { - notifyCompletionIfNeeded(false); - } - return true; - case MSG_RELEASE: - if (mMediaPeriod != null) { - mSampleSource.releasePeriod(mMediaPeriod); - mSampleSource.releaseSource(); - mMediaPeriod = null; - } - cleanUp(); - mSourceReaderHandler.removeCallbacksAndMessages(null); - return true; - } - return false; - } - - private int fetchSample( - int track, SampleHolder sample, ConditionVariable conditionVariable) { - FormatHolder dummyFormatHolder = new FormatHolder(); - mDecoderInputBuffer.clear(); - int ret = mStreams[track].readData(dummyFormatHolder, mDecoderInputBuffer); - if (ret == C.RESULT_BUFFER_READ - // Double-check if the extractor provided the data to prevent NPE. b/33758354 - && mDecoderInputBuffer.data != null) { - if (mCurrentPosition < mDecoderInputBuffer.timeUs) { - mCurrentPosition = mDecoderInputBuffer.timeUs; - } - try { - Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track); - if (lastExtractedPositionUs == null) { - mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs); - } else { - mLastExtractedPositionUsMap.put( - track, - Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs)); - } - queueSample(track, conditionVariable); - } catch (IOException e) { - mLastExtractedPositionUsMap.clear(); - mMetEos = true; - mSampleBuffer.setEos(); - } - } else if (ret == C.RESULT_END_OF_INPUT) { - mTrackMetEos[track] = true; - for (int i = 0; i < mTrackMetEos.length; ++i) { - if (!mTrackMetEos[i]) { - break; - } - if (i == mTrackMetEos.length - 1) { - mMetEos = true; - mSampleBuffer.setEos(); - } - } - } - // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263 - return ret; - } - - private void queueSample(int index, ConditionVariable conditionVariable) - throws IOException { - if (mVideoTrackIndex != INVALID_TRACK_INDEX) { - if (!mVideoTrackMet) { - if (index != mVideoTrackIndex) { - SampleHolder sample = - new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - mSampleHolder.flags = - (mDecoderInputBuffer.isKeyFrame() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC - : 0) - | (mDecoderInputBuffer.isDecodeOnly() - ? com.google - .android - .exoplayer - .C - .SAMPLE_FLAG_DECODE_ONLY - : 0); - sample.timeUs = mDecoderInputBuffer.timeUs; - sample.size = mDecoderInputBuffer.data.position(); - sample.ensureSpaceForWrite(sample.size); - mDecoderInputBuffer.flip(); - sample.data.position(0); - sample.data.put(mDecoderInputBuffer.data); - sample.data.flip(); - mPendingSamples.add(new Pair<>(index, sample)); - return; - } - mVideoTrackMet = true; - mBaseSamplePts = - mDecoderInputBuffer.timeUs - - MpegTsDefaultAudioTrackRenderer - .INITIAL_AUDIO_BUFFERING_TIME_US; - for (Pair pair : mPendingSamples) { - if (pair.second.timeUs >= mBaseSamplePts) { - mSampleBuffer.writeSample(pair.first, pair.second, conditionVariable); - } - } - mPendingSamples.clear(); - } else { - if (mDecoderInputBuffer.timeUs < mBaseSamplePts && mVideoTrackIndex != index) { - return; - } - } - } - // Copy the decoder input to the sample holder. - mSampleHolder.clearData(); - mSampleHolder.flags = - (mDecoderInputBuffer.isKeyFrame() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC - : 0) - | (mDecoderInputBuffer.isDecodeOnly() - ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY - : 0); - mSampleHolder.timeUs = mDecoderInputBuffer.timeUs; - mSampleHolder.size = mDecoderInputBuffer.data.position(); - mSampleHolder.ensureSpaceForWrite(mSampleHolder.size); - mDecoderInputBuffer.flip(); - mSampleHolder.data.position(0); - mSampleHolder.data.put(mDecoderInputBuffer.data); - mSampleHolder.data.flip(); - long writeStartTimeNs = SystemClock.elapsedRealtimeNanos(); - mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable); - - // Checks whether the storage has enough bandwidth for recording samples. - if (mSampleBuffer.isWriteSpeedSlow( - mSampleHolder.size, SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { - mSampleBuffer.handleWriteSpeedSlow(); - } - } - } - - @Override - public void maybeThrowError() throws IOException { - if (mError != null) { - IOException e = mError; - mError = null; - throw e; - } - } - - @Override - public boolean prepare() throws IOException { - if (!mSourceReaderThread.isAlive()) { - mSourceReaderThread.start(); - mSourceReaderHandler = - new Handler(mSourceReaderThread.getLooper(), mSourceReaderWorker); - mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_PREPARE); - } - if (mExceptionOnPrepare != null) { - throw mExceptionOnPrepare; - } - return mPrepared; - } - - @Override - public List getTrackFormats() { - return mTrackFormats; - } - - @Override - public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) { - outMediaFormatHolder.format = mTrackFormats.get(track); - outMediaFormatHolder.drmInitData = null; - } - - @Override - public void selectTrack(int index) { - mSampleBuffer.selectTrack(index); - } - - @Override - public void deselectTrack(int index) { - mSampleBuffer.deselectTrack(index); - } - - @Override - public long getBufferedPositionUs() { - return mSampleBuffer.getBufferedPositionUs(); - } - - @Override - public boolean continueBuffering(long positionUs) { - return mSampleBuffer.continueBuffering(positionUs); - } - - @Override - public void seekTo(long positionUs) { - mSampleBuffer.seekTo(positionUs); - } - - @Override - public int readSample(int track, SampleHolder sampleHolder) { - return mSampleBuffer.readSample(track, sampleHolder); - } - - @Override - public void release() { - if (mSourceReaderThread.isAlive()) { - mSourceReaderHandler.removeCallbacksAndMessages(null); - mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_RELEASE); - mSourceReaderThread.quitSafely(); - // Return early in this case so that session worker can start working on the next - // request as early as it can. The clean up will be done in the reader thread while - // handling MSG_RELEASE. - } else { - cleanUp(); - } - } - - private void cleanUp() { - boolean result = true; - try { - if (mSampleBuffer != null) { - mSampleBuffer.release(); - mSampleBuffer = null; - } - } catch (IOException e) { - result = false; - } - notifyCompletionIfNeeded(result); - setOnCompletionListener(null, null); - } - - private void notifyCompletionIfNeeded(final boolean result) { - if (!mOnCompletionCalled.getAndSet(true)) { - final OnCompletionListener listener = mOnCompletionListener; - final long lastExtractedPositionUs = getLastExtractedPositionUs(); - if (mOnCompletionListenerHandler != null && mOnCompletionListener != null) { - mOnCompletionListenerHandler.post( - new Runnable() { - @Override - public void run() { - listener.onCompletion(result, lastExtractedPositionUs); - } - }); - } - } - } - - private long getLastExtractedPositionUs() { - long lastExtractedPositionUs = Long.MIN_VALUE; - for (Map.Entry entry : mLastExtractedPositionUsMap.entrySet()) { - if (mVideoTrackIndex != entry.getKey()) { - lastExtractedPositionUs = Math.max(lastExtractedPositionUs, entry.getValue()); - } - } - if (lastExtractedPositionUs == Long.MIN_VALUE) { - lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US; - } - return lastExtractedPositionUs; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java deleted file mode 100644 index e7224422..00000000 --- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.os.Handler; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.MediaFormatUtil; -import com.google.android.exoplayer.SampleHolder; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * A class that plays a recorded stream without using {@link android.media.MediaExtractor}, since - * all samples are extracted and stored to the permanent storage already. - */ -public class FileSampleExtractor implements SampleExtractor { - private static final String TAG = "FileSampleExtractor"; - private static final boolean DEBUG = false; - - private int mTrackCount; - private boolean mReleased; - - private final List mTrackFormats = new ArrayList<>(); - private final BufferManager mBufferManager; - private final PlaybackBufferListener mBufferListener; - private BufferManager.SampleBuffer mSampleBuffer; - - public FileSampleExtractor(BufferManager bufferManager, PlaybackBufferListener bufferListener) { - mBufferManager = bufferManager; - mBufferListener = bufferListener; - mTrackCount = -1; - } - - @Override - public void maybeThrowError() throws IOException { - // Do nothing. - } - - @Override - public boolean prepare() throws IOException { - List trackFormatList = mBufferManager.readTrackInfoFiles(); - if (trackFormatList == null || trackFormatList.isEmpty()) { - throw new IOException("Cannot find meta files for the recording."); - } - mTrackCount = trackFormatList.size(); - List ids = new ArrayList<>(); - mTrackFormats.clear(); - for (int i = 0; i < mTrackCount; ++i) { - BufferManager.TrackFormat trackFormat = trackFormatList.get(i); - ids.add(trackFormat.trackId); - mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format)); - } - mSampleBuffer = - new RecordingSampleBuffer( - mBufferManager, - mBufferListener, - true, - RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK); - mSampleBuffer.init(ids, mTrackFormats); - return true; - } - - @Override - public List getTrackFormats() { - return mTrackFormats; - } - - @Override - public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) { - outMediaFormatHolder.format = mTrackFormats.get(track); - outMediaFormatHolder.drmInitData = null; - } - - @Override - public void release() { - if (!mReleased) { - if (mSampleBuffer != null) { - try { - mSampleBuffer.release(); - } catch (IOException e) { - // Do nothing. Playback ends now. - } - } - } - mReleased = true; - } - - @Override - public void selectTrack(int index) { - mSampleBuffer.selectTrack(index); - } - - @Override - public void deselectTrack(int index) { - mSampleBuffer.deselectTrack(index); - } - - @Override - public long getBufferedPositionUs() { - return mSampleBuffer.getBufferedPositionUs(); - } - - @Override - public void seekTo(long positionUs) { - mSampleBuffer.seekTo(positionUs); - } - - @Override - public int readSample(int track, SampleHolder sampleHolder) { - return mSampleBuffer.readSample(track, sampleHolder); - } - - @Override - public boolean continueBuffering(long positionUs) { - return mSampleBuffer.continueBuffering(positionUs); - } - - @Override - public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {} -} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java deleted file mode 100644 index a49cbfaf..00000000 --- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java +++ /dev/null @@ -1,672 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.content.Context; -import android.media.AudioFormat; -import android.media.MediaCodec.CryptoException; -import android.media.PlaybackParams; -import android.os.Handler; -import android.support.annotation.IntDef; -import android.view.Surface; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.data.Cea708Data; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; -import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer; -import com.android.tv.tuner.source.TsDataSource; -import com.android.tv.tuner.source.TsDataSourceManager; -import com.android.tv.tuner.tvinput.EventDetector; -import com.android.tv.tuner.tvinput.TunerDebug; -import com.google.android.exoplayer.DummyTrackRenderer; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.ExoPlayer; -import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; -import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; -import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.audio.AudioCapabilities; -import com.google.android.exoplayer.audio.AudioTrack; -import com.google.android.exoplayer.upstream.DataSource; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** MPEG-2 TS stream player implementation using ExoPlayer. */ -public class MpegTsPlayer - implements ExoPlayer.Listener, - MediaCodecVideoTrackRenderer.EventListener, - MpegTsDefaultAudioTrackRenderer.EventListener, - MpegTsMediaCodecAudioTrackRenderer.Ac3EventListener { - private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER; - - /** Interface definition for building specific track renderers. */ - public interface RendererBuilder { - void buildRenderers( - MpegTsPlayer mpegTsPlayer, - DataSource dataSource, - boolean hasSoftwareAudioDecoder, - RendererBuilderCallback callback); - } - - /** Interface definition for {@link RendererBuilder#buildRenderers} to notify the result. */ - public interface RendererBuilderCallback { - void onRenderers(String[][] trackNames, TrackRenderer[] renderers); - - void onRenderersError(Exception e); - } - - /** Interface definition for a callback to be notified of changes in player state. */ - public interface Listener { - void onStateChanged(boolean playWhenReady, int playbackState); - - void onError(Exception e); - - void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio); - - void onDrawnToSurface(MpegTsPlayer player, Surface surface); - - void onAudioUnplayable(); - - void onSmoothTrickplayForceStopped(); - } - - /** Interface definition for a callback to be notified of changes on video display. */ - public interface VideoEventListener { - /** Notifies the caption event. */ - void onEmitCaptionEvent(CaptionEvent event); - - /** Notifies clearing up whole closed caption event. */ - void onClearCaptionEvent(); - - /** Notifies the discovered caption service number. */ - void onDiscoverCaptionServiceNumber(int serviceNumber); - } - - public static final int RENDERER_COUNT = 3; - public static final int MIN_BUFFER_MS = 0; - public static final int MIN_REBUFFER_MS = 500; - - @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT}) - @Retention(RetentionPolicy.SOURCE) - public @interface TrackType {} - - public static final int TRACK_TYPE_VIDEO = 0; - public static final int TRACK_TYPE_AUDIO = 1; - public static final int TRACK_TYPE_TEXT = 2; - - @IntDef({ - RENDERER_BUILDING_STATE_IDLE, - RENDERER_BUILDING_STATE_BUILDING, - RENDERER_BUILDING_STATE_BUILT - }) - @Retention(RetentionPolicy.SOURCE) - public @interface RendererBuildingState {} - - private static final int RENDERER_BUILDING_STATE_IDLE = 1; - private static final int RENDERER_BUILDING_STATE_BUILDING = 2; - private static final int RENDERER_BUILDING_STATE_BUILT = 3; - - private static final float MAX_SMOOTH_TRICKPLAY_SPEED = 9.0f; - private static final float MIN_SMOOTH_TRICKPLAY_SPEED = 0.1f; - - private final RendererBuilder mRendererBuilder; - private final ExoPlayer mPlayer; - private final Handler mMainHandler; - private final AudioCapabilities mAudioCapabilities; - private final TsDataSourceManager mSourceManager; - - private Listener mListener; - @RendererBuildingState private int mRendererBuildingState; - - private Surface mSurface; - private TsDataSource mDataSource; - private InternalRendererBuilderCallback mBuilderCallback; - private TrackRenderer mVideoRenderer; - private TrackRenderer mAudioRenderer; - private Cea708TextTrackRenderer mTextRenderer; - private final Cea708TextTrackRenderer.CcListener mCcListener; - private VideoEventListener mVideoEventListener; - private boolean mTrickplayRunning; - private float mVolume; - - /** - * Creates MPEG2-TS stream player. - * - * @param rendererBuilder the builder of track renderers - * @param handler the handler for the playback events in track renderers - * @param sourceManager the manager for {@link DataSource} - * @param capabilities the {@link AudioCapabilities} of the current device - * @param listener the listener for playback state changes - */ - public MpegTsPlayer( - RendererBuilder rendererBuilder, - Handler handler, - TsDataSourceManager sourceManager, - AudioCapabilities capabilities, - Listener listener) { - mRendererBuilder = rendererBuilder; - mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS); - mPlayer.addListener(this); - mMainHandler = handler; - mAudioCapabilities = capabilities; - mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - mCcListener = new MpegTsCcListener(); - mSourceManager = sourceManager; - mListener = listener; - } - - /** - * Sets the video event listener. - * - * @param videoEventListener the listener for video events - */ - public void setVideoEventListener(VideoEventListener videoEventListener) { - mVideoEventListener = videoEventListener; - } - - /** - * Sets the closed caption service number. - * - * @param captionServiceNumber the service number of CEA-708 closed caption - */ - public void setCaptionServiceNumber(int captionServiceNumber) { - mCaptionServiceNumber = captionServiceNumber; - if (mTextRenderer != null) { - mPlayer.sendMessage( - mTextRenderer, - Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, - mCaptionServiceNumber); - } - } - - /** - * Sets the surface for the player. - * - * @param surface the {@link Surface} to render video - */ - public void setSurface(Surface surface) { - mSurface = surface; - pushSurface(false); - } - - /** Returns the current surface of the player. */ - public Surface getSurface() { - return mSurface; - } - - /** Clears the surface and waits until the surface is being cleaned. */ - public void blockingClearSurface() { - mSurface = null; - pushSurface(true); - } - - /** - * Creates renderers and {@link DataSource} and initializes player. - * - * @param context a {@link Context} instance - * @param channel to play - * @param hasSoftwareAudioDecoder {@code true} if there is connected software decoder - * @param eventListener for program information which will be scanned from MPEG2-TS stream - * @return true when everything is created and initialized well, false otherwise - */ - public boolean prepare( - Context context, - TunerChannel channel, - boolean hasSoftwareAudioDecoder, - EventDetector.EventListener eventListener) { - TsDataSource source = null; - if (channel != null) { - source = mSourceManager.createDataSource(context, channel, eventListener); - if (source == null) { - return false; - } - } - mDataSource = source; - if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILT) { - mPlayer.stop(); - } - if (mBuilderCallback != null) { - mBuilderCallback.cancel(); - } - mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; - mBuilderCallback = new InternalRendererBuilderCallback(); - mRendererBuilder.buildRenderers(this, source, hasSoftwareAudioDecoder, mBuilderCallback); - return true; - } - - /** Returns {@link TsDataSource} which provides MPEG2-TS stream. */ - public TsDataSource getDataSource() { - return mDataSource; - } - - private void onRenderers(TrackRenderer[] renderers) { - mBuilderCallback = null; - for (int i = 0; i < RENDERER_COUNT; i++) { - if (renderers[i] == null) { - // Convert a null renderer to a dummy renderer. - renderers[i] = new DummyTrackRenderer(); - } - } - mVideoRenderer = renderers[TRACK_TYPE_VIDEO]; - mAudioRenderer = renderers[TRACK_TYPE_AUDIO]; - mTextRenderer = (Cea708TextTrackRenderer) renderers[TRACK_TYPE_TEXT]; - mTextRenderer.setCcListener(mCcListener); - mPlayer.sendMessage( - mTextRenderer, Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber); - mRendererBuildingState = RENDERER_BUILDING_STATE_BUILT; - pushSurface(false); - mPlayer.prepare(renderers); - pushTrackSelection(TRACK_TYPE_VIDEO, true); - pushTrackSelection(TRACK_TYPE_AUDIO, true); - pushTrackSelection(TRACK_TYPE_TEXT, true); - } - - private void onRenderersError(Exception e) { - mBuilderCallback = null; - mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - if (mListener != null) { - mListener.onError(e); - } - } - - /** - * Sets the player state to pause or play. - * - * @param playWhenReady sets the player state to being ready to play when {@code true}, sets the - * player state to being paused when {@code false} - */ - public void setPlayWhenReady(boolean playWhenReady) { - mPlayer.setPlayWhenReady(playWhenReady); - stopSmoothTrickplay(false); - } - - /** Returns true, if trickplay is supported. */ - public boolean supportSmoothTrickPlay(float playbackSpeed) { - return playbackSpeed > MIN_SMOOTH_TRICKPLAY_SPEED - && playbackSpeed < MAX_SMOOTH_TRICKPLAY_SPEED; - } - - /** - * Starts trickplay. It'll be reset, if {@link #seekTo} or {@link #setPlayWhenReady} is called. - */ - public void startSmoothTrickplay(PlaybackParams playbackParams) { - SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed())); - mPlayer.setPlayWhenReady(true); - mTrickplayRunning = true; - if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, - MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, - playbackParams.getSpeed()); - } else { - mPlayer.sendMessage( - mAudioRenderer, - MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS, - playbackParams); - } - } - - private void stopSmoothTrickplay(boolean calledBySeek) { - if (mTrickplayRunning) { - mTrickplayRunning = false; - if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, - MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED, - 1.0f); - } else { - mPlayer.sendMessage( - mAudioRenderer, - MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS, - new PlaybackParams().setSpeed(1.0f)); - } - if (!calledBySeek) { - mPlayer.seekTo(mPlayer.getCurrentPosition()); - } - } - } - - /** - * Seeks to the specified position of the current playback. - * - * @param positionMs the specified position in milli seconds. - */ - public void seekTo(long positionMs) { - mPlayer.seekTo(positionMs); - stopSmoothTrickplay(true); - } - - /** Releases the player. */ - public void release() { - if (mDataSource != null) { - mSourceManager.releaseDataSource(mDataSource); - mDataSource = null; - } - if (mBuilderCallback != null) { - mBuilderCallback.cancel(); - mBuilderCallback = null; - } - mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - mSurface = null; - mListener = null; - mPlayer.release(); - } - - /** Returns the current status of the player. */ - public int getPlaybackState() { - if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { - return ExoPlayer.STATE_PREPARING; - } - return mPlayer.getPlaybackState(); - } - - /** Returns {@code true} when the player is prepared to play, {@code false} otherwise. */ - public boolean isPrepared() { - int state = getPlaybackState(); - return state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING; - } - - /** Returns {@code true} when the player is being ready to play, {@code false} otherwise. */ - public boolean isPlaying() { - int state = getPlaybackState(); - return (state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING) - && mPlayer.getPlayWhenReady(); - } - - /** Returns {@code true} when the player is buffering, {@code false} otherwise. */ - public boolean isBuffering() { - return getPlaybackState() == ExoPlayer.STATE_BUFFERING; - } - - /** Returns the current position of the playback in milli seconds. */ - public long getCurrentPosition() { - return mPlayer.getCurrentPosition(); - } - - /** Returns the total duration of the playback. */ - public long getDuration() { - return mPlayer.getDuration(); - } - - /** - * Returns {@code true} when the player is being ready to play, {@code false} when the player is - * paused. - */ - public boolean getPlayWhenReady() { - return mPlayer.getPlayWhenReady(); - } - - /** - * Sets the volume of the audio. - * - * @param volume see also {@link AudioTrack#setVolume(float)} - */ - public void setVolume(float volume) { - mVolume = volume; - if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME, volume); - } else { - mPlayer.sendMessage( - mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume); - } - } - - /** - * Enables or disables audio and closed caption. - * - * @param enable enables the audio and closed caption when {@code true}, disables otherwise. - */ - public void setAudioTrackAndClosedCaption(boolean enable) { - if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, - MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK, - enable ? 1 : 0); - } else { - mPlayer.sendMessage( - mAudioRenderer, - MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, - enable ? mVolume : 0.0f); - } - mPlayer.sendMessage( - mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION, enable); - } - - /** Returns {@code true} when AC3 audio can be played, {@code false} otherwise. */ - public boolean isAc3Playable() { - return mAudioCapabilities != null - && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3); - } - - /** Notifies when the audio cannot be played by the current device. */ - public void onAudioUnplayable() { - if (mListener != null) { - mListener.onAudioUnplayable(); - } - } - - /** Returns {@code true} if the player has any video track, {@code false} otherwise. */ - public boolean hasVideo() { - return mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0; - } - - /** Returns {@code true} if the player has any audio trock, {@code false} otherwise. */ - public boolean hasAudio() { - return mPlayer.getTrackCount(TRACK_TYPE_AUDIO) > 0; - } - - /** Returns the number of tracks exposed by the specified renderer. */ - public int getTrackCount(int rendererIndex) { - return mPlayer.getTrackCount(rendererIndex); - } - - /** Selects a track for the specified renderer. */ - public void setSelectedTrack(int rendererIndex, int trackIndex) { - if (trackIndex >= getTrackCount(rendererIndex)) { - return; - } - mPlayer.setSelectedTrack(rendererIndex, trackIndex); - } - - /** - * Returns the index of the currently selected track for the specified renderer. - * - * @param rendererIndex The index of the renderer. - * @return The selected track. A negative value or a value greater than or equal to the - * renderer's track count indicates that the renderer is disabled. - */ - public int getSelectedTrack(int rendererIndex) { - return mPlayer.getSelectedTrack(rendererIndex); - } - - /** - * Returns the format of a track. - * - * @param rendererIndex The index of the renderer. - * @param trackIndex The index of the track. - * @return The format of the track. - */ - public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) { - return mPlayer.getTrackFormat(rendererIndex, trackIndex); - } - - /** Gets the main handler of the player. */ - /* package */ Handler getMainHandler() { - return mMainHandler; - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int state) { - if (mListener == null) { - return; - } - mListener.onStateChanged(playWhenReady, state); - if (state == ExoPlayer.STATE_READY - && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0 - && playWhenReady) { - MediaFormat format = mPlayer.getTrackFormat(TRACK_TYPE_VIDEO, 0); - mListener.onVideoSizeChanged(format.width, format.height, format.pixelWidthHeightRatio); - } - } - - @Override - public void onPlayerError(ExoPlaybackException exception) { - mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - if (mListener != null) { - mListener.onError(exception); - } - } - - @Override - public void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - if (mListener != null) { - mListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio); - } - } - - @Override - public void onDecoderInitialized( - String decoderName, long elapsedRealtimeMs, long initializationDurationMs) { - // Do nothing. - } - - @Override - public void onDecoderInitializationError(DecoderInitializationException e) { - // Do nothing. - } - - @Override - public void onAudioTrackInitializationError(AudioTrack.InitializationException e) { - if (mListener != null) { - mListener.onAudioUnplayable(); - } - } - - @Override - public void onAudioTrackWriteError(AudioTrack.WriteException e) { - // Do nothing. - } - - @Override - public void onAudioTrackUnderrun( - int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { - // Do nothing. - } - - @Override - public void onCryptoError(CryptoException e) { - // Do nothing. - } - - @Override - public void onPlayWhenReadyCommitted() { - // Do nothing. - } - - @Override - public void onDrawnToSurface(Surface surface) { - if (mListener != null) { - mListener.onDrawnToSurface(this, surface); - } - } - - @Override - public void onDroppedFrames(int count, long elapsed) { - TunerDebug.notifyVideoFrameDrop(count, elapsed); - if (mTrickplayRunning && mListener != null) { - mListener.onSmoothTrickplayForceStopped(); - } - } - - @Override - public void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { - if (mTrickplayRunning && mListener != null) { - mListener.onSmoothTrickplayForceStopped(); - } - } - - private void pushSurface(boolean blockForSurfacePush) { - if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) { - return; - } - - if (blockForSurfacePush) { - mPlayer.blockingSendMessage( - mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface); - } else { - mPlayer.sendMessage( - mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface); - } - } - - private void pushTrackSelection(@TrackType int type, boolean allowRendererEnable) { - if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) { - return; - } - mPlayer.setSelectedTrack(type, allowRendererEnable ? 0 : -1); - } - - private class MpegTsCcListener implements Cea708TextTrackRenderer.CcListener { - - @Override - public void emitEvent(CaptionEvent captionEvent) { - if (mVideoEventListener != null) { - mVideoEventListener.onEmitCaptionEvent(captionEvent); - } - } - - @Override - public void clearCaption() { - if (mVideoEventListener != null) { - mVideoEventListener.onClearCaptionEvent(); - } - } - - @Override - public void discoverServiceNumber(int serviceNumber) { - if (mVideoEventListener != null) { - mVideoEventListener.onDiscoverCaptionServiceNumber(serviceNumber); - } - } - } - - private class InternalRendererBuilderCallback implements RendererBuilderCallback { - private boolean canceled; - - public void cancel() { - canceled = true; - } - - @Override - public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) { - if (!canceled) { - MpegTsPlayer.this.onRenderers(renderers); - } - } - - @Override - public void onRenderersError(Exception e) { - if (!canceled) { - MpegTsPlayer.this.onRenderersError(e); - } - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java deleted file mode 100644 index c7f5b333..00000000 --- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.content.Context; -import com.android.tv.Features; -import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder; -import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback; -import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.upstream.DataSource; - -/** Builder for renderer objects for {@link MpegTsPlayer}. */ -public class MpegTsRendererBuilder implements RendererBuilder { - private final Context mContext; - private final BufferManager mBufferManager; - private final PlaybackBufferListener mBufferListener; - - public MpegTsRendererBuilder( - Context context, BufferManager bufferManager, PlaybackBufferListener bufferListener) { - mContext = context; - mBufferManager = bufferManager; - mBufferListener = bufferListener; - } - - @Override - public void buildRenderers( - MpegTsPlayer mpegTsPlayer, - DataSource dataSource, - boolean mHasSoftwareAudioDecoder, - RendererBuilderCallback callback) { - // Build the video and audio renderers. - SampleExtractor extractor = - dataSource == null - ? new MpegTsSampleExtractor(mBufferManager, mBufferListener) - : new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener); - SampleSource sampleSource = new MpegTsSampleSource(extractor); - MpegTsVideoTrackRenderer videoRenderer = - new MpegTsVideoTrackRenderer( - mContext, sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer); - // TODO: Only using MpegTsDefaultAudioTrackRenderer for A/V sync issue. We will use - // {@link MpegTsMediaCodecAudioTrackRenderer} when we use ExoPlayer's extractor. - TrackRenderer audioRenderer = - new MpegTsDefaultAudioTrackRenderer( - sampleSource, - MediaCodecSelector.DEFAULT, - mpegTsPlayer.getMainHandler(), - mpegTsPlayer, - mHasSoftwareAudioDecoder, - !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext)); - Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource); - - TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT]; - renderers[MpegTsPlayer.TRACK_TYPE_VIDEO] = videoRenderer; - renderers[MpegTsPlayer.TRACK_TYPE_AUDIO] = audioRenderer; - renderers[MpegTsPlayer.TRACK_TYPE_TEXT] = textRenderer; - callback.onRenderers(null, renderers); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java deleted file mode 100644 index 593b576e..00000000 --- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer; - -import android.net.Uri; -import android.os.Handler; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.SamplePool; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.util.MimeTypes; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** Extracts samples from {@link DataSource} for MPEG-TS streams. */ -public final class MpegTsSampleExtractor implements SampleExtractor { - public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708"; - - private static final int CC_BUFFER_SIZE_IN_BYTES = 9600 / 8; - - private final SampleExtractor mSampleExtractor; - private final List mTrackFormats = new ArrayList<>(); - private final List mReachedEos = new ArrayList<>(); - private int mVideoTrackIndex; - private final SamplePool mCcSamplePool = new SamplePool(); - private final List mPendingCcSamples = new LinkedList<>(); - - private int mCea708TextTrackIndex; - private boolean mCea708TextTrackSelected; - - private CcParser mCcParser; - - private void init() { - mVideoTrackIndex = -1; - mCea708TextTrackIndex = -1; - mCea708TextTrackSelected = false; - } - - /** - * Creates MpegTsSampleExtractor for {@link DataSource}. - * - * @param source the {@link DataSource} to extract from - * @param bufferManager the manager for reading & writing samples backed by physical storage - * @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status - * change - */ - public MpegTsSampleExtractor( - DataSource source, BufferManager bufferManager, PlaybackBufferListener bufferListener) { - mSampleExtractor = - new ExoPlayerSampleExtractor( - Uri.EMPTY, source, bufferManager, bufferListener, false); - init(); - } - - /** - * Creates MpegTsSampleExtractor for a recorded program. - * - * @param bufferManager the samples provider which is stored in physical storage - * @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status - * change - */ - public MpegTsSampleExtractor( - BufferManager bufferManager, PlaybackBufferListener bufferListener) { - mSampleExtractor = new FileSampleExtractor(bufferManager, bufferListener); - init(); - } - - @Override - public void maybeThrowError() throws IOException { - if (mSampleExtractor != null) { - mSampleExtractor.maybeThrowError(); - } - } - - @Override - public boolean prepare() throws IOException { - if (!mSampleExtractor.prepare()) { - return false; - } - List formats = mSampleExtractor.getTrackFormats(); - int trackCount = formats.size(); - mTrackFormats.clear(); - mReachedEos.clear(); - - for (int i = 0; i < trackCount; ++i) { - mTrackFormats.add(formats.get(i)); - mReachedEos.add(false); - String mime = formats.get(i).mimeType; - if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) { - mVideoTrackIndex = i; - if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) { - mCcParser = new Mpeg2CcParser(); - } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { - mCcParser = new H264CcParser(); - } - } - } - - if (mVideoTrackIndex != -1) { - mCea708TextTrackIndex = trackCount; - } - if (mCea708TextTrackIndex >= 0) { - mTrackFormats.add( - MediaFormat.createTextFormat( - null, MIMETYPE_TEXT_CEA_708, 0, mTrackFormats.get(0).durationUs, "")); - } - return true; - } - - @Override - public List getTrackFormats() { - return mTrackFormats; - } - - @Override - public void selectTrack(int index) { - if (index == mCea708TextTrackIndex) { - mCea708TextTrackSelected = true; - return; - } - mSampleExtractor.selectTrack(index); - } - - @Override - public void deselectTrack(int index) { - if (index == mCea708TextTrackIndex) { - mCea708TextTrackSelected = false; - return; - } - mSampleExtractor.deselectTrack(index); - } - - @Override - public long getBufferedPositionUs() { - return mSampleExtractor.getBufferedPositionUs(); - } - - @Override - public void seekTo(long positionUs) { - mSampleExtractor.seekTo(positionUs); - for (SampleHolder holder : mPendingCcSamples) { - mCcSamplePool.releaseSample(holder); - } - mPendingCcSamples.clear(); - } - - @Override - public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) { - if (track != mCea708TextTrackIndex) { - mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder); - } - } - - @Override - public int readSample(int track, SampleHolder sampleHolder) { - if (track == mCea708TextTrackIndex) { - if (mCea708TextTrackSelected && !mPendingCcSamples.isEmpty()) { - SampleHolder holder = mPendingCcSamples.remove(0); - holder.data.flip(); - sampleHolder.timeUs = holder.timeUs; - sampleHolder.data.put(holder.data); - mCcSamplePool.releaseSample(holder); - return SampleSource.SAMPLE_READ; - } else { - return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex) - ? SampleSource.END_OF_STREAM - : SampleSource.NOTHING_READ; - } - } - - int result = mSampleExtractor.readSample(track, sampleHolder); - switch (result) { - case SampleSource.END_OF_STREAM: - { - mReachedEos.set(track, true); - break; - } - case SampleSource.SAMPLE_READ: - { - if (mCea708TextTrackSelected - && track == mVideoTrackIndex - && sampleHolder.data != null) { - mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs); - } - break; - } - } - return result; - } - - @Override - public void release() { - mSampleExtractor.release(); - mVideoTrackIndex = -1; - mCea708TextTrackIndex = -1; - mCea708TextTrackSelected = false; - } - - @Override - public boolean continueBuffering(long positionUs) { - return mSampleExtractor.continueBuffering(positionUs); - } - - @Override - public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {} - - private abstract class CcParser { - // Interim buffer for reduce direct access to ByteBuffer which is expensive. Using - // relatively small buffer size in order to minimize memory footprint increase. - protected final byte[] mBuffer = new byte[1024]; - - abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs); - - protected int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) { - // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9. - int pos = offset; - if (pos + 2 >= buffer.position()) { - return offset; - } - boolean processCcDataFlag = (buffer.get(pos) & 64) != 0; - int ccCount = buffer.get(pos) & 0x1f; - pos += 2; - if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) { - return offset; - } - SampleHolder holder = mCcSamplePool.acquireSample(CC_BUFFER_SIZE_IN_BYTES); - for (int i = 0; i < 3 * ccCount; i++) { - holder.data.put(buffer.get(pos++)); - } - holder.timeUs = presentationTimeUs; - mPendingCcSamples.add(holder); - return pos; - } - } - - private class Mpeg2CcParser extends CcParser { - private static final int PATTERN_LENGTH = 9; - - @Override - public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) { - int totalSize = buffer.position(); - // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with - // overlapping to handle the case that the pattern exists in the boundary. - for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) { - buffer.position(i); - int size = Math.min(totalSize - i, mBuffer.length); - buffer.get(mBuffer, 0, size); - int j = 0; - while (j < size - PATTERN_LENGTH) { - // Find the start prefix code of private user data. - if (mBuffer[j] == 0 - && mBuffer[j + 1] == 0 - && mBuffer[j + 2] == 1 - && (mBuffer[j + 3] & 0xff) == 0xb2) { - // ATSC closed caption data embedded in MPEG2VIDEO stream has 'GA94' user - // identifier and user data type code 3. - if (mBuffer[j + 4] == 'G' - && mBuffer[j + 5] == 'A' - && mBuffer[j + 6] == '9' - && mBuffer[j + 7] == '4' - && mBuffer[j + 8] == 3) { - j = - parseClosedCaption( - buffer, - i + j + PATTERN_LENGTH, - presentationTimeUs) - - i; - } else { - j += PATTERN_LENGTH; - } - } else { - ++j; - } - } - } - buffer.position(totalSize); - } - } - - private class H264CcParser extends CcParser { - private static final int PATTERN_LENGTH = 14; - - @Override - public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) { - int totalSize = buffer.position(); - // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with - // overlapping to handle the case that the pattern exists in the boundary. - for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) { - buffer.position(i); - int size = Math.min(totalSize - i, mBuffer.length); - buffer.get(mBuffer, 0, size); - int j = 0; - while (j < size - PATTERN_LENGTH) { - // Find the start prefix code of a NAL Unit. - if (mBuffer[j] == 0 && mBuffer[j + 1] == 0 && mBuffer[j + 2] == 1) { - int nalType = mBuffer[j + 3] & 0x1f; - int payloadType = mBuffer[j + 4] & 0xff; - - // ATSC closed caption data embedded in H264 private user data has NAL type - // 6, payload type 4, and 'GA94' user identifier for ATSC. - if (nalType == 6 - && payloadType == 4 - && mBuffer[j + 9] == 'G' - && mBuffer[j + 10] == 'A' - && mBuffer[j + 11] == '9' - && mBuffer[j + 12] == '4') { - j = - parseClosedCaption( - buffer, - i + j + PATTERN_LENGTH, - presentationTimeUs) - - i; - } else { - j += 7; - } - } else { - ++j; - } - } - } - buffer.position(totalSize); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java deleted file mode 100644 index 3b5d1011..00000000 --- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.tuner.exoplayer; - -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.SampleSource.SampleSourceReader; -import com.google.android.exoplayer.util.Assertions; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** {@link SampleSource} that extracts sample data using a {@link SampleExtractor}. */ -public final class MpegTsSampleSource implements SampleSource, SampleSourceReader { - - private static final int TRACK_STATE_DISABLED = 0; - private static final int TRACK_STATE_ENABLED = 1; - private static final int TRACK_STATE_FORMAT_SENT = 2; - - private final SampleExtractor mSampleExtractor; - private final List mTrackStates = new ArrayList<>(); - private final List mPendingDiscontinuities = new ArrayList<>(); - - private boolean mPrepared; - private IOException mPreparationError; - private int mRemainingReleaseCount; - - private long mLastSeekPositionUs; - private long mPendingSeekPositionUs; - - /** - * Creates a new sample source that extracts samples using {@code mSampleExtractor}. - * - * @param sampleExtractor a sample extractor for accessing media samples - */ - public MpegTsSampleSource(SampleExtractor sampleExtractor) { - mSampleExtractor = Assertions.checkNotNull(sampleExtractor); - } - - @Override - public SampleSourceReader register() { - mRemainingReleaseCount++; - return this; - } - - @Override - public boolean prepare(long positionUs) { - if (!mPrepared) { - if (mPreparationError != null) { - return false; - } - try { - if (mSampleExtractor.prepare()) { - int trackCount = mSampleExtractor.getTrackFormats().size(); - mTrackStates.clear(); - mPendingDiscontinuities.clear(); - for (int i = 0; i < trackCount; ++i) { - mTrackStates.add(i, TRACK_STATE_DISABLED); - mPendingDiscontinuities.add(i, false); - } - mPrepared = true; - } else { - return false; - } - } catch (IOException e) { - mPreparationError = e; - return false; - } - } - return true; - } - - @Override - public int getTrackCount() { - Assertions.checkState(mPrepared); - return mSampleExtractor.getTrackFormats().size(); - } - - @Override - public MediaFormat getFormat(int track) { - Assertions.checkState(mPrepared); - return mSampleExtractor.getTrackFormats().get(track); - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(mPrepared); - Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED); - mTrackStates.set(track, TRACK_STATE_ENABLED); - mSampleExtractor.selectTrack(track); - seekToUsInternal(positionUs, positionUs != 0); - } - - @Override - public void disable(int track) { - Assertions.checkState(mPrepared); - Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); - mSampleExtractor.deselectTrack(track); - mPendingDiscontinuities.set(track, false); - mTrackStates.set(track, TRACK_STATE_DISABLED); - } - - @Override - public boolean continueBuffering(int track, long positionUs) { - return mSampleExtractor.continueBuffering(positionUs); - } - - @Override - public long readDiscontinuity(int track) { - if (mPendingDiscontinuities.get(track)) { - mPendingDiscontinuities.set(track, false); - return mLastSeekPositionUs; - } - return NO_DISCONTINUITY; - } - - @Override - public int readData( - int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) { - Assertions.checkState(mPrepared); - Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); - if (mPendingDiscontinuities.get(track)) { - return NOTHING_READ; - } - if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) { - mSampleExtractor.getTrackMediaFormat(track, formatHolder); - mTrackStates.set(track, TRACK_STATE_FORMAT_SENT); - return FORMAT_READ; - } - - mPendingSeekPositionUs = C.UNKNOWN_TIME_US; - return mSampleExtractor.readSample(track, sampleHolder); - } - - @Override - public void maybeThrowError() throws IOException { - if (mPreparationError != null) { - throw mPreparationError; - } - if (mSampleExtractor != null) { - mSampleExtractor.maybeThrowError(); - } - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(mPrepared); - seekToUsInternal(positionUs, false); - } - - @Override - public long getBufferedPositionUs() { - Assertions.checkState(mPrepared); - return mSampleExtractor.getBufferedPositionUs(); - } - - @Override - public void release() { - Assertions.checkState(mRemainingReleaseCount > 0); - if (--mRemainingReleaseCount == 0) { - mSampleExtractor.release(); - } - } - - private void seekToUsInternal(long positionUs, boolean force) { - // Unless forced, avoid duplicate calls to the underlying extractor's seek method - // in the case that there have been no interleaving calls to readSample. - if (force || mPendingSeekPositionUs != positionUs) { - mLastSeekPositionUs = positionUs; - mPendingSeekPositionUs = positionUs; - mSampleExtractor.seekTo(positionUs); - for (int i = 0; i < mTrackStates.size(); ++i) { - if (mTrackStates.get(i) != TRACK_STATE_DISABLED) { - mPendingDiscontinuities.set(i, true); - } - } - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java deleted file mode 100644 index c4400b47..00000000 --- a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.android.tv.tuner.exoplayer; - -import android.content.Context; -import android.media.MediaCodec; -import android.os.Handler; -import android.util.Log; -import com.android.tv.common.feature.CommonFeatures; -import com.google.android.exoplayer.DecoderInfo; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.MediaCodecUtil; -import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.MediaSoftwareCodecUtil; -import com.google.android.exoplayer.SampleSource; -import java.lang.reflect.Field; - -/** MPEG-2 TS video track renderer */ -public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { - private static final String TAG = "MpegTsVideoTrackRender"; - - private static final int VIDEO_PLAYBACK_DEADLINE_IN_MS = 5000; - // If DROPPED_FRAMES_NOTIFICATION_THRESHOLD frames are consecutively dropped, it'll be notified. - private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10; - private static final int MIN_HD_HEIGHT = 720; - private static final String MIMETYPE_MPEG2 = "video/mpeg2"; - private static Field sRenderedFirstFrameField; - - private final boolean mIsSwCodecEnabled; - private boolean mCodecIsSwPreferred; - private boolean mSetRenderedFirstFrame; - - static { - // Remove the reflection below once b/31223646 is resolved. - try { - sRenderedFirstFrameField = - MediaCodecVideoTrackRenderer.class.getDeclaredField("renderedFirstFrame"); - sRenderedFirstFrameField.setAccessible(true); - } catch (NoSuchFieldException e) { - // Null-checking for {@code sRenderedFirstFrameField} will do the error handling. - } - } - - public MpegTsVideoTrackRenderer( - Context context, - SampleSource source, - Handler handler, - MediaCodecVideoTrackRenderer.EventListener listener) { - super( - context, - source, - MediaCodecSelector.DEFAULT, - MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, - VIDEO_PLAYBACK_DEADLINE_IN_MS, - handler, - listener, - DROPPED_FRAMES_NOTIFICATION_THRESHOLD); - mIsSwCodecEnabled = CommonFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context); - } - - @Override - protected DecoderInfo getDecoderInfo( - MediaCodecSelector codecSelector, String mimeType, boolean requiresSecureDecoder) - throws MediaCodecUtil.DecoderQueryException { - try { - if (mIsSwCodecEnabled && mCodecIsSwPreferred) { - DecoderInfo swCodec = - MediaSoftwareCodecUtil.getSoftwareDecoderInfo( - mimeType, requiresSecureDecoder); - if (swCodec != null) { - return swCodec; - } - } - } catch (MediaSoftwareCodecUtil.DecoderQueryException e) { - } - return super.getDecoderInfo(codecSelector, mimeType, requiresSecureDecoder); - } - - @Override - protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { - mCodecIsSwPreferred = - MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType) - && holder.format.height < MIN_HD_HEIGHT; - super.onInputFormatChanged(holder); - } - - @Override - protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { - super.onDiscontinuity(positionUs); - // Disabling pre-rendering of the first frame in order to avoid a frozen picture when - // starting the playback. We do this only once, when the renderer is enabled at first, since - // we need to pre-render the frame in advance when we do trickplay backed by seeking. - if (!mSetRenderedFirstFrame) { - setRenderedFirstFrame(true); - mSetRenderedFirstFrame = true; - } - } - - private void setRenderedFirstFrame(boolean renderedFirstFrame) { - if (sRenderedFirstFrameField != null) { - try { - sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame); - } catch (IllegalAccessException e) { - Log.w( - TAG, - "renderedFirstFrame is not accessible. Playback may start with a frozen" - + " picture."); - } - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java b/src/com/android/tv/tuner/exoplayer/SampleExtractor.java deleted file mode 100644 index 256aea92..00000000 --- a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.tuner.exoplayer; - -import android.os.Handler; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import java.io.IOException; -import java.util.List; - -/** - * Extractor for reading track metadata and samples stored in tracks. - * - *

Call {@link #prepare} until it returns {@code true}, then access track metadata via {@link - * #getTrackFormats} and {@link #getTrackMediaFormat}. - * - *

Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected - * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample data - * or seeking. Initially, all tracks are deselected. - * - *

Call {@link #release()} when the extractor is no longer needed to free resources. - */ -public interface SampleExtractor { - - /** - * If the extractor is currently having difficulty preparing or loading samples, then this - * method throws the underlying error. Otherwise does nothing. - * - * @throws IOException The underlying error. - */ - void maybeThrowError() throws IOException; - - /** - * Prepares the extractor for reading track metadata and samples. - * - * @return whether the source is ready; if {@code false}, this method must be called again. - * @throws IOException thrown if the source can't be read - */ - boolean prepare() throws IOException; - - /** Returns track information about all tracks that can be selected. */ - List getTrackFormats(); - - /** Selects the track at {@code index} for reading sample data. */ - void selectTrack(int index); - - /** Deselects the track at {@code index}, so no more samples will be read from that track. */ - void deselectTrack(int index); - - /** - * Returns an estimate of the position up to which data is buffered. - * - *

This method should not be called until after the extractor has been successfully prepared. - * - * @return an estimate of the absolute position in microseconds up to which data is buffered, or - * {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or - * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available. - */ - long getBufferedPositionUs(); - - /** - * Seeks to the specified time in microseconds. - * - *

This method should not be called until after the extractor has been successfully prepared. - * - * @param positionUs the seek position in microseconds - */ - void seekTo(long positionUs); - - /** Stores the {@link MediaFormat} of {@code track}. */ - void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder); - - /** - * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, - * returning {@link SampleSource#SAMPLE_READ} if it is available. - * - *

Advances to the next sample if a sample was read. - * - * @param track the index of the track from which to read a sample - * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is - * returned - * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or - * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or - * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not - * loaded. - */ - int readSample(int track, SampleHolder sampleHolder); - - /** Releases resources associated with this extractor. */ - void release(); - - /** Indicates to the source that it should still be buffering data. */ - boolean continueBuffering(long positionUs); - - /** - * Sets OnCompletionListener for notifying the completion of SampleExtractor. - * - * @param listener the OnCompletionListener - * @param handler the {@link Handler} for {@link Handler#post(Runnable)} of OnCompletionListener - */ - void setOnCompletionListener(OnCompletionListener listener, Handler handler); - - /** The listener for SampleExtractor being completed. */ - interface OnCompletionListener { - - /** - * Called when sample extraction is completed. - * - * @param result {@code true} when the extractor is finished without an error, {@code false} - * otherwise (storage error, weak signal, being reached at EoS prematurely, etc.) - * @param lastExtractedPositionUs the last extracted position when extractor is completed - */ - void onCompletion(boolean result, long lastExtractedPositionUs); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java deleted file mode 100644 index 13eabc3a..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.os.SystemClock; -import com.android.tv.common.SoftPreconditions; - -/** - * Copy of {@link com.google.android.exoplayer.MediaClock}. - * - *

A simple clock for tracking the progression of media time. The clock can be started, stopped - * and its time can be set and retrieved. When started, this clock is based on {@link - * SystemClock#elapsedRealtime()}. - */ -/* package */ class AudioClock { - private boolean mStarted; - - /** The media time when the clock was last set or stopped. */ - private long mPositionUs; - - /** - * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs} when - * the clock was last set or mStarted. - */ - private long mDeltaUs; - - private float mPlaybackSpeed = 1.0f; - private long mDeltaUpdatedTimeUs; - - /** Starts the clock. Does nothing if the clock is already started. */ - public void start() { - if (!mStarted) { - mStarted = true; - mDeltaUs = elapsedRealtimeMinus(mPositionUs); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - } - } - - /** Stops the clock. Does nothing if the clock is already stopped. */ - public void stop() { - if (mStarted) { - mPositionUs = elapsedRealtimeMinus(mDeltaUs); - mStarted = false; - } - } - - /** @param timeUs The position to set in microseconds. */ - public void setPositionUs(long timeUs) { - this.mPositionUs = timeUs; - mDeltaUs = elapsedRealtimeMinus(timeUs); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - } - - /** @return The current position in microseconds. */ - public long getPositionUs() { - if (!mStarted) { - return mPositionUs; - } - if (mPlaybackSpeed != 1.0f) { - long elapsedTimeFromPlaybackSpeedChanged = - SystemClock.elapsedRealtime() * 1000 - mDeltaUpdatedTimeUs; - return elapsedRealtimeMinus(mDeltaUs) - + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged); - } else { - return elapsedRealtimeMinus(mDeltaUs); - } - } - - /** Sets playback speed. {@code speed} should be positive. */ - public void setPlaybackSpeed(float speed) { - SoftPreconditions.checkState(speed > 0); - mDeltaUs = elapsedRealtimeMinus(getPositionUs()); - mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000; - mPlaybackSpeed = speed; - } - - private long elapsedRealtimeMinus(long toSubtractUs) { - return SystemClock.elapsedRealtime() * 1000 - toSubtractUs; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java deleted file mode 100644 index fa489883..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import java.nio.ByteBuffer; - -/** A base class for audio decoders. */ -public abstract class AudioDecoder { - - /** - * Decodes an audio sample. - * - * @param sampleHolder a holder that contains the sample data and corresponding metadata - */ - public abstract void decode(SampleHolder sampleHolder); - - /** Returns a decoded sample from decoder. */ - public abstract ByteBuffer getDecodedSample(); - - /** Returns the presentation time for the decoded sample. */ - public abstract long getDecodedTimeUs(); - - /** - * Clear previous decode state if any. Prepares to decode samples of the specified encoding. - * This method should be called before using decode. - * - * @param mime audio encoding - */ - public abstract void resetDecoderState(String mimeType); - - /** Releases all the resource. */ - public abstract void release(); - - /** - * Init decoder if needed. - * - * @param format the format used to initialize decoder - */ - public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException { - // Do nothing. - } - - /** Returns input buffer that will be used in decoder. */ - public ByteBuffer getInputBuffer() { - return null; - } - - /** Returns the output format. */ - public android.media.MediaFormat getOutputFormat() { - return null; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java deleted file mode 100644 index 28389017..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.os.SystemClock; -import android.util.Log; -import android.util.Pair; -import com.google.android.exoplayer.util.MimeTypes; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** Monitors the rendering position of {@link AudioTrack}. */ -public class AudioTrackMonitor { - private static final String TAG = "AudioTrackMonitor"; - private static final boolean DEBUG = false; - - // For fetched audio samples - private final ArrayList> mPtsList = new ArrayList<>(); - private final Set mSampleSize = new HashSet<>(); - private final Set mCurSampleSize = new HashSet<>(); - private final Set mHeader = new HashSet<>(); - - private long mExpireMs; - private long mDuration; - private long mSampleCount; - private long mTotalCount; - private long mStartMs; - - private boolean mIsMp2; - - private void flush() { - mExpireMs += mDuration; - mSampleCount = 0; - mCurSampleSize.clear(); - mPtsList.clear(); - } - - /** - * Resets and initializes {@link AudioTrackMonitor}. - * - * @param duration the frequency of monitoring in milliseconds - */ - public void reset(long duration) { - mExpireMs = SystemClock.elapsedRealtime(); - mDuration = duration; - mTotalCount = 0; - mStartMs = 0; - mSampleSize.clear(); - mHeader.clear(); - flush(); - } - - public void setEncoding(String mime) { - mIsMp2 = MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mime); - } - - /** - * Adds an audio sample information for monitoring. - * - * @param pts the presentation timestamp of the sample - * @param sampleSize the size in bytes of the sample - * @param header the bitrate & sampling information header of the sample - */ - public void addPts(long pts, int sampleSize, int header) { - mTotalCount++; - mSampleCount++; - mSampleSize.add(sampleSize); - mHeader.add(header); - mCurSampleSize.add(sampleSize); - if (mTotalCount == 1) { - mStartMs = SystemClock.elapsedRealtime(); - } - if (mPtsList.isEmpty() || mPtsList.get(mPtsList.size() - 1).first != pts) { - mPtsList.add(Pair.create(pts, 1)); - return; - } - Pair pair = mPtsList.get(mPtsList.size() - 1); - mPtsList.set(mPtsList.size() - 1, Pair.create(pair.first, pair.second + 1)); - } - - /** - * Logs if interested events are present. - * - *

Periodic logging is not enabled in release mode in order to avoid verbose logging. - */ - public void maybeLog() { - long now = SystemClock.elapsedRealtime(); - if (mExpireMs != 0 && now >= mExpireMs) { - if (DEBUG) { - long unitDuration = - mIsMp2 - ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US - : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US; - long sampleDuration = (mTotalCount - 1) * unitDuration / 1000; - long totalDuration = now - mStartMs; - StringBuilder ptsBuilder = new StringBuilder(); - ptsBuilder - .append("PTS received ") - .append(mSampleCount) - .append(", ") - .append(totalDuration - sampleDuration) - .append(' '); - - for (Pair pair : mPtsList) { - ptsBuilder - .append('[') - .append(pair.first) - .append(':') - .append(pair.second) - .append("], "); - } - Log.d(TAG, ptsBuilder.toString()); - } - if (DEBUG || mCurSampleSize.size() > 1) { - Log.d( - TAG, - "PTS received sample size: " - + String.valueOf(mSampleSize) - + mCurSampleSize - + mHeader); - } - flush(); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java deleted file mode 100644 index 7446c923..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.media.MediaFormat; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.audio.AudioTrack; -import java.nio.ByteBuffer; - -/** - * {@link AudioTrack} wrapper class for trickplay operations including FF/RW. FF/RW trickplay - * operations do not need framework {@link AudioTrack}. This wrapper class will do nothing in - * disabled status for those operations. - */ -public class AudioTrackWrapper { - private static final int PCM16_FRAME_BYTES = 2; - private static final int AC3_FRAMES_IN_ONE_SAMPLE = 1536; - private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK = - MpegTsDefaultAudioTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK; - private final AudioTrack mAudioTrack = new AudioTrack(); - private int mAudioSessionID; - private boolean mIsEnabled; - - AudioTrackWrapper() { - mIsEnabled = true; - } - - public void resetSessionId() { - mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET; - } - - public boolean isInitialized() { - return mIsEnabled && mAudioTrack.isInitialized(); - } - - public void restart() { - if (mAudioTrack.isInitialized()) { - mAudioTrack.release(); - } - mIsEnabled = true; - resetSessionId(); - } - - public void release() { - if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { - mAudioTrack.release(); - } - } - - public void initialize() throws AudioTrack.InitializationException { - if (!mIsEnabled) { - return; - } - if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) { - mAudioTrack.initialize(mAudioSessionID); - } else { - mAudioSessionID = mAudioTrack.initialize(); - } - } - - public void reset() { - if (!mIsEnabled) { - return; - } - mAudioTrack.reset(); - } - - public boolean isEnded() { - return !mIsEnabled || !mAudioTrack.hasPendingData(); - } - - public boolean isReady() { - // In the case of not playing actual audio data, Audio track is always ready. - return !mIsEnabled || mAudioTrack.hasPendingData(); - } - - public void play() { - if (!mIsEnabled) { - return; - } - mAudioTrack.play(); - } - - public void pause() { - if (!mIsEnabled) { - return; - } - mAudioTrack.pause(); - } - - public void setVolume(float volume) { - if (!mIsEnabled) { - return; - } - mAudioTrack.setVolume(volume); - } - - public void reconfigure(MediaFormat format, int audioBufferSize) { - if (!mIsEnabled || format == null) { - return; - } - String mimeType = format.getString(MediaFormat.KEY_MIME); - int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); - int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); - int pcmEncoding; - try { - pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING); - } catch (Exception e) { - pcmEncoding = C.ENCODING_PCM_16BIT; - } - // TODO: Handle non-AC3. - if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) { - // Workarounds b/25955476. - // Since all devices and platforms does not support passthrough for non-stereo AC3, - // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode. - // In other words, the channel count should be always 2. - channelCount = 2; - } - if (MediaFormat.MIMETYPE_AUDIO_RAW.equalsIgnoreCase(mimeType)) { - audioBufferSize = - channelCount - * PCM16_FRAME_BYTES - * AC3_FRAMES_IN_ONE_SAMPLE - * BUFFERED_SAMPLES_IN_AUDIOTRACK; - } - mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, audioBufferSize); - } - - public void handleDiscontinuity() { - if (!mIsEnabled) { - return; - } - mAudioTrack.handleDiscontinuity(); - } - - public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs) - throws AudioTrack.WriteException { - if (!mIsEnabled) { - return AudioTrack.RESULT_BUFFER_CONSUMED; - } - return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs); - } - - public void setStatus(boolean enable) { - if (enable == mIsEnabled) { - return; - } - mAudioTrack.reset(); - mIsEnabled = enable; - } - - public boolean isEnabled() { - return mIsEnabled; - } - - // This should be used only in case of being enabled. - public long getCurrentPositionUs(boolean isEnded) { - return mAudioTrack.getCurrentPositionUs(isEnded); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java deleted file mode 100644 index 80f91ebd..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.media.MediaCodec; -import android.util.Log; -import com.google.android.exoplayer.CodecCounters; -import com.google.android.exoplayer.DecoderInfo; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.MediaCodecUtil; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/** A decoder to use MediaCodec for decoding audio stream. */ -public class MediaCodecAudioDecoder extends AudioDecoder { - private static final String TAG = "MediaCodecAudioDecoder"; - - public static final int INDEX_INVALID = -1; - - private final CodecCounters mCodecCounters; - private final MediaCodecSelector mSelector; - - private MediaCodec mCodec; - private MediaCodec.BufferInfo mOutputBufferInfo; - private ByteBuffer mMediaCodecOutputBuffer; - private ArrayList mDecodeOnlyPresentationTimestamps; - private boolean mWaitingForFirstSyncFrame; - private boolean mIsNewIndex; - private int mInputIndex; - private int mOutputIndex; - - /** Creates a MediaCodec based audio decoder. */ - public MediaCodecAudioDecoder(MediaCodecSelector selector) { - mSelector = selector; - mOutputBufferInfo = new MediaCodec.BufferInfo(); - mCodecCounters = new CodecCounters(); - mDecodeOnlyPresentationTimestamps = new ArrayList<>(); - } - - /** Returns {@code true} if there is decoder for {@code mimeType}. */ - public static boolean supportMimeType(MediaCodecSelector selector, String mimeType) { - if (selector == null) { - return false; - } - return getDecoderInfo(selector, mimeType) != null; - } - - private static DecoderInfo getDecoderInfo(MediaCodecSelector selector, String mimeType) { - try { - return selector.getDecoderInfo(mimeType, false); - } catch (MediaCodecUtil.DecoderQueryException e) { - Log.e(TAG, "Select decoder error:" + e); - return null; - } - } - - private boolean shouldInitCodec(MediaFormat format) { - return format != null && mCodec == null; - } - - @Override - public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException { - if (!shouldInitCodec(format)) { - return; - } - - String mimeType = format.mimeType; - DecoderInfo decoderInfo = getDecoderInfo(mSelector, mimeType); - if (decoderInfo == null) { - Log.i(TAG, "There is not decoder found for " + mimeType); - return; - } - - String codecName = decoderInfo.name; - try { - mCodec = MediaCodec.createByCodecName(codecName); - mCodec.configure(format.getFrameworkMediaFormatV16(), null, null, 0); - mCodec.start(); - } catch (Exception e) { - Log.e(TAG, "Failed when configure or start codec:" + e); - throw new ExoPlaybackException(e); - } - mInputIndex = INDEX_INVALID; - mOutputIndex = INDEX_INVALID; - mWaitingForFirstSyncFrame = true; - mCodecCounters.codecInitCount++; - } - - @Override - public void resetDecoderState(String mimeType) { - if (mCodec == null) { - return; - } - mInputIndex = INDEX_INVALID; - mOutputIndex = INDEX_INVALID; - mDecodeOnlyPresentationTimestamps.clear(); - mCodec.flush(); - mWaitingForFirstSyncFrame = true; - } - - @Override - public void release() { - if (mCodec != null) { - mDecodeOnlyPresentationTimestamps.clear(); - mInputIndex = INDEX_INVALID; - mOutputIndex = INDEX_INVALID; - mCodecCounters.codecReleaseCount++; - try { - mCodec.stop(); - } finally { - try { - mCodec.release(); - } finally { - mCodec = null; - } - } - } - } - - /** Returns the index of input buffer which is ready for using. */ - public int getInputIndex() { - return mInputIndex; - } - - @Override - public ByteBuffer getInputBuffer() { - if (mInputIndex < 0) { - mInputIndex = mCodec.dequeueInputBuffer(0); - if (mInputIndex < 0) { - return null; - } - return mCodec.getInputBuffer(mInputIndex); - } - return mCodec.getInputBuffer(mInputIndex); - } - - @Override - public void decode(SampleHolder sampleHolder) { - if (mWaitingForFirstSyncFrame) { - if (!sampleHolder.isSyncFrame()) { - sampleHolder.clearData(); - return; - } - mWaitingForFirstSyncFrame = false; - } - long presentationTimeUs = sampleHolder.timeUs; - if (sampleHolder.isDecodeOnly()) { - mDecodeOnlyPresentationTimestamps.add(presentationTimeUs); - } - mCodec.queueInputBuffer(mInputIndex, 0, sampleHolder.data.limit(), presentationTimeUs, 0); - mInputIndex = INDEX_INVALID; - mCodecCounters.inputBufferCount++; - } - - private int getDecodeOnlyIndex(long presentationTimeUs) { - final int size = mDecodeOnlyPresentationTimestamps.size(); - for (int i = 0; i < size; i++) { - if (mDecodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) { - return i; - } - } - return INDEX_INVALID; - } - - /** Returns the index of output buffer which is ready for using. */ - public int getOutputIndex() { - if (mOutputIndex < 0) { - mOutputIndex = mCodec.dequeueOutputBuffer(mOutputBufferInfo, 0); - mIsNewIndex = true; - } else { - mIsNewIndex = false; - } - return mOutputIndex; - } - - @Override - public android.media.MediaFormat getOutputFormat() { - return mCodec.getOutputFormat(); - } - - /** Returns {@code true} if the output is only for decoding but not for rendering. */ - public boolean maybeDecodeOnlyIndex() { - int decodeOnlyIndex = getDecodeOnlyIndex(mOutputBufferInfo.presentationTimeUs); - if (decodeOnlyIndex != INDEX_INVALID) { - mCodec.releaseOutputBuffer(mOutputIndex, false); - mCodecCounters.skippedOutputBufferCount++; - mDecodeOnlyPresentationTimestamps.remove(decodeOnlyIndex); - mOutputIndex = INDEX_INVALID; - return true; - } - return false; - } - - @Override - public ByteBuffer getDecodedSample() { - if (maybeDecodeOnlyIndex() || mOutputIndex < 0) { - return null; - } - if (mIsNewIndex) { - mMediaCodecOutputBuffer = mCodec.getOutputBuffer(mOutputIndex); - } - return mMediaCodecOutputBuffer; - } - - @Override - public long getDecodedTimeUs() { - return mOutputBufferInfo.presentationTimeUs; - } - - /** Releases the output buffer after rendering. */ - public void releaseOutputBuffer() { - mCodecCounters.renderedOutputBufferCount++; - mCodec.releaseOutputBuffer(mOutputIndex, false); - mOutputIndex = INDEX_INVALID; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java deleted file mode 100644 index ae18e05d..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.media.MediaCodec; -import android.os.Build; -import android.os.Handler; -import android.os.SystemClock; -import android.util.Log; -import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; -import com.android.tv.tuner.tvinput.TunerDebug; -import com.google.android.exoplayer.CodecCounters; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaClock; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.audio.AudioTrack; -import com.google.android.exoplayer.util.Assertions; -import com.google.android.exoplayer.util.MimeTypes; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/** - * Decodes and renders DTV audio. Supports MediaCodec based decoding, passthrough playback and - * ffmpeg based software decoding (AC3, MP2). - */ -public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements MediaClock { - public static final int MSG_SET_VOLUME = 10000; - public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1; - public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2; - - // ATSC/53 allows sample rate to be only 48Khz. - // One AC3 sample has 1536 frames, and its duration is 32ms. - public static final long AC3_SAMPLE_DURATION_US = 32000; - - // TODO: Check whether DVB broadcasting uses sample rate other than 48Khz. - // MPEG-1 audio Layer II and III has 1152 frames per sample. - // 1152 frames duration is 24ms when sample rate is 48Khz. - static final long MP2_SAMPLE_DURATION_US = 24000; - - // This is around 150ms, 150ms is big enough not to under-run AudioTrack, - // and 150ms is also small enough to fill the buffer rapidly. - static int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5; - public static final long INITIAL_AUDIO_BUFFERING_TIME_US = - BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US; - - private static final String TAG = "MpegTsDefaultAudioTrac"; - private static final boolean DEBUG = false; - - /** - * Interface definition for a callback to be notified of {@link - * com.google.android.exoplayer.audio.AudioTrack} error. - */ - public interface EventListener { - void onAudioTrackInitializationError(AudioTrack.InitializationException e); - - void onAudioTrackWriteError(AudioTrack.WriteException e); - } - - private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2; - private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024 * 1024; - private static final int MONITOR_DURATION_MS = 1000; - private static final int AC3_HEADER_BITRATE_OFFSET = 4; - private static final int MP2_HEADER_BITRATE_OFFSET = 2; - private static final int MP2_HEADER_BITRATE_MASK = 0xfc; - - // Keep this as static in order to prevent new framework AudioTrack creation - // while old AudioTrack is being released. - private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper(); - private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000; - - // Ignore AudioTrack backward movement if duration of movement is below the threshold. - private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000; - - // AudioTrack position cannot go ahead beyond this limit. - private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000; - - // Since MediaCodec processing and AudioTrack playing add delay, - // PTS interpolated time should be delayed reasonably when AudioTrack is not used. - private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000; - - private final MediaCodecSelector mSelector; - - private final CodecCounters mCodecCounters; - private final SampleSource.SampleSourceReader mSource; - private final MediaFormatHolder mFormatHolder; - private final EventListener mEventListener; - private final Handler mEventHandler; - private final AudioTrackMonitor mMonitor; - private final AudioClock mAudioClock; - private final boolean mAc3Passthrough; - private final boolean mSoftwareDecoderAvailable; - - private MediaFormat mFormat; - private SampleHolder mSampleHolder; - private String mDecodingMime; - private boolean mFormatConfigured; - private int mSampleSize; - private final ByteBuffer mOutputBuffer; - private AudioDecoder mAudioDecoder; - private boolean mOutputReady; - private int mTrackIndex; - private boolean mSourceStateReady; - private boolean mInputStreamEnded; - private boolean mOutputStreamEnded; - private long mEndOfStreamMs; - private long mCurrentPositionUs; - private int mPresentationCount; - private long mPresentationTimeUs; - private long mInterpolatedTimeUs; - private long mPreviousPositionUs; - private boolean mIsStopped; - private boolean mEnabled = true; - private boolean mIsMuted; - private ArrayList mTracksIndex; - private boolean mUseFrameworkDecoder; - - public MpegTsDefaultAudioTrackRenderer( - SampleSource source, - MediaCodecSelector selector, - Handler eventHandler, - EventListener listener, - boolean hasSoftwareAudioDecoder, - boolean usePassthrough) { - mSource = source.register(); - mSelector = selector; - mEventHandler = eventHandler; - mEventListener = listener; - mTrackIndex = -1; - mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE); - mFormatHolder = new MediaFormatHolder(); - AUDIO_TRACK.restart(); - mCodecCounters = new CodecCounters(); - mMonitor = new AudioTrackMonitor(); - mAudioClock = new AudioClock(); - mTracksIndex = new ArrayList<>(); - mAc3Passthrough = usePassthrough; - mSoftwareDecoderAvailable = hasSoftwareAudioDecoder && FfmpegDecoderClient.isAvailable(); - } - - @Override - protected MediaClock getMediaClock() { - return this; - } - - private boolean handlesMimeType(String mimeType) { - return mimeType.equals(MimeTypes.AUDIO_AC3) - || mimeType.equals(MimeTypes.AUDIO_E_AC3) - || mimeType.equals(MimeTypes.AUDIO_MPEG_L2) - || MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType); - } - - @Override - protected boolean doPrepare(long positionUs) throws ExoPlaybackException { - boolean sourcePrepared = mSource.prepare(positionUs); - if (!sourcePrepared) { - return false; - } - for (int i = 0; i < mSource.getTrackCount(); i++) { - String mimeType = mSource.getFormat(i).mimeType; - if (MimeTypes.isAudio(mimeType) && handlesMimeType(mimeType)) { - if (mTrackIndex < 0) { - mTrackIndex = i; - } - mTracksIndex.add(i); - } - } - - // TODO: Check this case. Source does not have the proper mime type. - return true; - } - - @Override - protected int getTrackCount() { - return mTracksIndex.size(); - } - - @Override - protected MediaFormat getFormat(int track) { - Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); - return mSource.getFormat(mTracksIndex.get(track)); - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) { - Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); - mTrackIndex = mTracksIndex.get(track); - mSource.enable(mTrackIndex, positionUs); - seekToInternal(positionUs); - } - - @Override - protected void onDisabled() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - AUDIO_TRACK.resetSessionId(); - } - clearDecodeState(); - mFormat = null; - mSource.disable(mTrackIndex); - } - - @Override - protected void onReleased() { - releaseDecoder(); - AUDIO_TRACK.release(); - mSource.release(); - } - - @Override - protected boolean isEnded() { - return mOutputStreamEnded && AUDIO_TRACK.isEnded(); - } - - @Override - protected boolean isReady() { - return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady)); - } - - private void seekToInternal(long positionUs) { - mMonitor.reset(MONITOR_DURATION_MS); - mSourceStateReady = false; - mInputStreamEnded = false; - mOutputStreamEnded = false; - mPresentationTimeUs = positionUs; - mPresentationCount = 0; - mPreviousPositionUs = 0; - mCurrentPositionUs = Long.MIN_VALUE; - mInterpolatedTimeUs = Long.MIN_VALUE; - mAudioClock.setPositionUs(positionUs); - } - - @Override - protected void seekTo(long positionUs) { - mSource.seekToUs(positionUs); - AUDIO_TRACK.reset(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - // resetSessionId() will create a new framework AudioTrack instead of reusing old one. - AUDIO_TRACK.resetSessionId(); - } - seekToInternal(positionUs); - clearDecodeState(); - } - - @Override - protected void onStarted() { - AUDIO_TRACK.play(); - mAudioClock.start(); - mIsStopped = false; - } - - @Override - protected void onStopped() { - AUDIO_TRACK.pause(); - mAudioClock.stop(); - mIsStopped = true; - } - - @Override - protected void maybeThrowError() throws ExoPlaybackException { - try { - mSource.maybeThrowError(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - mMonitor.maybeLog(); - try { - if (mEndOfStreamMs != 0) { - // Ensure playback stops, after EoS was notified. - // Sometimes MediaCodecTrackRenderer does not fetch EoS timely - // after EoS was notified here long before. - long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs; - if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) { - throw new ExoPlaybackException("Much time has elapsed after EoS"); - } - } - boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs); - if (mSourceStateReady != continueBuffering) { - mSourceStateReady = continueBuffering; - if (DEBUG) { - Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady)); - } - } - long discontinuity = mSource.readDiscontinuity(mTrackIndex); - if (discontinuity != SampleSource.NO_DISCONTINUITY) { - AUDIO_TRACK.handleDiscontinuity(); - mPresentationTimeUs = discontinuity; - mPresentationCount = 0; - clearDecodeState(); - return; - } - if (mFormat == null) { - readFormat(); - return; - } - - if (mAudioDecoder != null) { - mAudioDecoder.maybeInitDecoder(mFormat); - } - // Process only one sample at a time for doSomeWork() when using FFmpeg decoder. - if (processOutput()) { - if (!mOutputReady) { - while (feedInputBuffer()) { - if (mOutputReady) break; - } - } - } - mCodecCounters.ensureUpdated(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - private void ensureAudioTrackInitialized() { - if (!AUDIO_TRACK.isInitialized()) { - try { - if (DEBUG) { - Log.d(TAG, "AudioTrack initialized"); - } - AUDIO_TRACK.initialize(); - } catch (AudioTrack.InitializationException e) { - Log.e(TAG, "Error on AudioTrack initialization", e); - notifyAudioTrackInitializationError(e); - - // Do not throw exception here but just disabling audioTrack to keep playing - // video without audio. - AUDIO_TRACK.setStatus(false); - } - if (getState() == TrackRenderer.STATE_STARTED) { - if (DEBUG) { - Log.d(TAG, "AudioTrack played"); - } - AUDIO_TRACK.play(); - } - } - } - - private void clearDecodeState() { - mOutputReady = false; - if (mAudioDecoder != null) { - mAudioDecoder.resetDecoderState(mDecodingMime); - } - AUDIO_TRACK.reset(); - } - - private void releaseDecoder() { - if (mAudioDecoder != null) { - mAudioDecoder.release(); - } - } - - private void readFormat() throws IOException, ExoPlaybackException { - int result = - mSource.readData(mTrackIndex, mCurrentPositionUs, mFormatHolder, mSampleHolder); - if (result == SampleSource.FORMAT_READ) { - onInputFormatChanged(mFormatHolder); - } - } - - private MediaFormat convertMediaFormatToRaw(MediaFormat format) { - return MediaFormat.createAudioFormat( - format.trackId, - MimeTypes.AUDIO_RAW, - format.bitrate, - format.maxInputSize, - format.durationUs, - format.channelCount, - format.sampleRate, - format.initializationData, - format.language); - } - - private void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException { - String mimeType = formatHolder.format.mimeType; - mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType); - if (mUseFrameworkDecoder) { - mAudioDecoder = new MediaCodecAudioDecoder(mSelector); - mFormat = formatHolder.format; - mAudioDecoder.maybeInitDecoder(mFormat); - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); - } else if (mSoftwareDecoderAvailable - && (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mimeType) - || MimeTypes.AUDIO_AC3.equalsIgnoreCase(mimeType) && !mAc3Passthrough)) { - releaseDecoder(); - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); - mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); - mAudioDecoder = FfmpegDecoderClient.getInstance(); - mDecodingMime = mimeType; - mFormat = convertMediaFormatToRaw(formatHolder.format); - } else { - mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); - mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); - mFormat = formatHolder.format; - releaseDecoder(); - } - mFormatConfigured = true; - mMonitor.setEncoding(mimeType); - if (DEBUG && !mUseFrameworkDecoder) { - Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString()); - } - clearDecodeState(); - if (!mUseFrameworkDecoder) { - AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), 0); - } - } - - private void onSampleSizeChanged(int sampleSize) { - if (DEBUG) { - Log.d(TAG, "Sample size was changed to : " + sampleSize); - } - clearDecodeState(); - int audioBufferSize = sampleSize * BUFFERED_SAMPLES_IN_AUDIOTRACK; - mSampleSize = sampleSize; - AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), audioBufferSize); - } - - private void onOutputFormatChanged(android.media.MediaFormat format) { - if (DEBUG) { - Log.d(TAG, "AudioTrack was configured to FORMAT: " + format.toString()); - } - AUDIO_TRACK.reconfigure(format, 0); - } - - private boolean feedInputBuffer() throws IOException, ExoPlaybackException { - if (mInputStreamEnded) { - return false; - } - - if (mUseFrameworkDecoder) { - boolean indexChanged = - ((MediaCodecAudioDecoder) mAudioDecoder).getInputIndex() - == MediaCodecAudioDecoder.INDEX_INVALID; - if (indexChanged) { - mSampleHolder.data = mAudioDecoder.getInputBuffer(); - if (mSampleHolder.data != null) { - mSampleHolder.clearData(); - } else { - return false; - } - } - } else { - mSampleHolder.data.clear(); - mSampleHolder.size = 0; - } - int result = - mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder); - switch (result) { - case SampleSource.NOTHING_READ: - { - return false; - } - case SampleSource.FORMAT_READ: - { - Log.i(TAG, "Format was read again"); - onInputFormatChanged(mFormatHolder); - return true; - } - case SampleSource.END_OF_STREAM: - { - Log.i(TAG, "End of stream from SampleSource"); - mInputStreamEnded = true; - return false; - } - default: - { - if (mSampleHolder.size != mSampleSize - && mFormatConfigured - && !mUseFrameworkDecoder) { - onSampleSizeChanged(mSampleHolder.size); - } - mSampleHolder.data.flip(); - if (!mUseFrameworkDecoder) { - if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { - mMonitor.addPts( - mSampleHolder.timeUs, - mOutputBuffer.position(), - mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET) - & MP2_HEADER_BITRATE_MASK); - } else { - mMonitor.addPts( - mSampleHolder.timeUs, - mOutputBuffer.position(), - mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff); - } - } - if (mAudioDecoder != null) { - mAudioDecoder.decode(mSampleHolder); - if (mUseFrameworkDecoder) { - int outputIndex = - ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex(); - if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - onOutputFormatChanged(mAudioDecoder.getOutputFormat()); - return true; - } else if (outputIndex < 0) { - return true; - } - if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) { - AUDIO_TRACK.handleDiscontinuity(); - return true; - } - } - ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample(); - long presentationTimeUs = mAudioDecoder.getDecodedTimeUs(); - decodeDone(outputBuffer, presentationTimeUs); - } else { - decodeDone(mSampleHolder.data, mSampleHolder.timeUs); - } - return true; - } - } - } - - private boolean processOutput() throws ExoPlaybackException { - if (mOutputStreamEnded) { - return false; - } - if (!mOutputReady) { - if (mInputStreamEnded) { - mOutputStreamEnded = true; - mEndOfStreamMs = SystemClock.elapsedRealtime(); - return false; - } - return true; - } - - ensureAudioTrackInitialized(); - int handleBufferResult; - try { - // To reduce discontinuity, interpolate presentation time. - if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) { - mInterpolatedTimeUs = - mPresentationTimeUs + mPresentationCount * MP2_SAMPLE_DURATION_US; - } else if (!mUseFrameworkDecoder) { - mInterpolatedTimeUs = - mPresentationTimeUs + mPresentationCount * AC3_SAMPLE_DURATION_US; - } else { - mInterpolatedTimeUs = mPresentationTimeUs; - } - handleBufferResult = - AUDIO_TRACK.handleBuffer( - mOutputBuffer, 0, mOutputBuffer.limit(), mInterpolatedTimeUs); - } catch (AudioTrack.WriteException e) { - notifyAudioTrackWriteError(e); - throw new ExoPlaybackException(e); - } - if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { - Log.i(TAG, "Play discontinuity happened"); - mCurrentPositionUs = Long.MIN_VALUE; - } - if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { - mCodecCounters.renderedOutputBufferCount++; - mOutputReady = false; - if (mUseFrameworkDecoder) { - ((MediaCodecAudioDecoder) mAudioDecoder).releaseOutputBuffer(); - } - return true; - } - return false; - } - - @Override - protected long getDurationUs() { - return mSource.getFormat(mTrackIndex).durationUs; - } - - @Override - protected long getBufferedPositionUs() { - long pos = mSource.getBufferedPositionUs(); - return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US - ? pos - : Math.max(pos, getPositionUs()); - } - - @Override - public long getPositionUs() { - if (!AUDIO_TRACK.isInitialized()) { - return mAudioClock.getPositionUs(); - } else if (!AUDIO_TRACK.isEnabled()) { - if (mInterpolatedTimeUs > 0 && !mUseFrameworkDecoder) { - return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US; - } - return mPresentationTimeUs; - } - long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded()); - if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) { - mPreviousPositionUs = 0L; - if (DEBUG) { - long oldPositionUs = Math.max(mCurrentPositionUs, 0); - long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); - Log.d( - TAG, - "Audio position is not set, diff in us: " - + String.valueOf(currentPositionUs - oldPositionUs)); - } - mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); - } else { - if (mPreviousPositionUs - > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) { - Log.e( - TAG, - "audio_position BACK JUMP: " - + (mPreviousPositionUs - audioTrackCurrentPositionUs)); - mCurrentPositionUs = audioTrackCurrentPositionUs; - } else { - mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs); - } - mPreviousPositionUs = audioTrackCurrentPositionUs; - } - long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US; - if (mCurrentPositionUs > upperBound) { - mCurrentPositionUs = upperBound; - } - return mCurrentPositionUs; - } - - private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) { - if (outputBuffer == null || mOutputBuffer == null) { - return; - } - if (presentationTimeUs < 0) { - Log.e(TAG, "decodeDone - invalid presentationTimeUs"); - return; - } - - if (TunerDebug.ENABLED) { - TunerDebug.setAudioPtsUs(presentationTimeUs); - } - - mOutputBuffer.clear(); - Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit()); - - mOutputBuffer.put(outputBuffer); - if (presentationTimeUs == mPresentationTimeUs) { - mPresentationCount++; - } else { - mPresentationCount = 0; - mPresentationTimeUs = presentationTimeUs; - } - mOutputBuffer.flip(); - mOutputReady = true; - } - - private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { - if (mEventHandler == null || mEventListener == null) { - return; - } - mEventHandler.post( - new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackInitializationError(e); - } - }); - } - - private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { - if (mEventHandler == null || mEventListener == null) { - return; - } - mEventHandler.post( - new Runnable() { - @Override - public void run() { - mEventListener.onAudioTrackWriteError(e); - } - }); - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - switch (messageType) { - case MSG_SET_VOLUME: - float volume = (Float) message; - // Workaround: we cannot mute the audio track by setting the volume to 0, we need to - // disable the AUDIO_TRACK for this intent. However, enabling/disabling audio track - // whenever volume is being set might cause side effects, therefore we only handle - // "explicit mute operations", i.e., only after certain non-zero volume has been - // set, the subsequent volume setting operations will be consider as mute/un-mute - // operations and thus enable/disable the audio track. - if (mIsMuted && volume > 0) { - mIsMuted = false; - if (mEnabled) { - setStatus(true); - } - } else if (!mIsMuted && volume == 0) { - mIsMuted = true; - if (mEnabled) { - setStatus(false); - } - } - AUDIO_TRACK.setVolume(volume); - break; - case MSG_SET_AUDIO_TRACK: - mEnabled = (Integer) message == 1; - setStatus(mEnabled); - break; - case MSG_SET_PLAYBACK_SPEED: - mAudioClock.setPlaybackSpeed((Float) message); - break; - default: - super.handleMessage(messageType, message); - } - } - - private void setStatus(boolean enabled) { - if (enabled == AUDIO_TRACK.isEnabled()) { - return; - } - if (!enabled) { - // mAudioClock can be different from getPositionUs. In order to sync them, - // we set mAudioClock. - mAudioClock.setPositionUs(getPositionUs()); - } - AUDIO_TRACK.setStatus(enabled); - if (enabled) { - // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to - // the current position. If not, AUDIO_TRACK has the obsolete data. - seekTo(mAudioClock.getPositionUs()); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java deleted file mode 100644 index b382545f..00000000 --- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.audio; - -import android.os.Handler; -import com.google.android.exoplayer.ExoPlaybackException; -import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; -import com.google.android.exoplayer.MediaCodecSelector; -import com.google.android.exoplayer.SampleSource; - -/** - * MPEG-2 TS audio track renderer. - * - *

Since the audio output from {@link android.media.MediaExtractor} contains extra samples at the - * beginning, using original {@link MediaCodecAudioTrackRenderer} as audio renderer causes - * asynchronous Audio/Video outputs. This class calculates the offset of audio data and adjust the - * presentation times to avoid the asynchronous Audio/Video problem. - */ -public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRenderer { - private final Ac3EventListener mListener; - - public interface Ac3EventListener extends EventListener { - /** - * Invoked when a {@link android.media.PlaybackParams} set to an {@link - * android.media.AudioTrack} is not valid. - * - * @param e The corresponding exception. - */ - void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e); - } - - public MpegTsMediaCodecAudioTrackRenderer( - SampleSource source, - MediaCodecSelector mediaCodecSelector, - Handler eventHandler, - EventListener eventListener) { - super(source, mediaCodecSelector, eventHandler, eventListener); - mListener = (Ac3EventListener) eventListener; - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - if (messageType == MSG_SET_PLAYBACK_PARAMS) { - try { - super.handleMessage(messageType, message); - } catch (IllegalArgumentException e) { - if (isAudioTrackSetPlaybackParamsError(e)) { - notifyAudioTrackSetPlaybackParamsError(e); - } - } - return; - } - super.handleMessage(messageType, message); - } - - private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) { - if (eventHandler != null && mListener != null) { - eventHandler.post( - new Runnable() { - @Override - public void run() { - mListener.onAudioTrackSetPlaybackParamsError(e); - } - }); - } - } - - private static boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) { - if (e.getStackTrace() == null || e.getStackTrace().length < 1) { - return false; - } - for (StackTraceElement element : e.getStackTrace()) { - String elementString = element.toString(); - if (elementString.startsWith("android.media.AudioTrack.setPlaybackParams")) { - return true; - } - } - return false; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java deleted file mode 100644 index 206e2098..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java +++ /dev/null @@ -1,682 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.media.MediaFormat; -import android.os.ConditionVariable; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Pair; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.SampleExtractor; -import com.android.tv.util.Utils; -import com.google.android.exoplayer.SampleHolder; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * Manages {@link SampleChunk} objects. - * - *

The buffer manager can be disabled, while running, if the write throughput to the associated - * external storage is detected to be lower than a threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}". - * This leads to restarting playback flow. - */ -public class BufferManager { - private static final String TAG = "BufferManager"; - private static final boolean DEBUG = false; - - // Constants for the disk write speed checking - private static final long MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK = - 10L * 1024 * 1024; // Checks for every 10M disk write - private static final int MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK = 15 * 1024; - private static final int MAXIMUM_SPEED_CHECK_COUNT = 5; // Checks only 5 times - private static final int MINIMUM_DISK_WRITE_SPEED_MBPS = 3; // 3 Megabytes per second - - private final SampleChunk.SampleChunkCreator mSampleChunkCreator; - // Maps from track name to a map which maps from starting position to {@link SampleChunk}. - private final Map>> mChunkMap = - new ArrayMap<>(); - private final Map mStartPositionMap = new ArrayMap<>(); - private final Map mEvictListeners = new ArrayMap<>(); - private final StorageManager mStorageManager; - private long mBufferSize = 0; - private final EvictChunkQueueMap mPendingDelete = new EvictChunkQueueMap(); - private final SampleChunk.ChunkCallback mChunkCallback = - new SampleChunk.ChunkCallback() { - @Override - public void onChunkWrite(SampleChunk chunk) { - mBufferSize += chunk.getSize(); - } - - @Override - public void onChunkDelete(SampleChunk chunk) { - mBufferSize -= chunk.getSize(); - } - }; - - private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK; - private long mTotalWriteSize; - private long mTotalWriteTimeNs; - private float mWriteBandwidth = 0.0f; - private volatile int mSpeedCheckCount; - - public interface ChunkEvictedListener { - void onChunkEvicted(String id, long createdTimeMs); - } - /** Handles I/O between BufferManager and {@link SampleExtractor}. */ - public interface SampleBuffer { - - /** - * Initializes SampleBuffer. - * - * @param Ids track identifiers for storage read/write. - * @param mediaFormats meta-data for each track. - * @throws IOException - */ - void init( - @NonNull List Ids, - @NonNull List mediaFormats) - throws IOException; - - /** Selects the track {@code index} for reading sample data. */ - void selectTrack(int index); - - /** - * Deselects the track at {@code index}, so that no more samples will be read from the - * track. - */ - void deselectTrack(int index); - - /** - * Writes sample to storage. - * - * @param index track index - * @param sample sample to write at storage - * @param conditionVariable notifies the completion of writing sample. - * @throws IOException - */ - void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException; - - /** Checks whether storage write speed is slow. */ - boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs); - - /** - * Handles when write speed is slow. - * - * @throws IOException - */ - void handleWriteSpeedSlow() throws IOException; - - /** Sets the flag when EoS was reached. */ - void setEos(); - - /** - * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, - * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ} if it is - * available. If the next sample is not available, returns {@link - * com.google.android.exoplayer.SampleSource#NOTHING_READ}. - */ - int readSample(int index, SampleHolder outSample); - - /** Seeks to the specified time in microseconds. */ - void seekTo(long positionUs); - - /** Returns an estimate of the position up to which data is buffered. */ - long getBufferedPositionUs(); - - /** Returns whether there is buffered data. */ - boolean continueBuffering(long positionUs); - - /** - * Cleans up and releases everything. - * - * @throws IOException - */ - void release() throws IOException; - } - - /** A Track format which will be loaded and saved from the permanent storage for recordings. */ - public static class TrackFormat { - - /** - * The track id for the specified track. The track id will be used as a track identifier for - * recordings. - */ - public final String trackId; - - /** The {@link MediaFormat} for the specified track. */ - public final MediaFormat format; - - /** - * Creates TrackFormat. - * - * @param trackId - * @param format - */ - public TrackFormat(String trackId, MediaFormat format) { - this.trackId = trackId; - this.format = format; - } - } - - /** A Holder for a sample position which will be loaded from the index file for recordings. */ - public static class PositionHolder { - - /** - * The current sample position in microseconds. The position is identical to the - * PTS(presentation time stamp) of the sample. - */ - public final long positionUs; - - /** Base sample position for the current {@link SampleChunk}. */ - public final long basePositionUs; - - /** The file offset for the current sample in the current {@link SampleChunk}. */ - public final int offset; - - /** - * Creates a holder for a specific position in the recording. - * - * @param positionUs - * @param offset - */ - public PositionHolder(long positionUs, long basePositionUs, int offset) { - this.positionUs = positionUs; - this.basePositionUs = basePositionUs; - this.offset = offset; - } - } - - /** Storage configuration and policy manager for {@link BufferManager} */ - public interface StorageManager { - - /** - * Provides eligible storage directory for {@link BufferManager}. - * - * @return a directory to save buffer(chunks) and meta files - */ - File getBufferDir(); - - /** - * Informs whether the storage is used for persistent use. (eg. dvr recording/play) - * - * @return {@code true} if stored files are persistent - */ - boolean isPersistent(); - - /** - * Informs whether the storage usage exceeds pre-determined size. - * - * @param bufferSize the current total usage of Storage in bytes. - * @param pendingDelete the current storage usage which will be deleted in near future by - * bytes - * @return {@code true} if it reached pre-determined max size - */ - boolean reachedStorageMax(long bufferSize, long pendingDelete); - - /** - * Informs whether the storage has enough remained space. - * - * @param pendingDelete the current storage usage which will be deleted in near future by - * bytes - * @return {@code true} if it has enough space - */ - boolean hasEnoughBuffer(long pendingDelete); - - /** - * Reads track name & {@link MediaFormat} from storage. - * - * @param isAudio {@code true} if it is for audio track - * @return {@link List} of TrackFormat - */ - List readTrackInfoFiles(boolean isAudio); - - /** - * Reads key sample positions for each written sample from storage. - * - * @param trackId track name - * @return indexes of the specified track - * @throws IOException - */ - ArrayList readIndexFile(String trackId) throws IOException; - - /** - * Writes track information to storage. - * - * @param formatList {@list List} of TrackFormat - * @param isAudio {@code true} if it is for audio track - * @throws IOException - */ - void writeTrackInfoFiles(List formatList, boolean isAudio) throws IOException; - - /** - * Writes index file to storage. - * - * @param trackName track name - * @param index {@link SampleChunk} container - * @throws IOException - */ - void writeIndexFile(String trackName, SortedMap> index) - throws IOException; - } - - private static class EvictChunkQueueMap { - private final Map> mEvictMap = new ArrayMap<>(); - private long mSize; - - private void init(String key) { - mEvictMap.put(key, new LinkedList<>()); - } - - private void add(String key, SampleChunk chunk) { - LinkedList queue = mEvictMap.get(key); - if (queue != null) { - mSize += chunk.getSize(); - queue.add(chunk); - } - } - - private SampleChunk poll(String key, long startPositionUs) { - LinkedList queue = mEvictMap.get(key); - if (queue != null) { - SampleChunk chunk = queue.peek(); - if (chunk != null && chunk.getStartPositionUs() < startPositionUs) { - mSize -= chunk.getSize(); - return queue.poll(); - } - } - return null; - } - - private long getSize() { - return mSize; - } - - private void release() { - for (Map.Entry> entry : mEvictMap.entrySet()) { - for (SampleChunk chunk : entry.getValue()) { - SampleChunk.IoState.release(chunk, true); - } - } - mEvictMap.clear(); - mSize = 0; - } - } - - public BufferManager(StorageManager storageManager) { - this(storageManager, new SampleChunk.SampleChunkCreator()); - } - - public BufferManager( - StorageManager storageManager, SampleChunk.SampleChunkCreator sampleChunkCreator) { - mStorageManager = storageManager; - mSampleChunkCreator = sampleChunkCreator; - } - - public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) { - mEvictListeners.put(id, listener); - } - - public void unregisterChunkEvictedListener(String id) { - mEvictListeners.remove(id); - } - - private static String getFileName(String id, long positionUs) { - return String.format(Locale.ENGLISH, "%s_%016x.chunk", id, positionUs); - } - - /** - * Creates a new {@link SampleChunk} for caching samples if it is needed. - * - * @param id the name of the track - * @param positionUs current position to write a sample in micro seconds. - * @param samplePool {@link SamplePool} for the fast creation of samples. - * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create a - * new {@link SampleChunk}. - * @param currentOffset the current offset to write. - * @return returns the created {@link SampleChunk}. - * @throws IOException - */ - public SampleChunk createNewWriteFileIfNeeded( - String id, - long positionUs, - SamplePool samplePool, - SampleChunk currentChunk, - int currentOffset) - throws IOException { - if (!maybeEvictChunk()) { - throw new IOException("Not enough storage space"); - } - SortedMap> map = mChunkMap.get(id); - if (map == null) { - map = new TreeMap<>(); - mChunkMap.put(id, map); - mStartPositionMap.put(id, positionUs); - mPendingDelete.init(id); - } - if (currentChunk == null) { - File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); - SampleChunk sampleChunk = - mSampleChunkCreator.createSampleChunk( - samplePool, file, positionUs, mChunkCallback); - map.put(positionUs, new Pair(sampleChunk, 0)); - return sampleChunk; - } else { - map.put(positionUs, new Pair(currentChunk, currentOffset)); - return null; - } - } - - /** - * Loads a track using {@link BufferManager.StorageManager}. - * - * @param trackId the name of the track. - * @param samplePool {@link SamplePool} for the fast creation of samples. - * @throws IOException - */ - public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException { - ArrayList keyPositions = mStorageManager.readIndexFile(trackId); - long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0; - - SortedMap> map = mChunkMap.get(trackId); - if (map == null) { - map = new TreeMap<>(); - mChunkMap.put(trackId, map); - mStartPositionMap.put(trackId, startPositionUs); - mPendingDelete.init(trackId); - } - SampleChunk chunk = null; - long basePositionUs = -1; - for (PositionHolder position : keyPositions) { - if (position.basePositionUs != basePositionUs) { - chunk = - mSampleChunkCreator.loadSampleChunkFromFile( - samplePool, - mStorageManager.getBufferDir(), - getFileName(trackId, position.positionUs), - position.positionUs, - mChunkCallback, - chunk); - basePositionUs = position.basePositionUs; - } - map.put(position.positionUs, new Pair(chunk, position.offset)); - } - } - - /** - * Finds a {@link SampleChunk} for the specified track name and the position. - * - * @param id the name of the track. - * @param positionUs the position. - * @return returns the found {@link SampleChunk}. - */ - public Pair getReadFile(String id, long positionUs) { - SortedMap> map = mChunkMap.get(id); - if (map == null) { - return null; - } - Pair ret; - SortedMap> headMap = map.headMap(positionUs + 1); - if (!headMap.isEmpty()) { - ret = headMap.get(headMap.lastKey()); - } else { - ret = map.get(map.firstKey()); - } - return ret; - } - - /** - * Evicts chunks which are ready to be evicted for the specified track - * - * @param id the specified track - * @param earlierThanPositionUs the start position of the {@link SampleChunk} should be earlier - * than - */ - public void evictChunks(String id, long earlierThanPositionUs) { - SampleChunk chunk = null; - while ((chunk = mPendingDelete.poll(id, earlierThanPositionUs)) != null) { - SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()); - } - } - - /** - * Returns the start position of the specified track in micro seconds. - * - * @param id the specified track - */ - public long getStartPositionUs(String id) { - Long ret = mStartPositionMap.get(id); - return ret == null ? 0 : ret; - } - - private boolean maybeEvictChunk() { - long pendingDelete = mPendingDelete.getSize(); - while (mStorageManager.reachedStorageMax(mBufferSize, pendingDelete) - || !mStorageManager.hasEnoughBuffer(pendingDelete)) { - if (mStorageManager.isPersistent()) { - // Since chunks are persistent, we cannot evict chunks. - return false; - } - SortedMap> earliestChunkMap = null; - SampleChunk earliestChunk = null; - String earliestChunkId = null; - for (Map.Entry>> entry : - mChunkMap.entrySet()) { - SortedMap> map = entry.getValue(); - if (map.isEmpty()) { - continue; - } - SampleChunk chunk = map.get(map.firstKey()).first; - if (earliestChunk == null - || chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) { - earliestChunkMap = map; - earliestChunk = chunk; - earliestChunkId = entry.getKey(); - } - } - if (earliestChunk == null) { - break; - } - mPendingDelete.add(earliestChunkId, earliestChunk); - earliestChunkMap.remove(earliestChunk.getStartPositionUs()); - if (DEBUG) { - Log.d( - TAG, - String.format( - "bufferSize = %d; pendingDelete = %b; " - + "earliestChunk size = %d; %s@%d (%s)", - mBufferSize, - pendingDelete, - earliestChunk.getSize(), - earliestChunkId, - earliestChunk.getStartPositionUs(), - Utils.toIsoDateTimeString(earliestChunk.getCreatedTimeMs()))); - } - ChunkEvictedListener listener = mEvictListeners.get(earliestChunkId); - if (listener != null) { - listener.onChunkEvicted(earliestChunkId, earliestChunk.getCreatedTimeMs()); - } - pendingDelete = mPendingDelete.getSize(); - } - for (Map.Entry>> entry : - mChunkMap.entrySet()) { - SortedMap> map = entry.getValue(); - if (map.isEmpty()) { - continue; - } - mStartPositionMap.put(entry.getKey(), map.firstKey()); - } - return true; - } - - /** - * Reads track information which includes {@link MediaFormat}. - * - * @return returns all track information which is found by {@link BufferManager.StorageManager}. - * @throws IOException - */ - public List readTrackInfoFiles() throws IOException { - List trackFormatList = new ArrayList<>(); - trackFormatList.addAll(mStorageManager.readTrackInfoFiles(false)); - trackFormatList.addAll(mStorageManager.readTrackInfoFiles(true)); - if (trackFormatList.isEmpty()) { - throw new IOException("No track information to load"); - } - return trackFormatList; - } - - /** - * Writes track information and index information for all tracks. - * - * @param audios list of audio track information - * @param videos list of audio track information - * @throws IOException - */ - public void writeMetaFiles(List audios, List videos) - throws IOException { - if (audios.isEmpty() && videos.isEmpty()) { - throw new IOException("No track information to save"); - } - if (!audios.isEmpty()) { - mStorageManager.writeTrackInfoFiles(audios, true); - for (TrackFormat trackFormat : audios) { - SortedMap> map = - mChunkMap.get(trackFormat.trackId); - if (map == null) { - throw new IOException("Audio track index missing"); - } - mStorageManager.writeIndexFile(trackFormat.trackId, map); - } - } - if (!videos.isEmpty()) { - mStorageManager.writeTrackInfoFiles(videos, false); - for (TrackFormat trackFormat : videos) { - SortedMap> map = - mChunkMap.get(trackFormat.trackId); - if (map == null) { - throw new IOException("Video track index missing"); - } - mStorageManager.writeIndexFile(trackFormat.trackId, map); - } - } - } - - /** Releases all the resources. */ - public void release() { - try { - mPendingDelete.release(); - for (Map.Entry>> entry : - mChunkMap.entrySet()) { - SampleChunk toRelease = null; - for (Pair positions : entry.getValue().values()) { - if (toRelease != positions.first) { - toRelease = positions.first; - SampleChunk.IoState.release(toRelease, !mStorageManager.isPersistent()); - } - } - } - mChunkMap.clear(); - } catch (ConcurrentModificationException | NullPointerException e) { - // TODO: remove this after it it confirmed that race condition issues are resolved. - // b/32492258, b/32373376 - SoftPreconditions.checkState( - false, "Exception on BufferManager#release: ", e.toString()); - } - } - - private void resetWriteStat(float writeBandwidth) { - mWriteBandwidth = writeBandwidth; - mTotalWriteSize = 0; - mTotalWriteTimeNs = 0; - } - - /** Adds a disk write sample size to calculate the average disk write bandwidth. */ - public void addWriteStat(long size, long timeNs) { - if (size >= mMinSampleSizeForSpeedCheck) { - mTotalWriteSize += size; - mTotalWriteTimeNs += timeNs; - } - } - - /** - * Returns if the average disk write bandwidth is slower than threshold {@code - * MINIMUM_DISK_WRITE_SPEED_MBPS}. - */ - public boolean isWriteSlow() { - if (mTotalWriteSize < MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK) { - return false; - } - - // Checks write speed for only MAXIMUM_SPEED_CHECK_COUNT times to ignore outliers - // by temporary system overloading during the playback. - if (mSpeedCheckCount > MAXIMUM_SPEED_CHECK_COUNT) { - return false; - } - mSpeedCheckCount++; - float megabytePerSecond = calculateWriteBandwidth(); - resetWriteStat(megabytePerSecond); - if (DEBUG) { - Log.d(TAG, "Measured disk write performance: " + megabytePerSecond + "MBps"); - } - return megabytePerSecond < MINIMUM_DISK_WRITE_SPEED_MBPS; - } - - /** - * Returns recent write bandwidth in MBps. If recent bandwidth is not available, returns {float - * -1.0f}. - */ - public float getWriteBandwidth() { - return mWriteBandwidth == 0.0f ? -1.0f : mWriteBandwidth; - } - - private float calculateWriteBandwidth() { - if (mTotalWriteTimeNs == 0) { - return -1; - } - return ((float) mTotalWriteSize * 1000 / mTotalWriteTimeNs); - } - - /** - * Returns if {@link BufferManager} has checked the write speed, which is suitable for - * Trickplay. - */ - @VisibleForTesting - public boolean hasSpeedCheckDone() { - return mSpeedCheckCount > 0; - } - - /** - * Sets minimum sample size for write speed check. - * - * @param sampleSize minimum sample size for write speed check. - */ - @VisibleForTesting - public void setMinimumSampleSizeForSpeedCheck(int sampleSize) { - mMinSampleSizeForSpeedCheck = sampleSize; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java deleted file mode 100644 index 2a58ffcf..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.media.MediaFormat; -import android.util.Log; -import android.util.Pair; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.google.protobuf.nano.MessageNano; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; - -/** Manages DVR storage. */ -public class DvrStorageManager implements BufferManager.StorageManager { - private static final String TAG = "DvrStorageManager"; - - // TODO: make serializable classes and use protobuf after internal data structure is finalized. - private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO = - "com.google.android.videos.pixelWidthHeightRatio"; - private static final String META_FILE_TYPE_AUDIO = "audio"; - private static final String META_FILE_TYPE_VIDEO = "video"; - private static final String META_FILE_TYPE_CAPTION = "caption"; - private static final String META_FILE_SUFFIX = ".meta"; - private static final String IDX_FILE_SUFFIX = ".idx"; - private static final String IDX_FILE_SUFFIX_V2 = IDX_FILE_SUFFIX + "2"; - - // Size of minimum reserved storage buffer which will be used to save meta files - // and index files after actual recording finished. - private static final long MIN_BUFFER_BYTES = 256L * 1024 * 1024; - private static final int NO_VALUE = -1; - private static final long NO_VALUE_LONG = -1L; - - private final File mBufferDir; - - // {@code true} when this is for recording, {@code false} when this is for replaying. - private final boolean mIsRecording; - - public DvrStorageManager(File file, boolean isRecording) { - mBufferDir = file; - mBufferDir.mkdirs(); - mIsRecording = isRecording; - } - - @Override - public File getBufferDir() { - return mBufferDir; - } - - @Override - public boolean isPersistent() { - return true; - } - - @Override - public boolean reachedStorageMax(long bufferSize, long pendingDelete) { - return false; - } - - @Override - public boolean hasEnoughBuffer(long pendingDelete) { - return !mIsRecording || mBufferDir.getUsableSpace() >= MIN_BUFFER_BYTES; - } - - private void readFormatInt(DataInputStream in, MediaFormat format, String key) - throws IOException { - int val = in.readInt(); - if (val != NO_VALUE) { - format.setInteger(key, val); - } - } - - private void readFormatLong(DataInputStream in, MediaFormat format, String key) - throws IOException { - long val = in.readLong(); - if (val != NO_VALUE_LONG) { - format.setLong(key, val); - } - } - - private void readFormatFloat(DataInputStream in, MediaFormat format, String key) - throws IOException { - float val = in.readFloat(); - if (val != NO_VALUE) { - format.setFloat(key, val); - } - } - - private String readString(DataInputStream in) throws IOException { - int len = in.readInt(); - if (len <= 0) { - return null; - } - byte[] strBytes = new byte[len]; - in.readFully(strBytes); - return new String(strBytes, StandardCharsets.UTF_8); - } - - private void readFormatString(DataInputStream in, MediaFormat format, String key) - throws IOException { - String str = readString(in); - if (str != null) { - format.setString(key, str); - } - } - - private void readFormatStringOptional(DataInputStream in, MediaFormat format, String key) { - try { - String str = readString(in); - if (str != null) { - format.setString(key, str); - } - } catch (IOException e) { - // Since we are reading optional field, ignore the exception. - } - } - - private ByteBuffer readByteBuffer(DataInputStream in) throws IOException { - int len = in.readInt(); - if (len <= 0) { - return null; - } - byte[] bytes = new byte[len]; - in.readFully(bytes); - ByteBuffer buffer = ByteBuffer.allocate(len); - buffer.put(bytes); - buffer.flip(); - - return buffer; - } - - private void readFormatByteBuffer(DataInputStream in, MediaFormat format, String key) - throws IOException { - ByteBuffer buffer = readByteBuffer(in); - if (buffer != null) { - format.setByteBuffer(key, buffer); - } - } - - @Override - public List readTrackInfoFiles(boolean isAudio) { - List trackFormatList = new ArrayList<>(); - int index = 0; - boolean trackNotFound = false; - do { - String fileName = - (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) - + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); - File file = new File(getBufferDir(), fileName); - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - String name = readString(in); - MediaFormat format = new MediaFormat(); - readFormatString(in, format, MediaFormat.KEY_MIME); - readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE); - readFormatInt(in, format, MediaFormat.KEY_WIDTH); - readFormatInt(in, format, MediaFormat.KEY_HEIGHT); - readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT); - readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE); - readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); - for (int i = 0; i < 3; ++i) { - readFormatByteBuffer(in, format, "csd-" + i); - } - readFormatLong(in, format, MediaFormat.KEY_DURATION); - - // This is optional since language field is added later. - readFormatStringOptional(in, format, MediaFormat.KEY_LANGUAGE); - trackFormatList.add(new BufferManager.TrackFormat(name, format)); - } catch (IOException e) { - trackNotFound = true; - } - index++; - } while (!trackNotFound); - return trackFormatList; - } - - /** - * Reads caption information from files. - * - * @return a list of {@link AtscCaptionTrack} objects which store caption information. - */ - public List readCaptionInfoFiles() { - List tracks = new ArrayList<>(); - int index = 0; - boolean trackNotFound = false; - do { - String fileName = - META_FILE_TYPE_CAPTION - + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX)); - File file = new File(getBufferDir(), fileName); - try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - byte[] data = new byte[(int) file.length()]; - in.read(data); - tracks.add(AtscCaptionTrack.parseFrom(data)); - } catch (IOException e) { - trackNotFound = true; - } - index++; - } while (!trackNotFound); - return tracks; - } - - private ArrayList readOldIndexFile(File indexFile) - throws IOException { - ArrayList indices = new ArrayList<>(); - try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) { - long count = in.readLong(); - for (long i = 0; i < count; ++i) { - long positionUs = in.readLong(); - indices.add(new BufferManager.PositionHolder(positionUs, positionUs, 0)); - } - return indices; - } - } - - private ArrayList readNewIndexFile(File indexFile) - throws IOException { - ArrayList indices = new ArrayList<>(); - try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) { - long count = in.readLong(); - for (long i = 0; i < count; ++i) { - long positionUs = in.readLong(); - long basePositionUs = in.readLong(); - int offset = in.readInt(); - indices.add(new BufferManager.PositionHolder(positionUs, basePositionUs, offset)); - } - return indices; - } - } - - @Override - public ArrayList readIndexFile(String trackId) - throws IOException { - File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX_V2); - if (file.exists()) { - return readNewIndexFile(file); - } else { - return readOldIndexFile(new File(getBufferDir(), trackId + IDX_FILE_SUFFIX)); - } - } - - private void writeFormatInt(DataOutputStream out, MediaFormat format, String key) - throws IOException { - if (format.containsKey(key)) { - out.writeInt(format.getInteger(key)); - } else { - out.writeInt(NO_VALUE); - } - } - - private void writeFormatLong(DataOutputStream out, MediaFormat format, String key) - throws IOException { - if (format.containsKey(key)) { - out.writeLong(format.getLong(key)); - } else { - out.writeLong(NO_VALUE_LONG); - } - } - - private void writeFormatFloat(DataOutputStream out, MediaFormat format, String key) - throws IOException { - if (format.containsKey(key)) { - out.writeFloat(format.getFloat(key)); - } else { - out.writeFloat(NO_VALUE); - } - } - - private void writeString(DataOutputStream out, String str) throws IOException { - byte[] data = str.getBytes(StandardCharsets.UTF_8); - out.writeInt(data.length); - if (data.length > 0) { - out.write(data); - } - } - - private void writeFormatString(DataOutputStream out, MediaFormat format, String key) - throws IOException { - if (format.containsKey(key)) { - writeString(out, format.getString(key)); - } else { - out.writeInt(0); - } - } - - private void writeByteBuffer(DataOutputStream out, ByteBuffer buffer) throws IOException { - byte[] data = new byte[buffer.limit()]; - buffer.get(data); - buffer.flip(); - out.writeInt(data.length); - if (data.length > 0) { - out.write(data); - } else { - out.writeInt(0); - } - } - - private void writeFormatByteBuffer(DataOutputStream out, MediaFormat format, String key) - throws IOException { - if (format.containsKey(key)) { - writeByteBuffer(out, format.getByteBuffer(key)); - } else { - out.writeInt(0); - } - } - - @Override - public void writeTrackInfoFiles(List formatList, boolean isAudio) - throws IOException { - for (int i = 0; i < formatList.size(); ++i) { - BufferManager.TrackFormat trackFormat = formatList.get(i); - String fileName = - (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO) - + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); - File file = new File(getBufferDir(), fileName); - try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { - writeString(out, trackFormat.trackId); - writeFormatString(out, trackFormat.format, MediaFormat.KEY_MIME); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_MAX_INPUT_SIZE); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_WIDTH); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_HEIGHT); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_CHANNEL_COUNT); - writeFormatInt(out, trackFormat.format, MediaFormat.KEY_SAMPLE_RATE); - writeFormatFloat(out, trackFormat.format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); - for (int j = 0; j < 3; ++j) { - writeFormatByteBuffer(out, trackFormat.format, "csd-" + j); - } - writeFormatLong(out, trackFormat.format, MediaFormat.KEY_DURATION); - writeFormatString(out, trackFormat.format, MediaFormat.KEY_LANGUAGE); - } - } - } - - /** - * Writes caption information to files. - * - * @param tracks a list of {@link AtscCaptionTrack} objects which store caption information. - */ - public void writeCaptionInfoFiles(List tracks) { - if (tracks == null || tracks.isEmpty()) { - return; - } - for (int i = 0; i < tracks.size(); i++) { - AtscCaptionTrack track = tracks.get(i); - String fileName = - META_FILE_TYPE_CAPTION + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX)); - File file = new File(getBufferDir(), fileName); - try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { - out.write(MessageNano.toByteArray(track)); - } catch (Exception e) { - Log.e(TAG, "Fail to write caption info to files", e); - } - } - } - - @Override - public void writeIndexFile(String trackName, SortedMap> index) - throws IOException { - File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2); - try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) { - out.writeLong(index.size()); - for (Map.Entry> entry : index.entrySet()) { - out.writeLong(entry.getKey()); - out.writeLong(entry.getValue().first.getStartPositionUs()); - out.writeInt(entry.getValue().second); - } - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java deleted file mode 100644 index ebf00f59..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.os.ConditionVariable; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.util.Log; -import com.android.tv.tuner.exoplayer.MpegTsPlayer; -import com.android.tv.tuner.exoplayer.SampleExtractor; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.util.Assertions; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Handles I/O between {@link SampleExtractor} and {@link BufferManager}.Reads & writes samples - * from/to {@link SampleChunk} which is backed by physical storage. - */ -public class RecordingSampleBuffer - implements BufferManager.SampleBuffer, BufferManager.ChunkEvictedListener { - private static final String TAG = "RecordingSampleBuffer"; - - @IntDef({BUFFER_REASON_LIVE_PLAYBACK, BUFFER_REASON_RECORDED_PLAYBACK, BUFFER_REASON_RECORDING}) - @Retention(RetentionPolicy.SOURCE) - public @interface BufferReason {} - - /** A buffer reason for live-stream playback. */ - public static final int BUFFER_REASON_LIVE_PLAYBACK = 0; - - /** A buffer reason for playback of a recorded program. */ - public static final int BUFFER_REASON_RECORDED_PLAYBACK = 1; - - /** A buffer reason for recording a program. */ - public static final int BUFFER_REASON_RECORDING = 2; - - /** The minimum duration to support seek in Trickplay. */ - static final long MIN_SEEK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500); - - /** The duration of a {@link SampleChunk} for recordings. */ - static final long RECORDING_CHUNK_DURATION_US = MIN_SEEK_DURATION_US * 1200; // 10 minutes - - private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds - private static final long BUFFER_NEEDED_US = - 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS); - - private final BufferManager mBufferManager; - private final PlaybackBufferListener mBufferListener; - private final @BufferReason int mBufferReason; - - private int mTrackCount; - private boolean[] mTrackSelected; - private List mReadSampleQueues; - private final SamplePool mSamplePool = new SamplePool(); - private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; - private long mCurrentPlaybackPositionUs = 0; - - // An error in I/O thread of {@link SampleChunkIoHelper} will be notified. - private volatile boolean mError; - - // Eos was reached in I/O thread of {@link SampleChunkIoHelper}. - private volatile boolean mEos; - private SampleChunkIoHelper mSampleChunkIoHelper; - private final SampleChunkIoHelper.IoCallback mIoCallback = - new SampleChunkIoHelper.IoCallback() { - @Override - public void onIoReachedEos() { - mEos = true; - } - - @Override - public void onIoError() { - mError = true; - } - }; - - /** - * Creates {@link BufferManager.SampleBuffer} with cached I/O backed by physical storage (e.g. - * trickplay,recording,recorded-playback). - * - * @param bufferManager the manager of {@link SampleChunk} - * @param bufferListener the listener for buffer I/O event - * @param enableTrickplay {@code true} when trickplay should be enabled - * @param bufferReason the reason for caching samples {@link RecordingSampleBuffer.BufferReason} - */ - public RecordingSampleBuffer( - BufferManager bufferManager, - PlaybackBufferListener bufferListener, - boolean enableTrickplay, - @BufferReason int bufferReason) { - mBufferManager = bufferManager; - mBufferListener = bufferListener; - if (bufferListener != null) { - bufferListener.onBufferStateChanged(enableTrickplay); - } - mBufferReason = bufferReason; - } - - @Override - public void init(@NonNull List ids, @NonNull List mediaFormats) - throws IOException { - mTrackCount = ids.size(); - if (mTrackCount <= 0) { - throw new IOException("No tracks to initialize"); - } - mTrackSelected = new boolean[mTrackCount]; - mReadSampleQueues = new ArrayList<>(); - mSampleChunkIoHelper = - new SampleChunkIoHelper( - ids, mediaFormats, mBufferReason, mBufferManager, mSamplePool, mIoCallback); - for (int i = 0; i < mTrackCount; ++i) { - mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); - } - mSampleChunkIoHelper.init(); - for (int i = 0; i < mTrackCount; ++i) { - mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this); - } - } - - @Override - public void selectTrack(int index) { - if (!mTrackSelected[index]) { - mTrackSelected[index] = true; - mReadSampleQueues.get(index).clear(); - mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs); - } - } - - @Override - public void deselectTrack(int index) { - if (mTrackSelected[index]) { - mTrackSelected[index] = false; - mReadSampleQueues.get(index).clear(); - mSampleChunkIoHelper.closeRead(index); - } - } - - @Override - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { - mSampleChunkIoHelper.writeSample(index, sample, conditionVariable); - - if (!conditionVariable.block(BUFFER_WRITE_TIMEOUT_MS)) { - Log.e(TAG, "Error: Serious delay on writing buffer"); - conditionVariable.block(); - } - } - - @Override - public boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs) { - if (mBufferReason == BUFFER_REASON_RECORDED_PLAYBACK) { - return false; - } - mBufferManager.addWriteStat(sampleSize, writeDurationNs); - return mBufferManager.isWriteSlow(); - } - - @Override - public void handleWriteSpeedSlow() throws IOException { - if (mBufferReason == BUFFER_REASON_RECORDING) { - // Recording does not need to stop because I/O speed is slow temporarily. - // If fixed size buffer of TsStreamer overflows, TsDataSource will reach EoS. - // Reaching EoS will stop recording eventually. - Log.w( - TAG, - "Disk I/O speed is slow for recording temporarily: " - + mBufferManager.getWriteBandwidth() - + "MBps"); - return; - } - // Disables buffering samples afterwards, and notifies the disk speed is slow. - Log.w(TAG, "Disk is too slow for trickplay"); - mBufferListener.onDiskTooSlow(); - } - - @Override - public void setEos() { - mSampleChunkIoHelper.closeWrite(); - } - - private boolean maybeReadSample(SampleQueue queue, int index) { - if (queue.getLastQueuedPositionUs() != null - && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US - && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) { - // The speed of queuing samples can be higher than the playback speed. - // If the duration of the samples in the queue is not limited, - // samples can be accumulated and there can be out-of-memory issues. - // But, the throttling should provide enough samples for the player to - // finish the buffering state. - return false; - } - SampleHolder sample = mSampleChunkIoHelper.readSample(index); - if (sample != null) { - queue.queueSample(sample); - return true; - } - return false; - } - - @Override - public int readSample(int track, SampleHolder outSample) { - Assertions.checkState(mTrackSelected[track]); - maybeReadSample(mReadSampleQueues.get(track), track); - int result = mReadSampleQueues.get(track).dequeueSample(outSample); - if ((result != SampleSource.SAMPLE_READ && mEos) || mError) { - return SampleSource.END_OF_STREAM; - } - return result; - } - - @Override - public void seekTo(long positionUs) { - for (int i = 0; i < mTrackCount; ++i) { - if (mTrackSelected[i]) { - mReadSampleQueues.get(i).clear(); - mSampleChunkIoHelper.openRead(i, positionUs); - } - } - mLastBufferedPositionUs = positionUs; - } - - @Override - public long getBufferedPositionUs() { - Long result = null; - for (int i = 0; i < mTrackCount; ++i) { - if (!mTrackSelected[i]) { - continue; - } - Long lastQueuedSamplePositionUs = mReadSampleQueues.get(i).getLastQueuedPositionUs(); - if (lastQueuedSamplePositionUs == null) { - // No sample has been queued. - result = mLastBufferedPositionUs; - continue; - } - if (result == null || result > lastQueuedSamplePositionUs) { - result = lastQueuedSamplePositionUs; - } - } - if (result == null) { - return mLastBufferedPositionUs; - } - return (mLastBufferedPositionUs = result); - } - - @Override - public boolean continueBuffering(long positionUs) { - mCurrentPlaybackPositionUs = positionUs; - for (int i = 0; i < mTrackCount; ++i) { - if (!mTrackSelected[i]) { - continue; - } - SampleQueue queue = mReadSampleQueues.get(i); - maybeReadSample(queue, i); - if (queue.getLastQueuedPositionUs() == null - || positionUs > queue.getLastQueuedPositionUs()) { - // No more buffered data. - return false; - } - } - return true; - } - - @Override - public void release() throws IOException { - if (mTrackCount <= 0) { - return; - } - if (mSampleChunkIoHelper != null) { - mSampleChunkIoHelper.release(); - } - } - - // onChunkEvictedListener - @Override - public void onChunkEvicted(String id, long createdTimeMs) { - if (mBufferListener != null) { - mBufferListener.onBufferStartTimeChanged( - createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US)); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java deleted file mode 100644 index 023d3295..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.util.Log; -import com.google.android.exoplayer.SampleHolder; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; - -/** - * {@link SampleChunk} stores samples into file and makes them available for read. Stored file = { - * Header, Sample } * N Header = sample size : int, sample flag : int, sample PTS in micro second : - * long - */ -public class SampleChunk { - private static final String TAG = "SampleChunk"; - private static final boolean DEBUG = false; - - private final long mCreatedTimeMs; - private final long mStartPositionUs; - private SampleChunk mNextChunk; - - // Header = sample size : int, sample flag : int, sample PTS in micro second : long - private static final int SAMPLE_HEADER_LENGTH = 16; - - private final File mFile; - private final ChunkCallback mChunkCallback; - private final SamplePool mSamplePool; - private RandomAccessFile mAccessFile; - private long mWriteOffset; - private boolean mWriteFinished; - private boolean mIsReading; - private boolean mIsWriting; - - /** A callback for chunks being committed to permanent storage. */ - public abstract static class ChunkCallback { - - /** - * Notifies when writing a SampleChunk is completed. - * - * @param chunk SampleChunk which is written completely - */ - public void onChunkWrite(SampleChunk chunk) {} - - /** - * Notifies when a SampleChunk is deleted. - * - * @param chunk SampleChunk which is deleted from storage - */ - public void onChunkDelete(SampleChunk chunk) {} - } - - /** A class for SampleChunk creation. */ - public static class SampleChunkCreator { - - /** - * Returns a newly created SampleChunk to read & write samples. - * - * @param samplePool sample allocator - * @param file filename which will be created newly - * @param startPositionUs the start position of the earliest sample to be stored - * @param chunkCallback for total storage usage change notification - */ - SampleChunk createSampleChunk( - SamplePool samplePool, - File file, - long startPositionUs, - ChunkCallback chunkCallback) { - return new SampleChunk( - samplePool, file, startPositionUs, System.currentTimeMillis(), chunkCallback); - } - - /** - * Returns a newly created SampleChunk which is backed by an existing file. Created - * SampleChunk is read-only. - * - * @param samplePool sample allocator - * @param bufferDir the directory where the file to read is located - * @param filename the filename which will be read afterwards - * @param startPositionUs the start position of the earliest sample in the file - * @param chunkCallback for total storage usage change notification - * @param prev the previous SampleChunk just before the newly created SampleChunk - * @throws IOException - */ - SampleChunk loadSampleChunkFromFile( - SamplePool samplePool, - File bufferDir, - String filename, - long startPositionUs, - ChunkCallback chunkCallback, - SampleChunk prev) - throws IOException { - File file = new File(bufferDir, filename); - SampleChunk chunk = new SampleChunk(samplePool, file, startPositionUs, chunkCallback); - if (prev != null) { - prev.mNextChunk = chunk; - } - return chunk; - } - } - - /** - * Handles I/O for SampleChunk. Maintains current SampleChunk and the current offset for next - * I/O operation. - */ - static class IoState { - private SampleChunk mChunk; - private long mCurrentOffset; - - private boolean equals(SampleChunk chunk, long offset) { - return chunk == mChunk && mCurrentOffset == offset; - } - - /** Returns whether read I/O operation is finished. */ - boolean isReadFinished() { - return mChunk == null; - } - - /** Returns the start position of the current SampleChunk */ - long getStartPositionUs() { - return mChunk == null ? 0 : mChunk.getStartPositionUs(); - } - - private void reset(@Nullable SampleChunk chunk) { - mChunk = chunk; - mCurrentOffset = 0; - } - - private void reset(SampleChunk chunk, long offset) { - mChunk = chunk; - mCurrentOffset = offset; - } - - /** - * Prepares for read I/O operation from a new SampleChunk. - * - * @param chunk the new SampleChunk to read from - * @throws IOException - */ - void openRead(SampleChunk chunk, long offset) throws IOException { - if (mChunk != null) { - mChunk.closeRead(); - } - chunk.openRead(); - reset(chunk, offset); - } - - /** - * Prepares for write I/O operation to a new SampleChunk. - * - * @param chunk the new SampleChunk to write samples afterwards - * @throws IOException - */ - void openWrite(SampleChunk chunk) throws IOException { - if (mChunk != null) { - mChunk.closeWrite(chunk); - } - chunk.openWrite(); - reset(chunk); - } - - /** - * Reads a sample if it is available. - * - * @return Returns a sample if it is available, null otherwise. - * @throws IOException - */ - SampleHolder read() throws IOException { - if (mChunk != null && mChunk.isReadFinished(this)) { - SampleChunk next = mChunk.mNextChunk; - mChunk.closeRead(); - if (next != null) { - next.openRead(); - } - reset(next); - } - if (mChunk != null) { - try { - return mChunk.read(this); - } catch (IllegalStateException e) { - // Write is finished and there is no additional buffer to read. - Log.w(TAG, "Tried to read sample over EOS."); - return null; - } - } else { - return null; - } - } - - /** - * Writes a sample. - * - * @param sample to write - * @param nextChunk if this is {@code null} writes at the current SampleChunk, otherwise - * close current SampleChunk and writes at this - * @throws IOException - */ - void write(SampleHolder sample, SampleChunk nextChunk) throws IOException { - if (nextChunk != null) { - if (mChunk == null || mChunk.mNextChunk != null) { - throw new IllegalStateException("Requested write for wrong SampleChunk"); - } - mChunk.closeWrite(nextChunk); - mChunk.mChunkCallback.onChunkWrite(mChunk); - nextChunk.openWrite(); - reset(nextChunk); - } - mChunk.write(sample, this); - } - - /** - * Finishes write I/O operation. - * - * @throws IOException - */ - void closeWrite() throws IOException { - if (mChunk != null) { - mChunk.closeWrite(null); - } - } - - /** Returns the current SampleChunk for subsequent I/O operation. */ - SampleChunk getChunk() { - return mChunk; - } - - /** Returns the current offset of the current SampleChunk for subsequent I/O operation. */ - long getOffset() { - return mCurrentOffset; - } - - /** - * Releases SampleChunk. the SampleChunk will not be used anymore. - * - * @param chunk to release - * @param delete {@code true} when the backed file needs to be deleted, {@code false} - * otherwise. - */ - static void release(SampleChunk chunk, boolean delete) { - chunk.release(delete); - } - } - - @VisibleForTesting - protected SampleChunk( - SamplePool samplePool, - File file, - long startPositionUs, - long createdTimeMs, - ChunkCallback chunkCallback) { - mStartPositionUs = startPositionUs; - mCreatedTimeMs = createdTimeMs; - mSamplePool = samplePool; - mFile = file; - mChunkCallback = chunkCallback; - } - - // Constructor of SampleChunk which is backed by the given existing file. - private SampleChunk( - SamplePool samplePool, File file, long startPositionUs, ChunkCallback chunkCallback) - throws IOException { - mStartPositionUs = startPositionUs; - mCreatedTimeMs = mStartPositionUs / 1000; - mSamplePool = samplePool; - mFile = file; - mChunkCallback = chunkCallback; - mWriteFinished = true; - } - - private void openRead() throws IOException { - if (!mIsReading) { - if (mAccessFile == null) { - mAccessFile = new RandomAccessFile(mFile, "r"); - } - if (mWriteFinished && mWriteOffset == 0) { - // Lazy loading of write offset, in order not to load - // all SampleChunk's write offset at start time of recorded playback. - mWriteOffset = mAccessFile.length(); - } - mIsReading = true; - } - } - - private void openWrite() throws IOException { - if (mWriteFinished) { - throw new IllegalStateException("Opened for write though write is already finished"); - } - if (!mIsWriting) { - if (mIsReading) { - throw new IllegalStateException( - "Write is requested for " + "an already opened SampleChunk"); - } - mAccessFile = new RandomAccessFile(mFile, "rw"); - mIsWriting = true; - } - } - - private void CloseAccessFileIfNeeded() throws IOException { - if (!mIsReading && !mIsWriting) { - try { - if (mAccessFile != null) { - mAccessFile.close(); - } - } finally { - mAccessFile = null; - } - } - } - - private void closeRead() throws IOException { - if (mIsReading) { - mIsReading = false; - CloseAccessFileIfNeeded(); - } - } - - private void closeWrite(SampleChunk nextChunk) throws IOException { - if (mIsWriting) { - mNextChunk = nextChunk; - mIsWriting = false; - mWriteFinished = true; - CloseAccessFileIfNeeded(); - } - } - - private boolean isReadFinished(IoState state) { - return mWriteFinished && state.equals(this, mWriteOffset); - } - - private SampleHolder read(IoState state) throws IOException { - if (mAccessFile == null || state.mChunk != this) { - throw new IllegalStateException("Requested read for wrong SampleChunk"); - } - long offset = state.mCurrentOffset; - if (offset >= mWriteOffset) { - if (mWriteFinished) { - throw new IllegalStateException("Requested read for wrong range"); - } else { - if (offset != mWriteOffset) { - Log.e(TAG, "This should not happen!"); - } - return null; - } - } - mAccessFile.seek(offset); - int size = mAccessFile.readInt(); - SampleHolder sample = mSamplePool.acquireSample(size); - sample.size = size; - sample.flags = mAccessFile.readInt(); - sample.timeUs = mAccessFile.readLong(); - sample.clearData(); - sample.data.put( - mAccessFile - .getChannel() - .map( - FileChannel.MapMode.READ_ONLY, - offset + SAMPLE_HEADER_LENGTH, - sample.size)); - offset += sample.size + SAMPLE_HEADER_LENGTH; - state.mCurrentOffset = offset; - return sample; - } - - @VisibleForTesting - protected void write(SampleHolder sample, IoState state) throws IOException { - if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) { - throw new IllegalStateException("Requested write for wrong SampleChunk"); - } - - mAccessFile.seek(mWriteOffset); - mAccessFile.writeInt(sample.size); - mAccessFile.writeInt(sample.flags); - mAccessFile.writeLong(sample.timeUs); - sample.data.position(0).limit(sample.size); - mAccessFile.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data); - mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH; - state.mCurrentOffset = mWriteOffset; - } - - private void release(boolean delete) { - mWriteFinished = true; - mIsReading = mIsWriting = false; - try { - if (mAccessFile != null) { - mAccessFile.close(); - } - } catch (IOException e) { - // Since the SampleChunk will not be reused, ignore exception. - } - if (delete) { - mFile.delete(); - mChunkCallback.onChunkDelete(this); - } - } - - /** Returns the start position. */ - public long getStartPositionUs() { - return mStartPositionUs; - } - - /** Returns the creation time. */ - public long getCreatedTimeMs() { - return mCreatedTimeMs; - } - - /** Returns the current size. */ - public long getSize() { - return mWriteOffset; - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java deleted file mode 100644 index 06fd6558..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.media.MediaCodec; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.util.ArraySet; -import android.util.Log; -import android.util.Pair; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.util.MimeTypes; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Handles all {@link SampleChunk} I/O operations. An I/O dedicated thread handles all I/O - * operations for synchronization. - */ -public class SampleChunkIoHelper implements Handler.Callback { - private static final String TAG = "SampleChunkIoHelper"; - - private static final int MAX_READ_BUFFER_SAMPLES = 3; - private static final int READ_RESCHEDULING_DELAY_MS = 10; - - private static final int MSG_OPEN_READ = 1; - private static final int MSG_OPEN_WRITE = 2; - private static final int MSG_CLOSE_READ = 3; - private static final int MSG_CLOSE_WRITE = 4; - private static final int MSG_READ = 5; - private static final int MSG_WRITE = 6; - private static final int MSG_RELEASE = 7; - - private final long mSampleChunkDurationUs; - private final int mTrackCount; - private final List mIds; - private final List mMediaFormats; - private final @BufferReason int mBufferReason; - private final BufferManager mBufferManager; - private final SamplePool mSamplePool; - private final IoCallback mIoCallback; - - private Handler mIoHandler; - private final ConcurrentLinkedQueue mReadSampleBuffers[]; - private final ConcurrentLinkedQueue mHandlerReadSampleBuffers[]; - private final long[] mWriteIndexEndPositionUs; - private final long[] mWriteChunkEndPositionUs; - private final SampleChunk.IoState[] mReadIoStates; - private final SampleChunk.IoState[] mWriteIoStates; - private final Set mSelectedTracks = new ArraySet<>(); - private long mBufferDurationUs = 0; - private boolean mWriteEnded; - private boolean mErrorNotified; - private boolean mFinished; - - /** A Callback for I/O events. */ - public abstract static class IoCallback { - - /** Called when there is no sample to read. */ - public void onIoReachedEos() {} - - /** Called when there is an irrecoverable error during I/O. */ - public void onIoError() {} - } - - private class IoParams { - private final int index; - private final long positionUs; - private final SampleHolder sample; - private final ConditionVariable conditionVariable; - private final ConcurrentLinkedQueue readSampleBuffer; - - private IoParams( - int index, - long positionUs, - SampleHolder sample, - ConditionVariable conditionVariable, - ConcurrentLinkedQueue readSampleBuffer) { - this.index = index; - this.positionUs = positionUs; - this.sample = sample; - this.conditionVariable = conditionVariable; - this.readSampleBuffer = readSampleBuffer; - } - } - - /** - * Creates {@link SampleChunk} I/O handler. - * - * @param ids track names - * @param mediaFormats {@link android.media.MediaFormat} for each track - * @param bufferReason reason to be buffered - * @param bufferManager manager of {@link SampleChunk} collections - * @param samplePool allocator for a sample - * @param ioCallback listeners for I/O events - */ - public SampleChunkIoHelper( - List ids, - List mediaFormats, - @BufferReason int bufferReason, - BufferManager bufferManager, - SamplePool samplePool, - IoCallback ioCallback) { - mTrackCount = ids.size(); - mIds = ids; - mMediaFormats = mediaFormats; - mBufferReason = bufferReason; - mBufferManager = bufferManager; - mSamplePool = samplePool; - mIoCallback = ioCallback; - - mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; - mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; - mWriteIndexEndPositionUs = new long[mTrackCount]; - mWriteChunkEndPositionUs = new long[mTrackCount]; - mReadIoStates = new SampleChunk.IoState[mTrackCount]; - mWriteIoStates = new SampleChunk.IoState[mTrackCount]; - - // Small chunk duration for live playback will give more fine grained storage usage - // and eviction handling for trickplay. - mSampleChunkDurationUs = - bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK - ? RecordingSampleBuffer.MIN_SEEK_DURATION_US - : RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US; - for (int i = 0; i < mTrackCount; ++i) { - mWriteIndexEndPositionUs[i] = RecordingSampleBuffer.MIN_SEEK_DURATION_US; - mWriteChunkEndPositionUs[i] = mSampleChunkDurationUs; - mReadIoStates[i] = new SampleChunk.IoState(); - mWriteIoStates[i] = new SampleChunk.IoState(); - } - } - - /** - * Prepares and initializes for I/O operations. - * - * @throws IOException - */ - public void init() throws IOException { - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - mIoHandler = new Handler(handlerThread.getLooper(), this); - if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) { - for (int i = 0; i < mTrackCount; ++i) { - mBufferManager.loadTrackFromStorage(mIds.get(i), mSamplePool); - } - mWriteEnded = true; - } else { - for (int i = 0; i < mTrackCount; ++i) { - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_WRITE, i)); - } - } - } - - /** - * Reads a sample if it is available. - * - * @param index track index - * @return {@code null} if a sample is not available, otherwise returns a sample - */ - public SampleHolder readSample(int index) { - SampleHolder sample = mReadSampleBuffers[index].poll(); - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index)); - return sample; - } - - /** - * Writes a sample. - * - * @param index track index - * @param sample to write - * @param conditionVariable which will be wait until the write is finished - * @throws IOException - */ - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { - if (mErrorNotified) { - throw new IOException("Storage I/O error happened"); - } - conditionVariable.close(); - IoParams params = new IoParams(index, 0, sample, conditionVariable, null); - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_WRITE, params)); - } - - /** - * Starts read from the specified position. - * - * @param index track index - * @param positionUs the specified position - */ - public void openRead(int index, long positionUs) { - // Old mReadSampleBuffers may have a pending read. - mReadSampleBuffers[index] = new ConcurrentLinkedQueue<>(); - IoParams params = new IoParams(index, positionUs, null, null, mReadSampleBuffers[index]); - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_READ, params)); - } - - /** - * Closes read from the specified track. - * - * @param index track index - */ - public void closeRead(int index) { - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_CLOSE_READ, index)); - } - - /** Notifies writes are finished. */ - public void closeWrite() { - mIoHandler.sendEmptyMessage(MSG_CLOSE_WRITE); - } - - /** - * Finishes I/O operations and releases all the resources. - * - * @throws IOException - */ - public void release() throws IOException { - if (mIoHandler == null) { - return; - } - // Finishes all I/O operations. - ConditionVariable conditionVariable = new ConditionVariable(); - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_RELEASE, conditionVariable)); - conditionVariable.block(); - - for (int i = 0; i < mTrackCount; ++i) { - mBufferManager.unregisterChunkEvictedListener(mIds.get(i)); - } - try { - if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) { - // Saves meta information for recording. - List audios = new LinkedList<>(); - List videos = new LinkedList<>(); - for (int i = 0; i < mTrackCount; ++i) { - android.media.MediaFormat format = - mMediaFormats.get(i).getFrameworkMediaFormatV16(); - format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs); - if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { - audios.add(new BufferManager.TrackFormat(mIds.get(i), format)); - } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { - videos.add(new BufferManager.TrackFormat(mIds.get(i), format)); - } - } - mBufferManager.writeMetaFiles(audios, videos); - } - } finally { - mBufferManager.release(); - mIoHandler.getLooper().quitSafely(); - } - } - - @Override - public boolean handleMessage(Message message) { - if (mFinished) { - return true; - } - releaseEvictedChunks(); - try { - switch (message.what) { - case MSG_OPEN_READ: - doOpenRead((IoParams) message.obj); - return true; - case MSG_OPEN_WRITE: - doOpenWrite((int) message.obj); - return true; - case MSG_CLOSE_READ: - doCloseRead((int) message.obj); - return true; - case MSG_CLOSE_WRITE: - doCloseWrite(); - return true; - case MSG_READ: - doRead((int) message.obj); - return true; - case MSG_WRITE: - doWrite((IoParams) message.obj); - // Since only write will increase storage, eviction will be handled here. - return true; - case MSG_RELEASE: - doRelease((ConditionVariable) message.obj); - return true; - } - } catch (IOException e) { - mIoCallback.onIoError(); - mErrorNotified = true; - Log.e(TAG, "IoException happened", e); - return true; - } - return false; - } - - private void doOpenRead(IoParams params) throws IOException { - int index = params.index; - mIoHandler.removeMessages(MSG_READ, index); - Pair readPosition = - mBufferManager.getReadFile(mIds.get(index), params.positionUs); - if (readPosition == null) { - String errorMessage = - "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs + "is not found"; - SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage); - throw new IOException(errorMessage); - } - mSelectedTracks.add(index); - mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second); - if (mHandlerReadSampleBuffers[index] != null) { - SampleHolder sample; - while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { - mSamplePool.releaseSample(sample); - } - } - mHandlerReadSampleBuffers[index] = params.readSampleBuffer; - mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index)); - } - - private void doOpenWrite(int index) throws IOException { - SampleChunk chunk = - mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0, mSamplePool, null, 0); - mWriteIoStates[index].openWrite(chunk); - } - - private void doCloseRead(int index) { - mSelectedTracks.remove(index); - if (mHandlerReadSampleBuffers[index] != null) { - SampleHolder sample; - while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { - mSamplePool.releaseSample(sample); - } - } - mIoHandler.removeMessages(MSG_READ, index); - } - - private void doRead(int index) throws IOException { - mIoHandler.removeMessages(MSG_READ, index); - if (mHandlerReadSampleBuffers[index].size() >= MAX_READ_BUFFER_SAMPLES) { - // If enough samples are buffered, try again few moments later hoping that - // buffered samples are consumed. - mIoHandler.sendMessageDelayed( - mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS); - } else { - if (mReadIoStates[index].isReadFinished()) { - for (int i = 0; i < mTrackCount; ++i) { - if (!mReadIoStates[i].isReadFinished()) { - return; - } - } - mIoCallback.onIoReachedEos(); - return; - } - SampleHolder sample = mReadIoStates[index].read(); - if (sample != null) { - mHandlerReadSampleBuffers[index].offer(sample); - } else { - // Read reached write but write is not finished yet --- wait a few moments to - // see if another sample is written. - mIoHandler.sendMessageDelayed( - mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS); - } - } - } - - private void doWrite(IoParams params) throws IOException { - try { - if (mWriteEnded) { - SoftPreconditions.checkState(false); - return; - } - int index = params.index; - SampleHolder sample = params.sample; - SampleChunk nextChunk = null; - if ((sample.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { - if (sample.timeUs > mBufferDurationUs) { - mBufferDurationUs = sample.timeUs; - } - if (sample.timeUs >= mWriteIndexEndPositionUs[index]) { - SampleChunk currentChunk = - sample.timeUs >= mWriteChunkEndPositionUs[index] - ? null - : mWriteIoStates[params.index].getChunk(); - int currentOffset = (int) mWriteIoStates[params.index].getOffset(); - nextChunk = - mBufferManager.createNewWriteFileIfNeeded( - mIds.get(index), - mWriteIndexEndPositionUs[index], - mSamplePool, - currentChunk, - currentOffset); - mWriteIndexEndPositionUs[index] = - ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) - * RecordingSampleBuffer.MIN_SEEK_DURATION_US; - if (nextChunk != null) { - mWriteChunkEndPositionUs[index] = - ((sample.timeUs / mSampleChunkDurationUs) + 1) - * mSampleChunkDurationUs; - } - } - } - mWriteIoStates[params.index].write(params.sample, nextChunk); - } finally { - params.conditionVariable.open(); - } - } - - private void doCloseWrite() throws IOException { - if (mWriteEnded) { - return; - } - mWriteEnded = true; - boolean readFinished = true; - for (int i = 0; i < mTrackCount; ++i) { - readFinished = readFinished && mReadIoStates[i].isReadFinished(); - mWriteIoStates[i].closeWrite(); - } - if (readFinished) { - mIoCallback.onIoReachedEos(); - } - } - - private void doRelease(ConditionVariable conditionVariable) { - mIoHandler.removeCallbacksAndMessages(null); - mFinished = true; - conditionVariable.open(); - mSelectedTracks.clear(); - } - - private void releaseEvictedChunks() { - if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK - || mSelectedTracks.isEmpty()) { - return; - } - long currentStartPositionUs = Long.MAX_VALUE; - for (int trackIndex : mSelectedTracks) { - currentStartPositionUs = - Math.min( - currentStartPositionUs, mReadIoStates[trackIndex].getStartPositionUs()); - } - for (int i = 0; i < mTrackCount; ++i) { - long evictEndPositionUs = - Math.min( - mBufferManager.getStartPositionUs(mIds.get(i)), currentStartPositionUs); - mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs); - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java b/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java deleted file mode 100644 index b89a14db..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import com.google.android.exoplayer.SampleHolder; -import java.util.LinkedList; - -/** Pool of samples to recycle ByteBuffers as much as possible. */ -public class SamplePool { - private final LinkedList mSamplePool = new LinkedList<>(); - - /** - * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize an - * existing buffer if necessary. - */ - public synchronized SampleHolder acquireSample(int size) { - if (mSamplePool.isEmpty()) { - SampleHolder sample = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - sample.ensureSpaceForWrite(size); - return sample; - } - SampleHolder smallestSufficientSample = null; - SampleHolder maxSample = mSamplePool.getFirst(); - for (SampleHolder sample : mSamplePool) { - // Grab the smallest sufficient sample. - if (sample.data.capacity() >= size - && (smallestSufficientSample == null - || smallestSufficientSample.data.capacity() > sample.data.capacity())) { - smallestSufficientSample = sample; - } - - // Grab the max size sample. - if (maxSample.data.capacity() < sample.data.capacity()) { - maxSample = sample; - } - } - SampleHolder sampleFromPool = smallestSufficientSample; - - // If there's no sufficient sample, grab the maximum sample and resize it to size. - if (sampleFromPool == null) { - sampleFromPool = maxSample; - sampleFromPool.ensureSpaceForWrite(size); - } - mSamplePool.remove(sampleFromPool); - return sampleFromPool; - } - - /** Releases the sample back to the pool. */ - public synchronized void releaseSample(SampleHolder sample) { - sample.clearData(); - mSamplePool.offerLast(sample); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java deleted file mode 100644 index e208f2c2..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import java.util.LinkedList; - -/** A sample queue which reads from the buffer and passes to player pipeline. */ -public class SampleQueue { - private final LinkedList mQueue = new LinkedList<>(); - private final SamplePool mSamplePool; - private Long mLastQueuedPositionUs = null; - - public SampleQueue(SamplePool samplePool) { - mSamplePool = samplePool; - } - - public void queueSample(SampleHolder sample) { - mQueue.offer(sample); - mLastQueuedPositionUs = sample.timeUs; - } - - public int dequeueSample(SampleHolder sample) { - SampleHolder sampleFromQueue = mQueue.poll(); - if (sampleFromQueue == null) { - return SampleSource.NOTHING_READ; - } - sample.ensureSpaceForWrite(sampleFromQueue.size); - sample.size = sampleFromQueue.size; - sample.flags = sampleFromQueue.flags; - sample.timeUs = sampleFromQueue.timeUs; - sample.clearData(); - sampleFromQueue.data.position(0).limit(sample.size); - sample.data.put(sampleFromQueue.data); - mSamplePool.releaseSample(sampleFromQueue); - return SampleSource.SAMPLE_READ; - } - - public void clear() { - while (!mQueue.isEmpty()) { - mSamplePool.releaseSample(mQueue.poll()); - } - mLastQueuedPositionUs = null; - } - - public Long getLastQueuedPositionUs() { - return mLastQueuedPositionUs; - } - - public boolean isDurationGreaterThan(long durationUs) { - return !mQueue.isEmpty() && mQueue.getLast().timeUs - mQueue.getFirst().timeUs > durationUs; - } - - public boolean isEmpty() { - return mQueue.isEmpty(); - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java deleted file mode 100644 index 4c6260bf..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.os.ConditionVariable; -import android.support.annotation.NonNull; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.exoplayer.SampleExtractor; -import com.android.tv.tuner.tvinput.PlaybackBufferListener; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import java.io.IOException; -import java.util.List; - -/** - * Handles I/O for {@link SampleExtractor} when physical storage based buffer is not used. Trickplay - * is disabled. - */ -public class SimpleSampleBuffer implements BufferManager.SampleBuffer { - private final SamplePool mSamplePool = new SamplePool(); - private SampleQueue[] mPlayingSampleQueues; - private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; - - private volatile boolean mEos; - - public SimpleSampleBuffer(PlaybackBufferListener bufferListener) { - if (bufferListener != null) { - // Disables trickplay. - bufferListener.onBufferStateChanged(false); - } - } - - @Override - public synchronized void init( - @NonNull List ids, @NonNull List mediaFormats) { - int trackCount = ids.size(); - mPlayingSampleQueues = new SampleQueue[trackCount]; - for (int i = 0; i < trackCount; i++) { - mPlayingSampleQueues[i] = null; - } - } - - @Override - public void setEos() { - mEos = true; - } - - private boolean reachedEos() { - return mEos; - } - - @Override - public void selectTrack(int index) { - synchronized (this) { - if (mPlayingSampleQueues[index] == null) { - mPlayingSampleQueues[index] = new SampleQueue(mSamplePool); - } else { - mPlayingSampleQueues[index].clear(); - } - } - } - - @Override - public void deselectTrack(int index) { - synchronized (this) { - if (mPlayingSampleQueues[index] != null) { - mPlayingSampleQueues[index].clear(); - mPlayingSampleQueues[index] = null; - } - } - } - - @Override - public synchronized long getBufferedPositionUs() { - Long result = null; - for (SampleQueue queue : mPlayingSampleQueues) { - if (queue == null) { - continue; - } - Long lastQueuedSamplePositionUs = queue.getLastQueuedPositionUs(); - if (lastQueuedSamplePositionUs == null) { - // No sample has been queued. - result = mLastBufferedPositionUs; - continue; - } - if (result == null || result > lastQueuedSamplePositionUs) { - result = lastQueuedSamplePositionUs; - } - } - if (result == null) { - return mLastBufferedPositionUs; - } - return (mLastBufferedPositionUs = result); - } - - @Override - public synchronized int readSample(int track, SampleHolder sampleHolder) { - SampleQueue queue = mPlayingSampleQueues[track]; - SoftPreconditions.checkNotNull(queue); - int result = queue == null ? SampleSource.NOTHING_READ : queue.dequeueSample(sampleHolder); - if (result != SampleSource.SAMPLE_READ && reachedEos()) { - return SampleSource.END_OF_STREAM; - } - return result; - } - - @Override - public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) - throws IOException { - sample.data.position(0).limit(sample.size); - SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size); - sampleToQueue.size = sample.size; - sampleToQueue.clearData(); - sampleToQueue.data.put(sample.data); - sampleToQueue.timeUs = sample.timeUs; - sampleToQueue.flags = sample.flags; - - synchronized (this) { - if (mPlayingSampleQueues[index] != null) { - mPlayingSampleQueues[index].queueSample(sampleToQueue); - } - } - } - - @Override - public boolean isWriteSpeedSlow(int sampleSize, long durationNs) { - // Since SimpleSampleBuffer write samples only to memory (not to physical storage), - // write speed is always fine. - return false; - } - - @Override - public void handleWriteSpeedSlow() { - // no-op - } - - @Override - public synchronized boolean continueBuffering(long positionUs) { - for (SampleQueue queue : mPlayingSampleQueues) { - if (queue == null) { - continue; - } - if (queue.getLastQueuedPositionUs() == null - || positionUs > queue.getLastQueuedPositionUs()) { - // No more buffered data. - return false; - } - } - return true; - } - - @Override - public void seekTo(long positionUs) { - // Not used. - } - - @Override - public void release() { - // Not used. - } -} diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java deleted file mode 100644 index b22b8af1..00000000 --- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.buffer; - -import android.content.Context; -import android.os.AsyncTask; -import android.provider.Settings; -import android.support.annotation.NonNull; -import android.util.Pair; -import com.android.tv.common.SoftPreconditions; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedMap; - -/** Manages Trickplay storage. */ -public class TrickplayStorageManager implements BufferManager.StorageManager { - // TODO: Support multi-sessions. - private static final String BUFFER_DIR = "timeshift"; - - // Copied from android.provider.Settings.Global (hidden fields) - private static final String SYS_STORAGE_THRESHOLD_PERCENTAGE = - "sys_storage_threshold_percentage"; - private static final String SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes"; - - // Copied from android.os.StorageManager - private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; - private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500L * 1024 * 1024; - - private static AsyncTask sLastCacheCleanUpTask; - private static File sBufferDir; - private static long sStorageBufferBytes; - - private final long mMaxBufferSize; - - private static void initParamsIfNeeded(Context context, @NonNull File path) { - // TODO: Support multi-sessions. - SoftPreconditions.checkState(sBufferDir == null || sBufferDir.equals(path)); - if (path.equals(sBufferDir)) { - return; - } - sBufferDir = path; - long lowPercentage = - Settings.Global.getInt( - context.getContentResolver(), - SYS_STORAGE_THRESHOLD_PERCENTAGE, - DEFAULT_THRESHOLD_PERCENTAGE); - long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100; - long maxLowBytes = - Settings.Global.getLong( - context.getContentResolver(), - SYS_STORAGE_THRESHOLD_MAX_BYTES, - DEFAULT_THRESHOLD_MAX_BYTES); - sStorageBufferBytes = Math.min(lowPercentageToBytes, maxLowBytes); - } - - public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) { - initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR)); - sBufferDir.mkdirs(); - mMaxBufferSize = maxBufferSize; - clearStorage(); - } - - private void clearStorage() { - long now = System.currentTimeMillis(); - if (sLastCacheCleanUpTask != null) { - sLastCacheCleanUpTask.cancel(true); - } - sLastCacheCleanUpTask = - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - if (isCancelled()) { - return null; - } - File files[] = sBufferDir.listFiles(); - if (files == null || files.length == 0) { - return null; - } - for (File file : files) { - if (isCancelled()) { - break; - } - long lastModified = file.lastModified(); - if (lastModified != 0 && lastModified < now) { - file.delete(); - } - } - return null; - } - }; - sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public File getBufferDir() { - return sBufferDir; - } - - @Override - public boolean isPersistent() { - return false; - } - - @Override - public boolean reachedStorageMax(long bufferSize, long pendingDelete) { - return bufferSize - pendingDelete > mMaxBufferSize; - } - - @Override - public boolean hasEnoughBuffer(long pendingDelete) { - return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes; - } - - @Override - public List readTrackInfoFiles(boolean isAudio) { - return null; - } - - @Override - public ArrayList readIndexFile(String trackId) { - return null; - } - - @Override - public void writeTrackInfoFiles(List formatList, boolean isAudio) {} - - @Override - public void writeIndexFile( - String trackName, SortedMap> index) {} -} diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java deleted file mode 100644 index 421192f1..00000000 --- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ffmpeg; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; -import android.support.annotation.WorkerThread; -import com.android.tv.Features; -import com.android.tv.tuner.exoplayer.audio.AudioDecoder; -import com.google.android.exoplayer.SampleHolder; -import java.nio.ByteBuffer; - -/** - * The class connects {@link FfmpegDecoderService} to decode audio samples. In order to sandbox - * ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process without any permission - * and connected by binder. - */ -public class FfmpegDecoderClient extends AudioDecoder { - private static FfmpegDecoderClient sInstance; - - private IFfmpegDecoder mService; - private Boolean mIsAvailable; - - private static final String FFMPEG_DECODER_SERVICE_FILTER = - "com.android.tv.tuner.exoplayer.ffmpeg.IFfmpegDecoder"; - private static final long FFMPEG_SERVICE_CONNECT_TIMEOUT_MS = 500; - - private final ServiceConnection mConnection = - new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - mService = IFfmpegDecoder.Stub.asInterface(service); - synchronized (FfmpegDecoderClient.this) { - try { - mIsAvailable = mService.isAvailable(); - } catch (RemoteException e) { - } - FfmpegDecoderClient.this.notify(); - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - synchronized (FfmpegDecoderClient.this) { - sInstance.releaseLocked(); - mIsAvailable = false; - mService = null; - } - } - }; - - /** - * Connects to the decoder service for future uses. - * - * @param context - * @return {@code true} when decoder service is connected. - */ - @MainThread - public static synchronized boolean connect(Context context) { - if (Features.AC3_SOFTWARE_DECODE.isEnabled(context)) { - if (sInstance == null) { - sInstance = new FfmpegDecoderClient(); - Intent intent = - new Intent(FFMPEG_DECODER_SERVICE_FILTER) - .setComponent( - new ComponentName(context, FfmpegDecoderService.class)); - if (context.bindService(intent, sInstance.mConnection, Context.BIND_AUTO_CREATE)) { - return true; - } else { - sInstance = null; - } - } - } - return false; - } - - /** - * Disconnects from the decoder service and release resources. - * - * @param context - */ - @MainThread - public static synchronized void disconnect(Context context) { - if (sInstance != null) { - synchronized (sInstance) { - sInstance.releaseLocked(); - if (sInstance.mIsAvailable != null && sInstance.mIsAvailable) { - context.unbindService(sInstance.mConnection); - } - sInstance.mIsAvailable = false; - sInstance.mService = null; - } - sInstance = null; - } - } - - /** - * Returns whether service is available or not. Before using client, this should be used to - * check availability. - */ - @WorkerThread - public static synchronized boolean isAvailable() { - if (sInstance != null) { - return sInstance.available(); - } - return false; - } - - /** Returns an client instance. */ - public static synchronized FfmpegDecoderClient getInstance() { - if (sInstance != null) { - sInstance.createDecoder(); - } - return sInstance; - } - - private FfmpegDecoderClient() {} - - private synchronized boolean available() { - if (mIsAvailable == null) { - try { - this.wait(FFMPEG_SERVICE_CONNECT_TIMEOUT_MS); - } catch (InterruptedException e) { - } - } - return mIsAvailable != null && mIsAvailable == true; - } - - private synchronized void createDecoder() { - if (mIsAvailable == null || mIsAvailable == false) { - return; - } - try { - mService.create(); - } catch (RemoteException e) { - } - } - - private void releaseLocked() { - if (mIsAvailable == null || mIsAvailable == false) { - return; - } - try { - mService.release(); - } catch (RemoteException e) { - } - } - - @Override - public synchronized void release() { - releaseLocked(); - } - - @Override - public synchronized void decode(SampleHolder sampleHolder) { - if (mIsAvailable == null || mIsAvailable == false) { - return; - } - byte[] sampleBytes = new byte[sampleHolder.data.limit()]; - sampleHolder.data.get(sampleBytes, 0, sampleBytes.length); - try { - mService.decode(sampleHolder.timeUs, sampleBytes); - } catch (RemoteException e) { - } - } - - @Override - public synchronized void resetDecoderState(String mimeType) { - if (mIsAvailable == null || mIsAvailable == false) { - return; - } - try { - mService.resetDecoderState(mimeType); - } catch (RemoteException e) { - } - } - - @Override - public synchronized ByteBuffer getDecodedSample() { - if (mIsAvailable == null || mIsAvailable == false) { - return null; - } - try { - byte[] outputBytes = mService.getDecodedSample(); - if (outputBytes != null && outputBytes.length > 0) { - return ByteBuffer.wrap(outputBytes); - } - } catch (RemoteException e) { - } - return null; - } - - @Override - public synchronized long getDecodedTimeUs() { - if (mIsAvailable == null || mIsAvailable == false) { - return 0; - } - try { - return mService.getDecodedTimeUs(); - } catch (RemoteException e) { - } - return 0; - } - - @VisibleForTesting - public boolean testSandboxIsolatedProcess() { - // When testing isolated process, we will check the permission in FfmpegDecoderService. - // If the service have any permission, an exception will be thrown. - try { - mService.testSandboxIsolatedProcess(); - } catch (RemoteException e) { - return false; - } - return true; - } - - @VisibleForTesting - public void testSandboxMinijail() { - // When testing minijail, we will call a system call which is blocked by minijail. In that - // case, the FfmpegDecoderService will be disconnected, we can check the connection status - // to make sure if the minijail works or not. - try { - mService.testSandboxMinijail(); - } catch (RemoteException e) { - } - } -} diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java deleted file mode 100644 index 0172d817..00000000 --- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ffmpeg; - -import android.app.Service; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.AssetFileDescriptor; -import android.os.AsyncTask; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.util.Log; -import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioDecoder; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** Ffmpeg based audio decoder service. It should be isolatedProcess due to security reason. */ -public class FfmpegDecoderService extends Service { - private static final String TAG = "FfmpegDecoderService"; - private static final boolean DEBUG = false; - - private static final String POLICY_FILE = "whitelist.policy"; - - private static final long MINIJAIL_SETUP_WAIT_TIMEOUT_MS = 5000; - - private static boolean sLibraryLoaded = true; - - static { - try { - System.loadLibrary("minijail_jni"); - } catch (Exception | Error e) { - Log.e(TAG, "Load minijail failed:", e); - sLibraryLoaded = false; - } - } - - private FfmpegDecoder mBinder = new FfmpegDecoder(); - private volatile Object mMinijailSetupMonitor = new Object(); - // @GuardedBy("mMinijailSetupMonitor") - private volatile Boolean mMinijailSetup; - - @Override - public void onCreate() { - if (sLibraryLoaded) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - synchronized (mMinijailSetupMonitor) { - int pipeFd = getPolicyPipeFd(); - if (pipeFd <= 0) { - Log.e(TAG, "fail to open policy file"); - mMinijailSetup = false; - } else { - nativeSetupMinijail(pipeFd); - mMinijailSetup = true; - if (DEBUG) Log.d(TAG, "Minijail setup successfully"); - } - mMinijailSetupMonitor.notify(); - } - return null; - } - }.execute(); - } else { - synchronized (mMinijailSetupMonitor) { - mMinijailSetup = false; - mMinijailSetupMonitor.notify(); - } - } - super.onCreate(); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - private int getPolicyPipeFd() { - try { - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - final ParcelFileDescriptor.AutoCloseOutputStream outputStream = - new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]); - final AssetFileDescriptor policyFile = getAssets().openFd("whitelist.policy"); - final byte[] buffer = new byte[2048]; - final FileInputStream policyStream = policyFile.createInputStream(); - while (true) { - int bytesRead = policyStream.read(buffer); - if (bytesRead == -1) break; - outputStream.write(buffer, 0, bytesRead); - } - policyStream.close(); - outputStream.close(); - return pipe[0].detachFd(); - } catch (IOException e) { - Log.e(TAG, "Policy file not found:" + e); - } - return -1; - } - - private final class FfmpegDecoder extends IFfmpegDecoder.Stub { - FfmpegAudioDecoder mDecoder; - - @Override - public boolean isAvailable() { - return isMinijailSetupDone() && FfmpegAudioDecoder.isAvailable(); - } - - @Override - public void create() { - mDecoder = new FfmpegAudioDecoder(FfmpegDecoderService.this); - } - - @Override - public void release() { - if (mDecoder != null) { - mDecoder.release(); - mDecoder = null; - } - } - - @Override - public void decode(long timeUs, byte[] sample) { - if (!isMinijailSetupDone()) { - // If minijail is not setup, we don't run decode for better security. - return; - } - mDecoder.decode(timeUs, sample); - } - - @Override - public void resetDecoderState(String mimetype) { - mDecoder.resetDecoderState(mimetype); - } - - @Override - public byte[] getDecodedSample() { - ByteBuffer decodedBuffer = mDecoder.getDecodedSample(); - byte[] ret = new byte[decodedBuffer.limit()]; - decodedBuffer.get(ret, 0, ret.length); - return ret; - } - - @Override - public long getDecodedTimeUs() { - return mDecoder.getDecodedTimeUs(); - } - - private boolean isMinijailSetupDone() { - synchronized (mMinijailSetupMonitor) { - if (DEBUG) Log.d(TAG, "mMinijailSetup in isAvailable(): " + mMinijailSetup); - if (mMinijailSetup == null) { - try { - if (DEBUG) Log.d(TAG, "Wait till Minijail setup is done"); - mMinijailSetupMonitor.wait(MINIJAIL_SETUP_WAIT_TIMEOUT_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - return mMinijailSetup != null && mMinijailSetup; - } - } - - @Override - public void testSandboxIsolatedProcess() { - if (!isMinijailSetupDone()) { - // If minijail is not setup, we return directly to make the test fail. - return; - } - if (FfmpegDecoderService.this.checkSelfPermission("android.permission.INTERNET") - == PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Shouldn't have the permission of internet"); - } - } - - @Override - public void testSandboxMinijail() { - if (!isMinijailSetupDone()) { - // If minijail is not setup, we return directly to make the test fail. - return; - } - nativeTestMinijail(); - } - } - - private native void nativeSetupMinijail(int policyFd); - - private native void nativeTestMinijail(); -} diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl b/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl deleted file mode 100644 index ed053790..00000000 --- a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.exoplayer.ffmpeg; - -interface IFfmpegDecoder { - boolean isAvailable(); - void create(); - void release(); - void resetDecoderState(String mimetype); - void decode(long timeUs, in byte[] sample); - byte[] getDecodedSample(); - long getDecodedTimeUs(); - void testSandboxIsolatedProcess(); - void testSandboxMinijail(); -} \ No newline at end of file diff --git a/src/com/android/tv/tuner/layout/ScaledLayout.java b/src/com/android/tv/tuner/layout/ScaledLayout.java deleted file mode 100644 index dd92b641..00000000 --- a/src/com/android/tv/tuner/layout/ScaledLayout.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.layout; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Display; -import android.view.View; -import android.view.ViewGroup; -import com.android.tv.tuner.R; -import java.util.Arrays; -import java.util.Comparator; - -/** A layout that scales its children using the given percentage value. */ -public class ScaledLayout extends ViewGroup { - private static final String TAG = "ScaledLayout"; - private static final boolean DEBUG = false; - private static final Comparator mRectTopLeftSorter = - new Comparator() { - @Override - public int compare(Rect lhs, Rect rhs) { - if (lhs.top != rhs.top) { - return lhs.top - rhs.top; - } else { - return lhs.left - rhs.left; - } - } - }; - - private Rect[] mRectArray; - private final int mMaxWidth; - private final int mMaxHeight; - - public ScaledLayout(Context context) { - this(context, null); - } - - public ScaledLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ScaledLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - Point size = new Point(); - DisplayManager displayManager = - (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); - Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - display.getRealSize(size); - mMaxWidth = size.x; - mMaxHeight = size.y; - } - - /** - * ScaledLayoutParams stores the four scale factors.
- * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) % - * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) % - *
- * In XML, for example, - * - *

{@code
-     * 
-     * }
- */ - public static class ScaledLayoutParams extends ViewGroup.LayoutParams { - public static final float SCALE_UNSPECIFIED = -1; - public final float scaleStartRow; - public final float scaleEndRow; - public final float scaleStartCol; - public final float scaleEndCol; - - public ScaledLayoutParams( - float scaleStartRow, float scaleEndRow, float scaleStartCol, float scaleEndCol) { - super(MATCH_PARENT, MATCH_PARENT); - this.scaleStartRow = scaleStartRow; - this.scaleEndRow = scaleEndRow; - this.scaleStartCol = scaleStartCol; - this.scaleEndCol = scaleEndCol; - } - - public ScaledLayoutParams(Context context, AttributeSet attrs) { - super(MATCH_PARENT, MATCH_PARENT); - TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout); - scaleStartRow = - array.getFloat( - R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED); - scaleEndRow = - array.getFloat( - R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED); - scaleStartCol = - array.getFloat( - R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED); - scaleEndCol = - array.getFloat( - R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED); - array.recycle(); - } - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new ScaledLayoutParams(getContext(), attrs); - } - - @Override - protected boolean checkLayoutParams(LayoutParams p) { - return (p instanceof ScaledLayoutParams); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - int width = widthSpecSize - getPaddingLeft() - getPaddingRight(); - int height = heightSpecSize - getPaddingTop() - getPaddingBottom(); - if (DEBUG) { - Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height)); - } - int count = getChildCount(); - mRectArray = new Rect[count]; - for (int i = 0; i < count; ++i) { - View child = getChildAt(i); - ViewGroup.LayoutParams params = child.getLayoutParams(); - float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol; - if (!(params instanceof ScaledLayoutParams)) { - throw new RuntimeException( - "A child of ScaledLayout cannot have the UNSPECIFIED scale factors"); - } - scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow; - scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow; - scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol; - scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol; - if (scaleStartRow < 0 || scaleStartRow > 1) { - throw new RuntimeException( - "A child of ScaledLayout should have a range of " - + "scaleStartRow between 0 and 1"); - } - if (scaleEndRow < scaleStartRow || scaleStartRow > 1) { - throw new RuntimeException( - "A child of ScaledLayout should have a range of " - + "scaleEndRow between scaleStartRow and 1"); - } - if (scaleEndCol < 0 || scaleEndCol > 1) { - throw new RuntimeException( - "A child of ScaledLayout should have a range of " - + "scaleStartCol between 0 and 1"); - } - if (scaleEndCol < scaleStartCol || scaleEndCol > 1) { - throw new RuntimeException( - "A child of ScaledLayout should have a range of " - + "scaleEndCol between scaleStartCol and 1"); - } - if (DEBUG) { - Log.d( - TAG, - String.format( - "onMeasure child scaleStartRow: %f scaleEndRow: %f " - + "scaleStartCol: %f scaleEndCol: %f", - scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol)); - } - mRectArray[i] = - new Rect( - (int) (scaleStartCol * width), - (int) (scaleStartRow * height), - (int) (scaleEndCol * width), - (int) (scaleEndRow * height)); - int scaleWidth = (int) (width * (scaleEndCol - scaleStartCol)); - int childWidthSpec = - MeasureSpec.makeMeasureSpec( - scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY); - int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - child.measure(childWidthSpec, childHeightSpec); - - // If the height of the measured child view is bigger than the height of the calculated - // region by the given ScaleLayoutParams, the height of the region should be increased - // to fit the size of the child view. - if (child.getMeasuredHeight() > mRectArray[i].height()) { - int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height(); - overflowedHeight = (overflowedHeight + 1) / 2; - mRectArray[i].bottom += overflowedHeight; - mRectArray[i].top -= overflowedHeight; - if (mRectArray[i].top < 0) { - mRectArray[i].bottom -= mRectArray[i].top; - mRectArray[i].top = 0; - } - if (mRectArray[i].bottom > height) { - mRectArray[i].top -= mRectArray[i].bottom - height; - mRectArray[i].bottom = height; - } - } - int scaleHeight = (int) (height * (scaleEndRow - scaleStartRow)); - childHeightSpec = - MeasureSpec.makeMeasureSpec( - scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, - MeasureSpec.EXACTLY); - child.measure(childWidthSpec, childHeightSpec); - } - - // Avoid overlapping rectangles. - // Step 1. Sort rectangles by position (top-left). - int visibleRectCount = 0; - int[] visibleRectGroup = new int[count]; - Rect[] visibleRectArray = new Rect[count]; - for (int i = 0; i < count; ++i) { - if (getChildAt(i).getVisibility() == View.VISIBLE) { - visibleRectGroup[visibleRectCount] = visibleRectCount; - visibleRectArray[visibleRectCount] = mRectArray[i]; - ++visibleRectCount; - } - } - Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter); - - // Step 2. Move down if there are overlapping rectangles. - for (int i = 0; i < visibleRectCount - 1; ++i) { - for (int j = i + 1; j < visibleRectCount; ++j) { - if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) { - visibleRectGroup[j] = visibleRectGroup[i]; - visibleRectArray[j].set( - visibleRectArray[j].left, - visibleRectArray[i].bottom, - visibleRectArray[j].right, - visibleRectArray[i].bottom + visibleRectArray[j].height()); - } - } - } - - // Step 3. Move up if there is any overflowed rectangle. - for (int i = visibleRectCount - 1; i >= 0; --i) { - if (visibleRectArray[i].bottom > height) { - int overflowedHeight = visibleRectArray[i].bottom - height; - for (int j = 0; j <= i; ++j) { - if (visibleRectGroup[i] == visibleRectGroup[j]) { - visibleRectArray[j].set( - visibleRectArray[j].left, - visibleRectArray[j].top - overflowedHeight, - visibleRectArray[j].right, - visibleRectArray[j].bottom - overflowedHeight); - } - } - } - } - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int paddingLeft = getPaddingLeft(); - int paddingTop = getPaddingTop(); - int count = getChildCount(); - for (int i = 0; i < count; ++i) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - int childLeft = paddingLeft + mRectArray[i].left; - int childTop = paddingTop + mRectArray[i].top; - int childBottom = paddingLeft + mRectArray[i].bottom; - int childRight = paddingTop + mRectArray[i].right; - if (DEBUG) { - Log.d( - TAG, - String.format( - "layoutChild bottom: %d left: %d right: %d top: %d", - childBottom, childLeft, childRight, childTop)); - } - child.layout(childLeft, childTop, childRight, childBottom); - } - } - } -} diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java deleted file mode 100644 index 75d1c34c..00000000 --- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import com.android.tv.common.ui.setup.SetupGuidedStepFragment; -import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.tuner.R; -import java.util.List; - -/** A fragment for connection type selection. */ -public class ConnectionTypeFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = - "com.android.tv.tuner.setup.ConnectionTypeFragment"; - - @Override - public void onCreate(Bundle savedInstanceState) { - ((TunerSetupActivity) getActivity()).generateTunerHal(); - super.onCreate(savedInstanceState); - } - - @Override - public void onResume() { - ((TunerSetupActivity) getActivity()).generateTunerHal(); - super.onResume(); - } - - @Override - public void onDestroy() { - ((TunerSetupActivity) getActivity()).clearTunerHal(); - super.onDestroy(); - } - - @Override - protected SetupGuidedStepFragment onCreateContentFragment() { - return new ContentFragment(); - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - - @Override - protected boolean needsDoneButton() { - return false; - } - - public static class ContentFragment extends SetupGuidedStepFragment { - - @NonNull - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - return new Guidance( - getString(R.string.ut_connection_title), - getString(R.string.ut_connection_description), - getString(R.string.ut_setup_breadcrumb), - null); - } - - @Override - public void onCreateActions( - @NonNull List actions, Bundle savedInstanceState) { - String[] choices = getResources().getStringArray(R.array.ut_connection_choices); - int length = choices.length - 1; - int startOffset = 0; - for (int i = 0; i < length; ++i) { - actions.add( - new GuidedAction.Builder(getActivity()) - .id(startOffset + i) - .title(choices[i]) - .build()); - } - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - } -} diff --git a/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/src/com/android/tv/tuner/setup/PostalCodeFragment.java deleted file mode 100644 index fbf03909..00000000 --- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import android.support.v17.leanback.widget.GuidedActionsStylist; -import android.text.InputFilter; -import android.text.InputFilter.AllCaps; -import android.view.View; -import android.widget.TextView; -import com.android.tv.R; -import com.android.tv.common.ui.setup.SetupGuidedStepFragment; -import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.tuner.util.PostalCodeUtils; -import com.android.tv.util.LocationUtils; -import java.util.List; - -/** A fragment for initial screen. */ -public class PostalCodeFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.PostalCodeFragment"; - private static final int VIEW_TYPE_EDITABLE = 1; - - @Override - protected SetupGuidedStepFragment onCreateContentFragment() { - ContentFragment fragment = new ContentFragment(); - Bundle arguments = new Bundle(); - arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true); - fragment.setArguments(arguments); - return fragment; - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - - @Override - protected boolean needsDoneButton() { - return true; - } - - @Override - protected boolean needsSkipButton() { - return true; - } - - @Override - protected void setOnClickAction(View view, final String category, final int actionId) { - if (actionId == ACTION_DONE) { - view.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - CharSequence postalCode = - ((ContentFragment) getContentFragment()).mEditAction.getTitle(); - String region = LocationUtils.getCurrentCountry(getContext()); - if (postalCode != null && PostalCodeUtils.matches(postalCode, region)) { - PostalCodeUtils.setLastPostalCode( - getContext(), postalCode.toString()); - onActionClick(category, actionId); - } else { - ContentFragment contentFragment = - (ContentFragment) getContentFragment(); - contentFragment.mEditAction.setDescription( - getString(R.string.postal_code_invalid_warning)); - contentFragment.notifyActionChanged(0); - contentFragment.mEditedActionView.performClick(); - } - } - }); - } else if (actionId == ACTION_SKIP) { - super.setOnClickAction(view, category, ACTION_SKIP); - } - } - - public static class ContentFragment extends SetupGuidedStepFragment { - private GuidedAction mEditAction; - private View mEditedActionView; - private View mDoneActionView; - private boolean mProceed; - - @Override - public void onGuidedActionFocused(GuidedAction action) { - if (action.equals(mEditAction)) { - if (mProceed) { - // "NEXT" in IME was just clicked, moves focus to Done button. - if (mDoneActionView == null) { - mDoneActionView = getActivity().findViewById(R.id.button_done); - } - mDoneActionView.requestFocus(); - mProceed = false; - } else { - // Directly opens IME to input postal/zip code. - if (mEditedActionView == null) { - int maxLength = PostalCodeUtils.getRegionMaxLength(getContext()); - mEditedActionView = getView().findViewById(R.id.guidedactions_editable); - ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title)) - .setFilters( - new InputFilter[] { - new InputFilter.LengthFilter(maxLength), new AllCaps() - }); - } - mEditedActionView.performClick(); - } - } - } - - @Override - public long onGuidedActionEditedAndProceed(GuidedAction action) { - mProceed = true; - return 0; - } - - @NonNull - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title = getString(R.string.postal_code_guidance_title); - String description = getString(R.string.postal_code_guidance_description); - String breadcrumb = getString(R.string.ut_setup_breadcrumb); - return new Guidance(title, description, breadcrumb, null); - } - - @Override - public void onCreateActions( - @NonNull List actions, Bundle savedInstanceState) { - String description = getString(R.string.postal_code_action_description); - mEditAction = - new GuidedAction.Builder(getActivity()) - .id(0) - .editable(true) - .description(description) - .build(); - actions.add(mEditAction); - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - - @Override - public GuidedActionsStylist onCreateActionsStylist() { - return new GuidedActionsStylist() { - @Override - public int getItemViewType(GuidedAction action) { - if (action.isEditable()) { - return VIEW_TYPE_EDITABLE; - } - return super.getItemViewType(action); - } - - @Override - public int onProvideItemLayoutId(int viewType) { - if (viewType == VIEW_TYPE_EDITABLE) { - return R.layout.guided_action_editable; - } - return super.onProvideItemLayoutId(viewType); - } - }; - } - } -} diff --git a/src/com/android/tv/tuner/setup/ScanFragment.java b/src/com/android/tv/tuner/setup/ScanFragment.java deleted file mode 100644 index 044b0d26..00000000 --- a/src/com/android/tv/tuner/setup/ScanFragment.java +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.animation.LayoutTransition; -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Context; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.ConditionVariable; -import android.os.Handler; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.ui.setup.SetupFragment; -import com.android.tv.tuner.ChannelScanFileParser; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.data.PsipData; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Channel; -import com.android.tv.tuner.source.FileTsStreamer; -import com.android.tv.tuner.source.TsDataSource; -import com.android.tv.tuner.source.TsStreamer; -import com.android.tv.tuner.source.TunerTsStreamer; -import com.android.tv.tuner.tvinput.ChannelDataManager; -import com.android.tv.tuner.tvinput.EventDetector; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** A fragment for scanning channels. */ -public class ScanFragment extends SetupFragment { - private static final String TAG = "ScanFragment"; - private static final boolean DEBUG = false; - - // In the fake mode, the connection to antenna or cable is not necessary. - // Instead dummy channels are added. - private static final boolean FAKE_MODE = false; - - private static final String VCTLESS_CHANNEL_NAME_FORMAT = "RF%d-%d"; - - public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ScanFragment"; - public static final int ACTION_CANCEL = 1; - public static final int ACTION_FINISH = 2; - - public static final String EXTRA_FOR_CHANNEL_SCAN_FILE = "scan_file_choice"; - - private static final long CHANNEL_SCAN_SHOW_DELAY_MS = 10000; - private static final long CHANNEL_SCAN_PERIOD_MS = 4000; - private static final long SHOW_PROGRESS_DIALOG_DELAY_MS = 300; - - // Build channels out of the locally stored TS streams. - private static final boolean SCAN_LOCAL_STREAMS = true; - - private ChannelDataManager mChannelDataManager; - private ChannelScanTask mChannelScanTask; - private ProgressBar mProgressBar; - private TextView mScanningMessage; - private View mChannelHolder; - private ChannelAdapter mAdapter; - private volatile boolean mChannelListVisible; - private Button mCancelButton; - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreateView"); - View view = super.onCreateView(inflater, container, savedInstanceState); - mChannelDataManager = new ChannelDataManager(getActivity()); - mChannelDataManager.checkDataVersion(getActivity()); - mAdapter = new ChannelAdapter(); - mProgressBar = (ProgressBar) view.findViewById(R.id.tune_progress); - mScanningMessage = (TextView) view.findViewById(R.id.tune_description); - ListView channelList = (ListView) view.findViewById(R.id.channel_list); - channelList.setAdapter(mAdapter); - channelList.setOnItemClickListener(null); - ViewGroup progressHolder = (ViewGroup) view.findViewById(R.id.progress_holder); - LayoutTransition transition = new LayoutTransition(); - transition.enableTransitionType(LayoutTransition.CHANGING); - progressHolder.setLayoutTransition(transition); - mChannelHolder = view.findViewById(R.id.channel_holder); - mCancelButton = (Button) view.findViewById(R.id.tune_cancel); - mCancelButton.setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View v) { - finishScan(false); - } - }); - Bundle args = getArguments(); - int tunerType = (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0)); - // TODO: Handle the case when the fragment is restored. - startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0)); - TextView scanTitleView = (TextView) view.findViewById(R.id.tune_title); - switch (tunerType) { - case TunerHal.TUNER_TYPE_USB: - scanTitleView.setText(R.string.ut_channel_scan); - break; - case TunerHal.TUNER_TYPE_NETWORK: - scanTitleView.setText(R.string.nt_channel_scan); - break; - default: - scanTitleView.setText(R.string.bt_channel_scan); - } - return view; - } - - @Override - protected int getLayoutResourceId() { - return R.layout.ut_channel_scan; - } - - @Override - protected int[] getParentIdsForDelay() { - return new int[] {R.id.progress_holder}; - } - - private void startScan(int channelMapId) { - mChannelScanTask = new ChannelScanTask(channelMapId); - mChannelScanTask.execute(); - } - - @Override - public void onPause() { - Log.d(TAG, "onPause"); - if (mChannelScanTask != null) { - // Ensure scan task will stop. - Log.w(TAG, "The activity went to the background. Stopping channel scan."); - mChannelScanTask.stopScan(); - } - super.onPause(); - } - - /** - * Finishes the current scan thread. This fragment will be popped after the scan thread ends. - * - * @param cancel a flag which indicates the scan is canceled or not. - */ - public void finishScan(boolean cancel) { - if (mChannelScanTask != null) { - mChannelScanTask.cancelScan(cancel); - - // Notifies a user of waiting to finish the scanning process. - new Handler() - .postDelayed( - new Runnable() { - @Override - public void run() { - if (mChannelScanTask != null) { - mChannelScanTask.showFinishingProgressDialog(); - } - } - }, - SHOW_PROGRESS_DIALOG_DELAY_MS); - - // Hides the cancel button. - mCancelButton.setEnabled(false); - } - } - - private class ChannelAdapter extends BaseAdapter { - private final ArrayList mChannels; - - public ChannelAdapter() { - mChannels = new ArrayList<>(); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int pos) { - return false; - } - - @Override - public int getCount() { - return mChannels.size(); - } - - @Override - public Object getItem(int pos) { - return pos; - } - - @Override - public long getItemId(int pos) { - return pos; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Context context = parent.getContext(); - - if (convertView == null) { - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.ut_channel_list, parent, false); - } - - TextView channelNum = (TextView) convertView.findViewById(R.id.channel_num); - channelNum.setText(mChannels.get(position).getDisplayNumber()); - - TextView channelName = (TextView) convertView.findViewById(R.id.channel_name); - channelName.setText(mChannels.get(position).getName()); - return convertView; - } - - public void add(TunerChannel channel) { - mChannels.add(channel); - notifyDataSetChanged(); - } - } - - private class ChannelScanTask extends AsyncTask - implements EventDetector.EventListener, ChannelDataManager.ChannelScanListener { - private static final int MAX_PROGRESS = 100; - - private final Activity mActivity; - private final int mChannelMapId; - private final TsStreamer mScanTsStreamer; - private final TsStreamer mFileTsStreamer; - private final ConditionVariable mConditionStopped; - - private final List mScanChannelList = new ArrayList<>(); - private boolean mIsCanceled; - private boolean mIsFinished; - private ProgressDialog mFinishingProgressDialog; - private CountDownLatch mLatch; - - public ChannelScanTask(int channelMapId) { - mActivity = getActivity(); - mChannelMapId = channelMapId; - if (FAKE_MODE) { - mScanTsStreamer = new FakeTsStreamer(this); - } else { - TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal(); - if (hal == null) { - throw new RuntimeException("Failed to open a DVB device"); - } - mScanTsStreamer = new TunerTsStreamer(hal, this); - } - mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this, mActivity) : null; - mConditionStopped = new ConditionVariable(); - mChannelDataManager.setChannelScanListener(this, new Handler()); - } - - private void maybeSetChannelListVisible() { - mActivity.runOnUiThread( - new Runnable() { - @Override - public void run() { - int channelsFound = mAdapter.getCount(); - if (!mChannelListVisible && channelsFound > 0) { - String format = - getResources() - .getQuantityString( - R.plurals.ut_channel_scan_message, - channelsFound, - channelsFound); - mScanningMessage.setText(String.format(format, channelsFound)); - mChannelHolder.setVisibility(View.VISIBLE); - mChannelListVisible = true; - } - } - }); - } - - private void addChannel(final TunerChannel channel) { - mActivity.runOnUiThread( - new Runnable() { - @Override - public void run() { - mAdapter.add(channel); - if (mChannelListVisible) { - int channelsFound = mAdapter.getCount(); - String format = - getResources() - .getQuantityString( - R.plurals.ut_channel_scan_message, - channelsFound, - channelsFound); - mScanningMessage.setText(String.format(format, channelsFound)); - } - } - }); - } - - @Override - protected Void doInBackground(Void... params) { - mScanChannelList.clear(); - if (SCAN_LOCAL_STREAMS) { - FileTsStreamer.addLocalStreamFiles(mScanChannelList); - } - mScanChannelList.addAll( - ChannelScanFileParser.parseScanFile( - getResources().openRawResource(mChannelMapId))); - scanChannels(); - return null; - } - - @Override - protected void onCancelled() { - SoftPreconditions.checkState(false, TAG, "call cancelScan instead of cancel"); - } - - @Override - protected void onProgressUpdate(Integer... values) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mProgressBar.setProgress(values[0], true); - } else { - mProgressBar.setProgress(values[0]); - } - } - - private void stopScan() { - if (mLatch != null) { - mLatch.countDown(); - } - mConditionStopped.open(); - } - - private void cancelScan(boolean cancel) { - mIsCanceled = cancel; - stopScan(); - } - - private void scanChannels() { - if (DEBUG) Log.i(TAG, "Channel scan starting"); - mChannelDataManager.notifyScanStarted(); - - long startMs = System.currentTimeMillis(); - int i = 1; - for (ChannelScanFileParser.ScanChannel scanChannel : mScanChannelList) { - int frequency = scanChannel.frequency; - String modulation = scanChannel.modulation; - Log.i(TAG, "Tuning to " + frequency + " " + modulation); - - TsStreamer streamer = getStreamer(scanChannel.type); - SoftPreconditions.checkNotNull(streamer); - if (streamer != null && streamer.startStream(scanChannel)) { - mLatch = new CountDownLatch(1); - try { - mLatch.await(CHANNEL_SCAN_PERIOD_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Log.e( - TAG, - "The current thread is interrupted during scanChannels(). " - + "The TS stream is stopped earlier than expected.", - e); - } - streamer.stopStream(); - - addChannelsWithoutVct(scanChannel); - if (System.currentTimeMillis() > startMs + CHANNEL_SCAN_SHOW_DELAY_MS - && !mChannelListVisible) { - maybeSetChannelListVisible(); - } - } - if (mConditionStopped.block(-1)) { - break; - } - publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size()); - } - mChannelDataManager.notifyScanCompleted(); - if (!mConditionStopped.block(-1)) { - publishProgress(MAX_PROGRESS); - } - if (DEBUG) Log.i(TAG, "Channel scan ended"); - } - - private void addChannelsWithoutVct(ChannelScanFileParser.ScanChannel scanChannel) { - if (scanChannel.radioFrequencyNumber == null - || !(mScanTsStreamer instanceof TunerTsStreamer)) { - return; - } - for (TunerChannel tunerChannel : - ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) { - if ((tunerChannel.getVideoPid() != TunerChannel.INVALID_PID) - && (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) { - tunerChannel.setFrequency(scanChannel.frequency); - tunerChannel.setModulation(scanChannel.modulation); - tunerChannel.setShortName( - String.format( - Locale.US, - VCTLESS_CHANNEL_NAME_FORMAT, - scanChannel.radioFrequencyNumber, - tunerChannel.getProgramNumber())); - tunerChannel.setVirtualMajor(scanChannel.radioFrequencyNumber); - tunerChannel.setVirtualMinor(tunerChannel.getProgramNumber()); - onChannelDetected(tunerChannel, true); - } - } - } - - private TsStreamer getStreamer(int type) { - switch (type) { - case Channel.TYPE_TUNER: - return mScanTsStreamer; - case Channel.TYPE_FILE: - return mFileTsStreamer; - default: - return null; - } - } - - @Override - public void onEventDetected(TunerChannel channel, List items) { - mChannelDataManager.notifyEventDetected(channel, items); - } - - @Override - public void onChannelScanDone() { - if (mLatch != null) { - mLatch.countDown(); - } - } - - @Override - public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { - if (channelArrivedAtFirstTime) { - Log.i(TAG, "Found channel " + channel); - } - if (channelArrivedAtFirstTime && channel.hasAudio()) { - // Playbacks with video-only stream have not been tested yet. - // No video-only channel has been found. - addChannel(channel); - mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); - } - } - - public void showFinishingProgressDialog() { - // Show a progress dialog to wait for the scanning process if it's not done yet. - if (!mIsFinished && mFinishingProgressDialog == null) { - mFinishingProgressDialog = - ProgressDialog.show( - mActivity, "", getString(R.string.ut_setup_cancel), true, false); - } - } - - @Override - public void onChannelHandlingDone() { - mChannelDataManager.setCurrentVersion(mActivity); - mChannelDataManager.releaseSafely(); - mIsFinished = true; - TunerPreferences.setScannedChannelCount( - mActivity.getApplicationContext(), - mChannelDataManager.getScannedChannelCount()); - // Cancel a previously shown notification. - TunerSetupActivity.cancelNotification(mActivity.getApplicationContext()); - // Mark scan as done - TunerPreferences.setScanDone(mActivity.getApplicationContext()); - // finishing will be done manually. - if (mFinishingProgressDialog != null) { - mFinishingProgressDialog.dismiss(); - } - // If the fragment is not resumed, the next fragment (scan result page) can't be - // displayed. In that case, just close the activity. - if (isResumed()) { - onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH); - } else if (getActivity() != null) { - getActivity().finish(); - } - mChannelScanTask = null; - } - } - - private static class FakeTsStreamer implements TsStreamer { - private final EventDetector.EventListener mEventListener; - private int mProgramNumber = 0; - - FakeTsStreamer(EventDetector.EventListener eventListener) { - mEventListener = eventListener; - } - - @Override - public boolean startStream(ChannelScanFileParser.ScanChannel channel) { - if (++mProgramNumber % 2 == 1) { - return true; - } - final String displayNumber = Integer.toString(mProgramNumber); - final String name = "Channel-" + mProgramNumber; - mEventListener.onChannelDetected( - new TunerChannel(mProgramNumber, new ArrayList<>()) { - @Override - public String getDisplayNumber() { - return displayNumber; - } - - @Override - public String getName() { - return name; - } - }, - true); - return true; - } - - @Override - public boolean startStream(TunerChannel channel) { - return false; - } - - @Override - public void stopStream() {} - - @Override - public TsDataSource createDataSource() { - return null; - } - } -} diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java deleted file mode 100644 index a6160ef1..00000000 --- a/src/com/android/tv/tuner/setup/ScanResultFragment.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import com.android.tv.common.ui.setup.SetupGuidedStepFragment; -import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.TunerPreferences; -import java.util.List; - -/** A fragment for initial screen. */ -public class ScanResultFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ScanResultFragment"; - - @Override - protected SetupGuidedStepFragment onCreateContentFragment() { - return new ContentFragment(); - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - - @Override - protected boolean needsDoneButton() { - return false; - } - - public static class ContentFragment extends SetupGuidedStepFragment { - private int mChannelCountOnPreference; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - mChannelCountOnPreference = TunerPreferences.getScannedChannelCount(context); - } - - @NonNull - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title; - String description; - String breadcrumb; - if (mChannelCountOnPreference > 0) { - Resources res = getResources(); - title = - res.getQuantityString( - R.plurals.ut_result_found_title, - mChannelCountOnPreference, - mChannelCountOnPreference); - description = - res.getQuantityString( - R.plurals.ut_result_found_description, - mChannelCountOnPreference, - mChannelCountOnPreference); - breadcrumb = null; - } else { - Bundle args = getArguments(); - int tunerType = - (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0)); - title = getString(R.string.ut_result_not_found_title); - switch (tunerType) { - case TunerHal.TUNER_TYPE_USB: - description = getString(R.string.ut_result_not_found_description); - break; - case TunerHal.TUNER_TYPE_NETWORK: - description = getString(R.string.nt_result_not_found_description); - break; - default: - description = getString(R.string.bt_result_not_found_description); - } - breadcrumb = getString(R.string.ut_setup_breadcrumb); - } - return new Guidance(title, description, breadcrumb, null); - } - - @Override - public void onCreateActions( - @NonNull List actions, Bundle savedInstanceState) { - String[] choices; - int doneActionIndex; - if (mChannelCountOnPreference > 0) { - choices = getResources().getStringArray(R.array.ut_result_found_choices); - doneActionIndex = 0; - } else { - choices = getResources().getStringArray(R.array.ut_result_not_found_choices); - doneActionIndex = 1; - } - for (int i = 0; i < choices.length; ++i) { - if (i == doneActionIndex) { - actions.add( - new GuidedAction.Builder(getActivity()) - .id(ACTION_DONE) - .title(choices[i]) - .build()); - } else { - actions.add( - new GuidedAction.Builder(getActivity()) - .id(i) - .title(choices[i]) - .build()); - } - } - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - } -} diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java deleted file mode 100644 index 58cfc927..00000000 --- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.tv.TvContract; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.support.annotation.WorkerThread; -import android.support.v4.app.NotificationCompat; -import android.text.TextUtils; -import android.util.Log; -import android.view.KeyEvent; -import android.widget.Toast; -import com.android.tv.Features; -import com.android.tv.TvApplication; -import com.android.tv.common.AutoCloseableUtils; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonConstants; -import com.android.tv.common.TvCommonUtils; -import com.android.tv.common.ui.setup.SetupActivity; -import com.android.tv.common.ui.setup.SetupFragment; -import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.experiments.Experiments; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.tvinput.TunerTvInputService; -import com.android.tv.tuner.util.PostalCodeUtils; -import java.util.concurrent.Executor; - -/** An activity that serves tuner setup process. */ -public class TunerSetupActivity extends SetupActivity { - private static final String TAG = "TunerSetupActivity"; - private static final boolean DEBUG = false; - - /** Key for passing tuner type to sub-fragments. */ - public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType"; - - // For the notification. - private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity"; - private static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel"; - private static final String NOTIFY_TAG = "TunerSetup"; - private static final int NOTIFY_ID = 1000; - private static final String TAG_DRAWABLE = "drawable"; - private static final String TAG_ICON = "ic_launcher_s"; - private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1; - - private static final int CHANNEL_MAP_SCAN_FILE[] = { - R.raw.ut_us_atsc_center_frequencies_8vsb, - R.raw.ut_us_cable_standard_center_frequencies_qam256, - R.raw.ut_us_all, - R.raw.ut_kr_atsc_center_frequencies_8vsb, - R.raw.ut_kr_cable_standard_center_frequencies_qam256, - R.raw.ut_kr_all, - R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256, - R.raw.ut_euro_dvbt_all, - R.raw.ut_euro_dvbt_all, - R.raw.ut_euro_dvbt_all - }; - - private ScanFragment mLastScanFragment; - private Integer mTunerType; - private TunerHalFactory mTunerHalFactory; - private boolean mNeedToShowPostalCodeFragment; - private String mPreviousPostalCode; - - @Override - protected void onCreate(Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreate"); - new AsyncTask() { - @Override - protected Integer doInBackground(Void... arg0) { - return TunerHal.getTunerTypeAndCount(TunerSetupActivity.this).first; - } - - @Override - protected void onPostExecute(Integer result) { - if (!TunerSetupActivity.this.isDestroyed()) { - mTunerType = result; - if (result == null) { - finish(); - } else { - showInitialFragment(); - } - } - } - }.execute(); - TvApplication.setCurrentRunningProcess(this, false); - super.onCreate(savedInstanceState); - // TODO: check {@link shouldShowRequestPermissionRationale}. - if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - // No need to check the request result. - requestPermissions( - new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, - PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); - } - mTunerHalFactory = new TunerHalFactory(getApplicationContext()); - try { - // Updating postal code takes time, therefore we called it here for "warm-up". - mPreviousPostalCode = PostalCodeUtils.getLastPostalCode(this); - PostalCodeUtils.setLastPostalCode(this, null); - PostalCodeUtils.updatePostalCode(this); - } catch (Exception e) { - // Do nothing. If the last known postal code is null, we'll show guided fragment to - // prompt users to input postal code before ConnectionTypeFragment is shown. - Log.i(TAG, "Can't get postal code:" + e); - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) { - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED - && Experiments.CLOUD_EPG.get()) { - try { - // Updating postal code takes time, therefore we should update postal code - // right after the permission is granted, so that the subsequent operations, - // especially EPG fetcher, could get the newly updated postal code. - PostalCodeUtils.updatePostalCode(this); - } catch (Exception e) { - // Do nothing - } - } - } - } - - @Override - protected Fragment onCreateInitialFragment() { - if (mTunerType != null) { - SetupFragment fragment = new WelcomeFragment(); - Bundle args = new Bundle(); - args.putInt(KEY_TUNER_TYPE, mTunerType); - fragment.setArguments(args); - fragment.setShortDistance( - SetupFragment.FRAGMENT_EXIT_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); - return fragment; - } else { - return null; - } - } - - @Override - protected boolean executeAction(String category, int actionId, Bundle params) { - switch (category) { - case WelcomeFragment.ACTION_CATEGORY: - switch (actionId) { - case SetupMultiPaneFragment.ACTION_DONE: - // If the scan was performed, then the result should be OK. - setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK); - finish(); - break; - default: - if (mNeedToShowPostalCodeFragment - || Features.ENABLE_CLOUD_EPG_REGION.isEnabled( - getApplicationContext()) - && TextUtils.isEmpty( - PostalCodeUtils.getLastPostalCode(this))) { - // We cannot get postal code automatically. Postal code input fragment - // should always be shown even if users have input some valid postal - // code in this activity before. - mNeedToShowPostalCodeFragment = true; - showPostalCodeFragment(); - } else { - showConnectionTypeFragment(); - } - break; - } - return true; - case PostalCodeFragment.ACTION_CATEGORY: - if (actionId == SetupMultiPaneFragment.ACTION_DONE - || actionId == SetupMultiPaneFragment.ACTION_SKIP) { - showConnectionTypeFragment(); - } - return true; - case ConnectionTypeFragment.ACTION_CATEGORY: - if (mTunerHalFactory.getOrCreate() == null) { - finish(); - Toast.makeText( - getApplicationContext(), - R.string.ut_channel_scan_tuner_unavailable, - Toast.LENGTH_LONG) - .show(); - return true; - } - mLastScanFragment = new ScanFragment(); - Bundle args1 = new Bundle(); - args1.putInt( - ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]); - args1.putInt(KEY_TUNER_TYPE, mTunerType); - mLastScanFragment.setArguments(args1); - showFragment(mLastScanFragment, true); - return true; - case ScanFragment.ACTION_CATEGORY: - switch (actionId) { - case ScanFragment.ACTION_CANCEL: - getFragmentManager().popBackStack(); - return true; - case ScanFragment.ACTION_FINISH: - mTunerHalFactory.clear(); - SetupFragment fragment = new ScanResultFragment(); - Bundle args2 = new Bundle(); - args2.putInt(KEY_TUNER_TYPE, mTunerType); - fragment.setArguments(args2); - fragment.setShortDistance( - SetupFragment.FRAGMENT_EXIT_TRANSITION - | SetupFragment.FRAGMENT_REENTER_TRANSITION); - showFragment(fragment, true); - return true; - } - break; - case ScanResultFragment.ACTION_CATEGORY: - switch (actionId) { - case SetupMultiPaneFragment.ACTION_DONE: - setResult(RESULT_OK); - finish(); - break; - default: - SetupFragment fragment = new ConnectionTypeFragment(); - fragment.setShortDistance( - SetupFragment.FRAGMENT_ENTER_TRANSITION - | SetupFragment.FRAGMENT_RETURN_TRANSITION); - showFragment(fragment, true); - break; - } - return true; - } - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - FragmentManager manager = getFragmentManager(); - int count = manager.getBackStackEntryCount(); - if (count > 0) { - String lastTag = manager.getBackStackEntryAt(count - 1).getName(); - if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) { - // Pops fragment including ScanFragment. - manager.popBackStack( - manager.getBackStackEntryAt(count - 2).getName(), - FragmentManager.POP_BACK_STACK_INCLUSIVE); - return true; - } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) { - mLastScanFragment.finishScan(true); - return true; - } - } - } - return super.onKeyUp(keyCode, event); - } - - @Override - public void onDestroy() { - if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) { - PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode); - } - super.onDestroy(); - } - - /** - * A callback to be invoked when the TvInputService is enabled or disabled. - * - * @param context a {@link Context} instance - * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled; otherwise - * {@code false} - */ - public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) { - // Send a notification for tuner setup if there's no channels and the tuner TV input - // setup has been not done. - boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context); - int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context); - if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) { - TunerPreferences.setShouldShowSetupActivity(context, true); - sendNotification(context, tunerType); - } else { - TunerPreferences.setShouldShowSetupActivity(context, false); - cancelNotification(context); - } - } - - /** - * Returns a {@link Intent} to launch the tuner TV input service. - * - * @param context a {@link Context} instance - */ - public static Intent createSetupActivity(Context context) { - String inputId = - TvContract.buildInputId( - new ComponentName( - context.getPackageName(), TunerTvInputService.class.getName())); - - // Make an intent to launch the setup activity of TV tuner input. - Intent intent = - TvCommonUtils.createSetupIntent( - new Intent(context, TunerSetupActivity.class), inputId); - intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId); - Intent tvActivityIntent = new Intent(); - tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME)); - intent.putExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent); - return intent; - } - - /** Gets the currently used tuner HAL. */ - TunerHal getTunerHal() { - return mTunerHalFactory.getOrCreate(); - } - - /** Generates tuner HAL. */ - void generateTunerHal() { - mTunerHalFactory.generate(); - } - - /** Clears the currently used tuner HAL. */ - void clearTunerHal() { - mTunerHalFactory.clear(); - } - - /** - * Returns a {@link PendingIntent} to launch the tuner TV input service. - * - * @param context a {@link Context} instance - */ - private static PendingIntent createPendingIntentForSetupActivity(Context context) { - return PendingIntent.getActivity( - context, 0, createSetupActivity(context), PendingIntent.FLAG_UPDATE_CURRENT); - } - - private static void sendNotification(Context context, Integer tunerType) { - SoftPreconditions.checkState( - tunerType != null, TAG, "tunerType is null when send notification"); - if (tunerType == null) { - return; - } - Resources resources = context.getResources(); - String contentTitle = resources.getString(R.string.ut_setup_notification_content_title); - int contentTextId = 0; - switch (tunerType) { - case TunerHal.TUNER_TYPE_BUILT_IN: - contentTextId = R.string.bt_setup_notification_content_text; - break; - case TunerHal.TUNER_TYPE_USB: - contentTextId = R.string.ut_setup_notification_content_text; - break; - case TunerHal.TUNER_TYPE_NETWORK: - contentTextId = R.string.nt_setup_notification_content_text; - break; - } - String contentText = resources.getString(contentTextId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - sendNotificationInternal(context, contentTitle, contentText); - } else { - Bitmap largeIcon = - BitmapFactory.decodeResource(resources, R.drawable.recommendation_antenna); - sendRecommendationCard(context, contentTitle, contentText, largeIcon); - } - } - - /** - * Sends the recommendation card to start the tuner TV input setup activity. - * - * @param context a {@link Context} instance - */ - private static void sendRecommendationCard( - Context context, String contentTitle, String contentText, Bitmap largeIcon) { - // Build and send the notification. - Notification notification = - new NotificationCompat.BigPictureStyle( - new NotificationCompat.Builder(context) - .setAutoCancel(false) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setContentInfo(contentText) - .setCategory(Notification.CATEGORY_RECOMMENDATION) - .setLargeIcon(largeIcon) - .setSmallIcon( - context.getResources() - .getIdentifier( - TAG_ICON, - TAG_DRAWABLE, - context.getPackageName())) - .setContentIntent( - createPendingIntentForSetupActivity(context))) - .build(); - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); - } - - private static void sendNotificationInternal( - Context context, String contentTitle, String contentText) { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.createNotificationChannel( - new NotificationChannel( - TUNER_SET_UP_NOTIFICATION_CHANNEL_ID, - context.getResources() - .getString(R.string.ut_setup_notification_channel_name), - NotificationManager.IMPORTANCE_HIGH)); - Notification notification = - new Notification.Builder(context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setSmallIcon( - context.getResources() - .getIdentifier( - TAG_ICON, TAG_DRAWABLE, context.getPackageName())) - .setContentIntent(createPendingIntentForSetupActivity(context)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .extend(new Notification.TvExtender()) - .build(); - notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification); - } - - private void showPostalCodeFragment() { - SetupFragment fragment = new PostalCodeFragment(); - fragment.setShortDistance( - SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION); - showFragment(fragment, true); - } - - private void showConnectionTypeFragment() { - SetupFragment fragment = new ConnectionTypeFragment(); - fragment.setShortDistance( - SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION); - showFragment(fragment, true); - } - - /** - * Cancels the previously shown notification. - * - * @param context a {@link Context} instance - */ - public static void cancelNotification(Context context) { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID); - } - - @VisibleForTesting - static class TunerHalFactory { - private Context mContext; - @VisibleForTesting TunerHal mTunerHal; - private GenerateTunerHalTask mGenerateTunerHalTask; - private final Executor mExecutor; - - TunerHalFactory(Context context) { - this(context, AsyncTask.SERIAL_EXECUTOR); - } - - TunerHalFactory(Context context, Executor executor) { - mContext = context; - mExecutor = executor; - } - - /** - * Returns tuner HAL currently used. If it's {@code null} and tuner HAL is not generated - * before, tries to generate it synchronously. - */ - @WorkerThread - TunerHal getOrCreate() { - if (mGenerateTunerHalTask != null - && mGenerateTunerHalTask.getStatus() != AsyncTask.Status.FINISHED) { - try { - return mGenerateTunerHalTask.get(); - } catch (Exception e) { - Log.e(TAG, "Cannot get Tuner HAL: " + e); - } - } else if (mGenerateTunerHalTask == null && mTunerHal == null) { - mTunerHal = createInstance(); - } - return mTunerHal; - } - - /** Generates tuner hal for scanning with asynchronous tasks. */ - @MainThread - void generate() { - if (mGenerateTunerHalTask == null && mTunerHal == null) { - mGenerateTunerHalTask = new GenerateTunerHalTask(); - mGenerateTunerHalTask.executeOnExecutor(mExecutor); - } - } - - /** Clears the currently used tuner hal. */ - @MainThread - void clear() { - if (mGenerateTunerHalTask != null) { - mGenerateTunerHalTask.cancel(true); - mGenerateTunerHalTask = null; - } - if (mTunerHal != null) { - AutoCloseableUtils.closeQuietly(mTunerHal); - mTunerHal = null; - } - } - - @WorkerThread - protected TunerHal createInstance() { - return TunerHal.createInstance(mContext); - } - - class GenerateTunerHalTask extends AsyncTask { - @Override - protected TunerHal doInBackground(Void... args) { - return createInstance(); - } - - @Override - protected void onPostExecute(TunerHal tunerHal) { - mTunerHal = tunerHal; - } - } - } -} diff --git a/src/com/android/tv/tuner/setup/WelcomeFragment.java b/src/com/android/tv/tuner/setup/WelcomeFragment.java deleted file mode 100644 index 326fe126..00000000 --- a/src/com/android/tv/tuner/setup/WelcomeFragment.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.setup; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v17.leanback.widget.GuidanceStylist.Guidance; -import android.support.v17.leanback.widget.GuidedAction; -import com.android.tv.common.ui.setup.SetupGuidedStepFragment; -import com.android.tv.common.ui.setup.SetupMultiPaneFragment; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.TunerPreferences; -import java.util.List; - -/** A fragment for initial screen. */ -public class WelcomeFragment extends SetupMultiPaneFragment { - public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.WelcomeFragment"; - - @Override - protected SetupGuidedStepFragment onCreateContentFragment() { - ContentFragment fragment = new ContentFragment(); - fragment.setArguments(getArguments()); - return fragment; - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - - @Override - protected boolean needsDoneButton() { - return false; - } - - public static class ContentFragment extends SetupGuidedStepFragment { - private int mChannelCountOnPreference; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - mChannelCountOnPreference = - TunerPreferences.getScannedChannelCount(getActivity().getApplicationContext()); - super.onCreate(savedInstanceState); - } - - @NonNull - @Override - public Guidance onCreateGuidance(Bundle savedInstanceState) { - String title; - String description; - int tunerType = - getArguments() - .getInt( - TunerSetupActivity.KEY_TUNER_TYPE, - TunerHal.TUNER_TYPE_BUILT_IN); - if (mChannelCountOnPreference == 0) { - switch (tunerType) { - case TunerHal.TUNER_TYPE_USB: - title = getString(R.string.ut_setup_new_title); - description = getString(R.string.ut_setup_new_description); - break; - case TunerHal.TUNER_TYPE_NETWORK: - title = getString(R.string.nt_setup_new_title); - description = getString(R.string.nt_setup_new_description); - break; - default: - title = getString(R.string.bt_setup_new_title); - description = getString(R.string.bt_setup_new_description); - } - } else { - title = getString(R.string.bt_setup_again_title); - switch (tunerType) { - case TunerHal.TUNER_TYPE_USB: - description = getString(R.string.ut_setup_again_description); - break; - case TunerHal.TUNER_TYPE_NETWORK: - description = getString(R.string.nt_setup_again_description); - break; - default: - description = getString(R.string.bt_setup_again_description); - } - } - return new Guidance(title, description, null, null); - } - - @Override - public void onCreateActions( - @NonNull List actions, Bundle savedInstanceState) { - String[] choices = - getResources() - .getStringArray( - mChannelCountOnPreference == 0 - ? R.array.ut_setup_new_choices - : R.array.ut_setup_again_choices); - for (int i = 0; i < choices.length - 1; ++i) { - actions.add( - new GuidedAction.Builder(getActivity()).id(i).title(choices[i]).build()); - } - actions.add( - new GuidedAction.Builder(getActivity()) - .id(ACTION_DONE) - .title(choices[choices.length - 1]) - .build()); - } - - @Override - protected String getActionCategory() { - return ACTION_CATEGORY; - } - } -} diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java deleted file mode 100644 index f74274f4..00000000 --- a/src/com/android/tv/tuner/source/FileTsStreamer.java +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import android.content.Context; -import android.os.Environment; -import android.util.Log; -import android.util.SparseBooleanArray; -import com.android.tv.Features; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.ChannelScanFileParser.ScanChannel; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.ts.TsParser; -import com.android.tv.tuner.tvinput.EventDetector; -import com.android.tv.tuner.tvinput.FileSourceEventDetector; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.upstream.DataSpec; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Provides MPEG-2 TS stream sources for both channel scanning and channel playing from a local file - * generated by capturing TV signal. - */ -public class FileTsStreamer implements TsStreamer { - private static final String TAG = "FileTsStreamer"; - - private static final int TS_PACKET_SIZE = 188; - private static final int TS_SYNC_BYTE = 0x47; - private static final int MIN_READ_UNIT = TS_PACKET_SIZE * 10; - private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~20KB - private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 4000; // ~ 8MB - private static final int PADDING_SIZE = MIN_READ_UNIT * 1000; // ~2MB - private static final int READ_TIMEOUT_MS = 10000; // 10 secs. - private static final int BUFFER_UNDERRUN_SLEEP_MS = 10; - private static final String FILE_DIR = - new File(Environment.getExternalStorageDirectory(), "Streams").getAbsolutePath(); - - // Virtual frequency base used for file-based source - public static final int FREQ_BASE = 100; - - private final Object mCircularBufferMonitor = new Object(); - private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE]; - private final FileSourceEventDetector mEventDetector; - private final Context mContext; - - private long mBytesFetched; - private long mLastReadPosition; - private boolean mStreaming; - - private Thread mStreamingThread; - private StreamProvider mSource; - - public static class FileDataSource extends TsDataSource { - private final FileTsStreamer mTsStreamer; - private final AtomicLong mLastReadPosition = new AtomicLong(0); - private long mStartBufferedPosition; - - private FileDataSource(FileTsStreamer tsStreamer) { - mTsStreamer = tsStreamer; - mStartBufferedPosition = tsStreamer.getBufferedPosition(); - } - - @Override - public long getBufferedPosition() { - return mTsStreamer.getBufferedPosition() - mStartBufferedPosition; - } - - @Override - public long getLastReadPosition() { - return mLastReadPosition.get(); - } - - @Override - public void shiftStartPosition(long offset) { - SoftPreconditions.checkState(mLastReadPosition.get() == 0); - SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition()); - mStartBufferedPosition += offset; - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - mLastReadPosition.set(0); - return C.LENGTH_UNBOUNDED; - } - - @Override - public void close() {} - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - int ret = - mTsStreamer.readAt( - mStartBufferedPosition + mLastReadPosition.get(), - buffer, - offset, - readLength); - if (ret > 0) { - mLastReadPosition.addAndGet(ret); - } - return ret; - } - } - - /** - * Creates {@link TsStreamer} for scanning & playing MPEG-2 TS file. - * - * @param eventListener the listener for channel & program information - */ - public FileTsStreamer(EventDetector.EventListener eventListener, Context context) { - mEventDetector = - new FileSourceEventDetector( - eventListener, Features.ENABLE_FILE_DVB.isEnabled(context)); - mContext = context; - } - - @Override - public boolean startStream(ScanChannel channel) { - String filepath = new File(FILE_DIR, channel.filename).getAbsolutePath(); - mSource = new StreamProvider(filepath); - if (!mSource.isReady()) { - return false; - } - mEventDetector.start(mSource, FileSourceEventDetector.ALL_PROGRAM_NUMBERS); - mSource.addPidFilter(TsParser.PAT_PID); - mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); - if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) { - mSource.addPidFilter(TsParser.DVB_EIT_PID); - mSource.addPidFilter(TsParser.DVB_SDT_PID); - } - synchronized (mCircularBufferMonitor) { - if (mStreaming) { - return true; - } - mStreaming = true; - } - - mStreamingThread = new StreamingThread(); - mStreamingThread.start(); - Log.i(TAG, "Streaming started"); - return true; - } - - @Override - public boolean startStream(TunerChannel channel) { - Log.i(TAG, "tuneToChannel with: " + channel.getFilepath()); - mSource = new StreamProvider(channel.getFilepath()); - if (!mSource.isReady()) { - return false; - } - mEventDetector.start(mSource, channel.getProgramNumber()); - mSource.addPidFilter(channel.getVideoPid()); - for (Integer i : channel.getAudioPids()) { - mSource.addPidFilter(i); - } - mSource.addPidFilter(channel.getPcrPid()); - mSource.addPidFilter(TsParser.PAT_PID); - mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID); - if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) { - mSource.addPidFilter(TsParser.DVB_EIT_PID); - mSource.addPidFilter(TsParser.DVB_SDT_PID); - } - synchronized (mCircularBufferMonitor) { - if (mStreaming) { - return true; - } - mStreaming = true; - } - - mStreamingThread = new StreamingThread(); - mStreamingThread.start(); - Log.i(TAG, "Streaming started"); - return true; - } - - /** - * Blocks the current thread until the streaming thread stops. In rare cases when the tuner - * device is overloaded this can take a while, but usually it returns pretty quickly. - */ - @Override - public void stopStream() { - synchronized (mCircularBufferMonitor) { - mStreaming = false; - mCircularBufferMonitor.notify(); - } - - try { - if (mStreamingThread != null) { - mStreamingThread.join(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public TsDataSource createDataSource() { - return new FileDataSource(this); - } - - /** - * Returns the current buffered position from the file. - * - * @return the current buffered position - */ - public long getBufferedPosition() { - synchronized (mCircularBufferMonitor) { - return mBytesFetched; - } - } - - /** Provides MPEG-2 transport stream from a local file. Stream can be filtered by PID. */ - public static class StreamProvider { - private final String mFilepath; - private final SparseBooleanArray mPids = new SparseBooleanArray(); - private final byte[] mPreBuffer = new byte[READ_BUFFER_SIZE]; - - private BufferedInputStream mInputStream; - - private StreamProvider(String filepath) { - mFilepath = filepath; - open(filepath); - } - - private void open(String filepath) { - try { - mInputStream = new BufferedInputStream(new FileInputStream(filepath)); - } catch (IOException e) { - Log.e(TAG, "Error opening input stream", e); - mInputStream = null; - } - } - - private boolean isReady() { - return mInputStream != null; - } - - /** Returns the file path of the MPEG-2 TS file. */ - public String getFilepath() { - return mFilepath; - } - - /** Adds a pid for filtering from the MPEG-2 TS file. */ - public void addPidFilter(int pid) { - mPids.put(pid, true); - } - - /** Returns whether the current pid filter is empty or not. */ - public boolean isFilterEmpty() { - return mPids.size() == 0; - } - - /** Clears the current pid filter. */ - public void clearPidFilter() { - mPids.clear(); - } - - /** - * Returns whether a pid is in the pid filter or not. - * - * @param pid the pid to check - */ - public boolean isInFilter(int pid) { - return mPids.get(pid); - } - - /** - * Reads from the MPEG-2 TS file to buffer. - * - * @param inputBuffer to read - * @return the number of read bytes - */ - private int read(byte[] inputBuffer) { - int readSize = readInternal(); - if (readSize <= 0) { - // Reached the end of stream. Restart from the beginning. - close(); - open(mFilepath); - if (mInputStream == null) { - return -1; - } - readSize = readInternal(); - } - - if (mPreBuffer[0] != TS_SYNC_BYTE) { - Log.e(TAG, "Error reading input stream - no TS sync found"); - return -1; - } - int filteredSize = 0; - for (int i = 0, destPos = 0; i < readSize; i += TS_PACKET_SIZE) { - if (mPreBuffer[i] == TS_SYNC_BYTE) { - int pid = ((mPreBuffer[i + 1] & 0x1f) << 8) + (mPreBuffer[i + 2] & 0xff); - if (mPids.get(pid)) { - System.arraycopy(mPreBuffer, i, inputBuffer, destPos, TS_PACKET_SIZE); - destPos += TS_PACKET_SIZE; - filteredSize += TS_PACKET_SIZE; - } - } - } - return filteredSize; - } - - private int readInternal() { - int readSize; - try { - readSize = mInputStream.read(mPreBuffer, 0, mPreBuffer.length); - } catch (IOException e) { - Log.e(TAG, "Error reading input stream", e); - return -1; - } - return readSize; - } - - private void close() { - try { - mInputStream.close(); - } catch (IOException e) { - Log.e(TAG, "Error closing input stream:", e); - } - mInputStream = null; - } - } - - /** - * Reads data from internal buffer. - * - * @param pos the position to read from - * @param buffer to read - * @param offset start position of the read buffer - * @param amount number of bytes to read - * @return number of read bytes when successful, {@code -1} otherwise - * @throws IOException - */ - public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException { - synchronized (mCircularBufferMonitor) { - long initialBytesFetched = mBytesFetched; - while (mBytesFetched < pos + amount && mStreaming) { - try { - mCircularBufferMonitor.wait(READ_TIMEOUT_MS); - } catch (InterruptedException e) { - // Wait again. - Thread.currentThread().interrupt(); - } - if (initialBytesFetched == mBytesFetched) { - Log.w(TAG, "No data update for " + READ_TIMEOUT_MS + "ms. returning -1."); - - // Returning -1 will make demux report EOS so that the input service can retry - // the playback. - return -1; - } - } - if (!mStreaming) { - Log.w(TAG, "Stream is already stopped."); - return -1; - } - if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) { - Log.e(TAG, "Demux is requesting the data which is already overwritten."); - return -1; - } - int posInBuffer = (int) (pos % CIRCULAR_BUFFER_SIZE); - int bytesToCopyInFirstPass = amount; - if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) { - bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer; - } - System.arraycopy(mCircularBuffer, posInBuffer, buffer, offset, bytesToCopyInFirstPass); - if (bytesToCopyInFirstPass < amount) { - System.arraycopy( - mCircularBuffer, - 0, - buffer, - offset + bytesToCopyInFirstPass, - amount - bytesToCopyInFirstPass); - } - mLastReadPosition = pos + amount; - mCircularBufferMonitor.notify(); - return amount; - } - } - - /** - * Adds {@link ScanChannel} instance for local files. - * - * @param output a list of channels where the results will be placed in - */ - public static void addLocalStreamFiles(List output) { - File dir = new File(FILE_DIR); - if (!dir.exists()) return; - - File[] tsFiles = dir.listFiles(); - if (tsFiles == null) return; - int freq = FileTsStreamer.FREQ_BASE; - for (File file : tsFiles) { - if (!file.isFile()) continue; - output.add(ScanChannel.forFile(freq, file.getName())); - freq += 100; - } - } - - /** - * A thread managing a circular buffer that holds stream data to be consumed by player. Keeps - * reading data in from a {@link StreamProvider} to hold enough amount for buffering. Started - * and stopped by {@link #startStream()} and {@link #stopStream()}, respectively. - */ - private class StreamingThread extends Thread { - @Override - public void run() { - byte[] dataBuffer = new byte[READ_BUFFER_SIZE]; - - synchronized (mCircularBufferMonitor) { - mBytesFetched = 0; - mLastReadPosition = 0; - } - - while (true) { - synchronized (mCircularBufferMonitor) { - while ((mBytesFetched - mLastReadPosition + PADDING_SIZE) > CIRCULAR_BUFFER_SIZE - && mStreaming) { - try { - mCircularBufferMonitor.wait(); - } catch (InterruptedException e) { - // Wait again. - Thread.currentThread().interrupt(); - } - } - if (!mStreaming) { - break; - } - } - - int bytesWritten = mSource.read(dataBuffer); - if (bytesWritten <= 0) { - try { - // When buffer is underrun, we sleep for short time to prevent - // unnecessary CPU draining. - sleep(BUFFER_UNDERRUN_SLEEP_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - continue; - } - - mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten); - - synchronized (mCircularBufferMonitor) { - int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE); - int bytesToCopyInFirstPass = bytesWritten; - if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) { - bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer; - } - System.arraycopy( - dataBuffer, 0, mCircularBuffer, posInBuffer, bytesToCopyInFirstPass); - if (bytesToCopyInFirstPass < bytesWritten) { - System.arraycopy( - dataBuffer, - bytesToCopyInFirstPass, - mCircularBuffer, - 0, - bytesWritten - bytesToCopyInFirstPass); - } - mBytesFetched += bytesWritten; - mCircularBufferMonitor.notify(); - } - } - - Log.i(TAG, "Streaming stopped"); - mSource.close(); - } - } -} diff --git a/src/com/android/tv/tuner/source/TsDataSource.java b/src/com/android/tv/tuner/source/TsDataSource.java deleted file mode 100644 index be902944..00000000 --- a/src/com/android/tv/tuner/source/TsDataSource.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import com.google.android.exoplayer.upstream.DataSource; - -/** {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}. */ -public abstract class TsDataSource implements DataSource { - - /** - * Returns the number of bytes being buffered by {@link TsStreamer} so far. - * - * @return the buffered position - */ - public long getBufferedPosition() { - return 0; - } - - /** - * Returns the offset position where the last {@link DataSource#read} read. - * - * @return the last read position - */ - public long getLastReadPosition() { - return 0; - } - - /** - * Shifts start position by the specified offset. Do not call this method when the class already - * provided MPEG-TS stream to the extractor. - * - * @param offset 0 <= offset <= buffered position - */ - public void shiftStartPosition(long offset) {} -} diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java deleted file mode 100644 index fc8a8327..00000000 --- a/src/com/android/tv/tuner/source/TsDataSourceManager.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import android.content.Context; -import android.support.annotation.VisibleForTesting; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Channel; -import com.android.tv.tuner.tvinput.EventDetector; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Manages {@link DataSource} for playback and recording. The class hides handling of {@link - * TunerHal} and {@link TsStreamer} from other classes. One TsDataSourceManager should be created - * for per session. - */ -public class TsDataSourceManager { - private static final Object sLock = new Object(); - private static final Map sTsStreamers = new ConcurrentHashMap<>(); - - private static int sSequenceId; - - private final int mId; - private final boolean mIsRecording; - private final TunerTsStreamerManager mTunerStreamerManager = - TunerTsStreamerManager.getInstance(); - - private boolean mKeepTuneStatus; - - /** - * Creates TsDataSourceManager to create and release {@link DataSource} which will be used for - * playing and recording. - * - * @param isRecording {@code true} when for recording, {@code false} otherwise - * @return {@link TsDataSourceManager} - */ - public static TsDataSourceManager createSourceManager(boolean isRecording) { - int id; - synchronized (sLock) { - id = ++sSequenceId; - } - return new TsDataSourceManager(id, isRecording); - } - - private TsDataSourceManager(int id, boolean isRecording) { - mId = id; - mIsRecording = isRecording; - mKeepTuneStatus = true; - } - - /** - * Creates or retrieves {@link TsDataSource} for playing or recording - * - * @param context a {@link Context} instance - * @param channel to play or record - * @param eventListener for program information which will be scanned from MPEG2-TS stream - * @return {@link TsDataSource} which will provide the specified channel stream - */ - public TsDataSource createDataSource( - Context context, TunerChannel channel, EventDetector.EventListener eventListener) { - if (channel.getType() == Channel.TYPE_FILE) { - // MPEG2 TS captured stream file recording is not supported. - if (mIsRecording) { - return null; - } - FileTsStreamer streamer = new FileTsStreamer(eventListener, context); - if (streamer.startStream(channel)) { - TsDataSource source = streamer.createDataSource(); - sTsStreamers.put(source, streamer); - return source; - } - return null; - } - return mTunerStreamerManager.createDataSource( - context, channel, eventListener, mId, !mIsRecording && mKeepTuneStatus); - } - - /** - * Releases the specified {@link TsDataSource} and underlying {@link TunerHal}. - * - * @param source to release - */ - public void releaseDataSource(TsDataSource source) { - if (source instanceof TunerTsStreamer.TunerDataSource) { - mTunerStreamerManager.releaseDataSource(source, mId, !mIsRecording && mKeepTuneStatus); - } else if (source instanceof FileTsStreamer.FileDataSource) { - FileTsStreamer streamer = (FileTsStreamer) sTsStreamers.get(source); - if (streamer != null) { - sTsStreamers.remove(source); - streamer.stopStream(); - } - } - } - - /** Indicates that the current session has pending tunes. */ - public void setHasPendingTune() { - mTunerStreamerManager.setHasPendingTune(mId); - } - - /** - * Indicates whether the underlying {@link TunerHal} should be kept or not when data source is - * being released. TODO: If b/30750953 is fixed, we can remove this function. - * - * @param keepTuneStatus underlying {@link TunerHal} will be reused when data source releasing. - */ - public void setKeepTuneStatus(boolean keepTuneStatus) { - mKeepTuneStatus = keepTuneStatus; - } - - /** Add tuner hal into TunerTsStreamerManager for test. */ - @VisibleForTesting - public void addTunerHalForTest(TunerHal tunerHal) { - mTunerStreamerManager.addTunerHal(tunerHal, mId); - } - - /** Releases persistent resources. */ - public void release() { - mTunerStreamerManager.release(mId); - } -} diff --git a/src/com/android/tv/tuner/source/TsStreamWriter.java b/src/com/android/tv/tuner/source/TsStreamWriter.java deleted file mode 100644 index f90136bf..00000000 --- a/src/com/android/tv/tuner/source/TsStreamWriter.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import android.content.Context; -import android.util.Log; -import com.android.tv.tuner.data.TunerChannel; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -/** Stores TS files to the disk for debugging. */ -public class TsStreamWriter { - private static final String TAG = "TsStreamWriter"; - private static final boolean DEBUG = false; - - private static final long TIME_LIMIT_MS = 10000; // 10s - private static final int NO_INSTANCE_ID = 0; - private static final int MAX_GET_ID_RETRY_COUNT = 5; - private static final int MAX_INSTANCE_ID = 10000; - private static final String SEPARATOR = "_"; - - private FileOutputStream mFileOutputStream; - private long mFileStartTimeMs; - private String mFileName = null; - private final String mDirectoryPath; - private final File mDirectory; - private final int mInstanceId; - private TunerChannel mChannel; - - public TsStreamWriter(Context context) { - File externalFilesDir = context.getExternalFilesDir(null); - if (externalFilesDir == null || !externalFilesDir.isDirectory()) { - mDirectoryPath = null; - mDirectory = null; - mInstanceId = NO_INSTANCE_ID; - if (DEBUG) { - Log.w(TAG, "Fail to get external files dir!"); - } - } else { - mDirectoryPath = externalFilesDir.getPath() + "/EngTsStream"; - mDirectory = new File(mDirectoryPath); - if (!mDirectory.exists()) { - boolean madeDir = mDirectory.mkdir(); - if (!madeDir) { - Log.w(TAG, "Error. Fail to create folder!"); - } - } - mInstanceId = generateInstanceId(); - } - } - - /** - * Sets the current channel. - * - * @param channel curren channel of the stream - */ - public void setChannel(TunerChannel channel) { - mChannel = channel; - } - - /** Opens a file to store TS data. */ - public void openFile() { - if (mChannel == null || mDirectoryPath == null) { - return; - } - mFileStartTimeMs = System.currentTimeMillis(); - mFileName = - mChannel.getDisplayNumber() - + SEPARATOR - + mFileStartTimeMs - + SEPARATOR - + mInstanceId - + ".ts"; - String filePath = mDirectoryPath + "/" + mFileName; - try { - mFileOutputStream = new FileOutputStream(filePath, false); - } catch (FileNotFoundException e) { - Log.w(TAG, "Cannot open file: " + filePath, e); - } - } - - /** - * Closes the file and stops storing TS data. - * - * @param calledWhenStopStream {@code true} if this method is called when the stream is stopped - * {@code false} otherwise - */ - public void closeFile(boolean calledWhenStopStream) { - if (mFileOutputStream == null) { - return; - } - try { - mFileOutputStream.close(); - deleteOutdatedFiles(calledWhenStopStream); - mFileName = null; - mFileOutputStream = null; - } catch (IOException e) { - Log.w(TAG, "Error on closing file.", e); - } - } - - /** - * Writes the data to the file. - * - * @param buffer the data to be written - * @param bytesWritten number of bytes written - */ - public void writeToFile(byte[] buffer, int bytesWritten) { - if (mFileOutputStream == null) { - return; - } - if (System.currentTimeMillis() - mFileStartTimeMs > TIME_LIMIT_MS) { - closeFile(false); - openFile(); - } - try { - mFileOutputStream.write(buffer, 0, bytesWritten); - } catch (IOException e) { - Log.w(TAG, "Error on writing TS stream.", e); - } - } - - /** - * Deletes outdated files to save storage. - * - * @param deleteAll {@code true} if all the files with the relative ID should be deleted {@code - * false} if the most recent file should not be deleted - */ - private void deleteOutdatedFiles(boolean deleteAll) { - if (mFileName == null) { - return; - } - if (mDirectory == null || !mDirectory.isDirectory()) { - Log.e(TAG, "Error. The folder doesn't exist!"); - return; - } - if (mFileName == null) { - Log.e(TAG, "Error. The current file name is null!"); - return; - } - for (File file : mDirectory.listFiles()) { - if (file.isFile() - && getFileId(file) == mInstanceId - && (deleteAll || !mFileName.equals(file.getName()))) { - boolean deleted = file.delete(); - if (DEBUG && !deleted) { - Log.w(TAG, "Failed to delete " + file.getName()); - } - } - } - } - - /** - * Generates a unique instance ID. - * - * @return a unique instance ID - */ - private int generateInstanceId() { - if (mDirectory == null) { - return NO_INSTANCE_ID; - } - Set idSet = getExistingIds(); - if (idSet == null) { - return NO_INSTANCE_ID; - } - for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) { - // Range [1, MAX_INSTANCE_ID] - int id = (int) Math.floor(Math.random() * MAX_INSTANCE_ID) + 1; - if (!idSet.contains(id)) { - return id; - } - } - return NO_INSTANCE_ID; - } - - /** - * Gets all existing instance IDs. - * - * @return a set of all existing instance IDs - */ - private Set getExistingIds() { - if (mDirectory == null || !mDirectory.isDirectory()) { - return null; - } - - Set idSet = new HashSet<>(); - for (File file : mDirectory.listFiles()) { - int id = getFileId(file); - if (id != NO_INSTANCE_ID) { - idSet.add(id); - } - } - return idSet; - } - - /** - * Gets the instance ID of a given file. - * - * @param file the file whose TsStreamWriter ID is returned - * @return the TsStreamWriter ID of the file or NO_INSTANCE_ID if not available - */ - private static int getFileId(File file) { - if (file == null || !file.isFile()) { - return NO_INSTANCE_ID; - } - String fileName = file.getName(); - int lastSeparator = fileName.lastIndexOf(SEPARATOR); - if (!fileName.endsWith(".ts") || lastSeparator == -1) { - return NO_INSTANCE_ID; - } - try { - return Integer.parseInt(fileName.substring(lastSeparator + 1, fileName.length() - 3)); - } catch (NumberFormatException e) { - if (DEBUG) { - Log.e(TAG, fileName + " is not a valid file name."); - } - } - return NO_INSTANCE_ID; - } -} diff --git a/src/com/android/tv/tuner/source/TsStreamer.java b/src/com/android/tv/tuner/source/TsStreamer.java deleted file mode 100644 index 3dbba7e7..00000000 --- a/src/com/android/tv/tuner/source/TsStreamer.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import com.android.tv.tuner.ChannelScanFileParser; -import com.android.tv.tuner.data.TunerChannel; - -/** - * Interface definition for a stream generator. The interface will provide streams for scanning - * channels and/or playback. - */ -public interface TsStreamer { - /** - * Starts streaming the data for channel scanning process. - * - * @param channel {@link ChannelScanFileParser.ScanChannel} to be scanned - * @return {@code true} if ready to stream, otherwise {@code false} - */ - boolean startStream(ChannelScanFileParser.ScanChannel channel); - - /** - * Starts streaming the data for channel playing or recording. - * - * @param channel {@link TunerChannel} to tune - * @return {@code true} if ready to stream, otherwise {@code false} - */ - boolean startStream(TunerChannel channel); - - /** Stops streaming the data. */ - void stopStream(); - - /** - * Creates {@link TsDataSource} which will provide MPEG-2 TS stream for {@link - * android.media.MediaExtractor}. The source will start from the position where it is created. - * - * @return {@link TsDataSource} - */ - TsDataSource createDataSource(); -} diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java deleted file mode 100644 index 21b7a1f8..00000000 --- a/src/com/android/tv/tuner/source/TunerTsStreamer.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import android.content.Context; -import android.util.Log; -import android.util.Pair; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.ChannelScanFileParser; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.tvinput.EventDetector; -import com.android.tv.tuner.tvinput.EventDetector.EventListener; -import com.google.android.exoplayer.C; -import com.google.android.exoplayer.upstream.DataSpec; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -/** Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device. */ -public class TunerTsStreamer implements TsStreamer { - private static final String TAG = "TunerTsStreamer"; - - private static final int MIN_READ_UNIT = 1500; - private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB - private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB - private static final int TS_PACKET_SIZE = 188; - - private static final int READ_TIMEOUT_MS = 5000; // 5 secs. - private static final int BUFFER_UNDERRUN_SLEEP_MS = 10; - private static final int READ_ERROR_STREAMING_ENDED = -1; - private static final int READ_ERROR_BUFFER_OVERWRITTEN = -2; - - private final Object mCircularBufferMonitor = new Object(); - private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE]; - private long mBytesFetched; - private final AtomicLong mLastReadPosition = new AtomicLong(); - private boolean mStreaming; - - private final TunerHal mTunerHal; - private TunerChannel mChannel; - private Thread mStreamingThread; - private final EventDetector mEventDetector; - private final List> mEventListenerActions = new ArrayList<>(); - - private final TsStreamWriter mTsStreamWriter; - private String mChannelNumber; - - public static class TunerDataSource extends TsDataSource { - private final TunerTsStreamer mTsStreamer; - private final AtomicLong mLastReadPosition = new AtomicLong(0); - private long mStartBufferedPosition; - - private TunerDataSource(TunerTsStreamer tsStreamer) { - mTsStreamer = tsStreamer; - mStartBufferedPosition = tsStreamer.getBufferedPosition(); - } - - @Override - public long getBufferedPosition() { - return mTsStreamer.getBufferedPosition() - mStartBufferedPosition; - } - - @Override - public long getLastReadPosition() { - return mLastReadPosition.get(); - } - - @Override - public void shiftStartPosition(long offset) { - SoftPreconditions.checkState(mLastReadPosition.get() == 0); - SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition()); - mStartBufferedPosition += offset; - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - mLastReadPosition.set(0); - return C.LENGTH_UNBOUNDED; - } - - @Override - public void close() {} - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - int ret = - mTsStreamer.readAt( - mStartBufferedPosition + mLastReadPosition.get(), - buffer, - offset, - readLength); - if (ret > 0) { - mLastReadPosition.addAndGet(ret); - } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) { - long currentPosition = mStartBufferedPosition + mLastReadPosition.get(); - long endPosition = mTsStreamer.getBufferedPosition(); - long diff = - ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE) - * TS_PACKET_SIZE; - Log.w(TAG, "Demux position jump by overwritten buffer: " + diff); - mStartBufferedPosition = currentPosition + diff; - mLastReadPosition.set(0); - return 0; - } - return ret; - } - } - /** - * Creates {@link TsStreamer} for playing or recording the specified channel. - * - * @param tunerHal the HAL for tuner device - * @param eventListener the listener for channel & program information - */ - public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) { - mTunerHal = tunerHal; - mEventDetector = new EventDetector(mTunerHal); - if (eventListener != null) { - mEventDetector.registerListener(eventListener); - } - mTsStreamWriter = - context != null && TunerPreferences.getStoreTsStream(context) - ? new TsStreamWriter(context) - : null; - } - - public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener) { - this(tunerHal, eventListener, null); - } - - @Override - public boolean startStream(TunerChannel channel) { - if (mTunerHal.tune( - channel.getFrequency(), channel.getModulation(), channel.getDisplayNumber(false))) { - if (channel.hasVideo()) { - mTunerHal.addPidFilter(channel.getVideoPid(), TunerHal.FILTER_TYPE_VIDEO); - } - boolean audioFilterSet = false; - for (Integer audioPid : channel.getAudioPids()) { - if (!audioFilterSet) { - mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_AUDIO); - audioFilterSet = true; - } else { - // FILTER_TYPE_AUDIO overrides the previous filter for audio. We use - // FILTER_TYPE_OTHER from the secondary one to get the all audio tracks. - mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_OTHER); - } - } - mTunerHal.addPidFilter(channel.getPcrPid(), TunerHal.FILTER_TYPE_PCR); - if (mEventDetector != null) { - mEventDetector.startDetecting( - channel.getFrequency(), - channel.getModulation(), - channel.getProgramNumber()); - } - mChannel = channel; - mChannelNumber = channel.getDisplayNumber(); - synchronized (mCircularBufferMonitor) { - if (mStreaming) { - Log.w(TAG, "Streaming should be stopped before start streaming"); - return true; - } - mStreaming = true; - mBytesFetched = 0; - mLastReadPosition.set(0L); - } - if (mTsStreamWriter != null) { - mTsStreamWriter.setChannel(mChannel); - mTsStreamWriter.openFile(); - } - mStreamingThread = new StreamingThread(); - mStreamingThread.start(); - Log.i(TAG, "Streaming started"); - return true; - } - return false; - } - - @Override - public boolean startStream(ChannelScanFileParser.ScanChannel channel) { - if (mTunerHal.tune(channel.frequency, channel.modulation, null)) { - mEventDetector.startDetecting( - channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS); - synchronized (mCircularBufferMonitor) { - if (mStreaming) { - Log.w(TAG, "Streaming should be stopped before start streaming"); - return true; - } - mStreaming = true; - mBytesFetched = 0; - mLastReadPosition.set(0L); - } - mStreamingThread = new StreamingThread(); - mStreamingThread.start(); - Log.i(TAG, "Streaming started"); - return true; - } - return false; - } - - /** - * Blocks the current thread until the streaming thread stops. In rare cases when the tuner - * device is overloaded this can take a while, but usually it returns pretty quickly. - */ - @Override - public void stopStream() { - mChannel = null; - synchronized (mCircularBufferMonitor) { - mStreaming = false; - mCircularBufferMonitor.notifyAll(); - } - - try { - if (mStreamingThread != null) { - mStreamingThread.join(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - if (mTsStreamWriter != null) { - mTsStreamWriter.closeFile(true); - mTsStreamWriter.setChannel(null); - } - } - - @Override - public TsDataSource createDataSource() { - return new TunerDataSource(this); - } - - /** - * Returns incomplete channel lists which was scanned so far. Incomplete channel means the - * channel whose channel information is not complete or is not well-formed. - * - * @return {@link List} of {@link TunerChannel} - */ - public List getMalFormedChannels() { - return mEventDetector.getMalFormedChannels(); - } - - /** - * Returns the current {@link TunerHal} which provides MPEG-TS stream for TunerTsStreamer. - * - * @return {@link TunerHal} - */ - public TunerHal getTunerHal() { - return mTunerHal; - } - - /** - * Returns the current tuned channel for TunerTsStreamer. - * - * @return {@link TunerChannel} - */ - public TunerChannel getChannel() { - return mChannel; - } - - /** - * Returns the current buffered position from tuner. - * - * @return the current buffered position - */ - public long getBufferedPosition() { - synchronized (mCircularBufferMonitor) { - return mBytesFetched; - } - } - - public String getStreamerInfo() { - return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming; - } - - public void registerListener(EventListener listener) { - if (mEventDetector != null && listener != null) { - synchronized (mEventListenerActions) { - mEventListenerActions.add(new Pair<>(listener, true)); - } - } - } - - public void unregisterListener(EventListener listener) { - if (mEventDetector != null) { - synchronized (mEventListenerActions) { - mEventListenerActions.add(new Pair(listener, false)); - } - } - } - - private class StreamingThread extends Thread { - @Override - public void run() { - // Buffers for streaming data from the tuner and the internal buffer. - byte[] dataBuffer = new byte[READ_BUFFER_SIZE]; - - while (true) { - synchronized (mCircularBufferMonitor) { - if (!mStreaming) { - break; - } - } - - if (mEventDetector != null) { - synchronized (mEventListenerActions) { - for (Pair listenerAction : mEventListenerActions) { - EventListener listener = (EventListener) listenerAction.first; - if ((boolean) listenerAction.second) { - mEventDetector.registerListener(listener); - } else { - mEventDetector.unregisterListener(listener); - } - } - mEventListenerActions.clear(); - } - } - - int bytesWritten = mTunerHal.readTsStream(dataBuffer, dataBuffer.length); - if (bytesWritten <= 0) { - try { - // When buffer is underrun, we sleep for short time to prevent - // unnecessary CPU draining. - sleep(BUFFER_UNDERRUN_SLEEP_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - continue; - } - - if (mTsStreamWriter != null) { - mTsStreamWriter.writeToFile(dataBuffer, bytesWritten); - } - - if (mEventDetector != null) { - mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten); - } - synchronized (mCircularBufferMonitor) { - int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE); - int bytesToCopyInFirstPass = bytesWritten; - if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) { - bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer; - } - System.arraycopy( - dataBuffer, 0, mCircularBuffer, posInBuffer, bytesToCopyInFirstPass); - if (bytesToCopyInFirstPass < bytesWritten) { - System.arraycopy( - dataBuffer, - bytesToCopyInFirstPass, - mCircularBuffer, - 0, - bytesWritten - bytesToCopyInFirstPass); - } - mBytesFetched += bytesWritten; - mCircularBufferMonitor.notifyAll(); - } - } - - Log.i(TAG, "Streaming stopped"); - } - } - - /** - * Reads data from internal buffer. - * - * @param pos the position to read from - * @param buffer to read - * @param offset start position of the read buffer - * @param amount number of bytes to read - * @return number of read bytes when successful, {@code -1} otherwise - * @throws IOException - */ - public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException { - while (true) { - synchronized (mCircularBufferMonitor) { - if (!mStreaming) { - return READ_ERROR_STREAMING_ENDED; - } - if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) { - Log.w(TAG, "Demux is requesting the data which is already overwritten."); - return READ_ERROR_BUFFER_OVERWRITTEN; - } - if (mBytesFetched < pos + amount) { - try { - mCircularBufferMonitor.wait(READ_TIMEOUT_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - // Try again to prevent starvation. - // Give chances to read from other threads. - continue; - } - int startPos = (int) (pos % CIRCULAR_BUFFER_SIZE); - int endPos = (int) ((pos + amount) % CIRCULAR_BUFFER_SIZE); - int firstLength = (startPos > endPos ? CIRCULAR_BUFFER_SIZE : endPos) - startPos; - System.arraycopy(mCircularBuffer, startPos, buffer, offset, firstLength); - if (firstLength < amount) { - System.arraycopy( - mCircularBuffer, 0, buffer, offset + firstLength, amount - firstLength); - } - mCircularBufferMonitor.notifyAll(); - return amount; - } - } - } -} diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java deleted file mode 100644 index e94bd56c..00000000 --- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.source; - -import android.content.Context; -import com.android.tv.common.AutoCloseableUtils; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.tvinput.EventDetector; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * Manages {@link TunerTsStreamer} for playback and recording. The class hides handling of {@link - * TunerHal} from other classes. This class is used by {@link TsDataSourceManager}. Don't use this - * class directly. - */ -class TunerTsStreamerManager { - // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator - // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from - // the same session. - private final Object mCancelLock = new Object(); - private final StreamerFinder mStreamerFinder = new StreamerFinder(); - private final Map mCreators = new HashMap<>(); - private final Map mListeners = new HashMap<>(); - private final Map mSourceToStreamerMap = new HashMap<>(); - private final TunerHalManager mTunerHalManager = new TunerHalManager(); - private static TunerTsStreamerManager sInstance; - - /** - * Returns the singleton instance for the class - * - * @return TunerTsStreamerManager - */ - static synchronized TunerTsStreamerManager getInstance() { - if (sInstance == null) { - sInstance = new TunerTsStreamerManager(); - } - return sInstance; - } - - private TunerTsStreamerManager() {} - - synchronized TsDataSource createDataSource( - Context context, - TunerChannel channel, - EventDetector.EventListener listener, - int sessionId, - boolean reuse) { - TsStreamerCreator creator; - synchronized (mCancelLock) { - if (mStreamerFinder.containsLocked(channel)) { - mStreamerFinder.appendSessionLocked(channel, sessionId); - TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); - TsDataSource source = streamer.createDataSource(); - mListeners.put(sessionId, listener); - streamer.registerListener(listener); - mSourceToStreamerMap.put(source, streamer); - return source; - } - creator = new TsStreamerCreator(context, channel, listener); - mCreators.put(sessionId, creator); - } - TunerTsStreamer streamer = creator.create(sessionId, reuse); - synchronized (mCancelLock) { - mCreators.remove(sessionId); - if (streamer == null) { - return null; - } - if (!creator.isCancelledLocked()) { - mStreamerFinder.putLocked(channel, sessionId, streamer); - TsDataSource source = streamer.createDataSource(); - mListeners.put(sessionId, listener); - mSourceToStreamerMap.put(source, streamer); - return source; - } - } - // Created streamer was cancelled by a new tune request. - streamer.stopStream(); - TunerHal hal = streamer.getTunerHal(); - hal.setHasPendingTune(false); - mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); - return null; - } - - synchronized void releaseDataSource(TsDataSource source, int sessionId, boolean reuse) { - TunerTsStreamer streamer; - synchronized (mCancelLock) { - streamer = mSourceToStreamerMap.get(source); - mSourceToStreamerMap.remove(source); - if (streamer == null) { - return; - } - EventDetector.EventListener listener = mListeners.remove(sessionId); - streamer.unregisterListener(listener); - TunerChannel channel = streamer.getChannel(); - SoftPreconditions.checkState(channel != null); - mStreamerFinder.removeSessionLocked(channel, sessionId); - if (mStreamerFinder.containsLocked(channel)) { - return; - } - } - streamer.stopStream(); - TunerHal hal = streamer.getTunerHal(); - hal.setHasPendingTune(false); - mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); - } - - void setHasPendingTune(int sessionId) { - synchronized (mCancelLock) { - if (mCreators.containsKey(sessionId)) { - mCreators.get(sessionId).cancelLocked(); - } - } - } - - /** Add tuner hal into TunerHalManager for test. */ - void addTunerHal(TunerHal tunerHal, int sessionId) { - mTunerHalManager.addTunerHal(tunerHal, sessionId); - } - - synchronized void release(int sessionId) { - mTunerHalManager.releaseCachedHal(sessionId); - } - - private class StreamerFinder { - private final Map> mSessions = new HashMap<>(); - private final Map mStreamers = new HashMap<>(); - - // @GuardedBy("mCancelLock") - private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) { - Set sessions = new HashSet<>(); - sessions.add(sessionId); - mSessions.put(channel, sessions); - mStreamers.put(channel, streamer); - } - - // @GuardedBy("mCancelLock") - private void appendSessionLocked(TunerChannel channel, int sessionId) { - if (mSessions.containsKey(channel)) { - mSessions.get(channel).add(sessionId); - } - } - - // @GuardedBy("mCancelLock") - private void removeSessionLocked(TunerChannel channel, int sessionId) { - Set sessions = mSessions.get(channel); - sessions.remove(sessionId); - if (sessions.size() == 0) { - mSessions.remove(channel); - mStreamers.remove(channel); - } - } - - // @GuardedBy("mCancelLock") - private boolean containsLocked(TunerChannel channel) { - return mSessions.containsKey(channel); - } - - // @GuardedBy("mCancelLock") - private TunerTsStreamer getStreamerLocked(TunerChannel channel) { - return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null; - } - } - - /** - * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same session. - * The class supports the cancellation in creating new {@link TunerTsStreamer}. - */ - private class TsStreamerCreator { - private final Context mContext; - private final TunerChannel mChannel; - private final EventDetector.EventListener mEventListener; - // mCancelled will be {@code true} if a new tune request for the same session - // cancels create(). - private boolean mCancelled; - private TunerHal mTunerHal; - - private TsStreamerCreator( - Context context, TunerChannel channel, EventDetector.EventListener listener) { - mContext = context; - mChannel = channel; - mEventListener = listener; - } - - private TunerTsStreamer create(int sessionId, boolean reuse) { - TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId); - if (hal == null) { - return null; - } - boolean canceled = false; - synchronized (mCancelLock) { - if (!mCancelled) { - mTunerHal = hal; - } else { - canceled = true; - } - } - if (!canceled) { - TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext); - if (tsStreamer.startStream(mChannel)) { - return tsStreamer; - } - synchronized (mCancelLock) { - mTunerHal = null; - } - } - hal.setHasPendingTune(false); - // Since TunerTsStreamer is not properly created, closes TunerHal. - // And do not re-use TunerHal when it is not cancelled. - mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse); - return null; - } - - // @GuardedBy("mCancelLock") - private void cancelLocked() { - if (mCancelled) { - return; - } - mCancelled = true; - if (mTunerHal != null) { - mTunerHal.setHasPendingTune(true); - } - } - - // @GuardedBy("mCancelLock") - private boolean isCancelledLocked() { - return mCancelled; - } - } - - /** - * Supports sharing {@link TunerHal} among multiple sessions. The class also supports session - * affinity for {@link TunerHal} allocation. - */ - private class TunerHalManager { - private final Map mTunerHals = new HashMap<>(); - - private TunerHal getOrCreateTunerHal(Context context, int sessionId) { - // Handles session affinity. - TunerHal hal = mTunerHals.get(sessionId); - if (hal != null) { - mTunerHals.remove(sessionId); - return hal; - } - // Finds a TunerHal which is cached for other sessions. - Iterator it = mTunerHals.keySet().iterator(); - if (it.hasNext()) { - Integer key = (Integer) it.next(); - hal = mTunerHals.get(key); - mTunerHals.remove(key); - return hal; - } - return TunerHal.createInstance(context); - } - - private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) { - if (!reuse || !hal.isReusable()) { - AutoCloseableUtils.closeQuietly(hal); - return; - } - TunerHal cachedHal = mTunerHals.get(sessionId); - if (cachedHal != hal) { - mTunerHals.put(sessionId, hal); - if (cachedHal != null) { - AutoCloseableUtils.closeQuietly(cachedHal); - } - } - } - - private void releaseCachedHal(int sessionId) { - TunerHal hal = mTunerHals.get(sessionId); - if (hal != null) { - mTunerHals.remove(sessionId); - } - if (hal != null) { - AutoCloseableUtils.closeQuietly(hal); - } - } - - private void addTunerHal(TunerHal tunerHal, int sessionId) { - mTunerHals.put(sessionId, tunerHal); - } - } -} diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java deleted file mode 100644 index 6d0eb90f..00000000 --- a/src/com/android/tv/tuner/ts/SectionParser.java +++ /dev/null @@ -1,2083 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.ts; - -import android.media.tv.TvContentRating; -import android.media.tv.TvContract.Programs.Genres; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; -import android.util.SparseArray; -import com.android.tv.tuner.data.PsiData.PatItem; -import com.android.tv.tuner.data.PsiData.PmtItem; -import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor; -import com.android.tv.tuner.data.PsipData.CaptionServiceDescriptor; -import com.android.tv.tuner.data.PsipData.ContentAdvisoryDescriptor; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.PsipData.EttItem; -import com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor; -import com.android.tv.tuner.data.PsipData.GenreDescriptor; -import com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor; -import com.android.tv.tuner.data.PsipData.MgtItem; -import com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor; -import com.android.tv.tuner.data.PsipData.PsipSection; -import com.android.tv.tuner.data.PsipData.RatingRegion; -import com.android.tv.tuner.data.PsipData.RegionalRating; -import com.android.tv.tuner.data.PsipData.SdtItem; -import com.android.tv.tuner.data.PsipData.ServiceDescriptor; -import com.android.tv.tuner.data.PsipData.ShortEventDescriptor; -import com.android.tv.tuner.data.PsipData.TsDescriptor; -import com.android.tv.tuner.data.PsipData.VctItem; -import com.android.tv.tuner.data.nano.Channel; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.util.ByteArrayBuffer; -import com.android.tv.tuner.util.ConvertUtils; -import com.ibm.icu.text.UnicodeDecompressor; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** Parses ATSC PSIP sections. */ -public class SectionParser { - private static final String TAG = "SectionParser"; - private static final boolean DEBUG = false; - - private static final byte TABLE_ID_PAT = (byte) 0x00; - private static final byte TABLE_ID_PMT = (byte) 0x02; - private static final byte TABLE_ID_MGT = (byte) 0xc7; - private static final byte TABLE_ID_TVCT = (byte) 0xc8; - private static final byte TABLE_ID_CVCT = (byte) 0xc9; - private static final byte TABLE_ID_EIT = (byte) 0xcb; - private static final byte TABLE_ID_ETT = (byte) 0xcc; - - // Table id for DVB - private static final byte TABLE_ID_SDT = (byte) 0x42; - private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e; - private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f; - private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50; - private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60; - - // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25. - public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a; - public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; - public static final int DESCRIPTOR_TAG_CONTENT_ADVISORY = 0x87; - public static final int DESCRIPTOR_TAG_AC3_AUDIO_STREAM = 0x81; - public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0; - public static final int DESCRIPTOR_TAG_GENRE = 0xab; - - // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12. - public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48; - public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d; - public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54; - public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55; - - private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00; - private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff - private static final byte MODE_UTF16 = (byte) 0x3f; - private static final byte MODE_SCSU = (byte) 0x3e; - private static final int MAX_SHORT_NAME_BYTES = 14; - - // See ANSI/CEA-766-C. - private static final int RATING_REGION_US_TV = 1; - private static final int RATING_REGION_KR_TV = 4; - - // The following values are defined in the live channels app. - // See https://developer.android.com/reference/android/media/tv/TvContentRating.html. - private static final String RATING_DOMAIN = "com.android.tv"; - private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV"; - private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV"; - private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV"; - - private static final String[] RATING_REGION_TABLE_US_TV = { - "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA" - }; - - private static final String[] RATING_REGION_TABLE_US_MV = { - "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17" - }; - - private static final String[] RATING_REGION_TABLE_KR_TV = { - "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19" - }; - - private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = { - "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV" - }; - - // According to ANSI-CEA-766-D - private static final int VALUE_US_TV_Y = 1; - private static final int VALUE_US_TV_Y7 = 2; - private static final int VALUE_US_TV_NONE = 1; - private static final int VALUE_US_TV_G = 2; - private static final int VALUE_US_TV_PG = 3; - private static final int VALUE_US_TV_14 = 4; - private static final int VALUE_US_TV_MA = 5; - - private static final int DIMENSION_US_TV_RATING = 0; - private static final int DIMENSION_US_TV_D = 1; - private static final int DIMENSION_US_TV_L = 2; - private static final int DIMENSION_US_TV_S = 3; - private static final int DIMENSION_US_TV_V = 4; - private static final int DIMENSION_US_TV_Y = 5; - private static final int DIMENSION_US_TV_FV = 6; - private static final int DIMENSION_US_MV_RATING = 7; - - private static final int VALUE_US_MV_G = 2; - private static final int VALUE_US_MV_PG = 3; - private static final int VALUE_US_MV_PG13 = 4; - private static final int VALUE_US_MV_R = 5; - private static final int VALUE_US_MV_NC17 = 6; - private static final int VALUE_US_MV_X = 7; - - private static final String STRING_US_TV_Y = "US_TV_Y"; - private static final String STRING_US_TV_Y7 = "US_TV_Y7"; - private static final String STRING_US_TV_FV = "US_TV_FV"; - - /* - * The following CRC table is from the code generated by the following command. - * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c - * To see the details of pycrc, visit http://www.tty1.net/pycrc/index_en.html - */ - public static final int[] CRC_TABLE = { - 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, - 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, - 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, - 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, - 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, - 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, - 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, - 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, - 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, - 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, - 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, - 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, - 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, - 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, - 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, - 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, - 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, - 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, - 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, - 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, - 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, - 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, - 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, - 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, - 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, - 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, - 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, - 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, - 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, - 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, - 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, - 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, - 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, - 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, - 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, - 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, - 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, - 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, - 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, - 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, - 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, - 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, - 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, - 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, - 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, - 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, - 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, - 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, - 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, - 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, - 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, - 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, - 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, - 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, - 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, - 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, - 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, - 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, - 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, - 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, - 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, - 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, - 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 - }; - - // A table which maps ATSC genres to TIF genres. - // See ATSC/65 Table 6.20. - private static final String[] CANONICAL_GENRES_TABLE = { - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - Genres.EDUCATION, - Genres.ENTERTAINMENT, - Genres.MOVIES, - Genres.NEWS, - Genres.LIFE_STYLE, - Genres.SPORTS, - null, - Genres.MOVIES, - null, - Genres.FAMILY_KIDS, - Genres.DRAMA, - null, - Genres.ENTERTAINMENT, - Genres.SPORTS, - Genres.SPORTS, - null, - null, - Genres.MUSIC, - Genres.EDUCATION, - null, - Genres.COMEDY, - null, - Genres.MUSIC, - null, - null, - Genres.MOVIES, - Genres.ENTERTAINMENT, - Genres.NEWS, - Genres.DRAMA, - Genres.EDUCATION, - Genres.MOVIES, - Genres.SPORTS, - Genres.MOVIES, - null, - Genres.LIFE_STYLE, - Genres.ARTS, - Genres.LIFE_STYLE, - Genres.SPORTS, - null, - null, - Genres.GAMING, - Genres.LIFE_STYLE, - Genres.SPORTS, - null, - Genres.LIFE_STYLE, - Genres.EDUCATION, - Genres.EDUCATION, - Genres.LIFE_STYLE, - Genres.SPORTS, - Genres.LIFE_STYLE, - Genres.MOVIES, - Genres.NEWS, - null, - null, - null, - Genres.EDUCATION, - null, - null, - null, - Genres.EDUCATION, - null, - null, - null, - Genres.DRAMA, - Genres.MUSIC, - Genres.MOVIES, - null, - Genres.ANIMAL_WILDLIFE, - null, - null, - Genres.PREMIER, - null, - null, - null, - null, - Genres.SPORTS, - Genres.ARTS, - null, - null, - null, - Genres.MOVIES, - Genres.TECH_SCIENCE, - Genres.DRAMA, - null, - Genres.SHOPPING, - Genres.DRAMA, - null, - Genres.MOVIES, - Genres.ENTERTAINMENT, - Genres.TECH_SCIENCE, - Genres.SPORTS, - Genres.TRAVEL, - Genres.ENTERTAINMENT, - Genres.ARTS, - Genres.NEWS, - null, - Genres.ARTS, - Genres.SPORTS, - Genres.SPORTS, - Genres.NEWS, - Genres.SPORTS, - Genres.SPORTS, - Genres.SPORTS, - Genres.FAMILY_KIDS, - Genres.FAMILY_KIDS, - Genres.MOVIES, - null, - Genres.TECH_SCIENCE, - Genres.MUSIC, - null, - Genres.SPORTS, - Genres.FAMILY_KIDS, - Genres.NEWS, - Genres.SPORTS, - Genres.NEWS, - Genres.SPORTS, - Genres.ANIMAL_WILDLIFE, - null, - Genres.MUSIC, - Genres.NEWS, - Genres.SPORTS, - null, - Genres.NEWS, - Genres.NEWS, - Genres.NEWS, - Genres.NEWS, - Genres.SPORTS, - Genres.MOVIES, - Genres.ARTS, - Genres.ANIMAL_WILDLIFE, - Genres.MUSIC, - Genres.MUSIC, - Genres.MOVIES, - Genres.EDUCATION, - Genres.DRAMA, - Genres.SPORTS, - Genres.SPORTS, - Genres.SPORTS, - Genres.SPORTS, - null, - Genres.SPORTS, - Genres.SPORTS, - }; - - // A table which contains ATSC categorical genre code assignments. - // See ATSC/65 Table 6.20. - private static final String[] BROADCAST_GENRES_TABLE = - new String[] { - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "Education", - "Entertainment", - "Movie", - "News", - "Religious", - "Sports", - "Other", - "Action", - "Advertisement", - "Animated", - "Anthology", - "Automobile", - "Awards", - "Baseball", - "Basketball", - "Bulletin", - "Business", - "Classical", - "College", - "Combat", - "Comedy", - "Commentary", - "Concert", - "Consumer", - "Contemporary", - "Crime", - "Dance", - "Documentary", - "Drama", - "Elementary", - "Erotica", - "Exercise", - "Fantasy", - "Farm", - "Fashion", - "Fiction", - "Food", - "Football", - "Foreign", - "Fund Raiser", - "Game/Quiz", - "Garden", - "Golf", - "Government", - "Health", - "High School", - "History", - "Hobby", - "Hockey", - "Home", - "Horror", - "Information", - "Instruction", - "International", - "Interview", - "Language", - "Legal", - "Live", - "Local", - "Math", - "Medical", - "Meeting", - "Military", - "Miniseries", - "Music", - "Mystery", - "National", - "Nature", - "Police", - "Politics", - "Premier", - "Prerecorded", - "Product", - "Professional", - "Public", - "Racing", - "Reading", - "Repair", - "Repeat", - "Review", - "Romance", - "Science", - "Series", - "Service", - "Shopping", - "Soap Opera", - "Special", - "Suspense", - "Talk", - "Technical", - "Tennis", - "Travel", - "Variety", - "Video", - "Weather", - "Western", - "Art", - "Auto Racing", - "Aviation", - "Biography", - "Boating", - "Bowling", - "Boxing", - "Cartoon", - "Children", - "Classic Film", - "Community", - "Computers", - "Country Music", - "Court", - "Extreme Sports", - "Family", - "Financial", - "Gymnastics", - "Headlines", - "Horse Racing", - "Hunting/Fishing/Outdoors", - "Independent", - "Jazz", - "Magazine", - "Motorcycle Racing", - "Music/Film/Books", - "News-International", - "News-Local", - "News-National", - "News-Regional", - "Olympics", - "Original", - "Performing Arts", - "Pets/Animals", - "Pop", - "Rock & Roll", - "Sci-Fi", - "Self Improvement", - "Sitcom", - "Skating", - "Skiing", - "Soccer", - "Track/Field", - "True", - "Volleyball", - "Wrestling", - }; - - // Audio language code map from ISO 639-2/B to 639-2/T, in order to show correct audio language. - private static final HashMap ISO_LANGUAGE_CODE_MAP; - - static { - ISO_LANGUAGE_CODE_MAP = new HashMap<>(); - ISO_LANGUAGE_CODE_MAP.put("alb", "sqi"); - ISO_LANGUAGE_CODE_MAP.put("arm", "hye"); - ISO_LANGUAGE_CODE_MAP.put("baq", "eus"); - ISO_LANGUAGE_CODE_MAP.put("bur", "mya"); - ISO_LANGUAGE_CODE_MAP.put("chi", "zho"); - ISO_LANGUAGE_CODE_MAP.put("cze", "ces"); - ISO_LANGUAGE_CODE_MAP.put("dut", "nld"); - ISO_LANGUAGE_CODE_MAP.put("fre", "fra"); - ISO_LANGUAGE_CODE_MAP.put("geo", "kat"); - ISO_LANGUAGE_CODE_MAP.put("ger", "deu"); - ISO_LANGUAGE_CODE_MAP.put("gre", "ell"); - ISO_LANGUAGE_CODE_MAP.put("ice", "isl"); - ISO_LANGUAGE_CODE_MAP.put("mac", "mkd"); - ISO_LANGUAGE_CODE_MAP.put("mao", "mri"); - ISO_LANGUAGE_CODE_MAP.put("may", "msa"); - ISO_LANGUAGE_CODE_MAP.put("per", "fas"); - ISO_LANGUAGE_CODE_MAP.put("rum", "ron"); - ISO_LANGUAGE_CODE_MAP.put("slo", "slk"); - ISO_LANGUAGE_CODE_MAP.put("tib", "bod"); - ISO_LANGUAGE_CODE_MAP.put("wel", "cym"); - ISO_LANGUAGE_CODE_MAP.put("esl", "spa"); // Special entry for channel 9-1 KQED in bay area. - } - - // Containers to store the last version numbers of the PSIP sections. - private final HashMap mSectionVersionMap = new HashMap<>(); - private final SparseArray> mParsedEttItems = new SparseArray<>(); - - public interface OutputListener { - void onPatParsed(List items); - - void onPmtParsed(int programNumber, List items); - - void onMgtParsed(List items); - - void onVctParsed(List items, int sectionNumber, int lastSectionNumber); - - void onEitParsed(int sourceId, List items); - - void onEttParsed(int sourceId, List descriptions); - - void onSdtParsed(List items); - } - - private final OutputListener mListener; - - public SectionParser(OutputListener listener) { - mListener = listener; - } - - public void parseSections(ByteArrayBuffer data) { - int pos = 0; - while (pos + 3 <= data.length()) { - if ((data.byteAt(pos) & 0xff) == 0xff) { - // Clear stuffing bytes according to H222.0 section 2.4.4. - data.setLength(0); - break; - } - int sectionLength = - (((data.byteAt(pos + 1) & 0x0f) << 8) | (data.byteAt(pos + 2) & 0xff)) + 3; - if (pos + sectionLength > data.length()) { - break; - } - if (DEBUG) { - Log.d(TAG, "parseSections 0x" + Integer.toHexString(data.byteAt(pos) & 0xff)); - } - parseSection(Arrays.copyOfRange(data.buffer(), pos, pos + sectionLength)); - pos += sectionLength; - } - if (mListener != null) { - for (int i = 0; i < mParsedEttItems.size(); ++i) { - int sourceId = mParsedEttItems.keyAt(i); - List descriptions = mParsedEttItems.valueAt(i); - mListener.onEttParsed(sourceId, descriptions); - } - } - mParsedEttItems.clear(); - } - - public void resetVersionNumbers() { - mSectionVersionMap.clear(); - } - - private void parseSection(byte[] data) { - if (!checkSanity(data)) { - Log.d(TAG, "Bad CRC!"); - return; - } - PsipSection section = PsipSection.create(data); - if (section == null) { - return; - } - - // The currentNextIndicator indicates that the section sent is currently applicable. - if (!section.getCurrentNextIndicator()) { - return; - } - int versionNumber = (data[5] & 0x3e) >> 1; - Integer oldVersionNumber = mSectionVersionMap.get(section); - - // The versionNumber shall be incremented when a change in the information carried within - // the section occurs. - if (oldVersionNumber != null && versionNumber == oldVersionNumber) { - return; - } - boolean result = false; - switch (data[0]) { - case TABLE_ID_PAT: - result = parsePAT(data); - break; - case TABLE_ID_PMT: - result = parsePMT(data); - break; - case TABLE_ID_MGT: - result = parseMGT(data); - break; - case TABLE_ID_TVCT: - case TABLE_ID_CVCT: - result = parseVCT(data); - break; - case TABLE_ID_EIT: - result = parseEIT(data); - break; - case TABLE_ID_ETT: - result = parseETT(data); - break; - case TABLE_ID_SDT: - result = parseSDT(data); - break; - case TABLE_ID_DVB_ACTUAL_P_F_EIT: - case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT: - result = parseDVBEIT(data); - break; - default: - break; - } - if (result) { - mSectionVersionMap.put(section, versionNumber); - } - } - - private boolean parsePAT(byte[] data) { - if (DEBUG) { - Log.d(TAG, "PAT is discovered."); - } - int pos = 8; - - List results = new ArrayList<>(); - for (; pos < data.length - 4; pos = pos + 4) { - if (pos > data.length - 4 - 4) { - Log.e(TAG, "Broken PAT."); - return false; - } - int programNo = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff); - int pmtPid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff); - results.add(new PatItem(programNo, pmtPid)); - } - if (mListener != null) { - mListener.onPatParsed(results); - } - return true; - } - - private boolean parsePMT(byte[] data) { - int table_id_ext = ((data[3] & 0xff) << 8) | (data[4] & 0xff); - if (DEBUG) { - Log.d(TAG, "PMT is discovered. programNo = " + table_id_ext); - } - if (data.length <= 11) { - Log.e(TAG, "Broken PMT."); - return false; - } - int pcrPid = (data[8] & 0x1f) << 8 | data[9]; - int programInfoLen = (data[10] & 0x0f) << 8 | data[11]; - int pos = 12; - List descriptors = parseDescriptors(data, pos, pos + programInfoLen); - pos += programInfoLen; - if (DEBUG) { - Log.d(TAG, "PMT descriptors size: " + descriptors.size()); - } - List results = new ArrayList<>(); - for (; pos < data.length - 4; ) { - if (pos < 0) { - Log.e(TAG, "Broken PMT."); - return false; - } - int streamType = data[pos] & 0xff; - int esPid = (data[pos + 1] & 0x1f) << 8 | (data[pos + 2] & 0xff); - int esInfoLen = (data[pos + 3] & 0xf) << 8 | (data[pos + 4] & 0xff); - if (data.length < pos + esInfoLen + 5) { - Log.e(TAG, "Broken PMT."); - return false; - } - descriptors = parseDescriptors(data, pos + 5, pos + 5 + esInfoLen); - List audioTracks = generateAudioTracks(descriptors); - List captionTracks = generateCaptionTracks(descriptors); - PmtItem pmtItem = new PmtItem(streamType, esPid, audioTracks, captionTracks); - if (DEBUG) { - Log.d(TAG, "PMT " + pmtItem + " descriptors size: " + descriptors.size()); - } - results.add(pmtItem); - pos = pos + esInfoLen + 5; - } - results.add(new PmtItem(PmtItem.ES_PID_PCR, pcrPid, null, null)); - if (mListener != null) { - mListener.onPmtParsed(table_id_ext, results); - } - return true; - } - - private boolean parseMGT(byte[] data) { - // For details of the structure for MGT, see ATSC A/65 Table 6.2. - if (DEBUG) { - Log.d(TAG, "MGT is discovered."); - } - if (data.length <= 10) { - Log.e(TAG, "Broken MGT."); - return false; - } - int tablesDefined = ((data[9] & 0xff) << 8) | (data[10] & 0xff); - int pos = 11; - List results = new ArrayList<>(); - for (int i = 0; i < tablesDefined; ++i) { - if (data.length <= pos + 10) { - Log.e(TAG, "Broken MGT."); - return false; - } - int tableType = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff); - int tableTypePid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff); - int descriptorsLength = ((data[pos + 9] & 0x0f) << 8) | (data[pos + 10] & 0xff); - pos += 11 + descriptorsLength; - results.add(new MgtItem(tableType, tableTypePid)); - } - // Skip the remaining descriptor part which we don't use. - - if (mListener != null) { - mListener.onMgtParsed(results); - } - return true; - } - - private boolean parseVCT(byte[] data) { - // For details of the structure for VCT, see ATSC A/65 Table 6.4 and 6.8. - if (DEBUG) { - Log.d(TAG, "VCT is discovered."); - } - if (data.length <= 9) { - Log.e(TAG, "Broken VCT."); - return false; - } - int numChannelsInSection = (data[9] & 0xff); - int sectionNumber = (data[6] & 0xff); - int lastSectionNumber = (data[7] & 0xff); - if (sectionNumber > lastSectionNumber) { - // According to section 6.3.1 of the spec ATSC A/65, - // last section number is the largest section number. - Log.w( - TAG, - "Invalid VCT. Section Number " - + sectionNumber - + " > Last Section Number " - + lastSectionNumber); - return false; - } - int pos = 10; - List results = new ArrayList<>(); - for (int i = 0; i < numChannelsInSection; ++i) { - if (data.length <= pos + 31) { - Log.e(TAG, "Broken VCT."); - return false; - } - String shortName = ""; - int shortNameSize = getShortNameSize(data, pos); - try { - shortName = - new String(Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Broken VCT.", e); - return false; - } - if ((data[pos + 14] & 0xf0) != 0xf0) { - Log.e(TAG, "Broken VCT."); - return false; - } - int majorNumber = ((data[pos + 14] & 0x0f) << 6) | ((data[pos + 15] & 0xff) >> 2); - int minorNumber = ((data[pos + 15] & 0x03) << 8) | (data[pos + 16] & 0xff); - if ((majorNumber & 0x3f0) == 0x3f0) { - // If the six MSBs are 111111, these indicate that there is only one-part channel - // number. To see details, refer A/65 Section 6.3.2. - majorNumber = ((majorNumber & 0xf) << 10) + minorNumber; - minorNumber = 0; - } - int channelTsid = ((data[pos + 22] & 0xff) << 8) | (data[pos + 23] & 0xff); - int programNumber = ((data[pos + 24] & 0xff) << 8) | (data[pos + 25] & 0xff); - boolean accessControlled = (data[pos + 26] & 0x20) != 0; - boolean hidden = (data[pos + 26] & 0x10) != 0; - int serviceType = (data[pos + 27] & 0x3f); - int sourceId = ((data[pos + 28] & 0xff) << 8) | (data[pos + 29] & 0xff); - int descriptorsPos = pos + 32; - int descriptorsLength = ((data[pos + 30] & 0x03) << 8) | (data[pos + 31] & 0xff); - pos += 32 + descriptorsLength; - if (data.length < pos) { - Log.e(TAG, "Broken VCT."); - return false; - } - List descriptors = - parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); - String longName = null; - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof ExtendedChannelNameDescriptor) { - ExtendedChannelNameDescriptor extendedChannelNameDescriptor = - (ExtendedChannelNameDescriptor) descriptor; - longName = extendedChannelNameDescriptor.getLongChannelName(); - break; - } - } - if (DEBUG) { - Log.d( - TAG, - String.format( - "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d " - + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d", - shortName, - longName, - serviceType, - channelTsid, - programNumber, - majorNumber, - minorNumber, - accessControlled, - hidden, - descriptors.size())); - } - if (!accessControlled - && !hidden - && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO - || serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION - || serviceType - == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) { - // Hide hidden, encrypted, or unsupported ATSC service type channels - results.add( - new VctItem( - shortName, - longName, - serviceType, - channelTsid, - programNumber, - majorNumber, - minorNumber, - sourceId)); - } - } - // Skip the remaining descriptor part which we don't use. - - if (mListener != null) { - mListener.onVctParsed(results, sectionNumber, lastSectionNumber); - } - return true; - } - - private boolean parseEIT(byte[] data) { - // For details of the structure for EIT, see ATSC A/65 Table 6.11. - if (DEBUG) { - Log.d(TAG, "EIT is discovered."); - } - if (data.length <= 9) { - Log.e(TAG, "Broken EIT."); - return false; - } - int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); - int numEventsInSection = (data[9] & 0xff); - - int pos = 10; - List results = new ArrayList<>(); - for (int i = 0; i < numEventsInSection; ++i) { - if (data.length <= pos + 9) { - Log.e(TAG, "Broken EIT."); - return false; - } - if ((data[pos] & 0xc0) != 0xc0) { - Log.e(TAG, "Broken EIT."); - return false; - } - int eventId = ((data[pos] & 0x3f) << 8) + (data[pos + 1] & 0xff); - long startTime = - ((data[pos + 2] & (long) 0xff) << 24) - | ((data[pos + 3] & 0xff) << 16) - | ((data[pos + 4] & 0xff) << 8) - | (data[pos + 5] & 0xff); - int lengthInSecond = - ((data[pos + 6] & 0x0f) << 16) - | ((data[pos + 7] & 0xff) << 8) - | (data[pos + 8] & 0xff); - int titleLength = (data[pos + 9] & 0xff); - if (data.length <= pos + 10 + titleLength + 1) { - Log.e(TAG, "Broken EIT."); - return false; - } - String titleText = ""; - if (titleLength > 0) { - titleText = extractText(data, pos + 10); - } - if ((data[pos + 10 + titleLength] & 0xf0) != 0xf0) { - Log.e(TAG, "Broken EIT."); - return false; - } - int descriptorsLength = - ((data[pos + 10 + titleLength] & 0x0f) << 8) - | (data[pos + 10 + titleLength + 1] & 0xff); - int descriptorsPos = pos + 10 + titleLength + 2; - if (data.length < descriptorsPos + descriptorsLength) { - Log.e(TAG, "Broken EIT."); - return false; - } - List descriptors = - parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); - if (DEBUG) { - Log.d(TAG, String.format("EIT descriptors size: %d", descriptors.size())); - } - String contentRating = generateContentRating(descriptors); - String broadcastGenre = generateBroadcastGenre(descriptors); - String canonicalGenre = generateCanonicalGenre(descriptors); - List audioTracks = generateAudioTracks(descriptors); - List captionTracks = generateCaptionTracks(descriptors); - pos += 10 + titleLength + 2 + descriptorsLength; - results.add( - new EitItem( - EitItem.INVALID_PROGRAM_ID, - eventId, - titleText, - startTime, - lengthInSecond, - contentRating, - audioTracks, - captionTracks, - broadcastGenre, - canonicalGenre, - null)); - } - if (mListener != null) { - mListener.onEitParsed(sourceId, results); - } - return true; - } - - private boolean parseETT(byte[] data) { - // For details of the structure for ETT, see ATSC A/65 Table 6.13. - if (DEBUG) { - Log.d(TAG, "ETT is discovered."); - } - if (data.length <= 12) { - Log.e(TAG, "Broken ETT."); - return false; - } - int sourceId = ((data[9] & 0xff) << 8) | (data[10] & 0xff); - int eventId = (((data[11] & 0xff) << 8) | (data[12] & 0xff)) >> 2; - String text = extractText(data, 13); - List ettItems = mParsedEttItems.get(sourceId); - if (ettItems == null) { - ettItems = new ArrayList<>(); - mParsedEttItems.put(sourceId, ettItems); - } - ettItems.add(new EttItem(eventId, text)); - return true; - } - - private boolean parseSDT(byte[] data) { - // For details of the structure for SDT, see DVB Document A038 Table 5. - if (DEBUG) { - Log.d(TAG, "SDT id discovered"); - } - if (data.length <= 11) { - Log.e(TAG, "Broken SDT."); - return false; - } - if ((data[1] & 0x80) >> 7 != 1) { - Log.e(TAG, "Broken SDT, section syntax indicator error."); - return false; - } - int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); - int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); - int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); - int pos = 11; - if (sectionLength + 3 > data.length) { - Log.e(TAG, "Broken SDT."); - } - List sdtItems = new ArrayList<>(); - while (pos + 9 < data.length) { - int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff); - int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff); - pos += 5; - List descriptors = parseDescriptors(data, pos, pos + descriptorsLength); - List serviceDescriptors = generateServiceDescriptors(descriptors); - String serviceName = ""; - String serviceProviderName = ""; - int serviceType = 0; - for (ServiceDescriptor serviceDescriptor : serviceDescriptors) { - serviceName = serviceDescriptor.getServiceName(); - serviceProviderName = serviceDescriptor.getServiceProviderName(); - serviceType = serviceDescriptor.getServiceType(); - } - if (serviceDescriptors.size() > 0) { - sdtItems.add( - new SdtItem( - serviceName, - serviceProviderName, - serviceType, - serviceId, - originalNetworkId)); - } - pos += descriptorsLength; - } - if (mListener != null) { - mListener.onSdtParsed(sdtItems); - } - return true; - } - - private boolean parseDVBEIT(byte[] data) { - // For details of the structure for DVB ETT, see DVB Document A038 Table 7. - if (DEBUG) { - Log.d(TAG, "DVB EIT is discovered."); - } - if (data.length < 18) { - Log.e(TAG, "Broken DVB EIT."); - return false; - } - int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff); - int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff); - int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff); - int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff); - - int pos = 14; - List results = new ArrayList<>(); - while (pos + 12 < data.length) { - int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff); - float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff); - int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f); - int mjdMonth = - (int) - ((modifiedJulianDate - 14956.1f - (int) (startYear * 365.25f)) - / 30.6001f); - int startDay = - (int) modifiedJulianDate - - 14956 - - (int) (startYear * 365.25f) - - (int) (mjdMonth * 30.6001f); - int startMonth = mjdMonth - 1; - if (mjdMonth == 14 || mjdMonth == 15) { - startYear += 1; - startMonth -= 12; - } - int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f); - int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f); - int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f); - Calendar calendar = Calendar.getInstance(); - startYear += 1900; - calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond); - long startTime = - ConvertUtils.convertUnixEpochToGPSTime(calendar.getTimeInMillis() / 1000); - int durationInSecond = - (((data[pos + 7] & 0xf0) >> 4) * 10 + (data[pos + 7] & 0x0f)) * 3600 - + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60 - + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f)); - int descriptorsLength = ((data[pos + 10] & 0x0f) << 8) | (data[pos + 10 + 1] & 0xff); - int descriptorsPos = pos + 10 + 2; - if (data.length < descriptorsPos + descriptorsLength) { - Log.e(TAG, "Broken EIT."); - return false; - } - List descriptors = - parseDescriptors(data, descriptorsPos, descriptorsPos + descriptorsLength); - if (DEBUG) { - Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size())); - } - // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for - // details. Content rating here will be null - String contentRating = generateContentRating(descriptors); - // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details. - // Genre here will be null here. - String broadcastGenre = generateBroadcastGenre(descriptors); - String canonicalGenre = generateCanonicalGenre(descriptors); - String titleText = generateShortEventName(descriptors); - List audioTracks = generateAudioTracks(descriptors); - List captionTracks = generateCaptionTracks(descriptors); - pos += 12 + descriptorsLength; - results.add( - new EitItem( - EitItem.INVALID_PROGRAM_ID, - eventId, - titleText, - startTime, - durationInSecond, - contentRating, - audioTracks, - captionTracks, - broadcastGenre, - canonicalGenre, - null)); - } - if (mListener != null) { - mListener.onEitParsed(sourceId, results); - } - return true; - } - - private static List generateAudioTracks(List descriptors) { - // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639 - // Language descriptor. - List ac3Tracks = new ArrayList<>(); - List iso639LanguageTracks = new ArrayList<>(); - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof Ac3AudioDescriptor) { - Ac3AudioDescriptor audioDescriptor = (Ac3AudioDescriptor) descriptor; - AtscAudioTrack audioTrack = new AtscAudioTrack(); - if (audioDescriptor.getLanguage() != null) { - audioTrack.language = audioDescriptor.getLanguage(); - } - if (audioTrack.language == null) { - audioTrack.language = ""; - } - audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED; - audioTrack.channelCount = audioDescriptor.getNumChannels(); - audioTrack.sampleRate = audioDescriptor.getSampleRate(); - ac3Tracks.add(audioTrack); - } - } - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof Iso639LanguageDescriptor) { - Iso639LanguageDescriptor iso639LanguageDescriptor = - (Iso639LanguageDescriptor) descriptor; - iso639LanguageTracks.addAll(iso639LanguageDescriptor.getAudioTracks()); - } - } - - // An AC3 audio stream descriptor only has a audio channel count and a audio sample rate - // while a ISO 639 Language descriptor only has a audio type, which describes a main use - // case of its audio track. - // Some channels contain only AC3 audio stream descriptors with valid language values. - // Other channels contain both an AC3 audio stream descriptor and a ISO 639 Language - // descriptor per audio track, and those AC3 audio stream descriptors often have a null - // value of language field. - // Combines two descriptors into one in order to gather more audio track specific - // information as much as possible. - List tracks = new ArrayList<>(); - if (!ac3Tracks.isEmpty() - && !iso639LanguageTracks.isEmpty() - && ac3Tracks.size() != iso639LanguageTracks.size()) { - // This shouldn't be happen. In here, it handles two cases. The first case is that the - // only one type of descriptors arrives. The second case is that the two types of - // descriptors have the same number of tracks. - Log.e(TAG, "AC3 audio stream descriptors size != ISO 639 Language descriptors size"); - return tracks; - } - int size = Math.max(ac3Tracks.size(), iso639LanguageTracks.size()); - for (int i = 0; i < size; ++i) { - AtscAudioTrack audioTrack = null; - if (i < ac3Tracks.size()) { - audioTrack = ac3Tracks.get(i); - } - if (i < iso639LanguageTracks.size()) { - if (audioTrack == null) { - audioTrack = iso639LanguageTracks.get(i); - } else { - AtscAudioTrack iso639LanguageTrack = iso639LanguageTracks.get(i); - if (audioTrack.language == null || TextUtils.equals(audioTrack.language, "")) { - audioTrack.language = iso639LanguageTrack.language; - } - audioTrack.audioType = iso639LanguageTrack.audioType; - } - } - String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.language); - if (language != null) { - audioTrack.language = language; - } - tracks.add(audioTrack); - } - return tracks; - } - - private static List generateCaptionTracks(List descriptors) { - List services = new ArrayList<>(); - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof CaptionServiceDescriptor) { - CaptionServiceDescriptor captionServiceDescriptor = - (CaptionServiceDescriptor) descriptor; - services.addAll(captionServiceDescriptor.getCaptionTracks()); - } - } - return services; - } - - @VisibleForTesting - static String generateContentRating(List descriptors) { - Set contentRatings = new ArraySet<>(); - List usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV); - List krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV); - for (RatingRegion region : usRatingRegions) { - String contentRating = getUsRating(region); - if (contentRating != null) { - contentRatings.add(contentRating); - } - } - for (RatingRegion region : krRatingRegions) { - String contentRating = getKrRating(region); - if (contentRating != null) { - contentRatings.add(contentRating); - } - } - return TextUtils.join(",", contentRatings); - } - - /** - * Gets a list of {@link RatingRegion} in the specific region. - * - * @param descriptors {@link TsDescriptor} list which may contains rating information - * @param region the specific region - * @return a list of {@link RatingRegion} in the specific region - */ - private static List getRatingRegions(List descriptors, int region) { - List ratingRegions = new ArrayList<>(); - for (TsDescriptor descriptor : descriptors) { - if (!(descriptor instanceof ContentAdvisoryDescriptor)) { - continue; - } - ContentAdvisoryDescriptor contentAdvisoryDescriptor = - (ContentAdvisoryDescriptor) descriptor; - for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) { - if (ratingRegion.getName() == region) { - ratingRegions.add(ratingRegion); - } - } - } - return ratingRegions; - } - - /** - * Gets US content rating and subratings (if any). - * - * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. - * @return A string representing the US content rating and subratings. The format of the string - * is defined in {@link TvContentRating}. null, if no such a string exists. - */ - private static String getUsRating(RatingRegion ratingRegion) { - if (ratingRegion.getName() != RATING_REGION_US_TV) { - return null; - } - List regionalRatings = ratingRegion.getRegionalRatings(); - String rating = null; - int ratingIndex = VALUE_US_TV_NONE; - List subratings = new ArrayList<>(); - for (RegionalRating index : regionalRatings) { - // See Table 3 of ANSI-CEA-766-D - int dimension = index.getDimension(); - int value = index.getRating(); - switch (dimension) { - // According to Table 6.27 of ATSC A65, - // the dimensions shall be in increasing order. - // Therefore, rating and ratingIndex are assigned before any corresponding - // subrating. - case DIMENSION_US_TV_RATING: - if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) { - rating = RATING_REGION_TABLE_US_TV[value]; - ratingIndex = value; - } - break; - case DIMENSION_US_TV_D: - if (value == 1 - && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) { - // US_TV_D is applicable to US_TV_PG and US_TV_14 - subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); - } - break; - case DIMENSION_US_TV_L: - case DIMENSION_US_TV_S: - case DIMENSION_US_TV_V: - if (value == 1 - && ratingIndex >= VALUE_US_TV_PG - && ratingIndex <= VALUE_US_TV_MA) { - // US_TV_L, US_TV_S, and US_TV_V are applicable to - // US_TV_PG, US_TV_14 and US_TV_MA - subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]); - } - break; - case DIMENSION_US_TV_Y: - if (rating == null) { - if (value == VALUE_US_TV_Y) { - rating = STRING_US_TV_Y; - } else if (value == VALUE_US_TV_Y7) { - rating = STRING_US_TV_Y7; - } - } - break; - case DIMENSION_US_TV_FV: - if (STRING_US_TV_Y7.equals(rating) && value == 1) { - // US_TV_FV is applicable to US_TV_Y7 - subratings.add(STRING_US_TV_FV); - } - break; - case DIMENSION_US_MV_RATING: - if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) { - if (value == VALUE_US_MV_X) { - // US_MV_X was replaced by US_MV_NC17 in 1990, - // and it's not supported by TvContentRating - value = VALUE_US_MV_NC17; - } - if (rating != null) { - // According to Table 3 of ANSI-CEA-766-D, - // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be - // present in the same descriptor. - Log.w( - TAG, - "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are " - + "present in the same descriptor"); - } else { - return TvContentRating.createRating( - RATING_DOMAIN, - RATING_REGION_RATING_SYSTEM_US_MV, - RATING_REGION_TABLE_US_MV[value - 2]) - .flattenToString(); - } - } - break; - - default: - break; - } - } - if (rating == null) { - return null; - } - - String[] subratingArray = subratings.toArray(new String[subratings.size()]); - return TvContentRating.createRating( - RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray) - .flattenToString(); - } - - /** - * Gets KR(South Korea) content rating. - * - * @param ratingRegion a {@link RatingRegion} instance which may contain rating information. - * @return A string representing the KR content rating. The format of the string is defined in - * {@link TvContentRating}. null, if no such a string exists. - */ - private static String getKrRating(RatingRegion ratingRegion) { - if (ratingRegion.getName() != RATING_REGION_KR_TV) { - return null; - } - List regionalRatings = ratingRegion.getRegionalRatings(); - String rating = null; - for (RegionalRating index : regionalRatings) { - if (index.getDimension() == 0 - && index.getRating() >= 0 - && index.getRating() < RATING_REGION_TABLE_KR_TV.length) { - rating = RATING_REGION_TABLE_KR_TV[index.getRating()]; - break; - } - } - if (rating == null) { - return null; - } - return TvContentRating.createRating( - RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating) - .flattenToString(); - } - - private static String generateBroadcastGenre(List descriptors) { - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof GenreDescriptor) { - GenreDescriptor genreDescriptor = (GenreDescriptor) descriptor; - return TextUtils.join(",", genreDescriptor.getBroadcastGenres()); - } - } - return null; - } - - private static String generateCanonicalGenre(List descriptors) { - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof GenreDescriptor) { - GenreDescriptor genreDescriptor = (GenreDescriptor) descriptor; - return Genres.encode(genreDescriptor.getCanonicalGenres()); - } - } - return null; - } - - private static List generateServiceDescriptors( - List descriptors) { - List serviceDescriptors = new ArrayList<>(); - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof ServiceDescriptor) { - ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor; - serviceDescriptors.add(serviceDescriptor); - } - } - return serviceDescriptors; - } - - private static String generateShortEventName(List descriptors) { - for (TsDescriptor descriptor : descriptors) { - if (descriptor instanceof ShortEventDescriptor) { - ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor; - return shortEventDescriptor.getEventName(); - } - } - return ""; - } - - private static List parseDescriptors(byte[] data, int offset, int limit) { - // For details of the structure for descriptors, see ATSC A/65 Section 6.9. - List descriptors = new ArrayList<>(); - if (data.length < limit) { - return descriptors; - } - int pos = offset; - while (pos + 1 < limit) { - int tag = data[pos] & 0xff; - int length = data[pos + 1] & 0xff; - if (length <= 0) { - break; - } - if (limit < pos + length + 2) { - break; - } - if (DEBUG) { - Log.d(TAG, String.format("Descriptor tag: %02x", tag)); - } - TsDescriptor descriptor = null; - switch (tag) { - case DESCRIPTOR_TAG_CONTENT_ADVISORY: - descriptor = parseContentAdvisory(data, pos, pos + length + 2); - break; - - case DESCRIPTOR_TAG_CAPTION_SERVICE: - descriptor = parseCaptionService(data, pos, pos + length + 2); - break; - - case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME: - descriptor = parseLongChannelName(data, pos, pos + length + 2); - break; - - case DESCRIPTOR_TAG_GENRE: - descriptor = parseGenre(data, pos, pos + length + 2); - break; - - case DESCRIPTOR_TAG_AC3_AUDIO_STREAM: - descriptor = parseAc3AudioStream(data, pos, pos + length + 2); - break; - - case DESCRIPTOR_TAG_ISO639LANGUAGE: - descriptor = parseIso639Language(data, pos, pos + length + 2); - break; - - case DVB_DESCRIPTOR_TAG_SERVICE: - descriptor = parseDvbService(data, pos, pos + length + 2); - break; - - case DVB_DESCRIPTOR_TAG_SHORT_EVENT: - descriptor = parseDvbShortEvent(data, pos, pos + length + 2); - break; - - case DVB_DESCRIPTOR_TAG_CONTENT: - descriptor = parseDvbContent(data, pos, pos + length + 2); - break; - - case DVB_DESCRIPTOR_TAG_PARENTAL_RATING: - descriptor = parseDvbParentalRating(data, pos, pos + length + 2); - break; - - default: - } - if (descriptor != null) { - if (DEBUG) { - Log.d(TAG, "Descriptor parsed: " + descriptor); - } - descriptors.add(descriptor); - } - pos += length + 2; - } - return descriptors; - } - - private static Iso639LanguageDescriptor parseIso639Language(byte[] data, int pos, int limit) { - // For the details of the structure of ISO 639 language descriptor, - // see ISO13818-1 second edition Section 2.6.18. - pos += 2; - List audioTracks = new ArrayList<>(); - while (pos + 4 <= limit) { - if (limit <= pos + 3) { - Log.e(TAG, "Broken Iso639Language."); - return null; - } - String language = new String(data, pos, 3); - int audioType = data[pos + 3] & 0xff; - AtscAudioTrack audioTrack = new AtscAudioTrack(); - audioTrack.language = language; - audioTrack.audioType = audioType; - audioTracks.add(audioTrack); - pos += 4; - } - return new Iso639LanguageDescriptor(audioTracks); - } - - private static CaptionServiceDescriptor parseCaptionService(byte[] data, int pos, int limit) { - // For the details of the structure of caption service descriptor, - // see ATSC A/65 Section 6.9.2. - if (limit <= pos + 2) { - Log.e(TAG, "Broken CaptionServiceDescriptor."); - return null; - } - List services = new ArrayList<>(); - pos += 2; - int numberServices = data[pos] & 0x1f; - ++pos; - if (limit < pos + numberServices * 6) { - Log.e(TAG, "Broken CaptionServiceDescriptor."); - return null; - } - for (int i = 0; i < numberServices; ++i) { - String language = new String(Arrays.copyOfRange(data, pos, pos + 3)); - pos += 3; - boolean ccType = (data[pos] & 0x80) != 0; - if (!ccType) { - pos += 3; - continue; - } - int captionServiceNumber = data[pos] & 0x3f; - ++pos; - boolean easyReader = (data[pos] & 0x80) != 0; - boolean wideAspectRatio = (data[pos] & 0x40) != 0; - byte[] reserved = new byte[2]; - reserved[0] = (byte) (data[pos] << 2); - reserved[0] |= (byte) ((data[pos + 1] & 0xc0) >>> 6); - reserved[1] = (byte) ((data[pos + 1] & 0x3f) << 2); - pos += 2; - AtscCaptionTrack captionTrack = new AtscCaptionTrack(); - captionTrack.language = language; - captionTrack.serviceNumber = captionServiceNumber; - captionTrack.easyReader = easyReader; - captionTrack.wideAspectRatio = wideAspectRatio; - services.add(captionTrack); - } - return new CaptionServiceDescriptor(services); - } - - private static ContentAdvisoryDescriptor parseContentAdvisory(byte[] data, int pos, int limit) { - // For details of the structure for content advisory descriptor, see A/65 Table 6.27. - if (limit <= pos + 2) { - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - int count = data[pos + 2] & 0x3f; - pos += 3; - List ratingRegions = new ArrayList<>(); - for (int i = 0; i < count; ++i) { - if (limit <= pos + 1) { - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - List indices = new ArrayList<>(); - int ratingRegion = data[pos] & 0xff; - int dimensionCount = data[pos + 1] & 0xff; - pos += 2; - int previousDimension = -1; - for (int j = 0; j < dimensionCount; ++j) { - if (limit <= pos + 1) { - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - int dimensionIndex = data[pos] & 0xff; - int ratingValue = data[pos + 1] & 0x0f; - if (dimensionIndex <= previousDimension) { - // According to Table 6.27 of ATSC A65, - // the indices shall be in increasing order. - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - previousDimension = dimensionIndex; - pos += 2; - indices.add(new RegionalRating(dimensionIndex, ratingValue)); - } - if (limit <= pos) { - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - int ratingDescriptionLength = data[pos] & 0xff; - ++pos; - if (limit < pos + ratingDescriptionLength) { - Log.e(TAG, "Broken ContentAdvisory"); - return null; - } - String ratingDescription = extractText(data, pos); - pos += ratingDescriptionLength; - ratingRegions.add(new RatingRegion(ratingRegion, ratingDescription, indices)); - } - return new ContentAdvisoryDescriptor(ratingRegions); - } - - private static ExtendedChannelNameDescriptor parseLongChannelName( - byte[] data, int pos, int limit) { - if (limit <= pos + 2) { - Log.e(TAG, "Broken ExtendedChannelName."); - return null; - } - pos += 2; - String text = extractText(data, pos); - if (text == null) { - Log.e(TAG, "Broken ExtendedChannelName."); - return null; - } - return new ExtendedChannelNameDescriptor(text); - } - - private static GenreDescriptor parseGenre(byte[] data, int pos, int limit) { - pos += 2; - int attributeCount = data[pos] & 0x1f; - if (limit <= pos + attributeCount) { - Log.e(TAG, "Broken Genre."); - return null; - } - HashSet broadcastGenreSet = new HashSet<>(); - HashSet canonicalGenreSet = new HashSet<>(); - for (int i = 0; i < attributeCount; ++i) { - ++pos; - int genreCode = data[pos] & 0xff; - if (genreCode < BROADCAST_GENRES_TABLE.length) { - String broadcastGenre = BROADCAST_GENRES_TABLE[genreCode]; - if (broadcastGenre != null && !broadcastGenreSet.contains(broadcastGenre)) { - broadcastGenreSet.add(broadcastGenre); - } - } - if (genreCode < CANONICAL_GENRES_TABLE.length) { - String canonicalGenre = CANONICAL_GENRES_TABLE[genreCode]; - if (canonicalGenre != null && !canonicalGenreSet.contains(canonicalGenre)) { - canonicalGenreSet.add(canonicalGenre); - } - } - } - return new GenreDescriptor( - broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]), - canonicalGenreSet.toArray(new String[canonicalGenreSet.size()])); - } - - private static TsDescriptor parseAc3AudioStream(byte[] data, int pos, int limit) { - // For details of the AC3 audio stream descriptor, see A/52 Table A4.1. - if (limit <= pos + 5) { - Log.e(TAG, "Broken AC3 audio stream descriptor."); - return null; - } - pos += 2; - byte sampleRateCode = (byte) ((data[pos] & 0xe0) >> 5); - byte bsid = (byte) (data[pos] & 0x1f); - ++pos; - byte bitRateCode = (byte) ((data[pos] & 0xfc) >> 2); - byte surroundMode = (byte) (data[pos] & 0x03); - ++pos; - byte bsmod = (byte) ((data[pos] & 0xe0) >> 5); - int numChannels = (data[pos] & 0x1e) >> 1; - boolean fullSvc = (data[pos] & 0x01) != 0; - ++pos; - byte langCod = data[pos]; - byte langCod2 = 0; - if (numChannels == 0) { - if (limit <= pos) { - Log.e(TAG, "Broken AC3 audio stream descriptor."); - return null; - } - ++pos; - langCod2 = data[pos]; - } - if (limit <= pos + 1) { - Log.e(TAG, "Broken AC3 audio stream descriptor."); - return null; - } - byte mainId = 0; - byte priority = 0; - byte asvcflags = 0; - ++pos; - if (bsmod < 2) { - mainId = (byte) ((data[pos] & 0xe0) >> 5); - priority = (byte) ((data[pos] & 0x18) >> 3); - if ((data[pos] & 0x07) != 0x07) { - Log.e(TAG, "Broken AC3 audio stream descriptor reserved failed"); - return null; - } - } else { - asvcflags = data[pos]; - } - - // See A/52B Table A3.6 num_channels. - int numEncodedChannels; - switch (numChannels) { - case 1: - case 8: - numEncodedChannels = 1; - break; - case 2: - case 9: - numEncodedChannels = 2; - break; - case 3: - case 4: - case 10: - numEncodedChannels = 3; - break; - case 5: - case 6: - case 11: - numEncodedChannels = 4; - break; - case 7: - case 12: - numEncodedChannels = 5; - break; - case 13: - numEncodedChannels = 6; - break; - default: - numEncodedChannels = 0; - break; - } - - if (limit <= pos + 1) { - Log.w(TAG, "Missing text and language fields on AC3 audio stream descriptor."); - return new Ac3AudioDescriptor( - sampleRateCode, - bsid, - bitRateCode, - surroundMode, - bsmod, - numEncodedChannels, - fullSvc, - langCod, - langCod2, - mainId, - priority, - asvcflags, - null, - null, - null); - } - ++pos; - int textLen = (data[pos] & 0xfe) >> 1; - boolean textCode = (data[pos] & 0x01) != 0; - ++pos; - String text = ""; - if (textLen > 0) { - if (limit < pos + textLen) { - Log.e(TAG, "Broken AC3 audio stream descriptor"); - return null; - } - if (textCode) { - text = new String(data, pos, textLen); - } else { - text = new String(data, pos, textLen, Charset.forName("UTF-16")); - } - pos += textLen; - } - String language = null; - String language2 = null; - if (pos < limit) { - // Many AC3 audio stream descriptors skip the language fields. - boolean languageFlag1 = (data[pos] & 0x80) != 0; - boolean languageFlag2 = (data[pos] & 0x40) != 0; - if ((data[pos] & 0x3f) != 0x3f) { - Log.e(TAG, "Broken AC3 audio stream descriptor"); - return null; - } - if (pos + (languageFlag1 ? 3 : 0) + (languageFlag2 ? 3 : 0) > limit) { - Log.e(TAG, "Broken AC3 audio stream descriptor"); - return null; - } - ++pos; - if (languageFlag1) { - language = new String(data, pos, 3); - pos += 3; - } - if (languageFlag2) { - language2 = new String(data, pos, 3); - } - } - - return new Ac3AudioDescriptor( - sampleRateCode, - bsid, - bitRateCode, - surroundMode, - bsmod, - numEncodedChannels, - fullSvc, - langCod, - langCod2, - mainId, - priority, - asvcflags, - text, - language, - language2); - } - - private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) { - // For details of DVB service descriptors, see DVB Document A038 Table 86. - if (limit < pos + 5) { - Log.e(TAG, "Broken service descriptor."); - return null; - } - pos += 2; - int serviceType = data[pos] & 0xff; - pos++; - int serviceProviderNameLength = data[pos] & 0xff; - pos++; - String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength); - pos += serviceProviderNameLength; - int serviceNameLength = data[pos] & 0xff; - pos++; - String serviceName = extractTextFromDvb(data, pos, serviceNameLength); - return new ServiceDescriptor(serviceType, serviceProviderName, serviceName); - } - - private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) { - // For details of DVB service descriptors, see DVB Document A038 Table 91. - if (limit < pos + 7) { - Log.e(TAG, "Broken short event descriptor."); - return null; - } - pos += 2; - String language = new String(data, pos, 3); - int eventNameLength = data[pos + 3] & 0xff; - pos += 4; - if (pos + eventNameLength > limit) { - Log.e(TAG, "Broken short event descriptor."); - return null; - } - String eventName = new String(data, pos, eventNameLength); - pos += eventNameLength; - int textLength = data[pos] & 0xff; - if (pos + textLength > limit) { - Log.e(TAG, "Broken short event descriptor."); - return null; - } - pos++; - String text = new String(data, pos, textLength); - return new ShortEventDescriptor(language, eventName, text); - } - - private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) { - // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to - // get content genre. - return null; - } - - private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) { - // For details of DVB service descriptors, see DVB Document A038 Table 81. - HashMap ratings = new HashMap<>(); - pos += 2; - while (pos + 4 <= limit) { - String countryCode = new String(data, pos, 3); - int rating = data[pos + 3] & 0xff; - pos += 4; - if (rating > 15) { - // Rating > 15 means that the ratings is defined by broadcaster. - continue; - } - ratings.put(countryCode, rating + 3); - } - return new ParentalRatingDescriptor(ratings); - } - - private static int getShortNameSize(byte[] data, int offset) { - for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) { - if (data[offset + i] == 0 && data[offset + i + 1] == 0) { - return i; - } - } - return MAX_SHORT_NAME_BYTES; - } - - private static String extractText(byte[] data, int pos) { - if (data.length < pos) { - return null; - } - int numStrings = data[pos] & 0xff; - pos++; - for (int i = 0; i < numStrings; ++i) { - if (data.length <= pos + 3) { - Log.e(TAG, "Broken text."); - return null; - } - int numSegments = data[pos + 3] & 0xff; - pos += 4; - for (int j = 0; j < numSegments; ++j) { - if (data.length <= pos + 2) { - Log.e(TAG, "Broken text."); - return null; - } - int compressionType = data[pos] & 0xff; - int mode = data[pos + 1] & 0xff; - int numBytes = data[pos + 2] & 0xff; - if (data.length < pos + 3 + numBytes) { - Log.e(TAG, "Broken text."); - return null; - } - byte[] bytes = Arrays.copyOfRange(data, pos + 3, pos + 3 + numBytes); - if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION) { - try { - switch (mode) { - case MODE_SELECTED_UNICODE_RANGE_1: - return new String(bytes, "ISO-8859-1"); - case MODE_SCSU: - return UnicodeDecompressor.decompress(bytes); - case MODE_UTF16: - return new String(bytes, "UTF-16"); - } - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Unsupported text format.", e); - } - } - pos += 3 + numBytes; - } - } - return null; - } - - private static String extractTextFromDvb(byte[] data, int pos, int length) { - // For details of DVB character set selection, see DVB Document A038 Annex A. - if (data.length < pos + length) { - return null; - } - try { - String charsetPrefix = "ISO-8859-"; - switch (data[0]) { - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x09: - case 0x0A: - case 0x0B: - String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4); - return new String(data, pos, length, charset); - case 0x10: - if (length < 3) { - Log.e(TAG, "Broken DVB text"); - return null; - } - int codeTable = data[pos + 2] & 0xff; - if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) { - return new String( - data, pos, length, charsetPrefix + String.valueOf(codeTable)); - } else { - return new String(data, pos, length, "ISO-8859-1"); - } - case 0x11: - case 0x14: - case 0x15: - return new String(data, pos, length, "UTF-16BE"); - case 0x12: - return new String(data, pos, length, "EUC-KR"); - case 0x13: - return new String(data, pos, length, "GB2312"); - default: - return new String(data, pos, length, "ISO-8859-1"); - } - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Unsupported text format.", e); - } - return new String(data, pos, length); - } - - private static boolean checkSanity(byte[] data) { - if (data.length <= 1) { - return false; - } - boolean hasCRC = (data[1] & 0x80) != 0; // section_syntax_indicator - if (hasCRC) { - int crc = 0xffffffff; - for (byte b : data) { - int index = ((crc >> 24) ^ (b & 0xff)) & 0xff; - crc = CRC_TABLE[index] ^ (crc << 8); - } - if (crc != 0) { - return false; - } - } - return true; - } -} diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java deleted file mode 100644 index fbedc2c3..00000000 --- a/src/com/android/tv/tuner/ts/TsParser.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.ts; - -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import com.android.tv.tuner.data.PsiData.PatItem; -import com.android.tv.tuner.data.PsiData.PmtItem; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.PsipData.EttItem; -import com.android.tv.tuner.data.PsipData.MgtItem; -import com.android.tv.tuner.data.PsipData.SdtItem; -import com.android.tv.tuner.data.PsipData.VctItem; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.ts.SectionParser.OutputListener; -import com.android.tv.tuner.util.ByteArrayBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; - -/** Parses MPEG-2 TS packets. */ -public class TsParser { - private static final String TAG = "TsParser"; - private static final boolean DEBUG = false; - - public static final int ATSC_SI_BASE_PID = 0x1ffb; - public static final int PAT_PID = 0x0000; - public static final int DVB_SDT_PID = 0x0011; - public static final int DVB_EIT_PID = 0x0012; - private static final int TS_PACKET_START_CODE = 0x47; - private static final int TS_PACKET_TEI_MASK = 0x80; - private static final int TS_PACKET_SIZE = 188; - - /* - * Using a SparseArray removes the need to auto box the int key for mStreamMap - * in feedTdPacket which is called 100 times a second. This greatly reduces the - * number of objects created and the frequency of garbage collection. - * Other maps might be suitable for a SparseArray, but the performance - * trade offs must be considered carefully. - * mStreamMap is the only one called at such a high rate. - */ - private final SparseArray mStreamMap = new SparseArray<>(); - private final Map mSourceIdToVctItemMap = new HashMap<>(); - private final Map mSourceIdToVctItemDescriptionMap = new HashMap<>(); - private final Map mProgramNumberToVctItemMap = new HashMap<>(); - private final Map> mProgramNumberToPMTMap = new HashMap<>(); - private final Map> mSourceIdToEitMap = new HashMap<>(); - private final Map mProgramNumberToSdtItemMap = new HashMap<>(); - private final Map> mEitMap = new HashMap<>(); - private final Map> mETTMap = new HashMap<>(); - private final TreeSet mEITPids = new TreeSet<>(); - private final TreeSet mETTPids = new TreeSet<>(); - private final SparseBooleanArray mProgramNumberHandledStatus = new SparseBooleanArray(); - private final SparseBooleanArray mVctItemHandledStatus = new SparseBooleanArray(); - private final TsOutputListener mListener; - private final boolean mIsDvbSignal; - - private int mVctItemCount; - private int mHandledVctItemCount; - private int mVctSectionParsedCount; - private boolean[] mVctSectionParsed; - - public interface TsOutputListener { - void onPatDetected(List items); - - void onEitPidDetected(int pid); - - void onVctItemParsed(VctItem channel, List pmtItems); - - void onEitItemParsed(VctItem channel, List items); - - void onEttPidDetected(int pid); - - void onAllVctItemsParsed(); - - void onSdtItemParsed(SdtItem channel, List pmtItems); - } - - private abstract class Stream { - private static final int INVALID_CONTINUITY_COUNTER = -1; - private static final int NUM_CONTINUITY_COUNTER = 16; - - protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER; - protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE); - - public void feedData(byte[] data, int continuityCounter, boolean startIndicator) { - if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) { - mPacket.setLength(0); - } - mContinuityCounter = continuityCounter; - handleData(data, startIndicator); - } - - protected abstract void handleData(byte[] data, boolean startIndicator); - - protected abstract void resetDataVersions(); - } - - private class SectionStream extends Stream { - private final SectionParser mSectionParser; - private final int mPid; - - public SectionStream(int pid) { - mPid = pid; - mSectionParser = new SectionParser(mSectionListener); - } - - @Override - protected void handleData(byte[] data, boolean startIndicator) { - int startPos = 0; - if (mPacket.length() == 0) { - if (startIndicator) { - startPos = (data[0] & 0xff) + 1; - } else { - // Don't know where the section starts yet. Wait until start indicator is on. - return; - } - } else { - if (startIndicator) { - startPos = 1; - } - } - - // When a broken packet is encountered, parsing will stop and return right away. - if (startPos >= data.length) { - mPacket.setLength(0); - return; - } - mPacket.append(data, startPos, data.length - startPos); - mSectionParser.parseSections(mPacket); - } - - @Override - protected void resetDataVersions() { - mSectionParser.resetVersionNumbers(); - } - - private final OutputListener mSectionListener = - new OutputListener() { - @Override - public void onPatParsed(List items) { - for (PatItem i : items) { - startListening(i.getPmtPid()); - } - if (mListener != null) { - mListener.onPatDetected(items); - } - } - - @Override - public void onPmtParsed(int programNumber, List items) { - mProgramNumberToPMTMap.put(programNumber, items); - if (DEBUG) { - Log.d( - TAG, - "onPMTParsed, programNo " - + programNumber - + " handledStatus is " - + mProgramNumberHandledStatus.get( - programNumber, false)); - } - int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber); - if (statusIndex < 0) { - mProgramNumberHandledStatus.put(programNumber, false); - } - if (!mProgramNumberHandledStatus.get(programNumber)) { - VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber); - if (vctItem != null) { - // When PMT is parsed later than VCT. - mProgramNumberHandledStatus.put(programNumber, true); - handleVctItem(vctItem, items); - mHandledVctItemCount++; - if (mHandledVctItemCount >= mVctItemCount - && mVctSectionParsedCount >= mVctSectionParsed.length - && mListener != null) { - mListener.onAllVctItemsParsed(); - } - } - SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber); - if (sdtItem != null) { - // When PMT is parsed later than SDT. - mProgramNumberHandledStatus.put(programNumber, true); - handleSdtItem(sdtItem, items); - } - } - } - - @Override - public void onMgtParsed(List items) { - for (MgtItem i : items) { - if (mStreamMap.get(i.getTableTypePid()) != null) { - continue; - } - if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START - && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) { - startListening(i.getTableTypePid()); - mEITPids.add(i.getTableTypePid()); - if (mListener != null) { - mListener.onEitPidDetected(i.getTableTypePid()); - } - } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT - || (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START - && i.getTableType() - <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) { - startListening(i.getTableTypePid()); - mETTPids.add(i.getTableTypePid()); - if (mListener != null) { - mListener.onEttPidDetected(i.getTableTypePid()); - } - } - } - } - - @Override - public void onVctParsed( - List items, int sectionNumber, int lastSectionNumber) { - if (mVctSectionParsed == null) { - mVctSectionParsed = new boolean[lastSectionNumber + 1]; - } else if (mVctSectionParsed[sectionNumber]) { - // The current section was handled before. - if (DEBUG) { - Log.d(TAG, "Duplicate VCT section found."); - } - return; - } - mVctSectionParsed[sectionNumber] = true; - mVctSectionParsedCount++; - mVctItemCount += items.size(); - for (VctItem i : items) { - if (DEBUG) Log.d(TAG, "onVCTParsed " + i); - if (i.getSourceId() != 0) { - mSourceIdToVctItemMap.put(i.getSourceId(), i); - i.setDescription( - mSourceIdToVctItemDescriptionMap.get(i.getSourceId())); - } - int programNumber = i.getProgramNumber(); - mProgramNumberToVctItemMap.put(programNumber, i); - List pmtList = mProgramNumberToPMTMap.get(programNumber); - if (pmtList != null) { - mProgramNumberHandledStatus.put(programNumber, true); - handleVctItem(i, pmtList); - mHandledVctItemCount++; - if (mHandledVctItemCount >= mVctItemCount - && mVctSectionParsedCount >= mVctSectionParsed.length - && mListener != null) { - mListener.onAllVctItemsParsed(); - } - } else { - mProgramNumberHandledStatus.put(programNumber, false); - Log.i( - TAG, - "onVCTParsed, but PMT for programNo " - + programNumber - + " is not found yet."); - } - } - } - - @Override - public void onEitParsed(int sourceId, List items) { - if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId); - EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); - mEitMap.put(entry, items); - handleEvents(sourceId); - } - - @Override - public void onEttParsed(int sourceId, List descriptions) { - if (DEBUG) { - Log.d( - TAG, - String.format( - "onETTParsed sourceId: %d, descriptions.size(): %d", - sourceId, descriptions.size())); - } - for (EttItem item : descriptions) { - if (item.eventId == 0) { - // Channel description - mSourceIdToVctItemDescriptionMap.put(sourceId, item.text); - VctItem vctItem = mSourceIdToVctItemMap.get(sourceId); - if (vctItem != null) { - vctItem.setDescription(item.text); - List pmtItems = - mProgramNumberToPMTMap.get(vctItem.getProgramNumber()); - if (pmtItems != null) { - handleVctItem(vctItem, pmtItems); - } - } - } - } - - // Event Information description - EventSourceEntry entry = new EventSourceEntry(mPid, sourceId); - mETTMap.put(entry, descriptions); - handleEvents(sourceId); - } - - @Override - public void onSdtParsed(List sdtItems) { - for (SdtItem sdtItem : sdtItems) { - if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem); - int programNumber = sdtItem.getServiceId(); - mProgramNumberToSdtItemMap.put(programNumber, sdtItem); - List pmtList = mProgramNumberToPMTMap.get(programNumber); - if (pmtList != null) { - mProgramNumberHandledStatus.put(programNumber, true); - handleSdtItem(sdtItem, pmtList); - } else { - mProgramNumberHandledStatus.put(programNumber, false); - Log.i( - TAG, - "onSdtParsed, but PMT for programNo " - + programNumber - + " is not found yet."); - } - } - } - }; - } - - private static class EventSourceEntry { - public final int pid; - public final int sourceId; - - public EventSourceEntry(int pid, int sourceId) { - this.pid = pid; - this.sourceId = sourceId; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + pid; - result = 31 * result + sourceId; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof EventSourceEntry) { - EventSourceEntry another = (EventSourceEntry) obj; - return pid == another.pid && sourceId == another.sourceId; - } - return false; - } - } - - private void handleVctItem(VctItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "handleVctItem " + channel); - } - if (mListener != null) { - mListener.onVctItemParsed(channel, pmtItems); - } - int sourceId = channel.getSourceId(); - int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId); - if (statusIndex < 0) { - mVctItemHandledStatus.put(sourceId, false); - return; - } - if (!mVctItemHandledStatus.valueAt(statusIndex)) { - List eitItems = mSourceIdToEitMap.get(sourceId); - if (eitItems != null) { - // When VCT is parsed later than EIT. - mVctItemHandledStatus.put(sourceId, true); - handleEitItems(channel, eitItems); - } - } - } - - private void handleEitItems(VctItem channel, List items) { - if (mListener != null) { - mListener.onEitItemParsed(channel, items); - } - } - - private void handleSdtItem(SdtItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "handleSdtItem " + channel); - } - if (mListener != null) { - mListener.onSdtItemParsed(channel, pmtItems); - } - } - - private void handleEvents(int sourceId) { - Map itemSet = new HashMap<>(); - for (int pid : mEITPids) { - List eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId)); - if (eitItems != null) { - for (EitItem item : eitItems) { - item.setDescription(null); - itemSet.put(item.getEventId(), item); - } - } - } - for (int pid : mETTPids) { - List ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId)); - if (ettItems != null) { - for (EttItem ettItem : ettItems) { - if (ettItem.eventId != 0) { - EitItem item = itemSet.get(ettItem.eventId); - if (item != null) { - item.setDescription(ettItem.text); - } - } - } - } - } - List items = new ArrayList<>(itemSet.values()); - mSourceIdToEitMap.put(sourceId, items); - VctItem channel = mSourceIdToVctItemMap.get(sourceId); - if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) { - mVctItemHandledStatus.put(sourceId, true); - handleEitItems(channel, items); - } else { - mVctItemHandledStatus.put(sourceId, false); - if (!mIsDvbSignal) { - // Log only when zapping to non-DVB channels, since there is not VCT in DVB signal. - Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet."); - } - } - } - - /** - * Creates MPEG-2 TS parser. - * - * @param listener TsOutputListener - */ - public TsParser(TsOutputListener listener, boolean isDvbSignal) { - startListening(PAT_PID); - startListening(ATSC_SI_BASE_PID); - mIsDvbSignal = isDvbSignal; - if (isDvbSignal) { - startListening(DVB_EIT_PID); - startListening(DVB_SDT_PID); - } - mListener = listener; - } - - private void startListening(int pid) { - mStreamMap.put(pid, new SectionStream(pid)); - } - - private boolean feedTSPacket(byte[] tsData, int pos) { - if (tsData.length < pos + TS_PACKET_SIZE) { - if (DEBUG) Log.d(TAG, "Data should include a single TS packet."); - return false; - } - if (tsData[pos] != TS_PACKET_START_CODE) { - if (DEBUG) Log.d(TAG, "Invalid ts packet."); - return false; - } - if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) { - if (DEBUG) Log.d(TAG, "Erroneous ts packet."); - return false; - } - - // For details for the structure of TS packet, see H.222.0 Table 2-2. - int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff); - boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0; - boolean hasPayload = (tsData[pos + 3] & 0x10) != 0; - boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0; - int continuityCounter = tsData[pos + 3] & 0x0f; - Stream stream = mStreamMap.get(pid); - int payloadPos = pos; - payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4; - if (!hasPayload || stream == null) { - // We are not interested in this packet. - return false; - } - if (payloadPos >= pos + TS_PACKET_SIZE) { - if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet."); - return false; - } - stream.feedData( - Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE), - continuityCounter, - payloadStartIndicator); - return true; - } - - /** - * Feeds MPEG-2 TS data to parse. - * - * @param tsData buffer for ATSC TS stream - * @param pos the offset where buffer starts - * @param length The length of available data - */ - public void feedTSData(byte[] tsData, int pos, int length) { - for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) { - feedTSPacket(tsData, pos); - } - } - - /** - * Retrieves the channel information regardless of being well-formed. - * - * @return {@link List} of {@link TunerChannel} - */ - public List getMalFormedChannels() { - List incompleteChannels = new ArrayList<>(); - for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) { - if (!mProgramNumberHandledStatus.valueAt(i)) { - int programNumber = mProgramNumberHandledStatus.keyAt(i); - List pmtList = mProgramNumberToPMTMap.get(programNumber); - if (pmtList != null) { - TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList); - incompleteChannels.add(tunerChannel); - } - } - } - return incompleteChannels; - } - - /** Reset the versions so that data with old version number can be handled. */ - public void resetDataVersions() { - for (int eitPid : mEITPids) { - Stream stream = mStreamMap.get(eitPid); - if (stream != null) { - stream.resetDataVersions(); - } - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java deleted file mode 100644 index 49fc0ca1..00000000 --- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java +++ /dev/null @@ -1,801 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.content.ComponentName; -import android.content.ContentProviderOperation; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.os.RemoteException; -import android.support.annotation.Nullable; -import android.text.format.DateUtils; -import android.util.Log; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.util.ConvertUtils; -import com.android.tv.util.PermissionUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -/** Manages the channel info and EPG data through {@link TvInputManager}. */ -public class ChannelDataManager implements Handler.Callback { - private static final String TAG = "ChannelDataManager"; - - private static final String[] ALL_PROGRAMS_SELECTION_ARGS = - new String[] { - TvContract.Programs._ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_BROADCAST_GENRE, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_VERSION_NUMBER - }; - private static final String[] CHANNEL_DATA_SELECTION_ARGS = - new String[] { - TvContract.Channels._ID, - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 - }; - - private static final int MSG_HANDLE_EVENTS = 1; - private static final int MSG_HANDLE_CHANNEL = 2; - private static final int MSG_BUILD_CHANNEL_MAP = 3; - private static final int MSG_REQUEST_PROGRAMS = 4; - private static final int MSG_CLEAR_CHANNELS = 6; - private static final int MSG_CHECK_VERSION = 7; - - // Throttle the batch operations to avoid TransactionTooLargeException. - private static final int BATCH_OPERATION_COUNT = 100; - // At most 16 days of program information is delivered through an EIT, - // according to the Chapter 6.4 of ATSC Recommended Practice A/69. - private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(16); - - /** - * A version number to enforce consistency of the channel data. - * - *

WARNING: If a change in the database serialization lead to breaking the backward - * compatibility, you must increment this value so that the old data are purged, and the user is - * requested to perform the auto-scan again to generate the new data set. - */ - private static final int VERSION = 6; - - private final Context mContext; - private final String mInputId; - private ProgramInfoListener mListener; - private ChannelScanListener mChannelScanListener; - private Handler mChannelScanHandler; - private final HandlerThread mHandlerThread; - private final Handler mHandler; - private final ConcurrentHashMap mTunerChannelMap; - private final ConcurrentSkipListMap mTunerChannelIdMap; - private final Uri mChannelsUri; - - // Used for scanning - private final ConcurrentSkipListSet mScannedChannels; - private final ConcurrentSkipListSet mPreviousScannedChannels; - private final AtomicBoolean mIsScanning; - private final AtomicBoolean scanCompleted = new AtomicBoolean(); - - public interface ProgramInfoListener { - - /** - * Invoked when a request for getting programs of a channel has been processed and passes - * the requested channel and the programs retrieved from database to the listener. - */ - void onRequestProgramsResponse(TunerChannel channel, List programs); - - /** - * Invoked when programs of a channel have been arrived and passes the arrived channel and - * programs to the listener. - */ - void onProgramsArrived(TunerChannel channel, List programs); - - /** - * Invoked when a channel has been arrived and passes the arrived channel to the listener. - */ - void onChannelArrived(TunerChannel channel); - - /** - * Invoked when the database schema has been changed and the old-format channels have been - * deleted. A receiver should notify to a user that re-scanning channels is necessary. - */ - void onRescanNeeded(); - } - - public interface ChannelScanListener { - /** Invoked when all pending channels have been handled. */ - void onChannelHandlingDone(); - } - - public ChannelDataManager(Context context) { - mContext = context; - mInputId = - TvContract.buildInputId( - new ComponentName( - mContext.getPackageName(), TunerTvInputService.class.getName())); - mChannelsUri = TvContract.buildChannelsUriForInput(mInputId); - mTunerChannelMap = new ConcurrentHashMap<>(); - mTunerChannelIdMap = new ConcurrentSkipListMap<>(); - mHandlerThread = new HandlerThread("TvInputServiceBackgroundThread"); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper(), this); - mIsScanning = new AtomicBoolean(); - mScannedChannels = new ConcurrentSkipListSet<>(); - mPreviousScannedChannels = new ConcurrentSkipListSet<>(); - } - - // Public methods - public void checkDataVersion(Context context) { - int version = TunerPreferences.getChannelDataVersion(context); - Log.d(TAG, "ChannelDataManager.VERSION=" + VERSION + " (current=" + version + ")"); - if (version == VERSION) { - // Everything is awesome. Return and continue. - return; - } - setCurrentVersion(context); - - if (version == TunerPreferences.CHANNEL_DATA_VERSION_NOT_SET) { - mHandler.sendEmptyMessage(MSG_CHECK_VERSION); - } else { - // The stored channel data seem outdated. Delete them all. - mHandler.sendEmptyMessage(MSG_CLEAR_CHANNELS); - } - } - - public void setCurrentVersion(Context context) { - TunerPreferences.setChannelDataVersion(context, VERSION); - } - - public void setListener(ProgramInfoListener listener) { - mListener = listener; - } - - public void setChannelScanListener(ChannelScanListener listener, Handler handler) { - mChannelScanListener = listener; - mChannelScanHandler = handler; - } - - public void release() { - mHandler.removeCallbacksAndMessages(null); - releaseSafely(); - } - - public void releaseSafely() { - mHandlerThread.quitSafely(); - mListener = null; - mChannelScanListener = null; - mChannelScanHandler = null; - } - - public TunerChannel getChannel(long channelId) { - TunerChannel channel = mTunerChannelMap.get(channelId); - if (channel != null) { - return channel; - } - mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP); - byte[] data = null; - try (Cursor cursor = - mContext.getContentResolver() - .query( - TvContract.buildChannelUri(channelId), - CHANNEL_DATA_SELECTION_ARGS, - null, - null, - null)) { - if (cursor != null && cursor.moveToFirst()) { - data = cursor.getBlob(1); - } - } - if (data == null) { - return null; - } - channel = TunerChannel.parseFrom(data); - if (channel == null) { - return null; - } - channel.setChannelId(channelId); - return channel; - } - - public void requestProgramsData(TunerChannel channel) { - mHandler.removeMessages(MSG_REQUEST_PROGRAMS); - mHandler.obtainMessage(MSG_REQUEST_PROGRAMS, channel).sendToTarget(); - } - - public void notifyEventDetected(TunerChannel channel, List items) { - mHandler.obtainMessage(MSG_HANDLE_EVENTS, new ChannelEvent(channel, items)).sendToTarget(); - } - - public void notifyChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { - if (mIsScanning.get()) { - // During scanning, channels should be handle first to improve scan time. - // EIT items can be handled in background after channel scan. - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel)); - } else { - mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel).sendToTarget(); - } - } - - // For scanning process - /** - * Invoked when starting a scanning mode. This method gets the previous channels to detect the - * obsolete channels after scanning and initializes the variables used for scanning. - */ - public void notifyScanStarted() { - mScannedChannels.clear(); - mPreviousScannedChannels.clear(); - try (Cursor cursor = - mContext.getContentResolver() - .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - long channelId = cursor.getLong(0); - byte[] data = cursor.getBlob(1); - TunerChannel channel = TunerChannel.parseFrom(data); - if (channel != null) { - channel.setChannelId(channelId); - mPreviousScannedChannels.add(channel); - } - } while (cursor.moveToNext()); - } - } - mIsScanning.set(true); - } - - /** - * Invoked when completing the scanning mode. Passes {@code MSG_SCAN_COMPLETED} to the handler - * in order to wait for finishing the remaining messages in the handler queue. Then removes the - * obsolete channels, which are previously scanned but are not in the current scanned result. - */ - public void notifyScanCompleted() { - // Send a dummy message to check whether there is any MSG_HANDLE_CHANNEL in queue - // and avoid race conditions. - scanCompleted.set(true); - mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, null)); - } - - public void scannedChannelHandlingCompleted() { - mIsScanning.set(false); - if (!mPreviousScannedChannels.isEmpty()) { - ArrayList ops = new ArrayList<>(); - for (TunerChannel channel : mPreviousScannedChannels) { - ops.add( - ContentProviderOperation.newDelete( - TvContract.buildChannelUri(channel.getChannelId())) - .build()); - } - try { - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Error deleting obsolete channels", e); - } - } - if (mChannelScanListener != null && mChannelScanHandler != null) { - mChannelScanHandler.post( - new Runnable() { - @Override - public void run() { - mChannelScanListener.onChannelHandlingDone(); - } - }); - } else { - Log.e(TAG, "Error. mChannelScanListener is null."); - } - } - - /** Returns the number of scanned channels in the scanning mode. */ - public int getScannedChannelCount() { - return mScannedChannels.size(); - } - - /** - * Removes all callbacks and messages in handler to avoid previous messages from last channel. - */ - public void removeAllCallbacksAndMessages() { - mHandler.removeCallbacksAndMessages(null); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_HANDLE_EVENTS: - { - ChannelEvent event = (ChannelEvent) msg.obj; - handleEvents(event.channel, event.eitItems); - return true; - } - case MSG_HANDLE_CHANNEL: - { - TunerChannel channel = (TunerChannel) msg.obj; - if (channel != null) { - handleChannel(channel); - } - if (scanCompleted.get() - && mIsScanning.get() - && !mHandler.hasMessages(MSG_HANDLE_CHANNEL)) { - // Complete the scan when all found channels have already been handled. - scannedChannelHandlingCompleted(); - } - return true; - } - case MSG_BUILD_CHANNEL_MAP: - { - mHandler.removeMessages(MSG_BUILD_CHANNEL_MAP); - buildChannelMap(); - return true; - } - case MSG_REQUEST_PROGRAMS: - { - if (mHandler.hasMessages(MSG_REQUEST_PROGRAMS)) { - return true; - } - TunerChannel channel = (TunerChannel) msg.obj; - if (mListener != null) { - mListener.onRequestProgramsResponse( - channel, getAllProgramsForChannel(channel)); - } - return true; - } - case MSG_CLEAR_CHANNELS: - { - clearChannels(); - return true; - } - case MSG_CHECK_VERSION: - { - checkVersion(); - return true; - } - } - return false; - } - - // Private methods - private void handleEvents(TunerChannel channel, List items) { - long channelId = getChannelId(channel); - if (channelId <= 0) { - return; - } - channel.setChannelId(channelId); - - // Schedule the audio and caption tracks of the current program and the programs being - // listed after the current one into TIS. - if (mListener != null) { - mListener.onProgramsArrived(channel, items); - } - - long currentTime = System.currentTimeMillis(); - List oldItems = - getAllProgramsForChannel( - channel, currentTime, currentTime + PROGRAM_QUERY_DURATION); - ArrayList ops = new ArrayList<>(); - // TODO: Find a right way to check if the programs are added outside. - boolean addedOutside = false; - for (EitItem item : oldItems) { - if (item.getEventId() == 0) { - // The event has been added outside TV tuner. - addedOutside = true; - break; - } - } - - // Inserting programs only when there is no overlapping with existing data assuming that: - // 1. external EPG is more accurate and rich and - // 2. the data we add here will be updated when we apply external EPG. - if (addedOutside) { - // oldItemCount cannot be 0 if addedOutside is true. - int oldItemCount = oldItems.size(); - for (EitItem newItem : items) { - if (newItem.getEndTimeUtcMillis() < currentTime) { - continue; - } - long newItemStartTime = newItem.getStartTimeUtcMillis(); - long newItemEndTime = newItem.getEndTimeUtcMillis(); - if (newItemStartTime < oldItems.get(0).getStartTimeUtcMillis()) { - // Start time smaller than that of any old items. Insert if no overlap. - if (newItemEndTime > oldItems.get(0).getStartTimeUtcMillis()) continue; - } else if (newItemStartTime - > oldItems.get(oldItemCount - 1).getStartTimeUtcMillis()) { - // Start time larger than that of any old item. Insert if no overlap. - if (newItemStartTime < oldItems.get(oldItemCount - 1).getEndTimeUtcMillis()) - continue; - } else { - int pos = - Collections.binarySearch( - oldItems, - newItem, - new Comparator() { - @Override - public int compare(EitItem lhs, EitItem rhs) { - return Long.compare( - lhs.getStartTimeUtcMillis(), - rhs.getStartTimeUtcMillis()); - } - }); - if (pos >= 0) { - // Same start Time found. Overlapped. - continue; - } - int insertPoint = -1 - pos; - // Check the two adjacent items. - if (newItemStartTime < oldItems.get(insertPoint - 1).getEndTimeUtcMillis() - || newItemEndTime > oldItems.get(insertPoint).getStartTimeUtcMillis()) { - continue; - } - } - ops.add( - buildContentProviderOperation( - ContentProviderOperation.newInsert(TvContract.Programs.CONTENT_URI), - newItem, - channel)); - if (ops.size() >= BATCH_OPERATION_COUNT) { - applyBatch(channel.getName(), ops); - ops.clear(); - } - } - applyBatch(channel.getName(), ops); - return; - } - - List outdatedOldItems = new ArrayList<>(); - Map newEitItemMap = new HashMap<>(); - for (EitItem item : items) { - newEitItemMap.put(item.getEventId(), item); - } - for (EitItem oldItem : oldItems) { - EitItem item = newEitItemMap.get(oldItem.getEventId()); - if (item == null) { - outdatedOldItems.add(oldItem); - continue; - } - - // Since program descriptions arrive at different time, the older one may have the - // correct program description while the newer one has no clue what value is. - if (oldItem.getDescription() != null - && item.getDescription() == null - && oldItem.getEventId() == item.getEventId() - && oldItem.getStartTime() == item.getStartTime() - && oldItem.getLengthInSecond() == item.getLengthInSecond() - && Objects.equals(oldItem.getContentRating(), item.getContentRating()) - && Objects.equals(oldItem.getBroadcastGenre(), item.getBroadcastGenre()) - && Objects.equals(oldItem.getCanonicalGenre(), item.getCanonicalGenre())) { - item.setDescription(oldItem.getDescription()); - } - if (item.compareTo(oldItem) != 0) { - ops.add( - buildContentProviderOperation( - ContentProviderOperation.newUpdate( - TvContract.buildProgramUri(oldItem.getProgramId())), - item, - null)); - if (ops.size() >= BATCH_OPERATION_COUNT) { - applyBatch(channel.getName(), ops); - ops.clear(); - } - } - newEitItemMap.remove(item.getEventId()); - } - for (EitItem unverifiedOldItems : outdatedOldItems) { - if (unverifiedOldItems.getStartTimeUtcMillis() > currentTime) { - // The given new EIT item list covers partial time span of EPG. Here, we delete old - // item only when it has an overlapping with the new EIT item list. - long startTime = unverifiedOldItems.getStartTimeUtcMillis(); - long endTime = unverifiedOldItems.getEndTimeUtcMillis(); - for (EitItem item : newEitItemMap.values()) { - long newItemStartTime = item.getStartTimeUtcMillis(); - long newItemEndTime = item.getEndTimeUtcMillis(); - if ((startTime >= newItemStartTime && startTime < newItemEndTime) - || (endTime > newItemStartTime && endTime <= newItemEndTime)) { - ops.add( - ContentProviderOperation.newDelete( - TvContract.buildProgramUri( - unverifiedOldItems.getProgramId())) - .build()); - if (ops.size() >= BATCH_OPERATION_COUNT) { - applyBatch(channel.getName(), ops); - ops.clear(); - } - break; - } - } - } - } - for (EitItem item : newEitItemMap.values()) { - if (item.getEndTimeUtcMillis() < currentTime) { - continue; - } - ops.add( - buildContentProviderOperation( - ContentProviderOperation.newInsert(TvContract.Programs.CONTENT_URI), - item, - channel)); - if (ops.size() >= BATCH_OPERATION_COUNT) { - applyBatch(channel.getName(), ops); - ops.clear(); - } - } - - applyBatch(channel.getName(), ops); - } - - private ContentProviderOperation buildContentProviderOperation( - ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) { - if (channel != null) { - builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - builder.withValue( - TvContract.Programs.COLUMN_RECORDING_PROHIBITED, - channel.isRecordingProhibited() ? 1 : 0); - } - } - if (item != null) { - builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText()) - .withValue( - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - item.getStartTimeUtcMillis()) - .withValue( - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - item.getEndTimeUtcMillis()) - .withValue(TvContract.Programs.COLUMN_CONTENT_RATING, item.getContentRating()) - .withValue(TvContract.Programs.COLUMN_AUDIO_LANGUAGE, item.getAudioLanguage()) - .withValue(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, item.getDescription()) - .withValue(TvContract.Programs.COLUMN_VERSION_NUMBER, item.getEventId()); - } - return builder.build(); - } - - private void applyBatch(String channelName, ArrayList operations) { - try { - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, operations); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Error updating EPG " + channelName, e); - } - } - - private void handleChannel(TunerChannel channel) { - long channelId = getChannelId(channel); - ContentValues values = new ContentValues(); - values.put(TvContract.Channels.COLUMN_NETWORK_AFFILIATION, channel.getShortName()); - values.put(TvContract.Channels.COLUMN_SERVICE_TYPE, channel.getServiceTypeName()); - values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.getTsid()); - values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.getDisplayNumber()); - values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.getName()); - values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, channel.toByteArray()); - values.put(TvContract.Channels.COLUMN_DESCRIPTION, channel.getDescription()); - values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.getVideoFormat()); - values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, VERSION); - values.put( - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, - channel.isRecordingProhibited() ? 1 : 0); - - if (channelId <= 0) { - values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId); - values.put( - TvContract.Channels.COLUMN_TYPE, - "QAM256".equals(channel.getModulation()) - ? TvContract.Channels.TYPE_ATSC_C - : TvContract.Channels.TYPE_ATSC_T); - values.put(TvContract.Channels.COLUMN_SERVICE_ID, channel.getProgramNumber()); - - // ATSC doesn't have original_network_id - values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.getFrequency()); - - Uri channelUri = - mContext.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values); - channelId = ContentUris.parseId(channelUri); - } else { - mContext.getContentResolver() - .update(TvContract.buildChannelUri(channelId), values, null, null); - } - channel.setChannelId(channelId); - mTunerChannelMap.put(channelId, channel); - mTunerChannelIdMap.put(channel, channelId); - if (mIsScanning.get()) { - mScannedChannels.add(channel); - mPreviousScannedChannels.remove(channel); - } - if (mListener != null) { - mListener.onChannelArrived(channel); - } - } - - private void clearChannels() { - int count = mContext.getContentResolver().delete(mChannelsUri, null, null); - if (count > 0) { - // We have just deleted obsolete data. Now tell the user that he or she needs - // to perform the auto-scan again. - if (mListener != null) { - mListener.onRescanNeeded(); - } - } - } - - private void checkVersion() { - if (PermissionUtils.hasAccessAllEpg(mContext)) { - String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?"; - try (Cursor cursor = - mContext.getContentResolver() - .query( - mChannelsUri, - CHANNEL_DATA_SELECTION_ARGS, - selection, - new String[] {Integer.toString(VERSION)}, - null)) { - if (cursor != null && cursor.moveToFirst()) { - // The stored channel data seem outdated. Delete them all. - clearChannels(); - } - } - } else { - try (Cursor cursor = - mContext.getContentResolver() - .query( - mChannelsUri, - new String[] { - TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 - }, - null, - null, - null)) { - if (cursor != null) { - while (cursor.moveToNext()) { - int version = cursor.getInt(0); - if (version != VERSION) { - clearChannels(); - break; - } - } - } - } - } - } - - private long getChannelId(TunerChannel channel) { - Long channelId = mTunerChannelIdMap.get(channel); - if (channelId != null) { - return channelId; - } - mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP); - try (Cursor cursor = - mContext.getContentResolver() - .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - channelId = cursor.getLong(0); - byte[] providerData = cursor.getBlob(1); - TunerChannel tunerChannel = TunerChannel.parseFrom(providerData); - if (tunerChannel != null && tunerChannel.compareTo(channel) == 0) { - channel.setChannelId(channelId); - mTunerChannelIdMap.put(channel, channelId); - mTunerChannelMap.put(channelId, channel); - return channelId; - } - } while (cursor.moveToNext()); - } - } - return -1; - } - - private List getAllProgramsForChannel(TunerChannel channel) { - return getAllProgramsForChannel(channel, null, null); - } - - private List getAllProgramsForChannel( - TunerChannel channel, @Nullable Long startTimeMs, @Nullable Long endTimeMs) { - List items = new ArrayList<>(); - long channelId = channel.getChannelId(); - Uri programsUri = - (startTimeMs == null || endTimeMs == null) - ? TvContract.buildProgramsUriForChannel(channelId) - : TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs); - try (Cursor cursor = - mContext.getContentResolver() - .query(programsUri, ALL_PROGRAMS_SELECTION_ARGS, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - long id = cursor.getLong(0); - String titleText = cursor.getString(1); - long startTime = - ConvertUtils.convertUnixEpochToGPSTime( - cursor.getLong(2) / DateUtils.SECOND_IN_MILLIS); - long endTime = - ConvertUtils.convertUnixEpochToGPSTime( - cursor.getLong(3) / DateUtils.SECOND_IN_MILLIS); - int lengthInSecond = (int) (endTime - startTime); - String contentRating = cursor.getString(4); - String broadcastGenre = cursor.getString(5); - String canonicalGenre = cursor.getString(6); - String description = cursor.getString(7); - int eventId = cursor.getInt(8); - EitItem eitItem = - new EitItem( - id, - eventId, - titleText, - startTime, - lengthInSecond, - contentRating, - null, - null, - broadcastGenre, - canonicalGenre, - description); - items.add(eitItem); - } while (cursor.moveToNext()); - } - } - return items; - } - - private void buildChannelMap() { - ArrayList channels = new ArrayList<>(); - try (Cursor cursor = - mContext.getContentResolver() - .query(mChannelsUri, CHANNEL_DATA_SELECTION_ARGS, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - long channelId = cursor.getLong(0); - byte[] data = cursor.getBlob(1); - TunerChannel channel = TunerChannel.parseFrom(data); - if (channel != null) { - channel.setChannelId(channelId); - channels.add(channel); - } - } while (cursor.moveToNext()); - } - } - mTunerChannelMap.clear(); - mTunerChannelIdMap.clear(); - for (TunerChannel channel : channels) { - mTunerChannelMap.put(channel.getChannelId(), channel); - mTunerChannelIdMap.put(channel, channel.getChannelId()); - } - } - - private static class ChannelEvent { - public final TunerChannel channel; - public final List eitItems; - - public ChannelEvent(TunerChannel channel, List eitItems) { - this.channel = channel; - this.eitItems = eitItems; - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java deleted file mode 100644 index c529c6db..00000000 --- a/src/com/android/tv/tuner/tvinput/EventDetector.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.data.PsiData; -import com.android.tv.tuner.data.PsipData; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.ts.TsParser; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information. - */ -public class EventDetector { - private static final String TAG = "EventDetector"; - private static final boolean DEBUG = false; - public static final int ALL_PROGRAM_NUMBERS = -1; - - private final TunerHal mTunerHal; - - private TsParser mTsParser; - private final Set mPidSet = new HashSet<>(); - - // To prevent channel duplication - private final Set mVctProgramNumberSet = new HashSet<>(); - private final Set mSdtProgramNumberSet = new HashSet<>(); - private final SparseArray mChannelMap = new SparseArray<>(); - private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); - private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); - private final List mEventListeners = new ArrayList<>(); - private int mFrequency; - private String mModulation; - private int mProgramNumber = ALL_PROGRAM_NUMBERS; - - private final TsParser.TsOutputListener mTsOutputListener = - new TsParser.TsOutputListener() { - @Override - public void onPatDetected(List items) { - for (PsiData.PatItem i : items) { - if (mProgramNumber == ALL_PROGRAM_NUMBERS - || mProgramNumber == i.getProgramNo()) { - mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER); - } - } - } - - @Override - public void onEitPidDetected(int pid) { - startListening(pid); - } - - @Override - public void onEitItemParsed( - PsipData.VctItem channel, List items) { - TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); - if (DEBUG) { - Log.d( - TAG, - "onEitItemParsed tunerChannel:" - + tunerChannel - + " " - + channel.getProgramNumber()); - } - int channelSourceId = channel.getSourceId(); - - // Source id 0 is useful for cases where a cable operator wishes to define a - // channel for - // which no EPG data is currently available. - // We don't handle such a case. - if (channelSourceId == 0) { - return; - } - - // If at least a one caption track have been found in EIT items for the given - // channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); - for (PsipData.EitItem item : items) { - if (captionTracksFound) { - break; - } - List captionTracks = item.getCaptionTracks(); - if (captionTracks != null && !captionTracks.isEmpty()) { - captionTracksFound = true; - } - } - mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); - if (captionTracksFound) { - for (PsipData.EitItem item : items) { - item.setHasCaptionTrack(); - } - } - if (tunerChannel != null && !mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onEventDetected(tunerChannel, items); - } - } - } - - @Override - public void onEttPidDetected(int pid) { - startListening(pid); - } - - @Override - public void onAllVctItemsParsed() { - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelScanDone(); - } - } - } - - @Override - public void onVctItemParsed( - PsipData.VctItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onVctItemParsed VCT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of - // the given - // tuner channel. - TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PsiData.PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getProgramNumber(); - - // If at least a one caption track have been found in VCT items for the given - // channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = - mVctCaptionTracksFound.get(channelProgramNumber) - || !captionTracks.isEmpty(); - mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); - if (captionTracksFound) { - tunerChannel.setHasCaptionTrack(); - } - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - tunerChannel.setFrequency(mFrequency); - tunerChannel.setModulation(mModulation); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mVctProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mVctProgramNumberSet.add(channelProgramNumber); - } - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelDetected(tunerChannel, !found); - } - } - } - - @Override - public void onSdtItemParsed( - PsipData.SdtItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onSdtItemParsed SDT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of - // the given - // tuner channel. - TunerChannel tunerChannel = new TunerChannel(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PsiData.PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getServiceId(); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - tunerChannel.setFrequency(mFrequency); - tunerChannel.setModulation(mModulation); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mSdtProgramNumberSet.add(channelProgramNumber); - } - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelDetected(tunerChannel, !found); - } - } - } - }; - - /** Listener for detecting ATSC TV channels and receiving EPG data. */ - public interface EventListener { - - /** - * Fired when new information of an ATSC TV channel arrived. - * - * @param channel an ATSC TV channel - * @param channelArrivedAtFirstTime tells whether this channel arrived at first time - */ - void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime); - - /** - * Fired when new program events of an ATSC TV channel arrived. - * - * @param channel an ATSC TV channel - * @param items a list of EIT items that were received - */ - void onEventDetected(TunerChannel channel, List items); - - /** - * Fired when information of all detectable ATSC TV channels in current frequency arrived. - */ - void onChannelScanDone(); - } - - /** - * Creates a detector for ATSC TV channles and program information. - * - * @param usbTunerInteface {@link TunerHal} - */ - public EventDetector(TunerHal usbTunerInteface) { - mTunerHal = usbTunerInteface; - } - - private void reset() { - // TODO: Use TsParser.reset() - int deliverySystemType = mTunerHal.getDeliverySystemType(); - mTsParser = - new TsParser( - mTsOutputListener, - TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType())); - mPidSet.clear(); - mVctProgramNumberSet.clear(); - mSdtProgramNumberSet.clear(); - mVctCaptionTracksFound.clear(); - mEitCaptionTracksFound.clear(); - mChannelMap.clear(); - } - - /** - * Starts detecting channel and program information. - * - * @param frequency The frequency to listen to. - * @param modulation The modulation type. - * @param programNumber The program number if this is for handling tune request. For scanning - * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. - */ - public void startDetecting(int frequency, String modulation, int programNumber) { - reset(); - mFrequency = frequency; - mModulation = modulation; - mProgramNumber = programNumber; - } - - private void startListening(int pid) { - if (mPidSet.contains(pid)) { - return; - } - mPidSet.add(pid); - mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER); - } - - /** - * Feeds ATSC TS stream to detect channel and program information. - * - * @param data buffer for ATSC TS stream - * @param startOffset the offset where buffer starts - * @param length The length of available data - */ - public void feedTSStream(byte[] data, int startOffset, int length) { - if (mPidSet.isEmpty()) { - startListening(TsParser.ATSC_SI_BASE_PID); - } - if (mTsParser != null) { - mTsParser.feedTSData(data, startOffset, length); - } - } - - /** - * Retrieves the channel information regardless of being well-formed. - * - * @return {@link List} of {@link TunerChannel} - */ - public List getMalFormedChannels() { - return mTsParser.getMalFormedChannels(); - } - - /** - * Registers an EventListener. - * - * @param eventListener the listener to be registered - */ - public void registerListener(EventListener eventListener) { - if (mTsParser != null) { - // Resets the version numbers so that the new listener can receive the EIT items. - // Otherwise, each EIT session is handled only once unless there is a new version. - mTsParser.resetDataVersions(); - } - mEventListeners.add(eventListener); - } - - /** - * Unregisters an EventListener. - * - * @param eventListener the listener to be unregistered - */ - public void unregisterListener(EventListener eventListener) { - boolean removed = mEventListeners.remove(eventListener); - if (!removed && DEBUG) { - Log.d(TAG, "Cannot unregister a non-registered listener!"); - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java deleted file mode 100644 index f2ed72f1..00000000 --- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import com.android.tv.tuner.data.PsiData.PatItem; -import com.android.tv.tuner.data.PsiData.PmtItem; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.PsipData.SdtItem; -import com.android.tv.tuner.data.PsipData.VctItem; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.source.FileTsStreamer; -import com.android.tv.tuner.ts.TsParser; -import com.android.tv.tuner.tvinput.EventDetector.EventListener; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * PSIP event detector for a file source. - * - *

Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports various - * PSIP-related events via {@link TsParser.TsOutputListener}. - */ -public class FileSourceEventDetector { - private static final String TAG = "FileSourceEventDetector"; - private static final boolean DEBUG = true; - public static final int ALL_PROGRAM_NUMBERS = 0; - - private TsParser mTsParser; - private final Set mVctProgramNumberSet = new HashSet<>(); - private final Set mSdtProgramNumberSet = new HashSet<>(); - private final SparseArray mChannelMap = new SparseArray<>(); - private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray(); - private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray(); - private final EventListener mEventListener; - private final boolean mEnableDvbSignal; - private FileTsStreamer.StreamProvider mStreamProvider; - private int mProgramNumber = ALL_PROGRAM_NUMBERS; - - public FileSourceEventDetector(EventDetector.EventListener listener, boolean enableDvbSignal) { - mEventListener = listener; - mEnableDvbSignal = enableDvbSignal; - } - - /** - * Starts detecting channel and program information. - * - * @param provider MPEG-2 transport stream source. - * @param programNumber The program number if this is for handling tune request. For scanning - * purpose, supply {@link #ALL_PROGRAM_NUMBERS}. - */ - public void start(FileTsStreamer.StreamProvider provider, int programNumber) { - mStreamProvider = provider; - mProgramNumber = programNumber; - reset(); - } - - private void reset() { - mTsParser = new TsParser(mTsOutputListener, mEnableDvbSignal); // TODO: Use TsParser.reset() - mStreamProvider.clearPidFilter(); - mVctProgramNumberSet.clear(); - mSdtProgramNumberSet.clear(); - mVctCaptionTracksFound.clear(); - mEitCaptionTracksFound.clear(); - mChannelMap.clear(); - } - - public void feedTSStream(byte[] data, int startOffset, int length) { - if (mStreamProvider.isFilterEmpty()) { - startListening(TsParser.ATSC_SI_BASE_PID); - startListening(TsParser.PAT_PID); - } - if (mTsParser != null) { - mTsParser.feedTSData(data, startOffset, length); - } - } - - private void startListening(int pid) { - if (mStreamProvider.isInFilter(pid)) { - return; - } - mStreamProvider.addPidFilter(pid); - } - - private final TsParser.TsOutputListener mTsOutputListener = - new TsParser.TsOutputListener() { - @Override - public void onPatDetected(List items) { - for (PatItem i : items) { - if (mProgramNumber == ALL_PROGRAM_NUMBERS - || mProgramNumber == i.getProgramNo()) { - mStreamProvider.addPidFilter(i.getPmtPid()); - } - } - } - - @Override - public void onEitPidDetected(int pid) { - startListening(pid); - } - - @Override - public void onEitItemParsed(VctItem channel, List items) { - TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber()); - if (DEBUG) { - Log.d( - TAG, - "onEitItemParsed tunerChannel:" - + tunerChannel - + " " - + channel.getProgramNumber()); - } - int channelSourceId = channel.getSourceId(); - - // Source id 0 is useful for cases where a cable operator wishes to define a - // channel for - // which no EPG data is currently available. - // We don't handle such a case. - if (channelSourceId == 0) { - return; - } - - // If at least a one caption track have been found in EIT items for the given - // channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId); - for (EitItem item : items) { - if (captionTracksFound) { - break; - } - List captionTracks = item.getCaptionTracks(); - if (captionTracks != null && !captionTracks.isEmpty()) { - captionTracksFound = true; - } - } - mEitCaptionTracksFound.put(channelSourceId, captionTracksFound); - if (captionTracksFound) { - for (EitItem item : items) { - item.setHasCaptionTrack(); - } - } - if (tunerChannel != null && mEventListener != null) { - mEventListener.onEventDetected(tunerChannel, items); - } - } - - @Override - public void onEttPidDetected(int pid) { - startListening(pid); - } - - @Override - public void onAllVctItemsParsed() { - // do nothing. - } - - @Override - public void onVctItemParsed(VctItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onVctItemParsed VCT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of - // the given - // tuner channel. - TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getProgramNumber(); - - // If at least a one caption track have been found in VCT items for the given - // channel, - // we starts to interpret the zero tracks as a clearance of the caption tracks. - boolean captionTracksFound = - mVctCaptionTracksFound.get(channelProgramNumber) - || !captionTracks.isEmpty(); - mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound); - if (captionTracksFound) { - tunerChannel.setHasCaptionTrack(); - } - tunerChannel.setFilepath(mStreamProvider.getFilepath()); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mVctProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mVctProgramNumberSet.add(channelProgramNumber); - } - if (mEventListener != null) { - mEventListener.onChannelDetected(tunerChannel, !found); - } - } - - @Override - public void onSdtItemParsed(SdtItem channel, List pmtItems) { - if (DEBUG) { - Log.d(TAG, "onSdtItemParsed SDT " + channel); - Log.d(TAG, " PMT " + pmtItems); - } - - // Merges the audio and caption tracks located in PMT items into the tracks of - // the given - // tuner channel. - TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems); - List audioTracks = new ArrayList<>(); - List captionTracks = new ArrayList<>(); - for (PmtItem pmtItem : pmtItems) { - if (pmtItem.getAudioTracks() != null) { - audioTracks.addAll(pmtItem.getAudioTracks()); - } - if (pmtItem.getCaptionTracks() != null) { - captionTracks.addAll(pmtItem.getCaptionTracks()); - } - } - int channelProgramNumber = channel.getServiceId(); - tunerChannel.setFilepath(mStreamProvider.getFilepath()); - tunerChannel.setAudioTracks(audioTracks); - tunerChannel.setCaptionTracks(captionTracks); - mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel); - boolean found = mSdtProgramNumberSet.contains(channelProgramNumber); - if (!found) { - mSdtProgramNumberSet.add(channelProgramNumber); - } - if (mEventListener != null) { - mEventListener.onChannelDetected(tunerChannel, !found); - } - } - }; -} diff --git a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java b/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java deleted file mode 100644 index 1628bcfb..00000000 --- a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -/** The listener for buffer events occurred during playback. */ -public interface PlaybackBufferListener { - - /** - * Invoked when the start position of the buffer has been changed. - * - * @param startTimeMs the new start time of the buffer in millisecond - */ - void onBufferStartTimeChanged(long startTimeMs); - - /** - * Invoked when the state of the buffer has been changed. - * - * @param available whether the buffer is available or not - */ - void onBufferStateChanged(boolean available); - - /** Invoked when the disk speed is too slow to write the buffers. */ - void onDiskTooSlow(); -} diff --git a/src/com/android/tv/tuner/tvinput/TunerDebug.java b/src/com/android/tv/tuner/tvinput/TunerDebug.java deleted file mode 100644 index 1df0b5c3..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerDebug.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.os.SystemClock; -import android.util.Log; - -/** A class to maintain various debugging information. */ -public class TunerDebug { - private static final String TAG = "TunerDebug"; - public static final boolean ENABLED = false; - - private int mVideoFrameDrop; - private int mBytesInQueue; - - private long mAudioPositionUs; - private long mAudioPtsUs; - private long mVideoPtsUs; - - private long mLastAudioPositionUs; - private long mLastAudioPtsUs; - private long mLastVideoPtsUs; - private long mLastCheckTimestampMs; - - private long mAudioPositionUsRate; - private long mAudioPtsUsRate; - private long mVideoPtsUsRate; - - private TunerDebug() { - mVideoFrameDrop = 0; - mLastCheckTimestampMs = SystemClock.elapsedRealtime(); - } - - private static class LazyHolder { - private static final TunerDebug INSTANCE = new TunerDebug(); - } - - public static TunerDebug getInstance() { - return LazyHolder.INSTANCE; - } - - public static void notifyVideoFrameDrop(int count, long delta) { - // TODO: provide timestamp mismatch information using delta - TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mVideoFrameDrop += count; - } - - public static int getVideoFrameDrop() { - TunerDebug sTunerDebug = getInstance(); - int videoFrameDrop = sTunerDebug.mVideoFrameDrop; - if (videoFrameDrop > 0) { - Log.d(TAG, "Dropped video frame: " + videoFrameDrop); - } - sTunerDebug.mVideoFrameDrop = 0; - return videoFrameDrop; - } - - public static void setBytesInQueue(int bytesInQueue) { - TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mBytesInQueue = bytesInQueue; - } - - public static int getBytesInQueue() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mBytesInQueue; - } - - public static void setAudioPositionUs(long audioPositionUs) { - TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mAudioPositionUs = audioPositionUs; - } - - public static long getAudioPositionUs() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mAudioPositionUs; - } - - public static void setAudioPtsUs(long audioPtsUs) { - TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mAudioPtsUs = audioPtsUs; - } - - public static long getAudioPtsUs() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mAudioPtsUs; - } - - public static void setVideoPtsUs(long videoPtsUs) { - TunerDebug sTunerDebug = getInstance(); - sTunerDebug.mVideoPtsUs = videoPtsUs; - } - - public static long getVideoPtsUs() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mVideoPtsUs; - } - - public static void calculateDiff() { - TunerDebug sTunerDebug = getInstance(); - long currentTime = SystemClock.elapsedRealtime(); - long duration = currentTime - sTunerDebug.mLastCheckTimestampMs; - if (duration != 0) { - sTunerDebug.mAudioPositionUsRate = - (sTunerDebug.mAudioPositionUs - sTunerDebug.mLastAudioPositionUs) - * 1000 - / duration; - sTunerDebug.mAudioPtsUsRate = - (sTunerDebug.mAudioPtsUs - sTunerDebug.mLastAudioPtsUs) * 1000 / duration; - sTunerDebug.mVideoPtsUsRate = - (sTunerDebug.mVideoPtsUs - sTunerDebug.mLastVideoPtsUs) * 1000 / duration; - } - - sTunerDebug.mLastAudioPositionUs = sTunerDebug.mAudioPositionUs; - sTunerDebug.mLastAudioPtsUs = sTunerDebug.mAudioPtsUs; - sTunerDebug.mLastVideoPtsUs = sTunerDebug.mVideoPtsUs; - sTunerDebug.mLastCheckTimestampMs = currentTime; - } - - public static long getAudioPositionUsRate() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mAudioPositionUsRate; - } - - public static long getAudioPtsUsRate() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mAudioPtsUsRate; - } - - public static long getVideoPtsUsRate() { - TunerDebug sTunerDebug = getInstance(); - return sTunerDebug.mVideoPtsUsRate; - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java deleted file mode 100644 index a1f0c773..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.content.Context; -import android.media.tv.TvInputService; -import android.net.Uri; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.util.Log; - -/** Processes DVR recordings, and deletes the previously recorded contents. */ -public class TunerRecordingSession extends TvInputService.RecordingSession { - private static final String TAG = "TunerRecordingSession"; - private static final boolean DEBUG = false; - - private final TunerRecordingSessionWorker mSessionWorker; - - public TunerRecordingSession( - Context context, String inputId, ChannelDataManager channelDataManager) { - super(context); - mSessionWorker = - new TunerRecordingSessionWorker(context, inputId, channelDataManager, this); - } - - // RecordingSession - @MainThread - @Override - public void onTune(Uri channelUri) { - // TODO(dvr): support calling more than once, http://b/27171225 - if (DEBUG) { - Log.d(TAG, "Requesting recording session tune: " + channelUri); - } - mSessionWorker.tune(channelUri); - } - - @MainThread - @Override - public void onRelease() { - if (DEBUG) { - Log.d(TAG, "Requesting recording session release."); - } - mSessionWorker.release(); - } - - @MainThread - @Override - public void onStartRecording(@Nullable Uri programUri) { - if (DEBUG) { - Log.d(TAG, "Requesting start recording."); - } - mSessionWorker.startRecording(programUri); - } - - @MainThread - @Override - public void onStopRecording() { - if (DEBUG) { - Log.d(TAG, "Requesting stop recording."); - } - mSessionWorker.stopRecording(); - } - - // Called from TunerRecordingSessionImpl in a worker thread. - @WorkerThread - public void onTuned(Uri channelUri) { - if (DEBUG) { - Log.d(TAG, "Notifying recording session tuned."); - } - notifyTuned(channelUri); - } - - @WorkerThread - public void onRecordFinished(final Uri recordedProgramUri) { - if (DEBUG) { - Log.d(TAG, "Notifying record successfully finished."); - } - notifyRecordingStopped(recordedProgramUri); - } - - @WorkerThread - public void onError(int reason) { - Log.w(TAG, "Notifying recording error: " + reason); - notifyError(reason); - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java deleted file mode 100644 index 1bc4e295..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.media.tv.TvInputManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.support.annotation.IntDef; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.util.Log; -import android.util.Pair; -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.recording.RecordingCapability; -import com.android.tv.dvr.DvrStorageStatusManager; -import com.android.tv.dvr.data.RecordedProgram; -import com.android.tv.tuner.DvbDeviceAccessor; -import com.android.tv.tuner.data.PsipData; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor; -import com.android.tv.tuner.exoplayer.SampleExtractor; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; -import com.android.tv.tuner.source.TsDataSource; -import com.android.tv.tuner.source.TsDataSourceManager; -import com.android.tv.util.Utils; -import com.google.android.exoplayer.C; -import java.io.File; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Random; -import java.util.concurrent.TimeUnit; - -/** Implements a DVR feature. */ -public class TunerRecordingSessionWorker - implements PlaybackBufferListener, - EventDetector.EventListener, - SampleExtractor.OnCompletionListener, - Handler.Callback { - private static final String TAG = "TunerRecordingSessionW"; - private static final boolean DEBUG = false; - - private static final String SORT_BY_TIME = - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS - + ", " - + TvContract.Programs.COLUMN_CHANNEL_ID - + ", " - + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS; - private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); - private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4); - private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10); - private static final long PREPARE_RECORDER_POLL_MS = 50; - private static final int MSG_TUNE = 1; - private static final int MSG_START_RECORDING = 2; - private static final int MSG_PREPARE_RECODER = 3; - private static final int MSG_STOP_RECORDING = 4; - private static final int MSG_MONITOR_STORAGE_STATUS = 5; - private static final int MSG_RELEASE = 6; - private static final int MSG_UPDATE_CC_INFO = 7; - private final RecordingCapability mCapabilities; - - public RecordingCapability getCapabilities() { - return mCapabilities; - } - - @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING}) - @Retention(RetentionPolicy.SOURCE) - public @interface DvrSessionState {} - - private static final int STATE_IDLE = 1; - private static final int STATE_TUNING = 2; - private static final int STATE_TUNED = 3; - private static final int STATE_RECORDING = 4; - - private static final long CHANNEL_ID_NONE = -1; - private static final int MAX_TUNING_RETRY = 6; - - private final Context mContext; - private final ChannelDataManager mChannelDataManager; - private final DvrStorageStatusManager mDvrStorageStatusManager; - private final Handler mHandler; - private final TsDataSourceManager mSourceManager; - private final Random mRandom = new Random(); - - private TsDataSource mTunerSource; - private TunerChannel mChannel; - private File mStorageDir; - private long mRecordStartTime; - private long mRecordEndTime; - private boolean mRecorderRunning; - private SampleExtractor mRecorder; - private final TunerRecordingSession mSession; - @DvrSessionState private int mSessionState = STATE_IDLE; - private final String mInputId; - private Uri mProgramUri; - - private PsipData.EitItem mCurrenProgram; - private List mCaptionTracks; - private DvrStorageManager mDvrStorageManager; - - public TunerRecordingSessionWorker( - Context context, - String inputId, - ChannelDataManager dataManager, - TunerRecordingSession session) { - mRandom.setSeed(System.nanoTime()); - mContext = context; - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper(), this); - mDvrStorageStatusManager = - TvApplication.getSingletons(context).getDvrStorageStatusManager(); - mChannelDataManager = dataManager; - mChannelDataManager.checkDataVersion(context); - mSourceManager = TsDataSourceManager.createSourceManager(true); - mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId); - mInputId = inputId; - if (DEBUG) Log.d(TAG, mCapabilities.toString()); - mSession = session; - } - - // PlaybackBufferListener - @Override - public void onBufferStartTimeChanged(long startTimeMs) {} - - @Override - public void onBufferStateChanged(boolean available) {} - - @Override - public void onDiskTooSlow() {} - - // EventDetector.EventListener - @Override - public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { - if (mChannel == null || mChannel.compareTo(channel) != 0) { - return; - } - mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); - } - - @Override - public void onEventDetected(TunerChannel channel, List items) { - if (mChannel == null || mChannel.compareTo(channel) != 0) { - return; - } - mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget(); - mChannelDataManager.notifyEventDetected(channel, items); - } - - @Override - public void onChannelScanDone() { - // do nothing. - } - - // SampleExtractor.OnCompletionListener - @Override - public void onCompletion(boolean success, long lastExtractedPositionUs) { - onRecordingResult(success, lastExtractedPositionUs); - reset(); - } - - /** Tunes to {@code channelUri}. */ - @MainThread - public void tune(Uri channelUri) { - mHandler.removeCallbacksAndMessages(null); - mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); - } - - /** Starts recording. */ - @MainThread - public void startRecording(@Nullable Uri programUri) { - mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget(); - } - - /** Stops recording. */ - @MainThread - public void stopRecording() { - mHandler.sendEmptyMessage(MSG_STOP_RECORDING); - } - - /** Releases all resources. */ - @MainThread - public void release() { - mHandler.removeCallbacksAndMessages(null); - mHandler.sendEmptyMessage(MSG_RELEASE); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_TUNE: - { - Uri channelUri = (Uri) msg.obj; - int retryCount = msg.arg1; - if (DEBUG) Log.d(TAG, "Tune to " + channelUri); - if (doTune(channelUri)) { - if (mSessionState == STATE_TUNED) { - mSession.onTuned(channelUri); - } else { - Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); - if (retryCount < MAX_TUNING_RETRY) { - Message tuneMsg = - mHandler.obtainMessage( - MSG_TUNE, retryCount + 1, 0, channelUri); - mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS); - } else { - mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); - reset(); - } - } - } - return true; - } - case MSG_START_RECORDING: - { - if (DEBUG) Log.d(TAG, "Start recording"); - if (!doStartRecording((Uri) msg.obj)) { - reset(); - } - return true; - } - case MSG_PREPARE_RECODER: - { - if (DEBUG) Log.d(TAG, "Preparing recorder"); - if (!mRecorderRunning) { - return true; - } - try { - if (!mRecorder.prepare()) { - mHandler.sendEmptyMessageDelayed( - MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS); - } - } catch (IOException e) { - Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor"); - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - reset(); - } - return true; - } - case MSG_STOP_RECORDING: - { - if (DEBUG) Log.d(TAG, "Stop recording"); - if (mSessionState != STATE_RECORDING) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - reset(); - return true; - } - if (mRecorderRunning) { - stopRecorder(); - } - return true; - } - case MSG_MONITOR_STORAGE_STATUS: - { - if (mSessionState != STATE_RECORDING) { - return true; - } - if (!mDvrStorageStatusManager.isStorageSufficient()) { - if (mRecorderRunning) { - stopRecorder(); - } - new DeleteRecordingTask().execute(mStorageDir); - mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - reset(); - } else { - mHandler.sendEmptyMessageDelayed( - MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); - } - return true; - } - case MSG_RELEASE: - { - // Since release was requested, current recording will be cancelled - // without notification. - reset(); - mSourceManager.release(); - mHandler.removeCallbacksAndMessages(null); - mHandler.getLooper().quitSafely(); - return true; - } - case MSG_UPDATE_CC_INFO: - { - Pair> pair = - (Pair>) msg.obj; - updateCaptionTracks(pair.first, pair.second); - return true; - } - } - return false; - } - - @Nullable - private TunerChannel getChannel(Uri channelUri) { - if (channelUri == null) { - return null; - } - long channelId; - try { - channelId = ContentUris.parseId(channelUri); - } catch (UnsupportedOperationException | NumberFormatException e) { - channelId = CHANNEL_ID_NONE; - } - return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId); - } - - private String getStorageKey() { - long prefix = System.currentTimeMillis(); - int suffix = mRandom.nextInt(); - return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix); - } - - private void reset() { - if (mRecorder != null) { - mRecorder.release(); - mRecorder = null; - } - if (mTunerSource != null) { - mSourceManager.releaseDataSource(mTunerSource); - mTunerSource = null; - } - mDvrStorageManager = null; - mSessionState = STATE_IDLE; - mRecorderRunning = false; - } - - private boolean doTune(Uri channelUri) { - if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.e(TAG, "Tuning was requested from wrong status."); - return false; - } - mChannel = getChannel(channelUri); - if (mChannel == null) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel); - return false; - } else if (mChannel.isRecordingProhibited()) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel); - return false; - } - if (!mDvrStorageStatusManager.isStorageSufficient()) { - mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - Log.w(TAG, "Tuning failed due to insufficient storage."); - return false; - } - mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this); - if (mTunerSource == null) { - // Retry tuning in this case. - mSessionState = STATE_TUNING; - return true; - } - mSessionState = STATE_TUNED; - return true; - } - - private boolean doStartRecording(@Nullable Uri programUri) { - if (mSessionState != STATE_TUNED) { - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.e(TAG, "Recording session status abnormal"); - return false; - } - mStorageDir = - mDvrStorageStatusManager.isStorageSufficient() - ? new File( - mDvrStorageStatusManager.getRecordingRootDataDirectory(), - getStorageKey()) - : null; - if (mStorageDir == null) { - mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); - Log.w(TAG, "Failed to start recording due to insufficient storage."); - return false; - } - // Since tuning might be happened a while ago, shifts the start position of tuned source. - mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); - mRecordStartTime = System.currentTimeMillis(); - mDvrStorageManager = new DvrStorageManager(mStorageDir, true); - mRecorder = - new ExoPlayerSampleExtractor( - Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true); - mRecorder.setOnCompletionListener(this, mHandler); - mProgramUri = programUri; - mSessionState = STATE_RECORDING; - mRecorderRunning = true; - mHandler.sendEmptyMessage(MSG_PREPARE_RECODER); - mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); - mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS); - return true; - } - - private void stopRecorder() { - // Do not change session status. - if (mRecorder != null) { - mRecorder.release(); - mRecordEndTime = System.currentTimeMillis(); - mRecorder = null; - } - mRecorderRunning = false; - mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS); - Log.i(TAG, "Recording stopped"); - } - - private void updateCaptionTracks(TunerChannel channel, List items) { - if (mChannel == null - || channel == null - || mChannel.compareTo(channel) != 0 - || items == null - || items.isEmpty()) { - return; - } - PsipData.EitItem currentProgram = getCurrentProgram(items); - if (currentProgram == null - || !currentProgram.hasCaptionTrack() - || mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0) { - return; - } - mCurrenProgram = currentProgram; - mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks()); - if (DEBUG) { - Log.d( - TAG, - "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram); - } - } - - private PsipData.EitItem getCurrentProgram(List items) { - for (PsipData.EitItem item : items) { - if (mRecordStartTime >= item.getStartTimeUtcMillis() - && mRecordStartTime < item.getEndTimeUtcMillis()) { - return item; - } - } - return null; - } - - private static class Program { - private final long mChannelId; - private final String mTitle; - private String mSeriesId; - private final String mSeasonTitle; - private final String mEpisodeTitle; - private final String mSeasonNumber; - private final String mEpisodeNumber; - private final String mDescription; - private final String mPosterArtUri; - private final String mThumbnailUri; - private final String mCanonicalGenres; - private final String mContentRatings; - private final long mStartTimeUtcMillis; - private final long mEndTimeUtcMillis; - private final int mVideoWidth; - private final int mVideoHeight; - private final byte[] mInternalProviderData; - - private static final String[] PROJECTION = { - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.Programs.COLUMN_TITLE, - TvContract.Programs.COLUMN_SEASON_TITLE, - TvContract.Programs.COLUMN_EPISODE_TITLE, - TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, - TvContract.Programs.COLUMN_SHORT_DESCRIPTION, - TvContract.Programs.COLUMN_POSTER_ART_URI, - TvContract.Programs.COLUMN_THUMBNAIL_URI, - TvContract.Programs.COLUMN_CANONICAL_GENRE, - TvContract.Programs.COLUMN_CONTENT_RATING, - TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, - TvContract.Programs.COLUMN_VIDEO_WIDTH, - TvContract.Programs.COLUMN_VIDEO_HEIGHT, - TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA - }; - - public Program(Cursor cursor) { - int index = 0; - mChannelId = cursor.getLong(index++); - mTitle = cursor.getString(index++); - mSeasonTitle = cursor.getString(index++); - mEpisodeTitle = cursor.getString(index++); - mSeasonNumber = cursor.getString(index++); - mEpisodeNumber = cursor.getString(index++); - mDescription = cursor.getString(index++); - mPosterArtUri = cursor.getString(index++); - mThumbnailUri = cursor.getString(index++); - mCanonicalGenres = cursor.getString(index++); - mContentRatings = cursor.getString(index++); - mStartTimeUtcMillis = cursor.getLong(index++); - mEndTimeUtcMillis = cursor.getLong(index++); - mVideoWidth = cursor.getInt(index++); - mVideoHeight = cursor.getInt(index++); - mInternalProviderData = cursor.getBlob(index++); - SoftPreconditions.checkArgument(index == PROJECTION.length); - } - - public Program(long channelId) { - mChannelId = channelId; - mTitle = "Unknown"; - mSeasonTitle = ""; - mEpisodeTitle = ""; - mSeasonNumber = ""; - mEpisodeNumber = ""; - mDescription = "Unknown"; - mPosterArtUri = null; - mThumbnailUri = null; - mCanonicalGenres = null; - mContentRatings = null; - mStartTimeUtcMillis = 0; - mEndTimeUtcMillis = 0; - mVideoWidth = 0; - mVideoHeight = 0; - mInternalProviderData = null; - } - - public static Program onQuery(Cursor c) { - Program program = null; - if (c != null && c.moveToNext()) { - program = new Program(c); - } - return program; - } - - public ContentValues buildValues() { - ContentValues values = new ContentValues(); - int index = 0; - values.put(PROJECTION[index++], mChannelId); - values.put(PROJECTION[index++], mTitle); - values.put(PROJECTION[index++], mSeasonTitle); - values.put(PROJECTION[index++], mEpisodeTitle); - values.put(PROJECTION[index++], mSeasonNumber); - values.put(PROJECTION[index++], mEpisodeNumber); - values.put(PROJECTION[index++], mDescription); - values.put(PROJECTION[index++], mPosterArtUri); - values.put(PROJECTION[index++], mThumbnailUri); - values.put(PROJECTION[index++], mCanonicalGenres); - values.put(PROJECTION[index++], mContentRatings); - values.put(PROJECTION[index++], mStartTimeUtcMillis); - values.put(PROJECTION[index++], mEndTimeUtcMillis); - values.put(PROJECTION[index++], mVideoWidth); - values.put(PROJECTION[index++], mVideoHeight); - values.put(PROJECTION[index++], mInternalProviderData); - SoftPreconditions.checkArgument(index == PROJECTION.length); - return values; - } - } - - private Program getRecordedProgram() { - ContentResolver resolver = mContext.getContentResolver(); - Uri programUri = mProgramUri; - if (mProgramUri == null) { - long avg = mRecordStartTime / 2 + mRecordEndTime / 2; - programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg); - } - try (Cursor c = resolver.query(programUri, Program.PROJECTION, null, null, SORT_BY_TIME)) { - if (c != null) { - Program result = Program.onQuery(c); - if (DEBUG) { - Log.v(TAG, "Finished query for " + this); - } - return result; - } else { - if (c == null) { - Log.e(TAG, "Unknown query error for " + this); - } else { - if (DEBUG) Log.d(TAG, "Canceled query for " + this); - } - return null; - } - } - } - - private Uri insertRecordedProgram( - Program program, - long channelId, - String storageUri, - long totalBytes, - long startTime, - long endTime) { - // TODO: Set title even though program is null. - RecordedProgram recordedProgram = - RecordedProgram.builder() - .setInputId(mInputId) - .setChannelId(channelId) - .setDataUri(storageUri) - .setDurationMillis(endTime - startTime) - .setDataBytes(totalBytes) - // startTime and endTime could be overridden by program's start and end - // value. - .setStartTimeUtcMillis(startTime) - .setEndTimeUtcMillis(endTime) - .build(); - ContentValues values = RecordedProgram.toValues(recordedProgram); - if (program != null) { - values.putAll(program.buildValues()); - } - return mContext.getContentResolver() - .insert(TvContract.RecordedPrograms.CONTENT_URI, values); - } - - private void onRecordingResult(boolean success, long lastExtractedPositionUs) { - if (mSessionState != STATE_RECORDING) { - // Error notification is not needed. - Log.e(TAG, "Recording session status abnormal"); - return; - } - if (mRecorderRunning) { - // In case of recorder not being stopped, because of premature termination of recording. - stopRecorder(); - } - if (!success - && lastExtractedPositionUs - < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) { - new DeleteRecordingTask().execute(mStorageDir); - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.w(TAG, "Recording failed during recording"); - return; - } - Log.i(TAG, "recording finished " + (success ? "completely" : "partially")); - long recordEndTime = - (lastExtractedPositionUs == C.UNKNOWN_TIME_US) - ? System.currentTimeMillis() - : mRecordStartTime + lastExtractedPositionUs / 1000; - Uri uri = - insertRecordedProgram( - getRecordedProgram(), - mChannel.getChannelId(), - Uri.fromFile(mStorageDir).toString(), - 1024 * 1024, - mRecordStartTime, - recordEndTime); - if (uri == null) { - new DeleteRecordingTask().execute(mStorageDir); - mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); - Log.e(TAG, "Inserting a recording to DB failed"); - return; - } - mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks); - mSession.onRecordFinished(uri); - } - - private static class DeleteRecordingTask extends AsyncTask { - - @Override - public Void doInBackground(File... files) { - if (files == null || files.length == 0) { - return null; - } - for (File file : files) { - Utils.deleteDirOrFile(file); - } - return null; - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java deleted file mode 100644 index eec5da1f..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerSession.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.annotation.TargetApi; -import android.content.Context; -import android.media.PlaybackParams; -import android.media.tv.TvContentRating; -import android.media.tv.TvInputManager; -import android.media.tv.TvInputService; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.text.Html; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener; -import com.android.tv.tuner.cc.CaptionLayout; -import com.android.tv.tuner.cc.CaptionTrackRenderer; -import com.android.tv.tuner.data.Cea708Data.CaptionEvent; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.util.GlobalSettingsUtils; -import com.android.tv.tuner.util.StatusTextUtils; -import com.android.tv.tuner.util.SystemPropertiesProxy; -import com.google.android.exoplayer.audio.AudioCapabilities; - -/** - * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions are - * implemented in {@link TunerSessionWorker}. - */ -public class TunerSession extends TvInputService.Session - implements Handler.Callback, TunerPreferencesChangedListener { - private static final String TAG = "TunerSession"; - private static final boolean DEBUG = false; - private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug"; - - public static final int MSG_UI_SHOW_MESSAGE = 1; - public static final int MSG_UI_HIDE_MESSAGE = 2; - public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3; - public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4; - public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5; - public static final int MSG_UI_START_CAPTION_TRACK = 6; - public static final int MSG_UI_STOP_CAPTION_TRACK = 7; - public static final int MSG_UI_RESET_CAPTION_TRACK = 8; - public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9; - public static final int MSG_UI_SET_STATUS_TEXT = 10; - public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11; - - private final Context mContext; - private final Handler mUiHandler; - private final View mOverlayView; - private final TextView mMessageView; - private final TextView mStatusView; - private final TextView mAudioStatusView; - private final ViewGroup mMessageLayout; - private final CaptionTrackRenderer mCaptionTrackRenderer; - private final TunerSessionWorker mSessionWorker; - private boolean mReleased = false; - private boolean mPlayPaused; - private long mTuneStartTimestamp; - - public TunerSession(Context context, ChannelDataManager channelDataManager) { - super(context); - mContext = context; - mUiHandler = new Handler(this); - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null); - mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout); - mMessageLayout.setVisibility(View.INVISIBLE); - mMessageView = (TextView) mOverlayView.findViewById(R.id.message); - mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status); - boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false); - mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE); - mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status); - mAudioStatusView.setVisibility(View.INVISIBLE); - CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption); - mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout); - mSessionWorker = new TunerSessionWorker(context, channelDataManager, this); - TunerPreferences.setTunerPreferencesChangedListener(this); - } - - public boolean isReleased() { - return mReleased; - } - - @Override - public View onCreateOverlayView() { - return mOverlayView; - } - - @Override - public boolean onSelectTrack(int type, String trackId) { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId); - return false; - } - - @Override - public void onSetCaptionEnabled(boolean enabled) { - mSessionWorker.setCaptionEnabled(enabled); - } - - @Override - public void onSetStreamVolume(float volume) { - mSessionWorker.setStreamVolume(volume); - } - - @Override - public boolean onSetSurface(Surface surface) { - mSessionWorker.setSurface(surface); - return true; - } - - @Override - public void onTimeShiftPause() { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE); - mPlayPaused = true; - } - - @Override - public void onTimeShiftResume() { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME); - mPlayPaused = false; - } - - @Override - public void onTimeShiftSeekTo(long timeMs) { - if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000); - mSessionWorker.sendMessage( - TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO, mPlayPaused ? 1 : 0, 0, timeMs); - } - - @Override - public void onTimeShiftSetPlaybackParams(PlaybackParams params) { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params); - } - - @Override - public long onTimeShiftGetStartPosition() { - return mSessionWorker.getStartPosition(); - } - - @Override - public long onTimeShiftGetCurrentPosition() { - return mSessionWorker.getCurrentPosition(); - } - - @Override - public boolean onTune(Uri channelUri) { - if (DEBUG) { - Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : ""); - } - if (channelUri == null) { - Log.w(TAG, "onTune() is failed due to null channelUri."); - mSessionWorker.stopTune(); - return false; - } - mTuneStartTimestamp = SystemClock.elapsedRealtime(); - mSessionWorker.tune(channelUri); - mPlayPaused = false; - return true; - } - - @TargetApi(Build.VERSION_CODES.N) - @Override - public void onTimeShiftPlay(Uri recordUri) { - if (recordUri == null) { - Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri."); - mSessionWorker.stopTune(); - return; - } - mTuneStartTimestamp = SystemClock.elapsedRealtime(); - mSessionWorker.tune(recordUri); - mPlayPaused = false; - } - - @Override - public void onUnblockContent(TvContentRating unblockedRating) { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING, unblockedRating); - } - - @Override - public void onRelease() { - if (DEBUG) { - Log.d(TAG, "onRelease"); - } - mReleased = true; - mSessionWorker.release(); - mUiHandler.removeCallbacksAndMessages(null); - TunerPreferences.setTunerPreferencesChangedListener(null); - } - - /** Sets {@link AudioCapabilities}. */ - public void setAudioCapabilities(AudioCapabilities audioCapabilities) { - mSessionWorker.sendMessage( - TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED, audioCapabilities); - } - - @Override - public void notifyVideoAvailable() { - super.notifyVideoAvailable(); - if (mTuneStartTimestamp != 0) { - Log.i( - TAG, - "[Profiler] Video available in " - + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) - + " ms"); - mTuneStartTimestamp = 0; - } - } - - @Override - public void notifyVideoUnavailable(int reason) { - super.notifyVideoUnavailable(reason); - if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING - && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) { - notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); - } - } - - public void sendUiMessage(int message) { - mUiHandler.sendEmptyMessage(message); - } - - public void sendUiMessage(int message, Object object) { - mUiHandler.obtainMessage(message, object).sendToTarget(); - } - - public void sendUiMessage(int message, int arg1, int arg2, Object object) { - mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget(); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_UI_SHOW_MESSAGE: - { - mMessageView.setText((String) msg.obj); - mMessageLayout.setVisibility(View.VISIBLE); - return true; - } - case MSG_UI_HIDE_MESSAGE: - { - mMessageLayout.setVisibility(View.INVISIBLE); - return true; - } - case MSG_UI_SHOW_AUDIO_UNPLAYABLE: - { - // Showing message of enabling surround sound only when global surround sound - // setting is "never". - final int value = - GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext); - if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) { - mAudioStatusView.setText( - Html.fromHtml( - StatusTextUtils.getAudioWarningInHTML( - mContext.getString( - R.string.ut_surround_sound_disabled)))); - } else { - mAudioStatusView.setText( - Html.fromHtml( - StatusTextUtils.getAudioWarningInHTML( - mContext.getString( - R.string - .audio_passthrough_not_supported)))); - } - mAudioStatusView.setVisibility(View.VISIBLE); - return true; - } - case MSG_UI_HIDE_AUDIO_UNPLAYABLE: - { - mAudioStatusView.setVisibility(View.INVISIBLE); - return true; - } - case MSG_UI_PROCESS_CAPTION_TRACK: - { - mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj); - return true; - } - case MSG_UI_START_CAPTION_TRACK: - { - mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj); - return true; - } - case MSG_UI_STOP_CAPTION_TRACK: - { - mCaptionTrackRenderer.stop(); - return true; - } - case MSG_UI_RESET_CAPTION_TRACK: - { - mCaptionTrackRenderer.reset(); - return true; - } - case MSG_UI_CLEAR_CAPTION_RENDERER: - { - mCaptionTrackRenderer.clear(); - return true; - } - case MSG_UI_SET_STATUS_TEXT: - { - mStatusView.setText((CharSequence) msg.obj); - return true; - } - case MSG_UI_TOAST_RESCAN_NEEDED: - { - Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show(); - return true; - } - } - return false; - } - - @Override - public void onTunerPreferencesChanged() { - mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED); - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java deleted file mode 100644 index 7a0897e2..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java +++ /dev/null @@ -1,1852 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.media.MediaFormat; -import android.media.PlaybackParams; -import android.media.tv.TvContentRating; -import android.media.tv.TvContract; -import android.media.tv.TvInputManager; -import android.media.tv.TvTrackInfo; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; -import android.os.SystemClock; -import android.support.annotation.AnyThread; -import android.support.annotation.MainThread; -import android.support.annotation.WorkerThread; -import android.text.Html; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.util.SparseArray; -import android.view.Surface; -import android.view.accessibility.CaptioningManager; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvContentRatingCache; -import com.android.tv.customization.TvCustomizationManager; -import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.TunerPreferences.TrickplaySetting; -import com.android.tv.tuner.data.Cea708Data; -import com.android.tv.tuner.data.PsipData.EitItem; -import com.android.tv.tuner.data.PsipData.TvTracksInterface; -import com.android.tv.tuner.data.TunerChannel; -import com.android.tv.tuner.data.nano.Channel; -import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; -import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; -import com.android.tv.tuner.exoplayer.MpegTsPlayer; -import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder; -import com.android.tv.tuner.exoplayer.buffer.BufferManager; -import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager; -import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager; -import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; -import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient; -import com.android.tv.tuner.source.TsDataSource; -import com.android.tv.tuner.source.TsDataSourceManager; -import com.android.tv.tuner.util.StatusTextUtils; -import com.android.tv.tuner.util.SystemPropertiesProxy; -import com.google.android.exoplayer.ExoPlayer; -import com.google.android.exoplayer.audio.AudioCapabilities; -import java.io.File; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs such as - * handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on. - */ -@WorkerThread -public class TunerSessionWorker - implements PlaybackBufferListener, - MpegTsPlayer.VideoEventListener, - MpegTsPlayer.Listener, - EventDetector.EventListener, - ChannelDataManager.ProgramInfoListener, - Handler.Callback { - private static final String TAG = "TunerSessionWorker"; - private static final boolean DEBUG = false; - private static final boolean ENABLE_PROFILER = true; - private static final String PLAY_FROM_CHANNEL = "channel"; - private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes"; - private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB - private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB - - // Public messages - public static final int MSG_SELECT_TRACK = 1; - public static final int MSG_UPDATE_CAPTION_TRACK = 2; - public static final int MSG_SET_STREAM_VOLUME = 3; - public static final int MSG_TIMESHIFT_PAUSE = 4; - public static final int MSG_TIMESHIFT_RESUME = 5; - public static final int MSG_TIMESHIFT_SEEK_TO = 6; - public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7; - public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8; - public static final int MSG_UNBLOCKED_RATING = 9; - public static final int MSG_TUNER_PREFERENCES_CHANGED = 10; - - // Private messages - private static final int MSG_TUNE = 1000; - private static final int MSG_RELEASE = 1001; - private static final int MSG_RETRY_PLAYBACK = 1002; - private static final int MSG_START_PLAYBACK = 1003; - private static final int MSG_UPDATE_PROGRAM = 1008; - private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009; - private static final int MSG_UPDATE_CHANNEL_INFO = 1010; - private static final int MSG_TRICKPLAY_BY_SEEK = 1011; - private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012; - private static final int MSG_PARENTAL_CONTROLS = 1015; - private static final int MSG_RESCHEDULE_PROGRAMS = 1016; - private static final int MSG_BUFFER_START_TIME_CHANGED = 1017; - private static final int MSG_CHECK_SIGNAL = 1018; - private static final int MSG_DISCOVER_CAPTION_SERVICE_NUMBER = 1019; - private static final int MSG_RESET_PLAYBACK = 1020; - private static final int MSG_BUFFER_STATE_CHANGED = 1021; - private static final int MSG_PROGRAM_DATA_RESULT = 1022; - private static final int MSG_STOP_TUNE = 1023; - private static final int MSG_SET_SURFACE = 1024; - private static final int MSG_NOTIFY_AUDIO_TRACK_UPDATED = 1025; - - private static final int TS_PACKET_SIZE = 188; - private static final int CHECK_NO_SIGNAL_INITIAL_DELAY_MS = 4000; - private static final int CHECK_NO_SIGNAL_PERIOD_MS = 500; - private static final int RECOVER_STOPPED_PLAYBACK_PERIOD_MS = 2500; - private static final int PARENTAL_CONTROLS_INTERVAL_MS = 5000; - private static final int RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS = 4000; - private static final int RESCHEDULE_PROGRAMS_INTERVAL_MS = 10000; - private static final int RESCHEDULE_PROGRAMS_TOLERANCE_MS = 2000; - // The following 3s is defined empirically. This should be larger than 2s considering video - // key frame interval in the TS stream. - private static final int PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS = 3000; - private static final int PLAYBACK_RETRY_DELAY_MS = 5000; - private static final int MAX_IMMEDIATE_RETRY_COUNT = 5; - private static final long INVALID_TIME = -1; - - // Some examples of the track ids of the audio tracks, "a0", "a1", "a2". - // The number after prefix is being used for indicating a index of the given audio track. - private static final String AUDIO_TRACK_PREFIX = "a"; - - // Some examples of the tracks id of the caption tracks, "s1", "s2", "s3". - // The number after prefix is being used for indicating a index of a caption service number - // of the given caption track. - private static final String SUBTITLE_TRACK_PREFIX = "s"; - private static final int TRACK_PREFIX_SIZE = 1; - private static final String VIDEO_TRACK_ID = "v"; - private static final long BUFFER_UNDERFLOW_BUFFER_MS = 5000; - - // Actual interval would be divided by the speed. - private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500; - private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20; - private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250; - private static final int RELEASE_WAIT_INTERVAL_MS = 50; - private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14); - - // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker - // creation/release is required. - // This is used to guarantee that at most one active TunerSessionWorker exists at any give time. - private static Semaphore sActiveSessionSemaphore = new Semaphore(1); - - private final Context mContext; - private final ChannelDataManager mChannelDataManager; - private final TsDataSourceManager mSourceManager; - private final int mMaxTrickplayBufferSizeMb; - private final File mTrickplayBufferDir; - private final @TRICKPLAY_MODE int mTrickplayModeCustomization; - private volatile Surface mSurface; - private volatile float mVolume = 1.0f; - private volatile boolean mCaptionEnabled; - private volatile MpegTsPlayer mPlayer; - private volatile TunerChannel mChannel; - private volatile Long mRecordingDuration; - private volatile long mRecordStartTimeMs; - private volatile long mBufferStartTimeMs; - private volatile boolean mTrickplayDisabledByStorageIssue; - private @TrickplaySetting int mTrickplaySetting; - private long mTrickplayExpiredMs; - private String mRecordingId; - private final Handler mHandler; - private int mRetryCount; - private final ArrayList mTvTracks; - private final SparseArray mAudioTrackMap; - private final SparseArray mCaptionTrackMap; - private AtscCaptionTrack mCaptionTrack; - private PlaybackParams mPlaybackParams = new PlaybackParams(); - private boolean mPlayerStarted = false; - private boolean mReportedDrawnToSurface = false; - private boolean mReportedWeakSignal = false; - private EitItem mProgram; - private List mPrograms; - private final TvInputManager mTvInputManager; - private boolean mChannelBlocked; - private TvContentRating mUnblockedContentRating; - private long mLastPositionMs; - private AudioCapabilities mAudioCapabilities; - private long mLastLimitInBytes; - private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance(); - private final TunerSession mSession; - private final boolean mHasSoftwareAudioDecoder; - private int mPlayerState = ExoPlayer.STATE_IDLE; - private long mPreparingStartTimeMs; - private long mBufferingStartTimeMs; - private long mReadyStartTimeMs; - private boolean mIsActiveSession; - private boolean mReleaseRequested; // Guarded by mReleaseLock - private final Object mReleaseLock = new Object(); - - public TunerSessionWorker( - Context context, ChannelDataManager channelDataManager, TunerSession tunerSession) { - if (DEBUG) Log.d(TAG, "TunerSessionWorker created"); - mContext = context; - - // HandlerThread should be set up before it is registered as a listener in the all other - // components. - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper(), this); - mSession = tunerSession; - mChannelDataManager = channelDataManager; - mChannelDataManager.setListener(this); - mChannelDataManager.checkDataVersion(mContext); - mSourceManager = TsDataSourceManager.createSourceManager(false); - mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - mTvTracks = new ArrayList<>(); - mAudioTrackMap = new SparseArray<>(); - mCaptionTrackMap = new SparseArray<>(); - CaptioningManager captioningManager = - (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); - mCaptionEnabled = captioningManager.isEnabled(); - mPlaybackParams.setSpeed(1.0f); - mMaxTrickplayBufferSizeMb = - SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); - mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context); - if (mTrickplayModeCustomization - == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { - boolean useExternalStorage = - Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) - && Environment.isExternalStorageRemovable(); - mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null; - } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { - mTrickplayBufferDir = context.getCacheDir(); - } else { - mTrickplayBufferDir = null; - } - mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null; - mTrickplaySetting = TunerPreferences.getTrickplaySetting(context); - if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET - && mTrickplayModeCustomization - == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) { - // Consider the case of Customization package updates the value of trickplay mode - // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install. - mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET; - TunerPreferences.setTrickplaySetting(context, mTrickplaySetting); - TunerPreferences.setTrickplayExpiredMs(context, 0); - } - mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context); - mPreparingStartTimeMs = INVALID_TIME; - mBufferingStartTimeMs = INVALID_TIME; - mReadyStartTimeMs = INVALID_TIME; - // NOTE: We assume that TunerSessionWorker instance will be at most one. - // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time. - // connect() will return false, if there is a connected TunerSessionWorker already. - mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context); - } - - // Public methods - @MainThread - public void tune(Uri channelUri) { - mHandler.removeCallbacksAndMessages(null); - mSourceManager.setHasPendingTune(); - sendMessage(MSG_TUNE, channelUri); - } - - @MainThread - public void stopTune() { - mHandler.removeCallbacksAndMessages(null); - sendMessage(MSG_STOP_TUNE); - } - - /** Sets {@link Surface}. */ - @MainThread - public void setSurface(Surface surface) { - if (surface != null && !surface.isValid()) { - Log.w(TAG, "Ignoring invalid surface."); - return; - } - // mSurface is kept even when tune is called right after. But, messages can be deleted by - // tune or updateChannelBlockStatus. So mSurface should be stored here, not through message. - mSurface = surface; - mHandler.sendEmptyMessage(MSG_SET_SURFACE); - } - - /** Sets volume. */ - @MainThread - public void setStreamVolume(float volume) { - // mVolume is kept even when tune is called right after. But, messages can be deleted by - // tune or updateChannelBlockStatus. So mVolume is stored here and mPlayer.setVolume will be - // called in MSG_SET_STREAM_VOLUME. - mVolume = volume; - mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME); - } - - /** Sets if caption is enabled or disabled. */ - @MainThread - public void setCaptionEnabled(boolean captionEnabled) { - // mCaptionEnabled is kept even when tune is called right after. But, messages can be - // deleted by tune or updateChannelBlockStatus. So mCaptionEnabled is stored here and - // start/stopCaptionTrack will be called in MSG_UPDATE_CAPTION_STATUS. - mCaptionEnabled = captionEnabled; - mHandler.sendEmptyMessage(MSG_UPDATE_CAPTION_TRACK); - } - - public TunerChannel getCurrentChannel() { - return mChannel; - } - - @MainThread - public long getStartPosition() { - return mBufferStartTimeMs; - } - - private String getRecordingPath() { - return Uri.parse(mRecordingId).getPath(); - } - - private Long getDurationForRecording(String recordingId) { - DvrStorageManager storageManager = - new DvrStorageManager(new File(getRecordingPath()), false); - List trackFormatList = storageManager.readTrackInfoFiles(false); - if (trackFormatList.isEmpty()) { - trackFormatList = storageManager.readTrackInfoFiles(true); - } - if (!trackFormatList.isEmpty()) { - BufferManager.TrackFormat trackFormat = trackFormatList.get(0); - Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION); - // we need duration by milli for trickplay notification. - return durationUs != null ? durationUs / 1000 : null; - } - Log.e(TAG, "meta file for recording was not found: " + recordingId); - return null; - } - - @MainThread - public long getCurrentPosition() { - // TODO: More precise time may be necessary. - MpegTsPlayer mpegTsPlayer = mPlayer; - long currentTime = - mpegTsPlayer != null - ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() - : mRecordStartTimeMs; - if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) { - currentTime = mRecordingDuration + mRecordStartTimeMs; - } - if (DEBUG) { - long systemCurrentTime = System.currentTimeMillis(); - Log.d( - TAG, - "currentTime = " - + currentTime - + " ; System.currentTimeMillis() = " - + systemCurrentTime - + " ; diff = " - + (currentTime - systemCurrentTime)); - } - return currentTime; - } - - @AnyThread - public void sendMessage(int messageType) { - mHandler.sendEmptyMessage(messageType); - } - - @AnyThread - public void sendMessage(int messageType, Object object) { - mHandler.obtainMessage(messageType, object).sendToTarget(); - } - - @AnyThread - public void sendMessage(int messageType, int arg1, int arg2, Object object) { - mHandler.obtainMessage(messageType, arg1, arg2, object).sendToTarget(); - } - - @MainThread - public void release() { - if (DEBUG) Log.d(TAG, "release()"); - synchronized (mReleaseLock) { - mReleaseRequested = true; - } - if (mHasSoftwareAudioDecoder) { - FfmpegDecoderClient.disconnect(mContext); - } - mChannelDataManager.setListener(null); - mHandler.removeCallbacksAndMessages(null); - mHandler.sendEmptyMessage(MSG_RELEASE); - } - - // MpegTsPlayer.Listener - // Called in the same thread as mHandler. - @Override - public void onStateChanged(boolean playWhenReady, int playbackState) { - if (DEBUG) Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady); - if (playbackState == mPlayerState) { - return; - } - mReadyStartTimeMs = INVALID_TIME; - mPreparingStartTimeMs = INVALID_TIME; - mBufferingStartTimeMs = INVALID_TIME; - if (playbackState == ExoPlayer.STATE_READY) { - if (DEBUG) Log.d(TAG, "ExoPlayer ready"); - if (!mPlayerStarted) { - sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer)); - } - mReadyStartTimeMs = SystemClock.elapsedRealtime(); - } else if (playbackState == ExoPlayer.STATE_PREPARING) { - mPreparingStartTimeMs = SystemClock.elapsedRealtime(); - } else if (playbackState == ExoPlayer.STATE_BUFFERING) { - mBufferingStartTimeMs = SystemClock.elapsedRealtime(); - } else if (playbackState == ExoPlayer.STATE_ENDED) { - // Final status - // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards. - Log.i(TAG, "Player ended: end of stream"); - if (mChannel != null) { - sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); - } - } - mPlayerState = playbackState; - } - - @Override - public void onError(Exception e) { - if (TunerPreferences.getStoreTsStream(mContext)) { - // Crash intentionally to capture the error causing TS file. - Log.e( - TAG, - "Crash intentionally to capture the error causing TS file. " + e.getMessage()); - SoftPreconditions.checkState(false); - } - // There maybe some errors that finally raise ExoPlaybackException and will be handled here. - // If we are playing live stream, retrying playback maybe helpful. But for recorded stream, - // retrying playback is not helpful. - if (mChannel != null) { - mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)) - .sendToTarget(); - } - } - - @Override - public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) { - if (mChannel != null && mChannel.hasVideo()) { - updateVideoTrack(width, height); - } - if (mRecordingId != null) { - updateVideoTrack(width, height); - } - } - - @Override - public void onDrawnToSurface(MpegTsPlayer player, Surface surface) { - if (mSurface != null && mPlayerStarted) { - if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE"); - if (mRecordingId != null) { - // Workaround of b/33298048: set it to 1 instead of 0. - mBufferStartTimeMs = mRecordStartTimeMs = 1; - } else { - mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); - } - notifyVideoAvailable(); - mReportedDrawnToSurface = true; - - // If surface is drawn successfully, it means that the playback was brought back - // to normal and therefore, the playback recovery status will be reset through - // setting a zero value to the retry count. - // TODO: Consider audio only channels for detecting playback status changes to - // be normal. - mRetryCount = 0; - if (mCaptionEnabled && mCaptionTrack != null) { - startCaptionTrack(); - } else { - stopCaptionTrack(); - } - mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED); - } - } - - @Override - public void onSmoothTrickplayForceStopped() { - if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) { - return; - } - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - doTrickplayBySeek((int) mPlayer.getCurrentPosition()); - } - - @Override - public void onAudioUnplayable() { - if (mPlayer == null) { - return; - } - Log.i(TAG, "AC3 audio cannot be played due to device limitation"); - mSession.sendUiMessage(TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE); - } - - // MpegTsPlayer.VideoEventListener - @Override - public void onEmitCaptionEvent(Cea708Data.CaptionEvent event) { - mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event); - } - - @Override - public void onClearCaptionEvent() { - mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER); - } - - @Override - public void onDiscoverCaptionServiceNumber(int serviceNumber) { - sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber); - } - - // ChannelDataManager.ProgramInfoListener - @Override - public void onProgramsArrived(TunerChannel channel, List programs) { - sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs)); - } - - @Override - public void onChannelArrived(TunerChannel channel) { - sendMessage(MSG_UPDATE_CHANNEL_INFO, channel); - } - - @Override - public void onRescanNeeded() { - mSession.sendUiMessage(TunerSession.MSG_UI_TOAST_RESCAN_NEEDED); - } - - @Override - public void onRequestProgramsResponse(TunerChannel channel, List programs) { - sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs)); - } - - // PlaybackBufferListener - @Override - public void onBufferStartTimeChanged(long startTimeMs) { - sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs); - } - - @Override - public void onBufferStateChanged(boolean available) { - sendMessage(MSG_BUFFER_STATE_CHANGED, available); - } - - @Override - public void onDiskTooSlow() { - mTrickplayDisabledByStorageIssue = true; - sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); - } - - // EventDetector.EventListener - @Override - public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { - mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime); - } - - @Override - public void onEventDetected(TunerChannel channel, List items) { - mChannelDataManager.notifyEventDetected(channel, items); - } - - @Override - public void onChannelScanDone() { - // do nothing. - } - - private long parseChannel(Uri uri) { - try { - List paths = uri.getPathSegments(); - if (paths.size() > 1 && paths.get(0).equals(PLAY_FROM_CHANNEL)) { - return ContentUris.parseId(uri); - } - } catch (UnsupportedOperationException | NumberFormatException e) { - } - return -1; - } - - private static class RecordedProgram { - private final long mChannelId; - private final String mDataUri; - - private static final String[] PROJECTION = { - TvContract.Programs.COLUMN_CHANNEL_ID, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI, - }; - - public RecordedProgram(Cursor cursor) { - int index = 0; - mChannelId = cursor.getLong(index++); - mDataUri = cursor.getString(index++); - } - - public RecordedProgram(long channelId, String dataUri) { - mChannelId = channelId; - mDataUri = dataUri; - } - - public static RecordedProgram onQuery(Cursor c) { - RecordedProgram recording = null; - if (c != null && c.moveToNext()) { - recording = new RecordedProgram(c); - } - return recording; - } - - public String getDataUri() { - return mDataUri; - } - } - - private RecordedProgram getRecordedProgram(Uri recordedUri) { - ContentResolver resolver = mContext.getContentResolver(); - try (Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) { - if (c != null) { - RecordedProgram result = RecordedProgram.onQuery(c); - if (DEBUG) { - Log.d(TAG, "Finished query for " + this); - } - return result; - } else { - if (c == null) { - Log.e(TAG, "Unknown query error for " + this); - } else { - if (DEBUG) Log.d(TAG, "Canceled query for " + this); - } - return null; - } - } - } - - private String parseRecording(Uri uri) { - RecordedProgram recording = getRecordedProgram(uri); - if (recording != null) { - return recording.getDataUri(); - } - return null; - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_TUNE: - { - if (DEBUG) Log.d(TAG, "MSG_TUNE"); - - // When sequential tuning messages arrived, it skips middle tuning messages in - // order - // to change to the last requested channel quickly. - if (mHandler.hasMessages(MSG_TUNE)) { - return true; - } - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); - if (!mIsActiveSession) { - // Wait until release is finished if there is a pending release. - try { - while (!sActiveSessionSemaphore.tryAcquire( - RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) { - synchronized (mReleaseLock) { - if (mReleaseRequested) { - return true; - } - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - synchronized (mReleaseLock) { - if (mReleaseRequested) { - sActiveSessionSemaphore.release(); - return true; - } - } - mIsActiveSession = true; - } - Uri channelUri = (Uri) msg.obj; - String recording = null; - long channelId = parseChannel(channelUri); - TunerChannel channel = - (channelId == -1) ? null : mChannelDataManager.getChannel(channelId); - if (channelId == -1) { - recording = parseRecording(channelUri); - } - if (channel == null && recording == null) { - Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri); - stopTune(); - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); - return true; - } - clearCallbacksAndMessagesSafely(); - mChannelDataManager.removeAllCallbacksAndMessages(); - if (channel != null) { - mChannelDataManager.requestProgramsData(channel); - } - prepareTune(channel, recording); - // TODO: Need to refactor. notifyContentAllowed() should not be called if - // parental - // control is turned on. - mSession.notifyContentAllowed(); - resetTvTracks(); - resetPlayback(); - mHandler.sendEmptyMessageDelayed( - MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); - return true; - } - case MSG_STOP_TUNE: - { - if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); - mChannel = null; - stopPlayback(true); - stopCaptionTrack(); - resetTvTracks(); - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); - return true; - } - case MSG_RELEASE: - { - if (DEBUG) Log.d(TAG, "MSG_RELEASE"); - mHandler.removeCallbacksAndMessages(null); - stopPlayback(true); - stopCaptionTrack(); - mSourceManager.release(); - mHandler.getLooper().quitSafely(); - if (mIsActiveSession) { - sActiveSessionSemaphore.release(); - } - return true; - } - case MSG_RETRY_PLAYBACK: - { - if (System.identityHashCode(mPlayer) == (int) msg.obj) { - Log.i(TAG, "Retrying the playback for channel: " + mChannel); - mHandler.removeMessages(MSG_RETRY_PLAYBACK); - // When there is a request of retrying playback, don't reuse TunerHal. - mSourceManager.setKeepTuneStatus(false); - mRetryCount++; - if (DEBUG) { - Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount); - } - mChannelDataManager.removeAllCallbacksAndMessages(); - if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) { - resetPlayback(); - } else { - // When it reaches this point, it may be due to an error that occurred - // in - // the tuner device. Calling stopPlayback() resets the tuner device - // to recover from the error. - stopPlayback(false); - stopCaptionTrack(); - - notifyVideoUnavailable( - TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - Log.i(TAG, "Notify weak signal since fail to retry playback"); - - // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically - // chosen - // value before recovering the playback. - mHandler.sendEmptyMessageDelayed( - MSG_RESET_PLAYBACK, RECOVER_STOPPED_PLAYBACK_PERIOD_MS); - } - } - return true; - } - case MSG_RESET_PLAYBACK: - { - if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK"); - mChannelDataManager.removeAllCallbacksAndMessages(); - resetPlayback(); - return true; - } - case MSG_START_PLAYBACK: - { - if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK"); - if (mChannel != null || mRecordingId != null) { - startPlayback((int) msg.obj); - } - return true; - } - case MSG_UPDATE_PROGRAM: - { - if (mChannel != null) { - EitItem program = (EitItem) msg.obj; - updateTvTracks(program, false); - mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); - } - return true; - } - case MSG_SCHEDULE_OF_PROGRAMS: - { - mHandler.removeMessages(MSG_UPDATE_PROGRAM); - Pair> pair = - (Pair>) msg.obj; - TunerChannel channel = pair.first; - if (mChannel == null) { - return true; - } - if (mChannel != null && mChannel.compareTo(channel) != 0) { - return true; - } - mPrograms = pair.second; - EitItem currentProgram = getCurrentProgram(); - if (currentProgram == null) { - mProgram = null; - } - long currentTimeMs = getCurrentPosition(); - if (mPrograms != null) { - for (EitItem item : mPrograms) { - if (currentProgram != null && currentProgram.compareTo(item) == 0) { - if (DEBUG) { - Log.d(TAG, "Update current TvTracks " + item); - } - if (mProgram != null && mProgram.compareTo(item) == 0) { - continue; - } - mProgram = item; - updateTvTracks(item, false); - } else if (item.getStartTimeUtcMillis() > currentTimeMs) { - if (DEBUG) { - Log.d( - TAG, - "Update next TvTracks " - + item - + " " - + (item.getStartTimeUtcMillis() - - currentTimeMs)); - } - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item), - item.getStartTimeUtcMillis() - currentTimeMs); - } - } - } - mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); - return true; - } - case MSG_UPDATE_CHANNEL_INFO: - { - TunerChannel channel = (TunerChannel) msg.obj; - if (mChannel != null && mChannel.compareTo(channel) == 0) { - updateChannelInfo(channel); - } - return true; - } - case MSG_PROGRAM_DATA_RESULT: - { - TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first; - - // If there already exists, skip it since real-time data is a top priority, - if (mChannel != null - && mChannel.compareTo(channel) == 0 - && mPrograms == null - && mProgram == null) { - sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj); - } - return true; - } - case MSG_TRICKPLAY_BY_SEEK: - { - if (mPlayer == null) { - return true; - } - doTrickplayBySeek(msg.arg1); - return true; - } - case MSG_SMOOTH_TRICKPLAY_MONITOR: - { - if (mPlayer == null) { - return true; - } - long systemCurrentTime = System.currentTimeMillis(); - long position = getCurrentPosition(); - if (mRecordingId == null) { - // Checks if the position exceeds the upper bound when forwarding, - // or exceed the lower bound when rewinding. - // If the direction is not checked, there can be some issues. - // (See b/29939781 for more details.) - if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L) - || (position < mBufferStartTimeMs - && mPlaybackParams.getSpeed() < 0L)) { - doTimeShiftResume(); - return true; - } - } else { - if (position > mRecordingDuration || position < 0) { - doTimeShiftPause(); - return true; - } - } - mHandler.sendEmptyMessageDelayed( - MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS); - return true; - } - case MSG_RESCHEDULE_PROGRAMS: - { - if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { - mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); - } else { - doReschedulePrograms(); - } - return true; - } - case MSG_PARENTAL_CONTROLS: - { - doParentalControls(); - mHandler.removeMessages(MSG_PARENTAL_CONTROLS); - mHandler.sendEmptyMessageDelayed( - MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); - return true; - } - case MSG_UNBLOCKED_RATING: - { - mUnblockedContentRating = (TvContentRating) msg.obj; - doParentalControls(); - mHandler.removeMessages(MSG_PARENTAL_CONTROLS); - mHandler.sendEmptyMessageDelayed( - MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); - return true; - } - case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: - { - int serviceNumber = (int) msg.obj; - doDiscoverCaptionServiceNumber(serviceNumber); - return true; - } - case MSG_SELECT_TRACK: - { - if (mChannel != null || mRecordingId != null) { - doSelectTrack(msg.arg1, (String) msg.obj); - } - return true; - } - case MSG_UPDATE_CAPTION_TRACK: - { - if (mCaptionEnabled) { - startCaptionTrack(); - } else { - stopCaptionTrack(); - } - return true; - } - case MSG_TIMESHIFT_PAUSE: - { - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE"); - if (mPlayer == null) { - return true; - } - setTrickplayEnabledIfNeeded(); - doTimeShiftPause(); - return true; - } - case MSG_TIMESHIFT_RESUME: - { - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME"); - if (mPlayer == null) { - return true; - } - setTrickplayEnabledIfNeeded(); - doTimeShiftResume(); - return true; - } - case MSG_TIMESHIFT_SEEK_TO: - { - long position = (long) msg.obj; - if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")"); - if (mPlayer == null) { - return true; - } - setTrickplayEnabledIfNeeded(); - doTimeShiftSeekTo(position); - return true; - } - case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: - { - if (mPlayer == null) { - return true; - } - setTrickplayEnabledIfNeeded(); - doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj); - return true; - } - case MSG_AUDIO_CAPABILITIES_CHANGED: - { - AudioCapabilities capabilities = (AudioCapabilities) msg.obj; - if (DEBUG) { - Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities); - } - if (capabilities == null) { - return true; - } - if (!capabilities.equals(mAudioCapabilities)) { - // HDMI supported encodings are changed. restart player. - mAudioCapabilities = capabilities; - resetPlayback(); - } - return true; - } - case MSG_SET_STREAM_VOLUME: - { - if (mPlayer != null && mPlayer.isPlaying()) { - mPlayer.setVolume(mVolume); - } - return true; - } - case MSG_TUNER_PREFERENCES_CHANGED: - { - mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED); - @TrickplaySetting - int trickplaySetting = TunerPreferences.getTrickplaySetting(mContext); - if (trickplaySetting != mTrickplaySetting) { - boolean wasTrcikplayEnabled = - mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; - boolean isTrickplayEnabled = - trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED; - mTrickplaySetting = trickplaySetting; - if (isTrickplayEnabled != wasTrcikplayEnabled) { - sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer)); - } - } - return true; - } - case MSG_BUFFER_START_TIME_CHANGED: - { - if (mPlayer == null) { - return true; - } - mBufferStartTimeMs = (long) msg.obj; - if (!hasEnoughBackwardBuffer() - && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) { - mPlayer.setPlayWhenReady(true); - mPlayer.setAudioTrackAndClosedCaption(true); - mPlaybackParams.setSpeed(1.0f); - } - return true; - } - case MSG_BUFFER_STATE_CHANGED: - { - boolean available = (boolean) msg.obj; - mSession.notifyTimeShiftStatusChanged( - available - ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE - : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); - return true; - } - case MSG_CHECK_SIGNAL: - { - if (mChannel == null || mPlayer == null) { - return true; - } - TsDataSource source = mPlayer.getDataSource(); - long limitInBytes = source != null ? source.getBufferedPosition() : 0L; - if (TunerDebug.ENABLED) { - TunerDebug.calculateDiff(); - mSession.sendUiMessage( - TunerSession.MSG_UI_SET_STATUS_TEXT, - Html.fromHtml( - StatusTextUtils.getStatusWarningInHTML( - (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, - TunerDebug.getVideoFrameDrop(), - TunerDebug.getBytesInQueue(), - TunerDebug.getAudioPositionUs(), - TunerDebug.getAudioPositionUsRate(), - TunerDebug.getAudioPtsUs(), - TunerDebug.getAudioPtsUsRate(), - TunerDebug.getVideoPtsUs(), - TunerDebug.getVideoPtsUsRate()))); - } - mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); - long currentTime = SystemClock.elapsedRealtime(); - long bufferingTimeMs = - mBufferingStartTimeMs != INVALID_TIME - ? currentTime - mBufferingStartTimeMs - : mBufferingStartTimeMs; - long preparingTimeMs = - mPreparingStartTimeMs != INVALID_TIME - ? currentTime - mPreparingStartTimeMs - : mPreparingStartTimeMs; - boolean isBufferingTooLong = - bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - boolean isPreparingTooLong = - preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - boolean isWeakSignal = - source != null - && mChannel.getType() != Channel.TYPE_FILE - && (isBufferingTooLong || isPreparingTooLong); - if (isWeakSignal && !mReportedWeakSignal) { - if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) { - mHandler.sendMessageDelayed( - mHandler.obtainMessage( - MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)), - PLAYBACK_RETRY_DELAY_MS); - } - if (mPlayer != null) { - mPlayer.setAudioTrackAndClosedCaption(false); - } - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - Log.i( - TAG, - "Notify weak signal due to signal check, " - + String.format( - "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " - + "videoFrameDrop:%d", - (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE, - bufferingTimeMs, - preparingTimeMs, - TunerDebug.getVideoFrameDrop())); - } else if (!isWeakSignal && mReportedWeakSignal) { - boolean isPlaybackStable = - mReadyStartTimeMs != INVALID_TIME - && currentTime - mReadyStartTimeMs - > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; - if (!isPlaybackStable) { - // Wait until playback becomes stable. - } else if (mReportedDrawnToSurface) { - mHandler.removeMessages(MSG_RETRY_PLAYBACK); - notifyVideoAvailable(); - mPlayer.setAudioTrackAndClosedCaption(true); - } - } - mLastLimitInBytes = limitInBytes; - mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); - return true; - } - case MSG_SET_SURFACE: - { - if (mPlayer != null) { - mPlayer.setSurface(mSurface); - } else { - // TODO: Since surface is dynamically set, we can remove the dependency of - // playback start on mSurface nullity. - resetPlayback(); - } - return true; - } - case MSG_NOTIFY_AUDIO_TRACK_UPDATED: - { - notifyAudioTracksUpdated(); - return true; - } - default: - { - Log.w(TAG, "Unhandled message code: " + msg.what); - return false; - } - } - } - - // Private methods - private void doSelectTrack(int type, String trackId) { - int numTrackId = - trackId != null ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1; - if (type == TvTrackInfo.TYPE_AUDIO) { - if (trackId == null) { - return; - } - if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) { - mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId); - } - mSession.notifyTrackSelected(type, trackId); - } else if (type == TvTrackInfo.TYPE_SUBTITLE) { - if (trackId == null) { - mSession.notifyTrackSelected(type, null); - mCaptionTrack = null; - stopCaptionTrack(); - return; - } - for (TvTrackInfo track : mTvTracks) { - if (track.getId().equals(trackId)) { - // The service number of the caption service is used for track id of a - // subtitle track. Passes the following track id on to TsParser. - mSession.notifyTrackSelected(type, trackId); - mCaptionTrack = mCaptionTrackMap.get(numTrackId); - startCaptionTrack(); - return; - } - } - } - } - - private void setTrickplayEnabledIfNeeded() { - if (mChannel == null - || mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) { - return; - } - if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { - mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED; - TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting); - } - } - - private MpegTsPlayer createPlayer(AudioCapabilities capabilities) { - if (capabilities == null) { - Log.w(TAG, "No Audio Capabilities"); - } - long now = System.currentTimeMillis(); - if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED - && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) { - if (mTrickplayExpiredMs == 0) { - mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS; - TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs); - } else { - if (mTrickplayExpiredMs < now) { - mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED; - TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting); - } - } - } - BufferManager bufferManager = null; - if (mRecordingId != null) { - StorageManager storageManager = - new DvrStorageManager(new File(getRecordingPath()), false); - bufferManager = new BufferManager(storageManager); - updateCaptionTracks(((DvrStorageManager) storageManager).readCaptionInfoFiles()); - } else if (!mTrickplayDisabledByStorageIssue - && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED - && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { - bufferManager = - new BufferManager( - new TrickplayStorageManager( - mContext, - mTrickplayBufferDir, - 1024L * 1024 * mMaxTrickplayBufferSizeMb)); - } else { - Log.w(TAG, "Trickplay is disabled."); - } - MpegTsPlayer player = - new MpegTsPlayer( - new MpegTsRendererBuilder(mContext, bufferManager, this), - mHandler, - mSourceManager, - capabilities, - this); - Log.i(TAG, "Passthrough AC3 renderer"); - if (DEBUG) Log.d(TAG, "ExoPlayer created"); - return player; - } - - private void startCaptionTrack() { - if (mCaptionEnabled && mCaptionTrack != null) { - mSession.sendUiMessage(TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack); - if (mPlayer != null) { - mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber); - } - } - } - - private void stopCaptionTrack() { - if (mPlayer != null) { - mPlayer.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); - } - mSession.sendUiMessage(TunerSession.MSG_UI_STOP_CAPTION_TRACK); - } - - private void resetTvTracks() { - mTvTracks.clear(); - mAudioTrackMap.clear(); - mCaptionTrackMap.clear(); - mSession.sendUiMessage(TunerSession.MSG_UI_RESET_CAPTION_TRACK); - mSession.notifyTracksChanged(mTvTracks); - } - - private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) { - synchronized (tvTracksInterface) { - if (DEBUG) { - Log.d(TAG, "UpdateTvTracks " + tvTracksInterface); - } - List audioTracks = tvTracksInterface.getAudioTracks(); - List captionTracks = tvTracksInterface.getCaptionTracks(); - // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for - // audio - // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust - // audio - // track info in PMT more and use info in EIT only when we have nothing. - if (audioTracks != null - && !audioTracks.isEmpty() - && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) { - updateAudioTracks(audioTracks); - } - if (captionTracks == null || captionTracks.isEmpty()) { - if (tvTracksInterface.hasCaptionTrack()) { - updateCaptionTracks(captionTracks); - } - } else { - updateCaptionTracks(captionTracks); - } - } - } - - private void removeTvTracks(int trackType) { - Iterator iterator = mTvTracks.iterator(); - while (iterator.hasNext()) { - TvTrackInfo tvTrackInfo = iterator.next(); - if (tvTrackInfo.getType() == trackType) { - iterator.remove(); - } - } - } - - private void updateVideoTrack(int width, int height) { - removeTvTracks(TvTrackInfo.TYPE_VIDEO); - mTvTracks.add( - new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID) - .setVideoWidth(width) - .setVideoHeight(height) - .build()); - mSession.notifyTracksChanged(mTvTracks); - mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID); - } - - private void updateAudioTracks(List audioTracks) { - if (DEBUG) { - Log.d(TAG, "Update AudioTracks " + audioTracks); - } - mAudioTrackMap.clear(); - if (audioTracks != null) { - int index = 0; - for (AtscAudioTrack audioTrack : audioTracks) { - audioTrack.index = index; - mAudioTrackMap.put(index, audioTrack); - ++index; - } - } - mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED); - } - - private void notifyAudioTracksUpdated() { - if (mPlayer == null) { - // Audio tracks will be updated later once player initialization is done. - return; - } - int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO); - removeTvTracks(TvTrackInfo.TYPE_AUDIO); - for (int i = 0; i < audioTrackCount; i++) { - // We use language information from EIT/VCT only when the player does not provide - // languages. - com.google.android.exoplayer.MediaFormat infoFromPlayer = - mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i); - AtscAudioTrack infoFromEit = mAudioTrackMap.get(i); - AtscAudioTrack infoFromVct = - (mChannel != null - && mChannel.getAudioTracks().size() == mAudioTrackMap.size() - && i < mChannel.getAudioTracks().size()) - ? mChannel.getAudioTracks().get(i) - : null; - String language = - !TextUtils.isEmpty(infoFromPlayer.language) - ? infoFromPlayer.language - : (infoFromEit != null && infoFromEit.language != null) - ? infoFromEit.language - : (infoFromVct != null && infoFromVct.language != null) - ? infoFromVct.language - : null; - TvTrackInfo.Builder builder = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); - builder.setLanguage(language); - builder.setAudioChannelCount(infoFromPlayer.channelCount); - builder.setAudioSampleRate(infoFromPlayer.sampleRate); - TvTrackInfo track = builder.build(); - mTvTracks.add(track); - } - mSession.notifyTracksChanged(mTvTracks); - } - - private void updateCaptionTracks(List captionTracks) { - if (DEBUG) { - Log.d(TAG, "Update CaptionTrack " + captionTracks); - } - removeTvTracks(TvTrackInfo.TYPE_SUBTITLE); - mCaptionTrackMap.clear(); - if (captionTracks != null) { - for (AtscCaptionTrack captionTrack : captionTracks) { - if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) { - continue; - } - String language = captionTrack.language; - - // The service number of the caption service is used for track id of a subtitle. - // Later, when a subtitle is chosen, track id will be passed on to TsParser. - TvTrackInfo.Builder builder = - new TvTrackInfo.Builder( - TvTrackInfo.TYPE_SUBTITLE, - SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber); - builder.setLanguage(language); - mTvTracks.add(builder.build()); - mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack); - } - } - mSession.notifyTracksChanged(mTvTracks); - } - - private void updateChannelInfo(TunerChannel channel) { - if (DEBUG) { - Log.d( - TAG, - String.format( - "Channel Info (old) videoPid: %d audioPid: %d " + "audioSize: %d", - mChannel.getVideoPid(), - mChannel.getAudioPid(), - mChannel.getAudioPids().size())); - } - - // The list of the audio tracks resided in a channel is often changed depending on a - // program being on the air. So, we should update the streaming PIDs and types of the - // tuned channel according to the newly received channel data. - int oldVideoPid = mChannel.getVideoPid(); - int oldAudioPid = mChannel.getAudioPid(); - List audioPids = channel.getAudioPids(); - List audioStreamTypes = channel.getAudioStreamTypes(); - int size = audioPids.size(); - mChannel.setVideoPid(channel.getVideoPid()); - mChannel.setAudioPids(audioPids); - mChannel.setAudioStreamTypes(audioStreamTypes); - updateTvTracks(channel, true); - int index = audioPids.isEmpty() ? -1 : 0; - for (int i = 0; i < size; ++i) { - if (audioPids.get(i) == oldAudioPid) { - index = i; - break; - } - } - mChannel.selectAudioTrack(index); - mSession.notifyTrackSelected( - TvTrackInfo.TYPE_AUDIO, index == -1 ? null : AUDIO_TRACK_PREFIX + index); - - // Reset playback if there is a change in the listening streaming PIDs. - if (oldVideoPid != mChannel.getVideoPid() || oldAudioPid != mChannel.getAudioPid()) { - // TODO: Implement a switching between tracks more smoothly. - resetPlayback(); - } - if (DEBUG) { - Log.d( - TAG, - String.format( - "Channel Info (new) videoPid: %d audioPid: %d " + " audioSize: %d", - mChannel.getVideoPid(), - mChannel.getAudioPid(), - mChannel.getAudioPids().size())); - } - } - - private void stopPlayback(boolean removeChannelDataCallbacks) { - if (removeChannelDataCallbacks) { - mChannelDataManager.removeAllCallbacksAndMessages(); - } - if (mPlayer != null) { - mPlayer.setPlayWhenReady(false); - mPlayer.release(); - mPlayer = null; - mPlayerState = ExoPlayer.STATE_IDLE; - mPlaybackParams.setSpeed(1.0f); - mPlayerStarted = false; - mReportedDrawnToSurface = false; - mPreparingStartTimeMs = INVALID_TIME; - mBufferingStartTimeMs = INVALID_TIME; - mReadyStartTimeMs = INVALID_TIME; - mLastLimitInBytes = 0L; - mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE); - mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE); - } - } - - private void startPlayback(int playerHashCode) { - // TODO: provide hasAudio()/hasVideo() for play recordings. - if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) { - return; - } - if (mChannel != null && !mChannel.hasAudio()) { - if (DEBUG) Log.d(TAG, "Channel " + mChannel + " does not have audio."); - // Playbacks with video-only stream have not been tested yet. - // No video-only channel has been found. - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); - return; - } - if (mChannel != null - && ((mChannel.hasAudio() && !mPlayer.hasAudio()) - || (mChannel.hasVideo() && !mPlayer.hasVideo())) - && mChannel.getType() != Channel.TYPE_NETWORK) { - // If the channel is from network, skip this part since the video and audio tracks - // information for channels from network are more reliable in the extractor. Otherwise, - // tracks haven't been detected in the extractor. Try again. - sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); - return; - } - // Since mSurface is volatile, we define a local variable surface to keep the same value - // inside this method. - Surface surface = mSurface; - if (surface != null && !mPlayerStarted) { - mPlayer.setSurface(surface); - mPlayer.setPlayWhenReady(true); - mPlayer.setVolume(mVolume); - if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) { - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY); - } else if (!mReportedWeakSignal) { - // Doesn't show buffering during weak signal. - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING); - } - mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); - mPlayerStarted = true; - } - } - - private void preparePlayback() { - SoftPreconditions.checkState(mPlayer == null); - if (mChannel == null && mRecordingId == null) { - return; - } - mSourceManager.setKeepTuneStatus(true); - MpegTsPlayer player = createPlayer(mAudioCapabilities); - player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); - player.setVideoEventListener(this); - player.setCaptionServiceNumber( - mCaptionTrack != null - ? mCaptionTrack.serviceNumber - : Cea708Data.EMPTY_SERVICE_NUMBER); - if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) { - mSourceManager.setKeepTuneStatus(false); - player.release(); - if (!mHandler.hasMessages(MSG_TUNE)) { - // When prepare failed, there may be some errors related to hardware. In that - // case, retry playback immediately may not help. - notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - Log.i(TAG, "Notify weak signal due to player preparation failure"); - mHandler.sendMessageDelayed( - mHandler.obtainMessage( - MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)), - PLAYBACK_RETRY_DELAY_MS); - } - } else { - mPlayer = player; - mPlayerStarted = false; - mHandler.removeMessages(MSG_CHECK_SIGNAL); - mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS); - } - } - - private void resetPlayback() { - long timestamp, oldTimestamp; - timestamp = SystemClock.elapsedRealtime(); - stopPlayback(false); - stopCaptionTrack(); - if (ENABLE_PROFILER) { - oldTimestamp = timestamp; - timestamp = SystemClock.elapsedRealtime(); - Log.i(TAG, "[Profiler] stopPlayback() takes " + (timestamp - oldTimestamp) + " ms"); - } - if (mChannelBlocked || mSurface == null) { - return; - } - preparePlayback(); - } - - private void prepareTune(TunerChannel channel, String recording) { - mChannelBlocked = false; - mUnblockedContentRating = null; - mRetryCount = 0; - mChannel = channel; - mRecordingId = recording; - mRecordingDuration = recording != null ? getDurationForRecording(recording) : null; - mProgram = null; - mPrograms = null; - if (mRecordingId != null) { - // Workaround of b/33298048: set it to 1 instead of 0. - mBufferStartTimeMs = mRecordStartTimeMs = 1; - } else { - mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis(); - } - mLastPositionMs = 0; - mCaptionTrack = null; - mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); - } - - private void doReschedulePrograms() { - long currentPositionMs = getCurrentPosition(); - long forwardDifference = - Math.abs(currentPositionMs - mLastPositionMs - RESCHEDULE_PROGRAMS_INTERVAL_MS); - mLastPositionMs = currentPositionMs; - - // A gap is measured as the time difference between previous and next current position - // periodically. If the gap has a significant difference with an interval of a period, - // this means that there is a change of playback status and the programs of the current - // channel should be rescheduled to new playback timeline. - if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) { - if (DEBUG) { - Log.d( - TAG, - "reschedule programs size:" - + (mPrograms != null ? mPrograms.size() : 0) - + " current program: " - + getCurrentProgram()); - } - mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms)) - .sendToTarget(); - } - mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS); - mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INTERVAL_MS); - } - - private int getTrickPlaySeekIntervalMs() { - return Math.max( - EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()), - MIN_TRICKPLAY_SEEK_INTERVAL_MS); - } - - private void doTrickplayBySeek(int seekPositionMs) { - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - if (mPlaybackParams.getSpeed() == 1.0f || !mPlayer.isPrepared()) { - return; - } - if (seekPositionMs < mBufferStartTimeMs - mRecordStartTimeMs) { - if (mPlaybackParams.getSpeed() > 1.0f) { - // If fast forwarding, the seekPositionMs can be out of the buffered range - // because of chuck evictions. - seekPositionMs = (int) (mBufferStartTimeMs - mRecordStartTimeMs); - } else { - mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs); - mPlaybackParams.setSpeed(1.0f); - mPlayer.setAudioTrackAndClosedCaption(true); - return; - } - } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) { - // Stops trickplay when FF requested the position later than current position. - // If RW trickplay requested the position later than current position, - // continue trickplay. - if (mPlaybackParams.getSpeed() > 0.0f) { - mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs); - mPlaybackParams.setSpeed(1.0f); - mPlayer.setAudioTrackAndClosedCaption(true); - return; - } - } - - long delayForNextSeek = getTrickPlaySeekIntervalMs(); - if (!mPlayer.isBuffering()) { - mPlayer.seekTo(seekPositionMs); - } else { - delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS; - } - seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek; - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek); - } - - private void doTimeShiftPause() { - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - if (!hasEnoughBackwardBuffer()) { - return; - } - mPlaybackParams.setSpeed(1.0f); - mPlayer.setPlayWhenReady(false); - mPlayer.setAudioTrackAndClosedCaption(true); - } - - private void doTimeShiftResume() { - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - mPlaybackParams.setSpeed(1.0f); - mPlayer.setPlayWhenReady(true); - mPlayer.setAudioTrackAndClosedCaption(true); - } - - private void doTimeShiftSeekTo(long timeMs) { - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs)); - } - - private void doTimeShiftSetPlaybackParams(PlaybackParams params) { - if (!hasEnoughBackwardBuffer() && params.getSpeed() < 1.0f) { - return; - } - mPlaybackParams = params; - float speed = mPlaybackParams.getSpeed(); - if (speed == 1.0f) { - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - doTimeShiftResume(); - } else if (mPlayer.supportSmoothTrickPlay(speed)) { - mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK); - mPlayer.setAudioTrackAndClosedCaption(false); - mPlayer.startSmoothTrickplay(mPlaybackParams); - mHandler.sendEmptyMessageDelayed( - MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS); - } else { - mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR); - if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) { - mPlayer.setAudioTrackAndClosedCaption(false); - mPlayer.setPlayWhenReady(false); - // Initiate trickplay - mHandler.sendMessage( - mHandler.obtainMessage( - MSG_TRICKPLAY_BY_SEEK, - (int) - (mPlayer.getCurrentPosition() - + speed * getTrickPlaySeekIntervalMs()), - 0)); - } - } - } - - private EitItem getCurrentProgram() { - if (mPrograms == null || mPrograms.isEmpty()) { - return null; - } - if (mChannel.getType() == Channel.TYPE_FILE) { - // For the playback from the local file, we use the first one from the given program. - EitItem first = mPrograms.get(0); - if (first != null - && (mProgram == null - || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) { - return first; - } - return null; - } - long currentTimeMs = getCurrentPosition(); - for (EitItem item : mPrograms) { - if (item.getStartTimeUtcMillis() <= currentTimeMs - && item.getEndTimeUtcMillis() >= currentTimeMs) { - return item; - } - } - return null; - } - - private void doParentalControls() { - boolean isParentalControlsEnabled = mTvInputManager.isParentalControlsEnabled(); - if (isParentalControlsEnabled) { - TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked(); - if (DEBUG) { - if (blockContentRating != null) { - Log.d( - TAG, - "Check parental controls: blocked by content rating - " - + blockContentRating); - } else { - Log.d(TAG, "Check parental controls: available"); - } - } - updateChannelBlockStatus(blockContentRating != null, blockContentRating); - } else { - if (DEBUG) { - Log.d(TAG, "Check parental controls: available"); - } - updateChannelBlockStatus(false, null); - } - } - - private void doDiscoverCaptionServiceNumber(int serviceNumber) { - int index = mCaptionTrackMap.indexOfKey(serviceNumber); - if (index < 0) { - AtscCaptionTrack captionTrack = new AtscCaptionTrack(); - captionTrack.serviceNumber = serviceNumber; - captionTrack.wideAspectRatio = false; - captionTrack.easyReader = false; - mCaptionTrackMap.put(serviceNumber, captionTrack); - mTvTracks.add( - new TvTrackInfo.Builder( - TvTrackInfo.TYPE_SUBTITLE, - SUBTITLE_TRACK_PREFIX + serviceNumber) - .build()); - mSession.notifyTracksChanged(mTvTracks); - } - } - - private TvContentRating getContentRatingOfCurrentProgramBlocked() { - EitItem currentProgram = getCurrentProgram(); - if (currentProgram == null) { - return null; - } - TvContentRating[] ratings = - mTvContentRatingCache.getRatings(currentProgram.getContentRating()); - if (ratings == null || ratings.length == 0) { - ratings = new TvContentRating[] {TvContentRating.UNRATED}; - } - for (TvContentRating rating : ratings) { - if (!Objects.equals(mUnblockedContentRating, rating) - && mTvInputManager.isRatingBlocked(rating)) { - return rating; - } - } - return null; - } - - private void updateChannelBlockStatus(boolean channelBlocked, TvContentRating contentRating) { - if (mChannelBlocked == channelBlocked) { - return; - } - mChannelBlocked = channelBlocked; - if (mChannelBlocked) { - clearCallbacksAndMessagesSafely(); - stopPlayback(true); - resetTvTracks(); - if (contentRating != null) { - mSession.notifyContentBlocked(contentRating); - } - mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); - } else { - clearCallbacksAndMessagesSafely(); - resetPlayback(); - mSession.notifyContentAllowed(); - mHandler.sendEmptyMessageDelayed( - MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); - mHandler.removeMessages(MSG_CHECK_SIGNAL); - mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS); - } - } - - @WorkerThread - private void clearCallbacksAndMessagesSafely() { - // If MSG_RELEASE is removed, TunerSessionWorker will hang forever. - // Do not remove messages, after release is requested from MainThread. - synchronized (mReleaseLock) { - if (!mReleaseRequested) { - mHandler.removeCallbacksAndMessages(null); - } - } - } - - private boolean hasEnoughBackwardBuffer() { - return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS - >= mBufferStartTimeMs - mRecordStartTimeMs; - } - - private void notifyVideoUnavailable(final int reason) { - mReportedWeakSignal = (reason == TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); - if (mSession != null) { - mSession.notifyVideoUnavailable(reason); - } - } - - private void notifyVideoAvailable() { - mReportedWeakSignal = false; - if (mSession != null) { - mSession.notifyVideoAvailable(); - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java deleted file mode 100644 index f014d568..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.tuner.tvinput; - -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.net.Uri; -import android.os.AsyncTask; -import android.util.Log; -import com.android.tv.TvApplication; -import com.android.tv.dvr.DvrStorageStatusManager; -import com.android.tv.util.Utils; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * Creates {@link JobService} to clean up recorded program files which are not referenced from - * database. - */ -public class TunerStorageCleanUpService extends JobService { - private static final String TAG = "TunerStorageCleanUpService"; - - private CleanUpStorageTask mTask; - - @Override - public void onCreate() { - if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) { - Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); - this.stopSelf(); - return; - } - TvApplication.setCurrentRunningProcess(this, false); - super.onCreate(); - mTask = new CleanUpStorageTask(this, this); - } - - @Override - public boolean onStartJob(JobParameters params) { - mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - return false; - } - - /** - * Cleans up recorded program files which are not referenced from database. Cleaning up will be - * done periodically. - */ - public static class CleanUpStorageTask extends AsyncTask { - private static final String[] mProjection = { - TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI - }; - private static final long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1); - - private final Context mContext; - private final DvrStorageStatusManager mDvrStorageStatusManager; - private final JobService mJobService; - private final ContentResolver mContentResolver; - - /** - * Creates a recurring storage cleaning task. - * - * @param context {@link Context} - * @param jobService {@link JobService} - */ - public CleanUpStorageTask(Context context, JobService jobService) { - mContext = context; - mDvrStorageStatusManager = - TvApplication.getSingletons(mContext).getDvrStorageStatusManager(); - mJobService = jobService; - mContentResolver = mContext.getContentResolver(); - } - - private Set getRecordedProgramsDirs() { - try (Cursor c = - mContentResolver.query( - TvContract.RecordedPrograms.CONTENT_URI, - mProjection, - null, - null, - null)) { - if (c == null) { - return null; - } - Set recordedProgramDirs = new HashSet<>(); - while (c.moveToNext()) { - String packageName = c.getString(0); - String dataUriString = c.getString(1); - if (dataUriString == null) { - continue; - } - Uri dataUri = Uri.parse(dataUriString); - if (!Utils.isInBundledPackageSet(packageName) - || dataUri == null - || dataUri.getPath() == null - || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { - continue; - } - File recordedProgramDir = new File(dataUri.getPath()); - try { - recordedProgramDirs.add(recordedProgramDir.getCanonicalPath()); - } catch (IOException | SecurityException e) { - } - } - return recordedProgramDirs; - } - } - - @Override - protected JobParameters[] doInBackground(JobParameters... params) { - if (mDvrStorageStatusManager.getDvrStorageStatus() - == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { - return params; - } - File dvrRecordingDir = mDvrStorageStatusManager.getRecordingRootDataDirectory(); - if (dvrRecordingDir == null || !dvrRecordingDir.isDirectory()) { - return params; - } - Set recordedProgramDirs = getRecordedProgramsDirs(); - if (recordedProgramDirs == null) { - return params; - } - File[] files = dvrRecordingDir.listFiles(); - if (files == null || files.length == 0) { - return params; - } - for (File recordingDir : files) { - try { - if (!recordedProgramDirs.contains(recordingDir.getCanonicalPath())) { - long lastModified = recordingDir.lastModified(); - long now = System.currentTimeMillis(); - if (lastModified != 0 && lastModified < now - ELAPSED_MILLIS_TO_DELETE) { - // To prevent current recordings from being deleted, - // deletes recordings which was not modified for long enough time. - Utils.deleteDirOrFile(recordingDir); - } - } - } catch (IOException | SecurityException e) { - // would not happen - } - } - return params; - } - - @Override - protected void onPostExecute(JobParameters[] params) { - for (JobParameters param : params) { - mJobService.jobFinished(param, false); - } - } - } -} diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java deleted file mode 100644 index a1596e3b..00000000 --- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.tvinput; - -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.media.tv.TvContract; -import android.media.tv.TvInputService; -import android.util.Log; -import com.android.tv.TvApplication; -import com.android.tv.common.feature.CommonFeatures; -import com.google.android.exoplayer.audio.AudioCapabilities; -import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; -import java.util.Collections; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.TimeUnit; - -/** {@link TunerTvInputService} serves TV channels coming from a tuner device. */ -public class TunerTvInputService extends TvInputService - implements AudioCapabilitiesReceiver.Listener { - private static final String TAG = "TunerTvInputService"; - private static final boolean DEBUG = false; - - private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100; - - // WeakContainer for {@link TvInputSessionImpl} - private final Set mTunerSessions = Collections.newSetFromMap(new WeakHashMap<>()); - private ChannelDataManager mChannelDataManager; - private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; - private AudioCapabilities mAudioCapabilities; - - @Override - public void onCreate() { - if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) { - Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); - this.stopSelf(); - return; - } - TvApplication.setCurrentRunningProcess(this, false); - super.onCreate(); - if (DEBUG) Log.d(TAG, "onCreate"); - mChannelDataManager = new ChannelDataManager(getApplicationContext()); - mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this); - mAudioCapabilitiesReceiver.register(); - if (CommonFeatures.DVR.isEnabled(this)) { - JobScheduler jobScheduler = - (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); - JobInfo pendingJob = jobScheduler.getPendingJob(DVR_STORAGE_CLEANUP_JOB_ID); - if (pendingJob != null) { - // storage cleaning job is already scheduled. - } else { - JobInfo job = - new JobInfo.Builder( - DVR_STORAGE_CLEANUP_JOB_ID, - new ComponentName(this, TunerStorageCleanUpService.class)) - .setPersisted(true) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) - .build(); - jobScheduler.schedule(job); - } - } - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy"); - super.onDestroy(); - mChannelDataManager.release(); - mAudioCapabilitiesReceiver.unregister(); - } - - @Override - public RecordingSession onCreateRecordingSession(String inputId) { - return new TunerRecordingSession(this, inputId, mChannelDataManager); - } - - @Override - public Session onCreateSession(String inputId) { - if (DEBUG) Log.d(TAG, "onCreateSession"); - try { - final TunerSession session = new TunerSession(this, mChannelDataManager); - mTunerSessions.add(session); - session.setAudioCapabilities(mAudioCapabilities); - session.setOverlayViewEnabled(true); - return session; - } catch (RuntimeException e) { - // There are no available DVB devices. - Log.e(TAG, "Creating a session for " + inputId + " failed.", e); - return null; - } - } - - @Override - public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) { - mAudioCapabilities = audioCapabilities; - for (TunerSession session : mTunerSessions) { - if (!session.isReleased()) { - session.setAudioCapabilities(audioCapabilities); - } - } - } - - public static String getInputId(Context context) { - return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class)); - } -} diff --git a/src/com/android/tv/tuner/util/ByteArrayBuffer.java b/src/com/android/tv/tuner/util/ByteArrayBuffer.java deleted file mode 100644 index c3e38443..00000000 --- a/src/com/android/tv/tuner/util/ByteArrayBuffer.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/ByteArrayBuffer.java $ - * $Revision: 496070 $ - * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.tv.tuner.util; - -/** An expandable byte buffer built on byte array. */ -public final class ByteArrayBuffer { - - private byte[] buffer; - private int len; - - public ByteArrayBuffer(int capacity) { - super(); - if (capacity < 0) { - throw new IllegalArgumentException("Buffer capacity may not be negative"); - } - this.buffer = new byte[capacity]; - } - - private void expand(int newlen) { - byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)]; - System.arraycopy(this.buffer, 0, newbuffer, 0, this.len); - this.buffer = newbuffer; - } - - public void append(final byte[] b, int off, int len) { - if (b == null) { - return; - } - if ((off < 0) - || (off > b.length) - || (len < 0) - || ((off + len) < 0) - || ((off + len) > b.length)) { - throw new IndexOutOfBoundsException(); - } - if (len == 0) { - return; - } - int newlen = this.len + len; - if (newlen > this.buffer.length) { - expand(newlen); - } - System.arraycopy(b, off, this.buffer, this.len, len); - this.len = newlen; - } - - public void append(int b) { - int newlen = this.len + 1; - if (newlen > this.buffer.length) { - expand(newlen); - } - this.buffer[this.len] = (byte) b; - this.len = newlen; - } - - public void append(final char[] b, int off, int len) { - if (b == null) { - return; - } - if ((off < 0) - || (off > b.length) - || (len < 0) - || ((off + len) < 0) - || ((off + len) > b.length)) { - throw new IndexOutOfBoundsException(); - } - if (len == 0) { - return; - } - int oldlen = this.len; - int newlen = oldlen + len; - if (newlen > this.buffer.length) { - expand(newlen); - } - for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) { - this.buffer[i2] = (byte) b[i1]; - } - this.len = newlen; - } - - public void clear() { - this.len = 0; - } - - public byte[] toByteArray() { - byte[] b = new byte[this.len]; - if (this.len > 0) { - System.arraycopy(this.buffer, 0, b, 0, this.len); - } - return b; - } - - public int byteAt(int i) { - return this.buffer[i]; - } - - public int capacity() { - return this.buffer.length; - } - - public int length() { - return this.len; - } - - public byte[] buffer() { - return this.buffer; - } - - public void setLength(int len) { - if (len < 0 || len > this.buffer.length) { - throw new IndexOutOfBoundsException(); - } - this.len = len; - } - - public boolean isEmpty() { - return this.len == 0; - } - - public boolean isFull() { - return this.len == this.buffer.length; - } -} diff --git a/src/com/android/tv/tuner/util/ConvertUtils.java b/src/com/android/tv/tuner/util/ConvertUtils.java deleted file mode 100644 index 4b7fbdae..00000000 --- a/src/com/android/tv/tuner/util/ConvertUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -/** Utility class for converting date and time. */ -public class ConvertUtils { - // Time diff between 1.1.1970 00:00:00 and 6.1.1980 00:00:00 - private static final long DIFF_BETWEEN_UNIX_EPOCH_AND_GPS = 315964800; - - private ConvertUtils() {} - - public static long convertGPSTimeToUnixEpoch(long gpsTime) { - return gpsTime + DIFF_BETWEEN_UNIX_EPOCH_AND_GPS; - } - - public static long convertUnixEpochToGPSTime(long epochTime) { - return epochTime - DIFF_BETWEEN_UNIX_EPOCH_AND_GPS; - } -} diff --git a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java b/src/com/android/tv/tuner/util/GlobalSettingsUtils.java deleted file mode 100644 index 98463f3b..00000000 --- a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -import android.content.Context; -import android.provider.Settings; - -/** Utility class that get information of global settings. */ -public class GlobalSettingsUtils { - // Since global surround setting is hided, add the related variable here for checking surround - // sound setting when the audio is unavailable. Remove this workaround after b/31254857 fixed. - private static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output"; - public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; - - private GlobalSettingsUtils() {} - - public static int getEncodedSurroundOutputSettings(Context context) { - return Settings.Global.getInt(context.getContentResolver(), ENCODED_SURROUND_OUTPUT, 0); - } -} diff --git a/src/com/android/tv/tuner/util/Ints.java b/src/com/android/tv/tuner/util/Ints.java deleted file mode 100644 index 74e0ca8d..00000000 --- a/src/com/android/tv/tuner/util/Ints.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.android.tv.tuner.util; - -import java.util.ArrayList; -import java.util.List; - -/** Static utility methods pertaining to int primitives. (Referred Guava's Ints class) */ -public class Ints { - private Ints() {} - - public static int[] toArray(List integerList) { - int[] intArray = new int[integerList.size()]; - int i = 0; - for (Integer data : integerList) { - intArray[i++] = data; - } - return intArray; - } - - public static List asList(int[] intArray) { - List integerList = new ArrayList<>(intArray.length); - for (int data : intArray) { - integerList.add(data); - } - return integerList; - } -} diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/src/com/android/tv/tuner/util/PostalCodeUtils.java deleted file mode 100644 index 502c5648..00000000 --- a/src/com/android/tv/tuner/util/PostalCodeUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -import android.content.Context; -import android.location.Address; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.util.LocationUtils; -import java.io.IOException; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; - -/** A utility class to update, get, and set the last known postal or zip code. */ -public class PostalCodeUtils { - private static final String TAG = "PostalCodeUtils"; - - // Postcode formats, where A signifies a letter and 9 a digit: - // US zip code format: 99999 - private static final String POSTCODE_REGEX_US = "^(\\d{5})"; - // UK postcode district formats: A9, A99, AA9, AA99 - // Full UK postcode format: Postcode District + space + 9AA - // Should be able to handle both postcode district and full postcode - private static final String POSTCODE_REGEX_GB = - "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$"; - private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode - - private static final Map REGION_PATTERN = new HashMap<>(); - private static final Map REGION_MAX_LENGTH = new HashMap<>(); - - static { - REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US)); - REGION_PATTERN.put( - Locale.UK.getCountry(), - Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR)); - REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5); - REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8); - } - - // The longest postcode number is 10-character-long. - // Use a larger number to accommodate future changes. - private static final int DEFAULT_MAX_LENGTH = 16; - - /** Returns {@code true} if postal code has been changed */ - public static boolean updatePostalCode(Context context) - throws IOException, SecurityException, NoPostalCodeException { - String postalCode = getPostalCode(context); - String lastPostalCode = getLastPostalCode(context); - if (TextUtils.isEmpty(postalCode)) { - if (TextUtils.isEmpty(lastPostalCode)) { - throw new NoPostalCodeException(); - } - } else if (!TextUtils.equals(postalCode, lastPostalCode)) { - setLastPostalCode(context, postalCode); - return true; - } - return false; - } - - /** - * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or - * input by users. - */ - public static String getLastPostalCode(Context context) { - return TunerPreferences.getLastPostalCode(context); - } - - /** - * Sets the last stored postal or zip code. This method will overwrite the value written by - * calling {@link #updatePostalCode(Context)}. - */ - public static void setLastPostalCode(Context context, String postalCode) { - Log.i(TAG, "Set Postal Code:" + postalCode); - TunerPreferences.setLastPostalCode(context, postalCode); - } - - @Nullable - private static String getPostalCode(Context context) throws IOException, SecurityException { - Address address = LocationUtils.getCurrentAddress(context); - if (address != null) { - Log.i( - TAG, - "Current country and postal code is " - + address.getCountryName() - + ", " - + address.getPostalCode()); - return address.getPostalCode(); - } - return null; - } - - /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ - public static class NoPostalCodeException extends Exception { - public NoPostalCodeException() {} - } - - /** - * Checks whether a postcode matches the format of the specific region. - * - * @return {@code false} if the region is supported and the postcode doesn't match; {@code true} - * otherwise - */ - public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) { - Pattern pattern = REGION_PATTERN.get(region.toUpperCase()); - return pattern == null || pattern.matcher(postcode).matches(); - } - - /** - * Gets the largest possible postcode length in the region. - * - * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH} - * otherwise - */ - public static int getRegionMaxLength(Context context) { - Integer maxLength = - REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); - return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; - } -} diff --git a/src/com/android/tv/tuner/util/StatusTextUtils.java b/src/com/android/tv/tuner/util/StatusTextUtils.java deleted file mode 100644 index 84e2fc5a..00000000 --- a/src/com/android/tv/tuner/util/StatusTextUtils.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -import java.util.Locale; - -/** Utility class for tuner status messages. */ -public class StatusTextUtils { - private static final int PACKETS_PER_SEC_YELLOW = 1500; - private static final int PACKETS_PER_SEC_RED = 1000; - private static final int AUDIO_POSITION_MS_RATE_DIFF_YELLOW = 100; - private static final int AUDIO_POSITION_MS_RATE_DIFF_RED = 200; - private static final String COLOR_RED = "red"; - private static final String COLOR_YELLOW = "yellow"; - private static final String COLOR_GREEN = "green"; - private static final String COLOR_GRAY = "gray"; - - private StatusTextUtils() {} - - /** - * Returns tuner status warning message in HTML. - * - *

This is only called for debuging and always shown in english. - */ - public static String getStatusWarningInHTML( - long packetsPerSec, - int videoFrameDrop, - int bytesInQueue, - long audioPositionUs, - long audioPositionUsRate, - long audioPtsUs, - long audioPtsUsRate, - long videoPtsUs, - long videoPtsUsRate) { - StringBuffer buffer = new StringBuffer(); - - // audioPosition should go in rate of 1000ms. - long audioPositionMsRate = audioPositionUsRate / 1000; - String audioPositionColor; - if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_RED) { - audioPositionColor = COLOR_RED; - } else if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_YELLOW) { - audioPositionColor = COLOR_YELLOW; - } else { - audioPositionColor = COLOR_GRAY; - } - buffer.append(String.format(Locale.US, "", audioPositionColor)); - buffer.append( - String.format( - Locale.US, - "audioPositionMs: %d (%d)
", - audioPositionUs / 1000, - audioPositionMsRate)); - buffer.append("
\n"); - buffer.append(""); - buffer.append( - String.format( - Locale.US, - "audioPtsMs: %d (%d, %d)
", - audioPtsUs / 1000, - audioPtsUsRate / 1000, - (audioPtsUs - audioPositionUs) / 1000)); - buffer.append( - String.format( - Locale.US, - "videoPtsMs: %d (%d, %d)
", - videoPtsUs / 1000, - videoPtsUsRate / 1000, - (videoPtsUs - audioPositionUs) / 1000)); - buffer.append("
\n"); - - appendStatusLine(buffer, "KbytesInQueue", bytesInQueue / 1000, 1, 10); - buffer.append("
"); - appendErrorStatusLine(buffer, "videoFrameDrop", videoFrameDrop, 0, 2); - buffer.append("
"); - appendStatusLine( - buffer, - "packetsPerSec", - packetsPerSec, - PACKETS_PER_SEC_RED, - PACKETS_PER_SEC_YELLOW); - return buffer.toString(); - } - - /** Returns audio unavailable warning message in HTML. */ - public static String getAudioWarningInHTML(String msg) { - return String.format("%s\n", COLOR_YELLOW, msg); - } - - private static void appendStatusLine( - StringBuffer buffer, String factorName, long value, int minRed, int minYellow) { - buffer.append(""); - buffer.append(factorName); - buffer.append(" : "); - buffer.append(value); - buffer.append(""); - } - - private static void appendErrorStatusLine( - StringBuffer buffer, String factorName, int value, int minGreen, int minYellow) { - buffer.append(""); - buffer.append(factorName); - buffer.append(" : "); - buffer.append(value); - buffer.append(""); - } -} diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java deleted file mode 100644 index 5c23c797..00000000 --- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -import android.util.Log; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Proxy class that gives an access to a hidden API {@link android.os.SystemProperties#getBoolean}. - */ -public class SystemPropertiesProxy { - private static final String TAG = "SystemPropertiesProxy"; - - private SystemPropertiesProxy() {} - - public static boolean getBoolean(String key, boolean def) throws IllegalArgumentException { - try { - Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); - Method getBooleanMethod = - SystemPropertiesClass.getDeclaredMethod( - "getBoolean", String.class, boolean.class); - getBooleanMethod.setAccessible(true); - return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def); - } catch (InvocationTargetException - | IllegalAccessException - | NoSuchMethodException - | ClassNotFoundException e) { - Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e); - } - return def; - } - - public static int getInt(String key, int def) throws IllegalArgumentException { - try { - Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); - Method getIntMethod = - SystemPropertiesClass.getDeclaredMethod("getInt", String.class, int.class); - getIntMethod.setAccessible(true); - return (int) getIntMethod.invoke(SystemPropertiesClass, key, def); - } catch (InvocationTargetException - | IllegalAccessException - | NoSuchMethodException - | ClassNotFoundException e) { - Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e); - } - return def; - } - - public static String getString(String key, String def) throws IllegalArgumentException { - try { - Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); - Method getIntMethod = - SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class); - getIntMethod.setAccessible(true); - return (String) getIntMethod.invoke(SystemPropertiesClass, key, def); - } catch (InvocationTargetException - | IllegalAccessException - | NoSuchMethodException - | ClassNotFoundException e) { - Log.e(TAG, "Failed to invoke SystemProperties.get()", e); - } - return def; - } -} diff --git a/src/com/android/tv/tuner/util/TisConfiguration.java b/src/com/android/tv/tuner/util/TisConfiguration.java deleted file mode 100644 index 8f1326ce..00000000 --- a/src/com/android/tv/tuner/util/TisConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.android.tv.tuner.util; - -import android.content.Context; - -/** A helper class of tuner configuration. */ -public class TisConfiguration { - private static final String LC_PACKAGE_NAME = "com.android.tv"; - - public static boolean isPackagedWithLiveChannels(Context context) { - return (LC_PACKAGE_NAME.equals(context.getPackageName())); - } - - public static boolean isInternalTunerTvInput(Context context) { - return (!LC_PACKAGE_NAME.equals(context.getPackageName())); - } - - public static int getTunerHwDeviceId(Context context) { - return 0; // FIXME: Make it OEM configurable - } -} diff --git a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java deleted file mode 100644 index 9a3eec2b..00000000 --- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.tuner.util; - -import android.annotation.TargetApi; -import android.content.ComponentName; -import android.content.Context; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; -import android.os.AsyncTask; -import android.os.Build; -import android.support.annotation.Nullable; -import android.util.Log; -import android.util.Pair; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.tuner.R; -import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.tvinput.TunerTvInputService; - -/** Utility class for providing tuner input info. */ -public class TunerInputInfoUtils { - private static final String TAG = "TunerInputInfoUtils"; - private static final boolean DEBUG = false; - - /** Builds tuner input's info. */ - @Nullable - @TargetApi(Build.VERSION_CODES.N) - public static TvInputInfo buildTunerInputInfo(Context context) { - Pair tunerTypeAndCount = TunerHal.getTunerTypeAndCount(context); - if (tunerTypeAndCount.first == null || tunerTypeAndCount.second == 0) { - return null; - } - int inputLabelId = 0; - switch (tunerTypeAndCount.first) { - case TunerHal.TUNER_TYPE_BUILT_IN: - inputLabelId = R.string.bt_app_name; - break; - case TunerHal.TUNER_TYPE_USB: - inputLabelId = R.string.ut_app_name; - break; - case TunerHal.TUNER_TYPE_NETWORK: - inputLabelId = R.string.nt_app_name; - break; - } - try { - TvInputInfo.Builder builder = - new TvInputInfo.Builder( - context, new ComponentName(context, TunerTvInputService.class)); - return builder.setLabel(inputLabelId) - .setCanRecord(CommonFeatures.DVR.isEnabled(context)) - .setTunerCount(tunerTypeAndCount.second) - .build(); - } catch (IllegalArgumentException | NullPointerException e) { - // TunerTvInputService is not enabled. - return null; - } - } - - /** - * Updates tuner input's info. - * - * @param context {@link Context} instance - */ - public static void updateTunerInputInfo(Context context) { - final Context appContext = context.getApplicationContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - new AsyncTask() { - @Override - protected TvInputInfo doInBackground(Void... params) { - if (DEBUG) Log.d(TAG, "updateTunerInputInfo()"); - return buildTunerInputInfo(appContext); - } - - @Override - @TargetApi(Build.VERSION_CODES.N) - protected void onPostExecute(TvInputInfo info) { - if (info != null) { - ((TvInputManager) appContext.getSystemService(Context.TV_INPUT_SERVICE)) - .updateTvInputInfo(info); - if (DEBUG) { - Log.d( - TAG, - "TvInputInfo [" - + info.loadLabel(appContext) - + "] updated: " - + info.toString()); - } - } else { - if (DEBUG) { - Log.d(TAG, "Updating tuner input info failed. Input is not ready yet."); - } - } - } - }.execute(); - } - } -} diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java index 5454132a..b2be9f02 100644 --- a/src/com/android/tv/ui/AppLayerTvView.java +++ b/src/com/android/tv/ui/AppLayerTvView.java @@ -21,8 +21,8 @@ import android.media.tv.TvView; import android.util.AttributeSet; import android.view.SurfaceView; import android.view.View; -import com.android.tv.util.Debug; -import com.android.tv.util.Utils; +import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.Debug; /** * A TvView class for application layer when multiple windows are being used in the app. @@ -53,7 +53,7 @@ public class AppLayerTvView extends TvView { public void onViewAdded(View child) { if (child instanceof SurfaceView) { // Note: See b/29118070 for detail. - ((SurfaceView) child).setSecure(!Utils.isDeveloper()); + ((SurfaceView) child).setSecure(!CommonUtils.isDeveloper()); } super.onViewAdded(child); } diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java index c8c3dc86..df487bb5 100644 --- a/src/com/android/tv/ui/ChannelBannerView.java +++ b/src/com/android/tv/ui/ChannelBannerView.java @@ -47,7 +47,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; @@ -213,12 +213,12 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage mMainActivity, R.animator.channel_banner_program_description_fade_out); if (CommonFeatures.DVR.isEnabled(mMainActivity)) { - mDvrManager = TvApplication.getSingletons(mMainActivity).getDvrManager(); + mDvrManager = TvSingletons.getSingletons(mMainActivity).getDvrManager(); } else { mDvrManager = null; } mContentRatingsManager = - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getTvInputManagerHelper() .getContentRatingsManager(); diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java index 0c3613f6..5919dbf1 100644 --- a/src/com/android/tv/ui/KeypadChannelSwitchView.java +++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java @@ -37,12 +37,12 @@ import android.widget.ListView; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +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.util.DurationTimer; import java.util.ArrayList; import java.util.List; @@ -116,7 +116,7 @@ public class KeypadChannelSwitchView extends LinearLayout super(context, attrs, defStyleAttr); mMainActivity = (MainActivity) context; - mTracker = TvApplication.getSingletons(context).getTracker(); + mTracker = TvSingletons.getSingletons(context).getTracker(); Resources resources = getResources(); mLayoutInflater = LayoutInflater.from(context); mShowDurationMillis = resources.getInteger(R.integer.keypad_channel_switch_show_duration); diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java index aa91aa50..2ec498a8 100644 --- a/src/com/android/tv/ui/SelectInputView.java +++ b/src/com/android/tv/ui/SelectInputView.java @@ -32,12 +32,11 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; +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.util.DurationTimer; import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; import java.util.Collections; @@ -144,9 +143,9 @@ public class SelectInputView extends VerticalGridView super(context, attrs, defStyleAttr); setAdapter(new InputListAdapter()); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mTracker = appSingletons.getTracker(); - mTvInputManagerHelper = appSingletons.getTvInputManagerHelper(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + mTracker = tvSingletons.getTracker(); + mTvInputManagerHelper = tvSingletons.getTvInputManagerHelper(); mComparator = new TvInputManagerHelper.HardwareInputComparator(context, mTvInputManagerHelper); diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index 97f7c65c..36de790f 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -57,15 +57,18 @@ import android.view.SurfaceView; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; -import com.android.tv.ApplicationSingletons; -import com.android.tv.Features; import com.android.tv.InputSessionManager; import com.android.tv.InputSessionManager.TvViewSession; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvFeatures; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; import com.android.tv.common.feature.CommonFeatures; +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; @@ -74,11 +77,8 @@ import com.android.tv.data.WatchedHistoryManager; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.recommendation.NotificationService; -import com.android.tv.util.Debug; -import com.android.tv.util.DurationTimer; import com.android.tv.util.ImageLoader; import com.android.tv.util.NetworkUtils; -import com.android.tv.util.PermissionUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import java.lang.annotation.Retention; @@ -362,7 +362,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { getContext().startActivity(intent); } }) - .setNegativeButton(android.R.string.no, null) + .setNegativeButton(android.R.string.cancel, null) .show(); } @@ -393,6 +393,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason); + break; default: // do nothing } @@ -454,17 +455,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo { super(context, attrs, defStyleAttr, defStyleRes); inflate(getContext(), R.layout.tunable_tv_view, this); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); if (CommonFeatures.DVR.isEnabled(context)) { - mInputSessionManager = appSingletons.getInputSessionManager(); + mInputSessionManager = tvSingletons.getInputSessionManager(); } else { mInputSessionManager = null; } - mInputManager = appSingletons.getTvInputManagerHelper(); + mInputManager = tvSingletons.getTvInputManagerHelper(); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context); - mTracker = appSingletons.getTracker(); + mTracker = tvSingletons.getTracker(); mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL; mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen); mBlockScreenView.addInfoFadeInAnimationListener( @@ -1081,7 +1082,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } private boolean closePipIfNeeded() { - if (Features.PICTURE_IN_PICTURE.isEnabled(getContext()) + if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(getContext()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ((Activity) getContext()).isInPictureInPictureMode() && (mScreenBlocked @@ -1152,7 +1153,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } private void updateMuteStatus() { - // Workaround: TunerTvInputService uses AC3 pass-through implementation, which disables + // Workaround: BaseTunerTvInputService uses AC3 pass-through implementation, which disables // audio tracks to enforce the mute request. We don't want to send mute request if we are // not going to block the screen to prevent the video jankiness resulted by disabling audio // track before the playback is started. In other way, we should send unmute request before @@ -1183,7 +1184,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { private boolean isBundledInput() { return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER - && Utils.isBundledInput(mInputInfo.getId()); + && CommonUtils.isBundledInput(mInputInfo.getId()); } /** Returns true if this view is faded out. */ diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java index 58ff8c2f..5daa525a 100644 --- a/src/com/android/tv/ui/TvOverlayManager.java +++ b/src/com/android/tv/ui/TvOverlayManager.java @@ -32,14 +32,13 @@ import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.ViewGroup; -import com.android.tv.ApplicationSingletons; import com.android.tv.ChannelTuner; import com.android.tv.MainActivity; import com.android.tv.MainActivity.KeyHandlerResultType; import com.android.tv.R; import com.android.tv.TimeShiftManager; -import com.android.tv.TvApplication; import com.android.tv.TvOptionsManager; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; import com.android.tv.common.feature.CommonFeatures; @@ -232,7 +231,7 @@ public class TvOverlayManager { ProgramGuideSearchFragment searchFragment) { mMainActivity = mainActivity; mChannelTuner = channelTuner; - ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity); + TvSingletons singletons = TvSingletons.getSingletons(mainActivity); mChannelDataManager = singletons.getChannelDataManager(); mInputManager = singletons.getTvInputManagerHelper(); mTvView = tvView; @@ -709,6 +708,10 @@ public class TvOverlayManager { } } + public boolean isOverlayOpened() { + return mOpenedOverlays != OVERLAY_TYPE_NONE; + } + /** Hides all the opened overlays according to the flags. */ // TODO: Add test for this method. public void hideOverlays(@HideOverlayFlag int flags) { @@ -1015,6 +1018,10 @@ public class TvOverlayManager { // Do not handle media key when any pop-ups which can handle keys are active. return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; } + if (mTvView.isScreenBlocked()) { + // Do not handle media key when screen is blocked. + return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; + } TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager(); if (!timeShiftManager.isAvailable()) { return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED; diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java index 23cd9718..7e354db3 100644 --- a/src/com/android/tv/ui/TvViewUiManager.java +++ b/src/com/android/tv/ui/TvViewUiManager.java @@ -42,8 +42,8 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; -import com.android.tv.Features; import com.android.tv.R; +import com.android.tv.TvFeatures; import com.android.tv.TvOptionsManager; import com.android.tv.data.DisplayMode; import com.android.tv.util.TvSettings; @@ -93,7 +93,7 @@ public class TvViewUiManager { mTvView.setLayoutParams(mTvViewFrame); // Smooth PIP size change, we don't change surface size when // isInPictureInPictureMode is true. - if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext) + if (!TvFeatures.PICTURE_IN_PICTURE.isEnabled(mContext) || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !((Activity) mContext).isInPictureInPictureMode())) { mTvView.setFixedSurfaceSize( diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java index ad2f13f1..8660c830 100644 --- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java +++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java @@ -28,7 +28,7 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.common.SharedPreferencesUtils; +import com.android.tv.common.util.SharedPreferencesUtils; import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; import com.android.tv.ui.OnRepeatedKeyInterceptListener; diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java index 766e206f..9d3b2450 100644 --- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java +++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java @@ -16,11 +16,20 @@ package com.android.tv.ui.sidepanel; +import android.accounts.Account; +import android.app.Activity; +import android.support.annotation.NonNull; +import android.util.Log; +import android.widget.Toast; + + + + import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; +import com.android.tv.common.CommonPreferences; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.util.Utils; +import com.android.tv.common.util.CommonUtils; import java.util.ArrayList; import java.util.List; @@ -51,7 +60,7 @@ public class DeveloperOptionFragment extends SideFragment { } }); } - if (Utils.isDeveloper()) { + if (CommonUtils.isDeveloper()) { items.add( new ActionItem(getString(R.string.dev_item_watch_history)) { @Override @@ -68,21 +77,21 @@ public class DeveloperOptionFragment extends SideFragment { @Override protected void onUpdate() { super.onUpdate(); - setChecked(TunerPreferences.getStoreTsStream(getContext())); + setChecked(CommonPreferences.getStoreTsStream(getContext())); } @Override protected void onSelected() { super.onSelected(); - TunerPreferences.setStoreTsStream(getContext(), isChecked()); + CommonPreferences.setStoreTsStream(getContext(), isChecked()); } }); - if (Utils.isDeveloper()) { + if (CommonUtils.isDeveloper()) { items.add( new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) { @Override protected void onSelected() { - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getPerformanceMonitor() .startPerformanceMonitorEventDebugActivity(getContext()); } diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java index 2552b5dd..31d00fa6 100644 --- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java @@ -16,7 +16,7 @@ package com.android.tv.ui.sidepanel; -import static com.android.tv.Features.TUNER; +import static com.android.tv.TvFeatures.TUNER; import android.app.ApplicationErrorReport; import android.content.Intent; @@ -26,13 +26,13 @@ import android.widget.Toast; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.customization.TvCustomizationManager; +import com.android.tv.TvSingletons; +import com.android.tv.common.CommonPreferences; +import com.android.tv.common.customization.CustomizationManager; +import com.android.tv.common.util.PermissionUtils; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.license.LicenseSideFragment; import com.android.tv.license.Licenses; -import com.android.tv.tuner.TunerPreferences; -import com.android.tv.util.PermissionUtils; -import com.android.tv.util.SetupUtils; import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -82,7 +82,9 @@ public class SettingsFragment extends SideFragment { items.add(customizeChannelListItem); final MainActivity activity = getMainActivity(); boolean hasNewInput = - SetupUtils.getInstance(activity).hasNewInput(activity.getTvInputManagerHelper()); + TvSingletons.getSingletons(getContext()) + .getSetupUtils() + .hasNewInput(activity.getTvInputManagerHelper()); items.add( new ActionItem( getString(R.string.settings_channel_source_item_setup), @@ -127,7 +129,7 @@ public class SettingsFragment extends SideFragment { boolean showTrickplaySetting = false; if (TUNER.isEnabled(getContext())) { for (TvInputInfo inputInfo : - TvApplication.getSingletons(getContext()) + TvSingletons.getSingletons(getContext()) .getTvInputManagerHelper() .getTvInputInfos(true, true)) { if (Utils.isInternalTvInput(getContext(), inputInfo.getId())) { @@ -137,8 +139,8 @@ public class SettingsFragment extends SideFragment { } if (showTrickplaySetting) { showTrickplaySetting = - TvCustomizationManager.getTrickplayMode(getContext()) - == TvCustomizationManager.TRICKPLAY_MODE_ENABLED; + CustomizationManager.getTrickplayMode(getContext()) + == CustomizationManager.TRICKPLAY_MODE_ENABLED; } } if (showTrickplaySetting) { @@ -152,20 +154,20 @@ public class SettingsFragment extends SideFragment { protected void onUpdate() { super.onUpdate(); boolean enabled = - TunerPreferences.getTrickplaySetting(getContext()) - != TunerPreferences.TRICKPLAY_SETTING_DISABLED; + CommonPreferences.getTrickplaySetting(getContext()) + != CommonPreferences.TRICKPLAY_SETTING_DISABLED; setChecked(enabled); } @Override protected void onSelected() { super.onSelected(); - @TunerPreferences.TrickplaySetting + @CommonPreferences.TrickplaySetting int setting = isChecked() - ? TunerPreferences.TRICKPLAY_SETTING_ENABLED - : TunerPreferences.TRICKPLAY_SETTING_DISABLED; - TunerPreferences.setTrickplaySetting(getContext(), setting); + ? CommonPreferences.TRICKPLAY_SETTING_ENABLED + : CommonPreferences.TRICKPLAY_SETTING_DISABLED; + CommonPreferences.setTrickplaySetting(getContext(), setting); } }); } diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java index 0660a6f9..2902ea7f 100644 --- a/src/com/android/tv/ui/sidepanel/SideFragment.java +++ b/src/com/android/tv/ui/sidepanel/SideFragment.java @@ -30,13 +30,13 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.HasTrackerLabel; import com.android.tv.analytics.Tracker; +import com.android.tv.common.util.DurationTimer; +import com.android.tv.common.util.SystemProperties; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ProgramDataManager; -import com.android.tv.util.DurationTimer; -import com.android.tv.util.SystemProperties; import com.android.tv.util.ViewCache; import java.util.List; @@ -85,7 +85,7 @@ public abstract class SideFragment extends Fragment implements H super.onAttach(context); mChannelDataManager = getMainActivity().getChannelDataManager(); mProgramDataManager = getMainActivity().getProgramDataManager(); - mTracker = TvApplication.getSingletons(context).getTracker(); + mTracker = TvSingletons.getSingletons(context).getTracker(); } @Override diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java index f0396bfe..d70cf97a 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java @@ -19,6 +19,8 @@ package com.android.tv.ui.sidepanel.parentalcontrols; import android.database.ContentObserver; import android.media.tv.TvContract; import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.support.v17.leanback.widget.VerticalGridView; @@ -30,6 +32,7 @@ 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.recommendation.ChannelPreviewUpdater; import com.android.tv.ui.OnRepeatedKeyInterceptListener; import com.android.tv.ui.sidepanel.ActionItem; import com.android.tv.ui.sidepanel.ChannelCheckItem; @@ -47,6 +50,7 @@ public class ChannelsBlockedFragment extends SideFragment { private final List mChannels = new ArrayList<>(); private long mLastFocusedChannelId = Channel.INVALID_ID; private int mSelectedPosition = INVALID_POSITION; + private boolean mUpdated; private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) { @Override @@ -94,6 +98,7 @@ public class ChannelsBlockedFragment extends SideFragment { .registerContentObserver( TvContract.Programs.CONTENT_URI, true, mProgramUpdateObserver); getMainActivity().startShrunkenTvView(true, true); + mUpdated = false; return view; } @@ -102,6 +107,10 @@ public class ChannelsBlockedFragment extends SideFragment { getActivity().getContentResolver().unregisterContentObserver(mProgramUpdateObserver); getChannelDataManager().applyUpdatedValuesToDb(); getMainActivity().endShrunkenTvView(); + if (VERSION.SDK_INT >= VERSION_CODES.O && mUpdated) { + ChannelPreviewUpdater.getInstance(getMainActivity()) + .updatePreviewDataForChannelsImmediately(); + } super.onDestroyView(); } @@ -185,6 +194,7 @@ public class ChannelsBlockedFragment extends SideFragment { } mBlockedChannelCount = lock ? mChannels.size() : 0; notifyItemsChanged(); + mUpdated = true; } @Override @@ -228,6 +238,7 @@ public class ChannelsBlockedFragment extends SideFragment { getChannelDataManager().updateLocked(getChannel().getId(), isChecked()); mBlockedChannelCount += isChecked() ? 1 : -1; notifyItemChanged(mLockAllItem); + mUpdated = true; } @Override diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java index 882843c2..128fcd1a 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java @@ -26,8 +26,8 @@ import android.widget.CompoundButton; import android.widget.ImageView; import com.android.tv.MainActivity; import com.android.tv.R; +import com.android.tv.common.experiments.Experiments; import com.android.tv.dialog.WebDialogFragment; -import com.android.tv.experiments.Experiments; import com.android.tv.license.LicenseUtils; import com.android.tv.parental.ContentRatingSystem; import com.android.tv.parental.ContentRatingSystem.Rating; diff --git a/src/com/android/tv/util/AccountHelper.java b/src/com/android/tv/util/AccountHelper.java deleted file mode 100644 index a3e6ad58..00000000 --- a/src/com/android/tv/util/AccountHelper.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.accounts.Account; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; - -/** Helper methods for getting and selecting a user account. */ -public class AccountHelper { - private static final String TAG = "AccountHelper"; - private static final boolean DEBUG = false; - private static final String SELECTED_ACCOUNT = "android.tv.livechannels.selected_account"; - - private final Context mContext; - private final SharedPreferences mDefaultPreferences; - - @Nullable private Account mSelectedAccount; - - public AccountHelper(Context context) { - mContext = context.getApplicationContext(); - mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - } - - /** Returns the currently selected account or {@code null} if none is selected. */ - @Nullable - public Account getSelectedAccount() { - String accountId = mDefaultPreferences.getString(SELECTED_ACCOUNT, null); - if (accountId == null) { - return null; - } - if (mSelectedAccount == null || !mSelectedAccount.name.equals((accountId))) { - mSelectedAccount = null; - for (Account account : getEligibleAccounts()) { - if (account.name.equals(accountId)) { - mSelectedAccount = account; - break; - } - } - } - return mSelectedAccount; - } - - /** Returns all eligible accounts . */ - private Account[] getEligibleAccounts() { - return new Account[0]; - } - - /** - * Selects the first account available. - * - * @return selected account or {@code null} if none is selected. - */ - @Nullable - public Account selectFirstAccount() { - Account account = getFirstEligibleAccount(); - if (account != null) { - selectAccount(account); - } - return account; - } - - /** - * Gets the first account eligible. - * - * @return first account or {@code null} if none is eligible. - */ - @Nullable - public Account getFirstEligibleAccount() { - Account[] accounts = getEligibleAccounts(); - return accounts.length == 0 ? null : accounts[0]; - } - - /** Sets the given account as the selected account. */ - private void selectAccount(Account account) { - SharedPreferences defaultPreferences = - PreferenceManager.getDefaultSharedPreferences(mContext); - defaultPreferences.edit().putString(SELECTED_ACCOUNT, account.name).commit(); - } -} diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java index 376fcc70..b575df53 100644 --- a/src/com/android/tv/util/AsyncDbTask.java +++ b/src/com/android/tv/util/AsyncDbTask.java @@ -28,6 +28,7 @@ import android.support.annotation.WorkerThread; import android.util.Log; import android.util.Range; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.concurrent.NamedThreadFactory; import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.dvr.data.RecordedProgram; @@ -47,6 +48,7 @@ import java.util.concurrent.RejectedExecutionException; * @param the type of the progress units published during the background computation. * @param the type of the result of the background computation. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public abstract class AsyncDbTask extends AsyncTask { private static final String TAG = "AsyncDbTask"; @@ -149,7 +151,7 @@ public abstract class AsyncDbTask return null; } } catch (Exception e) { - SoftPreconditions.warn(TAG, null, "Error querying " + this, e); + SoftPreconditions.warn(TAG, null, e, "Error querying " + this); return null; } } diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/BitmapUtils.java index 6902a6fe..4c67d934 100644 --- a/src/com/android/tv/util/BitmapUtils.java +++ b/src/com/android/tv/util/BitmapUtils.java @@ -29,6 +29,7 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import com.android.tv.common.util.NetworkTrafficTags; import java.io.BufferedInputStream; import java.io.Closeable; import java.io.IOException; diff --git a/src/com/android/tv/util/Clock.java b/src/com/android/tv/util/Clock.java deleted file mode 100644 index 0004a669..00000000 --- a/src/com/android/tv/util/Clock.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.util; - -import android.os.SystemClock; - -/** - * An interface through which system clocks can be read. The {@link #SYSTEM} implementation must be - * used for all non-test cases. - */ -public interface Clock { - /** - * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. See {@link - * System#currentTimeMillis()}. - */ - long currentTimeMillis(); - - /** - * Returns milliseconds since boot, including time spent in sleep. - * - * @see SystemClock#elapsedRealtime() - */ - long elapsedRealtime(); - - /** - * Waits a given number of milliseconds (of uptimeMillis) before returning. - * - * @param ms to sleep before returning, in milliseconds of uptime. - * @see SystemClock#sleep(long) - */ - void sleep(long ms); - - /** The default implementation of Clock. */ - Clock SYSTEM = - new Clock() { - @Override - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - @Override - public long elapsedRealtime() { - return SystemClock.elapsedRealtime(); - } - - @Override - public void sleep(long ms) { - SystemClock.sleep(ms); - } - }; -} diff --git a/src/com/android/tv/util/Debug.java b/src/com/android/tv/util/Debug.java deleted file mode 100644 index 422a61e3..00000000 --- a/src/com/android/tv/util/Debug.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** A class only for help developers. */ -public class Debug { - /** - * A threshold of start up time, when the start up time of Live TV is more than it, a warning - * will show to the developer. - */ - public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6); - /** Tag for measuring start up time of Live TV. */ - public static final String TAG_START_UP_TIMER = "start_up_timer"; - - /** A global map for duration timers. */ - private static final Map sTimerMap = new HashMap<>(); - - /** Returns the global duration timer by tag. */ - public static DurationTimer getTimer(String tag) { - if (sTimerMap.get(tag) != null) { - return sTimerMap.get(tag); - } - DurationTimer timer = new DurationTimer(tag, true); - sTimerMap.put(tag, timer); - return timer; - } - - /** Removes the global duration timer by tag. */ - public static DurationTimer removeTimer(String tag) { - return sTimerMap.remove(tag); - } -} diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/util/DurationTimer.java deleted file mode 100644 index 6aabf37b..00000000 --- a/src/com/android/tv/util/DurationTimer.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.os.SystemClock; -import android.util.Log; -import com.android.tv.common.BuildConfig; - -/** Times a duration. */ -public final class DurationTimer { - private static final String TAG = "DurationTimer"; - public static final long TIME_NOT_SET = -1; - - private long mStartTimeMs = TIME_NOT_SET; - private String mTag = TAG; - private boolean mLogEngOnly; - - public DurationTimer() {} - - public DurationTimer(String tag, boolean logEngOnly) { - mTag = tag; - mLogEngOnly = logEngOnly; - } - - /** Returns true if the timer is running. */ - public boolean isRunning() { - return mStartTimeMs != TIME_NOT_SET; - } - - /** Start the timer. */ - public void start() { - mStartTimeMs = SystemClock.elapsedRealtime(); - } - - /** Returns true if timer is started. */ - public boolean isStarted() { - return mStartTimeMs != TIME_NOT_SET; - } - - /** - * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not - * running. - */ - public long getDuration() { - return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET; - } - - /** - * Stops the timer and resets its value to {@link #TIME_NOT_SET}. - * - * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not - * running. - */ - public long reset() { - long duration = getDuration(); - mStartTimeMs = TIME_NOT_SET; - return duration; - } - - /** Adds information and duration time to the log. */ - public void log(String message) { - if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) { - Log.i(mTag, message + " : " + getDuration() + "ms"); - } - } -} diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java index 9b4d2a70..32ac89f0 100644 --- a/src/com/android/tv/util/ImageLoader.java +++ b/src/com/android/tv/util/ImageLoader.java @@ -31,6 +31,7 @@ import android.support.annotation.WorkerThread; 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 java.lang.ref.WeakReference; import java.util.HashMap; diff --git a/src/com/android/tv/util/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java deleted file mode 100644 index a960c616..00000000 --- a/src/com/android/tv/util/LocationUtils.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import android.content.Context; -import android.location.Address; -import android.location.Geocoder; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.text.TextUtils; -import android.util.Log; -import com.android.tv.tuner.util.PostalCodeUtils; -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -/** A utility class to get the current location. */ -public class LocationUtils { - private static final String TAG = "LocationUtils"; - private static final boolean DEBUG = false; - - private static Context sApplicationContext; - private static Address sAddress; - private static String sCountry; - private static IOException sError; - - /** Checks the current location. */ - public static synchronized Address getCurrentAddress(Context context) - throws IOException, SecurityException { - if (sAddress != null) { - return sAddress; - } - if (sError != null) { - throw sError; - } - if (sApplicationContext == null) { - sApplicationContext = context.getApplicationContext(); - } - LocationUtilsHelper.startLocationUpdates(); - return null; - } - - /** Returns the current country. */ - @NonNull - public static synchronized String getCurrentCountry(Context context) { - if (sCountry != null) { - return sCountry; - } - if (TextUtils.isEmpty(sCountry)) { - sCountry = context.getResources().getConfiguration().locale.getCountry(); - } - return sCountry; - } - - private static void updateAddress(Location location) { - if (DEBUG) Log.d(TAG, "Updating address with " + location); - if (location == null) { - return; - } - Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault()); - try { - List

addresses = - geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1); - if (addresses != null && !addresses.isEmpty()) { - sAddress = addresses.get(0); - if (DEBUG) Log.d(TAG, "Got " + sAddress); - try { - PostalCodeUtils.updatePostalCode(sApplicationContext); - } catch (Exception e) { - // Do nothing - } - } else { - if (DEBUG) Log.d(TAG, "No address returned"); - } - sError = null; - } catch (IOException e) { - Log.w(TAG, "Error in updating address", e); - sError = e; - } - } - - private LocationUtils() {} - - private static class LocationUtilsHelper { - private static final LocationListener LOCATION_LISTENER = - new LocationListener() { - @Override - public void onLocationChanged(Location location) { - updateAddress(location); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) {} - - @Override - public void onProviderEnabled(String provider) {} - - @Override - public void onProviderDisabled(String provider) {} - }; - - private static LocationManager sLocationManager; - - public static void startLocationUpdates() { - if (sLocationManager == null) { - sLocationManager = - (LocationManager) - sApplicationContext.getSystemService(Context.LOCATION_SERVICE); - try { - sLocationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null); - } catch (SecurityException e) { - // Enables requesting the location updates again. - sLocationManager = null; - throw e; - } - } - } - } -} diff --git a/src/com/android/tv/util/NamedThreadFactory.java b/src/com/android/tv/util/NamedThreadFactory.java deleted file mode 100644 index 264b8b3f..00000000 --- a/src/com/android/tv/util/NamedThreadFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.util; - -import android.support.annotation.NonNull; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** A thread factory that creates threads with a suffix. */ -public class NamedThreadFactory implements ThreadFactory { - private final AtomicInteger mCount = new AtomicInteger(0); - private final ThreadFactory mDefaultThreadFactory; - private final String mPrefix; - - public NamedThreadFactory(final String baseName) { - mDefaultThreadFactory = Executors.defaultThreadFactory(); - mPrefix = baseName + "-"; - } - - @Override - public Thread newThread(@NonNull final Runnable runnable) { - final Thread thread = mDefaultThreadFactory.newThread(runnable); - thread.setName(mPrefix + mCount.getAndIncrement()); - return thread; - } - - public boolean namedWithPrefix(Thread thread) { - return thread.getName().startsWith(mPrefix); - } -} diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/src/com/android/tv/util/NetworkTrafficTags.java deleted file mode 100644 index 85ecde5b..00000000 --- a/src/com/android/tv/util/NetworkTrafficTags.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.util; - -import android.net.TrafficStats; -import android.support.annotation.NonNull; -import java.util.concurrent.Executor; - -/** Constants for tagging network traffic in the Live channels app. */ -public final class NetworkTrafficTags { - - public static final int DEFAULT_LIVE_CHANNELS = 1; - public static final int LOGO_FETCHER = 2; - public static final int HDHOMERUN = 3; - public static final int EPG_FETCH = 4; - - /** - * An executor which simply wraps a provided delegate executor, but calls {@link - * TrafficStats#setThreadStatsTag(int)} before executing any task. - */ - public static class TrafficStatsTaggingExecutor implements Executor { - private final Executor delegateExecutor; - private final int tag; - - public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) { - this.delegateExecutor = delegateExecutor; - this.tag = tag; - } - - @Override - public void execute(final @NonNull Runnable command) { - // TODO(b/62038127): robolectric does not support lamdas in unbundled apps - delegateExecutor.execute( - new Runnable() { - @Override - public void run() { - TrafficStats.setThreadStatsTag(tag); - try { - command.run(); - } finally { - TrafficStats.clearThreadStatsTag(); - } - } - }); - } - } - - private NetworkTrafficTags() {} -} diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java index 3b72e091..63383aab 100644 --- a/src/com/android/tv/util/OnboardingUtils.java +++ b/src/com/android/tv/util/OnboardingUtils.java @@ -48,8 +48,8 @@ public final class OnboardingUtils { } /** - * Checks if this is the first run of {@link com.android.tv.MainActivity} with the current - * onboarding version. + * Checks if this is the first run of {@link com.android.tv.MainActivity} with the + * current onboarding version. */ public static boolean isFirstRunWithCurrentVersion(Context context) { int versionCode = @@ -59,8 +59,8 @@ public final class OnboardingUtils { } /** - * Marks that the first run of {@link com.android.tv.MainActivity} with the current onboarding - * version has been completed. + * Marks that the first run of {@link com.android.tv.MainActivity} with the current + * onboarding version has been completed. */ public static void setFirstRunWithCurrentVersionCompleted(Context context) { PreferenceManager.getDefaultSharedPreferences(context) diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java deleted file mode 100644 index b3e4e3a2..00000000 --- a/src/com/android/tv/util/PermissionUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.android.tv.util; - -import android.content.Context; -import android.content.pm.PackageManager; - -/** Util class to handle permissions. */ -public class PermissionUtils { - /** Permission to read the TV listings. */ - public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; - - private static Boolean sHasAccessAllEpgPermission; - private static Boolean sHasAccessWatchedHistoryPermission; - private static Boolean sHasModifyParentalControlsPermission; - - public static boolean hasAccessAllEpg(Context context) { - if (sHasAccessAllEpgPermission == null) { - sHasAccessAllEpgPermission = - context.checkSelfPermission( - "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA") - == PackageManager.PERMISSION_GRANTED; - } - return sHasAccessAllEpgPermission; - } - - public static boolean hasAccessWatchedHistory(Context context) { - if (sHasAccessWatchedHistoryPermission == null) { - sHasAccessWatchedHistoryPermission = - context.checkSelfPermission( - "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") - == PackageManager.PERMISSION_GRANTED; - } - return sHasAccessWatchedHistoryPermission; - } - - public static boolean hasModifyParentalControls(Context context) { - if (sHasModifyParentalControlsPermission == null) { - sHasModifyParentalControlsPermission = - context.checkSelfPermission("android.permission.MODIFY_PARENTAL_CONTROLS") - == PackageManager.PERMISSION_GRANTED; - } - return sHasModifyParentalControlsPermission; - } - - public static boolean hasReadTvListings(Context context) { - return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS) - == PackageManager.PERMISSION_GRANTED; - } - - public static boolean hasInternet(Context context) { - return context.checkSelfPermission("android.permission.INTERNET") - == PackageManager.PERMISSION_GRANTED; - } -} diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java index c1b724a2..764689c2 100644 --- a/src/com/android/tv/util/RecurringRunner.java +++ b/src/com/android/tv/util/RecurringRunner.java @@ -22,8 +22,8 @@ import android.os.AsyncTask; import android.os.Handler; import android.support.annotation.WorkerThread; import android.util.Log; -import com.android.tv.common.SharedPreferencesUtils; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.SharedPreferencesUtils; import java.util.Date; /** diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java index a1ff192b..ad5d5024 100644 --- a/src/com/android/tv/util/SetupUtils.java +++ b/src/com/android/tv/util/SetupUtils.java @@ -28,15 +28,15 @@ import android.media.tv.TvInputManager; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; +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.tuner.tvinput.TunerTvInputService; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -54,9 +54,8 @@ public class SetupUtils { // Recognized inputs means that the user already knows the inputs are installed. private static final String PREF_KEY_RECOGNIZED_INPUTS = "recognized_inputs"; private static final String PREF_KEY_IS_FIRST_TUNE = "is_first_tune"; - private static SetupUtils sSetupUtils; - private final TvApplication mTvApplication; + private final Context mContext; private final SharedPreferences mSharedPreferences; private final Set mKnownInputs; private final Set mSetUpInputs; @@ -64,9 +63,10 @@ public class SetupUtils { private boolean mIsFirstTune; private final String mTunerInputId; - private SetupUtils(TvApplication tvApplication) { - mTvApplication = tvApplication; - mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication); + @VisibleForTesting + protected SetupUtils(Context context) { + mContext = context; + mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); mSetUpInputs = new ArraySet<>(); mSetUpInputs.addAll( mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.emptySet())); @@ -77,18 +77,16 @@ public class SetupUtils { mRecognizedInputs.addAll( mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs)); mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true); - mTunerInputId = - TvContract.buildInputId( - new ComponentName(tvApplication, TunerTvInputService.class)); + mTunerInputId = BaseApplication.getSingletons(context).getEmbeddedTunerInputId(); } - /** Gets an instance of {@link SetupUtils}. */ - public static SetupUtils getInstance(Context context) { - if (sSetupUtils != null) { - return sSetupUtils; - } - sSetupUtils = new SetupUtils((TvApplication) context.getApplicationContext()); - return sSetupUtils; + /** + * Creates an instance of {@link SetupUtils}. + * + *

WARNING this should only be called by the top level application. + */ + public static SetupUtils createForTvSingletons(Context context) { + return new SetupUtils(context.getApplicationContext()); } /** Additional work after the setup of TV input. */ @@ -99,14 +97,15 @@ public class SetupUtils { // which one is the last callback. To reduce error prune, we update channel // list again and make all channels of {@code inputId} browsable. onSetupDone(inputId); - final ChannelDataManager manager = mTvApplication.getChannelDataManager(); + final ChannelDataManager manager = + TvSingletons.getSingletons(mContext).getChannelDataManager(); if (!manager.isDbLoadFinished()) { manager.addListener( new ChannelDataManager.Listener() { @Override public void onLoadFinished() { manager.removeListener(this); - updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); + updateChannelsAfterSetup(mContext, inputId, postRunnable); } @Override @@ -116,14 +115,14 @@ public class SetupUtils { public void onChannelBrowsableChanged() {} }); } else { - updateChannelsAfterSetup(mTvApplication, inputId, postRunnable); + updateChannelsAfterSetup(mContext, inputId, postRunnable); } } private static void updateChannelsAfterSetup( Context context, final String inputId, final Runnable postRunnable) { - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - final ChannelDataManager manager = appSingletons.getChannelDataManager(); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + final ChannelDataManager manager = tvSingletons.getChannelDataManager(); manager.updateChannels( new Runnable() { @Override @@ -159,8 +158,9 @@ public class SetupUtils { @UiThread public void markNewChannelsBrowsable() { Set newInputsWithChannels = new HashSet<>(); - TvInputManagerHelper tvInputManagerHelper = mTvApplication.getTvInputManagerHelper(); - ChannelDataManager channelDataManager = mTvApplication.getChannelDataManager(); + TvSingletons singletons = TvSingletons.getSingletons(mContext); + TvInputManagerHelper tvInputManagerHelper = singletons.getTvInputManagerHelper(); + ChannelDataManager channelDataManager = singletons.getChannelDataManager(); SoftPreconditions.checkState(channelDataManager.isDbLoadFinished()); for (TvInputInfo input : tvInputManagerHelper.getTvInputInfos(true, true)) { String inputId = input.getId(); @@ -340,8 +340,7 @@ public class SetupUtils { try { // Just after booting, input list from TvInputManager are not reliable. // So we need to double-check package existence. b/29034900 - mTvApplication - .getPackageManager() + mContext.getPackageManager() .getPackageInfo( ComponentName.unflattenFromString(input).getPackageName(), PackageManager.GET_ACTIVITIES); diff --git a/src/com/android/tv/util/SqlParams.java b/src/com/android/tv/util/SqlParams.java new file mode 100644 index 00000000..c4b803b6 --- /dev/null +++ b/src/com/android/tv/util/SqlParams.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.util; + +import android.database.DatabaseUtils; +import java.util.Arrays; + +/** Convenience class for SQL operations. */ +public class SqlParams { + private String mTables; + private String mSelection; + private String[] mSelectionArgs; + + public SqlParams(String tables, String selection, String... selectionArgs) { + setTables(tables); + setWhere(selection, selectionArgs); + } + + public String getTables() { + return mTables; + } + + public String getSelection() { + return mSelection; + } + + public String[] getSelectionArgs() { + return mSelectionArgs; + } + + public void setTables(String tables) { + mTables = tables; + } + + public void setWhere(String selection, String... selectionArgs) { + mSelection = selection; + mSelectionArgs = selectionArgs; + } + + public void appendWhere(String selection, String... selectionArgs) { + mSelection = DatabaseUtils.concatenateWhere(mSelection, selection); + if (selectionArgs != null) { + mSelectionArgs = DatabaseUtils.appendSelectionArgs(mSelectionArgs, selectionArgs); + } + } + + public void appendWhereEquals(String name, String value) { + appendWhere(name + "=?", value); + } + + @Override + public String toString() { + return "tables " + + getTables() + + " where " + + getSelection() + + " with " + + Arrays.toString(getSelectionArgs()); + } +} diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/util/StringUtils.java deleted file mode 100644 index eeaf33a6..00000000 --- a/src/com/android/tv/util/StringUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -/** Utility class for handling {@link String}. */ -public final class StringUtils { - - private StringUtils() {} - - /** Returns compares two strings lexicographically and handles null values quietly. */ - public static int compare(String a, String b) { - if (a == null) { - return b == null ? 0 : -1; - } - if (b == null) { - return 1; - } - return a.compareTo(b); - } -} diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java deleted file mode 100644 index e1b8a398..00000000 --- a/src/com/android/tv/util/SystemProperties.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.util; - -import com.android.tv.common.BooleanSystemProperty; - -/** A convenience class for getting TV related system properties. */ -public final class SystemProperties { - - /** Allow Google Analytics for eng builds. */ - public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = - new BooleanSystemProperty("tv_allow_analytics_in_eng", false); - - /** Allow Strict mode for debug builds. */ - public static final BooleanSystemProperty ALLOW_STRICT_MODE = - new BooleanSystemProperty("tv_allow_strict_mode", true); - - /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */ - public static final BooleanSystemProperty LOG_KEYEVENT = - new BooleanSystemProperty("tv_log_keyevent", false); - /** When true debug keys are used. Defaults to false. */ - public static final BooleanSystemProperty USE_DEBUG_KEYS = - new BooleanSystemProperty("tv_use_debug_keys", false); - - /** Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. */ - public static final BooleanSystemProperty USE_TRACKER = - new BooleanSystemProperty("tv_use_tracker", true); - - static { - updateSystemProperties(); - } - - private SystemProperties() {} - - /** Update the TV related system properties. */ - public static void updateSystemProperties() { - BooleanSystemProperty.resetAll(); - } -} diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index e97bc4f9..c4feafb7 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -21,17 +21,19 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.hardware.hdmi.HdmiDeviceInfo; +import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; import android.os.Handler; +import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import com.android.tv.Features; +import com.android.tv.TvFeatures; import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.common.util.CommonUtils; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import java.util.ArrayList; @@ -47,6 +49,58 @@ public class TvInputManagerHelper { private static final String TAG = "TvInputManagerHelper"; private static final boolean DEBUG = false; + public interface TvInputManagerInterface { + TvInputInfo getTvInputInfo(String inputId); + + Integer getInputState(String inputId); + + void registerCallback(TvInputCallback internalCallback, Handler handler); + + void unregisterCallback(TvInputCallback internalCallback); + + List getTvInputList(); + + List getTvContentRatingSystemList(); + } + + private static final class TvInputManagerImpl implements TvInputManagerInterface { + private final TvInputManager delegate; + + private TvInputManagerImpl(TvInputManager delegate) { + this.delegate = delegate; + } + + @Override + public TvInputInfo getTvInputInfo(String inputId) { + return delegate.getTvInputInfo(inputId); + } + + @Override + public Integer getInputState(String inputId) { + return delegate.getInputState(inputId); + } + + @Override + public void registerCallback(TvInputCallback internalCallback, Handler handler) { + delegate.registerCallback(internalCallback, handler); + } + + @Override + public void unregisterCallback(TvInputCallback internalCallback) { + delegate.unregisterCallback(internalCallback); + } + + @Override + public List getTvInputList() { + return delegate.getTvInputList(); + } + + @Override + public List getTvContentRatingSystemList() { + return delegate.getTvContentRatingSystemList(); + } + } + /** Types of HDMI device and bundled tuner. */ public static final int TYPE_CEC_DEVICE = -2; @@ -57,7 +111,8 @@ public class TvInputManagerHelper { private static final String PERMISSION_ACCESS_ALL_EPG_DATA = "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; - private static final String[] mPhysicalTunerBlackList = {}; + private static final String[] mPhysicalTunerBlackList = { + }; private static final String META_LABEL_SORT_KEY = "input_sort_key"; /** The default tv input priority to show. */ @@ -81,7 +136,8 @@ public class TvInputManagerHelper { DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER); } - private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {}; + private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = { + }; private static final String[] TESTABLE_INPUTS = { "com.android.tv.testinput/.TestTvInputService" @@ -89,7 +145,7 @@ public class TvInputManagerHelper { private final Context mContext; private final PackageManager mPackageManager; - private final TvInputManager mTvInputManager; + protected final TvInputManagerInterface mTvInputManager; private final Map mInputStateMap = new HashMap<>(); private final Map mInputMap = new HashMap<>(); private final Map mTvInputLabels = new ArrayMap<>(); @@ -206,10 +262,23 @@ public class TvInputManagerHelper { private final Comparator mTvInputInfoComparator; public TvInputManagerHelper(Context context) { + this(context, createTvInputManagerWrapper(context)); + } + + @Nullable + protected static TvInputManagerImpl createTvInputManagerWrapper(Context context) { + TvInputManager tvInputManager = + (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); + return tvInputManager == null ? null : new TvInputManagerImpl(tvInputManager); + } + + @VisibleForTesting + protected TvInputManagerHelper( + Context context, @Nullable TvInputManagerInterface tvInputManager) { mContext = context.getApplicationContext(); mPackageManager = context.getPackageManager(); - mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - mContentRatingsManager = new ContentRatingsManager(context); + mTvInputManager = tvInputManager; + mContentRatingsManager = new ContentRatingsManager(context, tvInputManager); mParentalControlSettings = new ParentalControlSettings(context); mTvInputInfoComparator = new InputComparatorInternal(this); } @@ -320,7 +389,7 @@ public class TvInputManagerHelper { /** Is the input one known bundled inputs not written by OEM/SOCs. */ public boolean isBundledInput(TvInputInfo inputInfo) { return inputInfo != null - && Utils.isInBundledPackageSet( + && CommonUtils.isInBundledPackageSet( inputInfo.getServiceInfo().applicationInfo.packageName); } @@ -428,9 +497,17 @@ public class TvInputManagerHelper { } return size; } - - public int getInputState(TvInputInfo inputInfo) { - return getInputState(inputInfo.getId()); + /** + * Returns TvInputInfo's input state. + * + * @param inputInfo + * @return An Integer which stands for the input state {@link + * TvInputManager.INPUT_STATE_DISCONNECTED} if inputInfo is null + */ + public int getInputState(@Nullable TvInputInfo inputInfo) { + return inputInfo == null + ? TvInputManager.INPUT_STATE_DISCONNECTED + : getInputState(inputInfo.getId()); } public int getInputState(String inputId) { @@ -501,14 +578,15 @@ public class TvInputManagerHelper { } private boolean isInBlackList(String inputId) { - if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { + if (TvFeatures.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) { for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) { if (inputId.contains(disabledTunerInputPrefix)) { return true; } } } - if (TvCommonUtils.isRunningInTest()) { + if (CommonUtils.isRoboTest()) return false; + if (CommonUtils.isRunningInTest()) { for (String testableInput : TESTABLE_INPUTS) { if (testableInput.equals(inputId)) { return false; diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java index ac3be643..1c8ccd5b 100644 --- a/src/com/android/tv/util/Utils.java +++ b/src/com/android/tv/util/Utils.java @@ -38,20 +38,15 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.text.format.DateUtils; -import android.util.ArraySet; import android.util.Log; import android.view.View; -import com.android.tv.ApplicationSingletons; import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.BuildConfig; +import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.data.Channel; import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; -import com.android.tv.experiments.Experiments; -import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -68,13 +63,11 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** A class that includes convenience methods for accessing TvProvider database. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class Utils { private static final String TAG = "Utils"; private static final boolean DEBUG = false; - private static final SimpleDateFormat ISO_8601 = - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); - public static final String EXTRA_KEY_ACTION = "action"; public static final String EXTRA_ACTION_SHOW_TV_INPUT = "show_tv_input"; public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher"; @@ -116,15 +109,6 @@ public class Utils { private static final long HALF_MINUTE_MS = TimeUnit.SECONDS.toMillis(30); private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); - // Hardcoded list for known bundled inputs not written by OEM/SOCs. - // Bundled (system) inputs not in the list will get the high priority - // so they and their channels come first in the UI. - private static final Set BUNDLED_PACKAGE_SET = new ArraySet<>(); - - static { - BUNDLED_PACKAGE_SET.add("com.android.tv"); - } - private enum AspectRatio { ASPECT_RATIO_4_3(4, 3), ASPECT_RATIO_16_9(16, 9), @@ -661,7 +645,7 @@ public class Utils { return null; } TvInputManagerHelper inputManager = - TvApplication.getSingletons(context).getTvInputManagerHelper(); + TvSingletons.getSingletons(context).getTvInputManagerHelper(); CharSequence customLabel = inputManager.loadCustomLabel(input); String label = (customLabel == null) ? null : customLabel.toString(); if (TextUtils.isEmpty(label)) { @@ -704,11 +688,6 @@ public class Utils { return toTimeString(timeMillis, true); } - /** Converts time in milliseconds to a ISO 8061 string. */ - public static String toIsoDateTimeString(long timeMillis) { - return ISO_8601.format(new Date(timeMillis)); - } - /** * Returns a {@link String} object which contains the layout information of the {@code view}. */ @@ -775,7 +754,7 @@ public class Utils { /** Checks where there is any internal TV input. */ public static boolean hasInternalTvInputs(Context context, boolean tunerInputOnly) { for (TvInputInfo input : - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getTvInputManagerHelper() .getTvInputInfos(true, tunerInputOnly)) { if (isInternalTvInput(context, input.getId())) { @@ -789,7 +768,7 @@ public class Utils { public static List getInternalTvInputs(Context context, boolean tunerInputOnly) { List inputs = new ArrayList<>(); for (TvInputInfo input : - TvApplication.getSingletons(context) + TvSingletons.getSingletons(context) .getTvInputManagerHelper() .getTvInputInfos(true, tunerInputOnly)) { if (isInternalTvInput(context, input.getId())) { @@ -817,47 +796,22 @@ public class Utils { /** Returns the TV input for the given channel ID. */ @Nullable public static TvInputInfo getTvInputInfoForChannelId(Context context, long channelId) { - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - Channel channel = appSingletons.getChannelDataManager().getChannel(channelId); + TvSingletons tvSingletons = TvSingletons.getSingletons(context); + Channel channel = tvSingletons.getChannelDataManager().getChannel(channelId); if (channel == null) { return null; } - return appSingletons.getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); + return tvSingletons.getTvInputManagerHelper().getTvInputInfo(channel.getInputId()); } /** Returns the {@link TvInputInfo} for the given input ID. */ @Nullable public static TvInputInfo getTvInputInfoForInputId(Context context, String inputId) { - return TvApplication.getSingletons(context) + return TvSingletons.getSingletons(context) .getTvInputManagerHelper() .getTvInputInfo(inputId); } - /** Deletes a file or a directory. */ - public static void deleteDirOrFile(File fileOrDirectory) { - if (fileOrDirectory.isDirectory()) { - for (File child : fileOrDirectory.listFiles()) { - deleteDirOrFile(child); - } - } - fileOrDirectory.delete(); - } - - /** Checks whether a given package is in our bundled package set. */ - public static boolean isInBundledPackageSet(String packageName) { - return BUNDLED_PACKAGE_SET.contains(packageName); - } - - /** Checks whether a given input is a bundled input. */ - public static boolean isBundledInput(String inputId) { - for (String prefix : BUNDLED_PACKAGE_SET) { - if (inputId.startsWith(prefix + "/")) { - return true; - } - } - return false; - } - /** Returns the canonical genre ID's from the {@code genres}. */ public static int[] getCanonicalGenreIds(String genres) { if (TextUtils.isEmpty(genres)) { @@ -899,11 +853,6 @@ public class Utils { return Genres.encode(genres); } - /** Returns true if the current user is a developer. */ - public static boolean isDeveloper() { - return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get(); - } - /** * Runs the method in main thread. If the current thread is not main thread, block it util the * method is finished. diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java index 2d5ecfe6..b8bdb6b8 100644 --- a/src/com/android/tv/util/ViewCache.java +++ b/src/com/android/tv/util/ViewCache.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv.util; import android.content.Context; diff --git a/src/com/android/tv/util/account/AccountHelper.java b/src/com/android/tv/util/account/AccountHelper.java new file mode 100644 index 00000000..e98b42ec --- /dev/null +++ b/src/com/android/tv/util/account/AccountHelper.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util.account; + +import android.accounts.Account; +import android.support.annotation.Nullable; + +/** Helper methods for getting and selecting a user account. */ +public interface AccountHelper { + /** Returns the currently selected account or {@code null} if none is selected. */ + @Nullable + Account getSelectedAccount(); + /** + * Selects the first account available. + * + * @return selected account or {@code null} if none is selected. + */ + @Nullable + Account selectFirstAccount(); + + /** Returns all eligible accounts . */ + @Nullable + Account getFirstEligibleAccount(); +} diff --git a/src/com/android/tv/util/account/AccountHelperImpl.java b/src/com/android/tv/util/account/AccountHelperImpl.java new file mode 100644 index 00000000..58fbd27e --- /dev/null +++ b/src/com/android/tv/util/account/AccountHelperImpl.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.util.account; + +import android.accounts.Account; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; + +/** Helper methods for getting and selecting a user account. */ +public class AccountHelperImpl implements com.android.tv.util.account.AccountHelper { + private static final String SELECTED_ACCOUNT = "android.tv.livechannels.selected_account"; + + protected final Context mContext; + private final SharedPreferences mDefaultPreferences; + + @Nullable private Account mSelectedAccount; + + public AccountHelperImpl(Context context) { + mContext = context.getApplicationContext(); + mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); + } + + /** Returns the currently selected account or {@code null} if none is selected. */ + @Override + @Nullable + public final Account getSelectedAccount() { + String accountId = mDefaultPreferences.getString(SELECTED_ACCOUNT, null); + if (accountId == null) { + return null; + } + if (mSelectedAccount == null || !mSelectedAccount.name.equals((accountId))) { + mSelectedAccount = null; + for (Account account : getEligibleAccounts()) { + if (account.name.equals(accountId)) { + mSelectedAccount = account; + break; + } + } + } + return mSelectedAccount; + } + + /** + * Returns all eligible accounts. + * + *

Override this method to return the accounts needed. + */ + protected Account[] getEligibleAccounts() { + return new Account[0]; + } + + /** + * Selects the first account available. + * + * @return selected account or {@code null} if none is selected. + */ + @Override + @Nullable + public final Account selectFirstAccount() { + Account account = getFirstEligibleAccount(); + if (account != null) { + selectAccount(account); + } + return account; + } + + /** + * Gets the first account eligible. + * + * @return first account or {@code null} if none is eligible. + */ + @Override + @Nullable + public final Account getFirstEligibleAccount() { + Account[] accounts = getEligibleAccounts(); + return accounts.length == 0 ? null : accounts[0]; + } + + /** Sets the given account as the selected account. */ + private void selectAccount(Account account) { + SharedPreferences defaultPreferences = + PreferenceManager.getDefaultSharedPreferences(mContext); + defaultPreferences.edit().putString(SELECTED_ACCOUNT, account.name).commit(); + } +} diff --git a/tests/common/Android.mk b/tests/common/Android.mk index 27c9f031..bb8d2fbb 100644 --- a/tests/common/Android.mk +++ b/tests/common/Android.mk @@ -8,8 +8,13 @@ LOCAL_SRC_FILES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-annotations \ + android-support-test \ + guava-22-0-jar \ + javax-annotations-jar \ mockito-target \ - ub-uiautomator + platform-robolectric-3.4.2-prebuilt \ + truth-0-36-prebuilt-jar \ + ub-uiautomator \ # Link tv-common as shared library to avoid the problem of initialization of the constants LOCAL_JAVA_LIBRARIES := tv-common @@ -17,9 +22,12 @@ LOCAL_JAVA_LIBRARIES := tv-common LOCAL_INSTRUMENTATION_FOR := LiveTv LOCAL_MODULE := tv-test-common LOCAL_MODULE_TAGS := optional -LOCAL_SDK_VERSION := current +LOCAL_SDK_VERSION := system_current LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/src include $(BUILD_STATIC_JAVA_LIBRARY) + + + diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/ChannelInfo.java deleted file mode 100644 index b28ac9fb..00000000 --- a/tests/common/src/com/android/tv/testing/ChannelInfo.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.testing; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.util.SparseArray; -import java.util.Objects; - -/** Channel Information. */ -public final class ChannelInfo { - private static final SparseArray VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>(); - - static { - VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P); - VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P); - VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, TvContract.Channels.VIDEO_FORMAT_720P); - VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, TvContract.Channels.VIDEO_FORMAT_1080P); - VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, TvContract.Channels.VIDEO_FORMAT_2160P); - VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P); - } - - public static final String[] PROJECTION = { - TvContract.Channels.COLUMN_DISPLAY_NUMBER, - TvContract.Channels.COLUMN_DISPLAY_NAME, - TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, - }; - - public final String number; - public final String name; - public final String logoUrl; - public final int originalNetworkId; - public final int videoWidth; - public final int videoHeight; - public final float videoPixelAspectRatio; - public final int audioChannel; - public final int audioLanguageCount; - public final boolean hasClosedCaption; - public final ProgramInfo program; - public final String appLinkText; - public final int appLinkColor; - public final String appLinkIconUri; - public final String appLinkPosterArtUri; - public final String appLinkIntentUri; - - /** - * Create a channel info for TVTestInput. - * - * @param context a context to insert logo. It can be null if logo isn't needed. - * @param channelNumber a channel number to be use as an identifier. {@link #originalNetworkId} - * will be assigned the same value, too. - */ - public static ChannelInfo create(@Nullable Context context, int channelNumber) { - Builder builder = - new Builder() - .setNumber(String.valueOf(channelNumber)) - .setName("Channel " + channelNumber) - .setOriginalNetworkId(channelNumber); - if (context != null) { - // tests/input/tools/get_test_logos.sh only stores 1000 logos. - builder.setLogoUrl(getUriStringForChannelLogo(context, channelNumber)); - } - return builder.build(); - } - - public static String getUriStringForChannelLogo(Context context, int logoIndex) { - int index = (logoIndex % 1000) + 1; - return new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(context.getPackageName()) - .path("drawable") - .appendPath("ch_" + index + "_logo") - .build() - .toString(); - } - - public static ChannelInfo fromCursor(Cursor c) { - // TODO: Fill other fields. - Builder builder = new Builder(); - int index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NUMBER); - if (index >= 0) { - builder.setNumber(c.getString(index)); - } - index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NAME); - if (index >= 0) { - builder.setName(c.getString(index)); - } - index = c.getColumnIndex(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID); - if (index >= 0) { - builder.setOriginalNetworkId(c.getInt(index)); - } - return builder.build(); - } - - private ChannelInfo( - String number, - String name, - String logoUrl, - int originalNetworkId, - int videoWidth, - int videoHeight, - float videoPixelAspectRatio, - int audioChannel, - int audioLanguageCount, - boolean hasClosedCaption, - ProgramInfo program, - String appLinkText, - int appLinkColor, - String appLinkIconUri, - String appLinkPosterArtUri, - String appLinkIntentUri) { - this.number = number; - this.name = name; - this.logoUrl = logoUrl; - this.originalNetworkId = originalNetworkId; - this.videoWidth = videoWidth; - this.videoHeight = videoHeight; - this.videoPixelAspectRatio = videoPixelAspectRatio; - this.audioChannel = audioChannel; - this.audioLanguageCount = audioLanguageCount; - this.hasClosedCaption = hasClosedCaption; - this.program = program; - this.appLinkText = appLinkText; - this.appLinkColor = appLinkColor; - this.appLinkIconUri = appLinkIconUri; - this.appLinkPosterArtUri = appLinkPosterArtUri; - this.appLinkIntentUri = appLinkIntentUri; - } - - public String getVideoFormat() { - return VIDEO_HEIGHT_TO_FORMAT_MAP.get(videoHeight); - } - - @Override - public String toString() { - return "Channel{" - + "number=" - + number - + ", name=" - + name - + ", logoUri=" - + logoUrl - + ", originalNetworkId=" - + originalNetworkId - + ", videoWidth=" - + videoWidth - + ", videoHeight=" - + videoHeight - + ", audioChannel=" - + audioChannel - + ", audioLanguageCount=" - + audioLanguageCount - + ", hasClosedCaption=" - + hasClosedCaption - + ", appLinkText=" - + appLinkText - + ", appLinkColor=" - + appLinkColor - + ", appLinkIconUri=" - + appLinkIconUri - + ", appLinkPosterArtUri=" - + appLinkPosterArtUri - + ", appLinkIntentUri=" - + appLinkIntentUri - + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ChannelInfo that = (ChannelInfo) o; - return Objects.equals(originalNetworkId, that.originalNetworkId) - && Objects.equals(videoWidth, that.videoWidth) - && Objects.equals(videoHeight, that.videoHeight) - && Objects.equals(audioChannel, that.audioChannel) - && Objects.equals(audioLanguageCount, that.audioLanguageCount) - && Objects.equals(hasClosedCaption, that.hasClosedCaption) - && Objects.equals(appLinkColor, that.appLinkColor) - && Objects.equals(number, that.number) - && Objects.equals(name, that.name) - && Objects.equals(logoUrl, that.logoUrl) - && Objects.equals(program, that.program) - && Objects.equals(appLinkText, that.appLinkText) - && Objects.equals(appLinkIconUri, that.appLinkIconUri) - && Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri) - && Objects.equals(appLinkIntentUri, that.appLinkIntentUri); - } - - @Override - public int hashCode() { - return Objects.hash(number, name, originalNetworkId); - } - - /** Builder class for {@code ChannelInfo}. */ - public static class Builder { - private String mNumber; - private String mName; - private String mLogoUrl = null; - private int mOriginalNetworkId; - private int mVideoWidth = 1920; // Width for HD video. - private int mVideoHeight = 1080; // Height for HD video. - private float mVideoPixelAspectRatio = 1.0f; // default value - private int mAudioChannel; - private int mAudioLanguageCount; - private boolean mHasClosedCaption; - private ProgramInfo mProgram; - private String mAppLinkText; - private int mAppLinkColor; - private String mAppLinkIconUri; - private String mAppLinkPosterArtUri; - private String mAppLinkIntentUri; - - public Builder() {} - - public Builder(ChannelInfo other) { - mNumber = other.number; - mName = other.name; - mLogoUrl = other.name; - mOriginalNetworkId = other.originalNetworkId; - mVideoWidth = other.videoWidth; - mVideoHeight = other.videoHeight; - mVideoPixelAspectRatio = other.videoPixelAspectRatio; - mAudioChannel = other.audioChannel; - mAudioLanguageCount = other.audioLanguageCount; - mHasClosedCaption = other.hasClosedCaption; - mProgram = other.program; - } - - public Builder setName(String name) { - mName = name; - return this; - } - - public Builder setNumber(String number) { - mNumber = number; - return this; - } - - public Builder setLogoUrl(String logoUrl) { - mLogoUrl = logoUrl; - return this; - } - - public Builder setOriginalNetworkId(int originalNetworkId) { - mOriginalNetworkId = originalNetworkId; - return this; - } - - public Builder setVideoWidth(int videoWidth) { - mVideoWidth = videoWidth; - return this; - } - - public Builder setVideoHeight(int videoHeight) { - mVideoHeight = videoHeight; - return this; - } - - public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { - mVideoPixelAspectRatio = videoPixelAspectRatio; - return this; - } - - public Builder setAudioChannel(int audioChannel) { - mAudioChannel = audioChannel; - return this; - } - - public Builder setAudioLanguageCount(int audioLanguageCount) { - mAudioLanguageCount = audioLanguageCount; - return this; - } - - public Builder setHasClosedCaption(boolean hasClosedCaption) { - mHasClosedCaption = hasClosedCaption; - return this; - } - - public Builder setProgram(ProgramInfo program) { - mProgram = program; - return this; - } - - public Builder setAppLinkText(String appLinkText) { - mAppLinkText = appLinkText; - return this; - } - - public Builder setAppLinkColor(int appLinkColor) { - mAppLinkColor = appLinkColor; - return this; - } - - public Builder setAppLinkIconUri(String appLinkIconUri) { - mAppLinkIconUri = appLinkIconUri; - return this; - } - - public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) { - mAppLinkPosterArtUri = appLinkPosterArtUri; - return this; - } - - public Builder setAppLinkIntentUri(String appLinkIntentUri) { - mAppLinkIntentUri = appLinkIntentUri; - return this; - } - - public ChannelInfo build() { - return new ChannelInfo( - mNumber, - mName, - mLogoUrl, - mOriginalNetworkId, - mVideoWidth, - mVideoHeight, - mVideoPixelAspectRatio, - mAudioChannel, - mAudioLanguageCount, - mHasClosedCaption, - mProgram, - mAppLinkText, - mAppLinkColor, - mAppLinkIconUri, - mAppLinkPosterArtUri, - mAppLinkIntentUri); - } - } -} diff --git a/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java new file mode 100644 index 00000000..23b86e89 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/ChannelNumberSubject.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import static junit.framework.Assert.fail; + +import android.support.annotation.Nullable; +import com.android.tv.data.ChannelNumber; +import com.google.common.truth.ComparableSubject; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.common.truth.Truth; + + +/** Propositions for {@link ChannelNumber} subjects. */ +public final class ChannelNumberSubject + extends ComparableSubject { + private static final Subject.Factory FACTORY = + ChannelNumberSubject::new; + + public static Subject.Factory channelNumbers() { + return FACTORY; + } + + public static ChannelNumberSubject assertThat(@Nullable ChannelNumber actual) { + return Truth.assertAbout(channelNumbers()).that(actual); + } + + public ChannelNumberSubject(FailureMetadata failureMetadata, @Nullable ChannelNumber subject) { + super(failureMetadata, subject); + } + + public void displaysAs(int major) { + if (!getSubject().majorNumber.equals(Integer.toString(major)) + || getSubject().hasDelimiter) { + fail("displaysAs", major); + } + } + + public void displaysAs(int major, int minor) { + if (!getSubject().majorNumber.equals(Integer.toString(major)) + || !getSubject().minorNumber.equals(Integer.toString(minor)) + || !getSubject().hasDelimiter) { + fail("displaysAs", major + "-" + minor); + } + } + + public void isEmpty() { + if (!getSubject().majorNumber.equals("") | !getSubject().minorNumber.equals("") + || getSubject().hasDelimiter) { + fail("isEmpty"); + } + } +} diff --git a/tests/common/src/com/android/tv/testing/ChannelUtils.java b/tests/common/src/com/android/tv/testing/ChannelUtils.java deleted file mode 100644 index b9c48f37..00000000 --- a/tests/common/src/com/android/tv/testing/ChannelUtils.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.testing; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContract; -import android.media.tv.TvContract.Channels; -import android.net.Uri; -import android.os.AsyncTask; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** Static helper methods for working with {@link android.media.tv.TvContract}. */ -public class ChannelUtils { - private static final String TAG = "ChannelUtils"; - private static final boolean DEBUG = false; - - /** - * Query and return the map of (channel_id, ChannelInfo). See: {@link - * ChannelInfo#fromCursor(Cursor)}. - */ - @WorkerThread - public static Map queryChannelInfoMapForTvInput( - Context context, String inputId) { - Uri uri = TvContract.buildChannelsUriForInput(inputId); - Map map = new HashMap<>(); - - String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; - projections[0] = Channels._ID; - System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); - try (Cursor cursor = - context.getContentResolver().query(uri, projections, null, null, null)) { - if (cursor != null) { - while (cursor.moveToNext()) { - map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); - } - } - return map; - } - } - - @WorkerThread - public static void updateChannels(Context context, String inputId, List channels) { - // Create a map from original network ID to channel row ID for existing channels. - SparseArray existingChannelsMap = new SparseArray<>(); - Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); - String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; - ContentResolver resolver = context.getContentResolver(); - try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - long rowId = cursor.getLong(0); - int originalNetworkId = cursor.getInt(1); - existingChannelsMap.put(originalNetworkId, rowId); - } - } - - Map logos = new HashMap<>(); - for (ChannelInfo channel : channels) { - // If a channel exists, update it. If not, insert a new one. - ContentValues values = new ContentValues(); - values.put(Channels.COLUMN_INPUT_ID, inputId); - values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); - values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); - values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); - String videoFormat = channel.getVideoFormat(); - if (videoFormat != null) { - values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat); - } else { - values.putNull(Channels.COLUMN_VIDEO_FORMAT); - } - if (!TextUtils.isEmpty(channel.appLinkText)) { - values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText); - } - if (channel.appLinkColor != 0) { - values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor); - } - if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) { - values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri); - } - if (!TextUtils.isEmpty(channel.appLinkIconUri)) { - values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri); - } - if (!TextUtils.isEmpty(channel.appLinkIntentUri)) { - values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri); - } - Long rowId = existingChannelsMap.get(channel.originalNetworkId); - Uri uri; - if (rowId == null) { - if (DEBUG) Log.d(TAG, "Inserting " + channel); - uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); - } else { - if (DEBUG) Log.d(TAG, "Updating " + channel); - uri = TvContract.buildChannelUri(rowId); - resolver.update(uri, values, null, null); - existingChannelsMap.remove(channel.originalNetworkId); - } - if (!TextUtils.isEmpty(channel.logoUrl)) { - logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl); - } - } - if (!logos.isEmpty()) { - new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); - } - - // Deletes channels which don't exist in the new feed. - int size = existingChannelsMap.size(); - for (int i = 0; i < size; ++i) { - Long rowId = existingChannelsMap.valueAt(i); - resolver.delete(TvContract.buildChannelUri(rowId), null, null); - } - } - - public static void copy(InputStream is, OutputStream os) throws IOException { - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - } - - private ChannelUtils() { - // Prevent instantiation. - } - - public static class InsertLogosTask extends AsyncTask, Void, Void> { - private final Context mContext; - - InsertLogosTask(Context context) { - mContext = context; - } - - @SafeVarargs - @Override - public final Void doInBackground(Map... logosList) { - for (Map logos : logosList) { - for (Uri uri : logos.keySet()) { - if (uri == null) { - continue; - } - Uri logoUri = Uri.parse(logos.get(uri)); - try (InputStream is = mContext.getContentResolver().openInputStream(logoUri); - OutputStream os = mContext.getContentResolver().openOutputStream(uri)) { - copy(is, os); - } catch (IOException ioe) { - Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe); - } - } - } - return null; - } - } -} diff --git a/tests/common/src/com/android/tv/testing/Constants.java b/tests/common/src/com/android/tv/testing/Constants.java deleted file mode 100644 index 879a88b4..00000000 --- a/tests/common/src/com/android/tv/testing/Constants.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tv.testing; - -import android.media.tv.TvTrackInfo; - -/** Constants for testing. */ -public final class Constants { - public static final int FUNC_TEST_CHANNEL_COUNT = 100; - public static final int UNIT_TEST_CHANNEL_COUNT = 4; - public static final int JANK_TEST_CHANNEL_COUNT = 500; // TODO: increase to 1000 see b/23526997 - - public static final TvTrackInfo EN_STEREO_AUDIO_TRACK = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "English Stereo Audio") - .setLanguage("en") - .setAudioChannelCount(2) - .build(); - public static final TvTrackInfo GENERIC_AUDIO_TRACK = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "Generic Audio").build(); - - public static final TvTrackInfo FHD1080P50_VIDEO_TRACK = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "FHD Video") - .setVideoHeight(1080) - .setVideoWidth(1920) - .setVideoFrameRate(50) - .build(); - public static final TvTrackInfo SVGA_VIDEO_TRACK = - new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "SVGA Video") - .setVideoHeight(600) - .setVideoWidth(800) - .build(); - - private Constants() {} -} diff --git a/tests/common/src/com/android/tv/testing/DbTestingUtils.java b/tests/common/src/com/android/tv/testing/DbTestingUtils.java new file mode 100644 index 00000000..53e26ca7 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/DbTestingUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.database.Cursor; +import java.util.ArrayList; +import java.util.List; + +/** Static utilities for testing using databases. */ +public final class DbTestingUtils { + + public static List> toList(Cursor cursor) { + ArrayList> result = new ArrayList<>(); + int colCount = cursor.getColumnCount(); + while (cursor.moveToNext()) { + List row = new ArrayList<>(colCount); + for (int i = 0; i < colCount; i++) { + row.add(cursor.getString(i)); + } + result.add(row); + } + return result; + } + + private DbTestingUtils() {} +} diff --git a/tests/common/src/com/android/tv/testing/EpgTestData.java b/tests/common/src/com/android/tv/testing/EpgTestData.java new file mode 100644 index 00000000..87bfb4ed --- /dev/null +++ b/tests/common/src/com/android/tv/testing/EpgTestData.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import com.android.tv.data.Channel; +import com.android.tv.data.Lineup; +import com.android.tv.data.Program; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import java.util.concurrent.TimeUnit; + +/** EPG data for use in tests. */ +public abstract class EpgTestData { + + public static final android.support.media.tv.Channel CHANNEL_10 = + new android.support.media.tv.Channel.Builder() + .setDisplayName("Channel TEN") + .setDisplayNumber("10") + .build(); + public static final android.support.media.tv.Channel CHANNEL_11 = + new android.support.media.tv.Channel.Builder() + .setDisplayName("Channel Eleven") + .setDisplayNumber("11") + .build(); + public static final android.support.media.tv.Channel CHANNEL_90_2 = + new android.support.media.tv.Channel.Builder() + .setDisplayName("Channel Ninety dot Two") + .setDisplayNumber("90.2") + .build(); + + public static final Lineup LINEUP_1 = + new Lineup( + "lineup1", + Lineup.LINEUP_SATELLITE, + "Lineup one", + "Location one", + ImmutableList.of("1", "2.2")); + public static final Lineup LINEUP_2 = + new Lineup( + "lineup2", + Lineup.LINEUP_SATELLITE, + "Lineup two", + "Location two", + ImmutableList.of("1", "2.3")); + + public static final Lineup LINEUP_90210 = + new Lineup( + "test90210", + Lineup.LINEUP_BROADCAST_DIGITAL, + "Test 90210", + "Beverly Hills", + ImmutableList.of("90.2", "10")); + + // Programs start and end times are set relative to 0. + // Then when loaded they are offset by the {@link #getStartTimeMs}. + // Start and end time may be negative meaning they happen before "now". + + public static final Program PROGRAM_1 = + new Program.Builder() + .setTitle("Program 1") + .setStartTimeUtcMillis(0) + .setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(30)) + .build(); + + public static final Program PROGRAM_2 = + new Program.Builder() + .setTitle("Program 2") + .setStartTimeUtcMillis(TimeUnit.MINUTES.toMillis(30)) + .setEndTimeUtcMillis(TimeUnit.MINUTES.toMillis(60)) + .build(); + + public static final EpgTestData DATA_90210 = + new EpgTestData() { + + // Thursday, June 1, 2017 4:00:00 PM GMT-07:00 + private final long testStartTimeMs = 1496358000000L; + + @Override + public ListMultimap getLineups() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builder(); + return builder.putAll("90210", LINEUP_1, LINEUP_2, LINEUP_90210).build(); + } + + @Override + public ListMultimap getLineupChannels() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builder(); + return builder.putAll( + LINEUP_90210.getId(), toTvChannels(CHANNEL_90_2, CHANNEL_10)) + .putAll(LINEUP_1.getId(), toTvChannels(CHANNEL_10, CHANNEL_11)) + .build(); + } + + @Override + public ListMultimap getEpgPrograms() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builder(); + return builder.putAll( + CHANNEL_10.getDisplayNumber(), + EpgTestData.updateTime(getStartTimeMs(), PROGRAM_1)) + .putAll( + CHANNEL_11.getDisplayNumber(), + EpgTestData.updateTime(getStartTimeMs(), PROGRAM_2)) + .build(); + } + + @Override + public long getStartTimeMs() { + return testStartTimeMs; + } + }; + + public abstract ListMultimap getLineups(); + + public abstract ListMultimap getLineupChannels(); + + public abstract ListMultimap getEpgPrograms(); + + /** The starting time for this test data */ + public abstract long getStartTimeMs(); + + /** + * Loads test data + * + *

+ * + *

    + *
  • Sets clock to {@link #getStartTimeMs()} and boot time to 12 hours before that + *
  • Loads lineups + *
  • Loads lineupChannels + *
  • Loads epgPrograms + *
+ */ + public final void loadData(FakeClock clock, FakeEpgReader epgReader) { + clock.setBootTimeMillis(getStartTimeMs() + TimeUnit.HOURS.toMillis(-12)); + clock.setCurrentTimeMillis(getStartTimeMs()); + epgReader.zip2lineups.putAll(getLineups()); + epgReader.lineup2Channels.putAll(getLineupChannels()); + epgReader.epgChannelId2Programs.putAll(getEpgPrograms()); + } + + public final void loadData(TestSingletonApp testSingletonApp) { + loadData(testSingletonApp.fakeClock, testSingletonApp.epgReader); + } + + private static Iterable toTvChannels(android.support.media.tv.Channel... channels) { + return Iterables.transform( + ImmutableList.copyOf(channels), + new Function() { + @Override + public Channel apply(android.support.media.tv.Channel original) { + return toTvChannel(original); + } + }); + } + + public static Channel toTvChannel(android.support.media.tv.Channel original) { + return new Channel.Builder() + .setDisplayName(original.getDisplayName()) + .setDisplayNumber(original.getDisplayNumber()) + // TODO implement the reset + .build(); + } + + /** Add time to the startTime and stopTime of each program */ + private static Iterable updateTime(long time, Program... programs) { + return Iterables.transform( + ImmutableList.copyOf(programs), + new Function() { + @Override + public Program apply(Program p) { + return new Program.Builder(p) + .setStartTimeUtcMillis(p.getStartTimeUtcMillis() + time) + .setEndTimeUtcMillis(p.getEndTimeUtcMillis() + time) + .build(); + } + }); + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeClock.java b/tests/common/src/com/android/tv/testing/FakeClock.java index fd6165cf..66978a3d 100644 --- a/tests/common/src/com/android/tv/testing/FakeClock.java +++ b/tests/common/src/com/android/tv/testing/FakeClock.java @@ -16,7 +16,7 @@ package com.android.tv.testing; -import com.android.tv.util.Clock; +import com.android.tv.common.util.Clock; import java.util.concurrent.TimeUnit; /** diff --git a/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java b/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java new file mode 100644 index 00000000..d1018a5c --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeEpgFetcher.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import com.android.tv.data.epg.EpgFetcher; + +/** Fake {@link EpgFetcher} for testing. */ +public class FakeEpgFetcher implements EpgFetcher { + public boolean fetchStarted = false; + + @Override + public void startRoutineService() {} + + @Override + public void fetchImmediatelyIfNeeded() {} + + @Override + public void fetchImmediately() { + fetchStarted = true; + } + + @Override + public void onChannelScanStarted() {} + + @Override + public void onChannelScanFinished() {} + + @Override + public boolean executeFetchTaskIfPossible(JobService jobService, JobParameters params) { + return false; + } + + @Override + public void stopFetchingJob() { + fetchStarted = false; + } + + public void reset() { + fetchStarted = false; + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeEpgReader.java b/tests/common/src/com/android/tv/testing/FakeEpgReader.java new file mode 100644 index 00000000..a3505279 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeEpgReader.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.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.ChannelNumber; +import com.android.tv.data.Lineup; +import com.android.tv.data.Program; +import com.android.tv.data.epg.EpgReader; +import com.android.tv.dvr.data.SeriesInfo; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Fake {@link EpgReader} for testing. */ +public final class FakeEpgReader implements EpgReader { + public final ListMultimap zip2lineups = LinkedListMultimap.create(2); + public final ListMultimap lineup2Channels = LinkedListMultimap.create(2); + public final ListMultimap epgChannelId2Programs = LinkedListMultimap.create(2); + public final FakeClock fakeClock; + + public FakeEpgReader(FakeClock fakeClock) { + this.fakeClock = fakeClock; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public long getEpgTimestamp() { + return fakeClock.currentTimeMillis(); + } + + @Override + public void setRegionCode(String regionCode) {} + + @Override + public List getLineups(@NonNull String postalCode) { + return zip2lineups.get(postalCode); + } + + @Override + public List getChannelNumbers(@NonNull String lineupId) { + return null; + } + + @Override + public Set getChannels(Set inputChannels, @NonNull String lineupId) { + Set result = new HashSet<>(); + List lineupChannels = lineup2Channels.get(lineupId); + for (Channel channel : lineupChannels) { + Channel match = + Iterables.find( + inputChannels, + new Predicate() { + @Override + public boolean apply(@Nullable Channel inputChannel) { + return ChannelNumber.equivalent( + inputChannel.getDisplayNumber(), + channel.getDisplayNumber()); + } + }, + null); + if (match != null) { + Channel updatedChannel = new Channel.Builder(match).build(); + updatedChannel.setLogoUri(channel.getLogoUri()); + result.add(EpgChannel.createEpgChannel(updatedChannel, channel.getDisplayNumber())); + } + } + return result; + } + + @Override + public void preloadChannels(@NonNull String lineupId) {} + + @Override + public void clearCachedChannels(@NonNull String lineupId) {} + + @Override + public List getPrograms(EpgChannel epgChannel) { + return ImmutableList.copyOf( + Iterables.transform( + epgChannelId2Programs.get(epgChannel.getEpgChannelId()), + updateWith(epgChannel))); + } + + @Override + public Map> getPrograms( + @NonNull Set epgChannels, long duration) { + Range validRange = + Range.create( + fakeClock.currentTimeMillis(), fakeClock.currentTimeMillis() + duration); + ImmutableMap.Builder> mapBuilder = ImmutableMap.builder(); + for (EpgChannel epgChannel : epgChannels) { + Iterable programs = getPrograms(epgChannel); + + mapBuilder.put( + epgChannel, + ImmutableList.copyOf(Iterables.filter(programs, isProgramDuring(validRange)))); + } + return mapBuilder.build(); + } + + protected Function updateWith(final EpgChannel channel) { + return new Function() { + @Nullable + @Override + public Program apply(@Nullable Program program) { + return new Program.Builder(program) + .setChannelId(channel.getChannel().getId()) + .setPackageName(channel.getChannel().getPackageName()) + .build(); + } + }; + } + + /** + * True if the start time or the end time is {@link Range#contains contained (inclusive)} in the + * range + */ + protected Predicate isProgramDuring(final Range validRange) { + return new Predicate() { + @Override + public boolean apply(@Nullable Program program) { + return validRange.contains(program.getStartTimeUtcMillis()) + || validRange.contains(program.getEndTimeUtcMillis()); + } + }; + } + + @Override + public SeriesInfo getSeriesInfo(@NonNull String seriesId) { + return null; + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java b/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java new file mode 100644 index 00000000..d8951d08 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeRemoteConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.text.TextUtils; +import com.android.tv.common.config.api.RemoteConfig; +import java.util.HashMap; +import java.util.Map; + +/** Fake {@link RemoteConfig} suitable for testing. */ +public class FakeRemoteConfig implements RemoteConfig { + public final Map values = new HashMap(); + + @Override + public void fetch(OnRemoteConfigUpdatedListener listener) {} + + @Override + public String getString(String key) { + return values.get(key); + } + + @Override + public boolean getBoolean(String key) { + String value = values.get(key); + return TextUtils.isEmpty(value) ? false : Boolean.valueOf(key); + } + + @Override + public long getLong(String key) { + String value = values.get(key); + return TextUtils.isEmpty(value) ? 0 : Long.valueOf(key); + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeTvInputManager.java b/tests/common/src/com/android/tv/testing/FakeTvInputManager.java new file mode 100644 index 00000000..397b4052 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeTvInputManager.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.media.tv.TvContentRatingSystemInfo; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager; +import android.os.Handler; +import com.android.tv.util.TvInputManagerHelper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Fake implementation for testing. */ +public class FakeTvInputManager implements TvInputManagerHelper.TvInputManagerInterface { + + private final Map mInputStateMap = new HashMap<>(); + private final Map mInputMap = new HashMap<>(); + private final Map mCallbacks = new HashMap<>(); + + public void add(TvInputInfo inputInfo, int state) { + final String inputId = inputInfo.getId(); + if (mInputStateMap.containsKey(inputId)) { + throw new IllegalArgumentException("inputId " + inputId); + } + mInputMap.put(inputId, inputInfo); + mInputStateMap.put(inputId, state); + for (final Map.Entry e : mCallbacks.entrySet()) { + e.getValue() + .post( + new Runnable() { + @Override + public void run() { + e.getKey().onInputAdded(inputId); + } + }); + } + } + + public void setInputState(final String inputId, final int state) { + if (!mInputStateMap.containsKey(inputId)) { + throw new IllegalArgumentException("inputId " + inputId); + } + mInputStateMap.put(inputId, state); + for (final Map.Entry e : mCallbacks.entrySet()) { + e.getValue() + .post( + new Runnable() { + @Override + public void run() { + e.getKey().onInputStateChanged(inputId, state); + } + }); + } + } + + public void remove(final String inputId) { + mInputMap.remove(inputId); + mInputStateMap.remove(inputId); + for (final Map.Entry e : mCallbacks.entrySet()) { + e.getValue() + .post( + new Runnable() { + @Override + public void run() { + e.getKey().onInputRemoved(inputId); + } + }); + } + } + + @Override + public TvInputInfo getTvInputInfo(String inputId) { + return mInputMap.get(inputId); + } + + @Override + public Integer getInputState(String inputId) { + return mInputStateMap.get(inputId); + } + + @Override + public void registerCallback(TvInputManager.TvInputCallback internalCallback, Handler handler) { + mCallbacks.put(internalCallback, handler); + } + + @Override + public void unregisterCallback(TvInputManager.TvInputCallback internalCallback) { + mCallbacks.remove(internalCallback); + } + + @Override + public List getTvInputList() { + return new ArrayList(mInputMap.values()); + } + + @Override + public List getTvContentRatingSystemList() { + // TODO implement + return new ArrayList<>(); + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java new file mode 100644 index 00000000..85bdcf04 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeTvInputManagerHelper.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.content.Context; +import com.android.tv.util.TvInputManagerHelper; + +/** Fake TvInputManagerHelper. */ +public class FakeTvInputManagerHelper extends TvInputManagerHelper { + + public FakeTvInputManagerHelper(Context context) { + super(context, new FakeTvInputManager()); + } + + public FakeTvInputManager getFakeTvInputManager() { + return (FakeTvInputManager) mTvInputManager; + } +} diff --git a/tests/common/src/com/android/tv/testing/FakeTvProvider.java b/tests/common/src/com/android/tv/testing/FakeTvProvider.java new file mode 100644 index 00000000..24c26f39 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/FakeTvProvider.java @@ -0,0 +1,2605 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.annotation.SuppressLint; +import android.content.ContentProvider; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.content.SharedPreferences; +import android.content.UriMatcher; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.tv.TvContract; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.annotation.VisibleForTesting; +import android.support.media.tv.TvContractCompat; +import android.support.media.tv.TvContractCompat.BaseTvColumns; +import android.support.media.tv.TvContractCompat.Channels; +import android.support.media.tv.TvContractCompat.PreviewPrograms; +import android.support.media.tv.TvContractCompat.Programs; +import android.support.media.tv.TvContractCompat.Programs.Genres; +import android.support.media.tv.TvContractCompat.RecordedPrograms; +import android.support.media.tv.TvContractCompat.WatchNextPrograms; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.util.SqlParams; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Fake TV content provider suitable for unit tests. The contract between this provider and + * applications is defined in {@link TvContractCompat}. + */ +// TODO(b/62143348): remove when error prone check fixed +@SuppressWarnings({"AndroidApiChecker", "TryWithResources"}) +public class FakeTvProvider extends ContentProvider { + // TODO either make this a shadow or move it to the support library + + private static final boolean DEBUG = false; + private static final String TAG = "TvProvider"; + + static final int DATABASE_VERSION = 34; + static final String SHARED_PREF_BLOCKED_PACKAGES_KEY = "blocked_packages"; + static final String CHANNELS_TABLE = "channels"; + static final String PROGRAMS_TABLE = "programs"; + static final String RECORDED_PROGRAMS_TABLE = "recorded_programs"; + static final String PREVIEW_PROGRAMS_TABLE = "preview_programs"; + static final String WATCH_NEXT_PROGRAMS_TABLE = "watch_next_programs"; + static final String WATCHED_PROGRAMS_TABLE = "watched_programs"; + static final String PROGRAMS_TABLE_PACKAGE_NAME_INDEX = "programs_package_name_index"; + static final String PROGRAMS_TABLE_CHANNEL_ID_INDEX = "programs_channel_id_index"; + static final String PROGRAMS_TABLE_START_TIME_INDEX = "programs_start_time_index"; + static final String PROGRAMS_TABLE_END_TIME_INDEX = "programs_end_time_index"; + static final String WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX = + "watched_programs_channel_id_index"; + // The internal column in the watched programs table to indicate whether the current log entry + // is consolidated or not. Unconsolidated entries may have columns with missing data. + static final String WATCHED_PROGRAMS_COLUMN_CONSOLIDATED = "consolidated"; + static final String CHANNELS_COLUMN_LOGO = "logo"; + private static final String DATABASE_NAME = "tv.db"; + private static final String DELETED_CHANNELS_TABLE = "deleted_channels"; // Deprecated + private static final String DEFAULT_PROGRAMS_SORT_ORDER = + Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC"; + private static final String DEFAULT_WATCHED_PROGRAMS_SORT_ORDER = + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + " DESC"; + private static final String CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE = + CHANNELS_TABLE + + " INNER JOIN " + + PROGRAMS_TABLE + + " ON (" + + CHANNELS_TABLE + + "." + + Channels._ID + + "=" + + PROGRAMS_TABLE + + "." + + Programs.COLUMN_CHANNEL_ID + + ")"; + + // Operation names for createSqlParams(). + private static final String OP_QUERY = "query"; + private static final String OP_UPDATE = "update"; + private static final String OP_DELETE = "delete"; + + private static final UriMatcher sUriMatcher; + private static final int MATCH_CHANNEL = 1; + private static final int MATCH_CHANNEL_ID = 2; + private static final int MATCH_CHANNEL_ID_LOGO = 3; + private static final int MATCH_PASSTHROUGH_ID = 4; + private static final int MATCH_PROGRAM = 5; + private static final int MATCH_PROGRAM_ID = 6; + private static final int MATCH_WATCHED_PROGRAM = 7; + private static final int MATCH_WATCHED_PROGRAM_ID = 8; + private static final int MATCH_RECORDED_PROGRAM = 9; + private static final int MATCH_RECORDED_PROGRAM_ID = 10; + private static final int MATCH_PREVIEW_PROGRAM = 11; + private static final int MATCH_PREVIEW_PROGRAM_ID = 12; + private static final int MATCH_WATCH_NEXT_PROGRAM = 13; + private static final int MATCH_WATCH_NEXT_PROGRAM_ID = 14; + + private static final int MAX_LOGO_IMAGE_SIZE = 256; + + private static final String EMPTY_STRING = ""; + + private static final Map sChannelProjectionMap; + private static final Map sProgramProjectionMap; + private static final Map sWatchedProgramProjectionMap; + private static final Map sRecordedProgramProjectionMap; + private static final Map sPreviewProgramProjectionMap; + private static final Map sWatchNextProgramProjectionMap; + + // TvContract hidden + private static final String PARAM_PACKAGE = "package"; + private static final String PARAM_PREVIEW = "preview"; + + private static boolean sInitialized; + + static { + sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel", MATCH_CHANNEL); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#", MATCH_CHANNEL_ID); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "channel/#/logo", MATCH_CHANNEL_ID_LOGO); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "passthrough/*", MATCH_PASSTHROUGH_ID); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program", MATCH_PROGRAM); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "program/#", MATCH_PROGRAM_ID); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "watched_program", MATCH_WATCHED_PROGRAM); + sUriMatcher.addURI( + TvContractCompat.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM); + sUriMatcher.addURI( + TvContractCompat.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID); + sUriMatcher.addURI(TvContractCompat.AUTHORITY, "preview_program", MATCH_PREVIEW_PROGRAM); + sUriMatcher.addURI( + TvContractCompat.AUTHORITY, "preview_program/#", MATCH_PREVIEW_PROGRAM_ID); + sUriMatcher.addURI( + TvContractCompat.AUTHORITY, "watch_next_program", MATCH_WATCH_NEXT_PROGRAM); + sUriMatcher.addURI( + TvContractCompat.AUTHORITY, "watch_next_program/#", MATCH_WATCH_NEXT_PROGRAM_ID); + + sChannelProjectionMap = new HashMap<>(); + sChannelProjectionMap.put(Channels._ID, CHANNELS_TABLE + "." + Channels._ID); + sChannelProjectionMap.put( + Channels.COLUMN_PACKAGE_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_PACKAGE_NAME); + sChannelProjectionMap.put( + Channels.COLUMN_INPUT_ID, CHANNELS_TABLE + "." + Channels.COLUMN_INPUT_ID); + sChannelProjectionMap.put( + Channels.COLUMN_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_TYPE); + sChannelProjectionMap.put( + Channels.COLUMN_SERVICE_TYPE, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_TYPE); + sChannelProjectionMap.put( + Channels.COLUMN_ORIGINAL_NETWORK_ID, + CHANNELS_TABLE + "." + Channels.COLUMN_ORIGINAL_NETWORK_ID); + sChannelProjectionMap.put( + Channels.COLUMN_TRANSPORT_STREAM_ID, + CHANNELS_TABLE + "." + Channels.COLUMN_TRANSPORT_STREAM_ID); + sChannelProjectionMap.put( + Channels.COLUMN_SERVICE_ID, CHANNELS_TABLE + "." + Channels.COLUMN_SERVICE_ID); + sChannelProjectionMap.put( + Channels.COLUMN_DISPLAY_NUMBER, + CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NUMBER); + sChannelProjectionMap.put( + Channels.COLUMN_DISPLAY_NAME, CHANNELS_TABLE + "." + Channels.COLUMN_DISPLAY_NAME); + sChannelProjectionMap.put( + Channels.COLUMN_NETWORK_AFFILIATION, + CHANNELS_TABLE + "." + Channels.COLUMN_NETWORK_AFFILIATION); + sChannelProjectionMap.put( + Channels.COLUMN_DESCRIPTION, CHANNELS_TABLE + "." + Channels.COLUMN_DESCRIPTION); + sChannelProjectionMap.put( + Channels.COLUMN_VIDEO_FORMAT, CHANNELS_TABLE + "." + Channels.COLUMN_VIDEO_FORMAT); + sChannelProjectionMap.put( + Channels.COLUMN_BROWSABLE, CHANNELS_TABLE + "." + Channels.COLUMN_BROWSABLE); + sChannelProjectionMap.put( + Channels.COLUMN_SEARCHABLE, CHANNELS_TABLE + "." + Channels.COLUMN_SEARCHABLE); + sChannelProjectionMap.put( + Channels.COLUMN_LOCKED, CHANNELS_TABLE + "." + Channels.COLUMN_LOCKED); + sChannelProjectionMap.put( + Channels.COLUMN_APP_LINK_ICON_URI, + CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_ICON_URI); + sChannelProjectionMap.put( + Channels.COLUMN_APP_LINK_POSTER_ART_URI, + CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_POSTER_ART_URI); + sChannelProjectionMap.put( + Channels.COLUMN_APP_LINK_TEXT, + CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_TEXT); + sChannelProjectionMap.put( + Channels.COLUMN_APP_LINK_COLOR, + CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_COLOR); + sChannelProjectionMap.put( + Channels.COLUMN_APP_LINK_INTENT_URI, + CHANNELS_TABLE + "." + Channels.COLUMN_APP_LINK_INTENT_URI); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_DATA, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_DATA); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4); + sChannelProjectionMap.put( + Channels.COLUMN_VERSION_NUMBER, + CHANNELS_TABLE + "." + Channels.COLUMN_VERSION_NUMBER); + sChannelProjectionMap.put( + Channels.COLUMN_TRANSIENT, CHANNELS_TABLE + "." + Channels.COLUMN_TRANSIENT); + sChannelProjectionMap.put( + Channels.COLUMN_INTERNAL_PROVIDER_ID, + CHANNELS_TABLE + "." + Channels.COLUMN_INTERNAL_PROVIDER_ID); + + sProgramProjectionMap = new HashMap<>(); + sProgramProjectionMap.put(Programs._ID, Programs._ID); + sProgramProjectionMap.put(Programs.COLUMN_PACKAGE_NAME, Programs.COLUMN_PACKAGE_NAME); + sProgramProjectionMap.put(Programs.COLUMN_CHANNEL_ID, Programs.COLUMN_CHANNEL_ID); + sProgramProjectionMap.put(Programs.COLUMN_TITLE, Programs.COLUMN_TITLE); + // COLUMN_SEASON_NUMBER is deprecated. Return COLUMN_SEASON_DISPLAY_NUMBER instead. + sProgramProjectionMap.put( + Programs.COLUMN_SEASON_NUMBER, + Programs.COLUMN_SEASON_DISPLAY_NUMBER + " AS " + Programs.COLUMN_SEASON_NUMBER); + sProgramProjectionMap.put( + Programs.COLUMN_SEASON_DISPLAY_NUMBER, Programs.COLUMN_SEASON_DISPLAY_NUMBER); + sProgramProjectionMap.put(Programs.COLUMN_SEASON_TITLE, Programs.COLUMN_SEASON_TITLE); + // COLUMN_EPISODE_NUMBER is deprecated. Return COLUMN_EPISODE_DISPLAY_NUMBER instead. + sProgramProjectionMap.put( + Programs.COLUMN_EPISODE_NUMBER, + Programs.COLUMN_EPISODE_DISPLAY_NUMBER + " AS " + Programs.COLUMN_EPISODE_NUMBER); + sProgramProjectionMap.put( + Programs.COLUMN_EPISODE_DISPLAY_NUMBER, Programs.COLUMN_EPISODE_DISPLAY_NUMBER); + sProgramProjectionMap.put(Programs.COLUMN_EPISODE_TITLE, Programs.COLUMN_EPISODE_TITLE); + sProgramProjectionMap.put( + Programs.COLUMN_START_TIME_UTC_MILLIS, Programs.COLUMN_START_TIME_UTC_MILLIS); + sProgramProjectionMap.put( + Programs.COLUMN_END_TIME_UTC_MILLIS, Programs.COLUMN_END_TIME_UTC_MILLIS); + sProgramProjectionMap.put(Programs.COLUMN_BROADCAST_GENRE, Programs.COLUMN_BROADCAST_GENRE); + sProgramProjectionMap.put(Programs.COLUMN_CANONICAL_GENRE, Programs.COLUMN_CANONICAL_GENRE); + sProgramProjectionMap.put( + Programs.COLUMN_SHORT_DESCRIPTION, Programs.COLUMN_SHORT_DESCRIPTION); + sProgramProjectionMap.put( + Programs.COLUMN_LONG_DESCRIPTION, Programs.COLUMN_LONG_DESCRIPTION); + sProgramProjectionMap.put(Programs.COLUMN_VIDEO_WIDTH, Programs.COLUMN_VIDEO_WIDTH); + sProgramProjectionMap.put(Programs.COLUMN_VIDEO_HEIGHT, Programs.COLUMN_VIDEO_HEIGHT); + sProgramProjectionMap.put(Programs.COLUMN_AUDIO_LANGUAGE, Programs.COLUMN_AUDIO_LANGUAGE); + sProgramProjectionMap.put(Programs.COLUMN_CONTENT_RATING, Programs.COLUMN_CONTENT_RATING); + sProgramProjectionMap.put(Programs.COLUMN_POSTER_ART_URI, Programs.COLUMN_POSTER_ART_URI); + sProgramProjectionMap.put(Programs.COLUMN_THUMBNAIL_URI, Programs.COLUMN_THUMBNAIL_URI); + sProgramProjectionMap.put(Programs.COLUMN_SEARCHABLE, Programs.COLUMN_SEARCHABLE); + sProgramProjectionMap.put( + Programs.COLUMN_RECORDING_PROHIBITED, Programs.COLUMN_RECORDING_PROHIBITED); + sProgramProjectionMap.put( + Programs.COLUMN_INTERNAL_PROVIDER_DATA, Programs.COLUMN_INTERNAL_PROVIDER_DATA); + sProgramProjectionMap.put( + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1, Programs.COLUMN_INTERNAL_PROVIDER_FLAG1); + sProgramProjectionMap.put( + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2, Programs.COLUMN_INTERNAL_PROVIDER_FLAG2); + sProgramProjectionMap.put( + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3, Programs.COLUMN_INTERNAL_PROVIDER_FLAG3); + sProgramProjectionMap.put( + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4, Programs.COLUMN_INTERNAL_PROVIDER_FLAG4); + sProgramProjectionMap.put(Programs.COLUMN_VERSION_NUMBER, Programs.COLUMN_VERSION_NUMBER); + sProgramProjectionMap.put( + Programs.COLUMN_REVIEW_RATING_STYLE, Programs.COLUMN_REVIEW_RATING_STYLE); + sProgramProjectionMap.put(Programs.COLUMN_REVIEW_RATING, Programs.COLUMN_REVIEW_RATING); + + sWatchedProgramProjectionMap = new HashMap<>(); + sWatchedProgramProjectionMap.put(WatchedPrograms._ID, WatchedPrograms._ID); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_CHANNEL_ID, WatchedPrograms.COLUMN_CHANNEL_ID); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_TITLE, WatchedPrograms.COLUMN_TITLE); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_DESCRIPTION, WatchedPrograms.COLUMN_DESCRIPTION); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS, + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS); + sWatchedProgramProjectionMap.put( + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN, + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN); + sWatchedProgramProjectionMap.put( + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED, WATCHED_PROGRAMS_COLUMN_CONSOLIDATED); + + sRecordedProgramProjectionMap = new HashMap<>(); + sRecordedProgramProjectionMap.put(RecordedPrograms._ID, RecordedPrograms._ID); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_PACKAGE_NAME, RecordedPrograms.COLUMN_PACKAGE_NAME); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INPUT_ID, RecordedPrograms.COLUMN_INPUT_ID); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_CHANNEL_ID, RecordedPrograms.COLUMN_CHANNEL_ID); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_TITLE, RecordedPrograms.COLUMN_TITLE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_SEASON_TITLE, RecordedPrograms.COLUMN_SEASON_TITLE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_EPISODE_TITLE, RecordedPrograms.COLUMN_EPISODE_TITLE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_BROADCAST_GENRE, RecordedPrograms.COLUMN_BROADCAST_GENRE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_CANONICAL_GENRE, RecordedPrograms.COLUMN_CANONICAL_GENRE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_SHORT_DESCRIPTION, + RecordedPrograms.COLUMN_SHORT_DESCRIPTION); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_LONG_DESCRIPTION, RecordedPrograms.COLUMN_LONG_DESCRIPTION); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_VIDEO_WIDTH, RecordedPrograms.COLUMN_VIDEO_WIDTH); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_VIDEO_HEIGHT, RecordedPrograms.COLUMN_VIDEO_HEIGHT); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_AUDIO_LANGUAGE, RecordedPrograms.COLUMN_AUDIO_LANGUAGE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_CONTENT_RATING, RecordedPrograms.COLUMN_CONTENT_RATING); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_POSTER_ART_URI, RecordedPrograms.COLUMN_POSTER_ART_URI); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_THUMBNAIL_URI, RecordedPrograms.COLUMN_THUMBNAIL_URI); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_SEARCHABLE, RecordedPrograms.COLUMN_SEARCHABLE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_RECORDING_DATA_URI, + RecordedPrograms.COLUMN_RECORDING_DATA_URI); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_VERSION_NUMBER, RecordedPrograms.COLUMN_VERSION_NUMBER); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE, + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE); + sRecordedProgramProjectionMap.put( + RecordedPrograms.COLUMN_REVIEW_RATING, RecordedPrograms.COLUMN_REVIEW_RATING); + + sPreviewProgramProjectionMap = new HashMap<>(); + sPreviewProgramProjectionMap.put(PreviewPrograms._ID, PreviewPrograms._ID); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_PACKAGE_NAME, PreviewPrograms.COLUMN_PACKAGE_NAME); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_CHANNEL_ID, PreviewPrograms.COLUMN_CHANNEL_ID); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_TITLE, PreviewPrograms.COLUMN_TITLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER, + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_SEASON_TITLE, PreviewPrograms.COLUMN_SEASON_TITLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_EPISODE_TITLE, PreviewPrograms.COLUMN_EPISODE_TITLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_CANONICAL_GENRE, PreviewPrograms.COLUMN_CANONICAL_GENRE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_SHORT_DESCRIPTION, PreviewPrograms.COLUMN_SHORT_DESCRIPTION); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_LONG_DESCRIPTION, PreviewPrograms.COLUMN_LONG_DESCRIPTION); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_VIDEO_WIDTH, PreviewPrograms.COLUMN_VIDEO_WIDTH); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_VIDEO_HEIGHT, PreviewPrograms.COLUMN_VIDEO_HEIGHT); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_AUDIO_LANGUAGE, PreviewPrograms.COLUMN_AUDIO_LANGUAGE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_CONTENT_RATING, PreviewPrograms.COLUMN_CONTENT_RATING); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_POSTER_ART_URI, PreviewPrograms.COLUMN_POSTER_ART_URI); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_THUMBNAIL_URI, PreviewPrograms.COLUMN_THUMBNAIL_URI); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_SEARCHABLE, PreviewPrograms.COLUMN_SEARCHABLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_VERSION_NUMBER, PreviewPrograms.COLUMN_VERSION_NUMBER); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID, + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_DURATION_MILLIS, PreviewPrograms.COLUMN_DURATION_MILLIS); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTENT_URI, PreviewPrograms.COLUMN_INTENT_URI); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_WEIGHT, PreviewPrograms.COLUMN_WEIGHT); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_TRANSIENT, PreviewPrograms.COLUMN_TRANSIENT); + sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.COLUMN_TYPE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_LOGO_URI, PreviewPrograms.COLUMN_LOGO_URI); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_AVAILABILITY, PreviewPrograms.COLUMN_AVAILABILITY); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_STARTING_PRICE, PreviewPrograms.COLUMN_STARTING_PRICE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_OFFER_PRICE, PreviewPrograms.COLUMN_OFFER_PRICE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_RELEASE_DATE, PreviewPrograms.COLUMN_RELEASE_DATE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_ITEM_COUNT, PreviewPrograms.COLUMN_ITEM_COUNT); + sPreviewProgramProjectionMap.put(PreviewPrograms.COLUMN_LIVE, PreviewPrograms.COLUMN_LIVE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERACTION_TYPE, PreviewPrograms.COLUMN_INTERACTION_TYPE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_INTERACTION_COUNT, PreviewPrograms.COLUMN_INTERACTION_COUNT); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_AUTHOR, PreviewPrograms.COLUMN_AUTHOR); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE, + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_REVIEW_RATING, PreviewPrograms.COLUMN_REVIEW_RATING); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_BROWSABLE, PreviewPrograms.COLUMN_BROWSABLE); + sPreviewProgramProjectionMap.put( + PreviewPrograms.COLUMN_CONTENT_ID, PreviewPrograms.COLUMN_CONTENT_ID); + + sWatchNextProgramProjectionMap = new HashMap<>(); + sWatchNextProgramProjectionMap.put(WatchNextPrograms._ID, WatchNextPrograms._ID); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_PACKAGE_NAME, WatchNextPrograms.COLUMN_PACKAGE_NAME); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_TITLE, WatchNextPrograms.COLUMN_TITLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER, + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_SEASON_TITLE, WatchNextPrograms.COLUMN_SEASON_TITLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_EPISODE_TITLE, WatchNextPrograms.COLUMN_EPISODE_TITLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_CANONICAL_GENRE, WatchNextPrograms.COLUMN_CANONICAL_GENRE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION, + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_LONG_DESCRIPTION, + WatchNextPrograms.COLUMN_LONG_DESCRIPTION); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_VIDEO_WIDTH, WatchNextPrograms.COLUMN_VIDEO_WIDTH); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_VIDEO_HEIGHT, WatchNextPrograms.COLUMN_VIDEO_HEIGHT); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_AUDIO_LANGUAGE, WatchNextPrograms.COLUMN_AUDIO_LANGUAGE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_CONTENT_RATING, WatchNextPrograms.COLUMN_CONTENT_RATING); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_POSTER_ART_URI, WatchNextPrograms.COLUMN_POSTER_ART_URI); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_THUMBNAIL_URI, WatchNextPrograms.COLUMN_THUMBNAIL_URI); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_SEARCHABLE, WatchNextPrograms.COLUMN_SEARCHABLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_VERSION_NUMBER, WatchNextPrograms.COLUMN_VERSION_NUMBER); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID, + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI, + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_DURATION_MILLIS, WatchNextPrograms.COLUMN_DURATION_MILLIS); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTENT_URI, WatchNextPrograms.COLUMN_INTENT_URI); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_TRANSIENT, WatchNextPrograms.COLUMN_TRANSIENT); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_TYPE, WatchNextPrograms.COLUMN_TYPE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_LOGO_URI, WatchNextPrograms.COLUMN_LOGO_URI); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_AVAILABILITY, WatchNextPrograms.COLUMN_AVAILABILITY); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_STARTING_PRICE, WatchNextPrograms.COLUMN_STARTING_PRICE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_OFFER_PRICE, WatchNextPrograms.COLUMN_OFFER_PRICE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_RELEASE_DATE, WatchNextPrograms.COLUMN_RELEASE_DATE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_ITEM_COUNT, WatchNextPrograms.COLUMN_ITEM_COUNT); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_LIVE, WatchNextPrograms.COLUMN_LIVE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERACTION_TYPE, + WatchNextPrograms.COLUMN_INTERACTION_TYPE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_INTERACTION_COUNT, + WatchNextPrograms.COLUMN_INTERACTION_COUNT); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_AUTHOR, WatchNextPrograms.COLUMN_AUTHOR); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE, + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_REVIEW_RATING, WatchNextPrograms.COLUMN_REVIEW_RATING); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_BROWSABLE, WatchNextPrograms.COLUMN_BROWSABLE); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_CONTENT_ID, WatchNextPrograms.COLUMN_CONTENT_ID); + sWatchNextProgramProjectionMap.put( + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS); + } + + // Mapping from broadcast genre to canonical genre. + private static Map sGenreMap; + + private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; + + private static final String PERMISSION_ACCESS_ALL_EPG_DATA = + "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"; + + private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS = + "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"; + + private static final String CREATE_RECORDED_PROGRAMS_TABLE_SQL = + "CREATE TABLE " + + RECORDED_PROGRAMS_TABLE + + " (" + + RecordedPrograms._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + RecordedPrograms.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + RecordedPrograms.COLUMN_INPUT_ID + + " TEXT NOT NULL," + + RecordedPrograms.COLUMN_CHANNEL_ID + + " INTEGER," + + RecordedPrograms.COLUMN_TITLE + + " TEXT," + + RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER + + " TEXT," + + RecordedPrograms.COLUMN_SEASON_TITLE + + " TEXT," + + RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + + " TEXT," + + RecordedPrograms.COLUMN_EPISODE_TITLE + + " TEXT," + + RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS + + " INTEGER," + + RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS + + " INTEGER," + + RecordedPrograms.COLUMN_BROADCAST_GENRE + + " TEXT," + + RecordedPrograms.COLUMN_CANONICAL_GENRE + + " TEXT," + + RecordedPrograms.COLUMN_SHORT_DESCRIPTION + + " TEXT," + + RecordedPrograms.COLUMN_LONG_DESCRIPTION + + " TEXT," + + RecordedPrograms.COLUMN_VIDEO_WIDTH + + " INTEGER," + + RecordedPrograms.COLUMN_VIDEO_HEIGHT + + " INTEGER," + + RecordedPrograms.COLUMN_AUDIO_LANGUAGE + + " TEXT," + + RecordedPrograms.COLUMN_CONTENT_RATING + + " TEXT," + + RecordedPrograms.COLUMN_POSTER_ART_URI + + " TEXT," + + RecordedPrograms.COLUMN_THUMBNAIL_URI + + " TEXT," + + RecordedPrograms.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1," + + RecordedPrograms.COLUMN_RECORDING_DATA_URI + + " TEXT," + + RecordedPrograms.COLUMN_RECORDING_DATA_BYTES + + " INTEGER," + + RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS + + " INTEGER," + + RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS + + " INTEGER," + + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA + + " BLOB," + + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER," + + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER," + + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER," + + RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER," + + RecordedPrograms.COLUMN_VERSION_NUMBER + + " INTEGER," + + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE + + " INTEGER," + + RecordedPrograms.COLUMN_REVIEW_RATING + + " TEXT," + + "FOREIGN KEY(" + + RecordedPrograms.COLUMN_CHANNEL_ID + + ") " + + "REFERENCES " + + CHANNELS_TABLE + + "(" + + Channels._ID + + ") " + + "ON UPDATE CASCADE ON DELETE SET NULL);"; + + private static final String CREATE_PREVIEW_PROGRAMS_TABLE_SQL = + "CREATE TABLE " + + PREVIEW_PROGRAMS_TABLE + + " (" + + PreviewPrograms._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + PreviewPrograms.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + PreviewPrograms.COLUMN_CHANNEL_ID + + " INTEGER," + + PreviewPrograms.COLUMN_TITLE + + " TEXT," + + PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER + + " TEXT," + + PreviewPrograms.COLUMN_SEASON_TITLE + + " TEXT," + + PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + + " TEXT," + + PreviewPrograms.COLUMN_EPISODE_TITLE + + " TEXT," + + PreviewPrograms.COLUMN_CANONICAL_GENRE + + " TEXT," + + PreviewPrograms.COLUMN_SHORT_DESCRIPTION + + " TEXT," + + PreviewPrograms.COLUMN_LONG_DESCRIPTION + + " TEXT," + + PreviewPrograms.COLUMN_VIDEO_WIDTH + + " INTEGER," + + PreviewPrograms.COLUMN_VIDEO_HEIGHT + + " INTEGER," + + PreviewPrograms.COLUMN_AUDIO_LANGUAGE + + " TEXT," + + PreviewPrograms.COLUMN_CONTENT_RATING + + " TEXT," + + PreviewPrograms.COLUMN_POSTER_ART_URI + + " TEXT," + + PreviewPrograms.COLUMN_THUMBNAIL_URI + + " TEXT," + + PreviewPrograms.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA + + " BLOB," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER," + + PreviewPrograms.COLUMN_VERSION_NUMBER + + " INTEGER," + + PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID + + " TEXT," + + PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI + + " TEXT," + + PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS + + " INTEGER," + + PreviewPrograms.COLUMN_DURATION_MILLIS + + " INTEGER," + + PreviewPrograms.COLUMN_INTENT_URI + + " TEXT," + + PreviewPrograms.COLUMN_WEIGHT + + " INTEGER," + + PreviewPrograms.COLUMN_TRANSIENT + + " INTEGER NOT NULL DEFAULT 0," + + PreviewPrograms.COLUMN_TYPE + + " INTEGER NOT NULL," + + PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO + + " INTEGER," + + PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO + + " INTEGER," + + PreviewPrograms.COLUMN_LOGO_URI + + " TEXT," + + PreviewPrograms.COLUMN_AVAILABILITY + + " INTERGER," + + PreviewPrograms.COLUMN_STARTING_PRICE + + " TEXT," + + PreviewPrograms.COLUMN_OFFER_PRICE + + " TEXT," + + PreviewPrograms.COLUMN_RELEASE_DATE + + " TEXT," + + PreviewPrograms.COLUMN_ITEM_COUNT + + " INTEGER," + + PreviewPrograms.COLUMN_LIVE + + " INTEGER NOT NULL DEFAULT 0," + + PreviewPrograms.COLUMN_INTERACTION_TYPE + + " INTEGER," + + PreviewPrograms.COLUMN_INTERACTION_COUNT + + " INTEGER," + + PreviewPrograms.COLUMN_AUTHOR + + " TEXT," + + PreviewPrograms.COLUMN_REVIEW_RATING_STYLE + + " INTEGER," + + PreviewPrograms.COLUMN_REVIEW_RATING + + " TEXT," + + PreviewPrograms.COLUMN_BROWSABLE + + " INTEGER NOT NULL DEFAULT 1," + + PreviewPrograms.COLUMN_CONTENT_ID + + " TEXT," + + "FOREIGN KEY(" + + PreviewPrograms.COLUMN_CHANNEL_ID + + "," + + PreviewPrograms.COLUMN_PACKAGE_NAME + + ") REFERENCES " + + CHANNELS_TABLE + + "(" + + Channels._ID + + "," + + Channels.COLUMN_PACKAGE_NAME + + ") ON UPDATE CASCADE ON DELETE CASCADE" + + ");"; + private static final String CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL = + "CREATE INDEX preview_programs_package_name_index ON " + + PREVIEW_PROGRAMS_TABLE + + "(" + + PreviewPrograms.COLUMN_PACKAGE_NAME + + ");"; + private static final String CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL = + "CREATE INDEX preview_programs_id_index ON " + + PREVIEW_PROGRAMS_TABLE + + "(" + + PreviewPrograms.COLUMN_CHANNEL_ID + + ");"; + private static final String CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL = + "CREATE TABLE " + + WATCH_NEXT_PROGRAMS_TABLE + + " (" + + WatchNextPrograms._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + WatchNextPrograms.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + WatchNextPrograms.COLUMN_TITLE + + " TEXT," + + WatchNextPrograms.COLUMN_SEASON_DISPLAY_NUMBER + + " TEXT," + + WatchNextPrograms.COLUMN_SEASON_TITLE + + " TEXT," + + WatchNextPrograms.COLUMN_EPISODE_DISPLAY_NUMBER + + " TEXT," + + WatchNextPrograms.COLUMN_EPISODE_TITLE + + " TEXT," + + WatchNextPrograms.COLUMN_CANONICAL_GENRE + + " TEXT," + + WatchNextPrograms.COLUMN_SHORT_DESCRIPTION + + " TEXT," + + WatchNextPrograms.COLUMN_LONG_DESCRIPTION + + " TEXT," + + WatchNextPrograms.COLUMN_VIDEO_WIDTH + + " INTEGER," + + WatchNextPrograms.COLUMN_VIDEO_HEIGHT + + " INTEGER," + + WatchNextPrograms.COLUMN_AUDIO_LANGUAGE + + " TEXT," + + WatchNextPrograms.COLUMN_CONTENT_RATING + + " TEXT," + + WatchNextPrograms.COLUMN_POSTER_ART_URI + + " TEXT," + + WatchNextPrograms.COLUMN_THUMBNAIL_URI + + " TEXT," + + WatchNextPrograms.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_DATA + + " BLOB," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER," + + WatchNextPrograms.COLUMN_VERSION_NUMBER + + " INTEGER," + + WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID + + " TEXT," + + WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI + + " TEXT," + + WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS + + " INTEGER," + + WatchNextPrograms.COLUMN_DURATION_MILLIS + + " INTEGER," + + WatchNextPrograms.COLUMN_INTENT_URI + + " TEXT," + + WatchNextPrograms.COLUMN_TRANSIENT + + " INTEGER NOT NULL DEFAULT 0," + + WatchNextPrograms.COLUMN_TYPE + + " INTEGER NOT NULL," + + WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE + + " INTEGER," + + WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO + + " INTEGER," + + WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO + + " INTEGER," + + WatchNextPrograms.COLUMN_LOGO_URI + + " TEXT," + + WatchNextPrograms.COLUMN_AVAILABILITY + + " INTEGER," + + WatchNextPrograms.COLUMN_STARTING_PRICE + + " TEXT," + + WatchNextPrograms.COLUMN_OFFER_PRICE + + " TEXT," + + WatchNextPrograms.COLUMN_RELEASE_DATE + + " TEXT," + + WatchNextPrograms.COLUMN_ITEM_COUNT + + " INTEGER," + + WatchNextPrograms.COLUMN_LIVE + + " INTEGER NOT NULL DEFAULT 0," + + WatchNextPrograms.COLUMN_INTERACTION_TYPE + + " INTEGER," + + WatchNextPrograms.COLUMN_INTERACTION_COUNT + + " INTEGER," + + WatchNextPrograms.COLUMN_AUTHOR + + " TEXT," + + WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE + + " INTEGER," + + WatchNextPrograms.COLUMN_REVIEW_RATING + + " TEXT," + + WatchNextPrograms.COLUMN_BROWSABLE + + " INTEGER NOT NULL DEFAULT 1," + + WatchNextPrograms.COLUMN_CONTENT_ID + + " TEXT," + + WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS + + " INTEGER" + + ");"; + private static final String CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL = + "CREATE INDEX watch_next_programs_package_name_index ON " + + WATCH_NEXT_PROGRAMS_TABLE + + "(" + + WatchNextPrograms.COLUMN_PACKAGE_NAME + + ");"; + + private String mCallingPackage = "com.android.tv"; + + static class DatabaseHelper extends SQLiteOpenHelper { + private Context mContext; + + public static synchronized DatabaseHelper createInstance(Context context) { + return new DatabaseHelper(context); + } + + private DatabaseHelper(Context context) { + this(context, DATABASE_NAME, DATABASE_VERSION); + } + + @VisibleForTesting + DatabaseHelper(Context context, String databaseName, int databaseVersion) { + super(context, databaseName, null, databaseVersion); + mContext = context; + } + + @Override + public void onConfigure(SQLiteDatabase db) { + db.setForeignKeyConstraintsEnabled(true); + } + + @Override + public void onCreate(SQLiteDatabase db) { + if (DEBUG) { + Log.d(TAG, "Creating database"); + } + // Set up the database schema. + db.execSQL( + "CREATE TABLE " + + CHANNELS_TABLE + + " (" + + Channels._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Channels.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + Channels.COLUMN_INPUT_ID + + " TEXT NOT NULL," + + Channels.COLUMN_TYPE + + " TEXT NOT NULL DEFAULT '" + + Channels.TYPE_OTHER + + "'," + + Channels.COLUMN_SERVICE_TYPE + + " TEXT NOT NULL DEFAULT '" + + Channels.SERVICE_TYPE_AUDIO_VIDEO + + "'," + + Channels.COLUMN_ORIGINAL_NETWORK_ID + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_TRANSPORT_STREAM_ID + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_SERVICE_ID + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_DISPLAY_NUMBER + + " TEXT," + + Channels.COLUMN_DISPLAY_NAME + + " TEXT," + + Channels.COLUMN_NETWORK_AFFILIATION + + " TEXT," + + Channels.COLUMN_DESCRIPTION + + " TEXT," + + Channels.COLUMN_VIDEO_FORMAT + + " TEXT," + + Channels.COLUMN_BROWSABLE + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1," + + Channels.COLUMN_LOCKED + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_APP_LINK_ICON_URI + + " TEXT," + + Channels.COLUMN_APP_LINK_POSTER_ART_URI + + " TEXT," + + Channels.COLUMN_APP_LINK_TEXT + + " TEXT," + + Channels.COLUMN_APP_LINK_COLOR + + " INTEGER," + + Channels.COLUMN_APP_LINK_INTENT_URI + + " TEXT," + + Channels.COLUMN_INTERNAL_PROVIDER_DATA + + " BLOB," + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER," + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER," + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER," + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER," + + CHANNELS_COLUMN_LOGO + + " BLOB," + + Channels.COLUMN_VERSION_NUMBER + + " INTEGER," + + Channels.COLUMN_TRANSIENT + + " INTEGER NOT NULL DEFAULT 0," + + Channels.COLUMN_INTERNAL_PROVIDER_ID + + " TEXT," + // Needed for foreign keys in other tables. + + "UNIQUE(" + + Channels._ID + + "," + + Channels.COLUMN_PACKAGE_NAME + + ")" + + ");"); + db.execSQL( + "CREATE TABLE " + + PROGRAMS_TABLE + + " (" + + Programs._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Programs.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + Programs.COLUMN_CHANNEL_ID + + " INTEGER," + + Programs.COLUMN_TITLE + + " TEXT," + + Programs.COLUMN_SEASON_DISPLAY_NUMBER + + " TEXT," + + Programs.COLUMN_SEASON_TITLE + + " TEXT," + + Programs.COLUMN_EPISODE_DISPLAY_NUMBER + + " TEXT," + + Programs.COLUMN_EPISODE_TITLE + + " TEXT," + + Programs.COLUMN_START_TIME_UTC_MILLIS + + " INTEGER," + + Programs.COLUMN_END_TIME_UTC_MILLIS + + " INTEGER," + + Programs.COLUMN_BROADCAST_GENRE + + " TEXT," + + Programs.COLUMN_CANONICAL_GENRE + + " TEXT," + + Programs.COLUMN_SHORT_DESCRIPTION + + " TEXT," + + Programs.COLUMN_LONG_DESCRIPTION + + " TEXT," + + Programs.COLUMN_VIDEO_WIDTH + + " INTEGER," + + Programs.COLUMN_VIDEO_HEIGHT + + " INTEGER," + + Programs.COLUMN_AUDIO_LANGUAGE + + " TEXT," + + Programs.COLUMN_CONTENT_RATING + + " TEXT," + + Programs.COLUMN_POSTER_ART_URI + + " TEXT," + + Programs.COLUMN_THUMBNAIL_URI + + " TEXT," + + Programs.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1," + + Programs.COLUMN_RECORDING_PROHIBITED + + " INTEGER NOT NULL DEFAULT 0," + + Programs.COLUMN_INTERNAL_PROVIDER_DATA + + " BLOB," + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER," + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER," + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER," + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER," + + Programs.COLUMN_REVIEW_RATING_STYLE + + " INTEGER," + + Programs.COLUMN_REVIEW_RATING + + " TEXT," + + Programs.COLUMN_VERSION_NUMBER + + " INTEGER," + + "FOREIGN KEY(" + + Programs.COLUMN_CHANNEL_ID + + "," + + Programs.COLUMN_PACKAGE_NAME + + ") REFERENCES " + + CHANNELS_TABLE + + "(" + + Channels._ID + + "," + + Channels.COLUMN_PACKAGE_NAME + + ") ON UPDATE CASCADE ON DELETE CASCADE" + + ");"); + db.execSQL( + "CREATE INDEX " + + PROGRAMS_TABLE_PACKAGE_NAME_INDEX + + " ON " + + PROGRAMS_TABLE + + "(" + + Programs.COLUMN_PACKAGE_NAME + + ");"); + db.execSQL( + "CREATE INDEX " + + PROGRAMS_TABLE_CHANNEL_ID_INDEX + + " ON " + + PROGRAMS_TABLE + + "(" + + Programs.COLUMN_CHANNEL_ID + + ");"); + db.execSQL( + "CREATE INDEX " + + PROGRAMS_TABLE_START_TIME_INDEX + + " ON " + + PROGRAMS_TABLE + + "(" + + Programs.COLUMN_START_TIME_UTC_MILLIS + + ");"); + db.execSQL( + "CREATE INDEX " + + PROGRAMS_TABLE_END_TIME_INDEX + + " ON " + + PROGRAMS_TABLE + + "(" + + Programs.COLUMN_END_TIME_UTC_MILLIS + + ");"); + db.execSQL( + "CREATE TABLE " + + WATCHED_PROGRAMS_TABLE + + " (" + + WatchedPrograms._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + WatchedPrograms.COLUMN_PACKAGE_NAME + + " TEXT NOT NULL," + + WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + + " INTEGER NOT NULL DEFAULT 0," + + WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS + + " INTEGER NOT NULL DEFAULT 0," + + WatchedPrograms.COLUMN_CHANNEL_ID + + " INTEGER," + + WatchedPrograms.COLUMN_TITLE + + " TEXT," + + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS + + " INTEGER," + + WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS + + " INTEGER," + + WatchedPrograms.COLUMN_DESCRIPTION + + " TEXT," + + WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS + + " TEXT," + + WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN + + " TEXT NOT NULL," + + WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + + " INTEGER NOT NULL DEFAULT 0," + + "FOREIGN KEY(" + + WatchedPrograms.COLUMN_CHANNEL_ID + + "," + + WatchedPrograms.COLUMN_PACKAGE_NAME + + ") REFERENCES " + + CHANNELS_TABLE + + "(" + + Channels._ID + + "," + + Channels.COLUMN_PACKAGE_NAME + + ") ON UPDATE CASCADE ON DELETE CASCADE" + + ");"); + db.execSQL( + "CREATE INDEX " + + WATCHED_PROGRAMS_TABLE_CHANNEL_ID_INDEX + + " ON " + + WATCHED_PROGRAMS_TABLE + + "(" + + WatchedPrograms.COLUMN_CHANNEL_ID + + ");"); + db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL); + db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL); + db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL); + db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL); + db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL); + db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 23) { + Log.i( + TAG, + "Upgrading from version " + + oldVersion + + " to " + + newVersion + + ", data will be lost!"); + db.execSQL("DROP TABLE IF EXISTS " + DELETED_CHANNELS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + WATCHED_PROGRAMS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + PROGRAMS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + CHANNELS_TABLE); + + onCreate(db); + return; + } + + Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + "."); + if (oldVersion <= 23) { + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER;"); + } + if (oldVersion <= 24) { + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3 + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4 + + " INTEGER;"); + } + if (oldVersion <= 25) { + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_APP_LINK_ICON_URI + + " TEXT;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_APP_LINK_POSTER_ART_URI + + " TEXT;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_APP_LINK_TEXT + + " TEXT;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_APP_LINK_COLOR + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_APP_LINK_INTENT_URI + + " TEXT;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_SEARCHABLE + + " INTEGER NOT NULL DEFAULT 1;"); + } + if (oldVersion <= 28) { + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_SEASON_TITLE + + " TEXT;"); + migrateIntegerColumnToTextColumn( + db, + PROGRAMS_TABLE, + Programs.COLUMN_SEASON_NUMBER, + Programs.COLUMN_SEASON_DISPLAY_NUMBER); + migrateIntegerColumnToTextColumn( + db, + PROGRAMS_TABLE, + Programs.COLUMN_EPISODE_NUMBER, + Programs.COLUMN_EPISODE_DISPLAY_NUMBER); + } + if (oldVersion <= 29) { + db.execSQL("DROP TABLE IF EXISTS " + RECORDED_PROGRAMS_TABLE); + db.execSQL(CREATE_RECORDED_PROGRAMS_TABLE_SQL); + } + if (oldVersion <= 30) { + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_RECORDING_PROHIBITED + + " INTEGER NOT NULL DEFAULT 0;"); + } + if (oldVersion <= 32) { + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_TRANSIENT + + " INTEGER NOT NULL DEFAULT 0;"); + db.execSQL( + "ALTER TABLE " + + CHANNELS_TABLE + + " ADD " + + Channels.COLUMN_INTERNAL_PROVIDER_ID + + " TEXT;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_REVIEW_RATING_STYLE + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + PROGRAMS_TABLE + + " ADD " + + Programs.COLUMN_REVIEW_RATING + + " TEXT;"); + if (oldVersion > 29) { + db.execSQL( + "ALTER TABLE " + + RECORDED_PROGRAMS_TABLE + + " ADD " + + RecordedPrograms.COLUMN_REVIEW_RATING_STYLE + + " INTEGER;"); + db.execSQL( + "ALTER TABLE " + + RECORDED_PROGRAMS_TABLE + + " ADD " + + RecordedPrograms.COLUMN_REVIEW_RATING + + " TEXT;"); + } + } + if (oldVersion <= 33) { + db.execSQL("DROP TABLE IF EXISTS " + PREVIEW_PROGRAMS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + WATCH_NEXT_PROGRAMS_TABLE); + db.execSQL(CREATE_PREVIEW_PROGRAMS_TABLE_SQL); + db.execSQL(CREATE_PREVIEW_PROGRAMS_PACKAGE_NAME_INDEX_SQL); + db.execSQL(CREATE_PREVIEW_PROGRAMS_CHANNEL_ID_INDEX_SQL); + db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_TABLE_SQL); + db.execSQL(CREATE_WATCH_NEXT_PROGRAMS_PACKAGE_NAME_INDEX_SQL); + } + Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion + " is done."); + } + + @Override + public void onOpen(SQLiteDatabase db) { + // Call a static method on the TvProvider because changes to sInitialized must + // be guarded by a lock on the class. + initOnOpenIfNeeded(mContext, db); + } + + private static void migrateIntegerColumnToTextColumn( + SQLiteDatabase db, String table, String integerColumn, String textColumn) { + db.execSQL("ALTER TABLE " + table + " ADD " + textColumn + " TEXT;"); + db.execSQL( + "UPDATE " + + table + + " SET " + + textColumn + + " = CAST(" + + integerColumn + + " AS TEXT);"); + } + } + + private DatabaseHelper mOpenHelper; + private static SharedPreferences sBlockedPackagesSharedPreference; + private static Map sBlockedPackages; + + @Override + public boolean onCreate() { + if (DEBUG) { + Log.d(TAG, "Creating TvProvider"); + } + mOpenHelper = DatabaseHelper.createInstance(getContext()); + return true; + } + + @VisibleForTesting + String getCallingPackage_() { + return mCallingPackage; + } + + public void setCallingPackage(String packageName) { + mCallingPackage = packageName; + } + + void setOpenHelper(DatabaseHelper helper) { + mOpenHelper = helper; + } + + @Override + public String getType(Uri uri) { + switch (sUriMatcher.match(uri)) { + case MATCH_CHANNEL: + return Channels.CONTENT_TYPE; + case MATCH_CHANNEL_ID: + return Channels.CONTENT_ITEM_TYPE; + case MATCH_CHANNEL_ID_LOGO: + return "image/png"; + case MATCH_PASSTHROUGH_ID: + return Channels.CONTENT_ITEM_TYPE; + case MATCH_PROGRAM: + return Programs.CONTENT_TYPE; + case MATCH_PROGRAM_ID: + return Programs.CONTENT_ITEM_TYPE; + case MATCH_WATCHED_PROGRAM: + return WatchedPrograms.CONTENT_TYPE; + case MATCH_WATCHED_PROGRAM_ID: + return WatchedPrograms.CONTENT_ITEM_TYPE; + case MATCH_RECORDED_PROGRAM: + return RecordedPrograms.CONTENT_TYPE; + case MATCH_RECORDED_PROGRAM_ID: + return RecordedPrograms.CONTENT_ITEM_TYPE; + case MATCH_PREVIEW_PROGRAM: + return PreviewPrograms.CONTENT_TYPE; + case MATCH_PREVIEW_PROGRAM_ID: + return PreviewPrograms.CONTENT_ITEM_TYPE; + case MATCH_WATCH_NEXT_PROGRAM: + return WatchNextPrograms.CONTENT_TYPE; + case MATCH_WATCH_NEXT_PROGRAM_ID: + return WatchNextPrograms.CONTENT_ITEM_TYPE; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + throw new UnsupportedOperationException(); + } + + @Override + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + ensureInitialized(); + boolean needsToValidateSortOrder = !callerHasAccessAllEpgDataPermission(); + SqlParams params = createSqlParams(OP_QUERY, uri, selection, selectionArgs); + + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setStrict(needsToValidateSortOrder); + queryBuilder.setTables(params.getTables()); + String orderBy = null; + Map projectionMap; + switch (params.getTables()) { + case PROGRAMS_TABLE: + projectionMap = sProgramProjectionMap; + orderBy = DEFAULT_PROGRAMS_SORT_ORDER; + break; + case WATCHED_PROGRAMS_TABLE: + projectionMap = sWatchedProgramProjectionMap; + orderBy = DEFAULT_WATCHED_PROGRAMS_SORT_ORDER; + break; + case RECORDED_PROGRAMS_TABLE: + projectionMap = sRecordedProgramProjectionMap; + break; + case PREVIEW_PROGRAMS_TABLE: + projectionMap = sPreviewProgramProjectionMap; + break; + case WATCH_NEXT_PROGRAMS_TABLE: + projectionMap = sWatchNextProgramProjectionMap; + break; + default: + projectionMap = sChannelProjectionMap; + break; + } + queryBuilder.setProjectionMap(createProjectionMapForQuery(projection, projectionMap)); + if (needsToValidateSortOrder) { + validateSortOrder(sortOrder, projectionMap.keySet()); + } + + // Use the default sort order only if no sort order is specified. + if (!TextUtils.isEmpty(sortOrder)) { + orderBy = sortOrder; + } + + // Get the database and run the query. + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor c = + queryBuilder.query( + db, + projection, + params.getSelection(), + params.getSelectionArgs(), + null, + null, + orderBy); + + // Tell the cursor what URI to watch, so it knows when its source data changes. + c.setNotificationUri(getContext().getContentResolver(), uri); + return c; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + ensureInitialized(); + switch (sUriMatcher.match(uri)) { + case MATCH_CHANNEL: + // Preview channels are not necessarily associated with TV input service. + // Therefore, we fill a fake ID to meet not null restriction for preview channels. + if (values.get(Channels.COLUMN_INPUT_ID) == null + && TvContractCompat.PARAM_CHANNEL.equals( + values.get(Channels.COLUMN_TYPE))) { + values.put(Channels.COLUMN_INPUT_ID, EMPTY_STRING); + } + filterContentValues(values, sChannelProjectionMap); + return insertChannel(uri, values); + case MATCH_PROGRAM: + filterContentValues(values, sProgramProjectionMap); + return insertProgram(uri, values); + case MATCH_WATCHED_PROGRAM: + return insertWatchedProgram(uri, values); + case MATCH_RECORDED_PROGRAM: + filterContentValues(values, sRecordedProgramProjectionMap); + return insertRecordedProgram(uri, values); + case MATCH_PREVIEW_PROGRAM: + filterContentValues(values, sPreviewProgramProjectionMap); + return insertPreviewProgram(uri, values); + case MATCH_WATCH_NEXT_PROGRAM: + filterContentValues(values, sWatchNextProgramProjectionMap); + return insertWatchNextProgram(uri, values); + case MATCH_CHANNEL_ID: + case MATCH_CHANNEL_ID_LOGO: + case MATCH_PASSTHROUGH_ID: + case MATCH_PROGRAM_ID: + case MATCH_WATCHED_PROGRAM_ID: + case MATCH_RECORDED_PROGRAM_ID: + case MATCH_PREVIEW_PROGRAM_ID: + throw new UnsupportedOperationException("Cannot insert into that URI: " + uri); + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + private Uri insertChannel(Uri uri, ContentValues values) { + if (TextUtils.equals( + values.getAsString(Channels.COLUMN_TYPE), TvContractCompat.Channels.TYPE_PREVIEW)) { + blockIllegalAccessFromBlockedPackage(); + } + // Mark the owner package of this channel. + values.put(Channels.COLUMN_PACKAGE_NAME, getCallingPackage_()); + blockIllegalAccessToChannelsSystemColumns(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(CHANNELS_TABLE, null, values); + if (rowId > 0) { + Uri channelUri = TvContractCompat.buildChannelUri(rowId); + notifyChange(channelUri); + return channelUri; + } + + throw new SQLException("Failed to insert row into " + uri); + } + + private Uri insertProgram(Uri uri, ContentValues values) { + if (!callerHasAccessAllEpgDataPermission() + || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) { + // Mark the owner package of this program. System app with a proper permission may + // change the owner of the program. + values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); + } + + checkAndConvertGenre(values); + checkAndConvertDeprecatedColumns(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(PROGRAMS_TABLE, null, values); + if (rowId > 0) { + Uri programUri = TvContractCompat.buildProgramUri(rowId); + notifyChange(programUri); + return programUri; + } + + throw new SQLException("Failed to insert row into " + uri); + } + + private Uri insertWatchedProgram(Uri uri, ContentValues values) { + if (DEBUG) { + Log.d(TAG, "insertWatchedProgram(uri=" + uri + ", values={" + values + "})"); + } + Long watchStartTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); + Long watchEndTime = values.getAsLong(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS); + // The system sends only two kinds of watch events: + // 1. The user tunes to a new channel. (COLUMN_WATCH_START_TIME_UTC_MILLIS) + // 2. The user stops watching. (COLUMN_WATCH_END_TIME_UTC_MILLIS) + if (watchStartTime != null && watchEndTime == null) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(WATCHED_PROGRAMS_TABLE, null, values); + if (rowId > 0) { + return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, rowId); + } + Log.w(TAG, "Failed to insert row for " + values + ". Channel does not exist."); + return null; + } else if (watchStartTime == null && watchEndTime != null) { + return null; + } + // All the other cases are invalid. + throw new IllegalArgumentException( + "Only one of COLUMN_WATCH_START_TIME_UTC_MILLIS and" + + " COLUMN_WATCH_END_TIME_UTC_MILLIS should be specified"); + } + + private Uri insertRecordedProgram(Uri uri, ContentValues values) { + // Mark the owner package of this program. + values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); + + checkAndConvertGenre(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(RECORDED_PROGRAMS_TABLE, null, values); + if (rowId > 0) { + Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(rowId); + notifyChange(recordedProgramUri); + return recordedProgramUri; + } + + throw new SQLException("Failed to insert row into " + uri); + } + + private Uri insertPreviewProgram(Uri uri, ContentValues values) { + if (!values.containsKey(PreviewPrograms.COLUMN_TYPE)) { + throw new IllegalArgumentException( + "Missing the required column: " + PreviewPrograms.COLUMN_TYPE); + } + blockIllegalAccessFromBlockedPackage(); + // Mark the owner package of this program. + values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); + blockIllegalAccessToPreviewProgramsSystemColumns(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(PREVIEW_PROGRAMS_TABLE, null, values); + if (rowId > 0) { + Uri previewProgramUri = TvContractCompat.buildPreviewProgramUri(rowId); + notifyChange(previewProgramUri); + return previewProgramUri; + } + + throw new SQLException("Failed to insert row into " + uri); + } + + private Uri insertWatchNextProgram(Uri uri, ContentValues values) { + if (!values.containsKey(WatchNextPrograms.COLUMN_TYPE)) { + throw new IllegalArgumentException( + "Missing the required column: " + WatchNextPrograms.COLUMN_TYPE); + } + blockIllegalAccessFromBlockedPackage(); + if (!callerHasAccessAllEpgDataPermission() + || !values.containsKey(Programs.COLUMN_PACKAGE_NAME)) { + // Mark the owner package of this program. System app with a proper permission may + // change the owner of the program. + values.put(Programs.COLUMN_PACKAGE_NAME, getCallingPackage_()); + } + blockIllegalAccessToPreviewProgramsSystemColumns(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(WATCH_NEXT_PROGRAMS_TABLE, null, values); + if (rowId > 0) { + Uri watchNextProgramUri = TvContractCompat.buildWatchNextProgramUri(rowId); + notifyChange(watchNextProgramUri); + return watchNextProgramUri; + } + + throw new SQLException("Failed to insert row into " + uri); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + SqlParams params = createSqlParams(OP_DELETE, uri, selection, selectionArgs); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + switch (sUriMatcher.match(uri)) { + case MATCH_CHANNEL_ID_LOGO: + ContentValues values = new ContentValues(); + values.putNull(CHANNELS_COLUMN_LOGO); + count = + db.update( + params.getTables(), + values, + params.getSelection(), + params.getSelectionArgs()); + break; + case MATCH_CHANNEL: + case MATCH_PROGRAM: + case MATCH_WATCHED_PROGRAM: + case MATCH_RECORDED_PROGRAM: + case MATCH_PREVIEW_PROGRAM: + case MATCH_WATCH_NEXT_PROGRAM: + case MATCH_CHANNEL_ID: + case MATCH_PASSTHROUGH_ID: + case MATCH_PROGRAM_ID: + case MATCH_WATCHED_PROGRAM_ID: + case MATCH_RECORDED_PROGRAM_ID: + case MATCH_PREVIEW_PROGRAM_ID: + case MATCH_WATCH_NEXT_PROGRAM_ID: + count = + db.delete( + params.getTables(), + params.getSelection(), + params.getSelectionArgs()); + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + if (count > 0) { + notifyChange(uri); + } + return count; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + SqlParams params = createSqlParams(OP_UPDATE, uri, selection, selectionArgs); + blockIllegalAccessToIdAndPackageName(uri, values); + boolean containImmutableColumn = false; + if (params.getTables().equals(CHANNELS_TABLE)) { + filterContentValues(values, sChannelProjectionMap); + containImmutableColumn = disallowModifyChannelType(values, params); + if (containImmutableColumn && sUriMatcher.match(uri) != MATCH_CHANNEL_ID) { + Log.i(TAG, "Updating failed. Attempt to change immutable column for channels."); + return 0; + } + blockIllegalAccessToChannelsSystemColumns(values); + } else if (params.getTables().equals(PROGRAMS_TABLE)) { + filterContentValues(values, sProgramProjectionMap); + checkAndConvertGenre(values); + checkAndConvertDeprecatedColumns(values); + } else if (params.getTables().equals(RECORDED_PROGRAMS_TABLE)) { + filterContentValues(values, sRecordedProgramProjectionMap); + checkAndConvertGenre(values); + } else if (params.getTables().equals(PREVIEW_PROGRAMS_TABLE)) { + filterContentValues(values, sPreviewProgramProjectionMap); + containImmutableColumn = disallowModifyChannelId(values, params); + if (containImmutableColumn && PreviewPrograms.CONTENT_URI.equals(uri)) { + Log.i( + TAG, + "Updating failed. Attempt to change unmodifiable column for " + + "preview programs."); + return 0; + } + blockIllegalAccessToPreviewProgramsSystemColumns(values); + } else if (params.getTables().equals(WATCH_NEXT_PROGRAMS_TABLE)) { + filterContentValues(values, sWatchNextProgramProjectionMap); + blockIllegalAccessToPreviewProgramsSystemColumns(values); + } + if (values.size() == 0) { + // All values may be filtered out, no need to update + return 0; + } + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = + db.update( + params.getTables(), + values, + params.getSelection(), + params.getSelectionArgs()); + if (count > 0) { + notifyChange(uri); + } else if (containImmutableColumn) { + Log.i( + TAG, + "Updating failed. The item may not exist or attempt to change " + + "immutable column."); + } + return count; + } + + private synchronized void ensureInitialized() { + if (!sInitialized) { + // Database is not accessed before and the projection maps and the blocked package list + // are not updated yet. Gets database here to make it initialized. + mOpenHelper.getReadableDatabase(); + } + } + + private static synchronized void initOnOpenIfNeeded(Context context, SQLiteDatabase db) { + if (!sInitialized) { + updateProjectionMap(db, CHANNELS_TABLE, sChannelProjectionMap); + updateProjectionMap(db, PROGRAMS_TABLE, sProgramProjectionMap); + updateProjectionMap(db, WATCHED_PROGRAMS_TABLE, sWatchedProgramProjectionMap); + updateProjectionMap(db, RECORDED_PROGRAMS_TABLE, sRecordedProgramProjectionMap); + updateProjectionMap(db, PREVIEW_PROGRAMS_TABLE, sPreviewProgramProjectionMap); + updateProjectionMap(db, WATCH_NEXT_PROGRAMS_TABLE, sWatchNextProgramProjectionMap); + sBlockedPackagesSharedPreference = + PreferenceManager.getDefaultSharedPreferences(context); + sBlockedPackages = new ConcurrentHashMap<>(); + for (String packageName : + sBlockedPackagesSharedPreference.getStringSet( + SHARED_PREF_BLOCKED_PACKAGES_KEY, new HashSet<>())) { + sBlockedPackages.put(packageName, true); + } + sInitialized = true; + } + } + + private static void updateProjectionMap( + SQLiteDatabase db, String tableName, Map projectionMap) { + try (Cursor cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0", null)) { + for (String columnName : cursor.getColumnNames()) { + if (!projectionMap.containsKey(columnName)) { + projectionMap.put(columnName, tableName + '.' + columnName); + } + } + } + } + + private Map createProjectionMapForQuery( + String[] projection, Map projectionMap) { + if (projection == null) { + return projectionMap; + } + Map columnProjectionMap = new HashMap<>(); + for (String columnName : projection) { + // Value NULL will be provided if the requested column does not exist in the database. + columnProjectionMap.put( + columnName, projectionMap.getOrDefault(columnName, "NULL as " + columnName)); + } + return columnProjectionMap; + } + + private void filterContentValues(ContentValues values, Map projectionMap) { + Iterator iter = values.keySet().iterator(); + while (iter.hasNext()) { + String columnName = iter.next(); + if (!projectionMap.containsKey(columnName)) { + iter.remove(); + } + } + } + + private SqlParams createSqlParams( + String operation, Uri uri, String selection, String[] selectionArgs) { + int match = sUriMatcher.match(uri); + SqlParams params = new SqlParams(null, selection, selectionArgs); + + // Control access to EPG data (excluding watched programs) when the caller doesn't have all + // access. + String prefix = match == MATCH_CHANNEL ? CHANNELS_TABLE + "." : ""; + if (!callerHasAccessAllEpgDataPermission() + && match != MATCH_WATCHED_PROGRAM + && match != MATCH_WATCHED_PROGRAM_ID) { + if (!TextUtils.isEmpty(selection)) { + throw new SecurityException("Selection not allowed for " + uri); + } + // Limit the operation only to the data that the calling package owns except for when + // the caller tries to read TV listings and has the appropriate permission. + if (operation.equals(OP_QUERY) && callerHasReadTvListingsPermission()) { + params.setWhere( + prefix + + BaseTvColumns.COLUMN_PACKAGE_NAME + + "=? OR " + + Channels.COLUMN_SEARCHABLE + + "=?", + getCallingPackage_(), + "1"); + } else { + params.setWhere( + prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_()); + } + } + String packageName = uri.getQueryParameter(PARAM_PACKAGE); + if (packageName != null) { + params.appendWhere(prefix + BaseTvColumns.COLUMN_PACKAGE_NAME + "=?", packageName); + } + + switch (match) { + case MATCH_CHANNEL: + String genre = uri.getQueryParameter(TvContractCompat.PARAM_CANONICAL_GENRE); + if (genre == null) { + params.setTables(CHANNELS_TABLE); + } else { + if (!operation.equals(OP_QUERY)) { + throw new SecurityException( + capitalize(operation) + " not allowed for " + uri); + } + if (!Genres.isCanonical(genre)) { + throw new IllegalArgumentException("Not a canonical genre : " + genre); + } + params.setTables(CHANNELS_TABLE_INNER_JOIN_PROGRAMS_TABLE); + String curTime = String.valueOf(System.currentTimeMillis()); + params.appendWhere( + "LIKE(?, " + + Programs.COLUMN_CANONICAL_GENRE + + ") AND " + + Programs.COLUMN_START_TIME_UTC_MILLIS + + "<=? AND " + + Programs.COLUMN_END_TIME_UTC_MILLIS + + ">=?", + "%" + genre + "%", + curTime, + curTime); + } + String inputId = uri.getQueryParameter(TvContractCompat.PARAM_INPUT); + if (inputId != null) { + params.appendWhere(Channels.COLUMN_INPUT_ID + "=?", inputId); + } + boolean browsableOnly = + uri.getBooleanQueryParameter(TvContractCompat.PARAM_BROWSABLE_ONLY, false); + if (browsableOnly) { + params.appendWhere(Channels.COLUMN_BROWSABLE + "=1"); + } + String preview = uri.getQueryParameter(PARAM_PREVIEW); + if (preview != null) { + String previewSelection = + Channels.COLUMN_TYPE + + (preview.equals(String.valueOf(true)) ? "=?" : "!=?"); + params.appendWhere(previewSelection, Channels.TYPE_PREVIEW); + } + break; + case MATCH_CHANNEL_ID: + params.setTables(CHANNELS_TABLE); + params.appendWhere(Channels._ID + "=?", uri.getLastPathSegment()); + break; + case MATCH_PROGRAM: + params.setTables(PROGRAMS_TABLE); + String paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL); + if (paramChannelId != null) { + String channelId = String.valueOf(Long.parseLong(paramChannelId)); + params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId); + } + String paramStartTime = uri.getQueryParameter(TvContractCompat.PARAM_START_TIME); + String paramEndTime = uri.getQueryParameter(TvContractCompat.PARAM_END_TIME); + if (paramStartTime != null && paramEndTime != null) { + String startTime = String.valueOf(Long.parseLong(paramStartTime)); + String endTime = String.valueOf(Long.parseLong(paramEndTime)); + params.appendWhere( + Programs.COLUMN_START_TIME_UTC_MILLIS + + "<=? AND " + + Programs.COLUMN_END_TIME_UTC_MILLIS + + ">=? AND ?<=?", + endTime, + startTime, + startTime, + endTime); + } + break; + case MATCH_PROGRAM_ID: + params.setTables(PROGRAMS_TABLE); + params.appendWhere(Programs._ID + "=?", uri.getLastPathSegment()); + break; + case MATCH_WATCHED_PROGRAM: + if (!callerHasAccessWatchedProgramsPermission()) { + throw new SecurityException("Access not allowed for " + uri); + } + params.setTables(WATCHED_PROGRAMS_TABLE); + params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1"); + break; + case MATCH_WATCHED_PROGRAM_ID: + if (!callerHasAccessWatchedProgramsPermission()) { + throw new SecurityException("Access not allowed for " + uri); + } + params.setTables(WATCHED_PROGRAMS_TABLE); + params.appendWhere(WatchedPrograms._ID + "=?", uri.getLastPathSegment()); + params.appendWhere(WATCHED_PROGRAMS_COLUMN_CONSOLIDATED + "=?", "1"); + break; + case MATCH_RECORDED_PROGRAM_ID: + params.appendWhere(RecordedPrograms._ID + "=?", uri.getLastPathSegment()); + // fall-through + case MATCH_RECORDED_PROGRAM: + params.setTables(RECORDED_PROGRAMS_TABLE); + paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL); + if (paramChannelId != null) { + String channelId = String.valueOf(Long.parseLong(paramChannelId)); + params.appendWhere(Programs.COLUMN_CHANNEL_ID + "=?", channelId); + } + break; + case MATCH_PREVIEW_PROGRAM_ID: + params.appendWhere(PreviewPrograms._ID + "=?", uri.getLastPathSegment()); + // fall-through + case MATCH_PREVIEW_PROGRAM: + params.setTables(PREVIEW_PROGRAMS_TABLE); + paramChannelId = uri.getQueryParameter(TvContractCompat.PARAM_CHANNEL); + if (paramChannelId != null) { + String channelId = String.valueOf(Long.parseLong(paramChannelId)); + params.appendWhere(PreviewPrograms.COLUMN_CHANNEL_ID + "=?", channelId); + } + break; + case MATCH_WATCH_NEXT_PROGRAM_ID: + params.appendWhere(WatchNextPrograms._ID + "=?", uri.getLastPathSegment()); + // fall-through + case MATCH_WATCH_NEXT_PROGRAM: + params.setTables(WATCH_NEXT_PROGRAMS_TABLE); + break; + case MATCH_CHANNEL_ID_LOGO: + if (operation.equals(OP_DELETE)) { + params.setTables(CHANNELS_TABLE); + params.appendWhere(Channels._ID + "=?", uri.getPathSegments().get(1)); + break; + } + // fall-through + case MATCH_PASSTHROUGH_ID: + throw new UnsupportedOperationException(operation + " not permmitted on " + uri); + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + return params; + } + + private static String capitalize(String str) { + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } + + @SuppressLint("DefaultLocale") + private void checkAndConvertGenre(ContentValues values) { + String canonicalGenres = values.getAsString(Programs.COLUMN_CANONICAL_GENRE); + + if (!TextUtils.isEmpty(canonicalGenres)) { + // Check if the canonical genres are valid. If not, clear them. + String[] genres = Genres.decode(canonicalGenres); + for (String genre : genres) { + if (!Genres.isCanonical(genre)) { + values.putNull(Programs.COLUMN_CANONICAL_GENRE); + canonicalGenres = null; + break; + } + } + } + + if (TextUtils.isEmpty(canonicalGenres)) { + // If the canonical genre is not set, try to map the broadcast genre to the canonical + // genre. + String broadcastGenres = values.getAsString(Programs.COLUMN_BROADCAST_GENRE); + if (!TextUtils.isEmpty(broadcastGenres)) { + Set genreSet = new HashSet<>(); + String[] genres = Genres.decode(broadcastGenres); + for (String genre : genres) { + String canonicalGenre = sGenreMap.get(genre.toUpperCase()); + if (Genres.isCanonical(canonicalGenre)) { + genreSet.add(canonicalGenre); + } + } + if (genreSet.size() > 0) { + values.put( + Programs.COLUMN_CANONICAL_GENRE, + Genres.encode(genreSet.toArray(new String[genreSet.size()]))); + } + } + } + } + + private void checkAndConvertDeprecatedColumns(ContentValues values) { + if (values.containsKey(Programs.COLUMN_SEASON_NUMBER)) { + if (!values.containsKey(Programs.COLUMN_SEASON_DISPLAY_NUMBER)) { + values.put( + Programs.COLUMN_SEASON_DISPLAY_NUMBER, + values.getAsInteger(Programs.COLUMN_SEASON_NUMBER)); + } + values.remove(Programs.COLUMN_SEASON_NUMBER); + } + if (values.containsKey(Programs.COLUMN_EPISODE_NUMBER)) { + if (!values.containsKey(Programs.COLUMN_EPISODE_DISPLAY_NUMBER)) { + values.put( + Programs.COLUMN_EPISODE_DISPLAY_NUMBER, + values.getAsInteger(Programs.COLUMN_EPISODE_NUMBER)); + } + values.remove(Programs.COLUMN_EPISODE_NUMBER); + } + } + + // We might have more than one thread trying to make its way through applyBatch() so the + // notification coalescing needs to be thread-local to work correctly. + private final ThreadLocal> mTLBatchNotifications = new ThreadLocal<>(); + + private Set getBatchNotificationsSet() { + return mTLBatchNotifications.get(); + } + + private void setBatchNotificationsSet(Set batchNotifications) { + mTLBatchNotifications.set(batchNotifications); + } + + @Override + public ContentProviderResult[] applyBatch(ArrayList operations) + throws OperationApplicationException { + setBatchNotificationsSet(new HashSet()); + Context context = getContext(); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + ContentProviderResult[] results = super.applyBatch(operations); + db.setTransactionSuccessful(); + return results; + } finally { + db.endTransaction(); + final Set notifications = getBatchNotificationsSet(); + setBatchNotificationsSet(null); + for (final Uri uri : notifications) { + context.getContentResolver().notifyChange(uri, null); + } + } + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + setBatchNotificationsSet(new HashSet()); + Context context = getContext(); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + int result = super.bulkInsert(uri, values); + db.setTransactionSuccessful(); + return result; + } finally { + db.endTransaction(); + final Set notifications = getBatchNotificationsSet(); + setBatchNotificationsSet(null); + for (final Uri notificationUri : notifications) { + context.getContentResolver().notifyChange(notificationUri, null); + } + } + } + + private void notifyChange(Uri uri) { + final Set batchNotifications = getBatchNotificationsSet(); + if (batchNotifications != null) { + batchNotifications.add(uri); + } else { + getContext().getContentResolver().notifyChange(uri, null); + } + } + + private boolean callerHasReadTvListingsPermission() { + return getContext().checkCallingOrSelfPermission(PERMISSION_READ_TV_LISTINGS) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean callerHasAccessAllEpgDataPermission() { + return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_ALL_EPG_DATA) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean callerHasAccessWatchedProgramsPermission() { + return getContext().checkCallingOrSelfPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean callerHasModifyParentalControlsPermission() { + return getContext() + .checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) + == PackageManager.PERMISSION_GRANTED; + } + + private void blockIllegalAccessToIdAndPackageName(Uri uri, ContentValues values) { + if (values.containsKey(BaseColumns._ID)) { + int match = sUriMatcher.match(uri); + switch (match) { + case MATCH_CHANNEL_ID: + case MATCH_PROGRAM_ID: + case MATCH_PREVIEW_PROGRAM_ID: + case MATCH_RECORDED_PROGRAM_ID: + case MATCH_WATCH_NEXT_PROGRAM_ID: + case MATCH_WATCHED_PROGRAM_ID: + if (TextUtils.equals( + values.getAsString(BaseColumns._ID), uri.getLastPathSegment())) { + break; + } + // fall through + default: + throw new IllegalArgumentException("Not allowed to change ID."); + } + } + if (values.containsKey(BaseTvColumns.COLUMN_PACKAGE_NAME) + && !callerHasAccessAllEpgDataPermission() + && !TextUtils.equals( + values.getAsString(BaseTvColumns.COLUMN_PACKAGE_NAME), + getCallingPackage_())) { + throw new SecurityException("Not allowed to change package name."); + } + } + + private void blockIllegalAccessToChannelsSystemColumns(ContentValues values) { + if (values.containsKey(Channels.COLUMN_LOCKED) + && !callerHasModifyParentalControlsPermission()) { + throw new SecurityException("Not allowed to access Channels.COLUMN_LOCKED"); + } + Boolean hasAccessAllEpgDataPermission = null; + if (values.containsKey(Channels.COLUMN_BROWSABLE)) { + hasAccessAllEpgDataPermission = callerHasAccessAllEpgDataPermission(); + if (!hasAccessAllEpgDataPermission) { + throw new SecurityException("Not allowed to access Channels.COLUMN_BROWSABLE"); + } + } + } + + private void blockIllegalAccessToPreviewProgramsSystemColumns(ContentValues values) { + if (values.containsKey(PreviewPrograms.COLUMN_BROWSABLE) + && !callerHasAccessAllEpgDataPermission()) { + throw new SecurityException("Not allowed to access Programs.COLUMN_BROWSABLE"); + } + } + + private void blockIllegalAccessFromBlockedPackage() { + String callingPackageName = getCallingPackage_(); + if (sBlockedPackages.containsKey(callingPackageName)) { + throw new SecurityException( + "Not allowed to access " + + TvContractCompat.AUTHORITY + + ", " + + callingPackageName + + " is blocked"); + } + } + + private boolean disallowModifyChannelType(ContentValues values, SqlParams params) { + if (values.containsKey(Channels.COLUMN_TYPE)) { + params.appendWhere( + Channels.COLUMN_TYPE + "=?", values.getAsString(Channels.COLUMN_TYPE)); + return true; + } + return false; + } + + private boolean disallowModifyChannelId(ContentValues values, SqlParams params) { + if (values.containsKey(PreviewPrograms.COLUMN_CHANNEL_ID)) { + params.appendWhere( + PreviewPrograms.COLUMN_CHANNEL_ID + "=?", + values.getAsString(PreviewPrograms.COLUMN_CHANNEL_ID)); + return true; + } + return false; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + switch (sUriMatcher.match(uri)) { + case MATCH_CHANNEL_ID_LOGO: + return openLogoFile(uri, mode); + default: + throw new FileNotFoundException(uri.toString()); + } + } + + private ParcelFileDescriptor openLogoFile(Uri uri, String mode) throws FileNotFoundException { + long channelId = Long.parseLong(uri.getPathSegments().get(1)); + + SqlParams params = + new SqlParams(CHANNELS_TABLE, Channels._ID + "=?", String.valueOf(channelId)); + if (!callerHasAccessAllEpgDataPermission()) { + if (callerHasReadTvListingsPermission()) { + params.appendWhere( + Channels.COLUMN_PACKAGE_NAME + "=? OR " + Channels.COLUMN_SEARCHABLE + "=?", + getCallingPackage_(), + "1"); + } else { + params.appendWhere(Channels.COLUMN_PACKAGE_NAME + "=?", getCallingPackage_()); + } + } + + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables(params.getTables()); + + // We don't write the database here. + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + if (mode.equals("r")) { + String sql = + queryBuilder.buildQuery( + new String[] {CHANNELS_COLUMN_LOGO}, + params.getSelection(), + null, + null, + null, + null); + ParcelFileDescriptor fd = + DatabaseUtils.blobFileDescriptorForQuery(db, sql, params.getSelectionArgs()); + if (fd == null) { + throw new FileNotFoundException(uri.toString()); + } + return fd; + } else { + try (Cursor cursor = + queryBuilder.query( + db, + new String[] {Channels._ID}, + params.getSelection(), + params.getSelectionArgs(), + null, + null, + null)) { + if (cursor.getCount() < 1) { + // Fails early if corresponding channel does not exist. + // PipeMonitor may still fail to update DB later. + throw new FileNotFoundException(uri.toString()); + } + } + + try { + ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createPipe(); + PipeMonitor pipeMonitor = new PipeMonitor(pipeFds[0], channelId, params); + pipeMonitor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return pipeFds[1]; + } catch (IOException ioe) { + FileNotFoundException fne = new FileNotFoundException(uri.toString()); + fne.initCause(ioe); + throw fne; + } + } + } + + /** + * Validates the sort order based on the given field set. + * + * @throws IllegalArgumentException if there is any unknown field. + */ + @SuppressLint("DefaultLocale") + private static void validateSortOrder(String sortOrder, Set possibleFields) { + if (TextUtils.isEmpty(sortOrder) || possibleFields.isEmpty()) { + return; + } + String[] orders = sortOrder.split(","); + for (String order : orders) { + String field = + order.replaceAll("\\s+", " ") + .trim() + .toLowerCase() + .replace(" asc", "") + .replace(" desc", ""); + if (!possibleFields.contains(field)) { + throw new IllegalArgumentException("Illegal field in sort order " + order); + } + } + } + + private class PipeMonitor extends AsyncTask { + private final ParcelFileDescriptor mPfd; + private final long mChannelId; + private final SqlParams mParams; + + private PipeMonitor(ParcelFileDescriptor pfd, long channelId, SqlParams params) { + mPfd = pfd; + mChannelId = channelId; + mParams = params; + } + + @Override + protected Void doInBackground(Void... params) { + int count = 0; + try (AutoCloseInputStream is = new AutoCloseInputStream(mPfd); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + Bitmap bitmap = BitmapFactory.decodeStream(is); + if (bitmap == null) { + Log.e(TAG, "Failed to decode logo image for channel ID " + mChannelId); + return null; + } + + float scaleFactor = + Math.min( + 1f, + ((float) MAX_LOGO_IMAGE_SIZE) + / Math.max(bitmap.getWidth(), bitmap.getHeight())); + if (scaleFactor < 1f) { + bitmap = + Bitmap.createScaledBitmap( + bitmap, + (int) (bitmap.getWidth() * scaleFactor), + (int) (bitmap.getHeight() * scaleFactor), + false); + } + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + byte[] bytes = baos.toByteArray(); + + ContentValues values = new ContentValues(); + values.put(CHANNELS_COLUMN_LOGO, bytes); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + count = + db.update( + mParams.getTables(), + values, + mParams.getSelection(), + mParams.getSelectionArgs()); + if (count > 0) { + Uri uri = TvContractCompat.buildChannelLogoUri(mChannelId); + notifyChange(uri); + } + } catch (IOException e) { + Log.e(TAG, "Failed to write logo for channel ID " + mChannelId, e); + + } finally { + if (count == 0) { + try { + mPfd.closeWithError("Failed to write logo for channel ID " + mChannelId); + } catch (IOException ioe) { + Log.e(TAG, "Failed to close pipe", ioe); + } + } + } + return null; + } + } + + /** + * Column definitions for the TV programs that the user watched. Applications do not have access + * to this table. + * + *

+ * + *

By default, the query results will be sorted by {@link + * WatchedPrograms#COLUMN_WATCH_START_TIME_UTC_MILLIS} in descending order. + * + * @hide + */ + public static final class WatchedPrograms implements BaseTvColumns { + + /** The content:// style URI for this table. */ + public static final Uri CONTENT_URI = + Uri.parse("content://" + TvContract.AUTHORITY + "/watched_program"); + + /** The MIME type of a directory of watched programs. */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/watched_program"; + + /** The MIME type of a single item in this table. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watched_program"; + + /** + * The UTC time that the user started watching this TV program, in milliseconds since the + * epoch. + * + *

+ * + *

Type: INTEGER (long) + */ + public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS = + "watch_start_time_utc_millis"; + + /** + * The UTC time that the user stopped watching this TV program, in milliseconds since the + * epoch. + * + *

+ * + *

Type: INTEGER (long) + */ + public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis"; + + /** + * The ID of the TV channel that provides this TV program. + * + *

+ * + *

This is a required field. + * + *

+ * + *

Type: INTEGER (long) + */ + public static final String COLUMN_CHANNEL_ID = "channel_id"; + + /** + * The title of this TV program. + * + *

+ * + *

Type: TEXT + */ + public static final String COLUMN_TITLE = "title"; + + /** + * The start time of this TV program, in milliseconds since the epoch. + * + *

+ * + *

Type: INTEGER (long) + */ + public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; + + /** + * The end time of this TV program, in milliseconds since the epoch. + * + *

+ * + *

Type: INTEGER (long) + */ + public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; + + /** + * The description of this TV program. + * + *

+ * + *

Type: TEXT + */ + public static final String COLUMN_DESCRIPTION = "description"; + + /** + * Extra parameters given to {@link TvInputService.Session#tune(Uri, android.os.Bundle) + * TvInputService.Session.tune(Uri, android.os.Bundle)} when tuning to the channel that + * provides this TV program. (Used internally.) + * + *

+ * + *

This column contains an encoded string that represents comma-separated key-value pairs + * of the tune parameters. (Ex. "[key1]=[value1], [key2]=[value2]"). '%' is used as an + * escape character for '%', '=', and ','. + * + *

+ * + *

Type: TEXT + */ + public static final String COLUMN_INTERNAL_TUNE_PARAMS = "tune_params"; + + /** + * The session token of this TV program. (Used internally.) + * + *

+ * + *

This contains a String representation of {@link IBinder} for {@link + * TvInputService.Session} that provides the current TV program. It is used internally to + * distinguish watched programs entries from different TV input sessions. + * + *

+ * + *

Type: TEXT + */ + public static final String COLUMN_INTERNAL_SESSION_TOKEN = "session_token"; + + private WatchedPrograms() {} + } +} diff --git a/tests/common/src/com/android/tv/testing/ProgramInfo.java b/tests/common/src/com/android/tv/testing/ProgramInfo.java deleted file mode 100644 index 08742aed..00000000 --- a/tests/common/src/com/android/tv/testing/ProgramInfo.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.testing; - -import android.content.Context; -import android.database.Cursor; -import android.media.tv.TvContentRating; -import android.media.tv.TvContract; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -public final class ProgramInfo { - /** If this is specify for title, it will be generated by adding index. */ - public static final String GEN_TITLE = ""; - - /** - * If this is specify for episode title, it will be generated by adding index. Also, season and - * episode numbers would be generated, too. see: {@link #build} for detail. - */ - public static final String GEN_EPISODE = ""; - - private static final int SEASON_MAX = 10; - private static final int EPISODE_MAX = 12; - - /** - * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in - * order. - */ - public static final String GEN_POSTER = "GEN"; - - private static final int[] POSTER_ARTS_RES = { - 0, - R.drawable.blue, - R.drawable.red_large, - R.drawable.green, - R.drawable.red, - R.drawable.green_large, - R.drawable.blue_small - }; - - /** - * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order. - */ - public static final int GEN_DURATION = -1; - - private static final long[] DURATIONS_MS = { - TimeUnit.MINUTES.toMillis(15), - TimeUnit.MINUTES.toMillis(45), - TimeUnit.MINUTES.toMillis(90), - TimeUnit.MINUTES.toMillis(60), - TimeUnit.MINUTES.toMillis(30), - TimeUnit.MINUTES.toMillis(45), - TimeUnit.MINUTES.toMillis(60), - TimeUnit.MINUTES.toMillis(90), - TimeUnit.HOURS.toMillis(5) - }; - private static long DURATIONS_SUM_MS; - - static { - DURATIONS_SUM_MS = 0; - for (long duration : DURATIONS_MS) { - DURATIONS_SUM_MS += duration; - } - } - - /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */ - public static final String GEN_GENRE = "GEN"; - - private static final String[] GENRES = { - "", - TvContract.Programs.Genres.SPORTS, - TvContract.Programs.Genres.NEWS, - TvContract.Programs.Genres.SHOPPING, - TvContract.Programs.Genres.DRAMA, - TvContract.Programs.Genres.ENTERTAINMENT - }; - - public final String title; - public final String episode; - public final int seasonNumber; - public final int episodeNumber; - public final String posterArtUri; - public final String description; - public final long durationMs; - public final String genre; - public final TvContentRating[] contentRatings; - public final String resourceUri; - - public static ProgramInfo fromCursor(Cursor c) { - // TODO: Fill other fields. - Builder builder = new Builder(); - int index = c.getColumnIndex(TvContract.Programs.COLUMN_TITLE); - if (index >= 0) { - builder.setTitle(c.getString(index)); - } - index = c.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION); - if (index >= 0) { - builder.setDescription(c.getString(index)); - } - index = c.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE); - if (index >= 0) { - builder.setEpisode(c.getString(index)); - } - return builder.build(); - } - - public ProgramInfo( - String title, - String episode, - int seasonNumber, - int episodeNumber, - String posterArtUri, - String description, - long durationMs, - TvContentRating[] contentRatings, - String genre, - String resourceUri) { - this.title = title; - this.episode = episode; - this.seasonNumber = seasonNumber; - this.episodeNumber = episodeNumber; - this.posterArtUri = posterArtUri; - this.description = description; - this.durationMs = durationMs; - this.contentRatings = contentRatings; - this.genre = genre; - this.resourceUri = resourceUri; - } - - /** - * Create a instance of {@link ProgramInfo} whose content will be generated as much as possible. - */ - public static ProgramInfo create() { - return new Builder().build(); - } - - /** - * Get index of the program whose start time equals or less than {@code timeMs} and end time - * more than {@code timeMs}. - * - * @param timeMs target time in millis to find a program. - * @param channelId used to add complexity to the index between two consequence channels. - */ - public int getIndex(long timeMs, long channelId) { - if (durationMs != GEN_DURATION) { - return Math.max((int) (timeMs / durationMs), 0); - } - long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]; - int index = (int) ((timeMs - startTimeMs) / DURATIONS_SUM_MS) * DURATIONS_MS.length; - startTimeMs += (index / DURATIONS_MS.length) * DURATIONS_SUM_MS; - while (startTimeMs + DURATIONS_MS[index % DURATIONS_MS.length] < timeMs) { - startTimeMs += DURATIONS_MS[index % DURATIONS_MS.length]; - index++; - } - return index; - } - - /** - * Returns the start time for the program with the position. - * - * @param index index returned by {@link #getIndex} - */ - public long getStartTimeMs(int index, long channelId) { - if (durationMs != GEN_DURATION) { - return index * durationMs; - } - long startTimeMs = - channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))] - + (index / DURATIONS_MS.length) * DURATIONS_SUM_MS; - for (int i = 0; i < index % DURATIONS_MS.length; i++) { - startTimeMs += DURATIONS_MS[i]; - } - return startTimeMs; - } - - /** - * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link - * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}. - * - * @param index index returned by {@link #getIndex} - */ - public ProgramInfo build(Context context, int index) { - if (!GEN_TITLE.equals(title) - && episode == null - && !GEN_POSTER.equals(posterArtUri) - && durationMs != GEN_DURATION - && !GEN_GENRE.equals(genre)) { - return this; - } - return new ProgramInfo( - GEN_TITLE.equals(title) ? "Title(" + index + ")" : title, - GEN_EPISODE.equals(episode) ? "Episode(" + index + ")" : episode, - episode != null ? (index % SEASON_MAX + 1) : seasonNumber, - episode != null ? (index % EPISODE_MAX + 1) : episodeNumber, - GEN_POSTER.equals(posterArtUri) - ? Utils.getUriStringForResource( - context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length]) - : posterArtUri, - description, - durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs, - contentRatings, - GEN_GENRE.equals(genre) ? GENRES[index % GENRES.length] : genre, - resourceUri); - } - - @Override - public String toString() { - return "ProgramInfo{title=" - + title - + ", episode=" - + episode - + ", durationMs=" - + durationMs - + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ProgramInfo that = (ProgramInfo) o; - return Objects.equals(seasonNumber, that.seasonNumber) - && Objects.equals(episodeNumber, that.episodeNumber) - && Objects.equals(durationMs, that.durationMs) - && Objects.equals(title, that.title) - && Objects.equals(episode, that.episode) - && Objects.equals(posterArtUri, that.posterArtUri) - && Objects.equals(description, that.description) - && Objects.equals(genre, that.genre) - && Objects.equals(contentRatings, that.contentRatings) - && Objects.equals(resourceUri, that.resourceUri); - } - - @Override - public int hashCode() { - return Objects.hash(title, episode, seasonNumber, episodeNumber); - } - - public static class Builder { - private String mTitle = GEN_TITLE; - private String mEpisode = GEN_EPISODE; - private int mSeasonNumber; - private int mEpisodeNumber; - private String mPosterArtUri = GEN_POSTER; - private String mDescription; - private long mDurationMs = GEN_DURATION; - private TvContentRating[] mContentRatings; - private String mGenre = GEN_GENRE; - private String mResourceUri; - - public Builder setTitle(String title) { - mTitle = title; - return this; - } - - public Builder setEpisode(String episode) { - mEpisode = episode; - return this; - } - - public Builder setSeasonNumber(int seasonNumber) { - mSeasonNumber = seasonNumber; - return this; - } - - public Builder setEpisodeNumber(int episodeNumber) { - mEpisodeNumber = episodeNumber; - return this; - } - - public Builder setPosterArtUri(String posterArtUri) { - mPosterArtUri = posterArtUri; - return this; - } - - public Builder setDescription(String description) { - mDescription = description; - return this; - } - - public Builder setDurationMs(long durationMs) { - mDurationMs = durationMs; - return this; - } - - public Builder setContentRatings(TvContentRating[] contentRatings) { - mContentRatings = contentRatings; - return this; - } - - public Builder setGenre(String genre) { - mGenre = genre; - return this; - } - - public Builder setResourceUri(String resourceUri) { - mResourceUri = resourceUri; - return this; - } - - public ProgramInfo build() { - return new ProgramInfo( - mTitle, - mEpisode, - mSeasonNumber, - mEpisodeNumber, - mPosterArtUri, - mDescription, - mDurationMs, - mContentRatings, - mGenre, - mResourceUri); - } - } -} diff --git a/tests/common/src/com/android/tv/testing/ProgramUtils.java b/tests/common/src/com/android/tv/testing/ProgramUtils.java deleted file mode 100644 index 3be9d10c..00000000 --- a/tests/common/src/com/android/tv/testing/ProgramUtils.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.testing; - -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.media.tv.TvContract; -import android.media.tv.TvContract.Programs; -import android.net.Uri; -import android.util.Log; -import com.android.tv.common.TvContentRatingCache; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -public class ProgramUtils { - private static final String TAG = "ProgramUtils"; - private static final boolean DEBUG = false; - - // Populate program data for a week. - private static final long PROGRAM_INSERT_DURATION_MS = TimeUnit.DAYS.toMillis(7); - private static final int MAX_DB_INSERT_COUNT_AT_ONCE = 500; - - /** - * Populate programs by repeating given program information. This method will populate programs - * without any gap nor overlapping starting from the current time. - */ - public static void populatePrograms(Context context, Uri channelUri, ProgramInfo program) { - ContentValues values = new ContentValues(); - long channelId = ContentUris.parseId(channelUri); - - values.put(Programs.COLUMN_CHANNEL_ID, channelId); - values.put(Programs.COLUMN_SHORT_DESCRIPTION, program.description); - values.put( - Programs.COLUMN_CONTENT_RATING, - TvContentRatingCache.contentRatingsToString(program.contentRatings)); - - long currentTimeMs = System.currentTimeMillis(); - long targetEndTimeMs = currentTimeMs + PROGRAM_INSERT_DURATION_MS; - long timeMs = getLastProgramEndTimeMs(context, channelUri, currentTimeMs, targetEndTimeMs); - if (timeMs <= 0) { - timeMs = currentTimeMs; - } - int index = program.getIndex(timeMs, channelId); - timeMs = program.getStartTimeMs(index, channelId); - - ArrayList list = new ArrayList<>(); - while (timeMs < targetEndTimeMs) { - ProgramInfo programAt = program.build(context, index++); - values.put(Programs.COLUMN_TITLE, programAt.title); - values.put(Programs.COLUMN_EPISODE_TITLE, programAt.episode); - if (programAt.seasonNumber != 0) { - values.put(Programs.COLUMN_SEASON_NUMBER, programAt.seasonNumber); - } - if (programAt.episodeNumber != 0) { - values.put(Programs.COLUMN_EPISODE_NUMBER, programAt.episodeNumber); - } - values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri); - values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, timeMs); - values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, timeMs + programAt.durationMs); - values.put(Programs.COLUMN_CANONICAL_GENRE, programAt.genre); - values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri); - list.add(new ContentValues(values)); - timeMs += programAt.durationMs; - - if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) { - try { - context.getContentResolver() - .bulkInsert( - Programs.CONTENT_URI, - list.toArray(new ContentValues[list.size()])); - } catch (SQLiteException e) { - Log.e(TAG, "Can't insert EPG.", e); - return; - } - if (DEBUG) Log.d(TAG, "Inserted " + list.size() + " programs for " + channelUri); - list.clear(); - } - } - } - - private static long getLastProgramEndTimeMs( - Context context, Uri channelUri, long startTimeMs, long endTimeMs) { - Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs, endTimeMs); - String[] projection = {Programs.COLUMN_END_TIME_UTC_MILLIS}; - try (Cursor cursor = - context.getContentResolver().query(uri, projection, null, null, null)) { - if (cursor != null && cursor.moveToLast()) { - return cursor.getLong(0); - } - } - return 0; - } - - private ProgramUtils() {} -} diff --git a/tests/common/src/com/android/tv/testing/SingletonProvider.java b/tests/common/src/com/android/tv/testing/SingletonProvider.java new file mode 100644 index 00000000..d9c2d409 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/SingletonProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import javax.inject.Provider; + +/** A Provider that always returns the same instance. */ +public class SingletonProvider implements Provider { + private final T t; + + private SingletonProvider(T t) { + this.t = t; + } + + @Override + public T get() { + return t; + } + + public static Provider create(T t) { + return new SingletonProvider(t); + } +} diff --git a/tests/common/src/com/android/tv/testing/TestSingletonApp.java b/tests/common/src/com/android/tv/testing/TestSingletonApp.java new file mode 100644 index 00000000..a4a39c18 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/TestSingletonApp.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing; + +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import com.android.tv.InputSessionManager; +import com.android.tv.MainActivityWrapper; +import com.android.tv.TvSingletons; +import com.android.tv.analytics.Analytics; +import com.android.tv.analytics.Tracker; +import com.android.tv.common.BaseApplication; +import com.android.tv.common.config.api.RemoteConfig; +import com.android.tv.common.experiments.ExperimentLoader; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.util.Clock; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.PreviewDataManager; +import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.epg.EpgFetcher; +import com.android.tv.data.epg.EpgReader; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; +import com.android.tv.dvr.DvrScheduleManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.recorder.RecordingScheduler; +import com.android.tv.perf.PerformanceMonitor; +import com.android.tv.tuner.TunerInputController; +import com.android.tv.util.SetupUtils; +import com.android.tv.util.TvInputManagerHelper; +import com.android.tv.util.account.AccountHelper; +import javax.inject.Provider; + +/** Test application for Live TV. */ +public class TestSingletonApp extends Application implements TvSingletons { + public final FakeClock fakeClock = FakeClock.createWithCurrentTime(); + public final FakeEpgReader epgReader = new FakeEpgReader(fakeClock); + public final FakeRemoteConfig remoteConfig = new FakeRemoteConfig(); + public final FakeEpgFetcher epgFetcher = new FakeEpgFetcher(); + + public FakeTvInputManagerHelper tvInputManagerHelper; + public SetupUtils setupUtils; + + private final Provider mEpgReaderProvider = SingletonProvider.create(epgReader); + private TunerInputController mTunerInputController; + + @Override + public void onCreate() { + super.onCreate(); + mTunerInputController = + new TunerInputController( + ComponentName.unflattenFromString(getEmbeddedTunerInputId())); + + tvInputManagerHelper = new FakeTvInputManagerHelper(this); + setupUtils = SetupUtils.createForTvSingletons(this); + tvInputManagerHelper.start(); + // HACK reset the singleton for tests + BaseApplication.sSingletons = this; + } + + @Override + public Analytics getAnalytics() { + return null; + } + + @Override + public void handleInputCountChanged() {} + + @Override + public ChannelDataManager getChannelDataManager() { + return new ChannelDataManager(this, tvInputManagerHelper); + } + + @Override + public boolean isChannelDataManagerLoadFinished() { + return false; + } + + @Override + public ProgramDataManager getProgramDataManager() { + return null; + } + + @Override + public boolean isProgramDataManagerCurrentProgramsLoadFinished() { + return false; + } + + @Override + public PreviewDataManager getPreviewDataManager() { + return null; + } + + @Override + public DvrDataManager getDvrDataManager() { + return null; + } + + @Override + public DvrScheduleManager getDvrScheduleManager() { + return null; + } + + @Override + public DvrManager getDvrManager() { + return null; + } + + @Override + public RecordingScheduler getRecordingScheduler() { + return null; + } + + @Override + public DvrWatchedPositionManager getDvrWatchedPositionManager() { + return null; + } + + @Override + public InputSessionManager getInputSessionManager() { + return null; + } + + @Override + public Tracker getTracker() { + return null; + } + + @Override + public TvInputManagerHelper getTvInputManagerHelper() { + return tvInputManagerHelper; + } + + @Override + public Provider providesEpgReader() { + return mEpgReaderProvider; + } + + @Override + public EpgFetcher getEpgFetcher() { + return epgFetcher; + } + + @Override + public SetupUtils getSetupUtils() { + return setupUtils; + } + + @Override + public TunerInputController getTunerInputController() { + return mTunerInputController; + } + + @Override + public ExperimentLoader getExperimentLoader() { + return new ExperimentLoader(); + } + + @Override + public MainActivityWrapper getMainActivityWrapper() { + return null; + } + + @Override + public AccountHelper getAccountHelper() { + return null; + } + + @Override + public Clock getClock() { + return fakeClock; + } + + @Override + public RecordingStorageStatusManager getRecordingStorageStatusManager() { + return null; + } + + @Override + public RemoteConfig getRemoteConfig() { + return remoteConfig; + } + + @Override + public Intent getTunerSetupIntent(Context context) { + return null; + } + + @Override + public boolean isRunningInMainProcess() { + return false; + } + + @Override + public PerformanceMonitor getPerformanceMonitor() { + return null; + } + + @Override + public String getEmbeddedTunerInputId() { + return "com.android.tv/.tuner.tvinput.TunerTvInputService"; + } +} diff --git a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java b/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java deleted file mode 100644 index 30382209..00000000 --- a/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.testing; - -import android.media.tv.TvContentRating; - -/** Constants for the content rating strings. */ -public final class TvContentRatingConstants { - /** - * A content rating object. - * - *

Domain: com.android.tv - * - *

Rating system: US_TV - * - *

Rating: US_TV_Y7 - * - *

Sub ratings: US_TV_FV - */ - public static final TvContentRating CONTENT_RATING_US_TV_Y7_US_TV_FV = - TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7", "US_TV_FV"); - - public static final String STRING_US_TV_Y7_US_TV_FV = "com.android.tv/US_TV/US_TV_Y7/US_TV_FV"; - - /** - * A content rating object. - * - *

Domain: com.android.tv - * - *

Rating system: US_TV - * - *

Rating: US_TV_MA - */ - public static final TvContentRating CONTENT_RATING_US_TV_MA = - TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_MA"); - - public static final String STRING_US_TV_MA = "com.android.tv/US_TV/US_TV_MA"; - - /** - * A content rating object. - * - *

Domain: com.android.tv - * - *

Rating system: US_TV - * - *

Rating: US_TV_PG - * - *

Sub ratings: US_TV_L, US_TV_S - */ - public static final TvContentRating CONTENT_RATING_US_TV_PG_US_TV_L_US_TV_S = - TvContentRating.createRating( - "com.android.tv", "US_TV", "US_TV_PG", "US_TV_L", "US_TV_S"); -} diff --git a/tests/common/src/com/android/tv/testing/Utils.java b/tests/common/src/com/android/tv/testing/Utils.java deleted file mode 100644 index a250995a..00000000 --- a/tests/common/src/com/android/tv/testing/Utils.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.testing; - -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; -import android.net.Uri; -import android.util.Log; -import com.android.tv.common.TvCommonUtils; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; - -/** - * An utility class for testing. - * - *

This class is also used to check whether TV app is running in tests or not. - * - * @see TvCommonUtils#isRunningInTest - */ -public final class Utils { - private static final String TAG = "Utils"; - - private static final long DEFAULT_RANDOM_SEED = getSeed(); - - public static String getUriStringForResource(Context context, int resId) { - if (resId == 0) { - return ""; - } - Resources res = context.getResources(); - return new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(res.getResourcePackageName(resId)) - .path(res.getResourceTypeName(resId)) - .appendPath(res.getResourceEntryName(resId)) - .build() - .toString(); - } - - public static void copy(InputStream is, OutputStream os) throws IOException { - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - } - - public static String getServiceNameFromInputId(Context context, String inputId) { - TvInputManager tim = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - for (TvInputInfo info : tim.getTvInputList()) { - if (info.getId().equals(inputId)) { - return info.getServiceInfo().name; - } - } - return null; - } - - public static String getInputIdFromComponentName(Context context, ComponentName name) { - TvInputManager tim = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); - for (TvInputInfo info : tim.getTvInputList()) { - ServiceInfo si = info.getServiceInfo(); - if (new ComponentName(si.packageName, si.name).equals(name)) { - return info.getId(); - } - } - return null; - } - - /** - * Return the Random class which is needed to make random data for testing. Default seed of the - * random is today's date. - */ - public static Random createTestRandom() { - return new Random(DEFAULT_RANDOM_SEED); - } - - private static long getSeed() { - // Set random seed as the date to track failed test data easily. - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); - String today = dateFormat.format(new Date()); - Log.d(TAG, "Today's random seed is " + today); - return Long.valueOf(today); - } - - private Utils() {} - - /** Checks whether TvActivity is enabled or not. */ - public static boolean isTvActivityEnabled(Context context) { - PackageManager pm = context.getPackageManager(); - ComponentName name = new ComponentName("com.android.tv", "com.android.tv.TvActivity"); - int enabled = pm.getComponentEnabledSetting(name); - return enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED - || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - } -} diff --git a/tests/common/src/com/android/tv/testing/constants/Constants.java b/tests/common/src/com/android/tv/testing/constants/Constants.java new file mode 100644 index 00000000..09e1ada1 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/constants/Constants.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.testing.constants; + +import android.media.tv.TvTrackInfo; + +/** Constants for testing. */ +public final class Constants { + public static final int FUNC_TEST_CHANNEL_COUNT = 100; + public static final int UNIT_TEST_CHANNEL_COUNT = 4; + public static final int JANK_TEST_CHANNEL_COUNT = 500; // TODO: increase to 1000 see b/23526997 + + public static final TvTrackInfo EN_STEREO_AUDIO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "English Stereo Audio") + .setLanguage("en") + .setAudioChannelCount(2) + .build(); + public static final TvTrackInfo GENERIC_AUDIO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "Generic Audio").build(); + + public static final TvTrackInfo FHD1080P50_VIDEO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "FHD Video") + .setVideoHeight(1080) + .setVideoWidth(1920) + .setVideoFrameRate(50) + .build(); + public static final TvTrackInfo SVGA_VIDEO_TRACK = + new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "SVGA Video") + .setVideoHeight(600) + .setVideoWidth(800) + .build(); + + private Constants() {} +} diff --git a/tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java b/tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java new file mode 100644 index 00000000..e1a3d906 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/constants/TvContentRatingConstants.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.testing.constants; + +import android.media.tv.TvContentRating; + +/** Constants for the content rating strings. */ +public final class TvContentRatingConstants { + /** + * A content rating object. + * + *

Domain: com.android.tv + * + *

Rating system: US_TV + * + *

Rating: US_TV_Y7 + * + *

Sub ratings: US_TV_FV + */ + public static final TvContentRating CONTENT_RATING_US_TV_Y7_US_TV_FV = + TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7", "US_TV_FV"); + + public static final String STRING_US_TV_Y7_US_TV_FV = "com.android.tv/US_TV/US_TV_Y7/US_TV_FV"; + + /** + * A content rating object. + * + *

Domain: com.android.tv + * + *

Rating system: US_TV + * + *

Rating: US_TV_MA + */ + public static final TvContentRating CONTENT_RATING_US_TV_MA = + TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_MA"); + + public static final String STRING_US_TV_MA = "com.android.tv/US_TV/US_TV_MA"; + + /** + * A content rating object. + * + *

Domain: com.android.tv + * + *

Rating system: US_TV + * + *

Rating: US_TV_PG + * + *

Sub ratings: US_TV_L, US_TV_S + */ + public static final TvContentRating CONTENT_RATING_US_TV_PG_US_TV_L_US_TV_S = + TvContentRating.createRating( + "com.android.tv", "US_TV", "US_TV_PG", "US_TV_L", "US_TV_S"); +} diff --git a/tests/common/src/com/android/tv/testing/data/ChannelInfo.java b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java new file mode 100644 index 00000000..e39c057d --- /dev/null +++ b/tests/common/src/com/android/tv/testing/data/ChannelInfo.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.testing.data; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.util.SparseArray; +import java.util.Objects; + +/** Channel Information. */ +public final class ChannelInfo { + private static final SparseArray VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>(); + + static { + VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P); + VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P); + VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, TvContract.Channels.VIDEO_FORMAT_720P); + VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, TvContract.Channels.VIDEO_FORMAT_1080P); + VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, TvContract.Channels.VIDEO_FORMAT_2160P); + VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P); + } + + public static final String[] PROJECTION = { + TvContract.Channels.COLUMN_DISPLAY_NUMBER, + TvContract.Channels.COLUMN_DISPLAY_NAME, + TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, + }; + + public final String number; + public final String name; + public final String logoUrl; + public final int originalNetworkId; + public final int videoWidth; + public final int videoHeight; + public final float videoPixelAspectRatio; + public final int audioChannel; + public final int audioLanguageCount; + public final boolean hasClosedCaption; + public final ProgramInfo program; + public final String appLinkText; + public final int appLinkColor; + public final String appLinkIconUri; + public final String appLinkPosterArtUri; + public final String appLinkIntentUri; + + /** + * Create a channel info for TVTestInput. + * + * @param context a context to insert logo. It can be null if logo isn't needed. + * @param channelNumber a channel number to be use as an identifier. {@link #originalNetworkId} + * will be assigned the same value, too. + */ + public static ChannelInfo create(@Nullable Context context, int channelNumber) { + Builder builder = + new Builder() + .setNumber(String.valueOf(channelNumber)) + .setName("Channel " + channelNumber) + .setOriginalNetworkId(channelNumber); + if (context != null) { + // tests/input/tools/get_test_logos.sh only stores 1000 logos. + builder.setLogoUrl(getUriStringForChannelLogo(context, channelNumber)); + } + return builder.build(); + } + + public static String getUriStringForChannelLogo(Context context, int logoIndex) { + int index = (logoIndex % 1000) + 1; + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(context.getPackageName()) + .path("drawable") + .appendPath("ch_" + index + "_logo") + .build() + .toString(); + } + + public static ChannelInfo fromCursor(Cursor c) { + // TODO: Fill other fields. + Builder builder = new Builder(); + int index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NUMBER); + if (index >= 0) { + builder.setNumber(c.getString(index)); + } + index = c.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NAME); + if (index >= 0) { + builder.setName(c.getString(index)); + } + index = c.getColumnIndex(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID); + if (index >= 0) { + builder.setOriginalNetworkId(c.getInt(index)); + } + return builder.build(); + } + + private ChannelInfo( + String number, + String name, + String logoUrl, + int originalNetworkId, + int videoWidth, + int videoHeight, + float videoPixelAspectRatio, + int audioChannel, + int audioLanguageCount, + boolean hasClosedCaption, + ProgramInfo program, + String appLinkText, + int appLinkColor, + String appLinkIconUri, + String appLinkPosterArtUri, + String appLinkIntentUri) { + this.number = number; + this.name = name; + this.logoUrl = logoUrl; + this.originalNetworkId = originalNetworkId; + this.videoWidth = videoWidth; + this.videoHeight = videoHeight; + this.videoPixelAspectRatio = videoPixelAspectRatio; + this.audioChannel = audioChannel; + this.audioLanguageCount = audioLanguageCount; + this.hasClosedCaption = hasClosedCaption; + this.program = program; + this.appLinkText = appLinkText; + this.appLinkColor = appLinkColor; + this.appLinkIconUri = appLinkIconUri; + this.appLinkPosterArtUri = appLinkPosterArtUri; + this.appLinkIntentUri = appLinkIntentUri; + } + + public String getVideoFormat() { + return VIDEO_HEIGHT_TO_FORMAT_MAP.get(videoHeight); + } + + @Override + public String toString() { + return "Channel{" + + "number=" + + number + + ", name=" + + name + + ", logoUri=" + + logoUrl + + ", originalNetworkId=" + + originalNetworkId + + ", videoWidth=" + + videoWidth + + ", videoHeight=" + + videoHeight + + ", audioChannel=" + + audioChannel + + ", audioLanguageCount=" + + audioLanguageCount + + ", hasClosedCaption=" + + hasClosedCaption + + ", appLinkText=" + + appLinkText + + ", appLinkColor=" + + appLinkColor + + ", appLinkIconUri=" + + appLinkIconUri + + ", appLinkPosterArtUri=" + + appLinkPosterArtUri + + ", appLinkIntentUri=" + + appLinkIntentUri + + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ChannelInfo that = (ChannelInfo) o; + return Objects.equals(originalNetworkId, that.originalNetworkId) + && Objects.equals(videoWidth, that.videoWidth) + && Objects.equals(videoHeight, that.videoHeight) + && Objects.equals(audioChannel, that.audioChannel) + && Objects.equals(audioLanguageCount, that.audioLanguageCount) + && Objects.equals(hasClosedCaption, that.hasClosedCaption) + && Objects.equals(appLinkColor, that.appLinkColor) + && Objects.equals(number, that.number) + && Objects.equals(name, that.name) + && Objects.equals(logoUrl, that.logoUrl) + && Objects.equals(program, that.program) + && Objects.equals(appLinkText, that.appLinkText) + && Objects.equals(appLinkIconUri, that.appLinkIconUri) + && Objects.equals(appLinkPosterArtUri, that.appLinkPosterArtUri) + && Objects.equals(appLinkIntentUri, that.appLinkIntentUri); + } + + @Override + public int hashCode() { + return Objects.hash(number, name, originalNetworkId); + } + + /** Builder class for {@code ChannelInfo}. */ + public static class Builder { + private String mNumber; + private String mName; + private String mLogoUrl = null; + private int mOriginalNetworkId; + private int mVideoWidth = 1920; // Width for HD video. + private int mVideoHeight = 1080; // Height for HD video. + private float mVideoPixelAspectRatio = 1.0f; // default value + private int mAudioChannel; + private int mAudioLanguageCount; + private boolean mHasClosedCaption; + private ProgramInfo mProgram; + private String mAppLinkText; + private int mAppLinkColor; + private String mAppLinkIconUri; + private String mAppLinkPosterArtUri; + private String mAppLinkIntentUri; + + public Builder() {} + + public Builder(ChannelInfo other) { + mNumber = other.number; + mName = other.name; + mLogoUrl = other.name; + mOriginalNetworkId = other.originalNetworkId; + mVideoWidth = other.videoWidth; + mVideoHeight = other.videoHeight; + mVideoPixelAspectRatio = other.videoPixelAspectRatio; + mAudioChannel = other.audioChannel; + mAudioLanguageCount = other.audioLanguageCount; + mHasClosedCaption = other.hasClosedCaption; + mProgram = other.program; + } + + public Builder setName(String name) { + mName = name; + return this; + } + + public Builder setNumber(String number) { + mNumber = number; + return this; + } + + public Builder setLogoUrl(String logoUrl) { + mLogoUrl = logoUrl; + return this; + } + + public Builder setOriginalNetworkId(int originalNetworkId) { + mOriginalNetworkId = originalNetworkId; + return this; + } + + public Builder setVideoWidth(int videoWidth) { + mVideoWidth = videoWidth; + return this; + } + + public Builder setVideoHeight(int videoHeight) { + mVideoHeight = videoHeight; + return this; + } + + public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { + mVideoPixelAspectRatio = videoPixelAspectRatio; + return this; + } + + public Builder setAudioChannel(int audioChannel) { + mAudioChannel = audioChannel; + return this; + } + + public Builder setAudioLanguageCount(int audioLanguageCount) { + mAudioLanguageCount = audioLanguageCount; + return this; + } + + public Builder setHasClosedCaption(boolean hasClosedCaption) { + mHasClosedCaption = hasClosedCaption; + return this; + } + + public Builder setProgram(ProgramInfo program) { + mProgram = program; + return this; + } + + public Builder setAppLinkText(String appLinkText) { + mAppLinkText = appLinkText; + return this; + } + + public Builder setAppLinkColor(int appLinkColor) { + mAppLinkColor = appLinkColor; + return this; + } + + public Builder setAppLinkIconUri(String appLinkIconUri) { + mAppLinkIconUri = appLinkIconUri; + return this; + } + + public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) { + mAppLinkPosterArtUri = appLinkPosterArtUri; + return this; + } + + public Builder setAppLinkIntentUri(String appLinkIntentUri) { + mAppLinkIntentUri = appLinkIntentUri; + return this; + } + + public ChannelInfo build() { + return new ChannelInfo( + mNumber, + mName, + mLogoUrl, + mOriginalNetworkId, + mVideoWidth, + mVideoHeight, + mVideoPixelAspectRatio, + mAudioChannel, + mAudioLanguageCount, + mHasClosedCaption, + mProgram, + mAppLinkText, + mAppLinkColor, + mAppLinkIconUri, + mAppLinkPosterArtUri, + mAppLinkIntentUri); + } + } +} diff --git a/tests/common/src/com/android/tv/testing/data/ChannelUtils.java b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java new file mode 100644 index 00000000..c95c4d2a --- /dev/null +++ b/tests/common/src/com/android/tv/testing/data/ChannelUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.testing.data; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.media.tv.TvContract.Channels; +import android.net.Uri; +import android.os.AsyncTask; +import android.support.annotation.WorkerThread; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Static helper methods for working with {@link android.media.tv.TvContract}. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed +public class ChannelUtils { + private static final String TAG = "ChannelUtils"; + private static final boolean DEBUG = false; + + /** + * Query and return the map of (channel_id, ChannelInfo). See: {@link + * com.android.tv.testing.data.ChannelInfo#fromCursor(Cursor)}. + */ + @WorkerThread + public static Map queryChannelInfoMapForTvInput( + Context context, String inputId) { + Uri uri = TvContract.buildChannelsUriForInput(inputId); + Map map = new HashMap<>(); + + String[] projections = new String[ChannelInfo.PROJECTION.length + 1]; + projections[0] = Channels._ID; + System.arraycopy(ChannelInfo.PROJECTION, 0, projections, 1, ChannelInfo.PROJECTION.length); + try (Cursor cursor = + context.getContentResolver().query(uri, projections, null, null, null)) { + if (cursor != null) { + while (cursor.moveToNext()) { + map.put(cursor.getLong(0), ChannelInfo.fromCursor(cursor)); + } + } + return map; + } + } + + @WorkerThread + public static void updateChannels(Context context, String inputId, List channels) { + // Create a map from original network ID to channel row ID for existing channels. + SparseArray existingChannelsMap = new SparseArray<>(); + Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); + String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; + ContentResolver resolver = context.getContentResolver(); + try (Cursor cursor = resolver.query(channelsUri, projection, null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + long rowId = cursor.getLong(0); + int originalNetworkId = cursor.getInt(1); + existingChannelsMap.put(originalNetworkId, rowId); + } + } + + Map logos = new HashMap<>(); + for (ChannelInfo channel : channels) { + // If a channel exists, update it. If not, insert a new one. + ContentValues values = new ContentValues(); + values.put(Channels.COLUMN_INPUT_ID, inputId); + values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number); + values.put(Channels.COLUMN_DISPLAY_NAME, channel.name); + values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId); + String videoFormat = channel.getVideoFormat(); + if (videoFormat != null) { + values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat); + } else { + values.putNull(Channels.COLUMN_VIDEO_FORMAT); + } + if (!TextUtils.isEmpty(channel.appLinkText)) { + values.put(Channels.COLUMN_APP_LINK_TEXT, channel.appLinkText); + } + if (channel.appLinkColor != 0) { + values.put(Channels.COLUMN_APP_LINK_COLOR, channel.appLinkColor); + } + if (!TextUtils.isEmpty(channel.appLinkPosterArtUri)) { + values.put(Channels.COLUMN_APP_LINK_POSTER_ART_URI, channel.appLinkPosterArtUri); + } + if (!TextUtils.isEmpty(channel.appLinkIconUri)) { + values.put(Channels.COLUMN_APP_LINK_ICON_URI, channel.appLinkIconUri); + } + if (!TextUtils.isEmpty(channel.appLinkIntentUri)) { + values.put(Channels.COLUMN_APP_LINK_INTENT_URI, channel.appLinkIntentUri); + } + Long rowId = existingChannelsMap.get(channel.originalNetworkId); + Uri uri; + if (rowId == null) { + if (DEBUG) { + Log.d(TAG, "Inserting " + channel); + } + uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); + } else { + if (DEBUG) { + Log.d(TAG, "Updating " + channel); + } + uri = TvContract.buildChannelUri(rowId); + resolver.update(uri, values, null, null); + existingChannelsMap.remove(channel.originalNetworkId); + } + if (!TextUtils.isEmpty(channel.logoUrl)) { + logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl); + } + } + if (!logos.isEmpty()) { + new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); + } + + // Deletes channels which don't exist in the new feed. + int size = existingChannelsMap.size(); + for (int i = 0; i < size; ++i) { + Long rowId = existingChannelsMap.valueAt(i); + resolver.delete(TvContract.buildChannelUri(rowId), null, null); + } + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + + private ChannelUtils() { + // Prevent instantiation. + } + + public static class InsertLogosTask extends AsyncTask, Void, Void> { + private final Context mContext; + + InsertLogosTask(Context context) { + mContext = context; + } + + @SafeVarargs + @Override + public final Void doInBackground(Map... logosList) { + for (Map logos : logosList) { + for (Uri uri : logos.keySet()) { + if (uri == null) { + continue; + } + Uri logoUri = Uri.parse(logos.get(uri)); + try (InputStream is = mContext.getContentResolver().openInputStream(logoUri); + OutputStream os = mContext.getContentResolver().openOutputStream(uri)) { + copy(is, os); + } catch (IOException ioe) { + Log.e(TAG, "Failed to write " + logoUri + " to " + uri, ioe); + } + } + } + return null; + } + } +} diff --git a/tests/common/src/com/android/tv/testing/data/ProgramInfo.java b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java new file mode 100644 index 00000000..6d801425 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/data/ProgramInfo.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.testing.data; + +import android.content.Context; +import android.database.Cursor; +import android.media.tv.TvContentRating; +import android.media.tv.TvContract; +import com.android.tv.testing.R; +import com.android.tv.testing.utils.Utils; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public final class ProgramInfo { + /** If this is specify for title, it will be generated by adding index. */ + public static final String GEN_TITLE = ""; + + /** + * If this is specify for episode title, it will be generated by adding index. Also, season and + * episode numbers would be generated, too. see: {@link #build} for detail. + */ + public static final String GEN_EPISODE = ""; + + private static final int SEASON_MAX = 10; + private static final int EPISODE_MAX = 12; + + /** + * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in + * order. + */ + public static final String GEN_POSTER = "GEN"; + + private static final int[] POSTER_ARTS_RES = { + 0, + R.drawable.blue, + R.drawable.red_large, + R.drawable.green, + R.drawable.red, + R.drawable.green_large, + R.drawable.blue_small + }; + + /** + * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order. + */ + public static final int GEN_DURATION = -1; + + private static final long[] DURATIONS_MS = { + TimeUnit.MINUTES.toMillis(15), + TimeUnit.MINUTES.toMillis(45), + TimeUnit.MINUTES.toMillis(90), + TimeUnit.MINUTES.toMillis(60), + TimeUnit.MINUTES.toMillis(30), + TimeUnit.MINUTES.toMillis(45), + TimeUnit.MINUTES.toMillis(60), + TimeUnit.MINUTES.toMillis(90), + TimeUnit.HOURS.toMillis(5) + }; + private static long durationsSumMs; + + static { + durationsSumMs = 0; + for (long duration : DURATIONS_MS) { + durationsSumMs += duration; + } + } + + /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */ + public static final String GEN_GENRE = "GEN"; + + private static final String[] GENRES = { + "", + TvContract.Programs.Genres.SPORTS, + TvContract.Programs.Genres.NEWS, + TvContract.Programs.Genres.SHOPPING, + TvContract.Programs.Genres.DRAMA, + TvContract.Programs.Genres.ENTERTAINMENT + }; + + public final String title; + public final String episode; + public final int seasonNumber; + public final int episodeNumber; + public final String posterArtUri; + public final String description; + public final long durationMs; + public final String genre; + public final TvContentRating[] contentRatings; + public final String resourceUri; + + public static ProgramInfo fromCursor(Cursor c) { + // TODO: Fill other fields. + Builder builder = new Builder(); + int index = c.getColumnIndex(TvContract.Programs.COLUMN_TITLE); + if (index >= 0) { + builder.setTitle(c.getString(index)); + } + index = c.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION); + if (index >= 0) { + builder.setDescription(c.getString(index)); + } + index = c.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE); + if (index >= 0) { + builder.setEpisode(c.getString(index)); + } + return builder.build(); + } + + public ProgramInfo( + String title, + String episode, + int seasonNumber, + int episodeNumber, + String posterArtUri, + String description, + long durationMs, + TvContentRating[] contentRatings, + String genre, + String resourceUri) { + this.title = title; + this.episode = episode; + this.seasonNumber = seasonNumber; + this.episodeNumber = episodeNumber; + this.posterArtUri = posterArtUri; + this.description = description; + this.durationMs = durationMs; + this.contentRatings = contentRatings; + this.genre = genre; + this.resourceUri = resourceUri; + } + + /** + * Create a instance of {@link ProgramInfo} whose content will be generated as much as possible. + */ + public static ProgramInfo create() { + return new Builder().build(); + } + + /** + * Get index of the program whose start time equals or less than {@code timeMs} and end time + * more than {@code timeMs}. + * + * @param timeMs target time in millis to find a program. + * @param channelId used to add complexity to the index between two consequence channels. + */ + public int getIndex(long timeMs, long channelId) { + if (durationMs != GEN_DURATION) { + return Math.max((int) (timeMs / durationMs), 0); + } + long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]; + int index = (int) ((timeMs - startTimeMs) / durationsSumMs) * DURATIONS_MS.length; + startTimeMs += (index / DURATIONS_MS.length) * durationsSumMs; + while (startTimeMs + DURATIONS_MS[index % DURATIONS_MS.length] < timeMs) { + startTimeMs += DURATIONS_MS[index % DURATIONS_MS.length]; + index++; + } + return index; + } + + /** + * Returns the start time for the program with the position. + * + * @param index index returned by {@link #getIndex} + */ + public long getStartTimeMs(int index, long channelId) { + if (durationMs != GEN_DURATION) { + return index * durationMs; + } + long startTimeMs = + channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))] + + (index / DURATIONS_MS.length) * durationsSumMs; + for (int i = 0; i < index % DURATIONS_MS.length; i++) { + startTimeMs += DURATIONS_MS[i]; + } + return startTimeMs; + } + + /** + * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link + * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}. + * + * @param index index returned by {@link #getIndex} + */ + public ProgramInfo build(Context context, int index) { + if (!GEN_TITLE.equals(title) + && episode == null + && !GEN_POSTER.equals(posterArtUri) + && durationMs != GEN_DURATION + && !GEN_GENRE.equals(genre)) { + return this; + } + return new ProgramInfo( + GEN_TITLE.equals(title) ? "Title(" + index + ")" : title, + GEN_EPISODE.equals(episode) ? "Episode(" + index + ")" : episode, + episode != null ? (index % SEASON_MAX + 1) : seasonNumber, + episode != null ? (index % EPISODE_MAX + 1) : episodeNumber, + GEN_POSTER.equals(posterArtUri) + ? Utils.getUriStringForResource( + context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length]) + : posterArtUri, + description, + durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs, + contentRatings, + GEN_GENRE.equals(genre) ? GENRES[index % GENRES.length] : genre, + resourceUri); + } + + @Override + public String toString() { + return "ProgramInfo{title=" + + title + + ", episode=" + + episode + + ", durationMs=" + + durationMs + + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProgramInfo that = (ProgramInfo) o; + return Objects.equals(seasonNumber, that.seasonNumber) + && Objects.equals(episodeNumber, that.episodeNumber) + && Objects.equals(durationMs, that.durationMs) + && Objects.equals(title, that.title) + && Objects.equals(episode, that.episode) + && Objects.equals(posterArtUri, that.posterArtUri) + && Objects.equals(description, that.description) + && Objects.equals(genre, that.genre) + && Arrays.equals(contentRatings, that.contentRatings) + && Objects.equals(resourceUri, that.resourceUri); + } + + @Override + public int hashCode() { + return Objects.hash(title, episode, seasonNumber, episodeNumber); + } + + public static class Builder { + private String mTitle = GEN_TITLE; + private String mEpisode = GEN_EPISODE; + private int mSeasonNumber; + private int mEpisodeNumber; + private String mPosterArtUri = GEN_POSTER; + private String mDescription; + private long mDurationMs = GEN_DURATION; + private TvContentRating[] mContentRatings; + private String mGenre = GEN_GENRE; + private String mResourceUri; + + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + public Builder setEpisode(String episode) { + mEpisode = episode; + return this; + } + + public Builder setSeasonNumber(int seasonNumber) { + mSeasonNumber = seasonNumber; + return this; + } + + public Builder setEpisodeNumber(int episodeNumber) { + mEpisodeNumber = episodeNumber; + return this; + } + + public Builder setPosterArtUri(String posterArtUri) { + mPosterArtUri = posterArtUri; + return this; + } + + public Builder setDescription(String description) { + mDescription = description; + return this; + } + + public Builder setDurationMs(long durationMs) { + mDurationMs = durationMs; + return this; + } + + public Builder setContentRatings(TvContentRating[] contentRatings) { + mContentRatings = contentRatings; + return this; + } + + public Builder setGenre(String genre) { + mGenre = genre; + return this; + } + + public Builder setResourceUri(String resourceUri) { + mResourceUri = resourceUri; + return this; + } + + public ProgramInfo build() { + return new ProgramInfo( + mTitle, + mEpisode, + mSeasonNumber, + mEpisodeNumber, + mPosterArtUri, + mDescription, + mDurationMs, + mContentRatings, + mGenre, + mResourceUri); + } + } +} diff --git a/tests/common/src/com/android/tv/testing/data/ProgramUtils.java b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java new file mode 100644 index 00000000..21fd371f --- /dev/null +++ b/tests/common/src/com/android/tv/testing/data/ProgramUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.testing.data; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.media.tv.TvContract; +import android.media.tv.TvContract.Programs; +import android.net.Uri; +import android.util.Log; +import com.android.tv.common.TvContentRatingCache; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed +public class ProgramUtils { + private static final String TAG = "ProgramUtils"; + private static final boolean DEBUG = false; + + // Populate program data for a week. + private static final long PROGRAM_INSERT_DURATION_MS = TimeUnit.DAYS.toMillis(7); + private static final int MAX_DB_INSERT_COUNT_AT_ONCE = 500; + + /** + * Populate programs by repeating given program information. This method will populate programs + * without any gap nor overlapping starting from the current time. + */ + public static void populatePrograms(Context context, Uri channelUri, ProgramInfo program) { + ContentValues values = new ContentValues(); + long channelId = ContentUris.parseId(channelUri); + + values.put(Programs.COLUMN_CHANNEL_ID, channelId); + values.put(Programs.COLUMN_SHORT_DESCRIPTION, program.description); + values.put( + Programs.COLUMN_CONTENT_RATING, + TvContentRatingCache.contentRatingsToString(program.contentRatings)); + + long currentTimeMs = System.currentTimeMillis(); + long targetEndTimeMs = currentTimeMs + PROGRAM_INSERT_DURATION_MS; + long timeMs = getLastProgramEndTimeMs(context, channelUri, currentTimeMs, targetEndTimeMs); + if (timeMs <= 0) { + timeMs = currentTimeMs; + } + int index = program.getIndex(timeMs, channelId); + timeMs = program.getStartTimeMs(index, channelId); + + ArrayList list = new ArrayList<>(); + while (timeMs < targetEndTimeMs) { + ProgramInfo programAt = program.build(context, index++); + values.put(Programs.COLUMN_TITLE, programAt.title); + values.put(Programs.COLUMN_EPISODE_TITLE, programAt.episode); + if (programAt.seasonNumber != 0) { + values.put(Programs.COLUMN_SEASON_NUMBER, programAt.seasonNumber); + } + if (programAt.episodeNumber != 0) { + values.put(Programs.COLUMN_EPISODE_NUMBER, programAt.episodeNumber); + } + values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri); + values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, timeMs); + values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, timeMs + programAt.durationMs); + values.put(Programs.COLUMN_CANONICAL_GENRE, programAt.genre); + values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri); + list.add(new ContentValues(values)); + timeMs += programAt.durationMs; + + if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) { + try { + context.getContentResolver() + .bulkInsert( + Programs.CONTENT_URI, + list.toArray(new ContentValues[list.size()])); + } catch (SQLiteException e) { + Log.e(TAG, "Can't insert EPG.", e); + return; + } + if (DEBUG) Log.d(TAG, "Inserted " + list.size() + " programs for " + channelUri); + list.clear(); + } + } + } + + private static long getLastProgramEndTimeMs( + Context context, Uri channelUri, long startTimeMs, long endTimeMs) { + Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs, endTimeMs); + String[] projection = {Programs.COLUMN_END_TIME_UTC_MILLIS}; + try (Cursor cursor = + context.getContentResolver().query(uri, projection, null, null, null)) { + if (cursor != null && cursor.moveToLast()) { + return cursor.getLong(0); + } + } + return 0; + } + + private ProgramUtils() {} +} diff --git a/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java new file mode 100644 index 00000000..5a2c41e6 --- /dev/null +++ b/tests/common/src/com/android/tv/testing/shadows/ShadowMediaSession.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.testing.shadows; + +import android.app.PendingIntent; +import android.content.Context; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow {@link MediaSession}. */ +@Implements(MediaSession.class) +public class ShadowMediaSession { + + public MediaSession.Callback mCallback; + public PendingIntent mMediaButtonReceiver; + public PendingIntent mSessionActivity; + public PlaybackState mPlaybackState; + public MediaMetadata mMediaMetadata; + public int mFlags; + public boolean mActive; + public boolean mReleased; + + /** Stand-in for the MediaSession constructor with the same parameters. */ + public void __constructor__(Context context, String tag, int userID) { + // This empty method prevents the real MediaSession constructor from being called. + } + + @Implementation + public void setCallback(MediaSession.Callback callback) { + mCallback = callback; + } + + @Implementation + public void setMediaButtonReceiver(PendingIntent mbr) { + mMediaButtonReceiver = mbr; + } + + @Implementation + public void setSessionActivity(PendingIntent activity) { + mSessionActivity = activity; + } + + @Implementation + public void setPlaybackState(PlaybackState state) { + mPlaybackState = state; + } + + @Implementation + public void setMetadata(MediaMetadata metadata) { + mMediaMetadata = metadata; + } + + @Implementation + public void setFlags(int flags) { + mFlags = flags; + } + + @Implementation + public boolean isActive() { + return mActive; + } + + @Implementation + public void setActive(boolean active) { + mActive = active; + } + + @Implementation + public void release() { + mReleased = true; + } +} diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java index c6f67b0b..3e8bab33 100644 --- a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java +++ b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java @@ -16,7 +16,7 @@ package com.android.tv.testing.testinput; import android.media.tv.TvTrackInfo; -import com.android.tv.testing.Constants; +import com.android.tv.testing.constants.Constants; import java.util.Collections; import java.util.List; diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java index 82b4614e..071b1d3c 100644 --- a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java +++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java @@ -21,7 +21,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; -import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.data.ChannelInfo; /** * Connection for controlling the Test TV Input Service. diff --git a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java index 0c768b21..27d3036c 100644 --- a/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java +++ b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java @@ -15,7 +15,7 @@ */ package com.android.tv.testing.testinput; -import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.data.ChannelInfo; /** Constants for interacting with TvTestInput. */ public final class TvTestInputConstants { diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java index 8c4e6e4b..1af1119f 100644 --- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java +++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv.testing.uihelper; import static com.android.tv.testing.uihelper.UiDeviceAsserts.waitForCondition; @@ -11,7 +26,7 @@ import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.util.Log; -import com.android.tv.testing.Utils; +import com.android.tv.testing.utils.Utils; import junit.framework.Assert; /** Helper for testing the Live TV Application. */ diff --git a/tests/common/src/com/android/tv/testing/utils/Utils.java b/tests/common/src/com/android/tv/testing/utils/Utils.java new file mode 100644 index 00000000..09a6c09c --- /dev/null +++ b/tests/common/src/com/android/tv/testing/utils/Utils.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.testing.utils; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager; +import android.net.Uri; +import android.util.Log; +import com.android.tv.common.util.CommonUtils; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Random; + +/** + * An utility class for testing. + * + * @see CommonUtils#isRunningInTest + */ +public final class Utils { + private static final String TAG = "Utils"; + + private static final long DEFAULT_RANDOM_SEED = getSeed(); + + public static String getUriStringForResource(Context context, int resId) { + if (resId == 0) { + return ""; + } + Resources res = context.getResources(); + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(res.getResourcePackageName(resId)) + .path(res.getResourceTypeName(resId)) + .appendPath(res.getResourceEntryName(resId)) + .build() + .toString(); + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + + public static String getServiceNameFromInputId(Context context, String inputId) { + TvInputManager tim = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); + for (TvInputInfo info : tim.getTvInputList()) { + if (info.getId().equals(inputId)) { + return info.getServiceInfo().name; + } + } + return null; + } + + public static String getInputIdFromComponentName(Context context, ComponentName name) { + TvInputManager tim = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); + for (TvInputInfo info : tim.getTvInputList()) { + ServiceInfo si = info.getServiceInfo(); + if (new ComponentName(si.packageName, si.name).equals(name)) { + return info.getId(); + } + } + return null; + } + + /** + * Return the Random class which is needed to make random data for testing. Default seed of the + * random is today's date. + */ + public static Random createTestRandom() { + return new Random(DEFAULT_RANDOM_SEED); + } + + private static long getSeed() { + // Set random seed as the date to track failed test data easily. + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); + String today = dateFormat.format(new Date()); + Log.d(TAG, "Today's random seed is " + today); + return Long.valueOf(today); + } + + /** Checks whether TvActivity is enabled or not. */ + public static boolean isTvActivityEnabled(Context context) { + PackageManager pm = context.getPackageManager(); + ComponentName name = + new ComponentName("com.android.tv", "com.android.tv.TvActivity"); + int enabled = pm.getComponentEnabledSetting(name); + return enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + private Utils() {} +} diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java index 36a3b07b..916ef8f1 100644 --- a/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java +++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java @@ -66,8 +66,9 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { public void testDisplayModeCancel() { ChannelStateData data = new ChannelStateData(); - data.mTvTrackInfos.add(com.android.tv.testing.Constants.SVGA_VIDEO_TRACK); - data.mSelectedVideoTrackId = com.android.tv.testing.Constants.SVGA_VIDEO_TRACK.getId(); + data.mTvTrackInfos.add(com.android.tv.testing.constants.Constants.SVGA_VIDEO_TRACK); + data.mSelectedVideoTrackId = + com.android.tv.testing.constants.Constants.SVGA_VIDEO_TRACK.getId(); updateThenTune(data, TvTestInputConstants.CH_2); mMenuHelper.assertPressOptionsDisplayMode(); @@ -88,7 +89,8 @@ public class LiveChannelsAppTest extends LiveChannelsTestCase { public void testMultiAudioCancel() { ChannelStateData data = new ChannelStateData(); - data.mTvTrackInfos.add(com.android.tv.testing.Constants.GENERIC_AUDIO_TRACK); + data.mTvTrackInfos.add( + com.android.tv.testing.constants.Constants.GENERIC_AUDIO_TRACK); updateThenTune(data, TvTestInputConstants.CH_2); mMenuHelper.assertPressOptionsMultiAudio(); diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java index 9a1bc043..38d8ce1a 100644 --- a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java +++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java @@ -23,7 +23,7 @@ import android.content.res.Resources; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.test.InstrumentationTestCase; -import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TestInputControlConnection; import com.android.tv.testing.testinput.TestInputControlUtils; diff --git a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java index 265e85e4..9b824a63 100644 --- a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java +++ b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java @@ -111,7 +111,8 @@ public class PlayControlsRowViewTest extends LiveChannelsTestCase { assertButtonHasFocus(BUTTON_ID_PLAY_PAUSE); } - public void testKeepPausedAfterVisitingHome() { + // TODO("b/70727167"): fix tests + public void notestKeepPausedAfterVisitingHome() { // Pause the playback. mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE); mMenuHelper.assertWaitForMenu(); diff --git a/tests/input/func.sh b/tests/input/func.sh new file mode 100644 index 00000000..3b2af4da --- /dev/null +++ b/tests/input/func.sh @@ -0,0 +1,24 @@ +#!/system/bin/sh +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# text fixture setup for unit tests + + +echo "text fixture setup for func tests" + +am instrument \ + -e testSetupMode func \ + -w com.android.tv.testinput/.instrument.TestSetupInstrumentation \ No newline at end of file diff --git a/tests/input/src/com/android/tv/testinput/TestInputControl.java b/tests/input/src/com/android/tv/testinput/TestInputControl.java index 54845b80..5e5ec32a 100644 --- a/tests/input/src/com/android/tv/testinput/TestInputControl.java +++ b/tests/input/src/com/android/tv/testinput/TestInputControl.java @@ -21,8 +21,8 @@ import android.net.Uri; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; -import com.android.tv.testing.ChannelInfo; -import com.android.tv.testing.ChannelUtils; +import com.android.tv.testing.data.ChannelInfo; +import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.testinput.ChannelState; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.ITestInputControl; diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java index 6e9405d8..5cefdc72 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputService.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java @@ -42,12 +42,13 @@ import android.util.Log; import android.view.KeyEvent; import android.view.Surface; import com.android.tv.input.TunerHelper; -import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.testinput.ChannelState; import java.util.Date; import java.util.concurrent.TimeUnit; /** Simple TV input service which provides test channels. */ +@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed public class TestTvInputService extends TvInputService { private static final String TAG = "TestTvInputService"; private static final int REFRESH_DELAY_MS = 1000 / 5; diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java index ce18ff81..b315785d 100644 --- a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java +++ b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java @@ -26,11 +26,11 @@ import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.os.Bundle; import android.util.Log; -import com.android.tv.testing.ChannelInfo; -import com.android.tv.testing.ChannelUtils; -import com.android.tv.testing.Constants; -import com.android.tv.testing.ProgramInfo; -import com.android.tv.testing.ProgramUtils; +import com.android.tv.testing.constants.Constants; +import com.android.tv.testing.data.ChannelInfo; +import com.android.tv.testing.data.ChannelUtils; +import com.android.tv.testing.data.ProgramInfo; +import com.android.tv.testing.data.ProgramUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java index 498e25dc..a4bd45c0 100644 --- a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java +++ b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java @@ -21,7 +21,7 @@ import android.app.Instrumentation; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; -import com.android.tv.testing.Constants; +import com.android.tv.testing.constants.Constants; import com.android.tv.testinput.TestTvInputService; import com.android.tv.testinput.TestTvInputSetupActivity; diff --git a/tests/input/tools/get_test_logos.sh b/tests/input/tools/get_test_logos.sh new file mode 100755 index 00000000..4dd87a3a --- /dev/null +++ b/tests/input/tools/get_test_logos.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +BASE="http://www.google.com/chart?chs=32&chst=d_simple_text_icon_above" + +# From http://developers.google.com/chart/image/docs/gallery/dynamic_icons#basic-icons +icons=( + academy activities airport amusement aquarium + art-gallery atm baby bank-dollar bank-euro + bank-intl bank-pound bank-yen bar barber + beach beer bicycle books bowling + bus cafe camping car-dealer car-rental + car-repair casino caution cemetery-grave cemetery-tomb + cinema civic-building computer corporate courthouse + fire flag floral helicopter home + info landslide legal location locomotive + medical mobile motorcycle music parking + pet petrol phone picnic postal + repair restaurant sail school scissors + ship shoppingbag shoppingcart ski snack + snow sport star swim taxi train + truck wc-female wc-male wc + wheelchair + ) + +# The 500s from https://spec.googleplex.com/quantumpalette# +colors=( + DB4437 E91E63 9C27B0 673AB7 3F51B5 + 4285F4 03A9F4 00BCD4 009688 0F9D58 + 8BC34A CDDC39 FFEB3B F4B400 FF9800 + FF5722 795548 9E9E9E 607D8B +) + + +# See https://developers.google.com/chart/image/docs/gallery/dynamic_icons +for n in `seq 1 80`; +do + i=n%76 + c=$(($RANDOM%19)) + # ||||| + url=${BASE}"&chld=Ch+"${n}"|7|00F|${icons[${i}]}|24|${colors[${c}]}|FFF" + echo ${url} + curl ${url} -o tests/input/res/drawable-xhdpi/ch_${n}_logo.png +done; \ No newline at end of file diff --git a/tests/input/unit.sh b/tests/input/unit.sh new file mode 100644 index 00000000..a14d3292 --- /dev/null +++ b/tests/input/unit.sh @@ -0,0 +1,24 @@ +#!/system/bin/sh +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# text fixture setup for unit tests + + +echo "text fixture setup for unit tests" + +am instrument \ + -e testSetupMode unit \ + -w com.android.tv.testinput/.instrument.TestSetupInstrumentation \ No newline at end of file diff --git a/tests/tunerscripts/measure-tuning-time.awk b/tests/tunerscripts/measure-tuning-time.awk new file mode 100644 index 00000000..e7febcf1 --- /dev/null +++ b/tests/tunerscripts/measure-tuning-time.awk @@ -0,0 +1,32 @@ +# Awk script to measure tuning time statistics from logcat dump + +BEGIN { + n = 0; + sum = 0; +} + +# Collect tuning time with "Video available in

Sort partner inputs first, then sort by input label, then by input id. See b/23031603. */ + @Test public void testComparatorLabel() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); Mockito.when(manager.isPartnerInput(Matchers.anyString())) @@ -321,6 +333,7 @@ public class ChannelTest { comparatorTester.test(); } + @Test public void testNormalizeChannelNumber() { assertNormalizedDisplayNumber(null, null); assertNormalizedDisplayNumber("", ""); @@ -341,10 +354,10 @@ public class ChannelTest { } private void assertNormalizedDisplayNumber(String displayNumber, String normalized) { - assertEquals(normalized, Channel.normalizeDisplayNumber(displayNumber)); + assertThat(Channel.normalizeDisplayNumber(displayNumber)).isEqualTo(normalized); } - private class TestChannelComparator extends Channel.DefaultComparator { + private static final class TestChannelComparator extends Channel.DefaultComparator { public TestChannelComparator(TvInputManagerHelper manager) { super(null, manager); } @@ -355,7 +368,8 @@ public class ChannelTest { } } - private static class ChannelComparatorWithDescriptionAsLabel extends Channel.DefaultComparator { + private static final class ChannelComparatorWithDescriptionAsLabel + extends Channel.DefaultComparator { public ChannelComparatorWithDescriptionAsLabel(TvInputManagerHelper manager) { super(null, manager); } diff --git a/tests/unit/src/com/android/tv/data/GenreItemTest.java b/tests/unit/src/com/android/tv/data/GenreItemTest.java index dbf99eac..02bf4b30 100644 --- a/tests/unit/src/com/android/tv/data/GenreItemTest.java +++ b/tests/unit/src/com/android/tv/data/GenreItemTest.java @@ -17,18 +17,18 @@ package com.android.tv.data; import static android.support.test.InstrumentationRegistry.getTargetContext; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import android.media.tv.TvContract.Programs.Genres; import android.os.Build; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link Channel}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class GenreItemTest { private static final String INVALID_GENRE = "INVALID GENRE"; @@ -41,17 +41,17 @@ public class GenreItemTest { @Test public void testGetCanonicalGenre() { int count = GenreItems.getGenreCount(); - assertNull(GenreItems.getCanonicalGenre(GenreItems.ID_ALL_CHANNELS)); + assertThat(GenreItems.getCanonicalGenre(GenreItems.ID_ALL_CHANNELS)).isNull(); for (int i = 1; i < count; ++i) { - assertNotNull(GenreItems.getCanonicalGenre(i)); + assertThat(GenreItems.getCanonicalGenre(i)).isNotNull(); } } @Test public void testGetId_base() { int count = GenreItems.getGenreCount(); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(null)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(INVALID_GENRE)); + assertThat(GenreItems.getId(null)).isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(INVALID_GENRE)).isEqualTo(GenreItems.ID_ALL_CHANNELS); assertInRange(GenreItems.getId(Genres.FAMILY_KIDS), 1, count - 1); assertInRange(GenreItems.getId(Genres.SPORTS), 1, count - 1); assertInRange(GenreItems.getId(Genres.SHOPPING), 1, count - 1); @@ -68,12 +68,13 @@ public class GenreItemTest { @Test public void testGetId_lmp_mr1() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.ARTS)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.ENTERTAINMENT)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.LIFE_STYLE)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.MUSIC)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.PREMIER)); - assertEquals(GenreItems.ID_ALL_CHANNELS, GenreItems.getId(Genres.TECH_SCIENCE)); + assertThat(GenreItems.getId(Genres.ARTS)).isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(Genres.ENTERTAINMENT)) + .isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(Genres.LIFE_STYLE)).isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(Genres.MUSIC)).isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(Genres.PREMIER)).isEqualTo(GenreItems.ID_ALL_CHANNELS); + assertThat(GenreItems.getId(Genres.TECH_SCIENCE)).isEqualTo(GenreItems.ID_ALL_CHANNELS); } else { int count = GenreItems.getGenreCount(); assertInRange(GenreItems.getId(Genres.ARTS), 1, count - 1); @@ -86,6 +87,6 @@ public class GenreItemTest { } private void assertInRange(int value, int lower, int upper) { - assertTrue(value >= lower && value <= upper); + assertThat(value >= lower && value <= upper).isTrue(); } } diff --git a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java index 2b75ee4b..c1d670fe 100644 --- a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java @@ -17,10 +17,8 @@ package com.android.tv.data; import static android.support.test.InstrumentationRegistry.getTargetContext; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.content.Context; import android.database.ContentObserver; @@ -30,14 +28,15 @@ import android.net.Uri; import android.os.HandlerThread; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.mock.MockCursor; import android.util.Log; import android.util.SparseArray; -import com.android.tv.testing.Constants; import com.android.tv.testing.FakeClock; -import com.android.tv.testing.ProgramInfo; +import com.android.tv.testing.constants.Constants; +import com.android.tv.testing.data.ProgramInfo; import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.Arrays; @@ -46,10 +45,14 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; /** Test for {@link com.android.tv.data.ProgramDataManager} */ @SmallTest +@RunWith(AndroidJUnit4.class) +@Ignore("b/69836704") public class ProgramDataManagerTest { private static final boolean DEBUG = false; private static final String TAG = "ProgramDataManagerTest"; @@ -94,7 +97,8 @@ public class ProgramDataManagerTest { private void startAndWaitForComplete() throws InterruptedException { mProgramDataManager.start(); - assertTrue(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertThat(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) + .isTrue(); } /** Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. */ @@ -105,8 +109,8 @@ public class ProgramDataManagerTest { int index = stub.getIndex(mClock.currentTimeMillis(), channelId); long startTimeMs = stub.getStartTimeMs(index, channelId); ProgramInfo programAt = stub.build(InstrumentationRegistry.getContext(), index); - assertTrue(startTimeMs <= mClock.currentTimeMillis()); - assertTrue(mClock.currentTimeMillis() < startTimeMs + programAt.durationMs); + assertThat(startTimeMs).isAtMost(mClock.currentTimeMillis()); + assertThat(mClock.currentTimeMillis()).isLessThan(startTimeMs + programAt.durationMs); } } @@ -130,9 +134,11 @@ public class ProgramDataManagerTest { for (long channelId = 1; channelId <= Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) { Program currentProgram = mProgramDataManager.getCurrentProgram(channelId); // Test {@link ProgramDataManager#getCurrentProgram(long)}. - assertTrue( - currentProgram.getStartTimeUtcMillis() <= mClock.currentTimeMillis() - && mClock.currentTimeMillis() <= currentProgram.getEndTimeUtcMillis()); + assertThat( + currentProgram.getStartTimeUtcMillis() <= mClock.currentTimeMillis() + && mClock.currentTimeMillis() + <= currentProgram.getEndTimeUtcMillis()) + .isTrue(); // Test {@link ProgramDataManager#getPrograms(long)}. // Case #1: Normal case @@ -149,14 +155,14 @@ public class ProgramDataManagerTest { // Case #2: Corner cases where there's a program that starts at the start of the range. long startTimeMs = programs.get(0).getStartTimeUtcMillis(); programs = mProgramDataManager.getPrograms(channelId, startTimeMs); - assertEquals(startTimeMs, programs.get(0).getStartTimeUtcMillis()); + assertThat(programs.get(0).getStartTimeUtcMillis()).isEqualTo(startTimeMs); // Test {@link ProgramDataManager#setPrefetchTimeRange(long)}. programs = mProgramDataManager.getPrograms( channelId, prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1)); for (Program program : programs) { - assertTrue(program.getEndTimeUtcMillis() >= prefetchTimeRangeStartMs); + assertThat(program.getEndTimeUtcMillis()).isAtLeast(prefetchTimeRangeStartMs); } } } @@ -186,12 +192,14 @@ public class ProgramDataManagerTest { TestProgramDataManagerOnCurrentProgramUpdatedListener listener = new TestProgramDataManagerOnCurrentProgramUpdatedListener(); mProgramDataManager.addOnCurrentProgramUpdatedListener(testChannelId, listener); - assertTrue( - listener.currentProgramUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); - assertEquals(testChannelId, listener.updatedChannelId); + assertThat( + listener.currentProgramUpdatedLatch.await( + WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) + .isTrue(); + assertThat(listener.updatedChannelId).isEqualTo(testChannelId); Program currentProgram = mProgramDataManager.getCurrentProgram(testChannelId); assertProgramEquals(nextProgramStartTimeMs, nextProgramInfo, currentProgram); - assertEquals(listener.updatedProgram, currentProgram); + assertThat(currentProgram).isEqualTo(listener.updatedProgram); } /** Test if program data is refreshed after the program insertion. */ @@ -204,14 +212,15 @@ public class ProgramDataManagerTest { mListener.reset(); List programList = mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis()); - assertNotNull(programList); + assertThat(programList).isNotNull(); long lastProgramEndTime = programList.get(programList.size() - 1).getEndTimeUtcMillis(); // Make change in content provider mContentProvider.simulateAppend(testChannelId); - assertTrue(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertThat(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) + .isTrue(); programList = mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis()); - assertTrue( - lastProgramEndTime < programList.get(programList.size() - 1).getEndTimeUtcMillis()); + assertThat(lastProgramEndTime) + .isLessThan(programList.get(programList.size() - 1).getEndTimeUtcMillis()); } /** Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. */ @@ -224,23 +233,28 @@ public class ProgramDataManagerTest { mListener.reset(); mProgramDataManager.setPauseProgramUpdate(true); mContentProvider.simulateAppend(testChannelId); - assertFalse( - mListener.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertThat(mListener.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, TimeUnit.MILLISECONDS)) + .isFalse(); } public static void assertProgramEquals( long expectedStartTime, ProgramInfo expectedInfo, Program actualProgram) { - assertEquals("title", expectedInfo.title, actualProgram.getTitle()); - assertEquals("episode", expectedInfo.episode, actualProgram.getEpisodeTitle()); - assertEquals("description", expectedInfo.description, actualProgram.getDescription()); - assertEquals("startTime", expectedStartTime, actualProgram.getStartTimeUtcMillis()); - assertEquals( - "endTime", - expectedStartTime + expectedInfo.durationMs, - actualProgram.getEndTimeUtcMillis()); + assertWithMessage("title").that(actualProgram.getTitle()).isEqualTo(expectedInfo.title); + assertWithMessage("episode") + .that(actualProgram.getEpisodeTitle()) + .isEqualTo(expectedInfo.episode); + assertWithMessage("description") + .that(actualProgram.getDescription()) + .isEqualTo(expectedInfo.description); + assertWithMessage("startTime") + .that(actualProgram.getStartTimeUtcMillis()) + .isEqualTo(expectedStartTime); + assertWithMessage("endTime") + .that(actualProgram.getEndTimeUtcMillis()) + .isEqualTo(expectedStartTime + expectedInfo.durationMs); } - private class FakeContentResolver extends MockContentResolver { + private final class FakeContentResolver extends MockContentResolver { @Override public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { super.notifyChange(uri, observer, syncToNetwork); @@ -255,7 +269,7 @@ public class ProgramDataManagerTest { } } - private static class ProgramInfoWrapper { + private static final class ProgramInfoWrapper { private final int index; private final long startTimeMs; private final ProgramInfo programInfo; @@ -269,7 +283,7 @@ public class ProgramDataManagerTest { // This implements the minimal methods in content resolver // and detailed assumptions are written in each method. - private class FakeContentProvider extends MockContentProvider { + private final class FakeContentProvider extends MockContentProvider { private final SparseArray> mProgramInfoList = new SparseArray<>(); /** @@ -360,9 +374,9 @@ public class ProgramDataManagerTest { } private void assertProgramUri(Uri uri) { - assertTrue( - "Uri(" + uri + ") isn't channel uri", - uri.toString().startsWith(TvContract.Programs.CONTENT_URI.toString())); + assertWithMessage("Uri(" + uri + ") isn't channel uri") + .that(uri.toString().startsWith(TvContract.Programs.CONTENT_URI.toString())) + .isTrue(); } public ProgramInfoWrapper get(long channelId, int position) { @@ -374,8 +388,8 @@ public class ProgramDataManagerTest { } } - private class FakeCursor extends MockCursor { - private final String[] ALL_COLUMNS = { + private final class FakeCursor extends MockCursor { + private final String[] allColumns = { TvContract.Programs.COLUMN_CHANNEL_ID, TvContract.Programs.COLUMN_TITLE, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, @@ -403,7 +417,7 @@ public class ProgramDataManagerTest { * @param endTimeMs end of the time range to query programs. */ public FakeCursor(String[] columns, long channelId, long startTimeMs, long endTimeMs) { - mColumns = (columns == null) ? ALL_COLUMNS : columns; + mColumns = (columns == null) ? allColumns : columns; mIsQueryForSingleChannel = (channelId > 0); mChannelId = channelId; mProgramPosition = -1; @@ -466,11 +480,12 @@ public class ProgramDataManagerTest { return mCurrentProgram.startTimeMs; case TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS: return mCurrentProgram.startTimeMs + mCurrentProgram.programInfo.durationMs; + default: + if (DEBUG) { + Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()"); + } + return 0; } - if (DEBUG) { - Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()"); - } - return 0; } @Override @@ -483,11 +498,12 @@ public class ProgramDataManagerTest { return mCurrentProgram.programInfo.description; case TvContract.Programs.COLUMN_EPISODE_TITLE: return mCurrentProgram.programInfo.episode; + default: + if (DEBUG) { + Log.d(TAG, "Column (" + columnName + ") is ignored in getString()"); + } + return null; } - if (DEBUG) { - Log.d(TAG, "Column (" + columnName + ") is ignored in getString()"); - } - return null; } @Override diff --git a/tests/unit/src/com/android/tv/data/ProgramTest.java b/tests/unit/src/com/android/tv/data/ProgramTest.java index a69688d2..3f2a9f26 100644 --- a/tests/unit/src/com/android/tv/data/ProgramTest.java +++ b/tests/unit/src/com/android/tv/data/ProgramTest.java @@ -17,22 +17,24 @@ package com.android.tv.data; import static android.media.tv.TvContract.Programs.Genres.COMEDY; import static android.media.tv.TvContract.Programs.Genres.FAMILY_KIDS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.media.tv.TvContentRating; import android.media.tv.TvContract.Programs.Genres; import android.os.Parcel; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.tv.data.Program.CriticScore; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link Program}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class ProgramTest { private static final int NOT_FOUND_GENRE = 987; @@ -43,7 +45,7 @@ public class ProgramTest { @Test public void testBuild() { Program program = new Program.Builder().build(); - assertEquals("isValid", false, program.isValid()); + assertWithMessage("isValid").that(program.isValid()).isFalse(); } @Test @@ -127,7 +129,7 @@ public class ProgramTest { p2.unmarshall(bytes, 0, bytes.length); p2.setDataPosition(0); Program r2 = Program.fromParcel(p2); - assertEquals(p, r2); + assertThat(r2).isEqualTo(p); } finally { p1.recycle(); p2.recycle(); @@ -141,34 +143,36 @@ public class ProgramTest { .setTitle("MyTitle") .addCriticScore( new CriticScore( - "default source", "5/10", "https://testurl/testimage.jpg")) + "default source", "5/10", "http://testurl/testimage.jpg")) .build(); Parcel parcel = Parcel.obtain(); program.writeToParcel(parcel, 0); parcel.setDataPosition(0); Program programFromParcel = Program.CREATOR.createFromParcel(parcel); - assertNotNull(programFromParcel.getCriticScores()); - assertEquals(programFromParcel.getCriticScores().get(0).source, "default source"); - assertEquals(programFromParcel.getCriticScores().get(0).score, "5/10"); - assertEquals( - programFromParcel.getCriticScores().get(0).logoUrl, - "https://testurl/testimage.jpg"); + assertThat(programFromParcel.getCriticScores()).isNotNull(); + assertThat(programFromParcel.getCriticScores().get(0).source).isEqualTo("default source"); + assertThat(programFromParcel.getCriticScores().get(0).score).isEqualTo("5/10"); + assertThat(programFromParcel.getCriticScores().get(0).logoUrl) + .isEqualTo("http://testurl/testimage.jpg"); } private static void assertNullCanonicalGenres(Program program) { String[] actual = program.getCanonicalGenres(); - assertNull("Expected null canonical genres but was " + Arrays.toString(actual), actual); + assertWithMessage("Expected null canonical genres but was " + Arrays.toString(actual)) + .that(actual) + .isNull(); } private static void assertCanonicalGenres(Program program, String... expected) { - assertEquals( - "canonical genres", - Arrays.asList(expected), - Arrays.asList(program.getCanonicalGenres())); + assertWithMessage("canonical genres") + .that(Arrays.asList(program.getCanonicalGenres())) + .isEqualTo(Arrays.asList(expected)); } private static void assertHasGenre(Program program, int genreId, boolean expected) { - assertEquals("hasGenre(" + genreId + ")", expected, program.hasGenre(genreId)); + assertWithMessage("hasGenre(" + genreId + ")") + .that(program.hasGenre(genreId)) + .isEqualTo(expected); } } diff --git a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java index 496d1018..8bf3efbc 100644 --- a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java +++ b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java @@ -19,6 +19,7 @@ package com.android.tv.data; import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; 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.util.SetupUtils; @@ -27,6 +28,7 @@ import com.android.tv.util.TvInputManagerHelper; import java.util.Comparator; import java.util.LinkedHashMap; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -34,6 +36,7 @@ import org.mockito.stubbing.Answer; /** Test for {@link TvInputNewComparator} */ @SmallTest +@RunWith(AndroidJUnit4.class) public class TvInputNewComparatorTest { @Test public void testComparator() throws Exception { diff --git a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java index e65a71fb..539698bf 100644 --- a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java +++ b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java @@ -17,16 +17,16 @@ package com.android.tv.data; import static android.support.test.InstrumentationRegistry.getTargetContext; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import android.os.Looper; import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; import com.android.tv.data.WatchedHistoryManager.WatchedRecord; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test for {@link com.android.tv.data.WatchedHistoryManagerTest} @@ -34,6 +34,7 @@ import org.junit.Test; *

This is a medium test because it load files which accessing SharedPreferences. */ @MediumTest +@RunWith(AndroidJUnit4.class) public class WatchedHistoryManagerTest { // Wait time for expected success. private static final int MAX_HISTORY_SIZE = 100; @@ -53,13 +54,13 @@ public class WatchedHistoryManagerTest { private void startAndWaitForComplete() throws InterruptedException { mWatchedHistoryManager.start(); - assertTrue(mListener.mLoadFinished); + assertThat(mListener.mLoadFinished).isTrue(); } @Test public void testIsLoaded() throws InterruptedException { startAndWaitForComplete(); - assertTrue(mWatchedHistoryManager.isLoaded()); + assertThat(mWatchedHistoryManager.isLoaded()).isTrue(); } @Test @@ -74,10 +75,10 @@ public class WatchedHistoryManagerTest { WatchedRecord record = mWatchedHistoryManager.getRecord(0); WatchedRecord recordFromSharedPreferences = mWatchedHistoryManager.getRecordFromSharedPreferences(0); - assertEquals(record.channelId, fakeId); - assertEquals(record.watchedStartTime, time - duration); - assertEquals(record.duration, duration); - assertEquals(record, recordFromSharedPreferences); + assertThat(fakeId).isEqualTo(record.channelId); + assertThat(time - duration).isEqualTo(record.watchedStartTime); + assertThat(duration).isEqualTo(record.duration); + assertThat(recordFromSharedPreferences).isEqualTo(record); } @Test @@ -96,22 +97,21 @@ public class WatchedHistoryManagerTest { WatchedRecord record = mWatchedHistoryManager.getRecord(i); WatchedRecord recordFromSharedPreferences = mWatchedHistoryManager.getRecordFromSharedPreferences(i); - assertEquals(record, recordFromSharedPreferences); - assertEquals(record.channelId, startChannelId + size - 1 - i); + assertThat(recordFromSharedPreferences).isEqualTo(record); + assertThat(startChannelId + size - 1 - i).isEqualTo(record.channelId); } // Since the WatchedHistory is a circular queue, the value for 0 and maxHistorySize // are same. - assertEquals( - mWatchedHistoryManager.getRecordFromSharedPreferences(0), - mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE)); + assertThat(mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE)) + .isEqualTo(mWatchedHistoryManager.getRecordFromSharedPreferences(0)); } @Test public void testWatchedRecordEquals() { - assertTrue(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 3))); - assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 4))); - assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 4, 3))); - assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(4, 2, 3))); + assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 3))).isTrue(); + assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 4))).isFalse(); + assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 4, 3))).isFalse(); + assertThat(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(4, 2, 3))).isFalse(); } @Test @@ -122,10 +122,11 @@ public class WatchedHistoryManagerTest { WatchedRecord record = new WatchedRecord(fakeId, time, duration); WatchedRecord sameRecord = mWatchedHistoryManager.decode(mWatchedHistoryManager.encode(record)); - assertEquals(record, sameRecord); + assertThat(sameRecord).isEqualTo(record); } - private class TestWatchedHistoryManagerListener implements WatchedHistoryManager.Listener { + private static final class TestWatchedHistoryManagerListener + implements WatchedHistoryManager.Listener { boolean mLoadFinished; @Override diff --git a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java b/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java deleted file mode 100644 index c9c76a5a..00000000 --- a/tests/unit/src/com/android/tv/dvr/BaseDvrDataManagerTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import static android.support.test.InstrumentationRegistry.getContext; - -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.MoreAsserts; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** Tests for {@link BaseDvrDataManager} using {@link DvrDataManagerInMemoryImpl}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class BaseDvrDataManagerTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - private DvrDataManagerInMemoryImpl mDvrDataManager; - private FakeClock mFakeClock; - - @Before - public void setUp() { - mDvrFeature.enableForTest(); - mFakeClock = FakeClock.createWithCurrentTime(); - mDvrDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); - } - - @After - public void tearDown() { - mDvrFeature.resetForTests(); - } - - @Test - public void testGetNonStartedScheduledRecordings() { - ScheduledRecording recording = - mDvrDataManager.addScheduledRecordingInternal( - createNewScheduledRecordingStartingNow()); - List result = mDvrDataManager.getNonStartedScheduledRecordings(); - MoreAsserts.assertContentsInAnyOrder(result, recording); - } - - @Test - public void testGetNonStartedScheduledRecordings_past() { - mDvrDataManager.addScheduledRecordingInternal(createNewScheduledRecordingStartingNow()); - mFakeClock.increment(TimeUnit.MINUTES, 6); - List result = mDvrDataManager.getNonStartedScheduledRecordings(); - MoreAsserts.assertContentsInAnyOrder(result); - } - - @NonNull - private ScheduledRecording createNewScheduledRecordingStartingNow() { - return ScheduledRecording.buildFrom( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - ScheduledRecording.ID_NOT_SET, - INPUT_ID, - CHANNEL_ID, - mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5))) - .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED) - .build(); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java deleted file mode 100644 index 8a5dfabd..00000000 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerImplTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.junit.Assert.assertEquals; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; - -/** Tests for {@link DvrDataManagerImpl} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class DvrDataManagerImplTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - @Test - public void testGetNextScheduledStartTimeAfter() { - long id = 1; - List scheduledRecordings = new ArrayList<>(); - assertNextStartTime(scheduledRecordings, 0L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - assertNextStartTime(scheduledRecordings, 9L, 10L); - assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 20L, 30L)); - assertNextStartTime(scheduledRecordings, 9L, 10L); - assertNextStartTime(scheduledRecordings, 10L, 20L); - assertNextStartTime(scheduledRecordings, 20L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 30L, 40L)); - assertNextStartTime(scheduledRecordings, 9L, 10L); - assertNextStartTime(scheduledRecordings, 10L, 20L); - assertNextStartTime(scheduledRecordings, 20L, 30L); - assertNextStartTime(scheduledRecordings, 30L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - scheduledRecordings.clear(); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - scheduledRecordings.add( - RecordingTestUtils.createTestRecordingWithIdAndPeriod( - id++, INPUT_ID, CHANNEL_ID, 10L, 20L)); - assertNextStartTime(scheduledRecordings, 9L, 10L); - assertNextStartTime(scheduledRecordings, 10L, DvrDataManager.NEXT_START_TIME_NOT_FOUND); - } - - private void assertNextStartTime( - List scheduledRecordings, long startTime, long expected) { - assertEquals( - "getNextScheduledStartTimeAfter()", - expected, - DvrDataManagerImpl.getNextStartTimeAfter(scheduledRecordings, startTime)); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java b/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java deleted file mode 100644 index a16f7acc..00000000 --- a/tests/unit/src/com/android/tv/dvr/DvrDataManagerInMemoryImpl.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import android.content.Context; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.test.filters.SdkSuppress; -import android.text.TextUtils; -import android.util.Log; -import android.util.Range; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.dvr.data.RecordedProgram; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.dvr.data.ScheduledRecording.RecordingState; -import com.android.tv.dvr.data.SeriesRecording; -import com.android.tv.util.Clock; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; - -/** A DVR Data manager that stores values in memory suitable for testing. */ -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public final class DvrDataManagerInMemoryImpl extends BaseDvrDataManager { - private static final String TAG = "DvrDataManagerInMemory"; - private final AtomicLong mNextId = new AtomicLong(1); - private final Map mScheduledRecordings = new HashMap<>(); - private final Map mRecordedPrograms = new HashMap<>(); - private final Map mSeriesRecordings = new HashMap<>(); - - public DvrDataManagerInMemoryImpl(Context context, Clock clock) { - super(context, clock); - } - - @Override - public boolean isInitialized() { - return true; - } - - @Override - public boolean isDvrScheduleLoadFinished() { - return true; - } - - @Override - public boolean isRecordedProgramLoadFinished() { - return true; - } - - private List getScheduledRecordingsPrograms() { - return new ArrayList<>(mScheduledRecordings.values()); - } - - @Override - public List getRecordedPrograms() { - return new ArrayList<>(mRecordedPrograms.values()); - } - - @Override - public List getAllScheduledRecordings() { - return new ArrayList<>(mScheduledRecordings.values()); - } - - public List getSeriesRecordings() { - return new ArrayList<>(mSeriesRecordings.values()); - } - - @Override - public List getSeriesRecordings(String inputId) { - List result = new ArrayList<>(); - for (SeriesRecording r : mSeriesRecordings.values()) { - if (TextUtils.equals(r.getInputId(), inputId)) { - result.add(r); - } - } - return result; - } - - @Override - public long getNextScheduledStartTimeAfter(long startTime) { - - List temp = getNonStartedScheduledRecordings(); - Collections.sort(temp, ScheduledRecording.START_TIME_COMPARATOR); - for (ScheduledRecording r : temp) { - if (r.getStartTimeMs() > startTime) { - return r.getStartTimeMs(); - } - } - return DvrDataManager.NEXT_START_TIME_NOT_FOUND; - } - - @Override - public List getScheduledRecordings( - Range period, @RecordingState int state) { - List temp = getScheduledRecordingsPrograms(); - List result = new ArrayList<>(); - for (ScheduledRecording r : temp) { - if (r.isOverLapping(period) && r.getState() == state) { - result.add(r); - } - } - return result; - } - - @Override - public List getScheduledRecordings(long seriesRecordingId) { - List result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { - if (r.getSeriesRecordingId() == seriesRecordingId) { - result.add(r); - } - } - return result; - } - - @Override - public List getScheduledRecordings(String inputId) { - List result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { - if (TextUtils.equals(r.getInputId(), inputId)) { - result.add(r); - } - } - return result; - } - - /** Add a new scheduled recording. */ - @Override - public void addScheduledRecording(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording r : scheduledRecordings) { - addScheduledRecordingInternal(r); - } - } - - public void addRecordedProgram(RecordedProgram recordedProgram) { - addRecordedProgramInternal(recordedProgram); - } - - public void updateRecordedProgram(RecordedProgram r) { - long id = r.getId(); - if (mRecordedPrograms.containsKey(id)) { - mRecordedPrograms.put(id, r); - notifyRecordedProgramsChanged(r); - } else { - throw new IllegalArgumentException("Recording not found:" + r); - } - } - - public void removeRecordedProgram(RecordedProgram scheduledRecording) { - mRecordedPrograms.remove(scheduledRecording.getId()); - notifyRecordedProgramsRemoved(scheduledRecording); - } - - public ScheduledRecording addScheduledRecordingInternal(ScheduledRecording scheduledRecording) { - SoftPreconditions.checkState( - scheduledRecording.getId() == ScheduledRecording.ID_NOT_SET, - TAG, - "expected id of " - + ScheduledRecording.ID_NOT_SET - + " but was " - + scheduledRecording); - scheduledRecording = - ScheduledRecording.buildFrom(scheduledRecording) - .setId(mNextId.incrementAndGet()) - .build(); - mScheduledRecordings.put(scheduledRecording.getId(), scheduledRecording); - notifyScheduledRecordingAdded(scheduledRecording); - return scheduledRecording; - } - - public RecordedProgram addRecordedProgramInternal(RecordedProgram recordedProgram) { - SoftPreconditions.checkState( - recordedProgram.getId() == RecordedProgram.ID_NOT_SET, - TAG, - "expected id of " + RecordedProgram.ID_NOT_SET + " but was " + recordedProgram); - recordedProgram = - RecordedProgram.buildFrom(recordedProgram).setId(mNextId.incrementAndGet()).build(); - mRecordedPrograms.put(recordedProgram.getId(), recordedProgram); - notifyRecordedProgramsAdded(recordedProgram); - return recordedProgram; - } - - @Override - public void addSeriesRecording(SeriesRecording... seriesRecordings) { - for (SeriesRecording r : seriesRecordings) { - mSeriesRecordings.put(r.getId(), r); - } - notifySeriesRecordingAdded(seriesRecordings); - } - - @Override - public void removeScheduledRecording(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording r : scheduledRecordings) { - mScheduledRecordings.remove(r.getId()); - } - notifyScheduledRecordingRemoved(scheduledRecordings); - } - - @Override - public void removeScheduledRecording(boolean forceRemove, ScheduledRecording... schedule) { - removeScheduledRecording(schedule); - } - - @Override - public void removeSeriesRecording(SeriesRecording... seriesRecordings) { - for (SeriesRecording r : seriesRecordings) { - mSeriesRecordings.remove(r.getId()); - } - notifySeriesRecordingRemoved(seriesRecordings); - } - - @Override - public void updateScheduledRecording(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording r : scheduledRecordings) { - long id = r.getId(); - if (mScheduledRecordings.containsKey(id)) { - mScheduledRecordings.put(id, r); - } else { - Log.d(TAG, "Recording not found:" + r); - } - } - notifyScheduledRecordingStatusChanged(scheduledRecordings); - } - - @Override - public void updateSeriesRecording(SeriesRecording... seriesRecordings) { - for (SeriesRecording r : seriesRecordings) { - long id = r.getId(); - if (mSeriesRecordings.containsKey(id)) { - mSeriesRecordings.put(id, r); - } else { - throw new IllegalArgumentException("Recording not found:" + r); - } - } - notifySeriesRecordingChanged(seriesRecordings); - } - - @Nullable - @Override - public ScheduledRecording getScheduledRecording(long id) { - return mScheduledRecordings.get(id); - } - - @Nullable - @Override - public ScheduledRecording getScheduledRecordingForProgramId(long programId) { - for (ScheduledRecording r : mScheduledRecordings.values()) { - if (r.getProgramId() == programId) { - return r; - } - } - return null; - } - - @Nullable - @Override - public SeriesRecording getSeriesRecording(long seriesRecordingId) { - return mSeriesRecordings.get(seriesRecordingId); - } - - @Nullable - @Override - public SeriesRecording getSeriesRecording(String seriesId) { - for (SeriesRecording r : mSeriesRecordings.values()) { - if (r.getSeriesId().equals(seriesId)) { - return r; - } - } - return null; - } - - @Nullable - @Override - public RecordedProgram getRecordedProgram(long recordingId) { - return mRecordedPrograms.get(recordingId); - } - - @Override - @NonNull - protected List getRecordingsWithState(int... states) { - ArrayList result = new ArrayList<>(); - for (ScheduledRecording r : mScheduledRecordings.values()) { - for (int state : states) { - if (r.getState() == state) { - result.add(r); - break; - } - } - } - return result; - } -} diff --git a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java b/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java deleted file mode 100644 index ab464b18..00000000 --- a/tests/unit/src/com/android/tv/dvr/DvrScheduleManagerTest.java +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.MoreAsserts; -import android.util.Range; -import com.android.tv.dvr.DvrScheduleManager.ConflictInfo; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.Test; - -/** Tests for {@link DvrScheduleManager} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class DvrScheduleManagerTest { - private static final String INPUT_ID = "input_id"; - - @Test - public void testGetConflictingSchedules_emptySchedule() { - List schedules = new ArrayList<>(); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - } - - @Test - public void testGetConflictingSchedules_noConflict() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - } - - @Test - public void testGetConflictingSchedules_noTuner() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 0)); - - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L)); - assertEquals(schedules, DvrScheduleManager.getConflictingSchedules(schedules, 0)); - schedules.add( - 0, - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L)); - assertEquals(schedules, DvrScheduleManager.getConflictingSchedules(schedules, 0)); - } - - @Test - public void testGetConflictingSchedules_conflict() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r3); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - ScheduledRecording r4 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r4); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - - ScheduledRecording r5 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r5); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - - ScheduledRecording r6 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 10L, 90L); - schedules.add(r6); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - - ScheduledRecording r7 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 110L, 190L); - schedules.add(r7); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - - ScheduledRecording r8 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 50L, 150L); - schedules.add(r8); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), - r7, - r6, - r5, - r4, - r3, - r2, - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 4), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); - } - - @Test - public void testGetConflictingSchedules_conflict2() { - // The case when there is a long schedule. - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 1000L); - schedules.add(r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r3); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - } - - @Test - public void testGetConflictingSchedules_reverseOrder() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(0, r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 1)); - - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(0, r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(0, r3); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 2)); - - ScheduledRecording r4 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(0, r4); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - - ScheduledRecording r5 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(0, r5); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - - ScheduledRecording r6 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 10L, 90L); - schedules.add(0, r6); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - - ScheduledRecording r7 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 110L, 190L); - schedules.add(0, r7); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 4)); - - ScheduledRecording r8 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 50L, 150L); - schedules.add(0, r8); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), - r7, - r6, - r5, - r4, - r3, - r2, - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 2), r5, r4, r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 3), r3, r2, r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 4), r1); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 5)); - } - - @Test - public void testGetConflictingSchedules_period1() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), - r1); - } - - @Test - public void testGetConflictingSchedules_period2() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), - r1); - } - - @Test - public void testGetConflictingSchedules_period3() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r2); - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r3); - ScheduledRecording r4 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r4); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(10L, 20L))), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(110L, 120L))), - r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - schedules, 1, Collections.singletonList(new Range<>(50L, 150L))), - r2, - r1); - List> ranges = new ArrayList<>(); - ranges.add(new Range<>(10L, 20L)); - ranges.add(new Range<>(110L, 120L)); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1, ranges), r2, r1); - } - - @Test - public void testGetConflictingSchedules_addSchedules1() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 100L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) - .setPriority(++priority) - .build()), - schedules, - 1), - r2, - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) - .setPriority(++priority) - .build()), - schedules, - 1), - r1); - } - - @Test - public void testGetConflictingSchedules_addSchedules2() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 10L, 20L) - .setPriority(++priority) - .build()), - schedules, - 1), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 110L, 120L) - .setPriority(++priority) - .build()), - schedules, - 1), - r2, - r1); - } - - @Test - public void testGetConflictingSchedules_addLowestPriority() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 400L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r2); - // Returning r1 even though r1 has the higher priority than the new one. That's because r1 - // starts at 0 and stops at 100, and the new one will be recorded successfully. - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules( - Collections.singletonList( - ScheduledRecording.builder(INPUT_ID, ++channelId, 200L, 300L) - .setPriority(0) - .build()), - schedules, - 1), - r1); - } - - @Test - public void testGetConflictingSchedules_sameChannel() { - long priority = 0; - long channelId = 1; - List schedules = new ArrayList<>(); - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelId, ++priority, 0L, 200L)); - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelId, ++priority, 0L, 200L)); - MoreAsserts.assertEmpty(DvrScheduleManager.getConflictingSchedules(schedules, 3)); - } - - @Test - public void testGetConflictingSchedule_startEarlyAndFail() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 200L, 300L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 400L); - schedules.add(r2); - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 200L); - schedules.add(r3); - // r2 starts recording and fails when r3 starts. r1 is recorded successfully. - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r2); - } - - @Test - public void testGetConflictingSchedule_startLate() { - long priority = 0; - long channelId = 0; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 200L, 400L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 100L, 300L); - schedules.add(r2); - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r3); - // r2 and r1 are clipped. - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedules(schedules, 1), r2, r1); - } - - @Test - public void testGetConflictingSchedulesForTune_canTune() { - // Can tune to the recorded channel if tuner count is 1. - long priority = 0; - long channelId = 1; - List schedules = new ArrayList<>(); - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelId, ++priority, 0L, 200L)); - MoreAsserts.assertEmpty( - DvrScheduleManager.getConflictingSchedulesForTune( - INPUT_ID, channelId, 0L, priority + 1, schedules, 1)); - } - - @Test - public void testGetConflictingSchedulesForTune_cannotTune() { - // Can't tune to a channel if other channel is recording and tuner count is 1. - long priority = 0; - long channelId = 1; - List schedules = new ArrayList<>(); - schedules.add( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelId, ++priority, 0L, 200L)); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForTune( - INPUT_ID, channelId + 1, 0L, priority + 1, schedules, 1), - schedules.get(0)); - } - - @Test - public void testGetConflictingSchedulesForWatching_otherChannels() { - // The other channels are to be recorded. - long priority = 0; - long channelToWatch = 1; - long channelId = 1; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r2); - MoreAsserts.assertEmpty( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3)); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), - r1); - } - - @Test - public void testGetConflictingSchedulesForWatching_sameChannel1() { - long priority = 0; - long channelToWatch = 1; - long channelId = 1; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r2); - MoreAsserts.assertEmpty( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), - r2); - } - - @Test - public void testGetConflictingSchedulesForWatching_sameChannel2() { - long priority = 0; - long channelToWatch = 1; - long channelId = 1; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r2); - MoreAsserts.assertEmpty( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2)); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), - r1); - } - - @Test - public void testGetConflictingSchedulesForWatching_sameChannelConflict1() { - long priority = 0; - long channelToWatch = 1; - long channelId = 1; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r2); - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r3); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), - r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), - r2); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), - r2, - r1); - } - - @Test - public void testGetConflictingSchedulesForWatching_sameChannelConflict2() { - long priority = 0; - long channelToWatch = 1; - long channelId = 1; - List schedules = new ArrayList<>(); - ScheduledRecording r1 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r1); - ScheduledRecording r2 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - channelToWatch, ++priority, 0L, 200L); - schedules.add(r2); - ScheduledRecording r3 = - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, ++priority, 0L, 200L); - schedules.add(r3); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 3), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 2), - r1); - MoreAsserts.assertContentsInOrder( - DvrScheduleManager.getConflictingSchedulesForWatching( - INPUT_ID, channelToWatch, 0L, ++priority, schedules, 1), - r3, - r1); - } - - @Test - public void testPartiallyConflictingSchedules() { - long priority = 100; - long channelId = 0; - List schedules = - new ArrayList<>( - Arrays.asList( - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 0L, 400L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 0L, 200L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 200L, 500L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 400L, 600L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 700L, 800L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 600L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 800L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 800L, 900L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 750L, 850L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 300L, 450L), - RecordingTestUtils.createTestRecordingWithPriorityAndPeriod( - ++channelId, --priority, 50L, 900L))); - List conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 1); - - assertNotInList(schedules.get(0), conflicts); - assertFullConflict(schedules.get(1), conflicts); - assertPartialConflict(schedules.get(2), conflicts); - assertPartialConflict(schedules.get(3), conflicts); - assertNotInList(schedules.get(4), conflicts); - assertPartialConflict(schedules.get(5), conflicts); - assertNotInList(schedules.get(6), conflicts); - assertFullConflict(schedules.get(7), conflicts); - assertFullConflict(schedules.get(8), conflicts); - assertFullConflict(schedules.get(9), conflicts); - assertFullConflict(schedules.get(10), conflicts); - - conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 2); - - assertNotInList(schedules.get(0), conflicts); - assertNotInList(schedules.get(1), conflicts); - assertNotInList(schedules.get(2), conflicts); - assertNotInList(schedules.get(3), conflicts); - assertNotInList(schedules.get(4), conflicts); - assertNotInList(schedules.get(5), conflicts); - assertNotInList(schedules.get(6), conflicts); - assertFullConflict(schedules.get(7), conflicts); - assertFullConflict(schedules.get(8), conflicts); - assertFullConflict(schedules.get(9), conflicts); - assertPartialConflict(schedules.get(10), conflicts); - - conflicts = DvrScheduleManager.getConflictingSchedulesInfo(schedules, 3); - - assertNotInList(schedules.get(0), conflicts); - assertNotInList(schedules.get(1), conflicts); - assertNotInList(schedules.get(2), conflicts); - assertNotInList(schedules.get(3), conflicts); - assertNotInList(schedules.get(4), conflicts); - assertNotInList(schedules.get(5), conflicts); - assertNotInList(schedules.get(6), conflicts); - assertNotInList(schedules.get(7), conflicts); - assertPartialConflict(schedules.get(8), conflicts); - assertNotInList(schedules.get(9), conflicts); - assertPartialConflict(schedules.get(10), conflicts); - } - - private void assertNotInList(ScheduledRecording schedule, List conflicts) { - for (ConflictInfo conflictInfo : conflicts) { - if (conflictInfo.schedule.equals(schedule)) { - fail(schedule + " conflicts with others."); - } - } - } - - private void assertPartialConflict(ScheduledRecording schedule, List conflicts) { - for (ConflictInfo conflictInfo : conflicts) { - if (conflictInfo.schedule.equals(schedule)) { - if (conflictInfo.partialConflict) { - return; - } else { - fail(schedule + " fully conflicts with others."); - } - } - } - fail(schedule + " doesn't conflict"); - } - - private void assertFullConflict(ScheduledRecording schedule, List conflicts) { - for (ConflictInfo conflictInfo : conflicts) { - if (conflictInfo.schedule.equals(schedule)) { - if (!conflictInfo.partialConflict) { - return; - } else { - fail(schedule + " partially conflicts with others."); - } - } - } - fail(schedule + " doesn't conflict"); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java b/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java deleted file mode 100644 index bf1b4d38..00000000 --- a/tests/unit/src/com/android/tv/dvr/ScheduledRecordingTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr; - -import static com.android.tv.testing.dvr.RecordingTestUtils.createTestRecordingWithIdAndPeriod; -import static com.android.tv.testing.dvr.RecordingTestUtils.normalizePriority; -import static junit.framework.TestCase.assertEquals; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.MoreAsserts; -import android.util.Range; -import com.android.tv.data.Channel; -import com.android.tv.data.Program; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import org.junit.Test; - -/** Tests for {@link ScheduledRecordingTest} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class ScheduledRecordingTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - @Test - public void testIsOverLapping() { - ScheduledRecording r = - createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); - assertOverLapping(false, 1L, 9L, r); - - assertOverLapping(true, 1L, 20L, r); - assertOverLapping(false, 1L, 10L, r); - assertOverLapping(true, 10L, 19L, r); - assertOverLapping(true, 10L, 20L, r); - assertOverLapping(true, 11L, 20L, r); - assertOverLapping(true, 11L, 21L, r); - assertOverLapping(false, 20L, 21L, r); - - assertOverLapping(false, 21L, 29L, r); - } - - @Test - public void testBuildProgram() { - Channel c = new Channel.Builder().build(); - Program p = new Program.Builder().build(); - ScheduledRecording actual = - ScheduledRecording.builder(INPUT_ID, p).setChannelId(c.getId()).build(); - assertEquals("type", ScheduledRecording.TYPE_PROGRAM, actual.getType()); - } - - @Test - public void testBuildTime() { - ScheduledRecording actual = - createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); - assertEquals("type", ScheduledRecording.TYPE_TIMED, actual.getType()); - } - - @Test - public void testBuildFrom() { - ScheduledRecording expected = - createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L); - ScheduledRecording actual = ScheduledRecording.buildFrom(expected).build(); - RecordingTestUtils.assertRecordingEquals(expected, actual); - } - - @Test - public void testBuild_priority() { - ScheduledRecording a = - normalizePriority( - createTestRecordingWithIdAndPeriod(1, INPUT_ID, CHANNEL_ID, 10L, 20L)); - ScheduledRecording b = - normalizePriority( - createTestRecordingWithIdAndPeriod(2, INPUT_ID, CHANNEL_ID, 10L, 20L)); - ScheduledRecording c = - normalizePriority( - createTestRecordingWithIdAndPeriod(3, INPUT_ID, CHANNEL_ID, 10L, 20L)); - - // default priority - MoreAsserts.assertContentsInOrder(sortByPriority(c, b, a), a, b, c); - - // make A preferred over B - a = ScheduledRecording.buildFrom(a).setPriority(b.getPriority() + 2).build(); - MoreAsserts.assertContentsInOrder(sortByPriority(a, b, c), b, c, a); - } - - public Collection sortByPriority( - ScheduledRecording a, ScheduledRecording b, ScheduledRecording c) { - List list = Arrays.asList(a, b, c); - Collections.sort(list, ScheduledRecording.PRIORITY_COMPARATOR); - return list; - } - - private void assertOverLapping(boolean expected, long lower, long upper, ScheduledRecording r) { - assertEquals( - "isOverlapping(Range(" + lower + "," + upper + "), recording " + r, - expected, - r.isOverLapping(new Range<>(lower, upper))); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java b/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java deleted file mode 100644 index 68929e95..00000000 --- a/tests/unit/src/com/android/tv/dvr/data/SeriesRecordingTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.data; - -import static org.junit.Assert.assertEquals; - -import android.os.Build; -import android.os.Parcel; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.data.Program; -import org.junit.Test; - -/** Tests for {@link SeriesRecording}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SeriesRecordingTest { - private static final String PROGRAM_TITLE = "MyProgram"; - private static final long CHANNEL_ID = 123; - private static final long OTHER_CHANNEL_ID = 321; - private static final String SERIES_ID = "SERIES_ID"; - private static final String OTHER_SERIES_ID = "OTHER_SERIES_ID"; - - private final SeriesRecording mBaseSeriesRecording = - new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID) - .setSeriesId(SERIES_ID) - .build(); - private final SeriesRecording mSeriesRecordingSeason2 = - SeriesRecording.buildFrom(mBaseSeriesRecording).setStartFromSeason(2).build(); - private final SeriesRecording mSeriesRecordingSeason2Episode5 = - SeriesRecording.buildFrom(mSeriesRecordingSeason2).setStartFromEpisode(5).build(); - private final Program mBaseProgram = - new Program.Builder() - .setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID) - .setSeriesId(SERIES_ID) - .build(); - - @Test - public void testParcelable() { - SeriesRecording r1 = - new SeriesRecording.Builder() - .setId(1) - .setChannelId(2) - .setPriority(3) - .setTitle("4") - .setDescription("5") - .setLongDescription("5-long") - .setSeriesId("6") - .setStartFromEpisode(7) - .setStartFromSeason(8) - .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) - .setCanonicalGenreIds(new int[] {9, 10}) - .setPosterUri("11") - .setPhotoUri("12") - .build(); - Parcel p1 = Parcel.obtain(); - Parcel p2 = Parcel.obtain(); - try { - r1.writeToParcel(p1, 0); - byte[] bytes = p1.marshall(); - p2.unmarshall(bytes, 0, bytes.length); - p2.setDataPosition(0); - SeriesRecording r2 = SeriesRecording.fromParcel(p2); - assertEquals(r1, r2); - } finally { - p1.recycle(); - p2.recycle(); - } - } - - @Test - public void testDoesProgramMatch_simpleMatch() { - assertDoesProgramMatch(mBaseProgram, mBaseSeriesRecording, true); - } - - @Test - public void testDoesProgramMatch_differentSeriesId() { - Program program = new Program.Builder(mBaseProgram).setSeriesId(OTHER_SERIES_ID).build(); - assertDoesProgramMatch(program, mBaseSeriesRecording, false); - } - - @Test - public void testDoesProgramMatch_differentChannel() { - Program program = new Program.Builder(mBaseProgram).setChannelId(OTHER_CHANNEL_ID).build(); - assertDoesProgramMatch(program, mBaseSeriesRecording, false); - } - - @Test - public void testDoesProgramMatch_startFromSeason2() { - Program program = mBaseProgram; - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - program = new Program.Builder(program).setSeasonNumber("1").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, false); - program = new Program.Builder(program).setSeasonNumber("2").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - program = new Program.Builder(program).setSeasonNumber("3").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2, true); - } - - @Test - public void testDoesProgramMatch_startFromSeason2episode5() { - Program program = mBaseProgram; - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setSeasonNumber("2").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setEpisodeNumber("4").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, false); - program = new Program.Builder(program).setEpisodeNumber("5").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setEpisodeNumber("6").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - program = new Program.Builder(program).setSeasonNumber("3").setEpisodeNumber("1").build(); - assertDoesProgramMatch(program, mSeriesRecordingSeason2Episode5, true); - } - - private void assertDoesProgramMatch( - Program p, SeriesRecording seriesRecording, boolean expected) { - assertEquals( - seriesRecording + " doesProgramMatch " + p, - expected, - seriesRecording.matchProgram(p)); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java b/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java deleted file mode 100644 index 10a882f6..00000000 --- a/tests/unit/src/com/android/tv/dvr/provider/DvrDbSyncTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.provider; - -import static android.support.test.InstrumentationRegistry.getContext; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.data.Program; -import com.android.tv.dvr.DvrDataManagerImpl; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.dvr.data.SeriesRecording; -import com.android.tv.dvr.recorder.SeriesRecordingScheduler; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link com.android.tv.dvr.DvrScheduleManager} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class DvrDbSyncTest { - private static final String INPUT_ID = "input_id"; - private static final long BASE_PROGRAM_ID = 1; - private static final long BASE_START_TIME_MS = 0; - private static final long BASE_END_TIME_MS = 1; - private static final String BASE_SEASON_NUMBER = "2"; - private static final String BASE_EPISODE_NUMBER = "3"; - private static final Program BASE_PROGRAM = - new Program.Builder() - .setId(BASE_PROGRAM_ID) - .setStartTimeUtcMillis(BASE_START_TIME_MS) - .setEndTimeUtcMillis(BASE_END_TIME_MS) - .build(); - private static final Program BASE_SERIES_PROGRAM = - new Program.Builder() - .setId(BASE_PROGRAM_ID) - .setStartTimeUtcMillis(BASE_START_TIME_MS) - .setEndTimeUtcMillis(BASE_END_TIME_MS) - .setSeasonNumber(BASE_SEASON_NUMBER) - .setEpisodeNumber(BASE_EPISODE_NUMBER) - .build(); - private static final ScheduledRecording BASE_SCHEDULE = - ScheduledRecording.builder(INPUT_ID, BASE_PROGRAM).build(); - private static final ScheduledRecording BASE_SERIES_SCHEDULE = - ScheduledRecording.builder(INPUT_ID, BASE_SERIES_PROGRAM).build(); - - private DvrDbSync mDbSync; - @Mock private DvrManager mDvrManager; - @Mock private DvrDataManagerImpl mDataManager; - @Mock private ChannelDataManager mChannelDataManager; - @Mock private SeriesRecordingScheduler mSeriesRecordingScheduler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); - when(mDvrManager.addSeriesRecording(anyObject(), anyObject(), anyInt())) - .thenReturn(SeriesRecording.builder(INPUT_ID, BASE_PROGRAM).build()); - mDbSync = - new DvrDbSync( - getContext(), - mDataManager, - mChannelDataManager, - mDvrManager, - mSeriesRecordingScheduler); - } - - @Test - public void testHandleUpdateProgram_null() { - addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); - mDbSync.handleUpdateProgram(null, BASE_PROGRAM_ID); - verify(mDataManager).removeScheduledRecording(BASE_SCHEDULE); - } - - @Test - public void testHandleUpdateProgram_changeTimeNotStarted() { - addSchedule(BASE_PROGRAM_ID, BASE_SCHEDULE); - long startTimeMs = BASE_START_TIME_MS + 1; - long endTimeMs = BASE_END_TIME_MS + 1; - Program program = - new Program.Builder(BASE_PROGRAM) - .setStartTimeUtcMillis(startTimeMs) - .setEndTimeUtcMillis(endTimeMs) - .build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - assertUpdateScheduleCalled(program); - } - - @Test - public void testHandleUpdateProgram_changeTimeInProgressNotCalled() { - addSchedule( - BASE_PROGRAM_ID, - ScheduledRecording.buildFrom(BASE_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS) - .build()); - long startTimeMs = BASE_START_TIME_MS + 1; - Program program = - new Program.Builder(BASE_PROGRAM).setStartTimeUtcMillis(startTimeMs).build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - verify(mDataManager, never()).updateScheduledRecording(anyObject()); - } - - @Test - public void testHandleUpdateProgram_changeSeason() { - addSchedule(BASE_PROGRAM_ID, BASE_SERIES_SCHEDULE); - String seasonNumber = BASE_SEASON_NUMBER + "1"; - String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = - new Program.Builder(BASE_SERIES_PROGRAM) - .setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber) - .build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - assertUpdateScheduleCalled(program); - } - - @Test - public void testHandleUpdateProgram_finished() { - addSchedule( - BASE_PROGRAM_ID, - ScheduledRecording.buildFrom(BASE_SERIES_SCHEDULE) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED) - .build()); - String seasonNumber = BASE_SEASON_NUMBER + "1"; - String episodeNumber = BASE_EPISODE_NUMBER + "1"; - Program program = - new Program.Builder(BASE_SERIES_PROGRAM) - .setSeasonNumber(seasonNumber) - .setEpisodeNumber(episodeNumber) - .build(); - mDbSync.handleUpdateProgram(program, BASE_PROGRAM_ID); - verify(mDataManager, never()).updateScheduledRecording(anyObject()); - } - - private void addSchedule(long programId, ScheduledRecording schedule) { - when(mDataManager.getScheduledRecordingForProgramId(programId)).thenReturn(schedule); - } - - private void assertUpdateScheduleCalled(Program program) { - verify(mDataManager) - .updateScheduledRecording( - eq(ScheduledRecording.builder(INPUT_ID, program).build())); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java b/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java deleted file mode 100644 index a586fd9e..00000000 --- a/tests/unit/src/com/android/tv/dvr/provider/EpisodicProgramLoadTaskTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.provider; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.dvr.data.SeasonEpisodeNumber; -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; - -/** Tests for {@link EpisodicProgramLoadTask} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class EpisodicProgramLoadTaskTest { - private static final long SERIES_RECORDING_ID1 = 1; - private static final long SERIES_RECORDING_ID2 = 2; - private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; - private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; - private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; - private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; - - @Test - public void testEpisodeAlreadyScheduled_true() { - List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); - seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertTrue( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1))); - } - - @Test - public void testEpisodeAlreadyScheduled_false() { - List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); - seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber( - SERIES_RECORDING_ID2, SEASON_NUMBER1, EPISODE_NUMBER1))); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER2, EPISODE_NUMBER1))); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber( - SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER2))); - } - - @Test - public void testEpisodeAlreadyScheduled_null() { - List seasonEpisodeNumbers = new ArrayList<>(); - SeasonEpisodeNumber seasonEpisodeNumber = - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, EPISODE_NUMBER1); - seasonEpisodeNumbers.add(seasonEpisodeNumber); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, EPISODE_NUMBER1))); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, SEASON_NUMBER1, null))); - assertFalse( - seasonEpisodeNumbers.contains( - new SeasonEpisodeNumber(SERIES_RECORDING_ID1, null, null))); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java deleted file mode 100644 index f4e6cdf9..00000000 --- a/tests/unit/src/com/android/tv/dvr/recorder/InputTaskSchedulerTest.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.recorder; - -import static android.support.test.InstrumentationRegistry.getContext; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.AlarmManager; -import android.media.tv.TvInputInfo; -import android.os.Build; -import android.os.Looper; -import android.os.SystemClock; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.InputSessionManager; -import com.android.tv.data.Channel; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.WritableDvrDataManager; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.dvr.recorder.InputTaskScheduler.RecordingTaskFactory; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import com.android.tv.util.Clock; -import com.android.tv.util.TestUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link InputTaskScheduler}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class InputTaskSchedulerTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 1; - private static final long LISTENER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); - private static final int TUNER_COUNT_ONE = 1; - private static final int TUNER_COUNT_TWO = 2; - private static final long LOW_PRIORITY = 1; - private static final long HIGH_PRIORITY = 2; - - private FakeClock mFakeClock; - private InputTaskScheduler mScheduler; - @Mock private DvrManager mDvrManager; - @Mock private WritableDvrDataManager mDataManager; - @Mock private InputSessionManager mSessionManager; - @Mock private AlarmManager mMockAlarmManager; - @Mock private ChannelDataManager mChannelDataManager; - private List mRecordingTasks; - - @Before - public void setUp() throws Exception { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mRecordingTasks = new ArrayList(); - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithCurrentTime(); - TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE); - mScheduler = - new InputTaskScheduler( - getContext(), - input, - Looper.myLooper(), - mChannelDataManager, - mDvrManager, - mDataManager, - mSessionManager, - mFakeClock, - new RecordingTaskFactory() { - @Override - public RecordingTask createRecordingTask( - ScheduledRecording scheduledRecording, - Channel channel, - DvrManager dvrManager, - InputSessionManager sessionManager, - WritableDvrDataManager dataManager, - Clock clock) { - RecordingTask task = mock(RecordingTask.class); - when(task.getPriority()) - .thenReturn(scheduledRecording.getPriority()); - when(task.getEndTimeMs()) - .thenReturn(scheduledRecording.getEndTimeMs()); - mRecordingTasks.add(task); - return task; - } - }); - } - - @Test - public void testAddSchedule_past() { - ScheduledRecording r = - RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, CHANNEL_ID, 0L, 1L); - when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1)) - .changeState( - any(ScheduledRecording.class), - eq(ScheduledRecording.STATE_RECORDING_FAILED)); - } - - @Test - public void testAddSchedule_start() { - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, - CHANNEL_ID, - mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - } - - @Test - public void testAddSchedule_consecutiveNoStop() { - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - // The first schedule should not be stopped because the second one should wait for the end - // of the first schedule. - SystemClock.sleep(LISTENER_TIMEOUT_MS); - verify(mRecordingTasks.get(0), never()).stop(); - } - - @Test - public void testAddSchedule_consecutiveNoFail() { - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - when(mDataManager.getScheduledRecording(anyLong())) - .thenReturn(ScheduledRecording.builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - SystemClock.sleep(LISTENER_TIMEOUT_MS); - verify(mRecordingTasks.get(0), never()).stop(); - // The second schedule should not fail because it can starts after the first one finishes. - SystemClock.sleep(LISTENER_TIMEOUT_MS); - verify(mDataManager, never()) - .changeState( - any(ScheduledRecording.class), - eq(ScheduledRecording.STATE_RECORDING_FAILED)); - } - - @Test - public void testAddSchedule_consecutiveUseLessSession() throws Exception { - TvInputInfo input = createTvInputInfo(TUNER_COUNT_TWO); - mScheduler.updateTvInputInfo(input); - long startTimeMs = mFakeClock.currentTimeMillis(); - long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - long id = 0; - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, LOW_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - startTimeMs = endTimeMs; - endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); - mScheduler.handleAddSchedule( - RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod( - ++id, CHANNEL_ID, HIGH_PRIORITY, startTimeMs, endTimeMs)); - mScheduler.handleBuildSchedule(); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); - SystemClock.sleep(LISTENER_TIMEOUT_MS); - verify(mRecordingTasks.get(0), never()).stop(); - // The second schedule should wait until the first one finishes rather than creating a new - // session even though there are available tuners. - assertTrue(mRecordingTasks.size() == 1); - } - - @Test - public void testUpdateSchedule_noCancel() { - ScheduledRecording r = - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, - CHANNEL_ID, - mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - mScheduler.handleUpdateSchedule(r); - SystemClock.sleep(LISTENER_TIMEOUT_MS); - verify(mRecordingTasks.get(0), never()).cancel(); - } - - @Test - public void testUpdateSchedule_cancel() { - ScheduledRecording r = - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, - CHANNEL_ID, - mFakeClock.currentTimeMillis(), - mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); - mScheduler.handleAddSchedule(r); - mScheduler.handleBuildSchedule(); - mScheduler.handleUpdateSchedule( - ScheduledRecording.buildFrom(r) - .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) - .build()); - verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel(); - } - - private TvInputInfo createTvInputInfo(int tunerCount) throws Exception { - return TestUtils.createTvInputInfo(null, null, null, 0, false, true, tunerCount); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java b/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java deleted file mode 100644 index 03a4fe50..00000000 --- a/tests/unit/src/com/android/tv/dvr/recorder/RecordingTaskTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.recorder; - -import static android.support.test.InstrumentationRegistry.getContext; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.InputSessionManager; -import com.android.tv.InputSessionManager.RecordingSession; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.data.Channel; -import com.android.tv.dvr.DvrDataManagerInMemoryImpl; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.dvr.recorder.RecordingTask.State; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link RecordingTask}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class RecordingTaskTest { - private static final long DURATION = TimeUnit.MINUTES.toMillis(30); - private static final long START_OFFSET_MS = RecordingScheduler.MS_TO_WAKE_BEFORE_START; - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDataManager; - @Mock Handler mMockHandler; - @Mock DvrManager mDvrManager; - @Mock InputSessionManager mMockSessionManager; - @Mock RecordingSession mMockRecordingSession; - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - - @Before - public void setUp() { - mDvrFeature.enableForTest(); - if (Looper.myLooper() == null) { - Looper.prepare(); - } - MockitoAnnotations.initMocks(this); - mFakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); - } - - @After - public void tearDown() { - mDvrFeature.resetForTests(); - } - - @Test - public void testHandle_init() { - Channel channel = createTestChannel(); - ScheduledRecording r = createRecording(channel); - RecordingTask task = createRecordingTask(r, channel); - String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession( - eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong())) - .thenReturn(mMockRecordingSession); - when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); - assertTrue(task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE))); - assertEquals(State.CONNECTION_PENDING, task.getState()); - verify(mMockSessionManager) - .createRecordingSession( - eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong()); - verify(mMockRecordingSession).tune(eq(inputId), eq(channel.getUri())); - verifyNoMoreInteractions(mMockHandler, mMockRecordingSession, mMockSessionManager); - } - - private static Channel createTestChannel() { - return new Channel.Builder() - .setInputId(INPUT_ID) - .setId(CHANNEL_ID) - .setDisplayName("Test Ch " + CHANNEL_ID) - .build(); - } - - @Test - public void testOnConnected() { - Channel channel = createTestChannel(); - ScheduledRecording r = createRecording(channel); - mDataManager.addScheduledRecording(r); - RecordingTask task = createRecordingTask(r, channel); - String inputId = channel.getInputId(); - when(mMockSessionManager.createRecordingSession( - eq(inputId), anyString(), eq(task), eq(mMockHandler), anyLong())) - .thenReturn(mMockRecordingSession); - when(mMockHandler.sendMessageAtTime(anyObject(), anyLong())).thenReturn(true); - task.handleMessage(createMessage(RecordingTask.MSG_INITIALIZE)); - task.onTuned(channel.getUri()); - assertEquals(State.CONNECTED, task.getState()); - } - - private ScheduledRecording createRecording(Channel c) { - long startTime = mFakeClock.currentTimeMillis() + START_OFFSET_MS; - long endTime = startTime + DURATION; - return RecordingTestUtils.createTestRecordingWithPeriod( - c.getInputId(), c.getId(), startTime, endTime); - } - - private RecordingTask createRecordingTask(ScheduledRecording r, Channel channel) { - RecordingTask recordingTask = - new RecordingTask( - getContext(), - r, - channel, - mDvrManager, - mMockSessionManager, - mDataManager, - mFakeClock); - recordingTask.setHandler(mMockHandler); - return recordingTask; - } - - private Message createMessage(int what) { - Message msg = new Message(); - msg.setTarget(mMockHandler); - msg.what = what; - return msg; - } -} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java b/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java deleted file mode 100644 index dc47fc86..00000000 --- a/tests/unit/src/com/android/tv/dvr/recorder/ScheduledProgramReaperTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.recorder; - -import static android.support.test.InstrumentationRegistry.getContext; -import static org.junit.Assert.assertTrue; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.MoreAsserts; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.dvr.DvrDataManagerInMemoryImpl; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link ScheduledProgramReaper}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class ScheduledProgramReaperTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - private static final long DURATION = TimeUnit.HOURS.toMillis(1); - - private ScheduledProgramReaper mReaper; - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDvrDataManager; - @Mock private DvrManager mDvrManager; - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mDvrFeature.enableForTest(); - mFakeClock = FakeClock.createWithTimeOne(); - mDvrDataManager = new DvrDataManagerInMemoryImpl(getContext(), mFakeClock); - mReaper = new ScheduledProgramReaper(mDvrDataManager, mFakeClock); - } - - @After - public void tearDown() { - mDvrFeature.resetForTests(); - } - - @Test - public void testRun_noRecordings() { - assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty()); - mReaper.run(); - assertTrue(mDvrDataManager.getAllScheduledRecordings().isEmpty()); - } - - @Test - public void testRun_oneRecordingsTomorrow() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - mReaper.run(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - } - - @Test - public void testRun_oneRecordingsStarted() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS); - mReaper.run(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - } - - @Test - public void testRun_oneRecordingsFinished() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS); - mFakeClock.increment(TimeUnit.MINUTES, 2); - mReaper.run(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - } - - @Test - public void testRun_oneRecordingsExpired() { - ScheduledRecording recording = addNewScheduledRecordingForTomorrow(); - MoreAsserts.assertContentsInAnyOrder( - mDvrDataManager.getAllScheduledRecordings(), recording); - mFakeClock.increment(TimeUnit.DAYS, 1 + ScheduledProgramReaper.DAYS); - mFakeClock.increment(TimeUnit.MILLISECONDS, DURATION); - // After the cutoff and enough so we can see on the clock - mFakeClock.increment(TimeUnit.SECONDS, 1); - - mReaper.run(); - assertTrue( - "Recordings after reaper at " - + com.android.tv.util.Utils.toIsoDateTimeString( - mFakeClock.currentTimeMillis()), - mDvrDataManager.getAllScheduledRecordings().isEmpty()); - } - - private ScheduledRecording addNewScheduledRecordingForTomorrow() { - long startTime = mFakeClock.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); - ScheduledRecording recording = - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, CHANNEL_ID, startTime, startTime + DURATION); - return mDvrDataManager.addScheduledRecordingInternal( - ScheduledRecording.buildFrom(recording) - .setState(ScheduledRecording.STATE_RECORDING_FINISHED) - .build()); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java deleted file mode 100644 index 008e43f1..00000000 --- a/tests/unit/src/com/android/tv/dvr/recorder/SchedulerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.dvr.recorder; - -import static android.support.test.InstrumentationRegistry.getTargetContext; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.os.Build; -import android.os.Looper; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import com.android.tv.InputSessionManager; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.data.ChannelDataManager; -import com.android.tv.dvr.DvrDataManagerInMemoryImpl; -import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.data.ScheduledRecording; -import com.android.tv.testing.FakeClock; -import com.android.tv.testing.dvr.RecordingTestUtils; -import com.android.tv.util.TvInputManagerHelper; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link RecordingScheduler}. */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SchedulerTest { - private static final String INPUT_ID = "input_id"; - private static final int CHANNEL_ID = 273; - - private FakeClock mFakeClock; - private DvrDataManagerInMemoryImpl mDataManager; - private RecordingScheduler mScheduler; - @Mock DvrManager mDvrManager; - @Mock InputSessionManager mSessionManager; - @Mock AlarmManager mMockAlarmManager; - @Mock ChannelDataManager mChannelDataManager; - @Mock TvInputManagerHelper mInputManager; - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mDvrFeature.enableForTest(); - mFakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getTargetContext(), mFakeClock); - Mockito.when(mChannelDataManager.isDbLoadFinished()).thenReturn(true); - mScheduler = - new RecordingScheduler( - Looper.myLooper(), - mDvrManager, - mSessionManager, - mDataManager, - mChannelDataManager, - mInputManager, - getTargetContext(), - mFakeClock, - mMockAlarmManager); - } - - @After - public void tearDown() { - mDvrFeature.resetForTests(); - } - - @Test - public void testUpdate_none() { - mScheduler.updateAndStartServiceIfNeeded(); - verifyZeroInteractions(mMockAlarmManager); - } - - @Test - public void testUpdate_nextIn12Hours() { - long now = mFakeClock.currentTimeMillis(); - long startTime = now + TimeUnit.HOURS.toMillis(12); - ScheduledRecording r = - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, CHANNEL_ID, startTime, startTime + TimeUnit.HOURS.toMillis(1)); - mDataManager.addScheduledRecording(r); - verify(mMockAlarmManager) - .setExactAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); - Mockito.reset(mMockAlarmManager); - mScheduler.updateAndStartServiceIfNeeded(); - verify(mMockAlarmManager) - .setExactAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - eq(startTime - RecordingScheduler.MS_TO_WAKE_BEFORE_START), - any(PendingIntent.class)); - } - - @Test - public void testStartsWithin() { - long now = mFakeClock.currentTimeMillis(); - long startTime = now + 3; - ScheduledRecording r = - RecordingTestUtils.createTestRecordingWithPeriod( - INPUT_ID, CHANNEL_ID, startTime, startTime + 100); - assertFalse(mScheduler.startsWithin(r, 2)); - assertTrue(mScheduler.startsWithin(r, 3)); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java b/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java deleted file mode 100644 index c7cbff55..00000000 --- a/tests/unit/src/com/android/tv/dvr/recorder/SeriesRecordingSchedulerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.recorder; - -import static android.support.test.InstrumentationRegistry.getContext; - -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.test.MoreAsserts; -import android.util.LongSparseArray; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.common.feature.TestableFeature; -import com.android.tv.data.Program; -import com.android.tv.dvr.DvrDataManagerInMemoryImpl; -import com.android.tv.dvr.data.SeriesRecording; -import com.android.tv.testing.FakeClock; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** Tests for {@link SeriesRecordingScheduler} */ -@SmallTest -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) -public class SeriesRecordingSchedulerTest { - private static final String PROGRAM_TITLE = "MyProgram"; - private static final long CHANNEL_ID = 123; - private static final long SERIES_RECORDING_ID1 = 1; - private static final String SERIES_ID = "SERIES_ID"; - private static final String SEASON_NUMBER1 = "SEASON NUMBER1"; - private static final String SEASON_NUMBER2 = "SEASON NUMBER2"; - private static final String EPISODE_NUMBER1 = "EPISODE NUMBER1"; - private static final String EPISODE_NUMBER2 = "EPISODE NUMBER2"; - - private final SeriesRecording mBaseSeriesRecording = - new SeriesRecording.Builder() - .setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID) - .setSeriesId(SERIES_ID) - .build(); - private final Program mBaseProgram = - new Program.Builder() - .setTitle(PROGRAM_TITLE) - .setChannelId(CHANNEL_ID) - .setSeriesId(SERIES_ID) - .build(); - private final TestableFeature mDvrFeature = CommonFeatures.DVR; - - private DvrDataManagerInMemoryImpl mDataManager; - - @Before - public void setUp() { - mDvrFeature.enableForTest(); - FakeClock fakeClock = FakeClock.createWithCurrentTime(); - mDataManager = new DvrDataManagerInMemoryImpl(getContext(), fakeClock); - } - - @After - public void tearDown() { - mDvrFeature.resetForTests(); - } - - @Test - public void testPickOneProgramPerEpisode_onePerEpisode() { - SeriesRecording seriesRecording = - SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = - new Program.Builder(mBaseProgram) - .setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1) - .build(); - programs.add(program1); - Program program2 = - new Program.Builder(mBaseProgram) - .setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2) - .build(); - programs.add(program2); - LongSparseArray> result = - SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); - } - - @Test - public void testPickOneProgramPerEpisode_manyPerEpisode() { - SeriesRecording seriesRecording = - SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = - new Program.Builder(mBaseProgram) - .setSeasonNumber(SEASON_NUMBER1) - .setEpisodeNumber(EPISODE_NUMBER1) - .setStartTimeUtcMillis(0) - .build(); - programs.add(program1); - Program program2 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); - programs.add(program2); - Program program3 = - new Program.Builder(mBaseProgram) - .setSeasonNumber(SEASON_NUMBER2) - .setEpisodeNumber(EPISODE_NUMBER2) - .build(); - programs.add(program3); - Program program4 = new Program.Builder(program1).setStartTimeUtcMillis(1).build(); - programs.add(program4); - LongSparseArray> result = - SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program3); - } - - @Test - public void testPickOneProgramPerEpisode_nullEpisode() { - SeriesRecording seriesRecording = - SeriesRecording.buildFrom(mBaseSeriesRecording).setId(SERIES_RECORDING_ID1).build(); - mDataManager.addSeriesRecording(seriesRecording); - List programs = new ArrayList<>(); - Program program1 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(0).build(); - programs.add(program1); - Program program2 = new Program.Builder(mBaseProgram).setStartTimeUtcMillis(1).build(); - programs.add(program2); - LongSparseArray> result = - SeriesRecordingScheduler.pickOneProgramPerEpisode( - mDataManager, Collections.singletonList(seriesRecording), programs); - MoreAsserts.assertContentsInAnyOrder(result.get(SERIES_RECORDING_ID1), program1, program2); - } -} diff --git a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java b/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java deleted file mode 100644 index 2a1b5667..00000000 --- a/tests/unit/src/com/android/tv/dvr/ui/SortedArrayAdapterTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr.ui; - -import android.support.test.filters.SmallTest; -import android.support.v17.leanback.widget.ClassPresenterSelector; -import android.support.v17.leanback.widget.ObjectAdapter; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Objects; -import junit.framework.TestCase; -import org.junit.Before; -import org.junit.Test; - -/** Tests for {@link SortedArrayAdapter}. */ -@SmallTest -public class SortedArrayAdapterTest extends TestCase { - public static final TestData P1 = TestData.create(1, "c"); - public static final TestData P2 = TestData.create(2, "b"); - public static final TestData P3 = TestData.create(3, "a"); - public static final TestData EXTRA = TestData.create(4, "k"); - private TestSortedArrayAdapter mAdapter; - - @Before - public void setUp() { - mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, null); - } - - @Test - public void testContents_empty() { - assertEmpty(); - } - - @Test - public void testAdd_one() { - mAdapter.add(P1); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P1); - } - - @Test - public void testAdd_two() { - mAdapter.add(P1); - mAdapter.add(P2); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P2, P1); - } - - @Test - public void testSetInitialItems_two() { - mAdapter.setInitialItems(Arrays.asList(P1, P2)); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P2, P1); - } - - @Test - public void testMaxInitialCount() { - mAdapter = new TestSortedArrayAdapter(1, null); - mAdapter.setInitialItems(Arrays.asList(P1, P2)); - assertNotEmpty(); - assertEquals(mAdapter.size(), 1); - assertEquals(mAdapter.get(0), P2); - } - - @Test - public void testExtraItem() { - mAdapter = new TestSortedArrayAdapter(Integer.MAX_VALUE, EXTRA); - mAdapter.setInitialItems(Arrays.asList(P1, P2)); - assertNotEmpty(); - assertEquals(mAdapter.size(), 3); - assertEquals(mAdapter.get(0), P2); - assertEquals(mAdapter.get(2), EXTRA); - mAdapter.remove(P2); - mAdapter.remove(P1); - assertEquals(mAdapter.size(), 1); - assertEquals(mAdapter.get(0), EXTRA); - } - - @Test - public void testExtraItemWithMaxCount() { - mAdapter = new TestSortedArrayAdapter(1, EXTRA); - mAdapter.setInitialItems(Arrays.asList(P1, P2)); - assertNotEmpty(); - assertEquals(mAdapter.size(), 2); - assertEquals(mAdapter.get(0), P2); - assertEquals(mAdapter.get(1), EXTRA); - mAdapter.remove(P2); - assertEquals(mAdapter.size(), 1); - assertEquals(mAdapter.get(0), EXTRA); - } - - @Test - public void testRemove() { - mAdapter.add(P1); - mAdapter.add(P2); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P2, P1); - mAdapter.remove(P3); - assertContentsInOrder(mAdapter, P2, P1); - mAdapter.remove(P2); - assertContentsInOrder(mAdapter, P1); - mAdapter.remove(P1); - assertEmpty(); - mAdapter.add(P1); - mAdapter.add(P2); - mAdapter.add(P3); - assertContentsInOrder(mAdapter, P3, P2, P1); - mAdapter.removeItems(0, 2); - assertContentsInOrder(mAdapter, P1); - mAdapter.add(P2); - mAdapter.add(P3); - mAdapter.addExtraItem(EXTRA); - assertContentsInOrder(mAdapter, P3, P2, P1, EXTRA); - mAdapter.removeItems(1, 1); - assertContentsInOrder(mAdapter, P3, P1, EXTRA); - mAdapter.removeItems(1, 2); - assertContentsInOrder(mAdapter, P3); - mAdapter.addExtraItem(EXTRA); - mAdapter.addExtraItem(P2); - mAdapter.add(P1); - assertContentsInOrder(mAdapter, P3, P1, EXTRA, P2); - mAdapter.removeItems(1, 2); - assertContentsInOrder(mAdapter, P3, P2); - mAdapter.add(P1); - assertContentsInOrder(mAdapter, P3, P1, P2); - } - - @Test - public void testReplace() { - mAdapter.add(P1); - mAdapter.add(P2); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P2, P1); - mAdapter.replace(1, P3); - assertContentsInOrder(mAdapter, P3, P2); - mAdapter.replace(0, P1); - assertContentsInOrder(mAdapter, P2, P1); - mAdapter.addExtraItem(EXTRA); - assertContentsInOrder(mAdapter, P2, P1, EXTRA); - mAdapter.replace(2, P3); - assertContentsInOrder(mAdapter, P2, P1, P3); - } - - @Test - public void testChange_sorting() { - TestData p2_changed = TestData.create(2, "z changed"); - mAdapter.add(P1); - mAdapter.add(P2); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P2, P1); - mAdapter.change(p2_changed); - assertContentsInOrder(mAdapter, P1, p2_changed); - } - - @Test - public void testChange_new() { - mAdapter.change(P1); - assertNotEmpty(); - assertContentsInOrder(mAdapter, P1); - } - - private void assertEmpty() { - assertEquals("empty", true, mAdapter.isEmpty()); - } - - private void assertNotEmpty() { - assertEquals("empty", false, mAdapter.isEmpty()); - } - - private static void assertContentsInOrder(ObjectAdapter adapter, Object... contents) { - int ex = contents.length; - assertEquals("size", ex, adapter.size()); - for (int i = 0; i < ex; i++) { - assertEquals("element " + 1, contents[i], adapter.get(i)); - } - } - - private static class TestData { - @Override - public String toString() { - return "TestData[" + mId + "]{" + mText + '}'; - } - - static TestData create(long first, String text) { - return new TestData(first, text); - } - - private final long mId; - private final String mText; - - private TestData(long id, String second) { - this.mId = id; - this.mText = second; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof TestData)) return false; - TestData that = (TestData) o; - return mId == that.mId && Objects.equals(mText, that.mText); - } - - @Override - public int hashCode() { - return Objects.hash(mId, mText); - } - } - - private static class TestSortedArrayAdapter extends SortedArrayAdapter { - - private static final Comparator TEXT_COMPARATOR = - new Comparator() { - @Override - public int compare(TestData lhs, TestData rhs) { - return lhs.mText.compareTo(rhs.mText); - } - }; - - TestSortedArrayAdapter(int maxInitialCount, Object extra) { - super(new ClassPresenterSelector(), TEXT_COMPARATOR, maxInitialCount); - if (extra != null) { - addExtraItem((TestData) extra); - } - } - - @Override - protected long getId(TestData item) { - return item.mId; - } - } -} diff --git a/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java b/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java deleted file mode 100644 index f5fb353c..00000000 --- a/tests/unit/src/com/android/tv/experiments/ExperimentsTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.tv.experiments; - -import static org.junit.Assert.assertEquals; - -import android.support.test.filters.SmallTest; -import com.android.tv.common.BuildConfig; -import org.junit.Before; -import org.junit.Test; - -/** Tests for {@link Experiments}. */ -@SmallTest -public class ExperimentsTest { - @Before - public void setUp() { - ExperimentFlag.initForTest(); - } - - @Test - public void testEngOnlyDefault() { - assertEquals( - "ENABLE_DEVELOPER_FEATURES", - Boolean.valueOf(BuildConfig.ENG), - Experiments.ENABLE_DEVELOPER_FEATURES.get()); - } -} diff --git a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java index 4faca569..04d86bfc 100644 --- a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java +++ b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java @@ -24,7 +24,7 @@ import android.os.SystemClock; import android.support.test.filters.MediumTest; import android.text.TextUtils; import com.android.tv.BaseMainActivityTestCase; -import com.android.tv.testing.Constants; +import com.android.tv.testing.constants.Constants; import com.android.tv.testing.testinput.ChannelState; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TvTestInputConstants; diff --git a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java index d868d3f8..c2f0d0c5 100644 --- a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java +++ b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java @@ -20,7 +20,7 @@ import static android.support.test.InstrumentationRegistry.getContext; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; -import com.android.tv.testing.Utils; +import com.android.tv.testing.utils.Utils; import java.util.Random; import java.util.concurrent.TimeUnit; import org.junit.Before; diff --git a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java index af941177..6e76bf36 100644 --- a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java +++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; import com.android.tv.data.Channel; import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.recommendation.Recommender.Evaluator; -import com.android.tv.testing.Utils; +import com.android.tv.testing.utils.Utils; import java.util.ArrayList; import java.util.List; import org.junit.Before; diff --git a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java index eea42bf5..2fc4bb1c 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java +++ b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java @@ -18,7 +18,7 @@ package com.android.tv.recommendation; import android.content.Context; import com.android.tv.data.Channel; -import com.android.tv.testing.Utils; +import com.android.tv.testing.utils.Utils; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java index 062633a5..e2b0b249 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java @@ -25,7 +25,7 @@ import android.support.test.filters.SmallTest; import android.test.MoreAsserts; import com.android.tv.data.Channel; import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; -import com.android.tv.testing.Utils; +import com.android.tv.testing.utils.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,7 +46,7 @@ public class RecommenderTest { System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1); - private final Comparator CHANNEL_SORT_KEY_COMPARATOR = + private final Comparator mChannelSortKeyComparator = new Comparator() { @Override public int compare(Channel lhs, Channel rhs) { @@ -55,7 +55,7 @@ public class RecommenderTest { .compareTo(mRecommender.getChannelSortKey(rhs.getId())); } }; - private final Runnable START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS = + private final Runnable mStartDatamanagerRunnableAddFourChannels = new Runnable() { @Override public void run() { @@ -89,7 +89,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveNoScore() { - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); // Recommender doesn't recommend any channels because all channels are not recommended. assertEquals(0, mRecommender.recommendChannels().size()); @@ -102,7 +102,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveNoScore() { - createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(false, mStartDatamanagerRunnableAddFourChannels); // Recommender recommends every channel because it recommends not-recommended channels too. assertEquals(4, mRecommender.recommendChannels().size()); @@ -115,7 +115,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveScore() { - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); setChannelScores_scoreIncreasesAsChannelIdIncreases(); @@ -135,7 +135,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveScore() { - createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(false, mStartDatamanagerRunnableAddFourChannels); setChannelScores_scoreIncreasesAsChannelIdIncreases(); @@ -155,7 +155,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_includeRecommendedOnly_fewChannelsHaveScore() { - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); mEvaluator.setChannelScore(mChannel_1.getId(), 1.0); mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); @@ -175,7 +175,7 @@ public class RecommenderTest { @Test public void testRecommendChannels_notIncludeRecommendedOnly_fewChannelsHaveScore() { - createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(false, mStartDatamanagerRunnableAddFourChannels); mEvaluator.setChannelScore(mChannel_1.getId(), 1.0); mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); @@ -202,13 +202,13 @@ public class RecommenderTest { @Test public void testGetChannelSortKey_recommendAllChannels() { - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); setChannelScores_scoreIncreasesAsChannelIdIncreases(); List expectedChannelList = mRecommender.recommendChannels(); List channelList = Arrays.asList(mChannel_1, mChannel_2, mChannel_3, mChannel_4); - Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR); + Collections.sort(channelList, mChannelSortKeyComparator); // Recommended channel list and channel list sorted by sort key must be the same. MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray()); @@ -218,7 +218,7 @@ public class RecommenderTest { @Test public void testGetChannelSortKey_recommendFewChannels() { // Test with recommending 3 channels. - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); setChannelScores_scoreIncreasesAsChannelIdIncreases(); @@ -229,7 +229,7 @@ public class RecommenderTest { mRecommender.getChannelSortKey(mChannel_1.getId())); List channelList = Arrays.asList(mChannel_2, mChannel_3, mChannel_4); - Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR); + Collections.sort(channelList, mChannelSortKeyComparator); MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray()); assertSortKeyNotInvalid(channelList); @@ -237,7 +237,7 @@ public class RecommenderTest { @Test public void testListener_onRecommendationChanged() { - createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); + createRecommender(true, mStartDatamanagerRunnableAddFourChannels); // FakeEvaluator doesn't recommend a channel with empty watch log. As every channel // doesn't have a watch log, nothing is recommended and recommendation isn't changed. assertFalse(mOnRecommendationChanged); diff --git a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java index 3c972e93..a675df85 100644 --- a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java @@ -124,47 +124,47 @@ public class RoutineWatchEvaluatorTest extends EvaluatorTestCase { private static final String AUTHORITY = "com.android.tv.search"; private static final String KEYWORD = "keyword"; @@ -48,11 +49,11 @@ public class LocalSearchProviderTest extends ProviderTestCase2 providesEpgReader() { + return mApp.providesEpgReader(); + } + + @Override + public EpgFetcher getEpgFetcher() { + return mApp.getEpgFetcher(); + } + + @Override + public SetupUtils getSetupUtils() { + return mApp.getSetupUtils(); + } + + @Override + public TunerInputController getTunerInputController() { + return mApp.getTunerInputController(); + } + + @Override + public ExperimentLoader getExperimentLoader() { + return mApp.getExperimentLoader(); + } + + @Override + public MainActivityWrapper getMainActivityWrapper() { + return mApp.getMainActivityWrapper(); + } + + @Override + public com.android.tv.util.account.AccountHelper getAccountHelper() { + return mApp.getAccountHelper(); + } + + @Override + public RemoteConfig getRemoteConfig() { + return mApp.getRemoteConfig(); + } + + @Override + public Intent getTunerSetupIntent(Context context) { + return mApp.getTunerSetupIntent(context); + } + + @Override + public boolean isRunningInMainProcess() { + return mApp.isRunningInMainProcess(); + } + + @Override + public PerformanceMonitor getPerformanceMonitor() { + return mPerformanceMonitor != null ? mPerformanceMonitor : mApp.getPerformanceMonitor(); + } + + public void setPerformanceMonitor(PerformanceMonitor performanceMonitor) { + mPerformanceMonitor = performanceMonitor; + } + + @Override + public String getEmbeddedTunerInputId() { + return "com.android.tv/.tuner.tvinput.LiveTvTunerTvInputService"; + } +} diff --git a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java index 42325c83..f7c0ac2a 100644 --- a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java +++ b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java @@ -20,12 +20,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.test.MoreAsserts; import java.util.Collections; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link MultiLongSparseArray}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class MultiLongSparseArrayTest { @Test public void testEmpty() { diff --git a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java index 87ae5131..9bb69f80 100644 --- a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java +++ b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java @@ -1,14 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.tv.util; 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 org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link ScaledBitmapInfo}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class ScaledBitmapInfoTest { private static final Bitmap B80x100 = Bitmap.createBitmap(80, 100, Bitmap.Config.RGB_565); private static final Bitmap B960x1440 = Bitmap.createBitmap(960, 1440, Bitmap.Config.RGB_565); diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java index 44c4477e..168e7c6e 100644 --- a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java +++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java @@ -21,17 +21,20 @@ import static android.support.test.InstrumentationRegistry.getContext; import android.content.pm.ResolveInfo; import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.tv.testing.ComparatorTester; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** Test for {@link TvInputManagerHelper} */ @SmallTest +@RunWith(AndroidJUnit4.class) public class TvInputManagerHelperTest { final HashMap TEST_INPUT_MAP = new HashMap<>(); diff --git a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java index e60aae05..c4623bc7 100644 --- a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java +++ b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java @@ -20,15 +20,18 @@ import static org.junit.Assert.assertEquals; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import com.android.tv.testing.ComparatorTester; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link com.android.tv.util.TvTrackInfoUtils}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class TvTrackInfoUtilsTest { private static final String UN_MATCHED_ID = "no matching ID"; diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java index f5eefc64..0be1026f 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java @@ -19,6 +19,7 @@ import static android.support.test.InstrumentationRegistry.getContext; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.text.format.DateUtils; import java.util.Calendar; import java.util.GregorianCalendar; @@ -26,6 +27,7 @@ import java.util.Locale; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Tests for {@link com.android.tv.util.Utils#getDurationString}. @@ -36,6 +38,7 @@ import org.junit.Test; * defined in TV app, not this test. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class UtilsTest_GetDurationString { // TODO: Mock Context so we can specify current time and locale for test. private Locale mLocale; diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java index 79ed14c0..affe7d2f 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java @@ -22,10 +22,13 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.media.tv.TvTrackInfo; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link com.android.tv.util.Utils#getMultiAudioString}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class UtilsTest_GetMultiAudioString { private static final String TRACK_ID = "test_track_id"; private static final int AUDIO_SAMPLE_RATE = 48000; diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java index cf0212cb..15f15d1c 100644 --- a/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java +++ b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java @@ -20,13 +20,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; import org.junit.Test; +import org.junit.runner.RunWith; /** Tests for {@link com.android.tv.util.Utils#isInGivenDay}. */ @SmallTest +@RunWith(AndroidJUnit4.class) public class UtilsTest_IsInGivenDay { @Test public void testIsInGivenDay() { diff --git a/tuner/Android.mk b/tuner/Android.mk new file mode 100644 index 00000000..8bf51b5c --- /dev/null +++ b/tuner/Android.mk @@ -0,0 +1,47 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# Include all java and proto files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-proto-files-under, proto) + + +LOCAL_MODULE := live-tv-tuner +LOCAL_MODULE_CLASS := STATIC_JAVA_LIBRARIES +LOCAL_MODULE_TAGS := optional +LOCAL_SDK_VERSION := system_current + +LOCAL_USE_AAPT2 := true + +LOCAL_PROTOC_OPTIMIZE_TYPE := nano +LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/ +LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java + +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/res \ + $(TOP)/prebuilts/sdk/current/support/v17/leanback/res \ + +LOCAL_STATIC_JAVA_LIBRARIES := \ + tv-common \ + lib-exoplayer \ + lib-exoplayer-v2 \ + lib-exoplayer-v2-core \ + android-support-annotations \ + android-support-compat \ + android-support-core-ui \ + android-support-tv-provider \ + android-support-v7-palette \ + android-support-v7-recyclerview \ + android-support-v17-leanback \ + android-support-tv-provider \ + javax-annotations-jar \ + +LOCAL_AAPT_FLAGS := --auto-add-overlay \ + --extra-packages android.support.v17.leanback \ + --extra-packages com.android.tv.common \ + +include $(LOCAL_PATH)/buildconfig.mk + +include $(BUILD_STATIC_JAVA_LIBRARY) + diff --git a/tuner/AndroidManifest.xml b/tuner/AndroidManifest.xml new file mode 100644 index 00000000..12443cde --- /dev/null +++ b/tuner/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/tuner/BuildConfig.java.in b/tuner/BuildConfig.java.in new file mode 100644 index 00000000..85967fad --- /dev/null +++ b/tuner/BuildConfig.java.in @@ -0,0 +1,8 @@ +/* This file is auto generated. Do not modify. */ +package com.android.tv.tuner; + +public final class BuildConfig { + public static final boolean DEBUG = %DEBUG%; + public static final boolean ENG = %ENG%; + private BuildConfig() {} +} \ No newline at end of file diff --git a/tuner/buildconfig.mk b/tuner/buildconfig.mk new file mode 100644 index 00000000..cece7f2b --- /dev/null +++ b/tuner/buildconfig.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Emulate gradles BuildConfig.java + +ifeq "$(TARGET_BUILD_VARIANT)" "eng" + BC_DEBUG_STATUS := true +else ifeq "$(TARGET_BUILD_VARIANT)" "userdebug" + BC_DEBUG_STATUS := true +else + BC_DEBUG_STATUS := false +endif + +ifeq "$(TARGET_BUILD_VARIANT)" "eng" + BC_ENG_STATUS := true +else + BC_ENG_STATUS := false +endif + +gen := $(local-generated-sources-dir)/$(TARGET_BUILD_VARIANT)/BuildConfig.java +$(gen): PRIVATE_CUSTOM_TOOL = sed -e \ + 's/%DEBUG%/$(BC_DEBUG_STATUS)/;s/%ENG%/$(BC_ENG_STATUS)/' \ + $< > $@ +$(gen) : $(LOCAL_PATH)/BuildConfig.java.in + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) \ No newline at end of file diff --git a/tuner/proto/channel.proto b/tuner/proto/channel.proto new file mode 100644 index 00000000..1f994522 --- /dev/null +++ b/tuner/proto/channel.proto @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = 'proto2'; + +package com.android.tv.tuner.data; + +option java_package = "com.android.tv.tuner.data"; +option java_outer_classname = "Channel"; + +import "track.proto"; + +// Holds information about a channel used in the tuners. +message TunerChannelProto { + optional TunerType type = 1; + optional string short_name = 2; + optional string long_name = 3; + optional int32 frequency = 4; + optional string modulation = 5; + optional string filepath = 6; + optional int32 program_number = 7; + optional int32 virtual_major = 8; + optional int32 virtual_minor = 9; + optional int64 channel_id = 10; + optional string description = 11; + optional int32 tsid = 12; + optional int32 video_pid = 13; + optional VideoStreamType video_stream_type = 14; + optional int32 pcr_pid = 15; + repeated AtscAudioTrack audio_tracks = 16; + repeated int32 audio_pids = 17; + repeated AudioStreamType audio_stream_types = 18; + optional int32 audio_track_index = 19; + repeated AtscCaptionTrack caption_tracks = 20; + optional bool has_caption_track = 21; + optional AtscServiceType service_type = 22 [default = SERVICE_TYPE_ATSC_DIGITAL_TELEVISION]; + optional bool recording_prohibited = 23; + optional string video_format = 24; + /** + * The flag indicating whether this TV channel is locked or not. + * This is primarily used for alternative parental control to prevent unauthorized users from + * watching the current channel regardless of the content rating + * @see link + */ + optional bool locked = 25; +} + +// Enum describing the types of tuner. +enum TunerType { + TYPE_TUNER = 0; + TYPE_FILE = 1; + TYPE_NETWORK = 2; +} + +// Enum describing the types of video stream. +enum VideoStreamType { + // ISO/IEC 11172 Video (MPEG-1) + MPEG1 = 0x01; + // ISO/IEC 13818-2 (MPEG-2) Video + MPEG2 = 0x02; + // ISO/IEC 14496-2 (MPEG-4 H.263 based) + H263 = 0x10; + // ISO/IE 14496-10 (H.264 video) + H264 = 0x01b; + // ISO/IE 23008-2 (H.265 video) + H265 = 0x024; +} + +// Enum describing the types of audio stream. +enum AudioStreamType { + // ISO/IEC 11172 Audio (MPEG-1) + MPEG1AUDIO = 0x03; + // ISO/IEC 13818-3 Audio (MPEG-2) + MPEG2AUDIO = 0x04; + // ISO/IEC 13818-7 Audio with ADTS transport syntax + MPEG2AACAUDIO = 0x0f; + // ISO/IEC 14496-3 (MPEG-4 LOAS multi-format framed audio) + MPEG4LATMAACAUDIO = 0x11; + // Dolby Digital Audio (ATSC) + A52AC3AUDIO = 0x81; + // Dolby Digital Plus Audio (ATSC)ISO/IEC 14496-2Video (MPEG-1) + EAC3AUDIO = 0x87; +} + +// Enum describing ATSC service types +// See ATSC Code Points Registry. +enum AtscServiceType { + SERVICE_TYPE_ATSC_RESERVED = 0x0; + SERVICE_TYPE_ANALOG_TELEVISION_CHANNELS = 0x1; + SERVICE_TYPE_ATSC_DIGITAL_TELEVISION = 0x2; + SERVICE_TYPE_ATSC_AUDIO = 0x3; + SERVICE_TYPE_ATSC_DATA_ONLY_SERVICE = 0x4; + SERVICE_TYPE_SOFTWARE_DOWNLOAD = 0x5; + SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE = 0x6; + SERVICE_TYPE_PARAMETERIZED_SERVICE = 0x7; + SERVICE_TYPE_ATSC_NRT_SERVICE = 0x8; + SERVICE_TYPE_EXTENDED_PARAMERTERIZED_SERVICE = 0x9; +} diff --git a/tuner/proto/track.proto b/tuner/proto/track.proto new file mode 100644 index 00000000..fe60fed5 --- /dev/null +++ b/tuner/proto/track.proto @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.tv.tuner.data; + +option java_package = "com.android.tv.tuner.data"; +option java_outer_classname = "Track"; + +// Represents a AC3 audio track. +message AtscAudioTrack { + optional string language = 1; + optional AudioType audio_type = 2; + optional int32 index = 3; + optional int32 channel_count = 4; + optional int32 sample_rate = 5; + + // Enum describing the types of a audio track. + // See ISO/IEC 138181-1:2000(e) Table 2-53. + enum AudioType { + AUDIOTYPE_UNDEFINED = 0; + AUDIOTYPE_CLEAN_EFFECTS = 1; + AUDIOTYPE_HEARING_IMPAIRED = 2; + AUDIOTYPE_VISUAL_IMPAIRED = 3; + } +} + +// Represents a CEA-708 caption track. +message AtscCaptionTrack { + optional string language = 1; + optional int32 service_number = 2; + optional bool easy_reader = 3; + optional bool wide_aspect_ratio = 4; +} + diff --git a/tuner/res/drawable-xhdpi/recommendation_antenna.png b/tuner/res/drawable-xhdpi/recommendation_antenna.png new file mode 100644 index 00000000..c4710bcc Binary files /dev/null and b/tuner/res/drawable-xhdpi/recommendation_antenna.png differ diff --git a/tuner/res/drawable-xhdpi/usb_antenna.png b/tuner/res/drawable-xhdpi/usb_antenna.png new file mode 100644 index 00000000..ca5b2d72 Binary files /dev/null and b/tuner/res/drawable-xhdpi/usb_antenna.png differ diff --git a/tuner/res/drawable/ut_scan_progress.xml b/tuner/res/drawable/ut_scan_progress.xml new file mode 100644 index 00000000..f4daf6c8 --- /dev/null +++ b/tuner/res/drawable/ut_scan_progress.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tuner/res/layout/guided_action_editable.xml b/tuner/res/layout/guided_action_editable.xml new file mode 100644 index 00000000..84f56f8d --- /dev/null +++ b/tuner/res/layout/guided_action_editable.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tuner/res/layout/ut_channel_list.xml b/tuner/res/layout/ut_channel_list.xml new file mode 100644 index 00000000..430234f8 --- /dev/null +++ b/tuner/res/layout/ut_channel_list.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/tuner/res/layout/ut_channel_scan.xml b/tuner/res/layout/ut_channel_scan.xml new file mode 100644 index 00000000..415ac929 --- /dev/null +++ b/tuner/res/layout/ut_channel_scan.xml @@ -0,0 +1,117 @@ + + + + + + + + + + +